/mandos/trunk

To get this branch, use:
bzr branch http://bzr.recompile.se/loggerhead/mandos/trunk

« back to all changes in this revision

Viewing changes to plugbasedclient.c

  • Committer: Björn Påhlsson
  • Date: 2008-07-20 02:52:20 UTC
  • Revision ID: belorn@braxen-20080720025220-r5u0388uy9iu23h6
Added following support:
Pluginbased client handler
rewritten Mandos client
       Avahi instead of udp server discovery
       openpgp encrypted key support
Passprompt stand alone application for direct console input
Added logging for Mandos server

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/*  -*- coding: utf-8; mode: c; mode: orgtbl -*- */
2
 
/*
3
 
 * Mandos plugin runner - Run Mandos plugins
4
 
 *
5
 
 * Copyright © 2008-2013 Teddy Hogeborn
6
 
 * Copyright © 2008-2013 Björn Påhlsson
7
 
 * 
8
 
 * This program is free software: you can redistribute it and/or
9
 
 * modify it under the terms of the GNU General Public License as
10
 
 * published by the Free Software Foundation, either version 3 of the
11
 
 * License, or (at your option) any later version.
12
 
 * 
13
 
 * This program is distributed in the hope that it will be useful, but
14
 
 * WITHOUT ANY WARRANTY; without even the implied warranty of
15
 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16
 
 * General Public License for more details.
17
 
 * 
18
 
 * You should have received a copy of the GNU General Public License
19
 
 * along with this program.  If not, see
20
 
 * <http://www.gnu.org/licenses/>.
21
 
 * 
22
 
 * Contact the authors at <mandos@recompile.se>.
23
 
 */
24
 
 
25
 
#define _GNU_SOURCE             /* TEMP_FAILURE_RETRY(), getline(),
26
 
                                   asprintf(), O_CLOEXEC */
27
 
#include <stddef.h>             /* size_t, NULL */
28
 
#include <stdlib.h>             /* malloc(), exit(), EXIT_SUCCESS,
29
 
                                   realloc() */
30
 
#include <stdbool.h>            /* bool, true, false */
31
 
#include <stdio.h>              /* fileno(), fprintf(),
32
 
                                   stderr, STDOUT_FILENO */
33
 
#include <sys/types.h>          /* DIR, fdopendir(), stat(), struct
34
 
                                   stat, waitpid(), WIFEXITED(),
35
 
                                   WEXITSTATUS(), wait(), pid_t,
36
 
                                   uid_t, gid_t, getuid(), getgid(),
37
 
                                   dirfd() */
38
 
#include <sys/select.h>         /* fd_set, select(), FD_ZERO(),
39
 
                                   FD_SET(), FD_ISSET(), FD_CLR */
40
 
#include <sys/wait.h>           /* wait(), waitpid(), WIFEXITED(),
41
 
                                   WEXITSTATUS(), WTERMSIG(),
42
 
                                   WCOREDUMP() */
43
 
#include <sys/stat.h>           /* struct stat, stat(), S_ISREG() */
44
 
#include <iso646.h>             /* and, or, not */
45
 
#include <dirent.h>             /* DIR, struct dirent, fdopendir(),
46
 
                                   readdir(), closedir(), dirfd() */
47
 
#include <unistd.h>             /* struct stat, stat(), S_ISREG(),
48
 
                                   fcntl(), setuid(), setgid(),
49
 
                                   F_GETFD, F_SETFD, FD_CLOEXEC,
50
 
                                   access(), pipe(), fork(), close()
51
 
                                   dup2(), STDOUT_FILENO, _exit(),
52
 
                                   execve(), write(), read(),
53
 
                                   close() */
54
 
#include <fcntl.h>              /* fcntl(), F_GETFD, F_SETFD,
55
 
                                   FD_CLOEXEC */
56
 
#include <string.h>             /* strsep, strlen(), asprintf(),
57
 
                                   strsignal(), strcmp(), strncmp() */
58
 
#include <errno.h>              /* errno */
59
 
#include <argp.h>               /* struct argp_option, struct
60
 
                                   argp_state, struct argp,
61
 
                                   argp_parse(), ARGP_ERR_UNKNOWN,
62
 
                                   ARGP_KEY_END, ARGP_KEY_ARG,
63
 
                                   error_t */
64
 
#include <signal.h>             /* struct sigaction, sigemptyset(),
65
 
                                   sigaddset(), sigaction(),
66
 
                                   sigprocmask(), SIG_BLOCK, SIGCHLD,
67
 
                                   SIG_UNBLOCK, kill(), sig_atomic_t
68
 
                                */
69
 
#include <errno.h>              /* errno, EBADF */
70
 
#include <inttypes.h>           /* intmax_t, PRIdMAX, strtoimax() */
71
 
#include <sysexits.h>           /* EX_OSERR, EX_USAGE, EX_IOERR,
72
 
                                   EX_CONFIG, EX_UNAVAILABLE, EX_OK */
73
 
#include <errno.h>              /* errno */
74
 
#include <error.h>              /* error() */
75
 
 
76
 
#define BUFFER_SIZE 256
77
 
 
78
 
#define PDIR "/lib/mandos/plugins.d"
79
 
#define AFILE "/conf/conf.d/mandos/plugin-runner.conf"
80
 
 
81
 
const char *argp_program_version = "plugin-runner " VERSION;
82
 
const char *argp_program_bug_address = "<mandos@recompile.se>";
83
 
 
84
 
