/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-2016 Teddy Hogeborn
6
 
 * Copyright © 2008-2016 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
19
19
 * along with this program.  If not, see
20
20
 * <http://www.gnu.org/licenses/>.
21
21
 * 
22
 
 * Contact the authors at <mandos@recompile.se>.
 
22
 * Contact the authors at <mandos@fukt.bsnet.se>.
23
23
 */
24
24
 
25
25
#define _GNU_SOURCE             /* TEMP_FAILURE_RETRY(), getline(),
26
 
                                   O_CLOEXEC, pipe2() */
 
26
                                   asprintf() */
27
27
#include <stddef.h>             /* size_t, NULL */
28
 
#include <stdlib.h>             /* malloc(), exit(), EXIT_SUCCESS,
29
 
                                   realloc() */
 
28
#include <stdlib.h>             /* malloc(), exit(), EXIT_FAILURE,
 
29
                                   EXIT_SUCCESS, realloc() */
30
30
#include <stdbool.h>            /* bool, true, false */
31
 
#include <stdio.h>              /* fileno(), fprintf(),
32
 
                                   stderr, STDOUT_FILENO, fclose() */
33
 
#include <sys/types.h>          /* fstat(), struct stat, waitpid(),
34
 
                                   WIFEXITED(), WEXITSTATUS(), wait(),
35
 
                                   pid_t, uid_t, gid_t, getuid(),
36
 
                                   getgid() */
 
31
#include <stdio.h>              /* perror, fileno(), fprintf(),
 
32
                                   stderr, STDOUT_FILENO */
 
33
#include <sys/types.h>          /* DIR, opendir(), stat(), struct
 
34
                                   stat, waitpid(), WIFEXITED(),
 
35
                                   WEXITSTATUS(), wait(), pid_t,
 
36
                                   uid_t, gid_t, getuid(), getgid(),
 
37
                                   dirfd() */
37
38
#include <sys/select.h>         /* fd_set, select(), FD_ZERO(),
38
39
                                   FD_SET(), FD_ISSET(), FD_CLR */
39
40
#include <sys/wait.h>           /* wait(), waitpid(), WIFEXITED(),
40
 
                                   WEXITSTATUS(), WTERMSIG() */
41
 
#include <sys/stat.h>           /* struct stat, fstat(), S_ISREG() */
 
41
                                   WEXITSTATUS() */
 
42
#include <sys/stat.h>           /* struct stat, stat(), S_ISREG() */
42
43
#include <iso646.h>             /* and, or, not */
43
 
#include <dirent.h>             /* struct dirent, scandirat() */
44
 
#include <unistd.h>             /* fcntl(), F_GETFD, F_SETFD,
45
 
                                   FD_CLOEXEC, write(), STDOUT_FILENO,
46
 
                                   struct stat, fstat(), close(),
47
 
                                   setgid(), setuid(), S_ISREG(),
48
 
                                   faccessat() pipe2(), fork(),
49
 
                                   _exit(), dup2(), fexecve(), read()
50
 
                                */
 
44
#include <dirent.h>             /* DIR, struct dirent, opendir(),
 
45
                                   readdir(), closedir(), dirfd() */
 
46
#include <unistd.h>             /* struct stat, stat(), S_ISREG(),
 
47
                                   fcntl(), setuid(), setgid(),
 
48
                                   F_GETFD, F_SETFD, FD_CLOEXEC,
 
49
                                   access(), pipe(), fork(), close()
 
50
                                   dup2(), STDOUT_FILENO, _exit(),
 
51
                                   execv(), write(), read(),
 
52
                                   close() */
51
53
#include <fcntl.h>              /* fcntl(), F_GETFD, F_SETFD,
52
 
                                   FD_CLOEXEC, openat(), scandirat(),
53
 
                                   pipe2() */
54
 
#include <string.h>             /* strsep, strlen(), strsignal(),
55
 
                                   strcmp(), strncmp() */
 
54
                                   FD_CLOEXEC */
 
55
#include <string.h>             /* strsep, strlen(), asprintf() */
56
56
#include <errno.h>              /* errno */
57
57
#include <argp.h>               /* struct argp_option, struct
58
58
                                   argp_state, struct argp,
62
62
#include <signal.h>             /* struct sigaction, sigemptyset(),
63
63
                                   sigaddset(), sigaction(),
64
64
                                   sigprocmask(), SIG_BLOCK, SIGCHLD,
65
 
                                   SIG_UNBLOCK, kill(), sig_atomic_t
66
 
                                */
 
65
                                   SIG_UNBLOCK, kill() */
67
66
#include <errno.h>              /* errno, EBADF */
68
 
#include <inttypes.h>           /* intmax_t, PRIdMAX, strtoimax() */
69
 
#include <sysexits.h>           /* EX_OSERR, EX_USAGE, EX_IOERR,
70
 
                                   EX_CONFIG, EX_UNAVAILABLE, EX_OK */
71
 
#include <errno.h>              /* errno */
72
 
#include <error.h>              /* error() */
73
 
#include <fnmatch.h>            /* fnmatch() */
74
67
 
75
68
#define BUFFER_SIZE 256
76
69
 
77
70
#define PDIR "/lib/mandos/plugins.d"
78
 
#define PHDIR "/lib/mandos/plugin-helpers"
79
71
#define AFILE "/conf/conf.d/mandos/plugin-runner.conf"
80
72
 
81
73
const char *argp_program_version = "plugin-runner " VERSION;
82
 
const char *argp_program_bug_address = "<mandos@recompile.se>";
 
74
const char *argp_program_bug_address = "<mandos@fukt.bsnet.se>";
83
75
 
84
76
typedef struct plugin{
85
77
  char *name;                   /* can be NULL or any plugin name */
88
80
  char **environ;
89
81
  int envc;
90
82
  bool disabled;
91
 
  
 
83
 
92
84
  /* Variables used for running processes*/
93
85
  pid_t pid;
94
86
  int fd;
96
88
  size_t buffer_size;
97
89
  size_t buffer_length;
98
90
  bool eof;
99
 
  volatile sig_atomic_t completed;
100
 
  int status;
 
91
  volatile bool completed;
 
92
  volatile int status;
101
93
  struct plugin *next;
102
94
} plugin;
103
95
 
105
97
 
106
98
/* Gets an existing plugin based on name,
107
99
   or if none is found, creates a new one */
108
 
__attribute__((warn_unused_result))
109
100
static plugin *getplugin(char *name){
110
 
  /* Check for existing plugin with that name */
111
 
  for(plugin *p = plugin_list; p != NULL; p = p->next){
112
 
    if((p->name == name)
113
 
       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))){
114
105
      return p;
115
106
    }
116
107
  }
117
108
  /* Create a new plugin */
118
 
  plugin *new_plugin = NULL;
119
 
  do {
120
 
    new_plugin = malloc(sizeof(plugin));
121
 
  } while(new_plugin == NULL and errno == EINTR);
