/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,
62
61
#include <signal.h>             /* struct sigaction, sigemptyset(),
63
62
                                   sigaddset(), sigaction(),
64
63
                                   sigprocmask(), SIG_BLOCK, SIGCHLD,
65
 
                                   SIG_UNBLOCK, kill(), sig_atomic_t
66
 
                                */
 
64
                                   SIG_UNBLOCK, kill() */
67
65
#include <errno.h>              /* errno, EBADF */
68
 
#include <inttypes.h>           /* intmax_t, PRIdMAX, strtoimax() */
69
66
 
70
67
#define BUFFER_SIZE 256
71
68
 
72
69
#define PDIR "/lib/mandos/plugins.d"
73
70
#define AFILE "/conf/conf.d/mandos/plugin-runner.conf"
74
71
 
75
 
const char *argp_program_version = "plugin-runner " VERSION;
 
72
const char *argp_program_version = "plugin-runner 1.0";
76
73
const char *argp_program_bug_address = "<mandos@fukt.bsnet.se>";
77
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
 
78
89
typedef struct plugin{
79
90
  char *name;                   /* can be NULL or any plugin name */
80
91
  char **argv;
82
93
  char **environ;
83
94
  int envc;
84
95
  bool disabled;
85
 
  
86
 
  /* Variables used for running processes*/
87
 
  pid_t pid;
88
 
  int fd;
89
 
  char *buffer;
90
 
  size_t buffer_size;
91
 
  size_t buffer_length;
92
 
  bool eof;
93
 
  volatile sig_atomic_t completed;
94
 
  int status;
95
96
  struct plugin *next;
96
97
} plugin;
97
98
 
98
 
static plugin *plugin_list = NULL;
99
 
 
100
 
/* Gets an existing plugin based on name,
101
 
   or if none is found, creates a new one */
102
 
static plugin *getplugin(char *name){
103
 
  /* Check for exiting plugin with that name */
104
 
  for(plugin *p = plugin_list; p != NULL; p = p->next){
105
 
    if((p->name == name)
106
 
       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))){
107
103
      return p;
108
104
    }
109
105
  }
110
106
  /* Create a new plugin */
111
107
  plugin *new_plugin = malloc(sizeof(plugin));
112
 
  if(new_plugin == NULL){
 
108
  if (new_plugin == NULL){
113
109
    return NULL;
114
110
  }
115
111
  char *copy_name = NULL;
116
112
  if(name != NULL){
117
113
    copy_name = strdup(name);
118
114
    if(copy_name == NULL){
119
 
      free(new_plugin);
120
115
      return NULL;
121
116
    }
122
117
  }
123
118
  
124
 
  *new_plugin = (plugin){ .name = copy_name,
125
 
                          .argc = 1,
126
 
                          .disabled = false,
127
 
                          .next = plugin_list };
 
119
  *new_plugin = (plugin) { .name = copy_name,
 
120
                           .argc = 1,
 
121
                           .envc = 0,
 
122
                           .disabled = false,
 
123
                           .next = *plugin_list };
128
124
  
129
125
  new_plugin->argv = malloc(sizeof(char *) * 2);
130
 
  if(new_plugin->argv == NULL){
 
126
  if (new_plugin->argv == NULL){
131
127
    free(copy_name);
132
128
    free(new_plugin);
133
129
    return NULL;
134
130
  }
135
131
  new_plugin->argv[0] = copy_name;
136
132
  new_plugin->argv[1] = NULL;
137
 
  
 
133
 
138
134
  new_plugin->environ = malloc(sizeof(char *));
139
135
  if(new_plugin->environ == NULL){
140
136
    free(copy_name);
143
139
    return NULL;
144
140
  }
145
141
  new_plugin->environ[0] = NULL;
146
 
  
147
142
  /* Append the new plugin to the list */
148
 
  plugin_list = new_plugin;
 
143
  *plugin_list = new_plugin;
149
144
  return new_plugin;
150
145
}
151
146
 
181
176
}
182
177
 
183
178
/* Add to a plugin's environment */
184
 