typedef struct plugin{
85
 
  char *name;                   /* can be NULL or any plugin name */
86
 
  char **argv;
87
 
  int argc;
88
 
  char **environ;
89
 
  int envc;
90
 
  bool disabled;
91
 
  
92
 
  /* Variables used for running processes*/
 
1
#include <stdio.h>      /* popen, fileno */
 
2
#include <iso646.h>     /* and, or, not */
 
3
#include <sys/types.h>  /* DIR, opendir, stat, struct stat, waitpid,
 
4
                           WIFEXITED, WEXITSTATUS, wait */
 
5
#include <sys/wait.h>   /* wait */
 
6
#include <dirent.h>     /* DIR, opendir */
 
7
#include <sys/stat.h>   /* stat, struct stat */
 
8
#include <unistd.h>     /* stat, struct stat, chdir */
 
9
#include <stdlib.h>     /* EXIT_FAILURE */
 
10
#include <sys/select.h> /* fd_set, select, FD_ZERO, FD_SET, FD_ISSET */
 
11
#include <string.h>     /* strlen, strcpy, strcat */
 
12
#include <stdbool.h>    /* true */
 
13
#include <sys/wait.h>   /* waitpid, WIFEXITED, WEXITSTATUS */
 
14
#include <errno.h>      /* errno */
 
15
 
 
16
struct process;
 
17
 
 
18
typedef struct process{
93
19
  pid_t pid;
94
20
  int fd;
95
21
  char *buffer;
96
 
  size_t buffer_size;
97
 
  size_t buffer_length;
98
 
  bool eof;
99
 
  volatile sig_atomic_t completed;
100
 
  int status;
101
 
  struct plugin *next;
102
 
} plugin;
103
 
 
104
 
static plugin *plugin_list = NULL;
105
 
 
106
 
/* Gets an existing plugin based on name,
107
 
   or if none is found, creates a new one */
108
 
__attribute__((warn_unused_result))
109
 
static plugin *getplugin(char *name){
110
 
  /* Check for existing plugin with that name */
111
 
  for(plugin *p = plugin_list; p != NULL; p = p->next){
112
 
    if((p->name == name)
113
 
       or (p->name and name and (strcmp(p->name, name) == 0))){
114
 
      return p;
115
 
    }
116
 
  }
117
 
  /* Create a new plugin */
118
 
  plugin *new_plugin = NULL;
119
 
  do {
120
 
    new_plugin = malloc(sizeof(plugin));
121
 
  } while(new_plugin == NULL and errno == EINTR);
122
 
  if(new_plugin == NULL){
123
 
    return NULL;
124
 
  }
125
 
  char *copy_name = NULL;
126
 
  if(name != NULL){
127
 
    do {
128
 
      copy_name = strdup(name);
129
 
    } while(copy_name == NULL and errno == EINTR);
130
 
    if(copy_name == NULL){
131
 
      int e = errno;
132
 
      free(new_plugin);
133
 
      errno = e;
134
 
      return NULL;
135
 
    }
136
 
  }
137
 
  
138
 
  *new_plugin = (plugin){ .name = copy_name,
139
 
                          .argc = 1,
140
 
                          .disabled = false,
141
 
                          .next = plugin_list };
142
 
  
143
 
  do {
144
 
    new_plugin->argv = malloc(sizeof(char *) * 2);
145
 
  } while(new_plugin->argv == NULL and errno == EINTR);
146
 
  if(new_plugin->argv == NULL){
147
 
    int e = errno;
148
 
    free(copy_name);
149
 
    free(new_plugin);
150
 
    errno = e;
151
 
    return NULL;
152
 
  }
153
 
  new_plugin->argv[0] = copy_name;
154
 
  new_plugin->argv[1] = NULL;
155
 
  
156
 
  do {
157
 
    new_plugin->environ = malloc(sizeof(char *));
158
 
  } while(new_plugin->environ == NULL and errno == EINTR);
159
 
  if(new_plugin->environ == NULL){
160
 
    int e = errno;
161
 
    free(copy_name);
162
 
    free(new_plugin->argv);
163
 
    free(new_plugin);
164
 
    errno = e;
165
 
    return NULL;
166
 
  }
167
 
  new_plugin->environ[0] = NULL;
168
 
  
169
 
  /* Append the new plugin to the list */
170
 
  plugin_list = new_plugin;
171
 
  return new_plugin;
172
 
}
173
 
 
174
 
/* Helper function for add_argument and add_environment */
175
 
__attribute__((nonnull, warn_unused_result))
176
 
static bool add_to_char_array(const char *new, char ***array,
177
 
                              int *len){
178
 
  /* Resize the pointed-to array to hold one more pointer */
179
 
  char **new_array = NULL;
180
 
  do {
181
 
    new_array = realloc(*array, sizeof(char *)
182
 
                        * (size_t) ((*len) + 2));
183
 
  } while(new_array == NULL and errno == EINTR);
184
 
  /* Malloc check */
185
 
  if(new_array == NULL){
186
 
    return false;
187
 
  }
188
 
  *array = new_array;
189
 
  /* Make a copy of the new string */
190
 
  char *copy;
191
 
  do {
192
 
    copy = strdup(new);
193
 
  } while(copy == NULL and errno == EINTR);
194
 
  if(copy == NULL){
195
 
    return false;
196
 
  }
197
 
  /* Insert the copy */
198
 
  (*array)[*len] = copy;
199
 
  (*len)++;
200
 
  /* Add a new terminating NULL pointer to the last element */
201
 
  (*array)[*len] = NULL;
202
 
  return true;
203
 
}
204
 
 
205
 
/* Add to a plugin's argument vector */
206
 
__attribute__((nonnull(2), warn_unused_result))
207
 
static bool add_argument(plugin *p, const char *arg){
208
 
  if(p == NULL){
209
 
    return false;
210
 
  }
211
 
  return add_to_char_array(arg, &(p->argv), &(p->argc));
212
 
}
213
 
 
214
 
/* Add to a plugin's environment */
215
 
__attribute__((nonnull(2), warn_unused_result))
216
 
static bool add_environment(plugin *p, const char *def, bool replace){
217
 
  if(p == NULL){
218
 
    return false;
219
 
  }
220
 
  /* namelen = length of name of environment variable */
221
 
  size_t namelen = (size_t)(strchrnul(def, '=') - def);
222
 
  /* Search for this environment variable */
223
 
  for(char **envdef = p->environ; *envdef != NULL; envdef++){
224
 
    if(strncmp(*envdef, def, namelen + 1) == 0){
225
 
      /* It already exists */
226
 
      if(replace){
227
 
        char *new_envdef;
228
 
        do {
229
 
          new_envdef = realloc(*envdef, strlen(def) + 1);
230
 
        } while(new_envdef == NULL and errno == EINTR);
231
 
        if(new_envdef == NULL){
232
 
          return false;
233
 
        }
234
 
        *envdef = new_envdef;
235
 
        strcpy(*envdef, def);
236
 
      }
237
 
      return true;
238
 
    }
239
 
  }
240
 
  return add_to_char_array(def, &(p->environ), &(p->envc));
241
 
}
242
 
 
243
 
/*
244
 
 * Based on the example in the GNU LibC manual chapter 13.13 "File
245
 
 * Descriptor Flags".
246
 
 | [[info:libc:Descriptor%20Flags][File Descriptor Flags]] |
247
 
 */
248
 
__attribute__((warn_unused_result))
249
 
static int set_cloexec_flag(int fd){
250
 
  int ret = (int)TEMP_FAILURE_RETRY(fcntl(fd, F_GETFD, 0));
251
 
  /* If reading the flags failed, return error indication now. */
252
 
  if(ret < 0){
253
 
    return ret;
254
 
  }
255
 
  /* Store modified flag word in the descriptor. */
256
 
  return (int)TEMP_FAILURE_RETRY(fcntl(fd, F_SETFD,
257
 
                                       ret | FD_CLOEXEC));
258
 
}
259
 
 
260
 
 
261
 
/* Mark processes as completed when they exit, and save their exit
262
 
   status. */
263
 
static void handle_sigchld(__attribute__((unused)) int sig){
264
 
  int old_errno = errno;
265
 
  while(true){
266
 
    plugin *proc = plugin_list;
267
 
    int status;
268
 
    pid_t pid = waitpid(-1, &status, WNOHANG);
269
 
    if(pid == 0){
270
 
      /* Only still running child processes */
271
 
      break;
272
 
    }
273
 
    if(pid == -1){
274
 
      if(errno == ECHILD){
275
 
        /* No child processes */
276
 
        break;
277
 
      }
278
 
      error(0, errno, "waitpid");
279
 
    }
280
 
    
281
 
    /* A child exited, find it in process_list */
282
 
    while(proc != NULL and proc->pid != pid){
283
 
      proc = proc->next;
284
 
    }
285
 
    if(proc == NULL){
286
 
      /* Process not found in process list */
287
 
      continue;
288
 
    }
289
 
    proc->status = status;
290
 
    proc->completed = 1;
291
 
  }
292
 
  errno = old_errno;
293
 
}
294
 
 
295
 
/* Prints out a password to stdout */
296
 
__attribute__((nonnull, warn_unused_result))
297
 
static bool print_out_password(const char *buffer, size_t length){
298
 
  ssize_t ret;
299
 
  for(size_t written = 0; written < length; written += (size_t)ret){
300
 
    ret = TEMP_FAILURE_RETRY(write(STDOUT_FILENO, buffer + written,
301
 
                                   length - written));
302
 
    if(ret < 0){
303
 
      return false;
304
 
    }
305
 
  }
306
 
  return true;
307
 
}
308
 
 
309
 
/* Removes and free a plugin from the plugin list */
310
 
__attribute__((nonnull))
311
 
static void free_plugin(plugin *plugin_node){
312
 
  
313
 
  for(char **arg = plugin_node->argv; *arg != NULL; arg++){
314
 
    free(*arg);
315
 
  }
316
 
  free(plugin_node->argv);
317
 
  for(char **env = plugin_node->environ; *env != NULL; env++){
318
 
    free(*env);
319
 
  }
320
 
  free(plugin_node->environ);
321
 
  free(plugin_node->buffer);
322
 
  
323
 
  /* Removes the plugin from the singly-linked list */
324
 
  if(plugin_node == plugin_list){
325
 
    /* First one - simple */
326
 
    plugin_list = plugin_list->next;
327
 
  } else {
328
 
    /* Second one or later */
329
 
    for(plugin *p = plugin_list; p != NULL; p = p->next){
330
 
      if(p->next == plugin_node){
331
 
        p->next = plugin_node->next;
332
 
        break;
333
 
      }
334
 
    }
335
 
  }
336
 
  
337
 
  free(plugin_node);
338
 
}
339
 
 
340
 
static void free_plugin_list(void){
341
 
  while(plugin_list != NULL){
342
 
    free_plugin(plugin_list);
343
 
  }
344
 
}
 
22
  int buffer_size;
 
23
  int buffer_length;
 
24
  struct process *next;
 
25
} process;
 