122
 
  if(new_plugin == NULL){
 
109
  plugin *new_plugin = malloc(sizeof(plugin));
 
110
  if (new_plugin == NULL){
123
111
    return NULL;
124
112
  }
125
113
  char *copy_name = NULL;
126
114
  if(name != NULL){
127
 
    do {
128
 
      copy_name = strdup(name);
129
 
    } while(copy_name == NULL and errno == EINTR);
 
115
    copy_name = strdup(name);
130
116
    if(copy_name == NULL){
131
 
      int e = errno;
132
 
      free(new_plugin);
133
 
      errno = e;
134
117
      return NULL;
135
118
    }
136
119
  }
137
120
  
138
 
  *new_plugin = (plugin){ .name = copy_name,
139
 
                          .argc = 1,
140
 
                          .disabled = false,
141
 
                          .next = plugin_list };
 
121
  *new_plugin = (plugin) { .name = copy_name,
 
122
                           .argc = 1,
 
123
                           .disabled = false,
 
124
                           .next = plugin_list };
142
125
  
143
 
  do {
144
 
    new_plugin->argv = malloc(sizeof(char *) * 2);
145
 
  } while(new_plugin->argv == NULL and errno == EINTR);
146
 
  if(new_plugin->argv == NULL){
147
 
    int e = errno;
 
126
  new_plugin->argv = malloc(sizeof(char *) * 2);
 
127
  if (new_plugin->argv == NULL){
148
128
    free(copy_name);
149
129
    free(new_plugin);
150
 
    errno = e;
151
130
    return NULL;
152
131
  }
153
132
  new_plugin->argv[0] = copy_name;
154
133
  new_plugin->argv[1] = NULL;
155
134
  
156
 
  do {
157
 
    new_plugin->environ = malloc(sizeof(char *));
158
 
  } while(new_plugin->environ == NULL and errno == EINTR);
 
135
  new_plugin->environ = malloc(sizeof(char *));
159
136
  if(new_plugin->environ == NULL){
160
 
    int e = errno;
161
137
    free(copy_name);
162
138
    free(new_plugin->argv);
163
139
    free(new_plugin);
164
 
    errno = e;
165
140
    return NULL;
166
141
  }
167
142
  new_plugin->environ[0] = NULL;
172
147
}
173
148
 
174
149
/* Helper function for add_argument and add_environment */
175
 
__attribute__((nonnull, warn_unused_result))
176
150
static bool add_to_char_array(const char *new, char ***array,
177
151
                              int *len){
178
152
  /* Resize the pointed-to array to hold one more pointer */
179
 
  char **new_array = NULL;
180
 
  do {
181
 
    new_array = realloc(*array, sizeof(char *)
182
 
                        * (size_t) ((*len) + 2));
183
 
  } while(new_array == NULL and errno == EINTR);
 
153
  *array = realloc(*array, sizeof(char *)
 
154
                   * (size_t) ((*len) + 2));
184
155
  /* Malloc check */
185
 
  if(new_array == NULL){
 
156
  if(*array == NULL){
186
157
    return false;
187
158
  }
188
 
  *array = new_array;
189
159
  /* Make a copy of the new string */
190
 
  char *copy;
191
 
  do {
192
 
    copy = strdup(new);
193
 
  } while(copy == NULL and errno == EINTR);
 
160
  char *copy = strdup(new);
194
161
  if(copy == NULL){
195
162
    return false;
196
163
  }
203
170
}
204
171
 
205
172
/* Add to a plugin's argument vector */
206
 
__attribute__((nonnull(2), warn_unused_result))
207
173
static bool add_argument(plugin *p, const char *arg){
208
174
  if(p == NULL){
209
175
    return false;
212
178
}
213
179
 
214
180
/* Add to a plugin's environment */
215
 
__attribute__((nonnull(2), warn_unused_result))
216
181
static bool add_environment(plugin *p, const char *def, bool replace){
217
182
  if(p == NULL){
218
183
    return false;
220
185
  /* namelen = length of name of environment variable */
221
186
  size_t namelen = (size_t)(strchrnul(def, '=') - def);
222
187
  /* Search for this environment variable */
223
 
  for(char **envdef = p->environ; *envdef != NULL; envdef++){
224
 
    if(strncmp(*envdef, def, namelen + 1) == 0){
 
188
  for(char **e = p->environ; *e != NULL; e++){
 
189
    if(strncmp(*e, def, namelen + 1) == 0){
225
190
      /* It already exists */
226
191
      if(replace){
227
 
        char *new_envdef;
228
 
        do {
229
 
          new_envdef = realloc(*envdef, strlen(def) + 1);
230
 
        } while(new_envdef == NULL and errno == EINTR);
231
 
        if(new_envdef == NULL){
 
192
        char *new = realloc(*e, strlen(def) + 1);
 
193
        if(new == NULL){
232
194
          return false;
233
195
        }
234
 
        *envdef = new_envdef;
235
 
        strcpy(*envdef, def);
 
196
        *e = new;
 
197
        strcpy(*e, def);
236
198
      }
237
199
      return true;
238
200
    }
240
202
  return add_to_char_array(def, &(p->environ), &(p->envc));
241
203
}
242
204
 
243
 
#ifndef O_CLOEXEC
244
205
/*
245
206
 * Based on the example in the GNU LibC manual chapter 13.13 "File
246
207
 * Descriptor Flags".
247
 
 | [[info:libc:Descriptor%20Flags][File Descriptor Flags]] |
 
208
 * *Note File Descriptor Flags:(libc)Descriptor Flags.
248
209
 */
249
 
__attribute__((warn_unused_result))
250
210
static int set_cloexec_flag(int fd){
251
 
  int ret = (int)TEMP_FAILURE_RETRY(fcntl(fd, F_GETFD, 0));
 
211
  int ret = fcntl(fd, F_GETFD, 0);
252
212
  /* If reading the flags failed, return error indication now. */
253
213
  if(ret < 0){
254
214
    return ret;
255
215
  }
256
216
  /* Store modified flag word in the descriptor. */
257
 
  return (int)TEMP_FAILURE_RETRY(fcntl(fd, F_SETFD,
258
 
                                       ret | FD_CLOEXEC));
 
217
  return fcntl(fd, F_SETFD, ret | FD_CLOEXEC);
259
218
}
260
 
#endif  /* not O_CLOEXEC */
261
219
 
262
220
 
263
221
/* Mark processes as completed when they exit, and save their exit
264
222
   status. */
265
223
static void handle_sigchld(__attribute__((unused)) int sig){
266
 
  int old_errno = errno;
267
224
  while(true){
268
225
    plugin *proc = plugin_list;
269
226
    int status;
273
230
      break;
274
231
    }
275
232
    if(pid == -1){
276
 
      if(errno == ECHILD){
277
 
        /* No child processes */
278
 
        break;
 
233
      if (errno != ECHILD){
 
234
        perror("waitpid");
279
235
      }
280
 
      error(0, errno, "waitpid");
 
236
      /* No child processes */
 
237
      break;
281
238
    }
282
239
    
283
240
    /* A child exited, find it in process_list */
289
246
      continue;
290
247
    }
291
248
    proc->status = status;
292
 
    proc->completed = 1;
 
249
    proc->completed = true;
293
250
  }
294
 
  errno = old_errno;
295
251
}
296
252
 
297
253
/* Prints out a password to stdout */
298
 
__attribute__((nonnull, warn_unused_result))
299
254
static bool print_out_password(const char *buffer, size_t length){
300
255
  ssize_t ret;
301
256
  for(size_t written = 0; written < length; written += (size_t)ret){
309
264
}
310
265
 
311
266
/* Removes and free a plugin from the plugin list */
312
 
__attribute__((nonnull))
313
267
static void free_plugin(plugin *plugin_node){
314
268
  
315
269
  for(char **arg = plugin_node->argv; *arg != NULL; arg++){
321
275
  }
322
276
  free(plugin_node->environ);
323
277
  free(plugin_node->buffer);
324
 
  
 
278
 
325
279
  /* Removes the plugin from the singly-linked list */
326
280
  if(plugin_node == plugin_list){
327
281
    /* First one - simple */
347
301
 
348
302
int main(int argc, char *argv[]){
349
303
  char *plugindir = NULL;
350
 
  char *pluginhelperdir = NULL;
351
304
  char *argfile = NULL;
352
305
  FILE *conffp;
353
 
  struct dirent **direntries = NULL;
 
306
  size_t d_name_len;
 
307
  DIR *dir = NULL;
 
308
  struct dirent *dirst;
354
309
  struct stat st;
355
310
  fd_set rfds_all;
356
311
  int ret, maxfd = 0;
357
 
  ssize_t sret;
358
312
  uid_t uid = 65534;
359
313
  gid_t gid = 65534;
360
314
  bool debug = false;
364
318
                                      .sa_flags = SA_NOCLDSTOP };
365
319
  char **custom_argv = NULL;
366
320
  int custom_argc = 0;
367
 
  int dir_fd = -1;
368
321
  
369
322
  /* Establish a signal handler */
370
323
  sigemptyset(&sigchld_action.sa_mask);
371
324
  ret = sigaddset(&sigchld_action.sa_mask, SIGCHLD);
372
325
  if(ret == -1){
373
 
    error(0, errno, "sigaddset");
374
 
    exitstatus = EX_OSERR;
 
326
    perror("sigaddset");
 
327
    exitstatus = EXIT_FAILURE;
375
328
    goto fallback;
376
329
  }
377
330
  ret = sigaction(SIGCHLD, &sigchld_action, &old_sigchld_action);
378
331
  if(ret == -1){
379
 
    error(0, errno, "sigaction");
380
 
    exitstatus = EX_OSERR;
 
332
    perror("sigaction");
 
333
    exitstatus = EXIT_FAILURE;
381
334
    goto fallback;
382
335
  }
383
336
  
415
368
      .doc = "Group ID the plugins will run as", .group = 3 },
416
369
    { .name = "debug", .key = 132,
417
370
      .doc = "Debug mode", .group = 4 },
418
 
    { .name = "plugin-helper-dir", .key = 133,
419
 
      .arg = "DIRECTORY",
420
 
      .doc = "Specify a different plugin helper directory",
421
 
      .group = 2 },
422
 
    /*
423
 
     * These reproduce what we would get without ARGP_NO_HELP
424
 
     */
425
 
    { .name = "help", .key = '?',
426
 
      .doc = "Give this help list", .group = -1 },
427
 
    { .name = "usage", .key = -3,
428
 
      .doc = "Give a short usage message", .group = -1 },
429
 
    { .name = "version", .key = 'V',
430
 
      .doc = "Print program version", .group = -1 },
431
371
    { .name = NULL }
432
372
  };
433
373
  
434
 
  __attribute__((nonnull(3)))
435
 
  error_t parse_opt(int key, char *arg, struct argp_state *state){
436
 
    errno = 0;
437
 
    switch(key){
438
 
      char *tmp;
439
 
      intmax_t tmp_id;
 
374
  error_t parse_opt (int key, char *arg, __attribute__((unused))
 
375
                     struct argp_state *state) {
 
376
    switch (key) {
440
377
    case 'g':                   /* --global-options */
441
 
      {
442
 
        char *plugin_option;
443
 
        while((plugin_option = strsep(&arg, ",")) != NULL){
444
 
          if(not add_argument(getplugin(NULL), plugin_option)){
445
 
            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;
446
387
          }
447
388
        }
448
 
        errno = 0;
449
389
      }
450
390
      break;
451
391
    case 'G':                   /* --global-env */
452
 
      if(add_environment(getplugin(NULL), arg, true)){
453
 
        errno = 0;
 
392
      if(arg == NULL){
 
393
        break;
 
394
      }
 
395
      if(not add_environment(getplugin(NULL), arg, true)){
 
396
        perror("add_environment");
454
397
      }
455
398
      break;
456
399
    case 'o':                   /* --options-for */
457
 
      {
458
 
        char *option_list = strchr(arg, ':');
459
 
        if(option_list == NULL){
460
 
          argp_error(state, "No colon in \"%s\"", arg);
461
 
          errno = EINVAL;
462
 
          break;
463
 
        }
464
 
        *option_list = '\0';
465
 
        option_list++;
466
 
        if(arg[0] == '\0'){
467
 
          argp_error(state, "Empty plugin name");
468
 
          errno = EINVAL;
469
 
          break;
470
 
        }
471
 
        char *option;
472
 
        while((option = strsep(&option_list, ",")) != NULL){
473
 
          if(not add_argument(getplugin(arg), option)){
474
 
            break;
475
 
          }
476
 
        }
477
 
        errno = 0;
 
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;
 
417
          }
 
418
        }
478
419
      }
479
420
      break;
480
421
    case 'E':                   /* --env-for */
 
422
      if(arg == NULL){
 
423
        break;
 
424
      }
481
425
      {
482
426
        char *envdef = strchr(arg, ':');
483
427
        if(envdef == NULL){
484
 
          argp_error(state, "No colon in \"%s\"", arg);
485
 
          errno = EINVAL;
486
428
          break;
487
429
        }
488
430
        *envdef = '\0';
489
 
        envdef++;
490
 
        if(arg[0] == '\0'){
491
 
          argp_error(state, "Empty plugin name");
492
 
          errno = EINVAL;
493
 
          break;
494
 
        }
495
 
        if(add_environment(getplugin(arg), envdef, true)){
496
 
          errno = 0;
 
431
        if(not add_environment(getplugin(arg), envdef+1, true)){
 
432
          perror("add_environment");
497
433
        }
498
434
      }
499
435
      break;
500
436
    case 'd':                   /* --disable */
501
 
      {
 
437
      if (arg != NULL){
502
438
        plugin *p = getplugin(arg);
503
 
        if(p != NULL){
504
 
          p->disabled = true;
505
 
          errno = 0;
 
439
        if(p == NULL){
 
440
          return ARGP_ERR_UNKNOWN;
506
441
        }
 
442
        p->disabled = true;
507
443
      }
508
444
      break;
509
445
    case 'e':                   /* --enable */
510
 
      {
 
446
      if (arg != NULL){
511
447
        plugin *p = getplugin(arg);
512
 
        if(p != NULL){
513
 
          p->disabled = false;
514
 
          errno = 0;
 
448
        if(p == NULL){
 
449
          return ARGP_ERR_UNKNOWN;
515
450
        }
 
451
        p->disabled = false;
516
452
      }
517
453
      break;
518
454
    case 128:                   /* --plugin-dir */
519
455
      free(plugindir);
520
456
      plugindir = strdup(arg);
521
 
      if(plugindir != NULL){
522
 
        errno = 0;
523
 
      }
 
457
      if(plugindir == NULL){
 
458
        perror("strdup");
 
459
      }      
524
460
      break;
525
461
    case 129:                   /* --config-file */
526
462
      /* This is already done by parse_opt_config_file() */
527
463
      break;
528
464
    case 130:                   /* --userid */
529
 
      tmp_id = strtoimax(arg, &tmp, 10);
530
 
      if(errno != 0 or tmp == arg or *tmp != '\0'
531
 
         or tmp_id != (uid_t)tmp_id){
532
 
        argp_error(state, "Bad user ID number: \"%s\", using %"
533
 
                   PRIdMAX, arg, (intmax_t)uid);
534
 
        break;
535
 
      }
536
 
      uid = (uid_t)tmp_id;
537
 
      errno = 0;
 
465
      uid = (uid_t)strtol(arg, NULL, 10);
538
466
      break;
539
467
    case 131:                   /* --groupid */
540
 
      tmp_id = strtoimax(arg, &tmp, 10);
541
 
      if(errno != 0 or tmp == arg or *tmp != '\0'
542
 
         or tmp_id != (gid_t)tmp_id){
543
 
        argp_error(state, "Bad group ID number: \"%s\", using %"
544
 
                   PRIdMAX, arg, (intmax_t)gid);
545
 
        break;
546
 
      }
547
 
      gid = (gid_t)tmp_id;
548
 
      errno = 0;
 
468
      gid = (gid_t)strtol(arg, NULL, 10);
549
469
      break;
550
470
    case 132:                   /* --debug */
551
471
      debug = true;
552
472
      break;
553
 
    case 133:                   /* --plugin-helper-dir */
554
 
      free(pluginhelperdir);
555
 
      pluginhelperdir = strdup(arg);
556
 
      if(pluginhelperdir != NULL){
557
 
        errno = 0;
558
 
      }
559
 
      break;
560
 
      /*
561
 
       * These reproduce what we would get without ARGP_NO_HELP
562
 
       */
563
 
    case '?':                   /* --help */
564
 
      state->flags &= ~(unsigned int)ARGP_NO_EXIT; /* force exit */
565
 
      argp_state_help(state, state->out_stream, ARGP_HELP_STD_HELP);
566
 
    case -3:                    /* --usage */
567
 
      state->flags &= ~(unsigned int)ARGP_NO_EXIT; /* force exit */
568
 
      argp_state_help(state, state->out_stream,
569
 
                      ARGP_HELP_USAGE | ARGP_HELP_EXIT_OK);
570
 
    case 'V':                   /* --version */
571
 
      fprintf(state->out_stream, "%s\n", argp_program_version);
572
 
      exit(EXIT_SUCCESS);
573
 
      break;
574
 
/*
575
 
 * When adding more options before this line, remember to also add a
576
 
 * "case" to the "parse_opt_config_file" function below.
577
 
 */
578
473
    case ARGP_KEY_ARG:
579
474
      /* Cryptsetup always passes an argument, which is an empty
580
475
         string if "none" was specified in /etc/crypttab.  So if
581
476
         argument was empty, we ignore it silently. */
582
 
      if(arg[0] == '\0'){
583
 
        break;
 
477
      if(arg[0] != '\0'){
 
478
        fprintf(stderr, "Ignoring unknown argument \"%s\"\n", arg);
584
479
      }
 
480
      break;
 
481
    case ARGP_KEY_END:
 
482
      break;
585
483
    default:
586
484
      return ARGP_ERR_UNKNOWN;
587
485
    }
588
 
    return errno;               /* Set to 0 at start */
 
486
    return 0;
589
487
  }
590
488
  
591
489
  /* This option parser is the same as parse_opt() above, except it
592
490
     ignores everything but the --config-file option. */
593
 
  error_t parse_opt_config_file(int key, char *arg,
594
 
                                __attribute__((unused))
595
 
                                struct argp_state *state){
596
 
    errno = 0;
597
 
    switch(key){
 
491
  error_t parse_opt_config_file (int key, char *arg,
 
492
                                 __attribute__((unused))
 
493
                                 struct argp_state *state) {
 
494
    switch (key) {
598
495
    case 'g':                   /* --global-options */
599
496
    case 'G':                   /* --global-env */
600
497
    case 'o':                   /* --options-for */
606
503
    case 129:                   /* --config-file */
607
504
      free(argfile);
608
505
      argfile = strdup(arg);
609
 
      if(argfile != NULL){
610
 
        errno = 0;
 
506
      if(argfile == NULL){
 
507
        perror("strdup");
611
508
      }
612
 
      break;
 
509
      break;      
613
510
    case 130:                   /* --userid */
614
511
    case 131:                   /* --groupid */
615
512
    case 132:                   /* --debug */
616
 
    case 133:                   /* --plugin-helper-dir */
617
 
    case '?':                   /* --help */
618
 
    case -3:                    /* --usage */
619
 
    case 'V':                   /* --version */
620
513
    case ARGP_KEY_ARG:
 
514
    case ARGP_KEY_END:
621
515
      break;
622
516
    default:
623
517
      return ARGP_ERR_UNKNOWN;
624
518
    }
625
 
    return errno;
 
519
    return 0;
626
520
  }
627
521
  
628
522
  struct argp argp = { .options = options,
630
524
                       .args_doc = "",
631
525
                       .doc = "Mandos plugin runner -- Run plugins" };
632
526
  
633
 
  /* 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
634
528
     config file location, if any. */
635
 
  ret = argp_parse(&argp, argc, argv,
636
 
                   ARGP_IN_ORDER | ARGP_NO_EXIT | ARGP_NO_HELP,
637
 
                   NULL, NULL);
638
 
  switch(ret){
639
 
  case 0:
640
 
    break;
641
 
  case ENOMEM:
642
 
  default:
643
 
    errno = ret;
644
 
    error(0, errno, "argp_parse");
645
 
    exitstatus = EX_OSERR;
646
 
    goto fallback;
647
 
  case EINVAL:
648
 
    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;
649
533
    goto fallback;
650
534
  }
651
535
  
653
537
  argp.parser = parse_opt;
654
538
  
655
539
  /* Open the configfile if available */
656
 
  if(argfile == NULL){
 
540
  if (argfile == NULL){
657
541
    conffp = fopen(AFILE, "r");
658
542
  } else {
659
543
    conffp = fopen(argfile, "r");
660
 
  }
 
544
  }  
661
545
  if(conffp != NULL){
662
546
    char *org_line = NULL;
663
547
    char *p, *arg, *new_arg, *line;
664
548
    size_t size = 0;
 
549
    ssize_t sret;
665
550
    const char whitespace_delims[] = " \r\t\f\v\n";
666
551
    const char comment_delim[] = "#";
667
 
    
 
552
 
668
553
    custom_argc = 1;
669
554
    custom_argv = malloc(sizeof(char*) * 2);
670
555
    if(custom_argv == NULL){
671
 
      error(0, errno, "malloc");
672
 
      exitstatus = EX_OSERR;
 
556
      perror("malloc");
 
557
      exitstatus = EXIT_FAILURE;
673
558
      goto fallback;
674
559
    }
675
560
    custom_argv[0] = argv[0];
676
561
    custom_argv[1] = NULL;
677
 
    
 
562
 
678
563
    /* for each line in the config file, strip whitespace and ignore
679
564
       commented text */
680
565
    while(true){
682
567
      if(sret == -1){
683
568
        break;
684
569
      }
685
 
      
 
570
 
686
571
      line = org_line;
687
572
      arg = strsep(&line, comment_delim);
688
573
      while((p = strsep(&arg, whitespace_delims)) != NULL){
691
576
        }
692
577
        new_arg = strdup(p);
693
578
        if(new_arg == NULL){
694
 
          error(0, errno, "strdup");
695
 
          exitstatus = EX_OSERR;
 
579
          perror("strdup");
 
580
          exitstatus = EXIT_FAILURE;
696
581
          free(org_line);
697
582
          goto fallback;
698
583
        }
699
584
        
700
585
        custom_argc += 1;
701
 
        {
702
 
          char **new_argv = realloc(custom_argv, sizeof(char *)
703
 
                                    * ((size_t)custom_argc + 1));
704
 
          if(new_argv == NULL){
705
 
            error(0, errno, "realloc");
706
 
            exitstatus = EX_OSERR;
707
 
            free(new_arg);
708
 
            free(org_line);
709
 
            goto fallback;
710
 
          } else {
711
 
            custom_argv = new_argv;
712
 
          }
 
586
        custom_argv = realloc(custom_argv, sizeof(char *)
 
587
                              * ((unsigned int) custom_argc + 1));
 
588
        if(custom_argv == NULL){
 
589
          perror("realloc");
 
590
          exitstatus = EXIT_FAILURE;
 
591
          free(org_line);
 
592
          goto fallback;
713
593
        }
714
594
        custom_argv[custom_argc-1] = new_arg;
715
 
        custom_argv[custom_argc] = NULL;
 
595
        custom_argv[custom_argc] = NULL;        
716
596
      }
717
597
    }
718
 
    do {
719
 
      ret = fclose(conffp);
720
 
    } while(ret == EOF and errno == EINTR);
721
 
    if(ret == EOF){
722
 
      error(0, errno, "fclose");
723
 
      exitstatus = EX_IOERR;
724
 
      goto fallback;
725
 
    }
726
598
    free(org_line);
727
599
  } else {
728
600
    /* Check for harmful errors and go to fallback. Other errors might
729
601
       not affect opening plugins */
730
 
    if(errno == EMFILE or errno == ENFILE or errno == ENOMEM){
731
 
      error(0, errno, "fopen");
732
 
      exitstatus = EX_OSERR;
 
602
    if (errno == EMFILE or errno == ENFILE or errno == ENOMEM){
 
603
      perror("fopen");
 
604
      exitstatus = EXIT_FAILURE;
733
605
      goto fallback;
734
606
    }
735
607
  }
736
 
  /* If there were any arguments from the configuration file, pass
737
 
     them to parser as command line arguments */
 
608
  /* If there was any arguments from configuration file,
 
609
     pass them to parser as command arguments */
738
610
  if(custom_argv != NULL){
739
 
    ret = argp_parse(&argp, custom_argc, custom_argv,
740
 
                     ARGP_IN_ORDER | ARGP_NO_EXIT | ARGP_NO_HELP,
741
 
                     NULL, NULL);
742
 
    switch(ret){
743
 
    case 0:
744
 
      break;
745
 
    case ENOMEM:
746
 
    default:
747
 
      errno = ret;
748
 
      error(0, errno, "argp_parse");
749
 
      exitstatus = EX_OSERR;
750
 
      goto fallback;
751
 
    case EINVAL:
752
 
      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;
753
616
      goto fallback;
754
617
    }
755
618
  }
756
619
  
757
620
  /* Parse actual command line arguments, to let them override the
758
621
     config file */
759
 
  ret = argp_parse(&argp, argc, argv,
760
 
                   ARGP_IN_ORDER | ARGP_NO_EXIT | ARGP_NO_HELP,
761
 
                   NULL, NULL);
762
 
  switch(ret){
763
 
  case 0:
764
 
    break;
765
 
  case ENOMEM:
766
 
  default:
767
 
    errno = ret;
768
 
    error(0, errno, "argp_parse");
769
 
    exitstatus = EX_OSERR;
770
 
    goto fallback;
771
 
  case EINVAL:
772
 
    exitstatus = EX_USAGE;
773
 
    goto fallback;
774
 
  }
775
 
  
776
 
  {
777
 
    char *pluginhelperenv;
778
 
    bool bret = true;
779
 
    ret = asprintf(&pluginhelperenv, "MANDOSPLUGINHELPERDIR=%s",
780
 
                   pluginhelperdir != NULL ? pluginhelperdir : PHDIR);
781
 
    if(ret != -1){
782
 
      bret = add_environment(getplugin(NULL), pluginhelperenv, true);
783
 
    }
784
 
    if(ret == -1 or not bret){
785
 
      error(0, errno, "Failed to set MANDOSPLUGINHELPERDIR"
786
 
            " environment variable to \"%s\" for all plugins\n",
787
 
            pluginhelperdir != NULL ? pluginhelperdir : PHDIR);
788
 
    }
789
 
    if(ret != -1){
790
 
      free(pluginhelperenv);
791
 
    }
 
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;
 
626
    goto fallback;
792
627
  }
793
628
  
794
629
  if(debug){
798
633
      for(char **a = p->argv; *a != NULL; a++){
799
634
        fprintf(stderr, "\tArg: %s\n", *a);
800
635
      }
801
 
      fprintf(stderr, "...and %d environment variables\n", p->envc);
 
636
      fprintf(stderr, "...and %u environment variables\n", p->envc);
802
637
      for(char **a = p->environ; *a != NULL; a++){
803
638
        fprintf(stderr, "\t%s\n", *a);
804
639
      }
805
640
    }
806
641
  }
807
642
  
808
 
  if(getuid() == 0){
809
 
    /* Work around Debian bug #633582:
810
 
       <https://bugs.debian.org/633582> */
811
 
    int plugindir_fd = open(/* plugindir or */ PDIR, O_RDONLY);
812
 
    if(plugindir_fd == -1){
813
 
      if(errno != ENOENT){
814
 
        error(0, errno, "open(\"" PDIR "\")");
815
 
      }
816
 
    } else {
817
 
      ret = (int)TEMP_FAILURE_RETRY(fstat(plugindir_fd, &st));
818
 
      if(ret == -1){
819
 
        error(0, errno, "fstat");
820
 
      } else {
821
 
        if(S_ISDIR(st.st_mode) and st.st_uid == 0 and st.st_gid == 0){
822
 
          ret = fchown(plugindir_fd, uid, gid);
823
 
          if(ret == -1){
824
 
            error(0, errno, "fchown");
825
 
          }
826
 
        }
827
 
      }
828
 
      close(plugindir_fd);
829
 
    }
830
 
  }
831
 
  
832
 
  /* Lower permissions */
833
 
  ret = setgid(gid);
834
 
  if(ret == -1){
835
 
    error(0, errno, "setgid");
836
 
  }
 
643
  /* Strip permissions down to nobody */
837
644
  ret = setuid(uid);
838
 
  if(ret == -1){
839
 
    error(0, errno, "setuid");
840
 
  }
841
 
  
842
 
  /* Open plugin directory with close_on_exec flag */
 
645
  if (ret == -1){
 
646
    perror("setuid");
 
647
  }  
 
648
  setgid(gid);
 
649
  if (ret == -1){
 
650
    perror("setgid");
 
651
  }
 
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 */
843
666
  {
844
 
    dir_fd = open(plugindir != NULL ? plugindir : PDIR, O_RDONLY |
845
 
#ifdef O_CLOEXEC
846
 
                  O_CLOEXEC
847
 
#else  /* not O_CLOEXEC */
848
 
                  0
849
 
#endif  /* not O_CLOEXEC */
850
 
                  );
851
 
    if(dir_fd == -1){
852
 
      error(0, errno, "Could not open plugin dir");
853
 
      exitstatus = EX_UNAVAILABLE;
854
 
      goto fallback;
855
 
    }
856
 
    
857
 
#ifndef O_CLOEXEC
858
 
  /* Set the FD_CLOEXEC flag on the directory */
859
 
    ret = set_cloexec_flag(dir_fd);
860
 
    if(ret < 0){
861
 
      error(0, errno, "set_cloexec_flag");
862
 
      exitstatus = EX_OSERR;
863
 
      goto fallback;
864
 
    }
865
 
#endif  /* O_CLOEXEC */
866
 
  }
867
 
  
868
 
  int good_name(const struct dirent * const dirent){
869
 
    const char * const patterns[] = { ".*", "#*#", "*~", "*.dpkg-new",
870
 
                                      "*.dpkg-old", "*.dpkg-bak",
871
 
                                      "*.dpkg-divert", NULL };
872
 
#ifdef __GNUC__
873
 
#pragma GCC diagnostic push
874
 
#pragma GCC diagnostic ignored "-Wcast-qual"
875
 
#endif
876
 
    for(const char **pat = (const char **)patterns;
877
 
        *pat != NULL; pat++){
878
 
#ifdef __GNUC__
879
 
#pragma GCC diagnostic pop
880
 
#endif
881
 
      if(fnmatch(*pat, dirent->d_name, FNM_FILE_NAME | FNM_PERIOD)
882
 
         != FNM_NOMATCH){
883
 
        if(debug){
884
 
            fprintf(stderr, "Ignoring plugin dir entry \"%s\""
885
 
                    " matching pattern %s\n", dirent->d_name, *pat);
886
 
        }
887
 
        return 0;
 
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;
888
674
      }
889
675
    }
890
 
    return 1;
891
 
  }
892
 
  
893
 
  int numplugins = scandirat(dir_fd, ".", &direntries, good_name,
894
 
                             alphasort);
895
 
  if(numplugins == -1){
896
 
    error(0, errno, "Could not scan plugin dir");
897
 
    direntries = NULL;
898
 
    exitstatus = EX_OSERR;
899
 
    goto fallback;
900
676
  }
901
677
  
902
678
  FD_ZERO(&rfds_all);
903
679
  
904
680
  /* Read and execute any executable in the plugin directory*/
905
 
  for(int i = 0; i < numplugins; i++){
906
 
    
907
 
    int plugin_fd = openat(dir_fd, direntries[i]->d_name, O_RDONLY);
908
 
    if(plugin_fd == -1){
909
 
      error(0, errno, "Could not open plugin");
910
 
      free(direntries[i]);
911
 
      continue;
912
 
    }
913
 
    ret = (int)TEMP_FAILURE_RETRY(fstat(plugin_fd, &st));
914
 
    if(ret == -1){
915
 
      error(0, errno, "stat");
916
 
      close(plugin_fd);
917
 
      free(direntries[i]);
918
 
      continue;
919
 
    }
920
 
    
 
681
  while(true){
 
682
    dirst = readdir(dir);
 
683
    
 
684
    /* All directory entries have been processed */
 
685
    if(dirst == NULL){
 
686
      if (errno == EBADF){
 
687
        perror("readdir");
 
688
        exitstatus = EXIT_FAILURE;
 
689
        goto fallback;
 
690
      }
 
691
      break;
 
692
    }
 
693
    
 
694
    d_name_len = strlen(dirst->d_name);
 
695
    
 
696
    /* Ignore dotfiles, backup files and other junk */
 
697
    {
 
698
      bool bad_name = false;
 
699
      
 
700
      const char const *bad_prefixes[] = { ".", "#", NULL };
 
701
      
 
702
      const char const *bad_suffixes[] = { "~", "#", ".dpkg-new",
 
703
                                           ".dpkg-old",
 
704
                                           ".dpkg-bak",
 
705
                                           ".dpkg-divert", NULL };
 
706
      for(const char **pre = bad_prefixes; *pre != NULL; pre++){
 
707
        size_t pre_len = strlen(*pre);
 
708
        if((d_name_len >= pre_len)
 
709
           and strncmp((dirst->d_name), *pre, pre_len) == 0){
 
710
          if(debug){
 
711
            fprintf(stderr, "Ignoring plugin dir entry \"%s\""
 
712
                    " with bad prefix %s\n", dirst->d_name, *pre);
 
713
          }
 
714
          bad_name = true;
 
715
          break;
 
716
        }
 
717
      }
 
718
      if(bad_name){
 
719
        continue;
 
720
      }
 
721
      for(const char **suf = bad_suffixes; *suf != NULL; suf++){
 
722
        size_t suf_len = strlen(*suf);
 
723
        if((d_name_len >= suf_len)
 
724
           and (strcmp((dirst->d_name)+d_name_len-suf_len, *suf)
 
725
                == 0)){
 
726
          if(debug){
 
727
            fprintf(stderr, "Ignoring plugin dir entry \"%s\""
 
728
                    " with bad suffix %s\n", dirst->d_name, *suf);
 
729
          }
 
730
          bad_name = true;
 
731
          break;
 
732
        }
 
733
      }
 
734
      
 
735
      if(bad_name){
 
736
        continue;
 
737
      }
 
738
    }
 
739
 
 
740
    char *filename;
 
741
    if(plugindir == NULL){
 
742
      ret = asprintf(&filename, PDIR "/%s", dirst->d_name);
 
743
    } else {
 
744
      ret = asprintf(&filename, "%s/%s", plugindir, dirst->d_name);
 
745
    }
 
746
    if(ret < 0){
 
747
      perror("asprintf");
 
748
      continue;
 
749
    }
 
750
    
 
751
    ret = stat(filename, &st);
 
752
    if (ret == -1){
 
753
      perror("stat");
 
754
      free(filename);
 
755
      continue;
 
756
    }
 
757
 
921
758
    /* Ignore non-executable files */
922
 
    if(not S_ISREG(st.st_mode)
923
 
       or (TEMP_FAILURE_RETRY(faccessat(dir_fd, direntries[i]->d_name,
924
 
                                        X_OK, 0)) != 0)){
 
759
    if (not S_ISREG(st.st_mode) or (access(filename, X_OK) != 0)){
925
760
      if(debug){
926
 
        fprintf(stderr, "Ignoring plugin dir entry \"%s/%s\""
927
 
                " with bad type or mode\n",
928
 
                plugindir != NULL ? plugindir : PDIR,
929
 
                direntries[i]->d_name);
 
761
        fprintf(stderr, "Ignoring plugin dir entry \"%s\""
 
762
                " with bad type or mode\n", filename);
930
763
      }
931
 
      close(plugin_fd);
932
 
      free(direntries[i]);
 
764
      free(filename);
933
765
      continue;
934
766
    }
935
767
    
936
 
    plugin *p = getplugin(direntries[i]->d_name);
 
768
    plugin *p = getplugin(dirst->d_name);
937
769
    if(p == NULL){
938
 
      error(0, errno, "getplugin");
939
 
      close(plugin_fd);
940
 
      free(direntries[i]);
 
770
      perror("getplugin");
 
771
      free(filename);
941
772
      continue;
942
773
    }
943
774
    if(p->disabled){
944
775
      if(debug){
945
776
        fprintf(stderr, "Ignoring disabled plugin \"%s\"\n",
946
 
                direntries[i]->d_name);
 
777
                dirst->d_name);
947
778
      }
948
 
      close(plugin_fd);
949
 
      free(direntries[i]);
 
779
      free(filename);
950
780
      continue;
951
781
    }
952
782
    {
955
785
      if(g != NULL){
956
786
        for(char **a = g->argv + 1; *a != NULL; a++){
957
787
          if(not add_argument(p, *a)){
958
 
            error(0, errno, "add_argument");
 
788
            perror("add_argument");
959
789
          }
960
790
        }
961
791
        /* Add global environment variables */
962
792
        for(char **e = g->environ; *e != NULL; e++){
963
793
          if(not add_environment(p, *e, false)){
964
 
            error(0, errno, "add_environment");
 
794
            perror("add_environment");
965
795
          }
966
796
        }
967
797
      }
968
798
    }
969
 
    /* If this plugin has any environment variables, we need to
970
 
       duplicate the environment from this process, too. */
 
799
    /* If this plugin has any environment variables, we will call
 
800
       using execve and need to duplicate the environment from this
 
801
       process, too. */
971
802
    if(p->environ[0] != NULL){
972
803
      for(char **e = environ; *e != NULL; e++){
973
804
        if(not add_environment(p, *e, false)){
974
 
          error(0, errno, "add_environment");
 
805
          perror("add_environment");
975
806
        }
976
807
      }
977
808
    }
978
809
    
979
810
    int pipefd[2];
980
 
#ifndef O_CLOEXEC
981
 
    ret = (int)TEMP_FAILURE_RETRY(pipe(pipefd));
982
 
#else  /* O_CLOEXEC */
983
 
    ret = (int)TEMP_FAILURE_RETRY(pipe2(pipefd, O_CLOEXEC));
984
 
#endif  /* O_CLOEXEC */
985
 
    if(ret == -1){
986
 
      error(0, errno, "pipe");
987
 
      exitstatus = EX_OSERR;
988
 
      free(direntries[i]);
989
 
      goto fallback;
990
 
    }
991
 
    if(pipefd[0] >= FD_SETSIZE){
992
 
      fprintf(stderr, "pipe()[0] (%d) >= FD_SETSIZE (%d)", pipefd[0],
993
 
              FD_SETSIZE);
994
 
      close(pipefd[0]);
995
 
      close(pipefd[1]);
996
 
      exitstatus = EX_OSERR;
997
 
      free(direntries[i]);
998
 
      goto fallback;
999
 
    }
1000
 
#ifndef O_CLOEXEC
 
811
    ret = pipe(pipefd);
 
812
    if (ret == -1){
 
813
      perror("pipe");
 
814
      exitstatus = EXIT_FAILURE;
 
815
      goto fallback;
 
816
    }
1001
817
    /* Ask OS to automatic close the pipe on exec */
1002
818
    ret = set_cloexec_flag(pipefd[0]);
1003
819
    if(ret < 0){
1004
 
      error(0, errno, "set_cloexec_flag");
1005
 
      close(pipefd[0]);
1006
 
      close(pipefd[1]);
1007
 
      exitstatus = EX_OSERR;
1008
 
      free(direntries[i]);
 
820
      perror("set_cloexec_flag");
 
821
      exitstatus = EXIT_FAILURE;
1009
822
      goto fallback;
1010
823
    }
1011
824
    ret = set_cloexec_flag(pipefd[1]);
1012
825
    if(ret < 0){
1013
 
      error(0, errno, "set_cloexec_flag");
1014
 
      close(pipefd[0]);
1015
 
      close(pipefd[1]);
1016
 
      exitstatus = EX_OSERR;
1017
 
      free(direntries[i]);
 
826
      perror("set_cloexec_flag");
 
827
      exitstatus = EXIT_FAILURE;
1018
828
      goto fallback;
1019
829
    }
1020
 
#endif  /* not O_CLOEXEC */
1021
830
    /* Block SIGCHLD until process is safely in process list */
1022
 
    ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_BLOCK,
1023
 
                                              &sigchld_action.sa_mask,
1024
 
                                              NULL));
 
831
    ret = sigprocmask (SIG_BLOCK, &sigchld_action.sa_mask, NULL);
1025
832
    if(ret < 0){
1026
 
      error(0, errno, "sigprocmask");
1027
 
      exitstatus = EX_OSERR;
1028
 
      free(direntries[i]);
 
833
      perror("sigprocmask");
 
834
      exitstatus = EXIT_FAILURE;
1029
835
      goto fallback;
1030
836
    }
1031
837
    /* Starting a new process to be watched */
1032
 
    pid_t pid;
1033
 
    do {
1034
 
      pid = fork();
1035
 
    } while(pid == -1 and errno == EINTR);
 
838
    pid_t pid = fork();
1036
839
    if(pid == -1){
1037
 
      error(0, errno, "fork");
1038
 
      TEMP_FAILURE_RETRY(sigprocmask(SIG_UNBLOCK,
1039
 
                                     &sigchld_action.sa_mask, NULL));
1040
 
      close(pipefd[0]);
1041
 
      close(pipefd[1]);
1042
 
      exitstatus = EX_OSERR;
1043
 
      free(direntries[i]);
 
840
      perror("fork");
 
841
      exitstatus = EXIT_FAILURE;
1044
842
      goto fallback;
1045
843
    }
1046
844
    if(pid == 0){
1047
845
      /* this is the child process */
1048
846
      ret = sigaction(SIGCHLD, &old_sigchld_action, NULL);
1049
847
      if(ret < 0){
1050
 
        error(0, errno, "sigaction");
1051
 
        _exit(EX_OSERR);
 
848
        perror("sigaction");
 
849
        _exit(EXIT_FAILURE);
1052
850
      }
1053
851
      ret = sigprocmask(SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
1054
852
      if(ret < 0){
1055
 
        error(0, errno, "sigprocmask");
1056
 
        _exit(EX_OSERR);
 
853
        perror("sigprocmask");
 
854
        _exit(EXIT_FAILURE);
1057
855
      }
1058
856
      
1059
857
      ret = dup2(pipefd[1], STDOUT_FILENO); /* replace our stdout */
1060
858
      if(ret == -1){
1061
 
        error(0, errno, "dup2");
1062
 
        _exit(EX_OSERR);
 
859
        perror("dup2");
 
860
        _exit(EXIT_FAILURE);
1063
861
      }
1064
862
      
1065
 
      if(fexecve(plugin_fd, p->argv,
1066
 
                (p->environ[0] != NULL) ? p->environ : environ) < 0){
1067
 
        error(0, errno, "fexecve for %s/%s",
1068
 
              plugindir != NULL ? plugindir : PDIR,
1069
 
              direntries[i]->d_name);
1070
 
        _exit(EX_OSERR);
 
863
      if(dirfd(dir) < 0){
 
864
        /* If dir has no file descriptor, we could not set FD_CLOEXEC
 
865
           above and must now close it manually here. */
 
866
        closedir(dir);
 
867
      }
 
868
      if(p->environ[0] == NULL){
 
869
        if(execv(filename, p->argv) < 0){
 
870
          perror("execv");
 
871
          _exit(EXIT_FAILURE);
 
872
        }
 
873
      } else {
 
874
        if(execve(filename, p->argv, p->environ) < 0){
 
875
          perror("execve");
 
876
          _exit(EXIT_FAILURE);
 
877
        }
1071
878
      }
1072
879
      /* no return */
1073
880
    }
1074
881
    /* Parent process */
1075
882
    close(pipefd[1]);           /* Close unused write end of pipe */
1076
 
    close(plugin_fd);
1077
 
    plugin *new_plugin = getplugin(direntries[i]->d_name);
1078
 
    if(new_plugin == NULL){
1079
 
      error(0, errno, "getplugin");
1080
 
      ret = (int)(TEMP_FAILURE_RETRY
1081
 
                  (sigprocmask(SIG_UNBLOCK, &sigchld_action.sa_mask,
1082
 
                               NULL)));
 
883
    free(filename);
 
884
    plugin *new_plugin = getplugin(dirst->d_name);
 
885
    if (new_plugin == NULL){
 
886
      perror("getplugin");
 
887
      ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
1083
888
      if(ret < 0){
1084
 
        error(0, errno, "sigprocmask");
 
889
        perror("sigprocmask");
1085
890
      }
1086
 
      exitstatus = EX_OSERR;
1087
 
      free(direntries[i]);
 
891
      exitstatus = EXIT_FAILURE;
1088
892
      goto fallback;
1089
893
    }
1090
 
    free(direntries[i]);
1091
894
    
1092
895
    new_plugin->pid = pid;
1093
896
    new_plugin->fd = pipefd[0];
1094
897
    
1095
898
    /* Unblock SIGCHLD so signal handler can be run if this process
1096
899
       has already completed */
1097
 
    ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_UNBLOCK,
1098
 
                                              &sigchld_action.sa_mask,
1099
 
                                              NULL));
 
900
    ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
1100
901
    if(ret < 0){
1101
 
      error(0, errno, "sigprocmask");
1102
 
      exitstatus = EX_OSERR;
 
902
      perror("sigprocmask");
 
903
      exitstatus = EXIT_FAILURE;
1103
904
      goto fallback;
1104
905
    }
1105
906
    
1106
907
    FD_SET(new_plugin->fd, &rfds_all);
1107
908
    
1108
 
    if(maxfd < new_plugin->fd){
 
909
    if (maxfd < new_plugin->fd){
1109
910
      maxfd = new_plugin->fd;
1110
911
    }
1111
912
  }
1112
913
  
1113
 
  free(direntries);
1114
 
  direntries = NULL;
1115
 
  close(dir_fd);
1116
 
  dir_fd = -1;
1117
 
  free_plugin(getplugin(NULL));
 
914
  closedir(dir);
 
915
  dir = NULL;
1118
916
  
1119
917
  for(plugin *p = plugin_list; p != NULL; p = p->next){
1120
918
    if(p->pid != 0){
1131
929
  while(plugin_list){
1132
930
    fd_set rfds = rfds_all;
1133
931
    int select_ret = select(maxfd+1, &rfds, NULL, NULL, NULL);
1134
 
    if(select_ret == -1 and errno != EINTR){
1135
 
      error(0, errno, "select");
1136
 
      exitstatus = EX_OSERR;
 
932
    if (select_ret == -1){
 
933
      perror("select");
 
934
      exitstatus = EXIT_FAILURE;
1137
935
      goto fallback;
1138
936
    }
1139
937
    /* OK, now either a process completed, or something can be read
1140
938
       from one of them */
1141
939
    for(plugin *proc = plugin_list; proc != NULL;){
1142
940
      /* Is this process completely done? */
1143
 
      if(proc->completed and proc->eof){
 
941
      if(proc->eof and proc->completed){
1144
942
        /* Only accept the plugin output if it exited cleanly */
1145
943
        if(not WIFEXITED(proc->status)
1146
944
           or WEXITSTATUS(proc->status) != 0){
1147
945
          /* Bad exit by plugin */
1148
 
          
 
946
 
1149
947
          if(debug){
1150
948
            if(WIFEXITED(proc->status)){
1151
 
              fprintf(stderr, "Plugin %s [%" PRIdMAX "] exited with"
1152
 
                      " status %d\n", proc->name,
1153
 
                      (intmax_t) (proc->pid),
 
949
              fprintf(stderr, "Plugin %u exited with status %d\n",
 
950
                      (unsigned int) (proc->pid),
1154
951
                      WEXITSTATUS(proc->status));
1155
 
            } else if(WIFSIGNALED(proc->status)){
1156
 
              fprintf(stderr, "Plugin %s [%" PRIdMAX "] killed by"
1157
 
                      " signal %d: %s\n", proc->name,
1158
 
                      (intmax_t) (proc->pid),
1159
 
                      WTERMSIG(proc->status),
1160
 
                      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));
 
956
            } else if(WCOREDUMP(proc->status)){
 
957
              fprintf(stderr, "Plugin %d dumped core\n",
 
958
                      (unsigned int) (proc->pid));
1161
959
            }
1162
960
          }
1163
961
          
1164
962
          /* Remove the plugin */
1165
963
          FD_CLR(proc->fd, &rfds_all);
1166
 
          
 
964
 
1167
965
          /* Block signal while modifying process_list */
1168
 
          ret = (int)TEMP_FAILURE_RETRY(sigprocmask
1169
 
                                        (SIG_BLOCK,
1170
 
                                         &sigchld_action.sa_mask,
1171
 
                                         NULL));
 
966
          ret = sigprocmask(SIG_BLOCK, &sigchld_action.sa_mask, NULL);
1172
967
          if(ret < 0){
1173
 
            error(0, errno, "sigprocmask");
1174
 
            exitstatus = EX_OSERR;
 
968
            perror("sigprocmask");
 
969
            exitstatus = EXIT_FAILURE;
1175
970
            goto fallback;
1176
971
          }
1177
972
          
1180
975
          proc = next_plugin;
1181
976
          
1182
977
          /* We are done modifying process list, so unblock signal */
1183
 
          ret = (int)(TEMP_FAILURE_RETRY
1184
 
                      (sigprocmask(SIG_UNBLOCK,
1185
 
                                   &sigchld_action.sa_mask, NULL)));
 
978
          ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask,
 
979
                             NULL);
1186
980
          if(ret < 0){
1187
 
            error(0, errno, "sigprocmask");
1188
 
            exitstatus = EX_OSERR;
 
981
            perror("sigprocmask");
 
982
            exitstatus = EXIT_FAILURE;
1189
983
            goto fallback;
1190
984
          }
1191
985
          
1201
995
        bool bret = print_out_password(proc->buffer,
1202
996
                                       proc->buffer_length);
1203
997
        if(not bret){
1204
 
          error(0, errno, "print_out_password");
1205
 
          exitstatus = EX_IOERR;
 
998
          perror("print_out_password");
 
999
          exitstatus = EXIT_FAILURE;
1206
1000
        }
1207
1001
        goto fallback;
1208
1002
      }
1215
1009
      }
1216
1010
      /* Before reading, make the process' data buffer large enough */
1217
1011
      if(proc->buffer_length + BUFFER_SIZE > proc->buffer_size){
1218
 
        char *new_buffer = realloc(proc->buffer, proc->buffer_size
1219
 
                                   + (size_t) BUFFER_SIZE);
1220
 
        if(new_buffer == NULL){
1221
 
          error(0, errno, "malloc");
1222
 
          exitstatus = EX_OSERR;
 
1012
        proc->buffer = realloc(proc->buffer, proc->buffer_size
 
1013
                               + (size_t) BUFFER_SIZE);
 
1014
        if (proc->buffer == NULL){
 
1015
          perror("malloc");
 
1016
          exitstatus = EXIT_FAILURE;
1223
1017
          goto fallback;
1224
1018
        }
1225
 
        proc->buffer = new_buffer;
1226
1019
        proc->buffer_size += BUFFER_SIZE;
1227
1020
      }
1228
1021
      /* Read from the process */
1229
 
      sret = TEMP_FAILURE_RETRY(read(proc->fd,
1230
 
                                     proc->buffer
1231
 
                                     + proc->buffer_length,
1232
 
                                     BUFFER_SIZE));
1233
 
      if(sret < 0){
 
1022
      ret = read(proc->fd, proc->buffer + proc->buffer_length,
 
1023
                 BUFFER_SIZE);
 
1024
      if(ret < 0){
1234
1025
        /* Read error from this process; ignore the error */
1235
1026
        proc = proc->next;
1236
1027
        continue;
1237
1028
      }
1238
 
      if(sret == 0){
 
1029
      if(ret == 0){
1239
1030
        /* got EOF */
1240
1031
        proc->eof = true;
1241
1032
      } else {
1242
 
        proc->buffer_length += (size_t) sret;
 
1033
        proc->buffer_length += (size_t) ret;
1243
1034
      }
1244
1035
    }
1245
1036
  }
1246
 
  
1247
 
  
 
1037
 
 
1038
 
1248
1039
 fallback:
1249
1040
  
1250
 
  if(plugin_list == NULL or (exitstatus != EXIT_SUCCESS
1251
 
                             and exitstatus != EX_OK)){
 
1041
  if(plugin_list == NULL or exitstatus != EXIT_SUCCESS){
1252
1042
    /* Fallback if all plugins failed, none are found or an error
1253
1043
       occured */
1254
1044
    bool bret;
1262
1052
    }
1263
1053
    bret = print_out_password(passwordbuffer, len);
1264
1054
    if(not bret){
1265
 
      error(0, errno, "print_out_password");
1266
 
      exitstatus = EX_IOERR;
 
1055
      perror("print_out_password");
 
1056
      exitstatus = EXIT_FAILURE;
1267
1057
    }
1268
1058
  }
1269
1059
  
1270
1060
  /* Restore old signal handler */
1271
1061
  ret = sigaction(SIGCHLD, &old_sigchld_action, NULL);
1272
1062
  if(ret == -1){
1273
 
    error(0, errno, "sigaction");
1274
 
    exitstatus = EX_OSERR;
 
1063
    perror("sigaction");
 
1064
    exitstatus = EXIT_FAILURE;
1275
1065
  }
1276
1066
  
1277
1067
  if(custom_argv != NULL){
1281
1071
    free(custom_argv);
1282
1072
  }
1283
1073
  
1284
 
  free(direntries);
1285
 
  
1286
 
  if(dir_fd != -1){
1287
 
    close(dir_fd);
 
1074
  if(dir != NULL){
 
1075
    closedir(dir);
1288
1076
  }
1289
1077
  
1290
1078
  /* Kill the processes */
1294
1082
      ret = kill(p->pid, SIGTERM);
1295
1083
      if(ret == -1 and errno != ESRCH){
1296
1084
        /* Set-uid proccesses might not get closed */
1297
 
        error(0, errno, "kill");
 
1085
        perror("kill");
1298
1086
      }
1299
1087
    }
1300
1088
  }
1301
1089
  
1302
1090
  /* Wait for any remaining child processes to terminate */
1303
 
  do {
 
1091
  do{
1304
1092
    ret = wait(NULL);
1305
1093
  } while(ret >= 0);
1306
1094
  if(errno != ECHILD){
1307
 
    error(0, errno, "wait");
 
1095
    perror("wait");
1308
1096
  }
1309
1097
  
1310
1098
  free_plugin_list();
1311
1099
  
1312
1100
  free(plugindir);
1313
 
  free(pluginhelperdir);
1314
1101
  free(argfile);
1315
1102
  
1316
1103
  return exitstatus;