/mandos/release

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

« 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 a 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));
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
349
        if(p_name[0] == '\0'){
423
359
            if(p[0] == '\0'){
424
360
              continue;
425
361
            }
426
 
            if(not add_argument(getplugin(p_name), p)){
 
362
            if(not add_argument(getplugin(p_name, plugins), p)){
427
363
              perror("add_argument");
428
364
              return ARGP_ERR_UNKNOWN;
429
365
            }
431
367
        }
432
368
      }
433
369
      break;
434
 
    case 'E':                   /* --env-for */
 
370
    case 'f':
435
371
      if(arg == NULL){
436
372
        break;
437
373
      }
445
381
          break;
446
382
        }
447
383
        envdef++;
448
 
        if(not add_environment(getplugin(p_name), envdef, true)){
 
384
        if(not add_environment(getplugin(p_name, plugins), envdef)){
449
385
          perror("add_environment");
450
386
        }
451
387
      }
452
388
      break;
453
 
    case 'd':                   /* --disable */
 
389
    case 'd':
454
390
      if (arg != NULL){
455
 
        plugin *p = getplugin(arg);
 
391
        plugin *p = getplugin(arg, plugins);
456
392
        if(p == NULL){
457
393
          return ARGP_ERR_UNKNOWN;
458
394
        }
459
395
        p->disabled = true;
460
396
      }
461
397
      break;
462
 
    case 'e':                   /* --enable */
463
 
      if (arg != NULL){
464
 
        plugin *p = getplugin(arg);
465
 
        if(p == NULL){
466
 
          return ARGP_ERR_UNKNOWN;
467
 
        }
468
 
        p->disabled = false;
469
 
      }
470
 
      break;
471
 
    case 128:                   /* --plugin-dir */
472
 
      plugindir = strdup(arg);
473
 
      if(plugindir == NULL){
474
 
        perror("strdup");
475
 
      }      
476
 
      break;
477
 
    case 129:                   /* --config-file */
478
 
      /* This is already done by parse_opt_config_file() */
479
 
      break;
480
 
    case 130:                   /* --userid */
 
398
    case 128:
 
399
      plugindir = arg;
 
400
      break;
 
401
    case 129:
481
402
      uid = (uid_t)strtol(arg, NULL, 10);
482
403
      break;
483
 
    case 131:                   /* --groupid */
 
404
    case 130:
484
405
      gid = (gid_t)strtol(arg, NULL, 10);
485
406
      break;
486
 
    case 132:                   /* --debug */
 
407
    case 131:
487
408
      debug = true;
488
409
      break;
489
410
    case ARGP_KEY_ARG:
497
418
    return 0;
498
419
  }
499
420
  
500
 
  /* This option parser is the same as parse_opt() above, except it
501
 
     ignores everything but the --config-file option. */
502
 
  error_t parse_opt_config_file (int key, char *arg,
503
 
                                 __attribute__((unused))
504
 
                                 struct argp_state *state) {
505
 
    switch (key) {
506
 
    case 'g':                   /* --global-options */
507
 
    case 'G':                   /* --global-env */
508
 
    case 'o':                   /* --options-for */
509
 
    case 'E':                   /* --env-for */
510
 
    case 'd':                   /* --disable */
511
 
    case 'e':                   /* --enable */
512
 
    case 128:                   /* --plugin-dir */
513
 
      break;
514
 
    case 129:                   /* --config-file */
515
 
      argfile = strdup(arg);
516
 
      if(argfile == NULL){
517
 
        perror("strdup");
518
 
      }
519
 
      break;      
520
 
    case 130:                   /* --userid */
521
 
    case 131:                   /* --groupid */
522
 
    case 132:                   /* --debug */
523
 
    case ARGP_KEY_ARG:
524
 
    case ARGP_KEY_END:
525
 
      break;
526
 
    default:
527
 
      return ARGP_ERR_UNKNOWN;
528
 
    }
529
 
    return 0;
530
 
  }
 
421
  plugin *plugin_list = NULL;
531
422
  