26
 
 
27
#define BUFFER_SIZE 256
345
28
 
346
29
int main(int argc, char *argv[]){
347
 
  char *plugindir = NULL;
348
 
  char *argfile = NULL;
349
 
  FILE *conffp;
350
 
  size_t d_name_len;
351
 
  DIR *dir = NULL;
 
30
  char plugindir[] = "plugins.d";
 
31
  size_t d_name_len, plugindir_len = sizeof(plugindir)-1;
 
32
  DIR *dir;
352
33
  struct dirent *dirst;
353
34
  struct stat st;
354
 
  fd_set rfds_all;
 
35
  fd_set rfds_orig;
355
36
  int ret, maxfd = 0;
356
 
  ssize_t sret;
357
 
  uid_t uid = 65534;
358
 
  gid_t gid = 65534;
359
 
  bool debug = false;
360
 
  int exitstatus = EXIT_SUCCESS;
361
 
  struct sigaction old_sigchld_action;
362
 
  struct sigaction sigchld_action = { .sa_handler = handle_sigchld,
363
 
                                      .sa_flags = SA_NOCLDSTOP };
364
 
  char **custom_argv = NULL;
365
 
  int custom_argc = 0;
366
 
  
367
 
  /* Establish a signal handler */
368
 
  sigemptyset(&sigchld_action.sa_mask);
369
 
  ret = sigaddset(&sigchld_action.sa_mask, SIGCHLD);
370
 
  if(ret == -1){
371
 
    error(0, errno, "sigaddset");
372
 
    exitstatus = EX_OSERR;
373
 
    goto fallback;
374
 
  }
375
 
  ret = sigaction(SIGCHLD, &sigchld_action, &old_sigchld_action);
376
 
  if(ret == -1){
377
 
    error(0, errno, "sigaction");
378
 
    exitstatus = EX_OSERR;
379
 
    goto fallback;
380
 
  }
381
 
  
382
 
  /* The options we understand. */
383
 
  struct argp_option options[] = {
384
 
    { .name = "global-options", .key = 'g',
385
 
      .arg = "OPTION[,OPTION[,...]]",
386
 
      .doc = "Options passed to all plugins" },
387
 
    { .name = "global-env", .key = 'G',
388
 
      .arg = "VAR=value",
389
 
      .doc = "Environment variable passed to all plugins" },
390
 
    { .name = "options-for", .key = 'o',
391
 
      .arg = "PLUGIN:OPTION[,OPTION[,...]]",
392
 
      .doc = "Options passed only to specified plugin" },
393
 
    { .name = "env-for", .key = 'E',
394
 
      .arg = "PLUGIN:ENV=value",
395
 
      .doc = "Environment variable passed to specified plugin" },
396
 
    { .name = "disable", .key = 'd',
397
 
      .arg = "PLUGIN",
398
 
      .doc = "Disable a specific plugin", .group = 1 },
399
 
    { .name = "enable", .key = 'e',
400
 
      .arg = "PLUGIN",
401
 
      .doc = "Enable a specific plugin", .group = 1 },
402
 
    { .name = "plugin-dir", .key = 128,
403
 
      .arg = "DIRECTORY",
404
 
      .doc = "Specify a different plugin directory", .group = 2 },
405
 
    { .name = "config-file", .key = 129,
406
 
      .arg = "FILE",
407
 
      .doc = "Specify a different configuration file", .group = 2 },
408
 
    { .name = "userid", .key = 130,
409
 
      .arg = "ID", .flags = 0,
410
 
      .doc = "User ID the plugins will run as", .group = 3 },
411
 
    { .name = "groupid", .key = 131,
412
 
      .arg = "ID", .flags = 0,
413
 
      .doc = "Group ID the plugins will run as", .group = 3 },
414
 
    { .name = "debug", .key = 132,
415
 
      .doc = "Debug mode", .group = 4 },
416
 
    /*
417
 
     * These reproduce what we would get without ARGP_NO_HELP
418
 
     */
419
 
    { .name = "help", .key = '?',
420
 
      .doc = "Give this help list", .group = -1 },
421
 
    { .name = "usage", .key = -3,
422
 
      .doc = "Give a short usage message", .group = -1 },
423
 
    { .name = "version", .key = 'V',
424
 
      .doc = "Print program version", .group = -1 },
425
 
    { .name = NULL }
426
 
  };
427
 
  
428
 
  __attribute__((nonnull(3)))
429
 
  error_t parse_opt(int key, char *arg, struct argp_state *state){
430
 
    errno = 0;
431
 
    switch(key){
432
 
      char *tmp;
433
 
      intmax_t tmp_id;
434
 
    case 'g':                   /* --global-options */
435
 
      {
436
 
        char *plugin_option;
437
 
        while((plugin_option = strsep(&arg, ",")) != NULL){
438
 
          if(not add_argument(getplugin(NULL), plugin_option)){
439
 
            break;
440
 
          }
441
 
        }
442
 
        errno = 0;
443
 
      }
444
 
      break;
445
 
    case 'G':                   /* --global-env */
446
 
      if(add_environment(getplugin(NULL), arg, true)){
447
 
        errno = 0;
448
 
      }
449
 
      break;
450
 
    case 'o':                   /* --options-for */
451
 
      {
452
 
        char *option_list = strchr(arg, ':');
453
 
        if(option_list == NULL){
454
 
          argp_error(state, "No colon in \"%s\"", arg);
455
 
          errno = EINVAL;
456
 
          break;
457
 
        }
458
 
        *option_list = '\0';
459
 
        option_list++;
460
 
        if(arg[0] == '\0'){
461
 
          argp_error(state, "Empty plugin name");
462
 
          errno = EINVAL;
463
 
          break;
464
 
        }
465
 
        char *option;
466
 
        while((option = strsep(&option_list, ",")) != NULL){
467
 
          if(not add_argument(getplugin(arg), option)){
468
 
            break;
469
 
          }
470
 
        }
471
 
        errno = 0;
472
 
      }
473
 
      break;
474
 
    case 'E':                   /* --env-for */
475
 
      {
476
 
        char *envdef = strchr(arg, ':');
477
 
        if(envdef == NULL){
478
 
          argp_error(state, "No colon in \"%s\"", arg);
479
 
          errno = EINVAL;
480
 
          break;
481
 
        }
482
 
        *envdef = '\0';
483
 
        envdef++;
484
 
        if(arg[0] == '\0'){
485
 
          argp_error(state, "Empty plugin name");
486
 
          errno = EINVAL;
487
 
          break;
488
 
        }
489
 
        if(add_environment(getplugin(arg), envdef, true)){
490
 
          errno = 0;
491
 
        }
492
 
      }
493
 
      break;
494
 
    case 'd':                   /* --disable */
495
 
      {
496
 
        plugin *p = getplugin(arg);
497
 
        if(p != NULL){
498
 
          p->disabled = true;
499
 
          errno = 0;
500
 
        }
501
 
      }
502
 
      break;
503
 
    case 'e':                   /* --enable */
504
 
      {
505
 
        plugin *p = getplugin(arg);
506
 
        if(p != NULL){
507
 
          p->disabled = false;
508
 
          errno = 0;
509
 
        }
510
 
      }
511
 
      break;
512
 
    case 128:                   /* --plugin-dir */
513
 
      free(plugindir);
514
 
      plugindir = strdup(arg);
515
 
      if(plugindir != NULL){
516
 
        errno = 0;
517
 
      }
518
 
      break;
519
 
    case 129:                   /* --config-file */
520
 
      /* This is already done by parse_opt_config_file() */
521
 
      break;
522
 
    case 130:                   /* --userid */
523
 
      tmp_id = strtoimax(arg, &tmp, 10);
524
 
      if(errno != 0 or tmp == arg or *tmp != '\0'
525
 
         or tmp_id != (uid_t)tmp_id){
526
 
        argp_error(state, "Bad user ID number: \"%s\", using %"
527
 
                   PRIdMAX, arg, (intmax_t)uid);
528
 
        break;
529
 
      }
530
 
      uid = (uid_t)tmp_id;
531
 
      errno = 0;
532
 
      break;
533
 
    case 131:                   /* --groupid */
534
 
      tmp_id = strtoimax(arg, &tmp, 10);
535
 
      if(errno != 0 or tmp == arg or *tmp != '\0'
536
 
         or tmp_id != (gid_t)tmp_id){
537
 
        argp_error(state, "Bad group ID number: \"%s\", using %"
538
 
                   PRIdMAX, arg, (intmax_t)gid);
539
 
        break;
540
 
      }
541
 
      gid = (gid_t)tmp_id;
542
 
      errno = 0;
543
 
      break;
544
 
    case 132:                   /* --debug */
545
 
      debug = true;
546
 
      break;
547
 
      /*
548
 
       * These reproduce what we would get without ARGP_NO_HELP
549
 
       */
550
 
    case '?':                   /* --help */
551
 
      state->flags &= ~(unsigned int)ARGP_NO_EXIT; /* force exit */
552
 
      argp_state_help(state, state->out_stream, ARGP_HELP_STD_HELP);
553
 
    case -3:                    /* --usage */
554
 
      state->flags &= ~(unsigned int)ARGP_NO_EXIT; /* force exit */
555
 
      argp_state_help(state, state->out_stream,
556
 
                      ARGP_HELP_USAGE | ARGP_HELP_EXIT_OK);
557
 
    case 'V':                   /* --version */
558
 
      fprintf(state->out_stream, "%s\n", argp_program_version);
559
 
      exit(EXIT_SUCCESS);
560
 
      break;
561
 
/*
562
 
 * When adding more options before this line, remember to also add a
563
 
 * "case" to the "parse_opt_config_file" function below.
564
 
 */
565
 
    case ARGP_KEY_ARG:
566
 
      /* Cryptsetup always passes an argument, which is an empty
567
 
         string if "none" was specified in /etc/crypttab.  So if
568
 
         argument was empty, we ignore it silently. */
569
 
      if(arg[0] == '\0'){
570
 
        break;
571
 
      }
572
 
    default:
573
 
      return ARGP_ERR_UNKNOWN;
574
 
    }
575
 
    return errno;               /* Set to 0 at start */
576
 
  }
577
 
  
578
 
  /* This option parser is the same as parse_opt() above, except it
579
 
     ignores everything but the --config-file option. */
580
 
  error_t parse_opt_config_file(int key, char *arg,
581
 
                                __attribute__((unused))
582
 
                                struct argp_state *state){
583
 
    errno = 0;
584
 
    switch(key){
585
 
    case 'g':                   /* --global-options */
586
 
    case 'G':                   /* --global-env */
587
 
    case 'o':                   /* --options-for */
588
 
    case 'E':                   /* --env-for */
589
 
    case 'd':                   /* --disable */
590
 
    case 'e':                   /* --enable */
591
 
    case 128:                   /* --plugin-dir */
592
 
      break;
593
 
    case 129:                   /* --config-file */
594
 
      free(argfile);
595
 
      argfile = strdup(arg);
596
 
      if(argfile != NULL){
597
 
        errno = 0;
598
 
      }
599
 
      break;
600
 
    case 130:                   /* --userid */
601
 
    case 131:                   /* --groupid */
602
 
    case 132:                   /* --debug */
603
 
    case '?':                   /* --help */
604
 
    case -3:                    /* --usage */
605
 
    case 'V':                   /* --version */
606
 
    case ARGP_KEY_ARG:
607
 
      break;
608
 
    default:
609
 
      return ARGP_ERR_UNKNOWN;
610
 
    }
611
 
    return errno;
612
 
  }
613
 
  
614
 
  struct argp argp = { .options = options,
615
 
                       .parser = parse_opt_config_file,
616
 
                       .args_doc = "",
617
 
                       .doc = "Mandos plugin runner -- Run plugins" };
618
 
  
619
 
  /* Parse using parse_opt_config_file() in order to get the custom
620
 
     config file location, if any. */
621
 
  ret = argp_parse(&argp, argc, argv,
622
 
                   ARGP_IN_ORDER | ARGP_NO_EXIT | ARGP_NO_HELP,
623
 
                   NULL, NULL);
624
 
  switch(ret){
625
 
  case 0:
626
 
    break;
627
 
  case ENOMEM:
628
 
  default:
629
 
    errno = ret;
630
 
    error(0, errno, "argp_parse");
631
 
    exitstatus = EX_OSERR;
632
 
    goto fallback;
633
 
  case EINVAL:
634
 
    exitstatus = EX_USAGE;
635
 
    goto fallback;
636
 
  }
637
 
  
638
 
  /* Reset to the normal argument parser */
639
 
  argp.parser = parse_opt;
640
 
  
641
 
  /* Open the configfile if available */
642
 
  if(argfile == NULL){
643
 
    conffp = fopen(AFILE, "r");
644
 
  } else {
645
 
    conffp = fopen(argfile, "r");
646
 
  }
647
 
  if(conffp != NULL){
648
 
    char *org_line = NULL;
649
 
    char *p, *arg, *new_arg, *line;
650
 
    size_t size = 0;
651
 
    const char whitespace_delims[] = " \r\t\f\v\n";
652
 
    const char comment_delim[] = "#";
653
 
    
654
 
    custom_argc = 1;
655
 
    custom_argv = malloc(sizeof(char*) * 2);
656
 
    if(custom_argv == NULL){
657
 
      error(0, errno, "malloc");
658
 
      exitstatus = EX_OSERR;
659
 
      goto fallback;
660
 
    }
661
 
    custom_argv[0] = argv[0];
662
 
    custom_argv[1] = NULL;
663
 
    
664
 
    /* for each line in the config file, strip whitespace and ignore
665
 
       commented text */
666
 
    while(true){
667
 
      sret = getline(&org_line, &size, conffp);
668
 
      if(sret == -1){
669
 
        break;
670
 
      }
671
 
      
672
 
      line = org_line;
673
 
      arg = strsep(&line, comment_delim);
674
 
      while((p = strsep(&arg, whitespace_delims)) != NULL){
675
 
        if(p[0] == '\0'){
676
 
          continue;
677
 
        }
678
 
        new_arg = strdup(p);
679
 
        if(new_arg == NULL){
680
 
          error(0, errno, "strdup");
681
 
          exitstatus = EX_OSERR;
682
 
          free(org_line);
683
 
          goto fallback;
684
 
        }
685
 
        
686
 
        custom_argc += 1;
687
 
        {
688
 
          char **new_argv = realloc(custom_argv, sizeof(char *)
689
 
                                    * ((unsigned int)
690
 
                                       custom_argc + 1));
691
 
          if(new_argv == NULL){
692
 
            error(0, errno, "realloc");
693
 
            exitstatus = EX_OSERR;
694
 
            free(new_arg);
695
 
            free(org_line);
696
 
            goto fallback;
697
 
          } else {
698
 
            custom_argv = new_argv;
699
 
          }
700
 
        }
701
 
        custom_argv[custom_argc-1] = new_arg;
702
 
        custom_argv[custom_argc] = NULL;
703
 
      }
704
 
    }
705
 
    do {
706
 
      ret = fclose(conffp);
707
 
    } while(ret == EOF and errno == EINTR);
708
 
    if(ret == EOF){
709
 
      error(0, errno, "fclose");
710
 
      exitstatus = EX_IOERR;
711
 
      goto fallback;
712
 
    }
713
 
    free(org_line);
714
 
  } else {
715
 
    /* Check for harmful errors and go to fallback. Other errors might
716
 
       not affect opening plugins */
717
 
    if(errno == EMFILE or errno == ENFILE or errno == ENOMEM){
718
 
      error(0, errno, "fopen");
719
 
      exitstatus = EX_OSERR;
720
 
      goto fallback;
721
 
    }
722
 
  }
723
 
  /* If there were any arguments from the configuration file, pass
724
 
     them to parser as command line arguments */
725
 
  if(custom_argv != NULL){
726
 
    ret = argp_parse(&argp, custom_argc, custom_argv,
727
 
                     ARGP_IN_ORDER | ARGP_NO_EXIT | ARGP_NO_HELP,
728
 
                     NULL, NULL);
729
 
    switch(ret){
730
 
    case 0:
731
 
      break;
732
 
    case ENOMEM:
733
 
    default:
734
 
      errno = ret;
735
 
      error(0, errno, "argp_parse");
736
 
      exitstatus = EX_OSERR;
737
 
      goto fallback;
738
 
    case EINVAL:
739
 
      exitstatus = EX_CONFIG;
740
 
      goto fallback;
741
 
    }
742
 
  }
743
 
  
744
 
  /* Parse actual command line arguments, to let them override the
745
 
     config file */
746
 
  ret = argp_parse(&argp, argc, argv,
747
 
                   ARGP_IN_ORDER | ARGP_NO_EXIT | ARGP_NO_HELP,
748
 
                   NULL, NULL);
749
 
  switch(ret){
750
 
  case 0:
751
 
    break;
752
 
  case ENOMEM:
753
 
  default:
754
 
    errno = ret;
755
 
    error(0, errno, "argp_parse");
756
 
    exitstatus = EX_OSERR;
757
 
    goto fallback;
758
 
  case EINVAL:
759
 
    exitstatus = EX_USAGE;
760
 
    goto fallback;
761
 
  }
762
 
  
763
 
  if(debug){
764
 
    for(plugin *p = plugin_list; p != NULL; p=p->next){
765
 
      fprintf(stderr, "Plugin: %s has %d arguments\n",
766
 
              p->name ? p->name : "Global", p->argc - 1);
767
 
      for(char **a = p->argv; *a != NULL; a++){
768
 
        fprintf(stderr, "\tArg: %s\n", *a);
769
 
      }
770
 
      fprintf(stderr, "...and %d environment variables\n", p->envc);
771
 
      for(char **a = p->environ; *a != NULL; a++){
772
 
        fprintf(stderr, "\t%s\n", *a);
773
 
      }
774
 
    }
775
 
  }
776
 
  
777
 
  if(getuid() == 0){
778
 
    /* Work around Debian bug #633582:
779
 
       <http://bugs.debian.org/633582> */
780
 
    int plugindir_fd = open(/* plugindir or */ PDIR, O_RDONLY);
781
 
    if(plugindir_fd == -1){
782
 
      error(0, errno, "open(\"" PDIR "\")");
783
 
    } else {
784
 
      ret = (int)TEMP_FAILURE_RETRY(fstat(plugindir_fd, &st));
785
 
      if(ret == -1){
786
 
        error(0, errno, "fstat");
787
 
      } else {
788
 
        if(S_ISDIR(st.st_mode) and st.st_uid == 0 and st.st_gid == 0){
789
 
          ret = fchown(plugindir_fd, uid, gid);
790
 
          if(ret == -1){
791
 
            error(0, errno, "fchown");
792
 
          }
793
 
        }
794
 
      }
795
 
      TEMP_FAILURE_RETRY(close(plugindir_fd));
796
 
    }
797
 
  }
798
 
  
799
 
  /* Lower permissions */
800
 
  ret = setgid(gid);
801
 
  if(ret == -1){
802
 
    error(0, errno, "setgid");
803
 
  }
804
 
  ret = setuid(uid);
805
 
  if(ret == -1){
806
 
    error(0, errno, "setuid");
807
 
  }
808
 
  
809
 
  /* Open plugin directory with close_on_exec flag */
810
 
  {
811
 
    int dir_fd = -1;
812
 
    if(plugindir == NULL){
813
 
      dir_fd = open(PDIR, O_RDONLY |
814
 
#ifdef O_CLOEXEC
815
 
                    O_CLOEXEC
816
 
#else  /* not O_CLOEXEC */
817
 
                    0
818
 
#endif  /* not O_CLOEXEC */
819
 
                    );
820
 
    } else {
821
 
      dir_fd = open(plugindir, O_RDONLY |
822
 
#ifdef O_CLOEXEC
823
 
                    O_CLOEXEC
824
 
#else  /* not O_CLOEXEC */
825
 
                    0
826
 
#endif  /* not O_CLOEXEC */
827
 
                    );
828
 
    }
829
 
    if(dir_fd == -1){
830
 
      error(0, errno, "Could not open plugin dir");
831
 
      exitstatus = EX_UNAVAILABLE;
832
 
      goto fallback;
833
 
    }
834
 
    
835
 
#ifndef O_CLOEXEC
836
 
  /* Set the FD_CLOEXEC flag on the directory */
837
 
    ret = set_cloexec_flag(dir_fd);
838
 
    if(ret < 0){
839
 
      error(0, errno, "set_cloexec_flag");
840
 
      TEMP_FAILURE_RETRY(close(dir_fd));
841
 
      exitstatus = EX_OSERR;
842
 
      goto fallback;
843
 
    }
844
 
#endif  /* O_CLOEXEC */
845
 
    
846
 
    dir = fdopendir(dir_fd);
847
 
    if(dir == NULL){
848
 
      error(0, errno, "Could not open plugin dir");
849
 
      TEMP_FAILURE_RETRY(close(dir_fd));
850
 
      exitstatus = EX_OSERR;
851
 
      goto fallback;
852
 
    }
853
 
  }
854
 
  
855
 
  FD_ZERO(&rfds_all);
856
 
  
857
 
  /* Read and execute any executable in the plugin directory*/
 
37
  process *process_list = NULL;
 
38
  
 
39
  dir = opendir(plugindir);
 
40
 
 
41
  if(dir == NULL){
 
42
    fprintf(stderr, "Can not open directory\n");
 
43
    return EXIT_FAILURE;
 
44
  }
 
45
  
 
46
  FD_ZERO(&rfds_orig);
 
47
  
858
48
  while(true){
859
 
    do {
860
 
      dirst = readdir(dir);
861
 
    } while(dirst == NULL and errno == EINTR);
 
49
    dirst = readdir(dir);
862
50
    
863
 
    /* All directory entries have been processed */
 
51
    // All directory entries have been processed
864
52
    if(dirst == NULL){
865
 
      if(errno == EBADF){
866
 
        error(0, errno, "readdir");
867
 
        exitstatus = EX_IOERR;
868
 
        goto fallback;
869
 
      }
870
53
      break;
871
54
    }
872
55
    
873
56
    d_name_len = strlen(dirst->d_name);
874
57
    
875
 
    /* Ignore dotfiles, backup files and other junk */
876
 
    {
877
 
      bool bad_name = false;
878
 
      
879
 
      const char * const bad_prefixes[] = { ".", "#", NULL };
880
 
      
881
 
      const char * const bad_suffixes[] = { "~", "#", ".dpkg-new",
882
 
                                           ".dpkg-old",
883
 
                                           ".dpkg-bak",
884
 
                                           ".dpkg-divert", NULL };
885
 
#ifdef __GNUC__
886
 
#pragma GCC diagnostic push
887
 
#pragma GCC diagnostic ignored "-Wcast-qual"
888
 
#endif
889
 
      for(const char **pre = (const char **)bad_prefixes;
890
 
          *pre != NULL; pre++){
891
 
#ifdef __GNUC__
892
 
#pragma GCC diagnostic pop
893
 
#endif
894
 
        size_t pre_len = strlen(*pre);
895
 
        if((d_name_len >= pre_len)
896
 
           and strncmp((dirst->d_name), *pre, pre_len) == 0){
897
 
          if(debug){
898
 
            fprintf(stderr, "Ignoring plugin dir entry \"%s\""
899
 
                    " with bad prefix %s\n", dirst->d_name, *pre);
900
 
          }
901
 
          bad_name = true;
902
 
          break;
903
 
        }
904
 
      }
905
 
      if(bad_name){
906
 
        continue;
907
 
      }
908
 
#ifdef __GNUC__
909
 
#pragma GCC diagnostic push
910
 
#pragma GCC diagnostic ignored "-Wcast-qual"
911
 
#endif
912
 
      for(const char **suf = (const char **)bad_suffixes;
913
 
          *suf != NULL; suf++){
914
 
#ifdef __GNUC__
915
 
#pragma GCC diagnostic pop
916
 
#endif
917
 
        size_t suf_len = strlen(*suf);
918
 
        if((d_name_len >= suf_len)
919
 
           and (strcmp((dirst->d_name) + d_name_len-suf_len, *suf)
920
 
                == 0)){
921
 
          if(debug){
922
 
            fprintf(stderr, "Ignoring plugin dir entry \"%s\""
923
 
                    " with bad suffix %s\n", dirst->d_name, *suf);
924
 
          }
925
 
          bad_name = true;
926
 
          break;
927
 
        }
928
 
      }
929
 
      
930
 
      if(bad_name){
931
 
        continue;
932
 
      }
933
 
    }
934
 
    
935
 
    char *filename;
936
 
    if(plugindir == NULL){
937
 
      ret = (int)TEMP_FAILURE_RETRY(asprintf(&filename, PDIR "/%s",
938
 
                                             dirst->d_name));
939
 
    } else {
940
 
      ret = (int)TEMP_FAILURE_RETRY(asprintf(&filename, "%s/%s",
941
 
                                             plugindir,
942
 
                                             dirst->d_name));
943
 
    }
944
 
    if(ret < 0){
945
 
      error(0, errno, "asprintf");
946
 
      continue;
947
 
    }
948
 
    
949
 
    ret = (int)TEMP_FAILURE_RETRY(stat(filename, &st));
950
 
    if(ret == -1){
951
 
      error(0, errno, "stat");
952
 
      free(filename);
953
 
      continue;
954
 
    }
955
 
    
956
 
    /* Ignore non-executable files */
957
 
    if(not S_ISREG(st.st_mode)
958
 
       or (TEMP_FAILURE_RETRY(access(filename, X_OK)) != 0)){
959
 
      if(debug){
960
 
        fprintf(stderr, "Ignoring plugin dir entry \"%s\""
961
 
                " with bad type or mode\n", filename);
962
 
      }
963
 
      free(filename);
964
 
      continue;
965
 
    }
966
 
    
967
 
    plugin *p = getplugin(dirst->d_name);
968
 
    if(p == NULL){
969
 
      error(0, errno, "getplugin");
970
 
      free(filename);
971
 
      continue;
972
 
    }
973
 
    if(p->disabled){
974
 
      if(debug){
975
 
        fprintf(stderr, "Ignoring disabled plugin \"%s\"\n",
976
 
                dirst->d_name);
977
 
      }
978
 
      free(filename);
979
 
      continue;
980
 
    }
981
 
    {
982
 
      /* Add global arguments to argument list for this plugin */
983
 
      plugin *g = getplugin(NULL);
984
 
      if(g != NULL){
985
 
        for(char **a = g->argv + 1; *a != NULL; a++){
986
 
          if(not add_argument(p, *a)){
987
 
            error(0, errno, "add_argument");
988
 
          }
989
 
        }
990
 
        /* Add global environment variables */
991
 
        for(char **e = g->environ; *e != NULL; e++){
992
 
          if(not add_environment(p, *e, false)){
993
 
            error(0, errno, "add_environment");
994
 
          }
995
 
        }
996
 
      }
997
 
    }
998
 
    /* If this plugin has any environment variables, we will call
999
 
       using execve and need to duplicate the environment from this
1000
 
       process, too. */
1001
 
    if(p->environ[0] != NULL){
1002
 
      for(char **e = environ; *e != NULL; e++){
1003
 
        if(not add_environment(p, *e, false)){
1004
 
          error(0, errno, "add_environment");
1005
 
        }
1006
 
      }
1007
 
    }
1008
 
    
1009
 
    int pipefd[2];
1010
 
    ret = (int)TEMP_FAILURE_RETRY(pipe(pipefd));
1011
 
    if(ret == -1){
1012
 
      error(0, errno, "pipe");
1013
 
      exitstatus = EX_OSERR;
1014
 
      goto fallback;
1015
 
    }
1016
 
    /* Ask OS to automatic close the pipe on exec */
1017
 
    ret = set_cloexec_flag(pipefd[0]);
1018
 
    if(ret < 0){
1019
 
      error(0, errno, "set_cloexec_flag");
1020
 
      exitstatus = EX_OSERR;
1021
 
      goto fallback;
1022
 
    }
1023
 
    ret = set_cloexec_flag(pipefd[1]);
1024
 
    if(ret < 0){
1025
 
      error(0, errno, "set_cloexec_flag");
1026
 
      exitstatus = EX_OSERR;
1027
 
      goto fallback;
1028
 
    }
1029
 
    /* Block SIGCHLD until process is safely in process list */
1030
 
    ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_BLOCK,
1031
 
                                              &sigchld_action.sa_mask,
1032
 
                                              NULL));
