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