/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

  • Committer: Teddy Hogeborn
  • Date: 2015-05-23 20:18:34 UTC
  • mto: (237.7.304 trunk)
  • mto: This revision was merged to the branch mainline in revision 325.
  • 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,
62
63
#include <signal.h>             /* struct sigaction, sigemptyset(),
63
64
                                   sigaddset(), sigaction(),
64
65
                                   sigprocmask(), SIG_BLOCK, SIGCHLD,
65
 
                                   SIG_UNBLOCK, kill() */
 
66
                                   SIG_UNBLOCK, kill(), sig_atomic_t
 
67
                                */
66
68
#include <errno.h>              /* errno, EBADF */
67
 
#include <inttypes.h>           /* intmax_t, SCNdMAX, PRIdMAX,  */
 
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() */
68
75
 
69
76
#define BUFFER_SIZE 256
70
77
 
72
79
#define AFILE "/conf/conf.d/mandos/plugin-runner.conf"
73
80
 
74
81
const char *argp_program_version = "plugin-runner " VERSION;
75
 
const char *argp_program_bug_address = "<mandos@fukt.bsnet.se>";
 
82
const char *argp_program_bug_address = "<mandos@recompile.se>";
76
83
 
77
84
typedef struct plugin{
78
85
  char *name;                   /* can be NULL or any plugin name */
81
88
  char **environ;
82
89
  int envc;
83
90
  bool disabled;
84
 
 
 
91
  
85
92
  /* Variables used for running processes*/
86
93
  pid_t pid;
87
94
  int fd;
89
96
  size_t buffer_size;
90
97
  size_t buffer_length;
91
98
  bool eof;
92
 
  volatile bool completed;
93
 
  volatile int status;
 
99
  volatile sig_atomic_t completed;
 
100
  int status;
94
101
  struct plugin *next;
95
102
} plugin;
96
103
 
98
105
 
99
106
/* Gets an existing plugin based on name,
100
107
   or if none is found, creates a new one */
 
108
__attribute__((warn_unused_result))
101
109
static plugin *getplugin(char *name){
102
 
  /* Check for exiting plugin with that name */
 
110
  /* Check for existing plugin with that name */
103
111
  for(plugin *p = plugin_list; p != NULL; p = p->next){
104
112
    if((p->name == name)
105
113
       or (p->name and name and (strcmp(p->name, name) == 0))){
107
115
    }
108
116
  }
109
117
  /* Create a new plugin */
110
 
  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);
111
122
  if(new_plugin == NULL){
112
123
    return NULL;
113
124
  }
114
125
  char *copy_name = NULL;
115
126
  if(name != NULL){
116
 
    copy_name = strdup(name);
 
127
    do {
 
128
      copy_name = strdup(name);
 
129
    } while(copy_name == NULL and errno == EINTR);
117
130
    if(copy_name == NULL){
 
131
      int e = errno;
 
132
      free(new_plugin);
 
133
      errno = e;
118
134
      return NULL;
119
135
    }
120
136
  }
121
137
  
122
 
  *new_plugin = (plugin) { .name = copy_name,
123
 
                           .argc = 1,
124
 
                           .disabled = false,
125
 
                           .next = plugin_list };
 
138
  *new_plugin = (plugin){ .name = copy_name,
 
139
                          .argc = 1,
 
140
                          .disabled = false,
 
141
                          .next = plugin_list };
126
142
  
127
 
  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);
128
146
  if(new_plugin->argv == NULL){
 
147
    int e = errno;
129
148
    free(copy_name);
130
149
    free(new_plugin);
 
150
    errno = e;
131
151
    return NULL;
132
152
  }
133
153
  new_plugin->argv[0] = copy_name;
134
154
  new_plugin->argv[1] = NULL;
135
155
  
136
 
  new_plugin->environ = malloc(sizeof(char *));
 
156
  do {
 
157
    new_plugin->environ = malloc(sizeof(char *));
 
158
  } while(new_plugin->environ == NULL and errno == EINTR);