1033
 
    if(ret < 0){
1034
 
      error(0, errno, "sigprocmask");
1035
 
      exitstatus = EX_OSERR;
1036
 
      goto fallback;
1037
 
    }
1038
 
    /* Starting a new process to be watched */
1039
 
    pid_t pid;
1040
 
    do {
1041
 
      pid = fork();
1042
 
    } while(pid == -1 and errno == EINTR);
1043
 
    if(pid == -1){
1044
 
      error(0, errno, "fork");
1045
 
      exitstatus = EX_OSERR;
1046
 
      goto fallback;
1047
 
    }
1048
 
    if(pid == 0){
1049
 
      /* this is the child process */
1050
 
      ret = sigaction(SIGCHLD, &old_sigchld_action, NULL);
1051
 
      if(ret < 0){
1052
 
        error(0, errno, "sigaction");
1053
 
        _exit(EX_OSERR);
1054
 
      }
1055
 
      ret = sigprocmask(SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
1056
 
      if(ret < 0){
1057
 
        error(0, errno, "sigprocmask");
1058
 
        _exit(EX_OSERR);
1059
 
      }
1060
 
      
1061
 
      ret = dup2(pipefd[1], STDOUT_FILENO); /* replace our stdout */
1062
 
      if(ret == -1){
1063
 
        error(0, errno, "dup2");
1064
 
        _exit(EX_OSERR);
1065
 
      }
1066
 
      
1067
 
      if(dirfd(dir) < 0){
1068
 
        /* If dir has no file descriptor, we could not set FD_CLOEXEC
1069
 
           above and must now close it manually here. */
 
58
    // Ignore dotfiles and backup files
 
59
    if (dirst->d_name[0] == '.'
 
60
        or dirst->d_name[d_name_len - 1] == '~'){
 
61
      continue;
 
62
    }
 
63
    
 
64
    char *filename = malloc(d_name_len + plugindir_len + 1);
 
65
    strcpy(filename, plugindir);
 
66
    strcat(filename, "/");
 
67
    strcat(filename, dirst->d_name);    
 
68
    
 
69
    stat(filename, &st);
 
70
 
 
71
    if (S_ISREG(st.st_mode) and (access(filename, X_OK) == 0)){
 
72
      // Starting a new process to be watched
 
73
      process *new_process = malloc(sizeof(process));
 
74
      int pipefd[2];
 
75
      pipe(pipefd);
 
76
      new_process->pid = fork();
 
77
      if(new_process->pid == 0){
 
78
        /* this is the child process */
1070
79
        closedir(dir);
1071
 
      }
1072
 
      if(execve(filename, p->argv,
1073
 
                (p->environ[0] != NULL) ? p->environ : environ) < 0){
1074
 
        error(0, errno, "execve for %s", filename);
1075
 
        _exit(EX_OSERR);
1076
 
      }
1077
 
      /* no return */
1078
 
    }
1079
 
    /* Parent process */
1080
 
    TEMP_FAILURE_RETRY(close(pipefd[1])); /* Close unused write end of
1081
 
                                             pipe */
1082
 
    free(filename);
1083
 
    plugin *new_plugin = getplugin(dirst->d_name);
1084
 
    if(new_plugin == NULL){
1085
 
      error(0, errno, "getplugin");
1086
 
      ret = (int)(TEMP_FAILURE_RETRY
1087
 
                  (sigprocmask(SIG_UNBLOCK, &sigchld_action.sa_mask,
1088
 
                               NULL)));
1089
 
      if(ret < 0){
1090
 
        error(0, errno, "sigprocmask");
1091
 
      }
1092
 
      exitstatus = EX_OSERR;
1093
 
      goto fallback;
1094
 
    }
1095
 
    
1096
 
    new_plugin->pid = pid;
1097
 
    new_plugin->fd = pipefd[0];
1098
 
    
1099
 
    /* Unblock SIGCHLD so signal handler can be run if this process
1100
 
       has already completed */
1101
 
    ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_UNBLOCK,
1102
 
                                              &sigchld_action.sa_mask,
1103
 
                                              NULL));
1104
 
    if(ret < 0){
1105
 
      error(0, errno, "sigprocmask");
1106
 
      exitstatus = EX_OSERR;
1107
 
      goto fallback;
1108
 
    }
1109
 
    
1110
 
#if defined (__GNUC__) and defined (__GLIBC__)
1111
 
#if not __GLIBC_PREREQ(2, 16)
1112
 
#pragma GCC diagnostic push
1113
 
#pragma GCC diagnostic ignored "-Wsign-conversion"
1114
 
#endif
1115
 
#endif
1116
 
    FD_SET(new_plugin->fd, &rfds_all); /* Spurious warning from
1117
 
                                          -Wconversion in GNU libc
1118
 
                                          before 2.16 */
1119
 
#if defined (__GNUC__) and defined (__GLIBC__)
1120
 
#if not __GLIBC_PREREQ(2, 16)
1121
 
#pragma GCC diagnostic pop
1122
 
#endif
1123
 
#endif
1124
 
    
1125
 
    if(maxfd < new_plugin->fd){
1126
 
      maxfd = new_plugin->fd;
1127
 
    }
1128
 
  }
