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