/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 plugin-runner.c

  • Committer: Teddy Hogeborn
  • Date: 2008-08-16 03:29:08 UTC
  • Revision ID: teddy@fukt.bsnet.se-20080816032908-ihw7c05r2mnyk389
Add feature to specify custom environment variables for plugins.

* plugin-runner.c (plugin): New members "environ" and "envc" to
                            contain possible custom environment.
  (getplugin): Return NULL on failure instead of doing exit(); all
               callers changed.
  (add_to_char_array): New helper function for "add_argument" and
                       "add_environment".
  (addargument): Renamed to "add_argument".  Return bool.  Call
                 "add_to_char_array" to actually do things.
  (add_environment): New; analogous to "add_argument".
  (addcustomargument): Renamed to "add_to_argv" to avoid confusion
                       with "add_argument".
  (main): New options "--global-envs" and "--envs-for" to specify
          custom environment for plugins.  Print environment for
          plugins in debug mode.  Use asprintf instead of strcpy and
          strcat.  Use execve() for plugins with custom environments.
          Free environment for plugin when freeing plugin list.

Show diffs side-by-side

added added

removed removed

Lines of Context:
56
56
#include <argp.h>               /* struct argp_option, struct
57
57
                                   argp_state, struct argp,
58
58
                                   argp_parse(), ARGP_ERR_UNKNOWN,
59
 
                                   ARGP_KEY_END, ARGP_KEY_ARG,
60
 
                                   error_t */
 
59
                                   ARGP_KEY_END, ARGP_KEY_ARG, error_t */
61
60
#include <signal.h>             /* struct sigaction, sigemptyset(),
62
61
                                   sigaddset(), sigaction(),
63
62
                                   sigprocmask(), SIG_BLOCK, SIGCHLD,
65
64
#include <errno.h>              /* errno, EBADF */
66
65
 
67
66
#define BUFFER_SIZE 256
68
 
 
69
 
#define PDIR "/lib/mandos/plugins.d"
70
 
#define AFILE "/conf/conf.d/mandos/plugin-runner.conf"
 
67
#define ARGFILE "/conf/conf.d/mandos/plugin-runner.conf"
71
68
 
72
69
const char *argp_program_version = "plugin-runner 1.0";
73
70
const char *argp_program_bug_address = "<mandos@fukt.bsnet.se>";
74
71
 
75
 
struct plugin;
 
72
struct process;
 
73
 
 
74
typedef struct process{
 
75
  pid_t pid;
 
76
  int fd;
 
77
  char *buffer;
 
78
  size_t buffer_size;
 
79
  size_t buffer_length;
 
80
  bool eof;
 
81
  bool completed;
 
82
  int status;
 
83
  struct process *next;
 
84
} process;
76
85
 
77
86
typedef struct plugin{
78
87
  char *name;                   /* can be NULL or any plugin name */
81
90
  char **environ;
82
91
  int envc;
83
92
  bool disabled;
84
 
 
85
 
  /* Variables used for running processes*/
86
 
  pid_t pid;
87
 
  int fd;
88
 
  char *buffer;
89
 
  size_t buffer_size;
90
 
  size_t buffer_length;
91
 
  bool eof;
92
 
  volatile bool completed;
93
 
  volatile int status;
94
93
  struct plugin *next;
95
94
} plugin;
96
95
 
97
 
static plugin *plugin_list = NULL;
98
 
 
99
 
/* Gets an existing plugin based on name,
100
 
   or if none is found, creates a new one */
101
 