1129
 
  
1130
 
  TEMP_FAILURE_RETRY(closedir(dir));
1131
 
  dir = NULL;
1132
 
  free_plugin(getplugin(NULL));
1133
 
  
1134
 
  for(plugin *p = plugin_list; p != NULL; p = p->next){
1135
 
    if(p->pid != 0){
 
80
        close(pipefd[0]);       /* close unused read end of pipe */
 
81
        dup2(pipefd[1], STDOUT_FILENO); /* replace our stdout */
 
82
        /* create a new modified argument list */
 
83
        char **new_argv = malloc(sizeof(char *) * argc + 1);
 
84
        new_argv[0] = filename;
 
85
        for(int i = 1; i < argc; i++){
 
86
          new_argv[i] = argv[i];
 
87
        }
 
88
        new_argv[argc] = NULL;
 
89
        if(execv(filename, new_argv) < 0){
 
90
          perror(argv[0]);
 
91
          close(pipefd[1]);
 
92
          exit(EXIT_FAILURE);
 
93
        }
 
94
        /* no return */
 
95
      }
 
96
      close(pipefd[1]);         /* close unused write end of pipe */
 
97
      new_process->fd = pipefd[0];
 
98
      new_process->buffer = malloc(BUFFER_SIZE);
 
99
      if (new_process->buffer == NULL){
 
100
        perror(argv[0]);
 
101
        goto end;
 
102
      }
 
103
      new_process->buffer_size = BUFFER_SIZE;
 
104
      new_process->buffer_length = 0;
 
105
      FD_SET(new_process->fd, &rfds_orig);
 
106
      
 
107
      if (maxfd < new_process->fd){
 
108
        maxfd = new_process->fd;
 
109
      }
 
110
      
 
111
      //List handling
 
112
      new_process->next = process_list;
 
113
      process_list = new_process;
 
114
    }
 
