/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 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;
 
215
  if(length>0 and buffer[length-1] == '\n'){
 
216
    length--;
 
217
  }
258
218
  for(size_t written = 0; written < length; written += (size_t)ret){
259
219
    ret = TEMP_FAILURE_RETRY(write(STDOUT_FILENO, buffer + written,
260
220
                                   length - written));
265
225
  return true;
266
226
}
267
227
 
268
 
/* Removes and free a plugin from the plugin list */
269
 
static void free_plugin(plugin *plugin_node){
270
 
  
271
 
  for(char **arg = plugin_node->argv; *arg != NULL; arg++){
272
 
    free(*arg);
273
 
  }
274
 
  free(plugin_node->argv);
275
 
  for(char **env = plugin_node->environ; *env != NULL; env++){
276
 
    free(*env);
277
 
  }
278
 
  free(plugin_node->environ);
279
 
  free(plugin_node->buffer);
280
 
 
281
 
  /* Removes the plugin from the singly-linked list */
282
 
  if(plugin_node == plugin_list){
283
 
    /* First one - simple */
284
 
    plugin_list = plugin_list->next;
285
 
  } else {
286
 
    /* Second one or later */
287
 
    for(plugin *p = plugin_list; p != NULL; p = p->next){
288
 
      if(p->next == plugin_node){
289
 
        p->next = plugin_node->next;
290
 
        break;
291
 
      }
 
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;
292
234
    }
293
 
  }
294
 
  
295
 
  free(plugin_node);
296
 
}
297
 
 
298
 
static void free_plugin_list(void){
299
 
  while(plugin_list != NULL){
300
 
    free_plugin(plugin_list);
301
 
  }
 
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;
302
247
}
303
248
 