532
 
  struct argp argp = { .options = options,
533
 
                       .parser = parse_opt_config_file,
534
 
                       .args_doc = "",
 
423
  struct argp argp = { .options = options, .parser = parse_opt,
 
424
                       .args_doc = "[+PLUS_SEPARATED_OPTIONS]",
535
425
                       .doc = "Mandos plugin runner -- Run plugins" };
536
426
  
537
 
  /* Parse using the parse_opt_config_file in order to get the custom
538
 
     config file location, if any. */
539
 
  ret = argp_parse (&argp, argc, argv, ARGP_IN_ORDER, 0, NULL);
 
427
  ret = argp_parse (&argp, argc, argv, 0, 0, &plugin_list);
540
428
  if (ret == ARGP_ERR_UNKNOWN){
541
429
    fprintf(stderr, "Unknown error while parsing arguments\n");
542
430
    exitstatus = EXIT_FAILURE;
543
 
    goto fallback;
 
431
    goto end;
544
432
  }
545
 
  
546
 
  /* Reset to the normal argument parser */
547
 
  argp.parser = parse_opt;
548
 
  
549
 
  /* Open the configfile if available */
550
 
  if (argfile == NULL){
551
 
    conffp = fopen(AFILE, "r");
552
 
  } else {
553
 
    conffp = fopen(argfile, "r");
554
 
  }  
 
433
 
 
434
  conffp = fopen(argfile, "r");
555
435
  if(conffp != NULL){
556
436
    char *org_line = NULL;
557
 
    char *p, *arg, *new_arg, *line;
558
437
    size_t size = 0;
559
438
    ssize_t sret;
 
439
    char *p, *arg, *new_arg, *line;
560
440
    const char whitespace_delims[] = " \r\t\f\v\n";
561
441
    const char comment_delim[] = "#";
562
442
 
563
 
    custom_argc = 1;
564
 
    custom_argv = malloc(sizeof(char*) * 2);
565
 
    if(custom_argv == NULL){
566
 
      perror("malloc");
567
 
      exitstatus = EXIT_FAILURE;
568
 
      goto fallback;
569
 
    }
570
 
    custom_argv[0] = argv[0];
571
 
    custom_argv[1] = NULL;
572
 
 
573
 
    /* for each line in the config file, strip whitespace and ignore
574
 
       commented text */
575
443
    while(true){
576
444
      sret = getline(&org_line, &size, conffp);
577
445
      if(sret == -1){
585
453
          continue;
586
454
        }
587
455
        new_arg = strdup(p);
588
 
        if(new_arg == NULL){
589
 
          perror("strdup");
590
 
          exitstatus = EXIT_FAILURE;
591
 
          free(org_line);
592
 
          goto fallback;
593
 
        }
594
 
        
595
 
        custom_argc += 1;
596
 
        custom_argv = realloc(custom_argv, sizeof(char *)
597
 
                              * ((unsigned int) custom_argc + 1));
598
 
        if(custom_argv == NULL){
599
 
          perror("realloc");
600
 
          exitstatus = EXIT_FAILURE;
601
 
          free(org_line);
602
 
          goto fallback;
603
 
        }
604
 
        custom_argv[custom_argc-1] = new_arg;
605
 
        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
        }
606
462
      }
607
463
    }
608
464
    free(org_line);
609
 
  } else {
 
465
  } else{
610
466
    /* Check for harmful errors and go to fallback. Other errors might
611
467
       not affect opening plugins */
612
468
    if (errno == EMFILE or errno == ENFILE or errno == ENOMEM){
613
469
      perror("fopen");
614
470
      exitstatus = EXIT_FAILURE;
615
 
      goto fallback;
 
471
      goto end;
616
472
    }
617
473
  }
618
 
  /* If there was any arguments from configuration file,
619
 
     pass them to parser as command arguments */
 
474
 
620
475
  if(custom_argv != NULL){
621
 
    ret = argp_parse (&argp, custom_argc, custom_argv, ARGP_IN_ORDER,
622
 
                      0, NULL);
 
476
    custom_argv[0] = argv[0];
 
477
    ret = argp_parse (&argp, custom_argc, custom_argv, 0, 0, &plugin_list);
623
478
    if (ret == ARGP_ERR_UNKNOWN){
624
479
      fprintf(stderr, "Unknown error while parsing arguments\n");
625
480
      exitstatus = EXIT_FAILURE;
626
 
      goto fallback;
 
481
      goto end;
627
482
    }
628
483
  }
629
484
  
630
 
  /* Parse actual command line arguments, to let them override the
631
 
     config file */
632
 
  ret = argp_parse (&argp, argc, argv, ARGP_IN_ORDER, 0, NULL);
633
 
  if (ret == ARGP_ERR_UNKNOWN){
634
 
    fprintf(stderr, "Unknown error while parsing arguments\n");
635
 
    exitstatus = EXIT_FAILURE;
636
 
    goto fallback;
637
 
  }
638
 
  
639
485
  if(debug){
640
486
    for(plugin *p = plugin_list; p != NULL; p=p->next){
641
487
      fprintf(stderr, "Plugin: %s has %d arguments\n",
650
496
    }
651
497
  }
652
498
  
653
 
  /* Strip permissions down to nobody */
654
499
  ret = setuid(uid);
655
500
  if (ret == -1){
656
501
    perror("setuid");
657
 
  }  
 
502
  }
 