115
  }
 
116
  
 
117
  closedir(dir);
 
118
  
 
119
  if (process_list != NULL){
 
120
    while(true){
 
121
      fd_set rfds = rfds_orig;
 
122
      int select_ret = select(maxfd+1, &rfds, NULL, NULL, NULL);
 
123
      if (select_ret == -1){
 
124
        perror(argv[0]);
 
125
        goto end;
 
126
      }else{    
 
127
        for(process *process_itr = process_list; process_itr != NULL;
 
128
            process_itr = process_itr->next){
 
129
          if(FD_ISSET(process_itr->fd, &rfds)){
 
130
            if(process_itr->buffer_length + BUFFER_SIZE
 
131
               > process_itr->buffer_size){
 
132
                process_itr->buffer = realloc(process_itr->buffer,
 
133
                                              process_itr->buffer_size
 
134
                                              + BUFFER_SIZE);
 
135
                if (process_itr->buffer == NULL){
 
136
                  perror(argv[0]);
 
137
                  goto end;
 
138
                }
 
139
                process_itr->buffer_size += BUFFER_SIZE;
 
140
            }
 
141
            ret = read(process_itr->fd, process_itr->buffer
 
142
                       + process_itr->buffer_length, BUFFER_SIZE);
 
143
            process_itr->buffer_length+=ret;
 
144
            if(ret == 0){
 
145
              /* got EOF */
 
146
              /* wait for process exit */
 
147
              int status;
 
148
              waitpid(process_itr->pid, &status, 0);
 
149
              if(WIFEXITED(status) and WEXITSTATUS(status) == 0){
 
150
                write(STDOUT_FILENO, process_itr->buffer,
 
151
                      process_itr->buffer_length);
 
152
                goto end;
 
153
              } else {
 
154
                FD_CLR(process_itr->fd, &rfds_orig);
 
155
              }
 
156
            }
 
157
          }
 
158
        }
 
159
      }
 
160
    }
 
161
  }
 