304
249
int main(int argc, char *argv[]){
305
 
  char *plugindir = NULL;
306
 
  char *argfile = NULL;
 
250
  const char *plugindir = "/lib/mandos/plugins.d";
 
251
  const char *argfile = ARGFILE;
307
252
  FILE *conffp;
308
253
  size_t d_name_len;
309
254
  DIR *dir = NULL;
324
269
  /* Establish a signal handler */
325
270
  sigemptyset(&sigchld_action.sa_mask);
326
271
  ret = sigaddset(&sigchld_action.sa_mask, SIGCHLD);
327
 
  if(ret == -1){
 
272
  if(ret < 0){
328
273
    perror("sigaddset");
329
 
    exitstatus = EXIT_FAILURE;
330
 
    goto fallback;
 
274
    exit(EXIT_FAILURE);
331
275
  }
332
276
  ret = sigaction(SIGCHLD, &sigchld_action, &old_sigchld_action);
333
 
  if(ret == -1){
 
277
  if(ret < 0){
334
278
    perror("sigaction");
335
 
    exitstatus = EXIT_FAILURE;
336
 
    goto fallback;
 
279
    exit(EXIT_FAILURE);
337
280
  }
338
281
  
339
282
  /* The options we understand. */
341
284
    { .name = "global-options", .key = 'g',
342
285
      .arg = "OPTION[,OPTION[,...]]",
343
286
      .doc = "Options passed to all plugins" },
344
 
    { .name = "global-env", .key = 'G',
 
287
    { .name = "global-envs", .key = 'e',
345
288
      .arg = "VAR=value",
346
289
      .doc = "Environment variable passed to all plugins" },
347
290
    { .name = "options-for", .key = 'o',
348
291
      .arg = "PLUGIN:OPTION[,OPTION[,...]]",
349
292
      .doc = "Options passed only to specified plugin" },
350
 
    { .name = "env-for", .key = 'E',
 
293
    { .name = "envs-for", .key = 'f',
351
294
      .arg = "PLUGIN:ENV=value",
352
295
      .doc = "Environment variable passed to specified plugin" },
353
296
    { .name = "disable", .key = 'd',
354
297
      .arg = "PLUGIN",
355
298
      .doc = "Disable a specific plugin", .group = 1 },
356
 
    { .name = "enable", .key = 'e',
357
 
      .arg = "PLUGIN",
358
 
      .doc = "Enable a specific plugin", .group = 1 },
359
299
    { .name = "plugin-dir", .key = 128,
360
300
      .arg = "DIRECTORY",
361
301
      .doc = "Specify a different plugin directory", .group = 2 },
362
 
    { .name = "config-file", .key = 129,
363
 
      .arg = "FILE",
364
 
      .doc = "Specify a different configuration file", .group = 2 },
365
 
    { .name = "userid", .key = 130,
366
 
      .arg = "ID", .flags = 0,
367
 
      .doc = "User ID the plugins will run as", .group = 3 },
368
 
    { .name = "groupid", .key = 131,
369
 
      .arg = "ID", .flags = 0,
370
 
      .doc = "Group ID the plugins will run as", .group = 3 },
371
 
    { .name = "debug", .key = 132,
372
 
      .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 },
373
310
    { .name = NULL }
374
311
  };
375
312
  
376
 
  error_t parse_opt (int key, char *arg, __attribute__((unused))
377
 
                     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;
378
317
    switch (key) {
379
 
    case 'g':                   /* --global-options */
 
318
    case 'g':
380
319
      if (arg != NULL){
381
320
        char *p;
382
321
        while((p = strsep(&arg, ",")) != NULL){
383
322
          if(p[0] == '\0'){
384
323
            continue;
385
324
          }
386
 
          if(not add_argument(getplugin(NULL), p)){
 
325
          if(not add_argument(getplugin(NULL, plugins), p)){
387
326
            perror("add_argument");
388
327
            return ARGP_ERR_UNKNOWN;
389
328
          }
390
329
        }
391
330
      }
392
331
      break;
393
 
    case 'G':                   /* --global-env */
 
332
    case 'e':
394
333
      if(arg == NULL){
395
334
        break;
396
335
      }
397
 
      if(not add_environment(getplugin(NULL), arg, true)){
398
 
        perror("add_environment");
 
336
      {
 
337
        char *envdef = strdup(arg);
 
338
        if(envdef == NULL){
 
339
          break;
 
340
        }
 
341
        if(not add_environment(getplugin(NULL, plugins), envdef)){
 
342
          perror("add_environment");
 
343
        }
399
344
      }
400
345
      break;
401
 
    case 'o':                   /* --options-for */
 
346
    case 'o':
402
347
      if (arg != NULL){
403
348
        char *p_name = strsep(&arg, ":");
404
 
        if(p_name[0] == '\0' or arg == NULL){
 
349
        if(p_name[0] == '\0'){
405
350
          break;
406
351
        }
407
352
        char *opt = strsep(&arg, ":");
408
 
        if(opt[0] == '\0' or opt == NULL){
 
353
        if(opt[0] == '\0'){
409
354
          break;
410
355
        }
411
 
        char *p;
412
 
        while((p = strsep(&opt, ",")) != NULL){
413
 
          if(p[0] == '\0'){
414
 
            continue;
415
 
          }
416
 
          if(not add_argument(getplugin(p_name), p)){
417
 
            perror("add_argument");
418
 
            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
            }
419
366
          }
420
367
        }
421
368
      }
422
369
      break;
423
 
    case 'E':                   /* --env-for */
 
370
    case 'f':
424
371
      if(arg == NULL){
425
372
        break;
426
373
      }
429
376
        if(envdef == NULL){
430
377
          break;
431
378
        }
432
 
        *envdef = '\0';
433
 
        if(not add_environment(getplugin(arg), envdef+1, true)){
 
379
        char *p_name = strndup(arg, (size_t) (envdef-arg));
 
380
        if(p_name == NULL){
 
381
          break;
 
382
        }
 
383
        envdef++;
 
384
        if(not add_environment(getplugin(p_name, plugins), envdef)){
434
385
          perror("add_environment");
435
386
        }
436
387
      }
437
388
      break;
438
 
    case 'd':                   /* --disable */
 
389
    case 'd':
439
390
      if (arg != NULL){
440
 
        plugin *p = getplugin(arg);
 
391
        plugin *p = getplugin(arg, plugins);
441
392
        if(p == NULL){
442
393
          return ARGP_ERR_UNKNOWN;
443
394
        }
444
395
        p->disabled = true;
445
396
      }
446
397
      break;
447
 
    case 'e':                   /* --enable */
448
 
      if (arg != NULL){
449
 
        plugin *p = getplugin(arg);
450
 
        if(p == NULL){
451
 
          return ARGP_ERR_UNKNOWN;
452
 
        }
453
 
        p->disabled = false;
454
 
      }
455
 
      break;
456
 
    case 128:                   /* --plugin-dir */
457
 
      free(plugindir);
458
 
      plugindir = strdup(arg);
459
 
      if(plugindir == NULL){
460
 
        perror("strdup");
461
 
      }      
462
 
      break;
463
 
    case 129:                   /* --config-file */
464
 
      /* This is already done by parse_opt_config_file() */
465
 
      break;
466
 
    case 130:                   /* --userid */
 
398
    case 128:
 
399
      plugindir = arg;
 
400
      break;
 
401
    case 129:
467
402
      uid = (uid_t)strtol(arg, NULL, 10);
468
403
      break;
469
 
    case 131:                   /* --groupid */
 
404
    case 130:
470
405
      gid = (gid_t)strtol(arg, NULL, 10);
471
406
      break;
472
 
    case 132:                   /* --debug */
 
407
    case 131:
473
408
      debug = true;
474
409
      break;
475
410
    case ARGP_KEY_ARG:
476
 
      /* Cryptsetup always passes an argument, which is an empty
477
 
         string if "none" was specified in /etc/crypttab.  So if
478
 
         argument was empty, we ignore it silently. */
479
 
      if(arg[0] != '\0'){
480
 
        fprintf(stderr, "Ignoring unknown argument \"%s\"\n", arg);
481
 
      }
482
 
      break;
483
 
    case ARGP_KEY_END:
484
 
      break;
485
 
    default:
486
 
      return ARGP_ERR_UNKNOWN;
487
 
    }
488
 
    return 0;
489
 
  }
490
 
  
491
 
  /* This option parser is the same as parse_opt() above, except it
492
 
     ignores everything but the --config-file option. */
493
 
  error_t parse_opt_config_file (int key, char *arg,
494
 
                                 __attribute__((unused))
495
 
                                 struct argp_state *state) {
496
 
    switch (key) {
497
 
    case 'g':                   /* --global-options */
498
 
    case 'G':                   /* --global-env */
499
 
    case 'o':                   /* --options-for */
500
 
    case 'E':                   /* --env-for */
501
 
    case 'd':                   /* --disable */
502
 
    case 'e':                   /* --enable */
503
 
    case 128:                   /* --plugin-dir */
504
 
      break;
505
 
    case 129:                   /* --config-file */
506
 
      free(argfile);
507
 
      argfile = strdup(arg);
508
 
      if(argfile == NULL){
509
 
        perror("strdup");
510
 
      }
511
 
      break;      
512
 
    case 130:                   /* --userid */
513
 
    case 131:                   /* --groupid */
514
 
    case 132:                   /* --debug */
515
 
    case ARGP_KEY_ARG:
516
 
    case ARGP_KEY_END:
517
 
      break;
518
 
    default:
519
 
      return ARGP_ERR_UNKNOWN;
520
 
    }
521
 
    return 0;
522
 
  }
523
 
  
524
 
  struct argp argp = { .options = options,
525
 
                       .parser = parse_opt_config_file,
526
 
                       .args_doc = "",
 
411
      fprintf(stderr, "Ignoring unknown argument \"%s\"\n", arg);
 
412
      break;
 
413
    case ARGP_KEY_END:
 
414
      break;
 
415
    default:
 
416
      return ARGP_ERR_UNKNOWN;
 
417
    }
 
418
    return 0;
 
419
  }
 
420
  
 
421
  plugin *plugin_list = NULL;
 
422
  
 
423
  struct argp argp = { .options = options, .parser = parse_opt,
 
424
                       .args_doc = "[+PLUS_SEPARATED_OPTIONS]",
527
425
                       .doc = "Mandos plugin runner -- Run plugins" };
528
426
  
529
 
  /* Parse using the parse_opt_config_file in order to get the custom
530
 
     config file location, if any. */
531
 
  ret = argp_parse (&argp, argc, argv, ARGP_IN_ORDER, 0, NULL);
 
427
  ret = argp_parse (&argp, argc, argv, 0, 0, &plugin_list);
532
428
  if (ret == ARGP_ERR_UNKNOWN){
533
429
    fprintf(stderr, "Unknown error while parsing arguments\n");
534
430
    exitstatus = EXIT_FAILURE;
535
 
    goto fallback;
 
431
    goto end;
536
432
  }
537
 
  
538
 
  /* Reset to the normal argument parser */
539
 
  argp.parser = parse_opt;
540
 
  
541
 
  /* Open the configfile if available */
542
 
  if (argfile == NULL){
543
 
    conffp = fopen(AFILE, "r");
544
 
  } else {
545
 
    conffp = fopen(argfile, "r");
546
 
  }  
 
433
 
 
434
  conffp = fopen(argfile, "r");
547
435
  if(conffp != NULL){
548
436
    char *org_line = NULL;
549
 
    char *p, *arg, *new_arg, *line;
550
437
    size_t size = 0;
551
438
    ssize_t sret;
 
439
    char *p, *arg, *new_arg, *line;
552
440
    const char whitespace_delims[] = " \r\t\f\v\n";
553
441
    const char comment_delim[] = "#";
554
442
 
555
 
    custom_argc = 1;
556
 
    custom_argv = malloc(sizeof(char*) * 2);
557
 
    if(custom_argv == NULL){
558
 
      perror("malloc");
559
 
      exitstatus = EXIT_FAILURE;
560
 
      goto fallback;
561
 
    }
562
 
    custom_argv[0] = argv[0];
563
 
    custom_argv[1] = NULL;
564
 
 
565
 
    /* for each line in the config file, strip whitespace and ignore
566
 
       commented text */
567
443
    while(true){
568
444
      sret = getline(&org_line, &size, conffp);
569
445
      if(sret == -1){
577
453
          continue;
578
454
        }
579
455
        new_arg = strdup(p);
580
 
        if(new_arg == NULL){
581
 
          perror("strdup");
582
 
          exitstatus = EXIT_FAILURE;
583
 
          free(org_line);
584
 
          goto fallback;
585
 
        }
586
 
        
587
 
        custom_argc += 1;
588
 
        custom_argv = realloc(custom_argv, sizeof(char *)
589
 
                              * ((unsigned int) custom_argc + 1));
590
 
        if(custom_argv == NULL){
591
 
          perror("realloc");
592
 
          exitstatus = EXIT_FAILURE;
593
 
          free(org_line);
594
 
          goto fallback;
595
 
        }
596
 
        custom_argv[custom_argc-1] = new_arg;
597
 
        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
        }
598
462
      }
599
463
    }
600
464
    free(org_line);
601
 
  } else {
 
465
  } else{
602
466
    /* Check for harmful errors and go to fallback. Other errors might
603
467
       not affect opening plugins */
604
468
    if (errno == EMFILE or errno == ENFILE or errno == ENOMEM){
605
469
      perror("fopen");
606
470
      exitstatus = EXIT_FAILURE;
607
 
      goto fallback;
 
471
      goto end;
608
472
    }
609
473
  }
610
 
  /* If there was any arguments from configuration file,
611
 
     pass them to parser as command arguments */
 
474
 
612
475
  if(custom_argv != NULL){
613
 
    ret = argp_parse (&argp, custom_argc, custom_argv, ARGP_IN_ORDER,
614
 
                      0, NULL);
 
476
    custom_argv[0] = argv[0];
 
477
    ret = argp_parse (&argp, custom_argc, custom_argv, 0, 0, &plugin_list);
615
478
    if (ret == ARGP_ERR_UNKNOWN){
616
479
      fprintf(stderr, "Unknown error while parsing arguments\n");
617
480
      exitstatus = EXIT_FAILURE;
618
 
      goto fallback;
 
481
      goto end;
619
482
    }
620
483
  }
621
484
  
622
 
  /* Parse actual command line arguments, to let them override the
623
 
     config file */
624
 
  ret = argp_parse (&argp, argc, argv, ARGP_IN_ORDER, 0, NULL);
625
 
  if (ret == ARGP_ERR_UNKNOWN){
626
 
    fprintf(stderr, "Unknown error while parsing arguments\n");
627
 
    exitstatus = EXIT_FAILURE;
628
 
    goto fallback;
629
 
  }
630
 
  
631
485
  if(debug){
632
486
    for(plugin *p = plugin_list; p != NULL; p=p->next){
633
487
      fprintf(stderr, "Plugin: %s has %d arguments\n",
642
496
    }
643
497
  }
644
498
  
645
 
  /* Strip permissions down to nobody */
646
499
  ret = setuid(uid);
647
500
  if (ret == -1){
648
501
    perror("setuid");
649
 
  }  
 
502
  }
 
503
  
650
504
  setgid(gid);
651
505
  if (ret == -1){
652
506
    perror("setgid");
653
507
  }
654
508
  
655
 
  if (plugindir == NULL){
656
 
    dir = opendir(PDIR);
657
 
  } else {
658
 
    dir = opendir(plugindir);
659
 
  }
660
 
  
 
509
  dir = opendir(plugindir);
661
510
  if(dir == NULL){
662
511
    perror("Could not open plugin dir");
663
512
    exitstatus = EXIT_FAILURE;
664
 
    goto fallback;
 
513
    goto end;
665
514
  }
666
515
  
667
516
  /* Set the FD_CLOEXEC flag on the directory, if possible */
672
521
      if(ret < 0){
673
522
        perror("set_cloexec_flag");
674
523
        exitstatus = EXIT_FAILURE;
675
 
        goto fallback;
 
524
        goto end;
676
525
      }
677
526
    }
678
527
  }
679
528
  
680
529
  FD_ZERO(&rfds_all);
681
530
  
682
 
  /* Read and execute any executable in the plugin directory*/
683
531
  while(true){
684
532
    dirst = readdir(dir);
685
533
    
686
 
    /* All directory entries have been processed */
 
534
    // All directory entries have been processed
687
535
    if(dirst == NULL){
688
536
      if (errno == EBADF){
689
537
        perror("readdir");
690
538
        exitstatus = EXIT_FAILURE;
691
 
        goto fallback;
 
539
        goto end;
692
540
      }
693
541
      break;
694
542
    }
695
543
    
696
544
    d_name_len = strlen(dirst->d_name);
697
545
    
698
 
    /* Ignore dotfiles, backup files and other junk */
 
546
    // Ignore dotfiles, backup files and other junk
699
547
    {
700
548
      bool bad_name = false;
701
549
      
716
564
          break;
717
565
        }
718
566
      }
 
567
      
719
568
      if(bad_name){
720
569
        continue;
721
570
      }
 
571
      
722
572
      for(const char **suf = bad_suffixes; *suf != NULL; suf++){
723
573
        size_t suf_len = strlen(*suf);
724
574
        if((d_name_len >= suf_len)
739
589
    }
740
590
 
741
591
    char *filename;
742
 
    if(plugindir == NULL){
743
 
      ret = asprintf(&filename, PDIR "/%s", dirst->d_name);
744
 
    } else {
745
 
      ret = asprintf(&filename, "%s/%s", plugindir, dirst->d_name);
746
 
    }
 
592
    ret = asprintf(&filename, "%s/%s", plugindir, dirst->d_name);
747
593
    if(ret < 0){
748
594
      perror("asprintf");
749
595
      continue;
755
601
      free(filename);
756
602
      continue;
757
603
    }
758
 
 
759
 
    /* Ignore non-executable files */
 
604
    
760
605
    if (not S_ISREG(st.st_mode) or (access(filename, X_OK) != 0)){
761
606
      if(debug){
762
607
        fprintf(stderr, "Ignoring plugin dir entry \"%s\""
765
610
      free(filename);
766
611
      continue;
767
612
    }
768
 
    
769
 
    plugin *p = getplugin(dirst->d_name);
 
613
    plugin *p = getplugin(dirst->d_name, &plugin_list);
770
614
    if(p == NULL){
771
615
      perror("getplugin");
772
616
      free(filename);
782
626
    }
783
627
    {
784
628
      /* Add global arguments to argument list for this plugin */
785
 
      plugin *g = getplugin(NULL);
 
629
      plugin *g = getplugin(NULL, &plugin_list);
786
630
      if(g != NULL){
787
631
        for(char **a = g->argv + 1; *a != NULL; a++){
788
632
          if(not add_argument(p, *a)){
791
635
        }
792
636
        /* Add global environment variables */
793
637
        for(char **e = g->environ; *e != NULL; e++){
794
 
          if(not add_environment(p, *e, false)){
 
638
          if(not add_environment(p, *e)){
795
639
            perror("add_environment");
796
640
          }
797
641
        }
802
646
       process, too. */
803
647
    if(p->environ[0] != NULL){
804
648
      for(char **e = environ; *e != NULL; e++){
805
 
        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)){
806
655
          perror("add_environment");
807
656
        }
808
657
      }
809
658
    }
810
659
    
811
 
    int pipefd[2];
 
660
    int pipefd[2]; 
812
661
    ret = pipe(pipefd);
813
662
    if (ret == -1){
814
663
      perror("pipe");
815
664
      exitstatus = EXIT_FAILURE;
816
 
      goto fallback;
 
665
      goto end;
817
666
    }
818
 
    /* Ask OS to automatic close the pipe on exec */
819
667
    ret = set_cloexec_flag(pipefd[0]);
820
668
    if(ret < 0){
821
669
      perror("set_cloexec_flag");
822
670
      exitstatus = EXIT_FAILURE;
823
 
      goto fallback;
 
671
      goto end;
824
672
    }
825
673
    ret = set_cloexec_flag(pipefd[1]);
826
674
    if(ret < 0){
827
675
      perror("set_cloexec_flag");
828
676
      exitstatus = EXIT_FAILURE;
829
 
      goto fallback;
 
677
      goto end;
830
678
    }
831
679
    /* Block SIGCHLD until process is safely in process list */
832
680
    ret = sigprocmask (SIG_BLOCK, &sigchld_action.sa_mask, NULL);
833
681
    if(ret < 0){
834
682
      perror("sigprocmask");
835
683
      exitstatus = EXIT_FAILURE;
836
 
      goto fallback;
 
684
      goto end;
837
685
    }
838
 
    /* Starting a new process to be watched */
 
686
    // Starting a new process to be watched
839
687
    pid_t pid = fork();
840
688
    if(pid == -1){
841
689
      perror("fork");
842
690
      exitstatus = EXIT_FAILURE;
843
 
      goto fallback;
 
691
      goto end;
844
692
    }
845
693
    if(pid == 0){
846
694
      /* this is the child process */
879
727
      }
880
728
      /* no return */
881
729
    }
882
 
    /* Parent process */
883
 
    close(pipefd[1]);           /* Close unused write end of pipe */
 
730
    /* parent process */
884
731
    free(filename);
885
 
    plugin *new_plugin = getplugin(dirst->d_name);
886
 
    if (new_plugin == NULL){
887
 
      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");
888
736
      ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
889
737
      if(ret < 0){
890
 
        perror("sigprocmask");
 
738
        perror("sigprocmask");
891
739
      }
892
740
      exitstatus = EXIT_FAILURE;
893
 
      goto fallback;
 
741
      goto end;
894
742
    }
895
743
    
896
 
    new_plugin->pid = pid;
897
 
    new_plugin->fd = pipefd[0];
898
 
    
 
744
    *new_process = (struct process){ .pid = pid,
 
745
                                     .fd = pipefd[0],
 
746
                                     .next = process_list };
 
747
    // List handling
 
748
    process_list = new_process;
899
749
    /* Unblock SIGCHLD so signal handler can be run if this process
900
750
       has already completed */
901
751
    ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
902
752
    if(ret < 0){
903
753
      perror("sigprocmask");
904
754
      exitstatus = EXIT_FAILURE;
905
 
      goto fallback;
906
 
    }
907
 
    
908
 
    FD_SET(new_plugin->fd, &rfds_all);
909
 
    
910
 
    if (maxfd < new_plugin->fd){
911
 
      maxfd = new_plugin->fd;
912
 
    }
913
 
    
 
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);
914
776
  }
915
777
  
916
778
  closedir(dir);
917
779
  dir = NULL;
918
 
 
919
 
  for(plugin *p = plugin_list; p != NULL; p = p->next){
920
 
    if(p->pid != 0){
921
 
      break;
922
 
    }
923
 
    if(p->next == NULL){
924
 
      fprintf(stderr, "No plugin processes started. Incorrect plugin"
925
 
              " directory?\n");
926
 
      free_plugin_list();
927
 
    }
 
780
    
 
781
  if (process_list == NULL){
 
782
    fprintf(stderr, "No plugin processes started. Incorrect plugin"
 
783
            " directory?\n");
 
784
    process_list = NULL;
928
785
  }
929
 
 
930
 
  /* Main loop while running plugins exist */
931
 
  while(plugin_list){
 
786
  while(process_list){
932
787
    fd_set rfds = rfds_all;
933
788
    int select_ret = select(maxfd+1, &rfds, NULL, NULL, NULL);
934
789
    if (select_ret == -1){
935
790
      perror("select");
936
791
      exitstatus = EXIT_FAILURE;
937
 
      goto fallback;
 
792
      goto end;
938
793
    }
939
794
    /* OK, now either a process completed, or something can be read
940
795
       from one of them */
941
 
    for(plugin *proc = plugin_list; proc != NULL;){
 
796
    for(process *proc = process_list; proc ; proc = proc->next){
942
797
      /* Is this process completely done? */
943
798
      if(proc->eof and proc->completed){
944
799
        /* Only accept the plugin output if it exited cleanly */
945
800
        if(not WIFEXITED(proc->status)
946
801
           or WEXITSTATUS(proc->status) != 0){
947
802
          /* Bad exit by plugin */
948
 
 
949
803
          if(debug){
950
804
            if(WIFEXITED(proc->status)){
951
805
              fprintf(stderr, "Plugin %u exited with status %d\n",
960
814
                      (unsigned int) (proc->pid));
961
815
            }
962
816
          }
963
 
          
964
817
          /* Remove the plugin */
965
818
          FD_CLR(proc->fd, &rfds_all);
966
 
 
967
819
          /* Block signal while modifying process_list */
968
 
          ret = sigprocmask(SIG_BLOCK, &sigchld_action.sa_mask, NULL);
 
820
          ret = sigprocmask (SIG_BLOCK, &sigchld_action.sa_mask, NULL);
969
821
          if(ret < 0){
970
822
            perror("sigprocmask");
971
823
            exitstatus = EXIT_FAILURE;
972
 
            goto fallback;
973
 
          }
974
 
          
 
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
          }
975
839
          /* We are done modifying process list, so unblock signal */
976
840
          ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask,
977
841
                             NULL);
978
842
          if(ret < 0){
979
843
            perror("sigprocmask");
980
 
            exitstatus = EXIT_FAILURE;
981
 
            goto fallback;
982
 
          }
983
 
          
984
 
          if(plugin_list == NULL){
985
 
            break;
986
 
          }
987
 
          
988
 
          plugin *next_plugin = proc->next;
989
 
          free_plugin(proc);
990
 
          proc = next_plugin;
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 */
1008
 
        proc = proc->next;
1009
864
        continue;
1010
865
      }
1011
866
      /* Before reading, make the process' data buffer large enough */
1015
870
        if (proc->buffer == NULL){
1016
871
          perror("malloc");
1017
872
          exitstatus = EXIT_FAILURE;
1018
 
          goto fallback;
 
873
          goto end;
1019
874
        }
1020
875
        proc->buffer_size += BUFFER_SIZE;
1021
876
      }
1024
879
                 BUFFER_SIZE);
1025
880
      if(ret < 0){
1026
881
        /* Read error from this process; ignore the error */
1027
 
        proc = proc->next;
1028
882
        continue;
1029
883
      }
1030
884
      if(ret == 0){
1037
891
  }
1038
892
 
1039
893
 
1040
 
 fallback:
 
894
 end:
1041
895
  
1042
 
  if(plugin_list == NULL or exitstatus != EXIT_SUCCESS){
1043
 
    /* Fallback if all plugins failed, none are found or an error
1044
 
       occured */
 
896
  if(process_list == NULL or exitstatus != EXIT_SUCCESS){
 
897
    /* Fallback if all plugins failed, none are found or an error occured */
1045
898
    bool bret;
1046
899
    fprintf(stderr, "Going to fallback mode using getpass(3)\n");
1047
900
    char *passwordbuffer = getpass("Password: ");
1048
 
    size_t len = strlen(passwordbuffer);
1049
 
    /* Strip trailing newline */
1050
 
    if(len > 0 and passwordbuffer[len-1] == '\n'){
1051
 
      passwordbuffer[len-1] = '\0'; /* not strictly necessary */
1052
 
      len--;
1053
 
    }
1054
 
    bret = print_out_password(passwordbuffer, len);
 
901
    bret = print_out_password(passwordbuffer, strlen(passwordbuffer));
1055
902
    if(not bret){
1056
903
      perror("print_out_password");
1057
904
      exitstatus = EXIT_FAILURE;
 
905
      goto end;
1058
906
    }
1059
907
  }
1060
908
  
1061
909
  /* Restore old signal handler */
1062
 
  ret = sigaction(SIGCHLD, &old_sigchld_action, NULL);
1063
 
  if(ret == -1){
1064
 
    perror("sigaction");
1065
 
    exitstatus = EXIT_FAILURE;
1066
 
  }
1067
 
 
1068
 
  if(custom_argv != NULL){
1069
 
    for(char **arg = custom_argv+1; *arg != NULL; arg++){
1070
 
      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
      }
1071
922
    }
1072
 
    free(custom_argv);
 
923
    free(plugin_list->environ);
 
924
    free(plugin_list);
1073
925
  }
1074
926
  
1075
927
  if(dir != NULL){
1077
929
  }
1078
930
  
1079
931
  /* Free the process list and kill the processes */
1080
 
  for(plugin *p = plugin_list; p != NULL; p = p->next){
1081
 
    if(p->pid != 0){
1082
 
      close(p->fd);
1083
 
      ret = kill(p->pid, SIGTERM);
1084
 
      if(ret == -1 and errno != ESRCH){
1085
 
        /* Set-uid proccesses might not get closed */
1086
 
        perror("kill");
1087
 
      }
 
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");
1088
939
    }
 
940
    free(process_list->buffer);
 
941
    free(process_list);
1089
942
  }
1090
943
  
1091
944
  /* Wait for any remaining child processes to terminate */
1095
948
  if(errno != ECHILD){
1096
949
    perror("wait");
1097
950
  }
1098
 
 
1099
 
  free_plugin_list();
1100
 
  
1101
 
  free(plugindir);
1102
 
  free(argfile);
1103
951
  
1104
952
  return exitstatus;
1105
953
}