503
  
658
504
  setgid(gid);
659
505
  if (ret == -1){
660
506
    perror("setgid");
661
507
  }
662
508
  
663
 
  if (plugindir == NULL){
664
 
    dir = opendir(PDIR);
665
 
  } else {
666
 
    dir = opendir(plugindir);
667
 
  }
668
 
  
 
509
  dir = opendir(plugindir);
669
510
  if(dir == NULL){
670
511
    perror("Could not open plugin dir");
671
512
    exitstatus = EXIT_FAILURE;
672
 
    goto fallback;
 
513
    goto end;
673
514
  }
674
515
  
675
516
  /* Set the FD_CLOEXEC flag on the directory, if possible */
680
521
      if(ret < 0){
681
522
        perror("set_cloexec_flag");
682
523
        exitstatus = EXIT_FAILURE;
683
 
        goto fallback;
 
524
        goto end;
684
525
      }
685
526
    }
686
527
  }
687
528
  
688
529
  FD_ZERO(&rfds_all);
689
530
  
690
 
  /* Read and execute any executable in the plugin directory*/
691
531
  while(true){
692
532
    dirst = readdir(dir);
693
533
    
694
 
    /* All directory entries have been processed */
 
534
    // All directory entries have been processed
695
535
    if(dirst == NULL){
696
536
      if (errno == EBADF){
697
537
        perror("readdir");
698
538
        exitstatus = EXIT_FAILURE;
699
 
        goto fallback;
 
539
        goto end;
700
540
      }
701
541
      break;
702
542
    }
703
543
    
704
544
    d_name_len = strlen(dirst->d_name);
705
545
    
706
 
    /* Ignore dotfiles, backup files and other junk */
 
546
    // Ignore dotfiles, backup files and other junk
707
547
    {
708
548
      bool bad_name = false;
709
549
      
724
564
          break;
725
565
        }
726
566
      }
 
567
      
727
568
      if(bad_name){
728
569
        continue;
729
570
      }
 
571
      
