/mandos/trunk

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

« back to all changes in this revision

Viewing changes to plugin-runner.c

  • Committer: Teddy Hogeborn
  • Date: 2015-05-23 20:18:34 UTC
  • mto: This revision was merged to the branch mainline in revision 756.
  • Revision ID: teddy@recompile.se-20150523201834-e89ex4ito93yni8x
mandos: Use multiprocessing module to run checkers.

For a long time, the Mandos server has occasionally logged the message
"ERROR: Child process vanished".  This was never a fatal error, but it
has been annoying and slightly worrying, since a definite cause was
not found.  One potential cause could be the "multiprocessing" and
"subprocess" modules conflicting w.r.t. SIGCHLD.  To avoid this,
change the running of checkers from using subprocess.Popen
asynchronously to instead first create a multiprocessing.Process()
(which is asynchronous) calling a function, and have that function
then call subprocess.call() (which is synchronous).  In this way, the
only thing using any asynchronous subprocesses is the multiprocessing
module.

This makes it necessary to change one small thing in the D-Bus API,
since the subprocesses.call() function does not expose the raw wait(2)
status value.

DBUS-API (CheckerCompleted): Change the second value provided by this
                             D-Bus signal from the raw wait(2) status
                             to the actual terminating signal number.
mandos (subprocess_call_pipe): New function to be called by
                               multiprocessing.Process (starting a
                               separate process).
(Client.last_checker signal): New attribute for signal which
                              terminated last checker.  Like
                              last_checker_status, only not accessible
                              via D-Bus.
(Client.checker_callback): Take new "connection" argument and use it
                           to get returncode; set last_checker_signal.
                           Return False so gobject does not call this
                           callback again.
(Client.start_checker): Start checker using a multiprocessing.Process
                        instead of a subprocess.Popen.
(ClientDBus.checker_callback): Take new "connection" argument.        Call
                               Client.checker_callback early to have
                               it set last_checker_status and
                               last_checker_signal; use those.  Change
                               second value provided to D-Bus signal
                               CheckerCompleted to use
                               last_checker_signal if checker was
                               terminated by signal.
mandos-monitor: Update to reflect DBus API change.
(MandosClientWidget.checker_completed): Take "signal" instead of
                                        "condition" argument.  Use it
                                        accordingly.  Remove dead code
                                        (os.WCOREDUMP case).

Show diffs side-by-side

added added

removed removed

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