162
  
 
163
 end:
 
164
  for(process *process_itr = process_list; process_itr != NULL;
 
165
      process_itr = process_itr->next){
 
166
    close(process_itr->fd);
 
167
    kill(process_itr->pid, SIGTERM);
 
168
    free(process_itr->buffer);
 
169
  }
 
170
  
 
171
  while(true){
 
172
    int status;
 
173
    ret = wait(&status);
 
174
    if (ret == -1){
 
175
      if(errno != ECHILD){
 
176
        perror("wait");
 
177
      }
1136
178
      break;
1137
179
    }
1138
 
    if(p->next == NULL){
1139
 
      fprintf(stderr, "No plugin processes started. Incorrect plugin"
1140
 
              " directory?\n");
1141
 
      free_plugin_list();
1142
 
    }
1143
 
  }
1144
 
  
1145
 
  /* Main loop while running plugins exist */
1146
 
  while(plugin_list){
1147
 
    fd_set rfds = rfds_all;
1148
 
    int select_ret = select(maxfd+1, &rfds, NULL, NULL, NULL);
1149
 
    if(select_ret == -1 and errno != EINTR){
1150
 
      error(0, errno, "select");
1151
 
      exitstatus = EX_OSERR;
1152
 
      goto fallback;
1153
 
    }
1154
 
    /* OK, now either a process completed, or something can be read
1155
 
       from one of them */
1156
 
    for(plugin *proc = plugin_list; proc != NULL;){
1157
 
      /* Is this process completely done? */
1158
 
      if(proc->completed and proc->eof){
1159
 
        /* Only accept the plugin output if it exited cleanly */
1160
 
        if(not WIFEXITED(proc->status)
1161
 
           or WEXITSTATUS(proc->status) != 0){
1162
 
          /* Bad exit by plugin */
1163
 
          
1164
 
          if(debug){
1165
 
            if(WIFEXITED(proc->status)){
1166
 
              fprintf(stderr, "Plugin %s [%" PRIdMAX "] exited with"
1167
 
                      " status %d\n", proc->name,
1168
 
                      (intmax_t) (proc->pid),
1169
 
                      WEXITSTATUS(proc->status));
1170
 
            } else if(WIFSIGNALED(proc->status)){
1171
 
              fprintf(stderr, "Plugin %s [%" PRIdMAX "] killed by"
1172
 
                      " signal %d: %s\n", proc->name,
1173
 
                      (intmax_t) (proc->pid),
1174
 
                      WTERMSIG(proc->status),
1175
 
                      strsignal(WTERMSIG(proc->status)));
1176
 
            } else if(WCOREDUMP(proc->status)){
1177
 
              fprintf(stderr, "Plugin %s [%" PRIdMAX "] dumped"
1178
 
                      " core\n", proc->name, (intmax_t) (proc->pid));
1179
 
            }
1180
 
          }
1181
 
          
1182
 
          /* Remove the plugin */
1183
 
#if defined (__GNUC__) and defined (__GLIBC__)
1184
 
#if not __GLIBC_PREREQ(2, 16)
1185
 
#pragma GCC diagnostic push
1186
 
#pragma GCC diagnostic ignored "-Wsign-conversion"
1187
 
#endif
1188
 
#endif
1189
 
          FD_CLR(proc->fd, &rfds_all); /* Spurious warning from
1190
 
                                          -Wconversion in GNU libc
1191
 
                                          before 2.16 */
1192
 
#if defined (__GNUC__) and defined (__GLIBC__)
1193
 
#if not __GLIBC_PREREQ(2, 16)
1194
 
#pragma GCC diagnostic pop
1195
 
#endif
1196
 
#endif
1197
 
          
1198
 
          /* Block signal while modifying process_list */
1199
 
          ret = (int)TEMP_FAILURE_RETRY(sigprocmask
1200
 
                                        (SIG_BLOCK,
1201
 
                                         &sigchld_action.sa_mask,
1202
 
                                         NULL));
1203
 
          if(ret < 0){
1204
 
            error(0, errno, "sigprocmask");
1205
 
            exitstatus = EX_OSERR;
1206
 
            goto fallback;
1207
 
          }
1208
 
          
1209
 
          plugin *next_plugin = proc->next;
1210
 
          free_plugin(proc);
1211
 
          proc = next_plugin;
1212
 
          
1213
 
          /* We are done modifying process list, so unblock signal */
1214
 
          ret = (int)(TEMP_FAILURE_RETRY
1215
 
                      (sigprocmask(SIG_UNBLOCK,
1216
 
                                   &sigchld_action.sa_mask, NULL)));
1217
 
          if(ret < 0){
1218
 
            error(0, errno, "sigprocmask");
1219
 
            exitstatus = EX_OSERR;
1220
 
            goto fallback;
1221
 
          }
1222
 
          
1223
 
          if(plugin_list == NULL){
1224
 
            break;
1225
 
          }
1226
 
          
1227
 
          continue;
1228
 
        }
1229
 
        
1230
 
        /* This process exited nicely, so print its buffer */
1231
 
        
1232
 
        bool bret = print_out_password(proc->buffer,
1233
 
                                       proc->buffer_length);
1234
 
        if(not bret){
1235
 
          error(0, errno, "print_out_password");
1236
 
          exitstatus = EX_IOERR;
1237
 
        }
1238
 
        goto fallback;
1239
 
      }
1240
 
      
1241
 
      /* This process has not completed.  Does it have any output? */
1242
 
#if defined (__GNUC__) and defined (__GLIBC__)
1243
 
#if not __GLIBC_PREREQ(2, 16)
1244
 
#pragma GCC diagnostic push
1245
 
#pragma GCC diagnostic ignored "-Wsign-conversion"
1246
 
#endif
1247
 
#endif
1248
 
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){ /* Spurious
1249
 
                                                         warning from
1250
 
                                                         -Wconversion
1251
 
                                                         in GNU libc
1252
 
                                                         before
1253
 
                                                         2.16 */
1254
 
#if defined (__GNUC__) and defined (__GLIBC__)
1255
 
#if not __GLIBC_PREREQ(2, 16)
1256
 
#pragma GCC diagnostic pop
1257
 
#endif
1258
 
#endif
1259
 
        /* This process had nothing to say at this time */
1260
 
        proc = proc->next;
1261
 
        continue;
1262
 
      }
1263
 
      /* Before reading, make the process' data buffer large enough */
1264
 
      if(proc->buffer_length + BUFFER_SIZE > proc->buffer_size){
1265
 
        char *new_buffer = realloc(proc->buffer, proc->buffer_size
1266
 
                                   + (size_t) BUFFER_SIZE);
1267
 
        if(new_buffer == NULL){
1268
 
          error(0, errno, "malloc");
1269
 
          exitstatus = EX_OSERR;
1270
 
          goto fallback;
1271
 
        }
1272
 
        proc->buffer = new_buffer;
1273
 
        proc->buffer_size += BUFFER_SIZE;
1274
 
      }
1275
 
      /* Read from the process */
1276
 
      sret = TEMP_FAILURE_RETRY(read(proc->fd,
1277
 
                                     proc->buffer
1278
 
                                     + proc->buffer_length,
1279
 
                                     BUFFER_SIZE));
1280
 
      if(sret < 0){
1281
 
        /* Read error from this process; ignore the error */
1282
 
        proc = proc->next;
1283
 
        continue;
1284
 
      }
1285
 
      if(sret == 0){
1286
 
        /* got EOF */
1287
 
        proc->eof = true;
1288
 
      } else {
1289
 
        proc->buffer_length += (size_t) sret;
1290
 
      }
1291
 
    }
