/mandos/release

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

« back to all changes in this revision

Viewing changes to plugin-runner.c

First version of a somewhat complete D-Bus server interface.  Also
change user/group name to "_mandos".

* debian/mandos.postinst: Rename old "mandos" user and group to
                          "_mandos"; create "_mandos" user and group
                          if none exist.
* debian/mandos-client.postinst: - '' -

* initramfs-tools-hook: Try "_mandos" before "mandos" as user and
                        group name.

* mandos (_datetime_to_dbus_struct): New; was previously local.
  (Client.started): Renamed to "last_started".  All users changed.
  (Client.started): New; boolean.
  (Client.dbus_object_path): New.
  (Client.check_command): Renamed to "checker_command".  All users
                          changed.
  (Client.__init__): Set and use "self.dbus_object_path".  Set
                     "self.started".
  (Client.start): Update "self.started".  Emit "self.PropertyChanged"
                  signals for both "started" and "last_started".
  (Client.stop): Update "self.started".  Emit "self.PropertyChanged"
                 signal for "started".
  (Client.checker_callback): Take additional "command" argument.  All
                             callers changed. Emit
                             "self.PropertyChanged" signal.
  (Client.bump_timeout): Emit "self.PropertyChanged" signal for
                         "last_checked_ok".
  (Client.start_checker): Emit "self.PropertyChanged" signal for
                          "checker_running".
  (Client.stop_checker): Emit "self.PropertyChanged" signal for
                         "checker_running".
  (Client.still_valid): Bug fix: use "getattr(self, started, False)"
                        instead of "self.started" in case this client
                        object is so new that the "started" attribute
                        has not been created yet.
  (Client.IntervalChanged, Client.CheckerIsRunning, Client.GetChecker,
  Client.GetCreated, Client.GetFingerprint, Client.GetHost,
  Client.GetInterval, Client.GetName, Client.GetStarted,
  Client.GetTimeout, Client.StateChanged, Client.TimeoutChanged):
  Removed; all callers changed.
  (Client.CheckerCompleted): Add "condition" and "command" arguments.
                             All callers changed.
  (Client.GetAllProperties, Client.PropertyChanged): New.
  (Client.StillValid): Renamed to "IsStillValid".
  (Client.StartChecker): Changed to its own function to avoid the
                         return value from "Client.start_checker()".
  (Client.Stop): Changed to its own function to avoid the return value
                 from "Client.stop()".
  (main): Try "_mandos" before "mandos" as user and group name.
          Removed inner function "remove_from_clients".  New inner
          class "MandosServer".

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/*  -*- coding: utf-8; mode: c; mode: orgtbl -*- */
 
1
/*  -*- coding: utf-8 -*- */
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 © 2008 Teddy Hogeborn
 
6
 * Copyright © 2008 Björn Påhlsson
7
7
 * 
8
8
 * This program is free software: you can redistribute it and/or
9
9
 * modify it under the terms of the GNU General Public License as
23
23
 */
24
24
 
25
25
#define _GNU_SOURCE             /* TEMP_FAILURE_RETRY(), getline(),
26
 
                                   asprintf(), O_CLOEXEC */
 
26
                                   asprintf() */
27
27
#include <stddef.h>             /* size_t, NULL */
28
28
#include <stdlib.h>             /* malloc(), exit(), EXIT_FAILURE,
29
29
                                   EXIT_SUCCESS, realloc() */
30
30
#include <stdbool.h>            /* bool, true, false */
31
31
#include <stdio.h>              /* perror, fileno(), fprintf(),
32
32
                                   stderr, STDOUT_FILENO */
33
 
#include <sys/types.h>          /* DIR, fdopendir(), stat(), struct
 
33
#include <sys/types.h>          /* DIR, opendir(), stat(), struct
34
34
                                   stat, waitpid(), WIFEXITED(),
35
35
                                   WEXITSTATUS(), wait(), pid_t,
36
36
                                   uid_t, gid_t, getuid(), getgid(),
38
38
#include <sys/select.h>         /* fd_set, select(), FD_ZERO(),
39
39
                                   FD_SET(), FD_ISSET(), FD_CLR */
40
40
#include <sys/wait.h>           /* wait(), waitpid(), WIFEXITED(),
41
 
                                   WEXITSTATUS(), WTERMSIG(),
42
 
                                   WCOREDUMP() */
 
41
                                   WEXITSTATUS() */
43
42
#include <sys/stat.h>           /* struct stat, stat(), S_ISREG() */
44
43
#include <iso646.h>             /* and, or, not */
45
 
#include <dirent.h>             /* DIR, struct dirent, fdopendir(),
 
44
#include <dirent.h>             /* DIR, struct dirent, opendir(),
46
45
                                   readdir(), closedir(), dirfd() */
47
46
#include <unistd.h>             /* struct stat, stat(), S_ISREG(),
48
47
                                   fcntl(), setuid(), setgid(),
53
52
                                   close() */
54
53
#include <fcntl.h>              /* fcntl(), F_GETFD, F_SETFD,
55
54
                                   FD_CLOEXEC */
56
 
#include <string.h>             /* strsep, strlen(), asprintf(),
57
 
                                   strsignal() */
 
55
#include <string.h>             /* strsep, strlen(), asprintf() */
58
56
#include <errno.h>              /* errno */
59
57
#include <argp.h>               /* struct argp_option, struct
60
58
                                   argp_state, struct argp,
64
62
#include <signal.h>             /* struct sigaction, sigemptyset(),
65
63
                                   sigaddset(), sigaction(),
66
64
                                   sigprocmask(), SIG_BLOCK, SIGCHLD,
67
 
                                   SIG_UNBLOCK, kill(), sig_atomic_t