137
159
  if(new_plugin->environ == NULL){
 
160
    int e = errno;
138
161
    free(copy_name);
139
162
    free(new_plugin->argv);
140
163
    free(new_plugin);
 
164
    errno = e;
141
165
    return NULL;
142
166
  }
143
167
  new_plugin->environ[0] = NULL;
148
172
}
149
173
 
150
174
/* Helper function for add_argument and add_environment */
 
175
__attribute__((nonnull, warn_unused_result))
151
176
static bool add_to_char_array(const char *new, char ***array,
152
177
                              int *len){
153
178
  /* Resize the pointed-to array to hold one more pointer */
154
 
  *array = realloc(*array, sizeof(char *)
155
 
                   * (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);
156
184
  /* Malloc check */
157
 
  if(*array == NULL){
 
185
  if(new_array == NULL){
158
186
    return false;
159
187
  }
 
188
  *array = new_array;
160
189
  /* Make a copy of the new string */
161
 
  char *copy = strdup(new);
 
190
  char *copy;
 
191
  do {
 
192
    copy = strdup(new);
 
193
  } while(copy == NULL and errno == EINTR);
162
194
  if(copy == NULL){
163
195
    return false;
164
196
  }
171
203
}
172
204
 
173
205
/* Add to a plugin's argument vector */
 
206
__attribute__((nonnull(2), warn_unused_result))
174
207
static bool add_argument(plugin *p, const char *arg){
175
208
  if(p == NULL){
176
209
    return false;
179
212
}
180
213
 
181
214
/* Add to a plugin's environment */
 
215
__attribute__((nonnull(2), warn_unused_result))
182
216
static bool add_environment(plugin *p, const char *def, bool replace){
183
217
  if(p == NULL){
184
218
    return false;
186
220
  /* namelen = length of name of environment variable */
187
221
  size_t namelen = (size_t)(strchrnul(def, '=') - def);
188
222
  /* Search for this environment variable */
189
 
  for(char **e = p->environ; *e != NULL; e++){
190
 
    if(strncmp(*e, def, namelen + 1) == 0){
 
223
  for(char **envdef = p->environ; *envdef != NULL; envdef++){
 
224
    if(strncmp(*envdef, def, namelen + 1) == 0){
191
225
      /* It already exists */
192
226
      if(replace){
193
 
        char *new = realloc(*e, strlen(def) + 1);
194
 
        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){
195
232
          return false;
196
233
        }
197
 
        *e = new;
198
 
        strcpy(*e, def);
 
234
        *envdef = new_envdef;
 
235
        strcpy(*envdef, def);
199
236
      }
200
237
      return true;
201
238
    }
203
240
  return add_to_char_array(def, &(p->environ), &(p->envc));
204
241
}
205
242
 
 
243
#ifndef O_CLOEXEC
206
244
/*
207
245
 * Based on the example in the GNU LibC manual chapter 13.13 "File
208
246
 * Descriptor Flags".
209
 
 * *Note File Descriptor Flags:(libc)Descriptor Flags.
 
247
 | [[info:libc:Descriptor%20Flags][File Descriptor Flags]] |
210
248
 */
 
249
__attribute__((warn_unused_result))
211
250
static int set_cloexec_flag(int fd){
212
 
  int ret = fcntl(fd, F_GETFD, 0);
 
251
  int ret = (int)TEMP_FAILURE_RETRY(fcntl(fd, F_GETFD, 0));
213
252
  /* If reading the flags failed, return error indication now. */
214
253
  if(ret < 0){
215
254
    return ret;
216
255
  }
217
256
  /* Store modified flag word in the descriptor. */
218
 
  return fcntl(fd, F_SETFD, ret | FD_CLOEXEC);
 
257
  return (int)TEMP_FAILURE_RETRY(fcntl(fd, F_SETFD,
 
258
                                       ret | FD_CLOEXEC));
219
259
}
 
260
#endif  /* not O_CLOEXEC */
220
261
 
221
262
 
222
263
/* Mark processes as completed when they exit, and save their exit
223
264
   status. */
224
265
static void handle_sigchld(__attribute__((unused)) int sig){
 
266
  int old_errno = errno;
225
267
  while(true){
226
268
    plugin *proc = plugin_list;
227
269
    int status;
231
273
      break;
232
274
    }
233
275
    if(pid == -1){
234
 
      if(errno != ECHILD){
235
 
        perror("waitpid");
 
276
      if(errno == ECHILD){
 
277
        /* No child processes */
 
278
        break;
236
279
      }
237
 
      /* No child processes */
238
 
      break;
 
280
      error(0, errno, "waitpid");
239
281
    }
240
282
    
241
283
    /* A child exited, find it in process_list */
247
289
      continue;
248
290
    }
249
291
    proc->status = status;
250
 
    proc->completed = true;
 
292
    proc->completed = 1;
251
293
  }
 
294
  errno = old_errno;
252
295
}
253
296
 
254
297
/* Prints out a password to stdout */
 
298
__attribute__((nonnull, warn_unused_result))
255
299
static bool print_out_password(const char *buffer, size_t length){
256
300
  ssize_t ret;
257
301
  for(size_t written = 0; written < length; written += (size_t)ret){
265
309
}
266
310
 
267
311
/* Removes and free a plugin from the plugin list */
 
312
__attribute__((nonnull))
268
313
static void free_plugin(plugin *plugin_node){
269
314
  
270
315
  for(char **arg = plugin_node->argv; *arg != NULL; arg++){
276
321
  }
277
322
  free(plugin_node->environ);
278
323
  free(plugin_node->buffer);
279
 
 
 
324
  
280
325
  /* Removes the plugin from the singly-linked list */
281
326
  if(plugin_node == plugin_list){
282
327
    /* First one - simple */
304
349
  char *plugindir = NULL;
305
350
  char *argfile = NULL;
306
351
  FILE *conffp;
307
 
  size_t d_name_len;
308
 
  DIR *dir = NULL;
309
 
  struct dirent *dirst;
 
352
  struct dirent **direntries = NULL;
310
353
  struct stat st;
311
354
  fd_set rfds_all;
312
 
  int ret, numchars, maxfd = 0;
 
355
  int ret, maxfd = 0;
313
356
  ssize_t sret;
314
 
  intmax_t tmpmax;
315
357
  uid_t uid = 65534;
316
358
  gid_t gid = 65534;
317
359
  bool debug = false;
321
363
                                      .sa_flags = SA_NOCLDSTOP };
322
364
  char **custom_argv = NULL;
323
365
  int custom_argc = 0;
 
366
  int dir_fd = -1;
324
367
  
325
368
  /* Establish a signal handler */
326
369
  sigemptyset(&sigchld_action.sa_mask);
327
370
  ret = sigaddset(&sigchld_action.sa_mask, SIGCHLD);
328
371
  if(ret == -1){
329
 
    perror("sigaddset");
330
 
    exitstatus = EXIT_FAILURE;
 
372
    error(0, errno, "sigaddset");
 
373
    exitstatus = EX_OSERR;
331
374
    goto fallback;
332
375
  }
333
376
  ret = sigaction(SIGCHLD, &sigchld_action, &old_sigchld_action);
334
377
  if(ret == -1){
335
 
    perror("sigaction");
336
 
    exitstatus = EXIT_FAILURE;
 
378
    error(0, errno, "sigaction");
 
379
    exitstatus = EX_OSERR;
337
380
    goto fallback;
338
381
  }
339
382
  
371
414
      .doc = "Group ID the plugins will run as", .group = 3 },
372
415
    { .name = "debug", .key = 132,
373
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 },
374
426
    { .name = NULL }
375
427
  };
376
428
  
377
 
  error_t parse_opt(int key, char *arg, __attribute__((unused))
378
 
                    struct argp_state *state) {
379
 
    switch(key) {
 
429
  __attribute__((nonnull(3)))
 
430
  error_t parse_opt(int key, char *arg, struct argp_state *state){
 
431
    errno = 0;
 
432
    switch(key){
 
433
      char *tmp;
 
434
      intmax_t tmp_id;
380
435
    case 'g':                   /* --global-options */
381
 
      if(arg != NULL){
382
 
        char *p;
383
 
        while((p = strsep(&arg, ",")) != NULL){
384
 
          if(p[0] == '\0'){
385
 
            continue;
386
 
          }
387
 
          if(not add_argument(getplugin(NULL), p)){
388
 
            perror("add_argument");
389
 
            return ARGP_ERR_UNKNOWN;
 
436
      {
 
437
        char *plugin_option;
 
438
        while((plugin_option = strsep(&arg, ",")) != NULL){
 
439
          if(not add_argument(getplugin(NULL), plugin_option)){
 
440
            break;
390
441
          }
391
442
        }
 
443
        errno = 0;
392
444
      }
393
445
      break;
394
446
    case 'G':                   /* --global-env */
395
 
      if(arg == NULL){
396
 
        break;
397
 
      }
398
 
      if(not add_environment(getplugin(NULL), arg, true)){
399
 
        perror("add_environment");
 
447
      if(add_environment(getplugin(NULL), arg, true)){
 
448
        errno = 0;
400
449
      }
401
450
      break;
402
451
    case 'o':                   /* --options-for */
403
 
      if(arg != NULL){
404
 
        char *p_name = strsep(&arg, ":");
405
 
        if(p_name[0] == '\0' or arg == NULL){
406
 
          break;
407
 
        }
408
 
        char *opt = strsep(&arg, ":");
409
 
        if(opt[0] == '\0' or opt == NULL){
410
 
          break;
411
 
        }
412
 
        char *p;
413
 
        while((p = strsep(&opt, ",")) != NULL){
414
 
          if(p[0] == '\0'){
415
 
            continue;
416
 
          }
417
 
          if(not add_argument(getplugin(p_name), p)){
418
 
            perror("add_argument");
419
 
            return ARGP_ERR_UNKNOWN;
420
 
          }
421
 
        }
 
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;
 
470
          }
 
471
        }
 
472
        errno = 0;
422
473
      }
423
474
      break;
424
475
    case 'E':                   /* --env-for */
425
 
      if(arg == NULL){
426
 
        break;
427
 
      }
428
476
      {
429
477
        char *envdef = strchr(arg, ':');
430
478
        if(envdef == NULL){
 
479
          argp_error(state, "No colon in \"%s\"", arg);
 
480
          errno = EINVAL;
431
481
          break;
432
482
        }
433
483
        *envdef = '\0';
434
 
        if(not add_environment(getplugin(arg), envdef+1, true)){
435
 
          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;
436
492
        }
437
493
      }
438
494
      break;
439
495
    case 'd':                   /* --disable */
440
 
      if(arg != NULL){
 
496
      {
441
497
        plugin *p = getplugin(arg);
442
 
        if(p == NULL){
443
 
          return ARGP_ERR_UNKNOWN;
 
498
        if(p != NULL){
 
499
          p->disabled = true;
 
500
          errno = 0;
444
501
        }
445
 
        p->disabled = true;
446
502
      }
447
503
      break;
448
504
    case 'e':                   /* --enable */
449
 
      if(arg != NULL){
 
505
      {
450
506
        plugin *p = getplugin(arg);
451
 
        if(p == NULL){
452
 
          return ARGP_ERR_UNKNOWN;
 
507
        if(p != NULL){
 
508
          p->disabled = false;
 
509
          errno = 0;
453
510
        }
454
 
        p->disabled = false;
455
511
      }
456
512
      break;
457
513
    case 128:                   /* --plugin-dir */
458
514
      free(plugindir);
459
515
      plugindir = strdup(arg);
460
 
      if(plugindir == NULL){
461
 
        perror("strdup");
462
 
      }      
 
516
      if(plugindir != NULL){
 
517
        errno = 0;
 
518
      }
463
519
      break;
464
520
    case 129:                   /* --config-file */
465
521
      /* This is already done by parse_opt_config_file() */
466
522
      break;
467
523
    case 130:                   /* --userid */
468
 
      ret = sscanf(arg, "%" SCNdMAX "%n", &tmpmax, &numchars);
469
 
      if(ret < 1 or tmpmax != (uid_t)tmpmax
470
 
         or arg[numchars] != '\0'){
471
 
        fprintf(stderr, "Bad user ID number: \"%s\", using %"
472
 
                PRIdMAX "\n", arg, (intmax_t)uid);
473
 
      } else {
474
 
        uid = (uid_t)tmpmax;
 
524
      tmp_id = strtoimax(arg, &tmp, 10);
 
525
      if(errno != 0 or tmp == arg or *tmp != '\0'
 
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;
475
530
      }
 
531
      uid = (uid_t)tmp_id;
 
532
      errno = 0;
476
533
      break;
477
534
    case 131:                   /* --groupid */
478
 
      ret = sscanf(arg, "%" SCNdMAX "%n", &tmpmax, &numchars);
479
 
      if(ret < 1 or tmpmax != (gid_t)tmpmax
480
 
         or arg[numchars] != '\0'){
481
 
        fprintf(stderr, "Bad group ID number: \"%s\", using %"
482
 
                PRIdMAX "\n", arg, (intmax_t)gid);
483
 
      } else {
484
 
        gid = (gid_t)tmpmax;
 
535
      tmp_id = strtoimax(arg, &tmp, 10);
 
536
      if(errno != 0 or tmp == arg or *tmp != '\0'
 
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
511
580
     ignores everything but the --config-file option. */
512
581
  error_t parse_opt_config_file(int key, char *arg,
513
582
                                __attribute__((unused))
514
 
                                struct argp_state *state) {
515
 
    switch(key) {
 
583
                                struct argp_state *state){
 
584
    errno = 0;
 
585
    switch(key){
516
586
    case 'g':                   /* --global-options */
517
587
    case 'G':                   /* --global-env */
518
588
    case 'o':                   /* --options-for */
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;
569
651
    size_t size = 0;
570
652
    const char whitespace_delims[] = " \r\t\f\v\n";
571
653
    const char comment_delim[] = "#";
572
 
 
 
654
    
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];
581
663
    custom_argv[1] = NULL;
582
 
 
 
664
    
583
665
    /* for each line in the config file, strip whitespace and ignore
584
666
       commented text */
585
667
    while(true){
587
669
      if(sret == -1){
588
670
        break;
589
671
      }
590
 
 
 
672
      
591
673
      line = org_line;
592
674
      arg = strsep(&line, comment_delim);
593
675
      while((p = strsep(&arg, whitespace_delims)) != NULL){
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 */
 
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);
 
804
  if(ret == -1){
 
805
    error(0, errno, "setgid");
 
806
  }
664
807
  ret = setuid(uid);
665
808
  if(ret == -1){
666
 
    perror("setuid");
667
 
  }  
668
 
  setgid(gid);
669
 
  if(ret == -1){
670
 
    perror("setgid");
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;
 
1111
  free_plugin(getplugin(NULL));
936
1112
  
937
1113
  for(plugin *p = plugin_list; p != NULL; p = p->next){
938
1114
    if(p->pid != 0){
949
1125
  while(plugin_list){
950
1126
    fd_set rfds = rfds_all;
951
1127
    int select_ret = select(maxfd+1, &rfds, NULL, NULL, NULL);
952
 
    if(select_ret == -1){
953
 
      perror("select");
954
 
      exitstatus = EXIT_FAILURE;
 
1128
    if(select_ret == -1 and errno != EINTR){
 
1129
      error(0, errno, "select");
 
1130
      exitstatus = EX_OSERR;
955
1131
      goto fallback;
956
1132
    }
957
1133
    /* OK, now either a process completed, or something can be read
958
1134
       from one of them */
959
1135
    for(plugin *proc = plugin_list; proc != NULL;){
960
1136
      /* Is this process completely done? */
961
 
      if(proc->eof and proc->completed){
 
1137
      if(proc->completed and proc->eof){
962
1138
        /* Only accept the plugin output if it exited cleanly */
963
1139
        if(not WIFEXITED(proc->status)
964
1140
           or WEXITSTATUS(proc->status) != 0){
965
1141
          /* Bad exit by plugin */
966
 
 
 
1142
          
967
1143
          if(debug){
968
1144
            if(WIFEXITED(proc->status)){
969
 
              fprintf(stderr, "Plugin %" PRIdMAX " exited with status"
970
 
                      " %d\n", (intmax_t) (proc->pid),
 
1145
              fprintf(stderr, "Plugin %s [%" PRIdMAX "] exited with"
 
1146
                      " status %d\n", proc->name,
 
1147
                      (intmax_t) (proc->pid),
971
1148
                      WEXITSTATUS(proc->status));
972
 
            } else if(WIFSIGNALED(proc->status)) {
973
 
              fprintf(stderr, "Plugin %" PRIdMAX " killed by signal"
974
 
                      " %d\n", (intmax_t) (proc->pid),
975
 
                      WTERMSIG(proc->status));
 
1149
            } else if(WIFSIGNALED(proc->status)){
 
1150
              fprintf(stderr, "Plugin %s [%" PRIdMAX "] killed by"
 
1151
                      " signal %d: %s\n", proc->name,
 
1152
                      (intmax_t) (proc->pid),
 
1153
                      WTERMSIG(proc->status),
 
1154
                      strsignal(WTERMSIG(proc->status)));
976
1155
            } else if(WCOREDUMP(proc->status)){
977
 
              fprintf(stderr, "Plugin %" PRIdMAX " dumped core\n",
978
 
                      (intmax_t) (proc->pid));
 
1156
              fprintf(stderr, "Plugin %s [%" PRIdMAX "] dumped"
 
1157
                      " core\n", proc->name, (intmax_t) (proc->pid));
979
1158
            }
980
1159
          }
981
1160
          
982
1161
          /* Remove the plugin */
983
 
          FD_CLR(proc->fd, &rfds_all);
984
 
 
 
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
 
1176
          
985
1177
          /* Block signal while modifying process_list */
986
 
          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));
987
1182
          if(ret < 0){
988
 
            perror("sigprocmask");
989
 
            exitstatus = EXIT_FAILURE;
 
1183
            error(0, errno, "sigprocmask");
 
1184
            exitstatus = EX_OSERR;
990
1185
            goto fallback;
991
1186
          }
992
1187
          
995
1190
          proc = next_plugin;
996
1191
          
997
1192
          /* We are done modifying process list, so unblock signal */
998
 
          ret = sigprocmask(SIG_UNBLOCK, &sigchld_action.sa_mask,
999
 
                            NULL);
 
1193
          ret = (int)(TEMP_FAILURE_RETRY
 
1194
                      (sigprocmask(SIG_UNBLOCK,
 
1195
                                   &sigchld_action.sa_mask, NULL)));
1000
1196
          if(ret < 0){
1001
 
            perror("sigprocmask");
1002
 
            exitstatus = EXIT_FAILURE;
 
1197
            error(0, errno, "sigprocmask");
 
1198
            exitstatus = EX_OSERR;
1003
1199
            goto fallback;
1004
1200
          }
1005
1201
          
1015
1211
        bool bret = print_out_password(proc->buffer,
1016
1212
                                       proc->buffer_length);
1017
1213
        if(not bret){
1018
 
          perror("print_out_password");
1019
 
          exitstatus = EXIT_FAILURE;
 
1214
          error(0, errno, "print_out_password");
 
1215
          exitstatus = EX_IOERR;
1020
1216
        }
1021
1217
        goto fallback;
1022
1218
      }
1023
1219
      
1024
1220
      /* This process has not completed.  Does it have any output? */
1025
 
      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
1026
1238
        /* This process had nothing to say at this time */
1027
1239
        proc = proc->next;
1028
1240
        continue;
1029
1241
      }
1030
1242
      /* Before reading, make the process' data buffer large enough */
1031
1243
      if(proc->buffer_length + BUFFER_SIZE > proc->buffer_size){
1032
 
        proc->buffer = realloc(proc->buffer, proc->buffer_size
1033
 
                               + (size_t) BUFFER_SIZE);
1034
 
        if(proc->buffer == NULL){
1035
 
          perror("malloc");
1036
 
          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;
1037
1249
          goto fallback;
1038
1250
        }
 
1251
        proc->buffer = new_buffer;
1039
1252
        proc->buffer_size += BUFFER_SIZE;
1040
1253
      }
1041
1254
      /* Read from the process */
1042
 
      sret = read(proc->fd, proc->buffer + proc->buffer_length,
1043
 
                  BUFFER_SIZE);
 
1255
      sret = TEMP_FAILURE_RETRY(read(proc->fd,
 
1256
                                     proc->buffer
 
1257
                                     + proc->buffer_length,
 
1258
                                     BUFFER_SIZE));
1044
1259
      if(sret < 0){
1045
1260
        /* Read error from this process; ignore the error */
1046
1261
        proc = proc->next;
1054
1269
      }
1055
1270
    }
1056
1271
  }
1057
 
 
1058
 
 
 
1272
  
 
1273
  
1059
1274
 fallback:
1060
1275
  
1061
 
  if(plugin_list == NULL or exitstatus != EXIT_SUCCESS){
 
1276
  if(plugin_list == NULL or (exitstatus != EXIT_SUCCESS
 
1277
                             and exitstatus != EX_OK)){
1062
1278
    /* Fallback if all plugins failed, none are found or an error
1063
1279
       occured */
1064
1280
    bool bret;
1072
1288
    }
1073
1289
    bret = print_out_password(passwordbuffer, len);
1074
1290
    if(not bret){
1075
 
      perror("print_out_password");
1076
 
      exitstatus = EXIT_FAILURE;
 
1291
      error(0, errno, "print_out_password");
 
1292
      exitstatus = EX_IOERR;
1077
1293
    }
1078
1294
  }
1079
1295
  
1080
1296
  /* Restore old signal handler */
1081
1297
  ret = sigaction(SIGCHLD, &old_sigchld_action, NULL);
1082
1298
  if(ret == -1){
1083
 
    perror("sigaction");
1084
 
    exitstatus = EXIT_FAILURE;
 
1299
    error(0, errno, "sigaction");
 
1300
    exitstatus = EX_OSERR;
1085
1301
  }
1086
1302
  
1087
1303
  if(custom_argv != NULL){
1091
1307
    free(custom_argv);
1092
1308
  }
1093
1309
  
1094
 
  if(dir != NULL){
1095
 
    closedir(dir);
 
1310
  free(direntries);
 
1311
  
 
1312
  if(dir_fd != -1){
 
1313
    TEMP_FAILURE_RETRY(close(dir_fd));
1096
1314
  }
1097
1315
  
1098
1316
  /* Kill the processes */
1102
1320
      ret = kill(p->pid, SIGTERM);
1103
1321
      if(ret == -1 and errno != ESRCH){
1104
1322
        /* Set-uid proccesses might not get closed */
1105
 
        perror("kill");
 
1323
        error(0, errno, "kill");
1106
1324
      }
1107
1325
    }
1108
1326
  }
1109
1327
  
1110
1328
  /* Wait for any remaining child processes to terminate */
1111
 
  do{
 
1329
  do {
1112
1330
    ret = wait(NULL);
1113
1331
  } while(ret >= 0);
1114
1332
  if(errno != ECHILD){
1115
 
    perror("wait");
 
1333
    error(0, errno, "wait");
1116
1334
  }
1117
1335
  
1118
1336
  free_plugin_list();