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