/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-29 05:53:59 UTC
  • Revision ID: teddy@fukt.bsnet.se-20080829055359-wkdasnyxtylmnxus
* mandos.xml (EXAMPLE): Replaced all occurences of command name with
                        "&COMMANDNAME;".

* plugins.d/password-prompt.c (main): Improved some documentation
                                      strings.  Do perror() of
                                      tcgetattr() fails.  Add debug
                                      output if interrupted by signal.
                                      Loop over write() instead of
                                      using fwrite() when outputting
                                      password.  Add debug output if
                                      getline() returns 0, unless it
                                      was caused by a signal.  Add
                                      exit status code to debug
                                      output.

* plugins.d/password-prompt.xml: Changed all single quotes to double
                                 quotes for consistency.  Removed
                                 <?xml-stylesheet>.
  (ENTITY TIMESTAMP): New.  Automatically updated by Emacs time-stamp
                      by using Emacs local variables.
  (/refentry/refentryinfo/title): Changed to "Mandos Manual".
  (/refentry/refentryinfo/productname): Changed to "Mandos".
  (/refentry/refentryinfo/date): New; set to "&TIMESTAMP;".
  (/refentry/refentryinfo/copyright): Split copyright holders.
  (/refentry/refnamediv/refpurpose): Improved wording.
  (SYNOPSIS): Fix to use correct markup.  Add short options.
  (DESCRIPTION, OPTIONS): Improved wording.
  (OPTIONS): Improved wording.  Use more correct markup.  Document
             short options.
  (EXIT STATUS): Add text.
  (ENVIRONMENT): Document use of "cryptsource" and "crypttarget".
  (FILES): REMOVED.
  (BUGS): Add text.
  (EXAMPLE): Added some examples.
  (SECURITY): Added text.
  (SEE ALSO): Remove reference to mandos(8).  Add reference to
              crypttab(5).

Show diffs side-by-side

added added

removed removed

Lines of Context:
2
2
/*
3
3
 * Mandos plugin runner - Run Mandos plugins
4
4
 *
5
 
 * Copyright © 2008 Teddy Hogeborn & Björn Påhlsson
 
5
 * Copyright © 2007-2008 Teddy Hogeborn & Björn Påhlsson
6
6
 * 
7
7
 * This program is free software: you can redistribute it and/or
8
8
 * modify it under the terms of the GNU General Public License as
27
27
#include <stdlib.h>             /* malloc(), exit(), EXIT_FAILURE,
28
28
                                   EXIT_SUCCESS, realloc() */
29
29
#include <stdbool.h>            /* bool, true, false */
30
 
#include <stdio.h>              /* perror, fileno(), fprintf(),
31
 
                                   stderr, STDOUT_FILENO */
 
30
#include <stdio.h>              /* perror, popen(), fileno(),
 
31
                                   fprintf(), stderr, STDOUT_FILENO */
32
32
#include <sys/types.h>          /* DIR, opendir(), stat(), struct
33
33
                                   stat, waitpid(), WIFEXITED(),
34
34
                                   WEXITSTATUS(), wait(), pid_t,
46
46
                                   fcntl(), setuid(), setgid(),
47
47
                                   F_GETFD, F_SETFD, FD_CLOEXEC,
48
48
                                   access(), pipe(), fork(), close()
49
 
                                   dup2(), STDOUT_FILENO, _exit(),
 
49
                                   dup2, STDOUT_FILENO, _exit(),
50
50
                                   execv(), write(), read(),
51
51
                                   close() */
52
52
#include <fcntl.h>              /* fcntl(), F_GETFD, F_SETFD,
69
69
#define PDIR "/lib/mandos/plugins.d"
70
70
#define AFILE "/conf/conf.d/mandos/plugin-runner.conf"
71
71
 
72
 
const char *argp_program_version = "plugin-runner " VERSION;
 
72
const char *argp_program_version = "plugin-runner 1.0";
73
73
const char *argp_program_bug_address = "<mandos@fukt.bsnet.se>";
74
74
 
75
 
