/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: 2018-08-15 09:26:02 UTC
  • Revision ID: teddy@recompile.se-20180815092602-xoyb5s6gf8376i7u
mandos-client: Set system clock if necessary

* plugins.d/mandos-client.c (init_gpgme/import_key): If the system
  clock is not set, or set to january 1970, set the system clock to
  the more plausible value that is the mtime of the key file.  This is
  required by GnuPG to be able to import the keys.  (We can't pass the
  --ignore-time-conflict or the --ignore-valid-from options though
  GPGME.)

Show diffs side-by-side

added added

removed removed

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