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