typedef struct plugin{
76
 
  char *name;                   /* can be NULL or any plugin name */
77
 
  char **argv;
78
 
  int argc;
79
 
  char **environ;
80
 
  int envc;
81
 
  bool disabled;
 
75
struct process;
82
76
 
83
 
  /* Variables used for running processes*/
 
77
typedef struct process{
84
78
  pid_t pid;
85
79
  int fd;
86
80
  char *buffer;
89
83
  bool eof;
90
84
  volatile bool completed;
91
85
  volatile int status;
 
86
  struct process *next;
 
87
} process;
 
88
 
 
89
typedef struct plugin{
 
90
  char *name;                   /* can be NULL or any plugin name */
 
91
  char **argv;
 
92
  int argc;
 
93
  char **environ;
 
94
  int envc;
 
95
  bool disabled;
92
96
  struct plugin *next;
93
97
} plugin;
94
98
 
95
 
static plugin *plugin_list = NULL;
96
 
 
97
 
/* Gets an existing plugin based on name,
98
 
   or if none is found, creates a new one */
99
 
static plugin *getplugin(char *name){
100
 
  /* Check for exiting plugin with that name */
101
 
  for (plugin *p = plugin_list; p != NULL; p = p->next){
 
99
static plugin *getplugin(char *name, plugin **plugin_list){
 
100
  for (plugin *p = *plugin_list; p != NULL; p = p->next){
102
101
    if ((p->name == name)
103
102
        or (p->name and name and (strcmp(p->name, name) == 0))){
104
103
      return p;
119
118
  
120
119
  *new_plugin = (plugin) { .name = copy_name,
121
120
                           .argc = 1,
 
121
                           .envc = 0,
122
122
                           .disabled = false,
123
 
                           .next = plugin_list };
 
123
                           .next = *plugin_list };
124
124
  
125
125
  new_plugin->argv = malloc(sizeof(char *) * 2);
126
126
  if (new_plugin->argv == NULL){
130
130
  }
131
131
  new_plugin->argv[0] = copy_name;
132
132
  new_plugin->argv[1] = NULL;
133
 
  
 
133
 
134
134
  new_plugin->environ = malloc(sizeof(char *));
135
135
  if(new_plugin->environ == NULL){
136
136
    free(copy_name);
139
139
    return NULL;
140
140
  }
141
141
  new_plugin->environ[0] = NULL;
142
 
  
143
142
  /* Append the new plugin to the list */
144
 
  plugin_list = new_plugin;
 
143
  *plugin_list = new_plugin;
145
144
  return new_plugin;
146
145
}
147
146
 
177
176
}
178
177
 
179
178
/* Add to a plugin's environment */
180
 
static bool add_environment(plugin *p, const char *def, bool replace){
 
179
static bool add_environment(plugin *p, const char *def){
181
180
  if(p == NULL){
182
181
    return false;
183
182
  }
184
 
  /* namelen = length of name of environment variable */
185
 
  size_t namelen = (size_t)(strchrnul(def, '=') - def);
186
 
  /* Search for this environment variable */
187
 
  for(char **e = p->environ; *e != NULL; e++){
188
 
    if(strncmp(*e, def, namelen + 1) == 0){
189
 
      /* It already exists */
190
 
      if(replace){
191
 
        char *new = realloc(*e, strlen(def) + 1);
192
 
        if(new == NULL){
193
 
          return false;
194
 
        }
195
 
        *e = new;
196
 
        strcpy(*e, def);
197
 
      }
198
 
      return true;
199
 
    }
200
 
  }
201
183
  return add_to_char_array(def, &(p->environ), &(p->envc));
202
184
}
203
185
 
 
186
 
204
187
/*
205
188
 * Based on the example in the GNU LibC manual chapter 13.13 "File
206
189
 * Descriptor Flags".
207
190
 * *Note File Descriptor Flags:(libc)Descriptor Flags.
208
191
 */
209
 
static int set_cloexec_flag(int fd){
 
192
static int set_cloexec_flag(int fd)
 
193
{
210
194
  int ret = fcntl(fd, F_GETFD, 0);
211
195
  /* If reading the flags failed, return error indication now. */
212
196
  if(ret < 0){
216
200
  return fcntl(fd, F_SETFD, ret | FD_CLOEXEC);
217
201
}
218
202
 
 
203
process *process_list = NULL;
219
204
 
220
205
/* Mark processes as completed when they exit, and save their exit
221
206
   status. */
222
 
static void handle_sigchld(__attribute__((unused)) int sig){
 
207
void handle_sigchld(__attribute__((unused)) int sig){
223
208
  while(true){
224
 
    plugin *proc = plugin_list;
 
209
    process *proc = process_list;
225
210
    int status;
226
211
    pid_t pid = waitpid(-1, &status, WNOHANG);
227
212
    if(pid == 0){
235
220
      /* No child processes */
236
221
      break;
237
222
    }
238
 
    
 
223
 
239
224
    /* A child exited, find it in process_list */
240
225
    while(proc != NULL and proc->pid != pid){
241
226
      proc = proc->next;
249
234
  }
250
235
}
251
236
 
252
 
/* Prints out a password to stdout */
253
 
static bool print_out_password(const char *buffer, size_t length){
 
237
bool print_out_password(const char *buffer, size_t length){
254
238
  ssize_t ret;
 
239
  if(length>0 and buffer[length-1] == '\n'){
 
240
    length--;
 
241
  }
255
242
  for(size_t written = 0; written < length; written += (size_t)ret){
256
243
    ret = TEMP_FAILURE_RETRY(write(STDOUT_FILENO, buffer + written,
257
244
                                   length - written));
262
249
  return true;
263
250
}
264
251
 
265
 
/* Removes and free a plugin from the plugin list */
266
 
static void free_plugin(plugin *plugin_node){
267
 
  
268
 
  for(char **arg = plugin_node->argv; *arg != NULL; arg++){
269
 
    free(*arg);
270
 
  }
271
 
  free(plugin_node->argv);
272
 
  for(char **env = plugin_node->environ; *env != NULL; env++){
273
 
    free(*env);
274
 
  }
275
 
  free(plugin_node->environ);
276
 
  free(plugin_node->buffer);
277
 
 
278
 
  /* Removes the plugin from the singly-linked list */
279
 
  if(plugin_node == plugin_list){
280
 
    /* First one - simple */
281
 
    plugin_list = plugin_list->next;
282
 
  } else {
283
 
    /* Second one or later */
284
 
    for(plugin *p = plugin_list; p != NULL; p = p->next){
285
 
      if(p->next == plugin_node){
286
 
        p->next = plugin_node->next;
287
 
        break;
288
 
      }
289
 
    }
290
 
  }
291
 
  
292
 
  free(plugin_node);
293
 
}
294
 
 
295
 
static void free_plugin_list(void){
296
 
  while(plugin_list != NULL){
297
 
    free_plugin(plugin_list);
 
252
static void free_plugin_list(plugin *plugin_list){
 
253
  for(plugin *next; plugin_list != NULL; plugin_list = next){
 
254
    next = plugin_list->next;
 
255
    for(char **arg = plugin_list->argv; *arg != NULL; arg++){
 
256
      free(*arg);
 
257
    }
 
258
    free(plugin_list->argv);
 
259
    for(char **env = plugin_list->environ; *env != NULL; env++){
 
260
      free(*env);
 
261
    }
 
262
    free(plugin_list->environ);
 
263
    free(plugin_list);
298
264
  }
299
265
}
300
266
 
338
304
    { .name = "global-options", .key = 'g',
339
305
      .arg = "OPTION[,OPTION[,...]]",
340
306
      .doc = "Options passed to all plugins" },
341
 
    { .name = "global-env", .key = 'G',
 
307
    { .name = "global-envs", .key = 'e',
342
308
      .arg = "VAR=value",
343
309
      .doc = "Environment variable passed to all plugins" },
344
310
    { .name = "options-for", .key = 'o',
345
311
      .arg = "PLUGIN:OPTION[,OPTION[,...]]",
346
312
      .doc = "Options passed only to specified plugin" },
347
 
    { .name = "env-for", .key = 'E',
 
313
    { .name = "envs-for", .key = 'f',
348
314
      .arg = "PLUGIN:ENV=value",
349
315
      .doc = "Environment variable passed to specified plugin" },
350
316
    { .name = "disable", .key = 'd',
351
317
      .arg = "PLUGIN",
352
318
      .doc = "Disable a specific plugin", .group = 1 },
353
 
    { .name = "enable", .key = 'e',
354
 
      .arg = "PLUGIN",
355
 
      .doc = "Enable a specific plugin", .group = 1 },
356
319
    { .name = "plugin-dir", .key = 128,
357
320
      .arg = "DIRECTORY",
358
321
      .doc = "Specify a different plugin directory", .group = 2 },
370
333
    { .name = NULL }
371
334
  };
372
335
  
373
 
  error_t parse_opt (int key, char *arg, __attribute__((unused))
374
 
                     struct argp_state *state) {
 
336
  error_t parse_opt (int key, char *arg, struct argp_state *state) {
 
337
    /* Get the INPUT argument from `argp_parse', which we know is a
 
338
       pointer to our plugin list pointer. */
 
339
    plugin **plugins = state->input;
375
340
    switch (key) {
376
 
    case 'g':                   /* --global-options */
 
341
    case 'g':
377
342
      if (arg != NULL){
378
343
        char *p;
379
344
        while((p = strsep(&arg, ",")) != NULL){
380
345
          if(p[0] == '\0'){
381
346
            continue;
382
347
          }
383
 
          if(not add_argument(getplugin(NULL), p)){
 
348
          if(not add_argument(getplugin(NULL, plugins), p)){
384
349
            perror("add_argument");
385
350
            return ARGP_ERR_UNKNOWN;
386
351
          }
387
352
        }
388
353
      }
389
354
      break;
390
 
    case 'G':                   /* --global-env */
 
355
    case 'e':
391
356
      if(arg == NULL){
392
357
        break;
393
358
      }
394
 
      if(not add_environment(getplugin(NULL), arg, true)){
395
 
        perror("add_environment");
 
359
      {
 
360
        char *envdef = strdup(arg);
 
361
        if(envdef == NULL){
 
362
          break;
 
363
        }
 
364
        if(not add_environment(getplugin(NULL, plugins), envdef)){
 
365
          perror("add_environment");
 
366
        }
396
367
      }
397
368
      break;
398
 
    case 'o':                   /* --options-for */
 
369
    case 'o':
399
370
      if (arg != NULL){
400
371
        char *p_name = strsep(&arg, ":");
401
 
        if(p_name[0] == '\0' or arg == NULL){
 
372
        if(p_name[0] == '\0'){
402
373
          break;
403
374
        }
404
375
        char *opt = strsep(&arg, ":");
405
 
        if(opt[0] == '\0' or opt == NULL){
 
376
        if(opt[0] == '\0'){
406
377
          break;
407
378
        }
408
 
        char *p;
409
 
        while((p = strsep(&opt, ",")) != NULL){
410
 
          if(p[0] == '\0'){
411
 
            continue;
412
 
          }
413
 
          if(not add_argument(getplugin(p_name), p)){
414
 
            perror("add_argument");
415
 
            return ARGP_ERR_UNKNOWN;
 
379
        if(opt != NULL){
 
380
          char *p;
 
381
          while((p = strsep(&opt, ",")) != NULL){
 
382
            if(p[0] == '\0'){
 
383
              continue;
 
384
            }
 
385
            if(not add_argument(getplugin(p_name, plugins), p)){
 
386
              perror("add_argument");
 
387
              return ARGP_ERR_UNKNOWN;
 
388
            }
416
389
          }
417
390
        }
418
391
      }
419
392
      break;
420
 
    case 'E':                   /* --env-for */
 
393
    case 'f':
421
394
      if(arg == NULL){
422
395
        break;
423
396
      }
426
399
        if(envdef == NULL){
427
400
          break;
428
401
        }
429
 
        *envdef = '\0';
430
 
        if(not add_environment(getplugin(arg), envdef+1, true)){
 
402
        char *p_name = strndup(arg, (size_t) (envdef-arg));
 
403
        if(p_name == NULL){
 
404
          break;
 
405
        }
 
406
        envdef++;
 
407
        if(not add_environment(getplugin(p_name, plugins), envdef)){
431
408
          perror("add_environment");
432
409
        }
433
410
      }
434
411
      break;
435
 
    case 'd':                   /* --disable */
 
412
    case 'd':
436
413
      if (arg != NULL){
437
 
        plugin *p = getplugin(arg);
 
414
        plugin *p = getplugin(arg, plugins);
438
415
        if(p == NULL){
439
416
          return ARGP_ERR_UNKNOWN;
440
417
        }
441
418
        p->disabled = true;
442
419
      }
443
420
      break;
444
 
    case 'e':                   /* --enable */
445
 
      if (arg != NULL){
446
 
        plugin *p = getplugin(arg);
447
 
        if(p == NULL){
448
 
          return ARGP_ERR_UNKNOWN;
449
 
        }
450
 
        p->disabled = false;
451
 
      }
452
 
      break;
453
 
    case 128:                   /* --plugin-dir */
454
 
      free(plugindir);
 
421
    case 128:
455
422
      plugindir = strdup(arg);
456
423
      if(plugindir == NULL){
457
424
        perror("strdup");
458
425
      }      
459
426
      break;
460
 
    case 129:                   /* --config-file */
461
 
      /* This is already done by parse_opt_config_file() */
462
 
      break;
463
 
    case 130:                   /* --userid */
 
427
    case 129:
 
428
      argfile = strdup(arg);
 
429
      if(argfile == NULL){
 
430
        perror("strdup");
 
431
      }
 
432
      break;      
 
433
    case 130:
464
434
      uid = (uid_t)strtol(arg, NULL, 10);
465
435
      break;
466
 
    case 131:                   /* --groupid */
 
436
    case 131:
467
437
      gid = (gid_t)strtol(arg, NULL, 10);
468
438
      break;
469
 
    case 132:                   /* --debug */
 
439
    case 132:
470
440
      debug = true;
471
441
      break;
472
442
    case ARGP_KEY_ARG:
473
 
      /* Cryptsetup always passes an argument, which is an empty
474
 
         string if "none" was specified in /etc/crypttab.  So if
475
 
         argument was empty, we ignore it silently. */
476
 
      if(arg[0] != '\0'){
477
 
        fprintf(stderr, "Ignoring unknown argument \"%s\"\n", arg);
478
 
      }
479
 
      break;
480
 
    case ARGP_KEY_END:
481
 
      break;
482
 
    default:
483
 
      return ARGP_ERR_UNKNOWN;
484
 
    }
485
 
    return 0;
486
 
  }
487
 
  
488
 
  /* This option parser is the same as parse_opt() above, except it
489
 
     ignores everything but the --config-file option. */
490
 
  error_t parse_opt_config_file (int key, char *arg,
491
 
                                 __attribute__((unused))
492
 
                                 struct argp_state *state) {
493
 
    switch (key) {
494
 
    case 'g':                   /* --global-options */
495
 
    case 'G':                   /* --global-env */
496
 
    case 'o':                   /* --options-for */
497
 
    case 'E':                   /* --env-for */
498
 
    case 'd':                   /* --disable */
499
 
    case 'e':                   /* --enable */
500
 
    case 128:                   /* --plugin-dir */
501
 
      break;
502
 
    case 129:                   /* --config-file */
503
 
      free(argfile);
504
 
      argfile = strdup(arg);
505
 
      if(argfile == NULL){
506
 
        perror("strdup");
507
 
      }
508
 
      break;      
509
 
    case 130:                   /* --userid */
510
 
    case 131:                   /* --groupid */
511
 
    case 132:                   /* --debug */
512
 
    case ARGP_KEY_ARG:
513
 
    case ARGP_KEY_END:
514
 
      break;
515
 
    default:
516
 
      return ARGP_ERR_UNKNOWN;
517
 
    }
518
 
    return 0;
519
 
  }
520
 
  
521
 
  struct argp argp = { .options = options,
522
 
                       .parser = parse_opt_config_file,
523
 
                       .args_doc = "",
 
443
      fprintf(stderr, "Ignoring unknown argument \"%s\"\n", arg);
 
444
      break;
 
445
    case ARGP_KEY_END:
 
446
      break;
 
447
    default:
 
448
      return ARGP_ERR_UNKNOWN;
 
449
    }
 
450
    return 0;
 
451
  }
 
452
  
 
453
  plugin *plugin_list = NULL;
 
454
  
 
455
  struct argp argp = { .options = options, .parser = parse_opt,
 
456
                       .args_doc = "[+PLUS_SEPARATED_OPTIONS]",
524
457
                       .doc = "Mandos plugin runner -- Run plugins" };
525
458
  
526
 
  /* Parse using the parse_opt_config_file in order to get the custom
527
 
     config file location, if any. */
528
 
  ret = argp_parse (&argp, argc, argv, ARGP_IN_ORDER, 0, NULL);
 
459
  ret = argp_parse (&argp, argc, argv, 0, 0, &plugin_list);
529
460
  if (ret == ARGP_ERR_UNKNOWN){
530
461
    fprintf(stderr, "Unknown error while parsing arguments\n");
531
462
    exitstatus = EXIT_FAILURE;
532
463
    goto fallback;
533
464
  }
534
 
  
535
 
  /* Reset to the normal argument parser */
536
 
  argp.parser = parse_opt;
537
 
  
538
 
  /* Open the configfile if available */
 
465
 
539
466
  if (argfile == NULL){
540
467
    conffp = fopen(AFILE, "r");
541
468
  } else {
542
469
    conffp = fopen(argfile, "r");
543
 
  }  
 
470
  }
 
471
  
544
472
  if(conffp != NULL){
545
473
    char *org_line = NULL;
546
474
    char *p, *arg, *new_arg, *line;
558
486
    }
559
487
    custom_argv[0] = argv[0];
560
488
    custom_argv[1] = NULL;
561
 
 
562
 
    /* for each line in the config file, strip whitespace and ignore
563
 
       commented text */
 
489
    
564
490
    while(true){
565
491
      sret = getline(&org_line, &size, conffp);
566
492
      if(sret == -1){
595
521
      }
596
522
    }
597
523
    free(org_line);
598
 
  } else {
 
524
  } else{
599
525
    /* Check for harmful errors and go to fallback. Other errors might
600
526
       not affect opening plugins */
601
527
    if (errno == EMFILE or errno == ENFILE or errno == ENOMEM){
604
530
      goto fallback;
605
531
    }
606
532
  }
607
 
  /* If there was any arguments from configuration file,
608
 
     pass them to parser as command arguments */
 
533
 
609
534
  if(custom_argv != NULL){
610
 
    ret = argp_parse (&argp, custom_argc, custom_argv, ARGP_IN_ORDER,
611
 
                      0, NULL);
 
535
    ret = argp_parse (&argp, custom_argc, custom_argv, 0, 0, &plugin_list);
612
536
    if (ret == ARGP_ERR_UNKNOWN){
613
537
      fprintf(stderr, "Unknown error while parsing arguments\n");
614
538
      exitstatus = EXIT_FAILURE;
616
540
    }
617
541
  }
618
542
  
619
 
  /* Parse actual command line arguments, to let them override the
620
 
     config file */
621
 
  ret = argp_parse (&argp, argc, argv, ARGP_IN_ORDER, 0, NULL);
622
 
  if (ret == ARGP_ERR_UNKNOWN){
623
 
    fprintf(stderr, "Unknown error while parsing arguments\n");
624
 
    exitstatus = EXIT_FAILURE;
625
 
    goto fallback;
626
 
  }
627
 
  
628
543
  if(debug){
629
544
    for(plugin *p = plugin_list; p != NULL; p=p->next){
630
545
      fprintf(stderr, "Plugin: %s has %d arguments\n",
639
554
    }
640
555
  }
641
556
  
642
 
  /* Strip permissions down to nobody */
643
557
  ret = setuid(uid);
644
558
  if (ret == -1){
645
559
    perror("setuid");
646
 
  }  
 
560
  }
 
561
  
647
562
  setgid(gid);
648
563
  if (ret == -1){
649
564
    perror("setgid");
650
565
  }
651
 
  
 
566
 
652
567
  if (plugindir == NULL){
653
568
    dir = opendir(PDIR);
654
569
  } else {
676
591
  
677
592
  FD_ZERO(&rfds_all);
678
593
  
679
 
  /* Read and execute any executable in the plugin directory*/
680
594
  while(true){
681
595
    dirst = readdir(dir);
682
596
    
683
 
    /* All directory entries have been processed */
 
597
    // All directory entries have been processed
684
598
    if(dirst == NULL){
685
599
      if (errno == EBADF){
686
600
        perror("readdir");
692
606
    
693
607
    d_name_len = strlen(dirst->d_name);
694
608
    
695
 
    /* Ignore dotfiles, backup files and other junk */
 
609
    // Ignore dotfiles, backup files and other junk
696
610
    {
697
611
      bool bad_name = false;
698
612
      
713
627
          break;
714
628
        }
715
629
      }
 
630
      
716
631
      if(bad_name){
717
632
        continue;
718
633
      }
 
634
      
719
635
      for(const char **suf = bad_suffixes; *suf != NULL; suf++){
720
636
        size_t suf_len = strlen(*suf);
721
637
        if((d_name_len >= suf_len)
736
652
    }
737
653
 
738
654
    char *filename;
739
 
    if(plugindir == NULL){
740
 
      ret = asprintf(&filename, PDIR "/%s", dirst->d_name);
741
 
    } else {
742
 
      ret = asprintf(&filename, "%s/%s", plugindir, dirst->d_name);
743
 
    }
 
655
    ret = asprintf(&filename, "%s/%s", plugindir, dirst->d_name);
744
656
    if(ret < 0){
745
657
      perror("asprintf");
746
658
      continue;
752
664
      free(filename);
753
665
      continue;
754
666
    }
755
 
 
756
 
    /* Ignore non-executable files */
 
667
    
757
668
    if (not S_ISREG(st.st_mode) or (access(filename, X_OK) != 0)){
758
669
      if(debug){
759
670
        fprintf(stderr, "Ignoring plugin dir entry \"%s\""
762
673
      free(filename);
763
674
      continue;
764
675
    }
765
 
    
766
 
    plugin *p = getplugin(dirst->d_name);
 
676
    plugin *p = getplugin(dirst->d_name, &plugin_list);
767
677
    if(p == NULL){
768
678
      perror("getplugin");
769
679
      free(filename);
779
689
    }
780
690
    {
781
691
      /* Add global arguments to argument list for this plugin */
782
 
      plugin *g = getplugin(NULL);
 
692
      plugin *g = getplugin(NULL, &plugin_list);
783
693
      if(g != NULL){
784
694
        for(char **a = g->argv + 1; *a != NULL; a++){
785
695
          if(not add_argument(p, *a)){
788
698
        }
789
699
        /* Add global environment variables */
790
700
        for(char **e = g->environ; *e != NULL; e++){
791
 
          if(not add_environment(p, *e, false)){
 
701
          if(not add_environment(p, *e)){
792
702
            perror("add_environment");
793
703
          }
794
704
        }
799
709
       process, too. */
800
710
    if(p->environ[0] != NULL){
801
711
      for(char **e = environ; *e != NULL; e++){
802
 
        if(not add_environment(p, *e, false)){
 
712
        char *copy = strdup(*e);
 
713
        if(copy == NULL){
 
714
          perror("strdup");
 
715
          continue;
 
716
        }
 
717
        if(not add_environment(p, copy)){
803
718
          perror("add_environment");
804
719
        }
805
720
      }
812
727
      exitstatus = EXIT_FAILURE;
813
728
      goto fallback;
814
729
    }
815
 
    /* Ask OS to automatic close the pipe on exec */
816
730
    ret = set_cloexec_flag(pipefd[0]);
817
731
    if(ret < 0){
818
732
      perror("set_cloexec_flag");
832
746
      exitstatus = EXIT_FAILURE;
833
747
      goto fallback;
834
748
    }
835
 
    /* Starting a new process to be watched */
 
749
    // Starting a new process to be watched
836
750
    pid_t pid = fork();
837
751
    if(pid == -1){
838
752
      perror("fork");
846
760
        perror("sigaction");
847
761
        _exit(EXIT_FAILURE);
848
762
      }
849
 
      ret = sigprocmask(SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
 
763
      ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
850
764
      if(ret < 0){
851
765
        perror("sigprocmask");
852
766
        _exit(EXIT_FAILURE);
853
767
      }
854
 
      
 
768
 
855
769
      ret = dup2(pipefd[1], STDOUT_FILENO); /* replace our stdout */
856
770
      if(ret == -1){
857
771
        perror("dup2");
876
790
      }
877
791
      /* no return */
878
792
    }
879
 
    /* Parent process */
880
 
    close(pipefd[1]);           /* Close unused write end of pipe */
 
793
    /* parent process */
881
794
    free(filename);
882
 
    plugin *new_plugin = getplugin(dirst->d_name);
883
 
    if (new_plugin == NULL){
884
 
      perror("getplugin");
 
795
    close(pipefd[1]);           /* close unused write end of pipe */
 
796
    process *new_process = malloc(sizeof(process));
 
797
    if (new_process == NULL){
 
798
      perror("malloc");
885
799
      ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
886
800
      if(ret < 0){
887
 
        perror("sigprocmask");
 
801
        perror("sigprocmask");
888
802
      }
889
803
      exitstatus = EXIT_FAILURE;
890
804
      goto fallback;
891
805
    }
892
806
    
893
 
    new_plugin->pid = pid;
894
 
    new_plugin->fd = pipefd[0];
895
 
    
 
807
    *new_process = (struct process){ .pid = pid,
 
808
                                     .fd = pipefd[0],
 
809
                                     .next = process_list };
 
810
    // List handling
 
811
    process_list = new_process;
896
812
    /* Unblock SIGCHLD so signal handler can be run if this process
897
813
       has already completed */
898
814
    ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
902
818
      goto fallback;
903
819
    }
904
820
    
905
 
    FD_SET(new_plugin->fd, &rfds_all);
 
821
    FD_SET(new_process->fd, &rfds_all);
906
822
    
907
 
    if (maxfd < new_plugin->fd){
908
 
      maxfd = new_plugin->fd;
 
823
    if (maxfd < new_process->fd){
 
824
      maxfd = new_process->fd;
909
825
    }
 
826
    
910
827
  }
 
828
 
 
829
  free_plugin_list(plugin_list);
 
830
  plugin_list = NULL;
911
831
  
912
832
  closedir(dir);
913
833
  dir = NULL;
914
 
  
915
 
  for(plugin *p = plugin_list; p != NULL; p = p->next){
916
 
    if(p->pid != 0){
917
 
      break;
918
 
    }
919
 
    if(p->next == NULL){
920
 
      fprintf(stderr, "No plugin processes started. Incorrect plugin"
921
 
              " directory?\n");
922
 
      free_plugin_list();
923
 
    }
 
834
    
 
835
  if (process_list == NULL){
 
836
    fprintf(stderr, "No plugin processes started. Incorrect plugin"
 
837
            " directory?\n");
 
838
    process_list = NULL;
924
839
  }
925
 
  
926
 
  /* Main loop while running plugins exist */
927
 
  while(plugin_list){
 
840
  while(process_list){
928
841
    fd_set rfds = rfds_all;
929
842
    int select_ret = select(maxfd+1, &rfds, NULL, NULL, NULL);
930
843
    if (select_ret == -1){
934
847
    }
935
848
    /* OK, now either a process completed, or something can be read
936
849
       from one of them */
937
 
    for(plugin *proc = plugin_list; proc != NULL;){
 
850
    for(process *proc = process_list; proc ; proc = proc->next){
938
851
      /* Is this process completely done? */
939
852
      if(proc->eof and proc->completed){
940
853
        /* Only accept the plugin output if it exited cleanly */
941
854
        if(not WIFEXITED(proc->status)
942
855
           or WEXITSTATUS(proc->status) != 0){
943
856
          /* Bad exit by plugin */
944
 
 
945
857
          if(debug){
946
858
            if(WIFEXITED(proc->status)){
947
859
              fprintf(stderr, "Plugin %u exited with status %d\n",
956
868
                      (unsigned int) (proc->pid));
957
869
            }
958
870
          }
959
 
          
960
871
          /* Remove the plugin */
961
872
          FD_CLR(proc->fd, &rfds_all);
962
 
 
963
873
          /* Block signal while modifying process_list */
964
874
          ret = sigprocmask(SIG_BLOCK, &sigchld_action.sa_mask, NULL);
965
875
          if(ret < 0){
967
877
            exitstatus = EXIT_FAILURE;
968
878
            goto fallback;
969
879
          }
970
 
          
971
 
          plugin *next_plugin = proc->next;
972
 
          free_plugin(proc);
973
 
          proc = next_plugin;
974
 
          
 
880
          /* Delete this process entry from the list */
 
881
          if(process_list == proc){
 
882
            /* First one - simple */
 
883
            process_list = proc->next;
 
884
          } else {
 
885
            /* Second one or later */
 
886
            for(process *p = process_list; p != NULL; p = p->next){
 
887
              if(p->next == proc){
 
888
                p->next = proc->next;
 
889
                break;
 
890
              }
 
891
            }
 
892
          }
975
893
          /* We are done modifying process list, so unblock signal */
976
894
          ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask,
977
895
                             NULL);
978
896
          if(ret < 0){
979
897
            perror("sigprocmask");
980
 
            exitstatus = EXIT_FAILURE;
981
 
            goto fallback;
982
 
          }
983
 
          
984
 
          if(plugin_list == NULL){
985
 
            break;
986
 
          }
987
 
          
988
 
          continue;
 
898
          }
 
899
          free(proc->buffer);
 
900
          free(proc);
 
901
          /* We deleted this process from the list, so we can't go
 
902
             proc->next.  Therefore, start over from the beginning of
 
903
             the process list */
 
904
          break;
989
905
        }
990
 
        
991
906
        /* This process exited nicely, so print its buffer */
992
 
        
 
907
 
993
908
        bool bret = print_out_password(proc->buffer,
994
909
                                       proc->buffer_length);
995
910
        if(not bret){
998
913
        }
999
914
        goto fallback;
1000
915
      }
1001
 
      
1002
916
      /* This process has not completed.  Does it have any output? */
1003
917
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){
1004
918
        /* This process had nothing to say at this time */
1005
 
        proc = proc->next;
1006
919
        continue;
1007
920
      }
1008
921
      /* Before reading, make the process' data buffer large enough */
1021
934
                 BUFFER_SIZE);
1022
935
      if(ret < 0){
1023
936
        /* Read error from this process; ignore the error */
1024
 
        proc = proc->next;
1025
937
        continue;
1026
938
      }
1027
939
      if(ret == 0){
1036
948
 
1037
949
 fallback:
1038
950
  
1039
 
  if(plugin_list == NULL or exitstatus != EXIT_SUCCESS){
 
951
  if(process_list == NULL or exitstatus != EXIT_SUCCESS){
1040
952
    /* Fallback if all plugins failed, none are found or an error
1041
953
       occured */
1042
954
    bool bret;
1043
955
    fprintf(stderr, "Going to fallback mode using getpass(3)\n");
1044
956
    char *passwordbuffer = getpass("Password: ");
1045
 
    size_t len = strlen(passwordbuffer);
1046
 
    /* Strip trailing newline */
1047
 
    if(len > 0 and passwordbuffer[len-1] == '\n'){
1048
 
      passwordbuffer[len-1] = '\0'; /* not strictly necessary */
1049
 
      len--;
1050
 
    }
1051
 
    bret = print_out_password(passwordbuffer, len);
 
957
    bret = print_out_password(passwordbuffer, strlen(passwordbuffer));
1052
958
    if(not bret){
1053
959
      perror("print_out_password");
1054
960
      exitstatus = EXIT_FAILURE;
1061
967
    perror("sigaction");
1062
968
    exitstatus = EXIT_FAILURE;
1063
969
  }
1064
 
  
 
970
 
1065
971
  if(custom_argv != NULL){
1066
972
    for(char **arg = custom_argv+1; *arg != NULL; arg++){
1067
973
      free(*arg);
1068
974
    }
1069
975
    free(custom_argv);
1070
976
  }
 
977
  free_plugin_list(plugin_list);
1071
978
  
1072
979
  if(dir != NULL){
1073
980
    closedir(dir);
1074
981
  }
1075
982
  
1076
 
  /* Kill the processes */
1077
 
  for(plugin *p = plugin_list; p != NULL; p = p->next){
1078
 
    if(p->pid != 0){
1079
 
      close(p->fd);
1080
 
      ret = kill(p->pid, SIGTERM);
1081
 
      if(ret == -1 and errno != ESRCH){
1082
 
        /* Set-uid proccesses might not get closed */
1083
 
        perror("kill");
1084
 
      }
 
983
  /* Free the process list and kill the processes */
 
984
  for(process *next; process_list != NULL; process_list = next){
 
985
    next = process_list->next;
 
986
    close(process_list->fd);
 
987
    ret = kill(process_list->pid, SIGTERM);
 
988
    if(ret == -1 and errno != ESRCH){
 
989
      /* set-uid proccesses migth not get closed */
 
990
      perror("kill");
1085
991
    }
 
992
    free(process_list->buffer);
 
993
    free(process_list);
1086
994
  }
1087
995
  
1088
996
  /* Wait for any remaining child processes to terminate */
1092
1000
  if(errno != ECHILD){
1093
1001
    perror("wait");
1094
1002
  }
1095
 
  
1096
 
  free_plugin_list();
1097
 
  
 
1003
 
1098
1004
  free(plugindir);
1099
1005
  free(argfile);
1100
1006