static bool add_environment(plugin *p, const char *def, bool replace){
 
179
static bool add_environment(plugin *p, const char *def){
185
180
  if(p == NULL){
186
181
    return false;
187
182
  }
188
 
  /* namelen = length of name of environment variable */
189
 
  size_t namelen = (size_t)(strchrnul(def, '=') - def);
190
 
  /* Search for this environment variable */
191
 
  for(char **e = p->environ; *e != NULL; e++){
192
 
    if(strncmp(*e, def, namelen + 1) == 0){
193
 
      /* It already exists */
194
 
      if(replace){
195
 
        char *new = realloc(*e, strlen(def) + 1);
196
 
        if(new == NULL){
197
 
          return false;
198
 
        }
199
 
        *e = new;
200
 
        strcpy(*e, def);
201
 
      }
202
 
      return true;
203
 
    }
204
 
  }
205
183
  return add_to_char_array(def, &(p->environ), &(p->envc));
206
184
}
207
185
 
 
186
 
208
187
/*
209
188
 * Based on the example in the GNU LibC manual chapter 13.13 "File
210
189
 * Descriptor Flags".
211
190
 * *Note File Descriptor Flags:(libc)Descriptor Flags.
212
191
 */
213
 
static int set_cloexec_flag(int fd){
 
192
static int set_cloexec_flag(int fd)
 
193
{
214
194
  int ret = fcntl(fd, F_GETFD, 0);
215
195
  /* If reading the flags failed, return error indication now. */
216
196
  if(ret < 0){
220
200
  return fcntl(fd, F_SETFD, ret | FD_CLOEXEC);
221
201
}
222
202
 
 
203
process *process_list = NULL;
223
204
 
224
205
/* Mark processes as completed when they exit, and save their exit
225
206
   status. */
226
 
static void handle_sigchld(__attribute__((unused)) int sig){
227
 
  int old_errno = errno;
 
207
void handle_sigchld(__attribute__((unused)) int sig){
228
208
  while(true){
229
 
    plugin *proc = plugin_list;
 
209
    process *proc = process_list;
230
210
    int status;
231
211
    pid_t pid = waitpid(-1, &status, WNOHANG);
232
212
    if(pid == 0){
234
214
      break;
235
215
    }
236
216
    if(pid == -1){
237
 
      if(errno == ECHILD){
238
 
        /* No child processes */
239
 
        break;
 
217
      if (errno != ECHILD){
 
218
        perror("waitpid");
240
219
      }
241
 
      perror("waitpid");
 
220
      /* No child processes */
 
221
      break;
242
222
    }
243
 
    
 
223
 
244
224
    /* A child exited, find it in process_list */
245
225
    while(proc != NULL and proc->pid != pid){
246
226
      proc = proc->next;
250
230
      continue;
251
231
    }
252
232
    proc->status = status;
253
 
    proc->completed = 1;
 
233
    proc->completed = true;
254
234
  }
255
 
  errno = old_errno;
256
235
}
257
236
 
258
 
/* Prints out a password to stdout */
259
 
static bool print_out_password(const char *buffer, size_t length){
 
237
bool print_out_password(const char *buffer, size_t length){
260
238
  ssize_t ret;
 
239
  if(length>0 and buffer[length-1] == '\n'){
 
240
    length--;
 
241
  }
261
242
  for(size_t written = 0; written < length; written += (size_t)ret){
262
243
    ret = TEMP_FAILURE_RETRY(write(STDOUT_FILENO, buffer + written,
263
244
                                   length - written));
268
249
  return true;
269
250
}
270
251
 
271
 
/* Removes and free a plugin from the plugin list */
272
 
static void free_plugin(plugin *plugin_node){
273
 
  
274
 
  for(char **arg = plugin_node->argv; *arg != NULL; arg++){
275
 
    free(*arg);
276
 
  }
277
 
  free(plugin_node->argv);
278
 
  for(char **env = plugin_node->environ; *env != NULL; env++){
279
 
    free(*env);
280
 
  }
281
 
  free(plugin_node->environ);
282
 
  free(plugin_node->buffer);
283
 
  
284
 
  /* Removes the plugin from the singly-linked list */
285
 
  if(plugin_node == plugin_list){
286
 
    /* First one - simple */
287
 
    plugin_list = plugin_list->next;
288
 
  } else {
289
 
    /* Second one or later */
290
 
    for(plugin *p = plugin_list; p != NULL; p = p->next){
291
 
      if(p->next == plugin_node){
292
 
        p->next = plugin_node->next;
293
 
        break;
294
 
      }
295
 
    }
296
 
  }
297
 
  
298
 
  free(plugin_node);
299
 
}
300
 
 
301
 
static void free_plugin_list(void){
302
 
  while(plugin_list != NULL){
303
 
    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);
304
264
  }
305
265
}
306
266
 
314
274
  struct stat st;
315
275
  fd_set rfds_all;
316
276
  int ret, maxfd = 0;
317
 
  ssize_t sret;
318
 
  intmax_t tmpmax;
319
277
  uid_t uid = 65534;
320
278
  gid_t gid = 65534;
321
279
  bool debug = false;
346
304
    { .name = "global-options", .key = 'g',
347
305
      .arg = "OPTION[,OPTION[,...]]",
348
306
      .doc = "Options passed to all plugins" },
349
 
    { .name = "global-env", .key = 'G',
 
307
    { .name = "global-envs", .key = 'e',
350
308
      .arg = "VAR=value",
351
309
      .doc = "Environment variable passed to all plugins" },
352
310
    { .name = "options-for", .key = 'o',
353
311
      .arg = "PLUGIN:OPTION[,OPTION[,...]]",
354
312
      .doc = "Options passed only to specified plugin" },
355
 
    { .name = "env-for", .key = 'E',
 
313
    { .name = "envs-for", .key = 'f',
356
314
      .arg = "PLUGIN:ENV=value",
357
315
      .doc = "Environment variable passed to specified plugin" },
358
316
    { .name = "disable", .key = 'd',
359
317
      .arg = "PLUGIN",
360
318
      .doc = "Disable a specific plugin", .group = 1 },
361
 
    { .name = "enable", .key = 'e',
362
 
      .arg = "PLUGIN",
363
 
      .doc = "Enable a specific plugin", .group = 1 },
364
319
    { .name = "plugin-dir", .key = 128,
365
320
      .arg = "DIRECTORY",
366
321
      .doc = "Specify a different plugin directory", .group = 2 },
378
333
    { .name = NULL }
379
334
  };
380
335
  
381
 
  error_t parse_opt(int key, char *arg, __attribute__((unused))
382
 
                    struct argp_state *state){
383
 
    char *tmp;
384
 
    switch(key){
385
 
    case 'g':                   /* --global-options */
386
 
      if(arg != NULL){
387
 
        char *plugin_option;
388
 
        while((plugin_option = strsep(&arg, ",")) != NULL){
389
 
          if(plugin_option[0] == '\0'){
 
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){
 
343
        char *p;
 
344
        while((p = strsep(&arg, ",")) != NULL){
 
345
          if(p[0] == '\0'){
390
346
            continue;
391
347
          }
392
 
          if(not add_argument(getplugin(NULL), plugin_option)){
 
348
          if(not add_argument(getplugin(NULL, plugins), p)){
393
349
            perror("add_argument");
394
350
            return ARGP_ERR_UNKNOWN;
395
351
          }
396
352
        }
397
353
      }
398
354
      break;
399
 
    case 'G':                   /* --global-env */
 
355
    case 'e':
400
356
      if(arg == NULL){
401
357
        break;
402
358
      }
403
 
      if(not add_environment(getplugin(NULL), arg, true)){
404
 
        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
        }
405
367
      }
406
368
      break;
407
 
    case 'o':                   /* --options-for */
408
 
      if(arg != NULL){
409
 
        char *plugin_name = strsep(&arg, ":");
410
 
        if(plugin_name[0] == '\0'){
411
 
          break;
412
 
        }
413
 
        char *plugin_option;
414
 
        while((plugin_option = strsep(&arg, ",")) != NULL){
415
 
          if(not add_argument(getplugin(plugin_name), plugin_option)){
416
 
            perror("add_argument");
417
 
            return ARGP_ERR_UNKNOWN;
 
369
    case 'o':
 
370
      if (arg != NULL){
 
371
        char *p_name = strsep(&arg, ":");
 
372
        if(p_name[0] == '\0'){
 
373
          break;
 
374
        }
 
375
        char *opt = strsep(&arg, ":");
 
376
        if(opt[0] == '\0'){
 
377
          break;
 
378
        }
 
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 */
438
 
      if(arg != NULL){
439
 
        plugin *p = getplugin(arg);
 
412
    case 'd':
 
413
      if (arg != NULL){
 
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
 
      }
461
 
      break;
462
 
    case 129:                   /* --config-file */
463
 
      /* This is already done by parse_opt_config_file() */
464
 
      break;
465
 
    case 130:                   /* --userid */
466
 
      errno = 0;
467
 
      tmpmax = strtoimax(arg, &tmp, 10);
468
 
      if(errno != 0 or tmp == arg or *tmp != '\0'
469
 
         or tmpmax != (uid_t)tmpmax){
470
 
        fprintf(stderr, "Bad user ID number: \"%s\", using %"
471
 
                PRIdMAX "\n", arg, (intmax_t)uid);
472
 
      } else {
473
 
        uid = (uid_t)tmpmax;
474
 
      }
475
 
      break;
476
 
    case 131:                   /* --groupid */
477
 
      errno = 0;
478
 
      tmpmax = strtoimax(arg, &tmp, 10);
479
 
      if(errno != 0 or tmp == arg or *tmp != '\0'
480
 
         or tmpmax != (gid_t)tmpmax){
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);
 
425
      }      
 
426
      break;
 
427
    case 129:
526
428
      argfile = strdup(arg);
527
429
      if(argfile == NULL){
528
430
        perror("strdup");
529
431
      }
530
 
      break;
531
 
    case 130:                   /* --userid */
532
 
    case 131:                   /* --groupid */
533
 
    case 132:                   /* --debug */
 
432
      break;      
 
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
 
573
480
    custom_argc = 1;
574
481
    custom_argv = malloc(sizeof(char*) * 2);
575
482
    if(custom_argv == NULL){
580
487
    custom_argv[0] = argv[0];
581
488
    custom_argv[1] = NULL;
582
489
    
583
 
    /* for each line in the config file, strip whitespace and ignore
584
 
       commented text */
585
490
    while(true){
586
491
      sret = getline(&org_line, &size, conffp);
587
492
      if(sret == -1){
588
493
        break;
589
494
      }
590
 
      
 
495
 
591
496
      line = org_line;
592
497
      arg = strsep(&line, comment_delim);
593
498
      while((p = strsep(&arg, whitespace_delims)) != NULL){
612
517
          goto fallback;
613
518
        }
614
519
        custom_argv[custom_argc-1] = new_arg;
615
 
        custom_argv[custom_argc] = NULL;
 
520
        custom_argv[custom_argc] = NULL;        
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 */
 
557
  ret = setuid(uid);
 
558
  if (ret == -1){
 
559
    perror("setuid");
 
560
  }
 
561
  
664
562
  setgid(gid);
665
 
  if(ret == -1){
 
563
  if (ret == -1){
666
564
    perror("setgid");
667
565
  }
668
 
  ret = setuid(uid);
669
 
  if(ret == -1){
670
 
    perror("setuid");
671
 
  }
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)
756
650
        continue;
757
651
      }
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
667
    
778
 
    /* Ignore non-executable files */
779
 
    if(not S_ISREG(st.st_mode) or (access(filename, X_OK) != 0)){
 
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
 
  free_plugin(getplugin(NULL));
937
 
  
938
 
  for(plugin *p = plugin_list; p != NULL; p = p->next){
939
 
    if(p->pid != 0){
940
 
      break;
941
 
    }
942
 
    if(p->next == NULL){
943
 
      fprintf(stderr, "No plugin processes started. Incorrect plugin"
944
 
              " directory?\n");
945
 
      free_plugin_list();
946
 
    }
 
834
    
 
835
  if (process_list == NULL){
 
836
    fprintf(stderr, "No plugin processes started. Incorrect plugin"
 
837
            " directory?\n");
 
838
    process_list = NULL;
947
839
  }
948
 
  
949
 
  /* Main loop while running plugins exist */
950
 
  while(plugin_list){
 
840
  while(process_list){
951
841
    fd_set rfds = rfds_all;
952
842
    int select_ret = select(maxfd+1, &rfds, NULL, NULL, NULL);
953
 
    if(select_ret == -1){
 
843
    if (select_ret == -1){
954
844
      perror("select");
955
845
      exitstatus = EXIT_FAILURE;
956
846
      goto fallback;
957
847
    }
958
848
    /* OK, now either a process completed, or something can be read
959
849
       from one of them */
960
 
    for(plugin *proc = plugin_list; proc != NULL;){
 
850
    for(process *proc = process_list; proc ; proc = proc->next){
961
851
      /* Is this process completely done? */
962
 
      if(proc->completed and proc->eof){
 
852
      if(proc->eof and proc->completed){
963
853
        /* Only accept the plugin output if it exited cleanly */
964
854
        if(not WIFEXITED(proc->status)
965
855
           or WEXITSTATUS(proc->status) != 0){
966
856
          /* Bad exit by plugin */
967
 
          
968
857
          if(debug){
969
858
            if(WIFEXITED(proc->status)){
970
 
              fprintf(stderr, "Plugin %s [%" PRIdMAX "] exited with"
971
 
                      " status %d\n", proc->name,
972
 
                      (intmax_t) (proc->pid),
 
859
              fprintf(stderr, "Plugin %u exited with status %d\n",
 
860
                      (unsigned int) (proc->pid),
973
861
                      WEXITSTATUS(proc->status));
974
 
            } else if(WIFSIGNALED(proc->status)){
975
 
              fprintf(stderr, "Plugin %s [%" PRIdMAX "] killed by"
976
 
                      " signal %d\n", proc->name,
977
 
                      (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),
978
865
                      WTERMSIG(proc->status));
979
866
            } else if(WCOREDUMP(proc->status)){
980
 
              fprintf(stderr, "Plugin %s [%" PRIdMAX "] dumped"
981
 
                      " core\n", proc->name, (intmax_t) (proc->pid));
 
867
              fprintf(stderr, "Plugin %d dumped core\n",
 
868
                      (unsigned int) (proc->pid));
982
869
            }
983
870
          }
984
 
          
985
871
          /* Remove the plugin */
986
872
          FD_CLR(proc->fd, &rfds_all);
987
 
          
988
873
          /* Block signal while modifying process_list */
989
874
          ret = sigprocmask(SIG_BLOCK, &sigchld_action.sa_mask, NULL);
990
875
          if(ret < 0){
992
877
            exitstatus = EXIT_FAILURE;
993
878
            goto fallback;
994
879
          }
995
 
          
996
 
          plugin *next_plugin = proc->next;
997
 
          free_plugin(proc);
998
 
          proc = next_plugin;
999
 
          
 
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
          }
1000
893
          /* We are done modifying process list, so unblock signal */
1001
 
          ret = sigprocmask(SIG_UNBLOCK, &sigchld_action.sa_mask,
1002
 
                            NULL);
 
894
          ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask,
 
895
                             NULL);
1003
896
          if(ret < 0){
1004
897
            perror("sigprocmask");
1005
 
            exitstatus = EXIT_FAILURE;
1006
 
            goto fallback;
1007
 
          }
1008
 
          
1009
 
          if(plugin_list == NULL){
1010
 
            break;
1011
 
          }
1012
 
          
1013
 
          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;
1014
905
        }
1015
 
        
1016
906
        /* This process exited nicely, so print its buffer */
1017
 
        
 
907
 
1018
908
        bool bret = print_out_password(proc->buffer,
1019
909
                                       proc->buffer_length);
1020
910
        if(not bret){
1023
913
        }
1024
914
        goto fallback;
1025
915
      }
1026
 
      
1027
916
      /* This process has not completed.  Does it have any output? */
1028
917
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){
1029
918
        /* This process had nothing to say at this time */
1030
 
        proc = proc->next;
1031
919
        continue;
1032
920
      }
1033
921
      /* Before reading, make the process' data buffer large enough */
1034
922
      if(proc->buffer_length + BUFFER_SIZE > proc->buffer_size){
1035
923
        proc->buffer = realloc(proc->buffer, proc->buffer_size
1036
924
                               + (size_t) BUFFER_SIZE);
1037
 
        if(proc->buffer == NULL){
 
925
        if (proc->buffer == NULL){
1038
926
          perror("malloc");
1039
927
          exitstatus = EXIT_FAILURE;
1040
928
          goto fallback;
1042
930
        proc->buffer_size += BUFFER_SIZE;
1043
931
      }
1044
932
      /* Read from the process */
1045
 
      sret = read(proc->fd, proc->buffer + proc->buffer_length,
1046
 
                  BUFFER_SIZE);
1047
 
      if(sret < 0){
 
933
      ret = read(proc->fd, proc->buffer + proc->buffer_length,
 
934
                 BUFFER_SIZE);
 
935
      if(ret < 0){
1048
936
        /* Read error from this process; ignore the error */
1049
 
        proc = proc->next;
1050
937
        continue;
1051
938
      }
1052
 
      if(sret == 0){
 
939
      if(ret == 0){
1053
940
        /* got EOF */
1054
941
        proc->eof = true;
1055
942
      } else {
1056
 
        proc->buffer_length += (size_t) sret;
 
943
        proc->buffer_length += (size_t) ret;
1057
944
      }
1058
945
    }
1059
946
  }
1060
 
  
1061
 
  
 
947
 
 
948
 
1062
949
 fallback:
1063
950
  
1064
 
  if(plugin_list == NULL or exitstatus != EXIT_SUCCESS){
 
951
  if(process_list == NULL or exitstatus != EXIT_SUCCESS){
1065
952
    /* Fallback if all plugins failed, none are found or an error
1066
953
       occured */
1067
954
    bool bret;
1068
955
    fprintf(stderr, "Going to fallback mode using getpass(3)\n");
1069
956
    char *passwordbuffer = getpass("Password: ");
1070
 
    size_t len = strlen(passwordbuffer);
1071
 
    /* Strip trailing newline */
1072
 
    if(len > 0 and passwordbuffer[len-1] == '\n'){
1073
 
      passwordbuffer[len-1] = '\0'; /* not strictly necessary */
1074
 
      len--;
1075
 
    }
1076
 
    bret = print_out_password(passwordbuffer, len);
 
957
    bret = print_out_password(passwordbuffer, strlen(passwordbuffer));
1077
958
    if(not bret){
1078
959
      perror("print_out_password");
1079
960
      exitstatus = EXIT_FAILURE;
1086
967
    perror("sigaction");
1087
968
    exitstatus = EXIT_FAILURE;
1088
969
  }
1089
 
  
 
970
 
1090
971
  if(custom_argv != NULL){
1091
972
    for(char **arg = custom_argv+1; *arg != NULL; arg++){
1092
973
      free(*arg);
1093
974
    }
1094
975
    free(custom_argv);
1095
976
  }
 
977
  free_plugin_list(plugin_list);
1096
978
  
1097
979
  if(dir != NULL){
1098
980
    closedir(dir);
1099
981
  }
1100
982
  
1101
 
  /* Kill the processes */
1102
 
  for(plugin *p = plugin_list; p != NULL; p = p->next){
1103
 
    if(p->pid != 0){
1104
 
      close(p->fd);
1105
 
      ret = kill(p->pid, SIGTERM);
1106
 
      if(ret == -1 and errno != ESRCH){
1107
 
        /* Set-uid proccesses might not get closed */
1108
 
        perror("kill");
1109
 
      }
 
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");
1110
991
    }
 
992
    free(process_list->buffer);
 
993
    free(process_list);
1111
994
  }
1112
995
  
1113
996
  /* Wait for any remaining child processes to terminate */
1117
1000
  if(errno != ECHILD){
1118
1001
    perror("wait");
1119
1002
  }
1120
 
  
1121
 
  free_plugin_list();
1122
 
  
 
1003
 
1123
1004
  free(plugindir);
1124
1005
  free(argfile);
1125
1006