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