/mandos/release

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

« back to all changes in this revision

Viewing changes to plugin-runner.c

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