static plugin *getplugin(char *name){
102
 
  /* Check for exiting plugin with that name */
103
 
  for (plugin *p = plugin_list; p != NULL; p = p->next){
 
96
static plugin *getplugin(char *name, plugin **plugin_list){
 
97
  for (plugin *p = *plugin_list; p != NULL; p = p->next){
104
98
    if ((p->name == name)
105
99
        or (p->name and name and (strcmp(p->name, name) == 0))){
106
100
      return p;
111
105
  if (new_plugin == NULL){
112
106
    return NULL;
113
107
  }
114
 
  char *copy_name = NULL;
115
 
  if(name != NULL){
116
 
    copy_name = strdup(name);
117
 
    if(copy_name == NULL){
118
 
      return NULL;
119
 
    }
120
 
  }
121
 
  
122
 
  *new_plugin = (plugin) { .name = copy_name,
 
108
  *new_plugin = (plugin) { .name = name,
123
109
                           .argc = 1,
 
110
                           .envc = 0,
124
111
                           .disabled = false,
125
 
                           .next = plugin_list };
 
112
                           .next = *plugin_list };
126
113
  
127
114
  new_plugin->argv = malloc(sizeof(char *) * 2);
128
115
  if (new_plugin->argv == NULL){
129
 
    free(copy_name);
130
116
    free(new_plugin);
131
117
    return NULL;
132
118
  }
133
 
  new_plugin->argv[0] = copy_name;
 
119
  new_plugin->argv[0] = name;
134
120
  new_plugin->argv[1] = NULL;
135
 
  
 
121
 
136
122
  new_plugin->environ = malloc(sizeof(char *));
137
123
  if(new_plugin->environ == NULL){
138
 
    free(copy_name);
139
124
    free(new_plugin->argv);
140
125
    free(new_plugin);
141
126
    return NULL;
142
127
  }
143
128
  new_plugin->environ[0] = NULL;
144
 
  
145
129
  /* Append the new plugin to the list */
146
 
  plugin_list = new_plugin;
 
130
  *plugin_list = new_plugin;
147
131
  return new_plugin;
148
132
}
149
133
 
179
163
}
180
164
 
181
165
/* Add to a plugin's environment */
182
 
static bool add_environment(plugin *p, const char *def, bool replace){
 
166
static bool add_environment(plugin *p, const char *def){
183
167
  if(p == NULL){
184
168
    return false;
185
169
  }
186
 
  /* namelen = length of name of environment variable */
187
 
  size_t namelen = (size_t)(strchrnul(def, '=') - def);
188
 
  /* Search for this environment variable */
189
 
  for(char **e = p->environ; *e != NULL; e++){
190
 
    if(strncmp(*e, def, namelen + 1) == 0){
191
 
      /* It already exists */
192
 
      if(replace){
193
 
        char *new = realloc(*e, strlen(def) + 1);
194
 
        if(new == NULL){
195
 
          return false;
196
 
        }
197
 
        *e = new;
198
 
        strcpy(*e, def);
199
 
      }
200
 
      return true;
201
 
    }
202
 
  }
203
170
  return add_to_char_array(def, &(p->environ), &(p->envc));
204
171
}
205
172
 
 
173
 
206
174
/*
207
175
 * Based on the example in the GNU LibC manual chapter 13.13 "File
208
176
 * Descriptor Flags".
219
187
  return fcntl(fd, F_SETFD, ret | FD_CLOEXEC);
220
188
}
221
189
 
 
190
process *process_list = NULL;
222
191
 
223
 
/* Mark processes as completed when they exit, and save their exit
 
192
/* Mark a process as completed when it exits, and save its exit
224
193
   status. */
225
194
void handle_sigchld(__attribute__((unused)) int sig){
226
 
  while(true){
227
 
    plugin *proc = plugin_list;
228
 
    int status;
229
 
    pid_t pid = waitpid(-1, &status, WNOHANG);
230
 
    if(pid == 0){
231
 
      /* Only still running child processes */
232
 
      break;
233
 
    }
234
 
    if(pid == -1){
235
 
      if (errno != ECHILD){
236
 
        perror("waitpid");
237
 
      }
238
 
      /* No child processes */
239
 
      break;
240
 
    }
241
 
    
242
 
    /* A child exited, find it in process_list */
243
 
    while(proc != NULL and proc->pid != pid){
244
 
      proc = proc->next;
245
 
    }
246
 
    if(proc == NULL){
247
 
      /* Process not found in process list */
248
 
      continue;
249
 
    }
250
 
    proc->status = status;
251
 
    proc->completed = true;
252
 
  }
 
195
  process *proc = process_list;
 
196
  int status;
 
197
  pid_t pid = wait(&status);
 
198
  if(pid == -1){
 
199
    perror("wait");
 
200
    return;
 
201
  }
 
202
  while(proc != NULL and proc->pid != pid){
 
203
    proc = proc->next;
 
204
  }
 
205
  if(proc == NULL){
 
206
    /* Process not found in process list */
 
207
    return;
 
208
  }
 
209
  proc->status = status;
 
210
  proc->completed = true;
253
211
}
254
212
 
255
 
/* Prints out a password to stdout */
256
213
bool print_out_password(const char *buffer, size_t length){
257
214
  ssize_t ret;
258
215
  if(length>0 and buffer[length-1] == '\n'){
268
225
  return true;
269
226
}
270
227
 
271
 
/* Removes and free a plugin from the plugin list */
272
 
static void free_plugin(plugin *plugin_node){
273
 
  
274
 
  for(char **arg = plugin_node->argv; *arg != NULL; arg++){
275
 
    free(*arg);
276
 
  }
277
 
  free(plugin_node->argv);
278
 
  for(char **env = plugin_node->environ; *env != NULL; env++){
279
 
    free(*env);
280
 
  }
281
 
  free(plugin_node->environ);
282
 
  free(plugin_node->buffer);
283
 
 
284
 
  /* Removes the plugin from the singly-linked list */
285
 
  if(plugin_node == plugin_list){
286
 
    /* First one - simple */
287
 
    plugin_list = plugin_list->next;
288
 
  } else {
289
 
    /* Second one or later */
290
 
    for(plugin *p = plugin_list; p != NULL; p = p->next){
291
 
      if(p->next == plugin_node){
292
 
        p->next = plugin_node->next;
293
 
        break;
294
 
      }
 
228
char **add_to_argv(char **argv, int *argc, char *arg){
 
229
  if (argv == NULL){
 
230
    *argc = 1;
 
231
    argv = malloc(sizeof(char*) * 2);
 
232
    if(argv == NULL){
 
233
      return NULL;
295
234
    }
296
 
  }
297
 
  
298
 
  free(plugin_node);
299
 
}
300
 
 
301
 
static void free_plugin_list(void){
302
 
  while(plugin_list != NULL){
303
 
    free_plugin(plugin_list);
304
 
  }
 
235
    argv[0] = NULL;     /* Will be set to argv[0] in main before parsing */
 
236
    argv[1] = NULL;
 
237
  }
 
238
  *argc += 1;
 
239
  argv = realloc(argv, sizeof(char *)
 
240
                  * ((unsigned int) *argc + 1));
 
241
  if(argv == NULL){
 
242
    return NULL;
 
243
  }
 
244
  argv[*argc-1] = arg;
 
245
  argv[*argc] = NULL;   
 
246
  return argv;
305
247
}
306
248
 
307
249
int main(int argc, char *argv[]){
308
 
  char *plugindir = NULL;
309
 
  char *argfile = NULL;
 
250
  const char *plugindir = "/lib/mandos/plugins.d";
 
251
  const char *argfile = ARGFILE;
310
252
  FILE *conffp;
311
253
  size_t d_name_len;
312
254
  DIR *dir = NULL;
327
269
  /* Establish a signal handler */
328
270
  sigemptyset(&sigchld_action.sa_mask);
329
271
  ret = sigaddset(&sigchld_action.sa_mask, SIGCHLD);
330
 
  if(ret == -1){
 
272
  if(ret < 0){
331
273
    perror("sigaddset");
332
 
    exitstatus = EXIT_FAILURE;
333
 
    goto fallback;
 
274
    exit(EXIT_FAILURE);
334
275
  }
335
276
  ret = sigaction(SIGCHLD, &sigchld_action, &old_sigchld_action);
336
 
  if(ret == -1){
 
277
  if(ret < 0){
337
278
    perror("sigaction");
338
 
    exitstatus = EXIT_FAILURE;
339
 
    goto fallback;
 
279
    exit(EXIT_FAILURE);
340
280
  }
341
281
  
342
282
  /* The options we understand. */
344
284
    { .name = "global-options", .key = 'g',
345
285
      .arg = "OPTION[,OPTION[,...]]",
346
286
      .doc = "Options passed to all plugins" },
347
 
    { .name = "global-env", .key = 'G',
 
287
    { .name = "global-envs", .key = 'e',
348
288
      .arg = "VAR=value",
349
289
      .doc = "Environment variable passed to all plugins" },
350
290
    { .name = "options-for", .key = 'o',
351
291
      .arg = "PLUGIN:OPTION[,OPTION[,...]]",
352
292
      .doc = "Options passed only to specified plugin" },
353
 
    { .name = "env-for", .key = 'E',
 
293
    { .name = "envs-for", .key = 'f',
354
294
      .arg = "PLUGIN:ENV=value",
355
295
      .doc = "Environment variable passed to specified plugin" },
356
296
    { .name = "disable", .key = 'd',
357
297
      .arg = "PLUGIN",
358
298
      .doc = "Disable a specific plugin", .group = 1 },
359
 
    { .name = "enable", .key = 'e',
360
 
      .arg = "PLUGIN",
361
 
      .doc = "Enable a specific plugin", .group = 1 },
362
299
    { .name = "plugin-dir", .key = 128,
363
300
      .arg = "DIRECTORY",
364
301
      .doc = "Specify a different plugin directory", .group = 2 },
365
 
    { .name = "config-file", .key = 129,
366
 
      .arg = "FILE",
367
 
      .doc = "Specify a different configuration file", .group = 2 },
368
 
    { .name = "userid", .key = 130,
369
 
      .arg = "ID", .flags = 0,
370
 
      .doc = "User ID the plugins will run as", .group = 3 },
371
 
    { .name = "groupid", .key = 131,
372
 
      .arg = "ID", .flags = 0,
373
 
      .doc = "Group ID the plugins will run as", .group = 3 },
374
 
    { .name = "debug", .key = 132,
375
 
      .doc = "Debug mode", .group = 4 },
 
302
    { .name = "userid", .key = 129,
 
303
      .arg = "ID", .flags = 0,
 
304
      .doc = "User ID the plugins will run as", .group = 2 },
 
305
    { .name = "groupid", .key = 130,
 
306
      .arg = "ID", .flags = 0,
 
307
      .doc = "Group ID the plugins will run as", .group = 2 },
 
308
    { .name = "debug", .key = 131,
 
309
      .doc = "Debug mode", .group = 3 },
376
310
    { .name = NULL }
377
311
  };
378
312
  
379
 
  error_t parse_opt (int key, char *arg, __attribute__((unused))
380
 
                     struct argp_state *state) {
 
313
  error_t parse_opt (int key, char *arg, struct argp_state *state) {
 
314
    /* Get the INPUT argument from `argp_parse', which we know is a
 
315
       pointer to our plugin list pointer. */
 
316
    plugin **plugins = state->input;
381
317
    switch (key) {
382
 
    case 'g':                   /* --global-options */
 
318
    case 'g':
383
319
      if (arg != NULL){
384
320
        char *p;
385
321
        while((p = strsep(&arg, ",")) != NULL){
386
322
          if(p[0] == '\0'){
387
323
            continue;
388
324
          }
389
 
          if(not add_argument(getplugin(NULL), p)){
 
325
          if(not add_argument(getplugin(NULL, plugins), p)){
390
326
            perror("add_argument");
391
327
            return ARGP_ERR_UNKNOWN;
392
328
          }
393
329
        }
394
330
      }
395
331
      break;
396
 
    case 'G':                   /* --global-env */
 
332
    case 'e':
397
333
      if(arg == NULL){
398
334
        break;
399
335
      }
402
338
        if(envdef == NULL){
403
339
          break;
404
340
        }
405
 
        if(not add_environment(getplugin(NULL), envdef, true)){
 
341
        if(not add_environment(getplugin(NULL, plugins), envdef)){
406
342
          perror("add_environment");
407
343
        }
408
344
      }
409
345
      break;
410
 
    case 'o':                   /* --options-for */
 
346
    case 'o':
411
347
      if (arg != NULL){
412
348
        char *p_name = strsep(&arg, ":");
413
 
        if(p_name[0] == '\0' or arg == NULL){
 
349
        if(p_name[0] == '\0'){
414
350
          break;
415
351
        }
416
352
        char *opt = strsep(&arg, ":");
417
 
        if(opt[0] == '\0' or opt == NULL){
 
353
        if(opt[0] == '\0'){
418
354
          break;
419
355
        }
420
 
        char *p;
421
 
        while((p = strsep(&opt, ",")) != NULL){
422
 
          if(p[0] == '\0'){
423
 
            continue;
424
 
          }
425
 
          if(not add_argument(getplugin(p_name), p)){
426
 
            perror("add_argument");
427
 
            return ARGP_ERR_UNKNOWN;
 
356
        if(opt != NULL){
 
357
          char *p;
 
358
          while((p = strsep(&opt, ",")) != NULL){
 
359
            if(p[0] == '\0'){
 
360
              continue;
 
361
            }
 
362
            if(not add_argument(getplugin(p_name, plugins), p)){
 
363
              perror("add_argument");
 
364
              return ARGP_ERR_UNKNOWN;
 
365
            }
428
366
          }
429
367
        }
430
368
      }
431
369
      break;
432
 
    case 'E':                   /* --env-for */
 
370
    case 'f':
433
371
      if(arg == NULL){
434
372
        break;
435
373
      }
443
381
          break;
444
382
        }
445
383
        envdef++;
446
 
        if(not add_environment(getplugin(p_name), envdef, true)){
 
384
        if(not add_environment(getplugin(p_name, plugins), envdef)){
447
385
          perror("add_environment");
448
386
        }
449
387
      }
450
388
      break;
451
 
    case 'd':                   /* --disable */
 
389
    case 'd':
452
390
      if (arg != NULL){
453
 
        plugin *p = getplugin(arg);
 
391
        plugin *p = getplugin(arg, plugins);
454
392
        if(p == NULL){
455
393
          return ARGP_ERR_UNKNOWN;
456
394
        }
457
395
        p->disabled = true;
458
396
      }
459
397
      break;
460
 
    case 'e':                   /* --enable */
461
 
      if (arg != NULL){
462
 
        plugin *p = getplugin(arg);
463
 
        if(p == NULL){
464
 
          return ARGP_ERR_UNKNOWN;
465
 
        }
466
 
        p->disabled = false;
467
 
      }
468
 
      break;
469
 
    case 128:                   /* --plugin-dir */
470
 
      plugindir = strdup(arg);
471
 
      if(plugindir == NULL){
472
 
        perror("strdup");
473
 
      }      
474
 
      break;
475
 
    case 129:                   /* --config-file */
476
 
      /* This is already done by parse_opt_config_file() */
477
 
      break;
478
 
    case 130:                   /* --userid */
 
398
    case 128:
 
399
      plugindir = arg;
 
400
      break;
 
401
    case 129:
479
402
      uid = (uid_t)strtol(arg, NULL, 10);
480
403
      break;
481
 
    case 131:                   /* --groupid */
 
404
    case 130:
482
405
      gid = (gid_t)strtol(arg, NULL, 10);
483
406
      break;
484
 
    case 132:                   /* --debug */
 
407
    case 131:
485
408
      debug = true;
486
409
      break;
487
410
    case ARGP_KEY_ARG:
495
418
    return 0;
496
419
  }
497
420
  
498
 
  /* This option parser is the same as parse_opt() above, except it
499
 
     ignores everything but the --config-file option. */
500
 
  error_t parse_opt_config_file (int key, char *arg,
501
 
                                 __attribute__((unused))
502
 
                                 struct argp_state *state) {
503
 
    switch (key) {
504
 
    case 'g':                   /* --global-options */
505
 
    case 'G':                   /* --global-env */
506
 
    case 'o':                   /* --options-for */
507
 
    case 'E':                   /* --env-for */
508
 
    case 'd':                   /* --disable */
509
 
    case 'e':                   /* --enable */
510
 
    case 128:                   /* --plugin-dir */
511
 
      break;
512
 
    case 129:                   /* --config-file */
513
 
      argfile = strdup(arg);
514
 
      if(argfile == NULL){
515
 
        perror("strdup");
516
 
      }
517
 
      break;      
518
 
    case 130:                   /* --userid */
519
 
    case 131:                   /* --groupid */
520
 
    case 132:                   /* --debug */
521
 
    case ARGP_KEY_ARG:
522
 
    case ARGP_KEY_END:
523
 
      break;
524
 
    default:
525
 
      return ARGP_ERR_UNKNOWN;
526
 
    }
527
 
    return 0;
528
 
  }
 
421
  plugin *plugin_list = NULL;
529
422
  
530
 
  struct argp argp = { .options = options,
531
 
                       .parser = parse_opt_config_file,
532
 
                       .args_doc = "",
 
423
  struct argp argp = { .options = options, .parser = parse_opt,
 
424
                       .args_doc = "[+PLUS_SEPARATED_OPTIONS]",
533
425
                       .doc = "Mandos plugin runner -- Run plugins" };
534
426
  
535
 
  /* Parse using the parse_opt_config_file in order to get the custom
536
 
     config file location, if any. */
537
 
  ret = argp_parse (&argp, argc, argv, ARGP_IN_ORDER, 0, NULL);
 
427
  ret = argp_parse (&argp, argc, argv, 0, 0, &plugin_list);
538
428
  if (ret == ARGP_ERR_UNKNOWN){
539
429
    fprintf(stderr, "Unknown error while parsing arguments\n");
540
430
    exitstatus = EXIT_FAILURE;
541
 
    goto fallback;
 
431
    goto end;
542
432
  }
543
 
  
544
 
  /* Reset to the normal argument parser */
545
 
  argp.parser = parse_opt;
546
 
  
547
 
  /* Open the configfile if available */
548
 
  if (argfile == NULL){
549
 
    conffp = fopen(AFILE, "r");
550
 
  } else {
551
 
    conffp = fopen(argfile, "r");
552
 
  }  
 
433
 
 
434
  conffp = fopen(argfile, "r");
553
435
  if(conffp != NULL){
554
436
    char *org_line = NULL;
555
 
    char *p, *arg, *new_arg, *line;
556
437
    size_t size = 0;
557
438
    ssize_t sret;
 
439
    char *p, *arg, *new_arg, *line;
558
440
    const char whitespace_delims[] = " \r\t\f\v\n";
559
441
    const char comment_delim[] = "#";
560
442
 
561
 
    custom_argc = 1;
562
 
    custom_argv = malloc(sizeof(char*) * 2);
563
 
    if(custom_argv == NULL){
564
 
      perror("malloc");
565
 
      exitstatus = EXIT_FAILURE;
566
 
      goto fallback;
567
 
    }
568
 
    custom_argv[0] = argv[0];
569
 
    custom_argv[1] = NULL;
570
 
 
571
 
    /* for each line in the config file, strip whitespace and ignore
572
 
       commented text */
573
443
    while(true){
574
444
      sret = getline(&org_line, &size, conffp);
575
445
      if(sret == -1){
583
453
          continue;
584
454
        }
585
455
        new_arg = strdup(p);
586
 
        if(new_arg == NULL){
587
 
          perror("strdup");
588
 
          exitstatus = EXIT_FAILURE;
589
 
          free(org_line);
590
 
          goto fallback;
591
 
        }
592
 
        
593
 
        custom_argc += 1;
594
 
        custom_argv = realloc(custom_argv, sizeof(char *)
595
 
                              * ((unsigned int) custom_argc + 1));
596
 
        if(custom_argv == NULL){
597
 
          perror("realloc");
598
 
          exitstatus = EXIT_FAILURE;
599
 
          free(org_line);
600
 
          goto fallback;
601
 
        }
602
 
        custom_argv[custom_argc-1] = new_arg;
603
 
        custom_argv[custom_argc] = NULL;        
 
456
        custom_argv = add_to_argv(custom_argv, &custom_argc, new_arg);
 
457
        if (custom_argv == NULL){
 
458
          perror("add_to_argv");
 
459
          exitstatus = EXIT_FAILURE;
 
460
          goto end;
 
461
        }
604
462
      }
605
463
    }
606
464
    free(org_line);
607
 
  } else {
 
465
  } else{
608
466
    /* Check for harmful errors and go to fallback. Other errors might
609
467
       not affect opening plugins */
610
468
    if (errno == EMFILE or errno == ENFILE or errno == ENOMEM){
611
469
      perror("fopen");
612
470
      exitstatus = EXIT_FAILURE;
613
 
      goto fallback;
 
471
      goto end;
614
472
    }
615
473
  }
616
 
  /* If there was any arguments from configuration file,
617
 
     pass them to parser as command arguments */
 
474
 
618
475
  if(custom_argv != NULL){
619
 
    ret = argp_parse (&argp, custom_argc, custom_argv, ARGP_IN_ORDER,
620
 
                      0, NULL);
 
476
    custom_argv[0] = argv[0];
 
477
    ret = argp_parse (&argp, custom_argc, custom_argv, 0, 0, &plugin_list);
621
478
    if (ret == ARGP_ERR_UNKNOWN){
622
479
      fprintf(stderr, "Unknown error while parsing arguments\n");
623
480
      exitstatus = EXIT_FAILURE;
624
 
      goto fallback;
 
481
      goto end;
625
482
    }
626
483
  }
627
484
  
628
 
  /* Parse actual command line arguments, to let them override the
629
 
     config file */
630
 
  ret = argp_parse (&argp, argc, argv, ARGP_IN_ORDER, 0, NULL);
631
 
  if (ret == ARGP_ERR_UNKNOWN){
632
 
    fprintf(stderr, "Unknown error while parsing arguments\n");
633
 
    exitstatus = EXIT_FAILURE;
634
 
    goto fallback;
635
 
  }
636
 
  
637
485
  if(debug){
638
486
    for(plugin *p = plugin_list; p != NULL; p=p->next){
639
487
      fprintf(stderr, "Plugin: %s has %d arguments\n",
648
496
    }
649
497
  }
650
498
  
651
 
  /* Strip permissions down to nobody */
652
499
  ret = setuid(uid);
653
500
  if (ret == -1){
654
501
    perror("setuid");
655
 
  }  
 
502
  }
 
503
  
656
504
  setgid(gid);
657
505
  if (ret == -1){
658
506
    perror("setgid");
659
507
  }
660
508
  
661
 
  if (plugindir == NULL){
662
 
    dir = opendir(PDIR);
663
 
  } else {
664
 
    dir = opendir(plugindir);
665
 
  }
666
 
  
 
509
  dir = opendir(plugindir);
667
510
  if(dir == NULL){
668
511
    perror("Could not open plugin dir");
669
512
    exitstatus = EXIT_FAILURE;
670
 
    goto fallback;
 
513
    goto end;
671
514
  }
672
515
  
673
516
  /* Set the FD_CLOEXEC flag on the directory, if possible */
678
521
      if(ret < 0){
679
522
        perror("set_cloexec_flag");
680
523
        exitstatus = EXIT_FAILURE;
681
 
        goto fallback;
 
524
        goto end;
682
525
      }
683
526
    }
684
527
  }
685
528
  
686
529
  FD_ZERO(&rfds_all);
687
530
  
688
 
  /* Read and execute any executable in the plugin directory*/
689
531
  while(true){
690
532
    dirst = readdir(dir);
691
533
    
692
 
    /* All directory entries have been processed */
 
534
    // All directory entries have been processed
693
535
    if(dirst == NULL){
694
536
      if (errno == EBADF){
695
537
        perror("readdir");
696
538
        exitstatus = EXIT_FAILURE;
697
 
        goto fallback;
 
539
        goto end;
698
540
      }
699
541
      break;
700
542
    }
701
543
    
702
544
    d_name_len = strlen(dirst->d_name);
703
545
    
704
 
    /* Ignore dotfiles, backup files and other junk */
 
546
    // Ignore dotfiles, backup files and other junk
705
547
    {
706
548
      bool bad_name = false;
707
549
      
722
564
          break;
723
565
        }
724
566
      }
 
567
      
725
568
      if(bad_name){
726
569
        continue;
727
570
      }
 
571
      
728
572
      for(const char **suf = bad_suffixes; *suf != NULL; suf++){
729
573
        size_t suf_len = strlen(*suf);
730
574
        if((d_name_len >= suf_len)
757
601
      free(filename);
758
602
      continue;
759
603
    }
760
 
 
761
 
    /* Ignore non-executable files */
 
604
    
762
605
    if (not S_ISREG(st.st_mode) or (access(filename, X_OK) != 0)){
763
606
      if(debug){
764
607
        fprintf(stderr, "Ignoring plugin dir entry \"%s\""
767
610
      free(filename);
768
611
      continue;
769
612
    }
770
 
    
771
 
    plugin *p = getplugin(dirst->d_name);
 
613
    plugin *p = getplugin(dirst->d_name, &plugin_list);
772
614
    if(p == NULL){
773
615
      perror("getplugin");
774
616
      free(filename);
784
626
    }
785
627
    {
786
628
      /* Add global arguments to argument list for this plugin */
787
 
      plugin *g = getplugin(NULL);
 
629
      plugin *g = getplugin(NULL, &plugin_list);
788
630
      if(g != NULL){
789
631
        for(char **a = g->argv + 1; *a != NULL; a++){
790
632
          if(not add_argument(p, *a)){
793
635
        }
794
636
        /* Add global environment variables */
795
637
        for(char **e = g->environ; *e != NULL; e++){
796
 
          if(not add_environment(p, *e, false)){
 
638
          if(not add_environment(p, *e)){
797
639
            perror("add_environment");
798
640
          }
799
641
        }
804
646
       process, too. */
805
647
    if(p->environ[0] != NULL){
806
648
      for(char **e = environ; *e != NULL; e++){
807
 
        if(not add_environment(p, *e, false)){
 
649
        char *copy = strdup(*e);
 
650
        if(copy == NULL){
 
651
          perror("strdup");
 
652
          continue;
 
653
        }
 
654
        if(not add_environment(p, copy)){
808
655
          perror("add_environment");
809
656
        }
810
657
      }
811
658
    }
812
659
    
813
 
    int pipefd[2];
 
660
    int pipefd[2]; 
814
661
    ret = pipe(pipefd);
815
662
    if (ret == -1){
816
663
      perror("pipe");
817
664
      exitstatus = EXIT_FAILURE;
818
 
      goto fallback;
 
665
      goto end;
819
666
    }
820
 
    /* Ask OS to automatic close the pipe on exec */
821
667
    ret = set_cloexec_flag(pipefd[0]);
822
668
    if(ret < 0){
823
669
      perror("set_cloexec_flag");
824
670
      exitstatus = EXIT_FAILURE;
825
 
      goto fallback;
 
671
      goto end;
826
672
    }
827
673
    ret = set_cloexec_flag(pipefd[1]);
828
674
    if(ret < 0){
829
675
      perror("set_cloexec_flag");
830
676
      exitstatus = EXIT_FAILURE;
831
 
      goto fallback;
 
677
      goto end;
832
678
    }
833
679
    /* Block SIGCHLD until process is safely in process list */
834
680
    ret = sigprocmask (SIG_BLOCK, &sigchld_action.sa_mask, NULL);
835
681
    if(ret < 0){
836
682
      perror("sigprocmask");
837
683
      exitstatus = EXIT_FAILURE;
838
 
      goto fallback;
 
684
      goto end;
839
685
    }
840
 
    /* Starting a new process to be watched */
 
686
    // Starting a new process to be watched
841
687
    pid_t pid = fork();
842
688
    if(pid == -1){
843
689
      perror("fork");
844
690
      exitstatus = EXIT_FAILURE;
845
 
      goto fallback;
 
691
      goto end;
846
692
    }
847
693
    if(pid == 0){
848
694
      /* this is the child process */
881
727
      }
882
728
      /* no return */
883
729
    }
884
 
    /* Parent process */
885
 
    close(pipefd[1]);           /* Close unused write end of pipe */
 
730
    /* parent process */
886
731
    free(filename);
887
 
    plugin *new_plugin = getplugin(dirst->d_name);
888
 
    if (new_plugin == NULL){
889
 
      perror("getplugin");
 
732
    close(pipefd[1]);           /* close unused write end of pipe */
 
733
    process *new_process = malloc(sizeof(process));
 
734
    if (new_process == NULL){
 
735
      perror("malloc");
890
736
      ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
891
737
      if(ret < 0){
892
 
        perror("sigprocmask");
 
738
        perror("sigprocmask");
893
739
      }
894
740
      exitstatus = EXIT_FAILURE;
895
 
      goto fallback;
 
741
      goto end;
896
742
    }
897
743
    
898
 
    new_plugin->pid = pid;
899
 
    new_plugin->fd = pipefd[0];
900
 
    
 
744
    *new_process = (struct process){ .pid = pid,
 
745
                                     .fd = pipefd[0],
 
746
                                     .next = process_list };
 
747
    // List handling
 
748
    process_list = new_process;
901
749
    /* Unblock SIGCHLD so signal handler can be run if this process
902
750
       has already completed */
903
751
    ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
904
752
    if(ret < 0){
905
753
      perror("sigprocmask");
906
754
      exitstatus = EXIT_FAILURE;
907
 
      goto fallback;
908
 
    }
909
 
    
910
 
    FD_SET(new_plugin->fd, &rfds_all);
911
 
    
912
 
    if (maxfd < new_plugin->fd){
913
 
      maxfd = new_plugin->fd;
914
 
    }
915
 
    
 
755
      goto end;
 
756
    }
 
757
    
 
758
    FD_SET(new_process->fd, &rfds_all);
 
759
    
 
760
    if (maxfd < new_process->fd){
 
761
      maxfd = new_process->fd;
 
762
    }
 
763
    
 
764
  }
 
765
  
 
766
  /* Free the plugin list */
 
767
  for(plugin *next; plugin_list != NULL; plugin_list = next){
 
768
    next = plugin_list->next;
 
769
    free(plugin_list->argv);
 
770
    if(plugin_list->environ[0] != NULL){
 
771
      for(char **e = plugin_list->environ; *e != NULL; e++){
 
772
        free(*e);
 
773
      }
 
774
    }
 
775
    free(plugin_list);
916
776
  }
917
777
  
918
778
  closedir(dir);
919
779
  dir = NULL;
920
 
 
921
 
  for(plugin *p = plugin_list; p != NULL; p = p->next){
922
 
    if(p->pid != 0){
923
 
      break;
924
 
    }
925
 
    if(p->next == NULL){
926
 
      fprintf(stderr, "No plugin processes started. Incorrect plugin"
927
 
              " directory?\n");
928
 
      free_plugin_list();
929
 
    }
 
780
    
 
781
  if (process_list == NULL){
 
782
    fprintf(stderr, "No plugin processes started. Incorrect plugin"
 
783
            " directory?\n");
 
784
    process_list = NULL;
930
785
  }
931
 
 
932
 
  /* Main loop while running plugins exist */
933
 
  while(plugin_list){
 
786
  while(process_list){
934
787
    fd_set rfds = rfds_all;
935
788
    int select_ret = select(maxfd+1, &rfds, NULL, NULL, NULL);
936
789
    if (select_ret == -1){
937
790
      perror("select");
938
791
      exitstatus = EXIT_FAILURE;
939
 
      goto fallback;
 
792
      goto end;
940
793
    }
941
794
    /* OK, now either a process completed, or something can be read
942
795
       from one of them */
943
 
    for(plugin *proc = plugin_list; proc != NULL; proc = proc->next){
 
796
    for(process *proc = process_list; proc ; proc = proc->next){
944
797
      /* Is this process completely done? */
945
798
      if(proc->eof and proc->completed){
946
799
        /* Only accept the plugin output if it exited cleanly */
947
800
        if(not WIFEXITED(proc->status)
948
801
           or WEXITSTATUS(proc->status) != 0){
949
802
          /* Bad exit by plugin */
950
 
 
951
803
          if(debug){
952
804
            if(WIFEXITED(proc->status)){
953
805
              fprintf(stderr, "Plugin %u exited with status %d\n",
962
814
                      (unsigned int) (proc->pid));
963
815
            }
964
816
          }
965
 
          
966
817
          /* Remove the plugin */
967
818
          FD_CLR(proc->fd, &rfds_all);
968
 
 
969
819
          /* Block signal while modifying process_list */
970
 
          ret = sigprocmask(SIG_BLOCK, &sigchld_action.sa_mask, NULL);
 
820
          ret = sigprocmask (SIG_BLOCK, &sigchld_action.sa_mask, NULL);
971
821
          if(ret < 0){
972
822
            perror("sigprocmask");
973
823
            exitstatus = EXIT_FAILURE;
974
 
            goto fallback;
975
 
          }
976
 
          free_plugin(proc);
 
824
            goto end;
 
825
          }
 
826
          /* Delete this process entry from the list */
 
827
          if(process_list == proc){
 
828
            /* First one - simple */
 
829
            process_list = proc->next;
 
830
          } else {
 
831
            /* Second one or later */
 
832
            for(process *p = process_list; p != NULL; p = p->next){
 
833
              if(p->next == proc){
 
834
                p->next = proc->next;
 
835
                break;
 
836
              }
 
837
            }
 
838
          }
977
839
          /* We are done modifying process list, so unblock signal */
978
840
          ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask,
979
841
                             NULL);
980
842
          if(ret < 0){
981
843
            perror("sigprocmask");
982
 
            exitstatus = EXIT_FAILURE;
983
 
            goto fallback;
984
 
          }
985
 
          
986
 
          if(plugin_list == NULL){
987
 
            break;
988
 
          }
989
 
          continue;
 
844
          }
 
845
          free(proc->buffer);
 
846
          free(proc);
 
847
          /* We deleted this process from the list, so we can't go
 
848
             proc->next.  Therefore, start over from the beginning of
 
849
             the process list */
 
850
          break;
990
851
        }
991
 
        
992
852
        /* This process exited nicely, so print its buffer */
993
853
 
994
 
        bool bret = print_out_password(proc->buffer,
995
 
                                       proc->buffer_length);
 
854
        bool bret = print_out_password(proc->buffer, proc->buffer_length);
996
855
        if(not bret){
997
856
          perror("print_out_password");
998
857
          exitstatus = EXIT_FAILURE;
999
858
        }
1000
 
        goto fallback;
 
859
        goto end;
1001
860
      }
1002
 
      
1003
861
      /* This process has not completed.  Does it have any output? */
1004
862
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){
1005
863
        /* This process had nothing to say at this time */
1012
870
        if (proc->buffer == NULL){
1013
871
          perror("malloc");
1014
872
          exitstatus = EXIT_FAILURE;
1015
 
          goto fallback;
 
873
          goto end;
1016
874
        }
1017
875
        proc->buffer_size += BUFFER_SIZE;
1018
876
      }
1033
891
  }
1034
892
 
1035
893
 
1036
 
 fallback:
 
894
 end:
1037
895
  
1038
 
  if(plugin_list == NULL or exitstatus != EXIT_SUCCESS){
1039
 
    /* Fallback if all plugins failed, none are found or an error
1040
 
       occured */
 
896
  if(process_list == NULL or exitstatus != EXIT_SUCCESS){
 
897
    /* Fallback if all plugins failed, none are found or an error occured */
1041
898
    bool bret;
1042
899
    fprintf(stderr, "Going to fallback mode using getpass(3)\n");
1043
900
    char *passwordbuffer = getpass("Password: ");
1045
902
    if(not bret){
1046
903
      perror("print_out_password");
1047
904
      exitstatus = EXIT_FAILURE;
 
905
      goto end;
1048
906
    }
1049
907
  }
1050
908
  
1051
909
  /* Restore old signal handler */
1052
 
  ret = sigaction(SIGCHLD, &old_sigchld_action, NULL);
1053
 
  if(ret == -1){
1054
 
    perror("sigaction");
1055
 
    exitstatus = EXIT_FAILURE;
1056
 
  }
1057
 
 
1058
 
  if(custom_argv != NULL){
1059
 
    for(char **arg = custom_argv+1; *arg != NULL; arg++){
1060
 
      free(*arg);
 
910
  sigaction(SIGCHLD, &old_sigchld_action, NULL);
 
911
  
 
912
  free(custom_argv);
 
913
  
 
914
  /* Free the plugin list */
 
915
  for(plugin *next; plugin_list != NULL; plugin_list = next){
 
916
    next = plugin_list->next;
 
917
    free(plugin_list->argv);
 
918
    if(plugin_list->environ[0] != NULL){
 
919
      for(char **e = plugin_list->environ; *e != NULL; e++){
 
920
        free(*e);
 
921
      }
1061
922
    }
1062
 
    free(custom_argv);
 
923
    free(plugin_list->environ);
 
924
    free(plugin_list);
1063
925
  }
1064
926
  
1065
927
  if(dir != NULL){
1067
929
  }
1068
930
  
1069
931
  /* Free the process list and kill the processes */
1070
 
  for(plugin *p = plugin_list; p != NULL; p = p->next){
1071
 
    if(p->pid != 0){
1072
 
      close(p->fd);
1073
 
      ret = kill(p->pid, SIGTERM);
1074
 
      if(ret == -1 and errno != ESRCH){
1075
 
        /* Set-uid proccesses might not get closed */
1076
 
        perror("kill");
1077
 
      }
 
932
  for(process *next; process_list != NULL; process_list = next){
 
933
    next = process_list->next;
 
934
    close(process_list->fd);
 
935
    ret = kill(process_list->pid, SIGTERM);
 
936
    if(ret == -1 and errno != ESRCH){
 
937
      /* set-uid proccesses migth not get closed */
 
938
      perror("kill");
1078
939
    }
 
940
    free(process_list->buffer);
 
941
    free(process_list);
1079
942
  }
1080
943
  
1081
944
  /* Wait for any remaining child processes to terminate */
1085
948
  if(errno != ECHILD){
1086
949
    perror("wait");
1087
950
  }
1088
 
 
1089
 
  free_plugin_list();
1090
 
  
1091
 
  free(plugindir);
1092
 
  free(argfile);
1093
951
  
1094
952
  return exitstatus;
1095
953
}