68
 
                                */
 
65
                                   SIG_UNBLOCK, kill() */
69
66
#include <errno.h>              /* errno, EBADF */
70
 
#include <inttypes.h>           /* intmax_t, PRIdMAX, strtoimax() */
71
 
#include <sysexits.h>           /* EX_OSERR, EX_USAGE */
72
67
 
73
68
#define BUFFER_SIZE 256
74
69
 
85
80
  char **environ;
86
81
  int envc;
87
82
  bool disabled;
88
 
  
 
83
 
89
84
  /* Variables used for running processes*/
90
85
  pid_t pid;
91
86
  int fd;
93
88
  size_t buffer_size;
94
89
  size_t buffer_length;
95
90
  bool eof;
96
 
  volatile sig_atomic_t completed;
97
 
  int status;
 
91
  volatile bool completed;
 
92
  volatile int status;
98
93
  struct plugin *next;
99
94
} plugin;
100
95
 
103
98
/* Gets an existing plugin based on name,
104
99
   or if none is found, creates a new one */
105
100
static plugin *getplugin(char *name){
106
 
  /* Check for existing plugin with that name */
107
 
  for(plugin *p = plugin_list; p != NULL; p = p->next){
108
 
    if((p->name == name)
109
 
       or (p->name and name and (strcmp(p->name, name) == 0))){
 
101
  /* Check for exiting plugin with that name */
 
102
  for (plugin *p = plugin_list; p != NULL; p = p->next){
 
103
    if ((p->name == name)
 
104
        or (p->name and name and (strcmp(p->name, name) == 0))){
110
105
      return p;
111
106
    }
112
107
  }
113
108
  /* Create a new plugin */
114
 
  plugin *new_plugin = NULL;
115
 
  do {
116
 
    new_plugin = malloc(sizeof(plugin));
117
 
  } while(new_plugin == NULL and errno == EINTR);
118
 
  if(new_plugin == NULL){
 
109
  plugin *new_plugin = malloc(sizeof(plugin));
 
110
  if (new_plugin == NULL){
119
111
    return NULL;
120
112
  }
121
113
  char *copy_name = NULL;
122
114
  if(name != NULL){
123
 
    do {
124
 
      copy_name = strdup(name);
125
 
    } while(copy_name == NULL and errno == EINTR);
 
115
    copy_name = strdup(name);
126
116
    if(copy_name == NULL){
127
 
      int e = errno;
128
 
      free(new_plugin);
129
 
      errno = e;
130
117
      return NULL;
131
118
    }
132
119
  }
133
120
  
134
 
  *new_plugin = (plugin){ .name = copy_name,
135
 
                          .argc = 1,
136
 
                          .disabled = false,
137
 
                          .next = plugin_list };
 
121
  *new_plugin = (plugin) { .name = copy_name,
 
122
                           .argc = 1,
 
123
                           .disabled = false,
 
124
                           .next = plugin_list };
138
125
  
139
 
  do {
140
 
    new_plugin->argv = malloc(sizeof(char *) * 2);
141
 
  } while(new_plugin->argv == NULL and errno == EINTR);
142
 
  if(new_plugin->argv == NULL){
143
 
    int e = errno;
 
126
  new_plugin->argv = malloc(sizeof(char *) * 2);
 
127
  if (new_plugin->argv == NULL){
144
128
    free(copy_name);
145
129
    free(new_plugin);
146
 
    errno = e;
147
130
    return NULL;
148
131
  }
149
132
  new_plugin->argv[0] = copy_name;
150
133
  new_plugin->argv[1] = NULL;
151
134
  
152
 
  do {
153
 
    new_plugin->environ = malloc(sizeof(char *));
154
 
  } while(new_plugin->environ == NULL and errno == EINTR);
 
135
  new_plugin->environ = malloc(sizeof(char *));
155
136
  if(new_plugin->environ == NULL){
156
 
    int e = errno;
157
137
    free(copy_name);
158
138
    free(new_plugin->argv);
159
139
    free(new_plugin);
160
 
    errno = e;
161
140
    return NULL;
162
141
  }
163
142
  new_plugin->environ[0] = NULL;
171
150
static bool add_to_char_array(const char *new, char ***array,
172
151
                              int *len){
173
152
  /* Resize the pointed-to array to hold one more pointer */
174
 
  do {
175
 
    *array = realloc(*array, sizeof(char *)
176
 
                     * (size_t) ((*len) + 2));
177
 
  } while(*array == NULL and errno == EINTR);
 
153
  *array = realloc(*array, sizeof(char *)
 
154
                   * (size_t) ((*len) + 2));
178
155
  /* Malloc check */
179
156
  if(*array == NULL){
180
157
    return false;
181
158
  }
182
159
  /* Make a copy of the new string */
183
 
  char *copy;
184
 
  do {
185
 
    copy = strdup(new);
186
 
  } while(copy == NULL and errno == EINTR);
 
160
  char *copy = strdup(new);
187
161
  if(copy == NULL){
188
162
    return false;
189
163
  }
215
189
    if(strncmp(*e, def, namelen + 1) == 0){
216
190
      /* It already exists */
217
191
      if(replace){
218
 
        char *new;
219
 
        do {
220
 
          new = realloc(*e, strlen(def) + 1);
221
 
        } while(new == NULL and errno == EINTR);
 
192
        char *new = realloc(*e, strlen(def) + 1);
222
193
        if(new == NULL){
223
194
          return false;
224
195
        }
234
205
/*
235
206
 * Based on the example in the GNU LibC manual chapter 13.13 "File
236
207
 * Descriptor Flags".
237
 
 | [[info:libc:Descriptor%20Flags][File Descriptor Flags]] |
 
208
 * *Note File Descriptor Flags:(libc)Descriptor Flags.
238
209
 */
239
210
static int set_cloexec_flag(int fd){
240
 
  int ret = (int)TEMP_FAILURE_RETRY(fcntl(fd, F_GETFD, 0));
 
211
  int ret = fcntl(fd, F_GETFD, 0);
241
212
  /* If reading the flags failed, return error indication now. */
242
213
  if(ret < 0){
243
214
    return ret;
244
215
  }
245
216
  /* Store modified flag word in the descriptor. */
246
 
  return (int)TEMP_FAILURE_RETRY(fcntl(fd, F_SETFD,
247
 
                                       ret | FD_CLOEXEC));
 
217
  return fcntl(fd, F_SETFD, ret | FD_CLOEXEC);
248
218
}
249
219
 
250
220
 
251
221
/* Mark processes as completed when they exit, and save their exit
252
222
   status. */
253
223
static void handle_sigchld(__attribute__((unused)) int sig){
254
 
  int old_errno = errno;
255
224
  while(true){
256
225
    plugin *proc = plugin_list;
257
226
    int status;
261
230
      break;
262
231
    }
263
232
    if(pid == -1){
264
 
      if(errno == ECHILD){
265
 
        /* No child processes */
266
 
        break;
 
233
      if (errno != ECHILD){
 
234
        perror("waitpid");
267
235
      }
268
 
      perror("waitpid");
 
236
      /* No child processes */
 
237
      break;
269
238
    }
270
239
    
271
240
    /* A child exited, find it in process_list */
277
246
      continue;
278
247
    }
279
248
    proc->status = status;
280
 
    proc->completed = 1;
 
249
    proc->completed = true;
281
250
  }
282
 
  errno = old_errno;
283
251
}
284
252
 
285
253
/* Prints out a password to stdout */
307
275
  }
308
276
  free(plugin_node->environ);
309
277
  free(plugin_node->buffer);
310
 
  
 
278
 
311
279
  /* Removes the plugin from the singly-linked list */
312
280
  if(plugin_node == plugin_list){
313
281
    /* First one - simple */
341
309
  struct stat st;
342
310
  fd_set rfds_all;
343
311
  int ret, maxfd = 0;
344
 
  ssize_t sret;
345
312
  uid_t uid = 65534;
346
313
  gid_t gid = 65534;
347
314
  bool debug = false;
401
368
      .doc = "Group ID the plugins will run as", .group = 3 },
402
369
    { .name = "debug", .key = 132,
403
370
      .doc = "Debug mode", .group = 4 },
404
 
    /*
405
 
     * These reproduce what we would get without ARGP_NO_HELP
406
 
     */
407
 
    { .name = "help", .key = '?',
408
 
      .doc = "Give this help list", .group = -1 },
409
 
    { .name = "usage", .key = -3,
410
 
      .doc = "Give a short usage message", .group = -1 },
411
 
    { .name = "version", .key = 'V',
412
 
      .doc = "Print program version", .group = -1 },
413
371
    { .name = NULL }
414
372
  };
415
373
  
416
 
  error_t parse_opt(int key, char *arg, struct argp_state *state){
417
 
    errno = 0;
418
 
    switch(key){
419
 
      char *tmp;
420
 
      intmax_t tmpmax;
 
374
  error_t parse_opt (int key, char *arg, __attribute__((unused))
 
375
                     struct argp_state *state) {
 
376
    switch (key) {
421
377
    case 'g':                   /* --global-options */
422
 
      {
423
 
        char *plugin_option;
424
 
        while((plugin_option = strsep(&arg, ",")) != NULL){
425
 
          if(not add_argument(getplugin(NULL), plugin_option)){
426
 
            break;
 
378
      if (arg != NULL){
 
379
        char *p;
 
380
        while((p = strsep(&arg, ",")) != NULL){
 
381
          if(p[0] == '\0'){
 
382
            continue;
 
383
          }
 
384
          if(not add_argument(getplugin(NULL), p)){
 
385
            perror("add_argument");
 
386
            return ARGP_ERR_UNKNOWN;
427
387
          }
428
388
        }
429
389
      }
430
390
      break;
431
391
    case 'G':                   /* --global-env */
432
 
      add_environment(getplugin(NULL), arg, true);
 
392
      if(arg == NULL){
 
393
        break;
 
394
      }
 
395
      if(not add_environment(getplugin(NULL), arg, true)){
 
396
        perror("add_environment");
 
397
      }
433
398
      break;
434
399
    case 'o':                   /* --options-for */
435
 
      {
436
 
        char *option_list = strchr(arg, ':');
437
 
        if(option_list == NULL){
438
 
          argp_error(state, "No colon in \"%s\"", arg);
439
 
          errno = EINVAL;
440
 
          break;
441
 
        }
442
 
        *option_list = '\0';
443
 
        option_list++;
444
 
        if(arg[0] == '\0'){
445
 
          argp_error(state, "Empty plugin name");
446
 
          errno = EINVAL;
447
 
          break;
448
 
        }
449
 
        char *option;
450
 
        while((option = strsep(&option_list, ",")) != NULL){
451
 
          if(not add_argument(getplugin(arg), option)){
452
 
            break;
 
400
      if (arg != NULL){
 
401
        char *p_name = strsep(&arg, ":");
 
402
        if(p_name[0] == '\0' or arg == NULL){
 
403
          break;
 
404
        }
 
405
        char *opt = strsep(&arg, ":");
 
406
        if(opt[0] == '\0' or opt == NULL){
 
407
          break;
 
408
        }
 
409
        char *p;
 
410
        while((p = strsep(&opt, ",")) != NULL){
 
411
          if(p[0] == '\0'){
 
412
            continue;
 
413
          }
 
414
          if(not add_argument(getplugin(p_name), p)){
 
415
            perror("add_argument");
 
416
            return ARGP_ERR_UNKNOWN;
453
417
          }
454
418
        }
455
419
      }
456
420
      break;
457
421
    case 'E':                   /* --env-for */
 
422
      if(arg == NULL){
 
423
        break;
 
424
      }
458
425
      {
459
426
        char *envdef = strchr(arg, ':');
460
427
        if(envdef == NULL){
461
 
          argp_error(state, "No colon in \"%s\"", arg);
462
 
          errno = EINVAL;
463
428
          break;
464
429
        }
465
430
        *envdef = '\0';
466
 
        envdef++;
467
 
        if(arg[0] == '\0'){
468
 
          argp_error(state, "Empty plugin name");
469
 
          errno = EINVAL;
470
 
          break;
 
431
        if(not add_environment(getplugin(arg), envdef+1, true)){
 
432
          perror("add_environment");
471
433
        }
472
 
        add_environment(getplugin(arg), envdef, true);
473
434
      }
474
435
      break;
475
436
    case 'd':                   /* --disable */
476
 
      {
 
437
      if (arg != NULL){
477
438
        plugin *p = getplugin(arg);
478
 
        if(p != NULL){
479
 
          p->disabled = true;
 
439
        if(p == NULL){
 
440
          return ARGP_ERR_UNKNOWN;
480
441
        }
 
442
        p->disabled = true;
481
443
      }
482
444
      break;
483
445
    case 'e':                   /* --enable */
484
 
      {
 
446
      if (arg != NULL){
485
447
        plugin *p = getplugin(arg);
486
 
        if(p != NULL){
487
 
          p->disabled = false;
 
448
        if(p == NULL){
 
449
          return ARGP_ERR_UNKNOWN;
488
450
        }
 
451
        p->disabled = false;
489
452
      }
490
453
      break;
491
454
    case 128:                   /* --plugin-dir */
492
455
      free(plugindir);
493
456
      plugindir = strdup(arg);
 
457
      if(plugindir == NULL){
 
458
        perror("strdup");
 
459
      }      
494
460
      break;
495
461
    case 129:                   /* --config-file */
496
462
      /* This is already done by parse_opt_config_file() */
497
463
      break;
498
464
    case 130:                   /* --userid */
499
 
      tmpmax = strtoimax(arg, &tmp, 10);
500
 
      if(errno != 0 or tmp == arg or *tmp != '\0'
501
 
         or tmpmax != (uid_t)tmpmax){
502
 
        argp_error(state, "Bad user ID number: \"%s\", using %"
503
 
                   PRIdMAX, arg, (intmax_t)uid);
504
 
        break;
505
 
      }
506
 
      uid = (uid_t)tmpmax;
 
465
      uid = (uid_t)strtol(arg, NULL, 10);
507
466
      break;
508
467
    case 131:                   /* --groupid */
509
 
      tmpmax = strtoimax(arg, &tmp, 10);
510
 
      if(errno != 0 or tmp == arg or *tmp != '\0'
511
 
         or tmpmax != (gid_t)tmpmax){
512
 
        argp_error(state, "Bad group ID number: \"%s\", using %"
513
 
                   PRIdMAX, arg, (intmax_t)gid);
514
 
        break;
515
 
      }
516
 
      gid = (gid_t)tmpmax;
 
468
      gid = (gid_t)strtol(arg, NULL, 10);
517
469
      break;
518
470
    case 132:                   /* --debug */
519
471
      debug = true;
520
472
      break;
521
 
      /*
522
 
       * These reproduce what we would get without ARGP_NO_HELP
523
 
       */
524
 
    case '?':                   /* --help */
525
 
      state->flags &= ~(unsigned int)ARGP_NO_EXIT; /* force exit */
526
 
      argp_state_help(state, state->out_stream, ARGP_HELP_STD_HELP);
527
 
    case -3:                    /* --usage */
528
 
      state->flags &= ~(unsigned int)ARGP_NO_EXIT; /* force exit */
529
 
      argp_state_help(state, state->out_stream,
530
 
                      ARGP_HELP_USAGE | ARGP_HELP_EXIT_OK);
531
 
    case 'V':                   /* --version */
532
 
      fprintf(state->out_stream, "%s\n", argp_program_version);
533
 
      exit(EXIT_SUCCESS);
534
 
      break;
535
 
/*
536
 
 * When adding more options before this line, remember to also add a
537
 
 * "case" to the "parse_opt_config_file" function below.
538
 
 */
539
473
    case ARGP_KEY_ARG:
540
474
      /* Cryptsetup always passes an argument, which is an empty
541
475
         string if "none" was specified in /etc/crypttab.  So if
542
476
         argument was empty, we ignore it silently. */
543
 
      if(arg[0] == '\0'){
544
 
        break;
 
477
      if(arg[0] != '\0'){
 
478
        fprintf(stderr, "Ignoring unknown argument \"%s\"\n", arg);
545
479
      }
 
480
      break;
 
481
    case ARGP_KEY_END:
 
482
      break;
546
483
    default:
547
484
      return ARGP_ERR_UNKNOWN;
548
485
    }
549
 
    return errno;               /* Set to 0 at start */
 
486
    return 0;
550
487
  }
551
488
  
552
489
  /* This option parser is the same as parse_opt() above, except it
553
490
     ignores everything but the --config-file option. */
554
 
  error_t parse_opt_config_file(int key, char *arg,
555
 
                                __attribute__((unused))
556
 
                                struct argp_state *state){
557
 
    errno = 0;
558
 
    switch(key){
 
491
  error_t parse_opt_config_file (int key, char *arg,
 
492
                                 __attribute__((unused))
 
493
                                 struct argp_state *state) {
 
494
    switch (key) {
559
495
    case 'g':                   /* --global-options */
560
496
    case 'G':                   /* --global-env */
561
497
    case 'o':                   /* --options-for */
567
503
    case 129:                   /* --config-file */
568
504
      free(argfile);
569
505
      argfile = strdup(arg);
570
 
      break;
 
506
      if(argfile == NULL){
 
507
        perror("strdup");
 
508
      }
 
509
      break;      
571
510
    case 130:                   /* --userid */
572
511
    case 131:                   /* --groupid */
573
512
    case 132:                   /* --debug */
574
 
    case '?':                   /* --help */
575
 
    case -3:                    /* --usage */
576
 
    case 'V':                   /* --version */
577
513
    case ARGP_KEY_ARG:
 
514
    case ARGP_KEY_END:
578
515
      break;
579
516
    default:
580
517
      return ARGP_ERR_UNKNOWN;
581
518
    }
582
 
    return errno;
 
519
    return 0;
583
520
  }
584
521
  
585
522
  struct argp argp = { .options = options,
587
524
                       .args_doc = "",
588
525
                       .doc = "Mandos plugin runner -- Run plugins" };
589
526
  
590
 
  /* Parse using parse_opt_config_file() in order to get the custom
 
527
  /* Parse using the parse_opt_config_file in order to get the custom
591
528
     config file location, if any. */
592
 
  ret = argp_parse(&argp, argc, argv,
593
 
                   ARGP_IN_ORDER | ARGP_NO_EXIT | ARGP_NO_HELP,
594
 
                   NULL, NULL);
595
 
  switch(ret){
596
 
  case 0:
597
 
    break;
598
 
  case ENOMEM:
599
 
  default:
600
 
    errno = ret;
601
 
    perror("argp_parse");
602
 
    exitstatus = EX_OSERR;
603
 
    goto fallback;
604
 
  case EINVAL:
605
 
    exitstatus = EX_USAGE;
 
529
  ret = argp_parse (&argp, argc, argv, ARGP_IN_ORDER, 0, NULL);
 
530
  if (ret == ARGP_ERR_UNKNOWN){
 
531
    fprintf(stderr, "Unknown error while parsing arguments\n");
 
532
    exitstatus = EXIT_FAILURE;
606
533
    goto fallback;
607
534
  }
608
535
  
610
537
  argp.parser = parse_opt;
611
538
  
612
539
  /* Open the configfile if available */
613
 
  if(argfile == NULL){
 
540
  if (argfile == NULL){
614
541
    conffp = fopen(AFILE, "r");
615
542
  } else {
616
543
    conffp = fopen(argfile, "r");
617
 
  }
 
544
  }  
618
545
  if(conffp != NULL){
619
546
    char *org_line = NULL;
620
547
    char *p, *arg, *new_arg, *line;
621
548
    size_t size = 0;
 
549
    ssize_t sret;
622
550
    const char whitespace_delims[] = " \r\t\f\v\n";
623
551
    const char comment_delim[] = "#";
624
 
    
 
552
 
625
553
    custom_argc = 1;
626
554
    custom_argv = malloc(sizeof(char*) * 2);
627
555
    if(custom_argv == NULL){
631
559
    }
632
560
    custom_argv[0] = argv[0];
633
561
    custom_argv[1] = NULL;
634
 
    
 
562
 
635
563
    /* for each line in the config file, strip whitespace and ignore
636
564
       commented text */
637
565
    while(true){
639
567
      if(sret == -1){
640
568
        break;
641
569
      }
642
 
      
 
570
 
643
571
      line = org_line;
644
572
      arg = strsep(&line, comment_delim);
645
573
      while((p = strsep(&arg, whitespace_delims)) != NULL){
664
592
          goto fallback;
665
593
        }
666
594
        custom_argv[custom_argc-1] = new_arg;
667
 
        custom_argv[custom_argc] = NULL;
 
595
        custom_argv[custom_argc] = NULL;        
668
596
      }
669
597
    }
670
 
    do {
671
 
      ret = fclose(conffp);
672
 
    } while(ret == EOF and errno == EINTR);
673
 
    if(ret == EOF){
674
 
      perror("fclose");
675
 
      exitstatus = EXIT_FAILURE;
676
 
      goto fallback;
677
 
    }
678
598
    free(org_line);
679
599
  } else {
680
600
    /* Check for harmful errors and go to fallback. Other errors might
681
601
       not affect opening plugins */
682
 
    if(errno == EMFILE or errno == ENFILE or errno == ENOMEM){
 
602
    if (errno == EMFILE or errno == ENFILE or errno == ENOMEM){
683
603
      perror("fopen");
684
604
      exitstatus = EXIT_FAILURE;
685
605
      goto fallback;
686
606
    }
687
607
  }
688
 
  /* If there were any arguments from the configuration file, pass
689
 
     them to parser as command line arguments */
 
608
  /* If there was any arguments from configuration file,
 
609
     pass them to parser as command arguments */
690
610
  if(custom_argv != NULL){
691
 
    ret = argp_parse(&argp, custom_argc, custom_argv,
692
 
                     ARGP_IN_ORDER | ARGP_NO_EXIT | ARGP_NO_HELP,
693
 
                     NULL, NULL);
694
 
    switch(ret){
695
 
    case 0:
696
 
      break;
697
 
    case ENOMEM:
698
 
    default:
699
 
      errno = ret;
700
 
      perror("argp_parse");
701
 
      exitstatus = EX_OSERR;
702
 
      goto fallback;
703
 
    case EINVAL:
704
 
      exitstatus = EX_CONFIG;
 
611
    ret = argp_parse (&argp, custom_argc, custom_argv, ARGP_IN_ORDER,
 
612
                      0, NULL);
 
613
    if (ret == ARGP_ERR_UNKNOWN){
 
614
      fprintf(stderr, "Unknown error while parsing arguments\n");
 
615
      exitstatus = EXIT_FAILURE;
705
616
      goto fallback;
706
617
    }
707
618
  }
708
619
  
709
620
  /* Parse actual command line arguments, to let them override the
710
621
     config file */
711
 
  ret = argp_parse(&argp, argc, argv,
712
 
                   ARGP_IN_ORDER | ARGP_NO_EXIT | ARGP_NO_HELP,
713
 
                   NULL, NULL);
714
 
  switch(ret){
715
 
  case 0:
716
 
    break;
717
 
  case ENOMEM:
718
 
  default:
719
 
    errno = ret;
720
 
    perror("argp_parse");
721
 
    exitstatus = EX_OSERR;
722
 
    goto fallback;
723
 
  case EINVAL:
724
 
    exitstatus = EX_USAGE;
 
622
  ret = argp_parse (&argp, argc, argv, ARGP_IN_ORDER, 0, NULL);
 
623
  if (ret == ARGP_ERR_UNKNOWN){
 
624
    fprintf(stderr, "Unknown error while parsing arguments\n");
 
625
    exitstatus = EXIT_FAILURE;
725
626
    goto fallback;
726
627
  }
727
628
  
732
633
      for(char **a = p->argv; *a != NULL; a++){
733
634
        fprintf(stderr, "\tArg: %s\n", *a);
734
635
      }
735
 
      fprintf(stderr, "...and %d environment variables\n", p->envc);
 
636
      fprintf(stderr, "...and %u environment variables\n", p->envc);
736
637
      for(char **a = p->environ; *a != NULL; a++){
737
638
        fprintf(stderr, "\t%s\n", *a);
738
639
      }
740
641
  }
741
642
  
742
643
  /* Strip permissions down to nobody */
 
644
  ret = setuid(uid);
 
645
  if (ret == -1){
 
646
    perror("setuid");
 
647
  }  
743
648
  setgid(gid);
744
 
  if(ret == -1){
 
649
  if (ret == -1){
745
650
    perror("setgid");
746
651
  }
747
 
  ret = setuid(uid);
748
 
  if(ret == -1){
749
 
    perror("setuid");
750
 
  }
751
 
  
752
 
  /* Open plugin directory with close_on_exec flag */
 
652
  
 
653
  if (plugindir == NULL){
 
654
    dir = opendir(PDIR);
 
655
  } else {
 
656
    dir = opendir(plugindir);
 
657
  }
 
658
  
 
659
  if(dir == NULL){
 
660
    perror("Could not open plugin dir");
 
661
    exitstatus = EXIT_FAILURE;
 
662
    goto fallback;
 
663
  }
 
664
  
 
665
  /* Set the FD_CLOEXEC flag on the directory, if possible */
753
666
  {
754
 
    int dir_fd = -1;
755
 
    if(plugindir == NULL){
756
 
      dir_fd = open(PDIR, O_RDONLY |
757
 
#ifdef O_CLOEXEC
758
 
                    O_CLOEXEC
759
 
#else  /* not O_CLOEXEC */
760
 
                    0
761
 
#endif  /* not O_CLOEXEC */
762
 
                    );
763
 
    } else {
764
 
      dir_fd = open(plugindir, O_RDONLY |
765
 
#ifdef O_CLOEXEC
766
 
                    O_CLOEXEC
767
 
#else  /* not O_CLOEXEC */
768
 
                    0
769
 
#endif  /* not O_CLOEXEC */
770
 
                    );
771
 
    }
772
 
    if(dir_fd == -1){
773
 
      perror("Could not open plugin dir");
774
 
      exitstatus = EXIT_FAILURE;
775
 
      goto fallback;
776
 
    }
777
 
    
778
 
#ifndef O_CLOEXEC
779
 
  /* Set the FD_CLOEXEC flag on the directory */
780
 
    ret = set_cloexec_flag(dir_fd);
781
 
    if(ret < 0){
782
 
      perror("set_cloexec_flag");
783
 
      TEMP_FAILURE_RETRY(close(dir_fd));
784
 
      exitstatus = EXIT_FAILURE;
785
 
      goto fallback;
786
 
    }
787
 
#endif  /* O_CLOEXEC */
788
 
    
789
 
    dir = fdopendir(dir_fd);
790
 
    if(dir == NULL){
791
 
      perror("Could not open plugin dir");
792
 
      TEMP_FAILURE_RETRY(close(dir_fd));
793
 
      exitstatus = EXIT_FAILURE;
794
 
      goto fallback;
 
667
    int dir_fd = dirfd(dir);
 
668
    if(dir_fd >= 0){
 
669
      ret = set_cloexec_flag(dir_fd);
 
670
      if(ret < 0){
 
671
        perror("set_cloexec_flag");
 
672
        exitstatus = EXIT_FAILURE;
 
673
        goto fallback;
 
674
      }
795
675
    }
796
676
  }
797
677
  
799
679
  
800
680
  /* Read and execute any executable in the plugin directory*/
801
681
  while(true){
802
 
    do {
803
 
      dirst = readdir(dir);
804
 
    } while(dirst == NULL and errno == EINTR);
 
682
    dirst = readdir(dir);
805
683
    
806
684
    /* All directory entries have been processed */
807
685
    if(dirst == NULL){
808
 
      if(errno == EBADF){
 
686
      if (errno == EBADF){
809
687
        perror("readdir");
810
688
        exitstatus = EXIT_FAILURE;
811
689
        goto fallback;
858
736
        continue;
859
737
      }
860
738
    }
861
 
    
 
739
 
862
740
    char *filename;
863
741
    if(plugindir == NULL){
864
 
      ret = (int)TEMP_FAILURE_RETRY(asprintf(&filename, PDIR "/%s",
865
 
                                             dirst->d_name));
 
742
      ret = asprintf(&filename, PDIR "/%s", dirst->d_name);
866
743
    } else {
867
 
      ret = (int)TEMP_FAILURE_RETRY(asprintf(&filename, "%s/%s",
868
 
                                             plugindir,
869
 
                                             dirst->d_name));
 
744
      ret = asprintf(&filename, "%s/%s", plugindir, dirst->d_name);
870
745
    }
871
746
    if(ret < 0){
872
747
      perror("asprintf");
873
748
      continue;
874
749
    }
875
750
    
876
 
    ret = (int)TEMP_FAILURE_RETRY(stat(filename, &st));
877
 
    if(ret == -1){
 
751
    ret = stat(filename, &st);
 
752
    if (ret == -1){
878
753
      perror("stat");
879
754
      free(filename);
880
755
      continue;
881
756
    }
882
 
    
 
757
 
883
758
    /* Ignore non-executable files */
884
 
    if(not S_ISREG(st.st_mode)
885
 
       or (TEMP_FAILURE_RETRY(access(filename, X_OK)) != 0)){
 
759
    if (not S_ISREG(st.st_mode) or (access(filename, X_OK) != 0)){
886
760
      if(debug){
887
761
        fprintf(stderr, "Ignoring plugin dir entry \"%s\""
888
762
                " with bad type or mode\n", filename);
934
808
    }
935
809
    
936
810
    int pipefd[2];
937
 
    ret = (int)TEMP_FAILURE_RETRY(pipe(pipefd));
938
 
    if(ret == -1){
 
811
    ret = pipe(pipefd);
 
812
    if (ret == -1){
939
813
      perror("pipe");
940
814
      exitstatus = EXIT_FAILURE;
941
815
      goto fallback;
954
828
      goto fallback;
955
829
    }
956
830
    /* Block SIGCHLD until process is safely in process list */
957
 
    ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_BLOCK,
958
 
                                              &sigchld_action.sa_mask,
959
 
                                              NULL));
 
831
    ret = sigprocmask (SIG_BLOCK, &sigchld_action.sa_mask, NULL);
960
832
    if(ret < 0){
961
833
      perror("sigprocmask");
962
834
      exitstatus = EXIT_FAILURE;
963
835
      goto fallback;
964
836
    }
965
837
    /* Starting a new process to be watched */
966
 
    pid_t pid;
967
 
    do {
968
 
      pid = fork();
969
 
    } while(pid == -1 and errno == EINTR);
 
838
    pid_t pid = fork();
970
839
    if(pid == -1){
971
840
      perror("fork");
972
841
      exitstatus = EXIT_FAILURE;
1010
879
      /* no return */
1011
880
    }
1012
881
    /* Parent process */
1013
 
    TEMP_FAILURE_RETRY(close(pipefd[1])); /* Close unused write end of
1014
 
                                             pipe */
 
882
    close(pipefd[1]);           /* Close unused write end of pipe */
1015
883
    free(filename);
1016
884
    plugin *new_plugin = getplugin(dirst->d_name);
1017
 
    if(new_plugin == NULL){
 
885
    if (new_plugin == NULL){
1018
886
      perror("getplugin");
1019
 
      ret = (int)(TEMP_FAILURE_RETRY
1020
 
                  (sigprocmask(SIG_UNBLOCK, &sigchld_action.sa_mask,
1021
 
                               NULL)));
 
887
      ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
1022
888
      if(ret < 0){
1023
889
        perror("sigprocmask");
1024
890
      }
1031
897
    
1032
898
    /* Unblock SIGCHLD so signal handler can be run if this process
1033
899
       has already completed */
1034
 
    ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_UNBLOCK,
1035
 
                                              &sigchld_action.sa_mask,
1036
 
                                              NULL));
 
900
    ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
1037
901
    if(ret < 0){
1038
902
      perror("sigprocmask");
1039
903
      exitstatus = EXIT_FAILURE;
1040
904
      goto fallback;
1041
905
    }
1042
906
    
1043
 
    FD_SET(new_plugin->fd, &rfds_all); /* Spurious warning from
1044
 
                                          -Wconversion */
 
907
    FD_SET(new_plugin->fd, &rfds_all);
1045
908
    
1046
 
    if(maxfd < new_plugin->fd){
 
909
    if (maxfd < new_plugin->fd){
1047
910
      maxfd = new_plugin->fd;
1048
911
    }
1049
912
  }
1050
913
  
1051
 
  TEMP_FAILURE_RETRY(closedir(dir));
 
914
  closedir(dir);
1052
915
  dir = NULL;
1053
 
  free_plugin(getplugin(NULL));
1054
916
  
1055
917
  for(plugin *p = plugin_list; p != NULL; p = p->next){
1056
918
    if(p->pid != 0){
1067
929
  while(plugin_list){
1068
930
    fd_set rfds = rfds_all;
1069
931
    int select_ret = select(maxfd+1, &rfds, NULL, NULL, NULL);
1070
 
    if(select_ret == -1 and errno != EINTR){
 
932
    if (select_ret == -1){
1071
933
      perror("select");
1072
934
      exitstatus = EXIT_FAILURE;
1073
935
      goto fallback;
1076
938
       from one of them */
1077
939
    for(plugin *proc = plugin_list; proc != NULL;){
1078
940
      /* Is this process completely done? */
1079
 
      if(proc->completed and proc->eof){
 
941
      if(proc->eof and proc->completed){
1080
942
        /* Only accept the plugin output if it exited cleanly */
1081
943
        if(not WIFEXITED(proc->status)
1082
944
           or WEXITSTATUS(proc->status) != 0){
1083
945
          /* Bad exit by plugin */
1084
 
          
 
946
 
1085
947
          if(debug){
1086
948
            if(WIFEXITED(proc->status)){
1087
 
              fprintf(stderr, "Plugin %s [%" PRIdMAX "] exited with"
1088
 
                      " status %d\n", proc->name,
1089
 
                      (intmax_t) (proc->pid),
 
949
              fprintf(stderr, "Plugin %u exited with status %d\n",
 
950
                      (unsigned int) (proc->pid),
1090
951
                      WEXITSTATUS(proc->status));
1091
 
            } else if(WIFSIGNALED(proc->status)){
1092
 
              fprintf(stderr, "Plugin %s [%" PRIdMAX "] killed by"
1093
 
                      " signal %d: %s\n", proc->name,
1094
 
                      (intmax_t) (proc->pid),
1095
 
                      WTERMSIG(proc->status),
1096
 
                      strsignal(WTERMSIG(proc->status)));
 
952
            } else if(WIFSIGNALED(proc->status)) {
 
953
              fprintf(stderr, "Plugin %u killed by signal %d\n",
 
954
                      (unsigned int) (proc->pid),
 
955
                      WTERMSIG(proc->status));
1097
956
            } else if(WCOREDUMP(proc->status)){
1098
 
              fprintf(stderr, "Plugin %s [%" PRIdMAX "] dumped"
1099
 
                      " core\n", proc->name, (intmax_t) (proc->pid));
 
957
              fprintf(stderr, "Plugin %d dumped core\n",
 
958
                      (unsigned int) (proc->pid));
1100
959
            }
1101
960
          }
1102
961
          
1103
962
          /* Remove the plugin */
1104
 
          FD_CLR(proc->fd, &rfds_all); /* Spurious warning from
1105
 
                                          -Wconversion */
1106
 
          
 
963
          FD_CLR(proc->fd, &rfds_all);
 
964
 
1107
965
          /* Block signal while modifying process_list */
1108
 
          ret = (int)TEMP_FAILURE_RETRY(sigprocmask
1109
 
                                        (SIG_BLOCK,
1110
 
                                         &sigchld_action.sa_mask,
1111
 
                                         NULL));
 
966
          ret = sigprocmask(SIG_BLOCK, &sigchld_action.sa_mask, NULL);
1112
967
          if(ret < 0){
1113
968
            perror("sigprocmask");
1114
969
            exitstatus = EXIT_FAILURE;
1120
975
          proc = next_plugin;
1121
976
          
1122
977
          /* We are done modifying process list, so unblock signal */
1123
 
          ret = (int)(TEMP_FAILURE_RETRY
1124
 
                      (sigprocmask(SIG_UNBLOCK,
1125
 
                                   &sigchld_action.sa_mask, NULL)));
 
978
          ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask,
 
979
                             NULL);
1126
980
          if(ret < 0){
1127
981
            perror("sigprocmask");
1128
982
            exitstatus = EXIT_FAILURE;
1148
1002
      }
1149
1003
      
1150
1004
      /* This process has not completed.  Does it have any output? */
1151
 
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){ /* Spurious
1152
 
                                                         warning from
1153
 
                                                         -Wconversion */
 
1005
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){
1154
1006
        /* This process had nothing to say at this time */
1155
1007
        proc = proc->next;
1156
1008
        continue;
1159
1011
      if(proc->buffer_length + BUFFER_SIZE > proc->buffer_size){
1160
1012
        proc->buffer = realloc(proc->buffer, proc->buffer_size
1161
1013
                               + (size_t) BUFFER_SIZE);
1162
 
        if(proc->buffer == NULL){
 
1014
        if (proc->buffer == NULL){
1163
1015
          perror("malloc");
1164
1016
          exitstatus = EXIT_FAILURE;
1165
1017
          goto fallback;
1167
1019
        proc->buffer_size += BUFFER_SIZE;
1168
1020
      }
1169
1021
      /* Read from the process */
1170
 
      sret = TEMP_FAILURE_RETRY(read(proc->fd,
1171
 
                                     proc->buffer
1172
 
                                     + proc->buffer_length,
1173
 
                                     BUFFER_SIZE));
1174
 
      if(sret < 0){
 
1022
      ret = read(proc->fd, proc->buffer + proc->buffer_length,
 
1023
                 BUFFER_SIZE);
 
1024
      if(ret < 0){
1175
1025
        /* Read error from this process; ignore the error */
1176
1026
        proc = proc->next;
1177
1027
        continue;
1178
1028
      }
1179
 
      if(sret == 0){
 
1029
      if(ret == 0){
1180
1030
        /* got EOF */
1181
1031
        proc->eof = true;
1182
1032
      } else {
1183
 
        proc->buffer_length += (size_t) sret;
 
1033
        proc->buffer_length += (size_t) ret;
1184
1034
      }
1185
1035
    }
1186
1036
  }
1187
 
  
1188
 
  
 
1037
 
 
1038
 
1189
1039
 fallback:
1190
1040
  
1191
1041
  if(plugin_list == NULL or exitstatus != EXIT_SUCCESS){
1238
1088
  }
1239
1089
  
1240
1090
  /* Wait for any remaining child processes to terminate */
1241
 
  do {
 
1091
  do{
1242
1092
    ret = wait(NULL);
1243
1093
  } while(ret >= 0);
1244
1094
  if(errno != ECHILD){