730
572
      for(const char **suf = bad_suffixes; *suf != NULL; suf++){
731
573
        size_t suf_len = strlen(*suf);
732
574
        if((d_name_len >= suf_len)
759
601
      free(filename);
760
602
      continue;
761
603
    }
762
 
 
763
 
    /* Ignore non-executable files */
 
604
    
764
605
    if (not S_ISREG(st.st_mode) or (access(filename, X_OK) != 0)){
765
606
      if(debug){
766
607
        fprintf(stderr, "Ignoring plugin dir entry \"%s\""
769
610
      free(filename);
770
611
      continue;
771
612
    }
772
 
    
773
 
    plugin *p = getplugin(dirst->d_name);
 
613
    plugin *p = getplugin(dirst->d_name, &plugin_list);
774
614
    if(p == NULL){
775
615
      perror("getplugin");
776
616
      free(filename);
786
626
    }
787
627
    {
788
628
      /* Add global arguments to argument list for this plugin */
789
 
      plugin *g = getplugin(NULL);
 
629
      plugin *g = getplugin(NULL, &plugin_list);
790
630
      if(g != NULL){
791
631
        for(char **a = g->argv + 1; *a != NULL; a++){
792
632
          if(not add_argument(p, *a)){
795
635
        }
796
636
        /* Add global environment variables */
797
637
        for(char **e = g->environ; *e != NULL; e++){
798
 
          if(not add_environment(p, *e, false)){
 
638
          if(not add_environment(p, *e)){
799
639
            perror("add_environment");
800
640
          }
801
641
        }
806
646
       process, too. */
807
647
    if(p->environ[0] != NULL){
808
648
      for(char **e = environ; *e != NULL; e++){
809
 
        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)){
810
655
          perror("add_environment");
811
656
        }
812
657
      }
813
658
    }
814
659
    
815
 
    int pipefd[2];
 
660
    int pipefd[2]; 
816
661
    ret = pipe(pipefd);
817
662
    if (ret == -1){
818
663
      perror("pipe");
819
664
      exitstatus = EXIT_FAILURE;
820
 
      goto fallback;
 
665
      goto end;
821
666
    }
822
 
    /* Ask OS to automatic close the pipe on exec */
823
667
    ret = set_cloexec_flag(pipefd[0]);
824
668
    if(ret < 0){
825
669
      perror("set_cloexec_flag");
826
670
      exitstatus = EXIT_FAILURE;
827
 
      goto fallback;
 
671
      goto end;
828
672
    }
829
673
    ret = set_cloexec_flag(pipefd[1]);
830
674
    if(ret < 0){
831
675
      perror("set_cloexec_flag");
832
676
      exitstatus = EXIT_FAILURE;
833
 
      goto fallback;
 
677
      goto end;
834
678
    }
835
679
    /* Block SIGCHLD until process is safely in process list */
836
680
    ret = sigprocmask (SIG_BLOCK, &sigchld_action.sa_mask, NULL);
837
681
    if(ret < 0){
838
682
      perror("sigprocmask");
839
683
      exitstatus = EXIT_FAILURE;
840
 
      goto fallback;
 
684
      goto end;
841
685
    }
842
 
    /* Starting a new process to be watched */
 
686
    // Starting a new process to be watched
843
687
    pid_t pid = fork();
844
688
    if(pid == -1){
845
689
      perror("fork");
846
690
      exitstatus = EXIT_FAILURE;
847
 
      goto fallback;
 
691
      goto end;
848
692
    }
849
693
    if(pid == 0){
850
694
      /* this is the child process */
883
727
      }
884
728
      /* no return */
885
729
    }
886
 
    /* Parent process */
887
 
    close(pipefd[1]);           /* Close unused write end of pipe */
 
730
    /* parent process */
888
731
    free(filename);
889
 
    plugin *new_plugin = getplugin(dirst->d_name);
890
 
    if (new_plugin == NULL){
891
 
      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");
892
736
      ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
893
737
      if(ret < 0){
894
 
        perror("sigprocmask");
 
738
        perror("sigprocmask");
895
739
      }
896
740
      exitstatus = EXIT_FAILURE;
897
 
      goto fallback;
 
741
      goto end;
898
742
    }
899
743
    
900
 
    new_plugin->pid = pid;
901
 
    new_plugin->fd = pipefd[0];
902
 
    
 
744
    *new_process = (struct process){ .pid = pid,
 
745
                                     .fd = pipefd[0],
 
746
                                     .next = process_list };
 
747
    // List handling
 
748
    process_list = new_process;
903
749
    /* Unblock SIGCHLD so signal handler can be run if this process
904
750
       has already completed */
905
751
    ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
906
752
    if(ret < 0){
907
753
      perror("sigprocmask");
908
754
      exitstatus = EXIT_FAILURE;
909
 
      goto fallback;
910
 
    }
911
 
    
912
 
    FD_SET(new_plugin->fd, &rfds_all);
913
 
    
914
 
    if (maxfd < new_plugin->fd){
915
 
      maxfd = new_plugin->fd;
916
 
    }
917
 
    
 
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);
918
776
  }
919
777
  
920
778
  closedir(dir);
921
779
  dir = NULL;
922
 
 
923
 
  for(plugin *p = plugin_list; p != NULL; p = p->next){
924
 
    if(p->pid != 0){
925
 
      break;
926
 
    }
927
 
    if(p->next == NULL){
928
 
      fprintf(stderr, "No plugin processes started. Incorrect plugin"
929
 
              " directory?\n");
930
 
      free_plugin_list();
931
 
    }
 
780
    
 
781
  if (process_list == NULL){
 
782
    fprintf(stderr, "No plugin processes started. Incorrect plugin"
 
783
            " directory?\n");
 
784
    process_list = NULL;
932
785
  }
933
 
 
934
 
  /* Main loop while running plugins exist */
935
 
  while(plugin_list){
 
786
  while(process_list){
936
787
    fd_set rfds = rfds_all;
937
788
    int select_ret = select(maxfd+1, &rfds, NULL, NULL, NULL);
938
789
    if (select_ret == -1){
939
790
      perror("select");
940
791
      exitstatus = EXIT_FAILURE;
941
 
      goto fallback;
 
792
      goto end;
942
793
    }
943
794
    /* OK, now either a process completed, or something can be read
944
795
       from one of them */
945
 
    for(plugin *proc = plugin_list; proc != NULL; proc = proc->next){
 
796
    for(process *proc = process_list; proc ; proc = proc->next){
946
797
      /* Is this process completely done? */
947
798
      if(proc->eof and proc->completed){
948
799
        /* Only accept the plugin output if it exited cleanly */
949
800
        if(not WIFEXITED(proc->status)
950
801
           or WEXITSTATUS(proc->status) != 0){
951
802
          /* Bad exit by plugin */
952
 
 
953
803
          if(debug){
954
804
            if(WIFEXITED(proc->status)){
955
805
              fprintf(stderr, "Plugin %u exited with status %d\n",
964
814
                      (unsigned int) (proc->pid));
965
815
            }
966
816
          }
967
 
          
968
817
          /* Remove the plugin */
969
818
          FD_CLR(proc->fd, &rfds_all);
970
 
 
971
819
          /* Block signal while modifying process_list */
972
 
          ret = sigprocmask(SIG_BLOCK, &sigchld_action.sa_mask, NULL);
 
820
          ret = sigprocmask (SIG_BLOCK, &sigchld_action.sa_mask, NULL);
973
821
          if(ret < 0){
974
822
            perror("sigprocmask");
975
823
            exitstatus = EXIT_FAILURE;
976
 
            goto fallback;
977
 
          }
978
 
          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
          }
979
839
          /* We are done modifying process list, so unblock signal */
980
840
          ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask,
981
841
                             NULL);
982
842
          if(ret < 0){
983
843
            perror("sigprocmask");
984
 
            exitstatus = EXIT_FAILURE;
985
 
            goto fallback;
986
 
          }
987
 
          
988
 
          if(plugin_list == NULL){
989
 
            break;
990
 
          }
991
 
          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;
992
851
        }
993
 
        
994
852
        /* This process exited nicely, so print its buffer */
995
853
 
996
 
        bool bret = print_out_password(proc->buffer,
997
 
                                       proc->buffer_length);
 
854
        bool bret = print_out_password(proc->buffer, proc->buffer_length);
998
855
        if(not bret){
999
856
          perror("print_out_password");
1000
857
          exitstatus = EXIT_FAILURE;
1001
858
        }
1002
 
        goto fallback;
 
859
        goto end;
1003
860
      }
1004
 
      
1005
861
      /* This process has not completed.  Does it have any output? */
1006
862
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){
1007
863
        /* This process had nothing to say at this time */
1014
870
        if (proc->buffer == NULL){
1015
871
          perror("malloc");
1016
872
          exitstatus = EXIT_FAILURE;
1017
 
          goto fallback;
 
873
          goto end;
1018
874
        }
1019
875
        proc->buffer_size += BUFFER_SIZE;
1020
876
      }
1035
891
  }
1036
892
 
1037
893
 
1038
 
 fallback:
 
894
 end:
1039
895
  
1040
 
  if(plugin_list == NULL or exitstatus != EXIT_SUCCESS){
1041
 
    /* Fallback if all plugins failed, none are found or an error
1042
 
       occured */
 
896
  if(process_list == NULL or exitstatus != EXIT_SUCCESS){
 
897
    /* Fallback if all plugins failed, none are found or an error occured */
1043
898
    bool bret;
1044
899
    fprintf(stderr, "Going to fallback mode using getpass(3)\n");
1045
900
    char *passwordbuffer = getpass("Password: ");
1047
902
    if(not bret){
1048
903
      perror("print_out_password");
1049
904
      exitstatus = EXIT_FAILURE;
 
905
      goto end;
1050
906
    }
1051
907
  }
1052
908
  
1053
909
  /* Restore old signal handler */
1054
 
  ret = sigaction(SIGCHLD, &old_sigchld_action, NULL);
1055
 
  if(ret == -1){
1056
 
    perror("sigaction");
1057
 
    exitstatus = EXIT_FAILURE;
1058
 
  }
1059
 
 
1060
 
  if(custom_argv != NULL){
1061
 
    for(char **arg = custom_argv+1; *arg != NULL; arg++){
1062
 
      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
      }
1063
922
    }
1064
 
    free(custom_argv);
 
923
    free(plugin_list->environ);
 
924
    free(plugin_list);
1065
925
  }
1066
926
  
1067
927
  if(dir != NULL){
1069
929
  }
1070
930
  
1071
931
  /* Free the process list and kill the processes */
1072
 
  for(plugin *p = plugin_list; p != NULL; p = p->next){
1073
 
    if(p->pid != 0){
1074
 
      close(p->fd);
1075
 
      ret = kill(p->pid, SIGTERM);
1076
 
      if(ret == -1 and errno != ESRCH){
1077
 
        /* Set-uid proccesses might not get closed */
1078
 
        perror("kill");
1079
 
      }
 
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");
1080
939
    }
 
940
    free(process_list->buffer);
 
941
    free(process_list);
1081
942
  }
1082
943
  
1083
944
  /* Wait for any remaining child processes to terminate */
1087
948
  if(errno != ECHILD){
1088
949
    perror("wait");
1089
950
  }
1090
 
 
1091
 
  free_plugin_list();
1092
 
  
1093
 
  free(plugindir);
1094
 
  free(argfile);
1095
951
  
1096
952
  return exitstatus;
1097
953
}