/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: 2016-03-17 20:40:55 UTC
  • Revision ID: teddy@recompile.se-20160317204055-bhsh5xsidq7w5cxu
Client: Fix plymouth agent; broken since 1.7.2.

Fix an very old memory bug in the plymouth agent (which has been
present since its apperance in version 1.2), but which was only
recently detected at run time due to the new -fsanitize=address
compile- time flag, which has been used since version 1.7.2.  This
detection of a memory access violation causes the program to abort,
making the Plymouth graphical boot system unable to accept interactive
input of passwords when using the Mandos client.

* plugins.d/plymouth.c (exec_and_wait): Fix memory allocation bug when
  allocating new_argv.  Also tolerate a zero-length argv.

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