1292
 
  }
1293
 
  
1294
 
  
1295
 
 fallback:
1296
 
  
1297
 
  if(plugin_list == NULL or (exitstatus != EXIT_SUCCESS
1298
 
                             and exitstatus != EX_OK)){
1299
 
    /* Fallback if all plugins failed, none are found or an error
1300
 
       occured */
1301
 
    bool bret;
1302
 
    fprintf(stderr, "Going to fallback mode using getpass(3)\n");
1303
 
    char *passwordbuffer = getpass("Password: ");
1304
 
    size_t len = strlen(passwordbuffer);
1305
 
    /* Strip trailing newline */
1306
 
    if(len > 0 and passwordbuffer[len-1] == '\n'){
1307
 
      passwordbuffer[len-1] = '\0'; /* not strictly necessary */
1308
 
      len--;
1309
 
    }
1310
 
    bret = print_out_password(passwordbuffer, len);
1311
 
    if(not bret){
1312
 
      error(0, errno, "print_out_password");
1313
 
      exitstatus = EX_IOERR;
1314
 
    }
1315
 
  }
1316
 
  
1317
 
  /* Restore old signal handler */
1318
 
  ret = sigaction(SIGCHLD, &old_sigchld_action, NULL);
1319
 
  if(ret == -1){
1320
 
    error(0, errno, "sigaction");
1321
 
    exitstatus = EX_OSERR;
1322
 
  }
1323
 
  
1324
 
  if(custom_argv != NULL){
1325
 
    for(char **arg = custom_argv+1; *arg != NULL; arg++){
1326
 
      free(*arg);
1327
 
    }
1328
 
    free(custom_argv);
1329
 
  }
1330
 
  
1331
 
  if(dir != NULL){
1332
 
    closedir(dir);
1333
 
  }
1334
 
  
1335
 
  /* Kill the processes */
1336
 
  for(plugin *p = plugin_list; p != NULL; p = p->next){
1337
 
    if(p->pid != 0){
1338
 
      close(p->fd);
1339
 
      ret = kill(p->pid, SIGTERM);
1340
 
      if(ret == -1 and errno != ESRCH){
1341
 
        /* Set-uid proccesses might not get closed */
1342
 
        error(0, errno, "kill");
1343
 
      }
1344
 
    }
1345
 
  }
1346
 
  
1347
 
  /* Wait for any remaining child processes to terminate */
1348
 
  do {
1349
 
    ret = wait(NULL);
1350
 
  } while(ret >= 0);
1351
 
  if(errno != ECHILD){
1352
 
    error(0, errno, "wait");
1353
 
  }
1354
 
  
1355
 
  free_plugin_list();
1356
 
  
1357
 
  free(plugindir);
1358
 
  free(argfile);
1359
 
  
1360
 
  return exitstatus;
 
180
  }  
 
181
  return EXIT_SUCCESS;
1361
182
}