/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: 2019-02-10 03:50:20 UTC
  • Revision ID: teddy@recompile.se-20190210035020-nttr1tybgwwixueu
Show debconf note about new TLS key IDs

If mandos-client did not see TLS keys and had to create them, or if
mandos sees GnuTLS version 3.6.6 or later, show an important notice on
package installation about the importance of adding the new key_id
options to clients.conf on the Mandos server.

* debian/control (Package: mandos, Package: mandos-client): Depend on
                                                            debconf.
* debian/mandos-client.lintian-overrides: Override warnings.
* debian/mandos-client.postinst (create_keys): Show notice if new TLS
                                               key files were created.
* debian/mandos-client.templates: New.
* debian/mandos.lintian-overrides: Override warnings.
* debian/mandos.postinst (configure): If GnuTLS 3.6.6 or later is
                                      detected, show an important
                                      notice (once) about the new
                                      key_id option required in
                                      clients.conf.
* debian/mandos.templates: New.

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
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
 
 * *Note File Descriptor Flags:(libc)Descriptor Flags.
 
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;
319
358
  ssize_t sret;
320
 
  intmax_t tmpmax;
321
359
  uid_t uid = 65534;
322
360
  gid_t gid = 65534;
323
361
  bool debug = false;
327
365
                                      .sa_flags = SA_NOCLDSTOP };
328
366
  char **custom_argv = NULL;
329
367
  int custom_argc = 0;
 
368
  int dir_fd = -1;
330
369
  
331
370
  /* Establish a signal handler */
332
371
  sigemptyset(&sigchld_action.sa_mask);
333
372
  ret = sigaddset(&sigchld_action.sa_mask, SIGCHLD);
334
373
  if(ret == -1){
335
 
    perror("sigaddset");
336
 
    exitstatus = EXIT_FAILURE;
 
374
    error(0, errno, "sigaddset");
 
375
    exitstatus = EX_OSERR;
337
376
    goto fallback;
338
377
  }
339
378
  ret = sigaction(SIGCHLD, &sigchld_action, &old_sigchld_action);
340
379
  if(ret == -1){
341
 
    perror("sigaction");
342
 
    exitstatus = EXIT_FAILURE;
 
380
    error(0, errno, "sigaction");
 
381
    exitstatus = EX_OSERR;
343
382
    goto fallback;
344
383
  }
345
384
  
377
416
      .doc = "Group ID the plugins will run as", .group = 3 },
378
417
    { .name = "debug", .key = 132,
379
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 },
380
432
    { .name = NULL }
381
433
  };
382
434
  
383
 
  error_t parse_opt(int key, char *arg, __attribute__((unused))
384
 
                    struct argp_state *state){
385
 
    char *tmp;
 
435
  __attribute__((nonnull(3)))
 
436
  error_t parse_opt(int key, char *arg, struct argp_state *state){
 
437
    errno = 0;
386
438
    switch(key){
 
439
      char *tmp;
 
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
    }
 
720
    do {
 
721
      ret = fclose(conffp);
 
722
    } while(ret == EOF and errno == EINTR);
 
723
    if(ret == EOF){
 
724
      error(0, errno, "fclose");
 
725
      exitstatus = EX_IOERR;
 
726
      goto fallback;
 
727
    }
620
728
    free(org_line);
621
729
  } else {
622
730
    /* Check for harmful errors and go to fallback. Other errors might
623
731
       not affect opening plugins */
624
732
    if(errno == EMFILE or errno == ENFILE or errno == ENOMEM){
625
 
      perror("fopen");
626
 
      exitstatus = EXIT_FAILURE;
 
733
      error(0, errno, "fopen");
 
734
      exitstatus = EX_OSERR;
627
735
      goto fallback;
628
736
    }
629
737
  }
630
 
  /* If there was any arguments from configuration file,
631
 
     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 */
632
740
  if(custom_argv != NULL){
633
 
    ret = argp_parse(&argp, custom_argc, custom_argv, ARGP_IN_ORDER,
634
 
                     0, NULL);
635
 
    if(ret == ARGP_ERR_UNKNOWN){
636
 
      fprintf(stderr, "Unknown error while parsing arguments\n");
637
 
      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;
638
755
      goto fallback;
639
756
    }
640
757
  }
641
758
  
642
759
  /* Parse actual command line arguments, to let them override the
643
760
     config file */
644
 
  ret = argp_parse(&argp, argc, argv, ARGP_IN_ORDER, 0, NULL);
645
 
  if(ret == ARGP_ERR_UNKNOWN){
646
 
    fprintf(stderr, "Unknown error while parsing arguments\n");
647
 
    exitstatus = EXIT_FAILURE;
648
 
    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
    }
649
794
  }
650
795
  
651
796
  if(debug){
652
 
    for(plugin *p = plugin_list; p != NULL; p=p->next){
 
797
    for(plugin *p = plugin_list; p != NULL; p = p->next){
653
798
      fprintf(stderr, "Plugin: %s has %d arguments\n",
654
799
              p->name ? p->name : "Global", p->argc - 1);
655
800
      for(char **a = p->argv; *a != NULL; a++){
662
807
    }
663
808
  }
664
809
  
665
 
  /* Strip permissions down to nobody */
666
 
  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);
667
836
  if(ret == -1){
668
 
    perror("setgid");
 
837
    error(0, errno, "setgid");
669
838
  }
670
839
  ret = setuid(uid);
671
840
  if(ret == -1){
672
 
    perror("setuid");
673
 
  }
674
 
  
675
 
  if(plugindir == NULL){
676
 
    dir = opendir(PDIR);
677
 
  } else {
678
 
    dir = opendir(plugindir);
679
 
  }
680
 
  
681
 
  if(dir == NULL){
682
 
    perror("Could not open plugin dir");
683
 
    exitstatus = EXIT_FAILURE;
684
 
    goto fallback;
685
 
  }
686
 
  
687
 
  /* 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 */
688
845
  {
689
 
    int dir_fd = dirfd(dir);
690
 
    if(dir_fd >= 0){
691
 
      ret = set_cloexec_flag(dir_fd);
692
 
      if(ret < 0){
693
 
        perror("set_cloexec_flag");
694
 
        exitstatus = EXIT_FAILURE;
695
 
        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;
696
890
      }
697
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;
698
902
  }
699
903
  
700
904
  FD_ZERO(&rfds_all);
701
905
  
702
906
  /* Read and execute any executable in the plugin directory*/
703
 
  while(true){
704
 
    dirst = readdir(dir);
705
 
    
706
 
    /* All directory entries have been processed */
707
 
    if(dirst == NULL){
708
 
      if(errno == EBADF){
709
 
        perror("readdir");
710
 
        exitstatus = EXIT_FAILURE;
711
 
        goto fallback;
712
 
      }
713
 
      break;
714
 
    }
715
 
    
716
 
    d_name_len = strlen(dirst->d_name);
717
 
    
718
 
    /* Ignore dotfiles, backup files and other junk */
719
 
    {
720
 
      bool bad_name = false;
721
 
      
722
 
      const char const *bad_prefixes[] = { ".", "#", NULL };
723
 
      
724
 
      const char const *bad_suffixes[] = { "~", "#", ".dpkg-new",
725
 
                                           ".dpkg-old",
726
 
                                           ".dpkg-bak",
727
 
                                           ".dpkg-divert", NULL };
728
 
      for(const char **pre = bad_prefixes; *pre != NULL; pre++){
729
 
        size_t pre_len = strlen(*pre);
730
 
        if((d_name_len >= pre_len)
731
 
           and strncmp((dirst->d_name), *pre, pre_len) == 0){
732
 
          if(debug){
733
 
            fprintf(stderr, "Ignoring plugin dir entry \"%s\""
734
 
                    " with bad prefix %s\n", dirst->d_name, *pre);
735
 
          }
736
 
          bad_name = true;
737
 
          break;
738
 
        }
739
 
      }
740
 
      if(bad_name){
741
 
        continue;
742
 
      }
743
 
      for(const char **suf = bad_suffixes; *suf != NULL; suf++){
744
 
        size_t suf_len = strlen(*suf);
745
 
        if((d_name_len >= suf_len)
746
 
           and (strcmp((dirst->d_name)+d_name_len-suf_len, *suf)
747
 
                == 0)){
748
 
          if(debug){
749
 
            fprintf(stderr, "Ignoring plugin dir entry \"%s\""
750
 
                    " with bad suffix %s\n", dirst->d_name, *suf);
751
 
          }
752
 
          bad_name = true;
753
 
          break;
754
 
        }
755
 
      }
756
 
      
757
 
      if(bad_name){
758
 
        continue;
759
 
      }
760
 
    }
761
 
    
762
 
    char *filename;
763
 
    if(plugindir == NULL){
764
 
      ret = asprintf(&filename, PDIR "/%s", dirst->d_name);
765
 
    } else {
766
 
      ret = asprintf(&filename, "%s/%s", plugindir, dirst->d_name);
767
 
    }
768
 
    if(ret < 0){
769
 
      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]);
770
913
      continue;
771
914
    }
772
 
    
773
 
    ret = stat(filename, &st);
 
915
    ret = (int)TEMP_FAILURE_RETRY(fstat(plugin_fd, &st));
774
916
    if(ret == -1){
775
 
      perror("stat");
776
 
      free(filename);
 
917
      error(0, errno, "stat");
 
918
      close(plugin_fd);
 
919
      free(direntries[i]);
777
920
      continue;
778
921
    }
779
922
    
780
923
    /* Ignore non-executable files */
781
 
    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)){
782
927
      if(debug){
783
 
        fprintf(stderr, "Ignoring plugin dir entry \"%s\""
784
 
                " 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);
785
932
      }
786
 
      free(filename);
 
933
      close(plugin_fd);
 
934
      free(direntries[i]);
787
935
      continue;
788
936
    }
789
937
    
790
 
    plugin *p = getplugin(dirst->d_name);
 
938
    plugin *p = getplugin(direntries[i]->d_name);
791
939
    if(p == NULL){
792
 
      perror("getplugin");
793
 
      free(filename);
 
940
      error(0, errno, "getplugin");
 
941
      close(plugin_fd);
 
942
      free(direntries[i]);
794
943
      continue;
795
944
    }
796
945
    if(p->disabled){
797
946
      if(debug){
798
947
        fprintf(stderr, "Ignoring disabled plugin \"%s\"\n",
799
 
                dirst->d_name);
 
948
                direntries[i]->d_name);
800
949
      }
801
 
      free(filename);
 
950
      close(plugin_fd);
 
951
      free(direntries[i]);
802
952
      continue;
803
953
    }
804
954
    {
807
957
      if(g != NULL){
808
958
        for(char **a = g->argv + 1; *a != NULL; a++){
809
959
          if(not add_argument(p, *a)){
810
 
            perror("add_argument");
 
960
            error(0, errno, "add_argument");
811
961
          }
812
962
        }
813
963
        /* Add global environment variables */
814
964
        for(char **e = g->environ; *e != NULL; e++){
815
965
          if(not add_environment(p, *e, false)){
816
 
            perror("add_environment");
 
966
            error(0, errno, "add_environment");
817
967
          }
818
968
        }
819
969
      }
820
970
    }
821
 
    /* If this plugin has any environment variables, we will call
822
 
       using execve and need to duplicate the environment from this
823
 
       process, too. */
 
971
    /* If this plugin has any environment variables, we need to
 
972
       duplicate the environment from this process, too. */
824
973
    if(p->environ[0] != NULL){
825
974
      for(char **e = environ; *e != NULL; e++){
826
975
        if(not add_environment(p, *e, false)){
827
 
          perror("add_environment");
 
976
          error(0, errno, "add_environment");
828
977
        }
829
978
      }
830
979
    }
831
980
    
832
981
    int pipefd[2];
833
 
    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 */
834
987
    if(ret == -1){
835
 
      perror("pipe");
836
 
      exitstatus = EXIT_FAILURE;
837
 
      goto fallback;
838
 
    }
 
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
839
1003
    /* Ask OS to automatic close the pipe on exec */
840
1004
    ret = set_cloexec_flag(pipefd[0]);
841
1005
    if(ret < 0){
842
 
      perror("set_cloexec_flag");
843
 
      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]);
844
1011
      goto fallback;
845
1012
    }
846
1013
    ret = set_cloexec_flag(pipefd[1]);
847
1014
    if(ret < 0){
848
 
      perror("set_cloexec_flag");
849
 
      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]);
850
1020
      goto fallback;
851
1021
    }
 
1022
#endif  /* not O_CLOEXEC */
852
1023
    /* Block SIGCHLD until process is safely in process list */
853
 
    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));
854
1027
    if(ret < 0){
855
 
      perror("sigprocmask");
856
 
      exitstatus = EXIT_FAILURE;
 
1028
      error(0, errno, "sigprocmask");
 
1029
      exitstatus = EX_OSERR;
 
1030
      free(direntries[i]);
857
1031
      goto fallback;
858
1032
    }
859
1033
    /* Starting a new process to be watched */
860
 
    pid_t pid = fork();
 
1034
    pid_t pid;
 
1035
    do {
 
1036
      pid = fork();
 
1037
    } while(pid == -1 and errno == EINTR);
861
1038
    if(pid == -1){
862
 
      perror("fork");
863
 
      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]);
864
1046
      goto fallback;
865
1047
    }
866
1048
    if(pid == 0){
867
1049
      /* this is the child process */
868
1050
      ret = sigaction(SIGCHLD, &old_sigchld_action, NULL);
869
1051
      if(ret < 0){
870
 
        perror("sigaction");
871
 
        _exit(EXIT_FAILURE);
 
1052
        error(0, errno, "sigaction");
 
1053
        _exit(EX_OSERR);
872
1054
      }
873
1055
      ret = sigprocmask(SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
874
1056
      if(ret < 0){
875
 
        perror("sigprocmask");
876
 
        _exit(EXIT_FAILURE);
 
1057
        error(0, errno, "sigprocmask");
 
1058
        _exit(EX_OSERR);
877
1059
      }
878
1060
      
879
1061
      ret = dup2(pipefd[1], STDOUT_FILENO); /* replace our stdout */
880
1062
      if(ret == -1){
881
 
        perror("dup2");
882
 
        _exit(EXIT_FAILURE);
 
1063
        error(0, errno, "dup2");
 
1064
        _exit(EX_OSERR);
883
1065
      }
884
1066
      
885
 
      if(dirfd(dir) < 0){
886
 
        /* If dir has no file descriptor, we could not set FD_CLOEXEC
887
 
           above and must now close it manually here. */
888
 
        closedir(dir);
889
 
      }
890
 
      if(p->environ[0] == NULL){
891
 
        if(execv(filename, p->argv) < 0){
892
 
          perror("execv");
893
 
          _exit(EXIT_FAILURE);
894
 
        }
895
 
      } else {
896
 
        if(execve(filename, p->argv, p->environ) < 0){
897
 
          perror("execve");
898
 
          _exit(EXIT_FAILURE);
899
 
        }
 
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);
900
1073
      }
901
1074
      /* no return */
902
1075
    }
903
1076
    /* Parent process */
904
1077
    close(pipefd[1]);           /* Close unused write end of pipe */
905
 
    free(filename);
906
 
    plugin *new_plugin = getplugin(dirst->d_name);
 
1078
    close(plugin_fd);
 
1079
    plugin *new_plugin = getplugin(direntries[i]->d_name);
907
1080
    if(new_plugin == NULL){
908
 
      perror("getplugin");
909
 
      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)));
910
1085
      if(ret < 0){
911
 
        perror("sigprocmask");
 
1086
        error(0, errno, "sigprocmask");
912
1087
      }
913
 
      exitstatus = EXIT_FAILURE;
 
1088
      exitstatus = EX_OSERR;
 
1089
      free(direntries[i]);
914
1090
      goto fallback;
915
1091
    }
 
1092
    free(direntries[i]);
916
1093
    
917
1094
    new_plugin->pid = pid;
918
1095
    new_plugin->fd = pipefd[0];
919
 
    
 
1096
 
 
1097
    if(debug){
 
1098
      fprintf(stderr, "Plugin %s started (PID %" PRIdMAX ")\n",
 
1099
              new_plugin->name, (intmax_t) (new_plugin->pid));
 
1100
    }
 
1101
 
920
1102
    /* Unblock SIGCHLD so signal handler can be run if this process
921
1103
       has already completed */
922
 
    ret = sigprocmask(SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
 
1104
    ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_UNBLOCK,
 
1105
                                              &sigchld_action.sa_mask,
 
1106
                                              NULL));
923
1107
    if(ret < 0){
924
 
      perror("sigprocmask");
925
 
      exitstatus = EXIT_FAILURE;
 
1108
      error(0, errno, "sigprocmask");
 
1109
      exitstatus = EX_OSERR;
926
1110
      goto fallback;
927
1111
    }
928
1112
    
933
1117
    }
934
1118
  }
935
1119
  
936
 
  closedir(dir);
937
 
  dir = NULL;
 
1120
  free(direntries);
 
1121
  direntries = NULL;
 
1122
  close(dir_fd);
 
1123
  dir_fd = -1;
938
1124
  free_plugin(getplugin(NULL));
939
1125
  
940
1126
  for(plugin *p = plugin_list; p != NULL; p = p->next){
952
1138
  while(plugin_list){
953
1139
    fd_set rfds = rfds_all;
954
1140
    int select_ret = select(maxfd+1, &rfds, NULL, NULL, NULL);
955
 
    if(select_ret == -1){
956
 
      perror("select");
957
 
      exitstatus = EXIT_FAILURE;
 
1141
    if(select_ret == -1 and errno != EINTR){
 
1142
      error(0, errno, "select");
 
1143
      exitstatus = EX_OSERR;
958
1144
      goto fallback;
959
1145
    }
960
1146
    /* OK, now either a process completed, or something can be read
979
1165
                      (intmax_t) (proc->pid),
980
1166
                      WTERMSIG(proc->status),
981
1167
                      strsignal(WTERMSIG(proc->status)));
982
 
            } else if(WCOREDUMP(proc->status)){
983
 
              fprintf(stderr, "Plugin %s [%" PRIdMAX "] dumped"
984
 
                      " core\n", proc->name, (intmax_t) (proc->pid));
985
1168
            }
986
1169
          }
987
1170
          
989
1172
          FD_CLR(proc->fd, &rfds_all);
990
1173
          
991
1174
          /* Block signal while modifying process_list */
992
 
          ret = sigprocmask(SIG_BLOCK, &sigchld_action.sa_mask, NULL);
 
1175
          ret = (int)TEMP_FAILURE_RETRY(sigprocmask
 
1176
                                        (SIG_BLOCK,
 
1177
                                         &sigchld_action.sa_mask,
 
1178
                                         NULL));
993
1179
          if(ret < 0){
994
 
            perror("sigprocmask");
995
 
            exitstatus = EXIT_FAILURE;
 
1180
            error(0, errno, "sigprocmask");
 
1181
            exitstatus = EX_OSERR;
996
1182
            goto fallback;
997
1183
          }
998
1184
          
1001
1187
          proc = next_plugin;
1002
1188
          
1003
1189
          /* We are done modifying process list, so unblock signal */
1004
 
          ret = sigprocmask(SIG_UNBLOCK, &sigchld_action.sa_mask,
1005
 
                            NULL);
 
1190
          ret = (int)(TEMP_FAILURE_RETRY
 
1191
                      (sigprocmask(SIG_UNBLOCK,
 
1192
                                   &sigchld_action.sa_mask, NULL)));
1006
1193
          if(ret < 0){
1007
 
            perror("sigprocmask");
1008
 
            exitstatus = EXIT_FAILURE;
 
1194
            error(0, errno, "sigprocmask");
 
1195
            exitstatus = EX_OSERR;
1009
1196
            goto fallback;
1010
1197
          }
1011
1198
          
1021
1208
        bool bret = print_out_password(proc->buffer,
1022
1209
                                       proc->buffer_length);
1023
1210
        if(not bret){
1024
 
          perror("print_out_password");
1025
 
          exitstatus = EXIT_FAILURE;
 
1211
          error(0, errno, "print_out_password");
 
1212
          exitstatus = EX_IOERR;
1026
1213
        }
1027
1214
        goto fallback;
1028
1215
      }
1035
1222
      }
1036
1223
      /* Before reading, make the process' data buffer large enough */
1037
1224
      if(proc->buffer_length + BUFFER_SIZE > proc->buffer_size){
1038
 
        proc->buffer = realloc(proc->buffer, proc->buffer_size
1039
 
                               + (size_t) BUFFER_SIZE);
1040
 
        if(proc->buffer == NULL){
1041
 
          perror("malloc");
1042
 
          exitstatus = EXIT_FAILURE;
 
1225
        char *new_buffer = realloc(proc->buffer, proc->buffer_size
 
1226
                                   + (size_t) BUFFER_SIZE);
 
1227
        if(new_buffer == NULL){
 
1228
          error(0, errno, "malloc");
 
1229
          exitstatus = EX_OSERR;
1043
1230
          goto fallback;
1044
1231
        }
 
1232
        proc->buffer = new_buffer;
1045
1233
        proc->buffer_size += BUFFER_SIZE;
1046
1234
      }
1047
1235
      /* Read from the process */
1048
 
      sret = read(proc->fd, proc->buffer + proc->buffer_length,
1049
 
                  BUFFER_SIZE);
 
1236
      sret = TEMP_FAILURE_RETRY(read(proc->fd,
 
1237
                                     proc->buffer
 
1238
                                     + proc->buffer_length,
 
1239
                                     BUFFER_SIZE));
1050
1240
      if(sret < 0){
1051
1241
        /* Read error from this process; ignore the error */
1052
1242
        proc = proc->next;
1064
1254
  
1065
1255
 fallback:
1066
1256
  
1067
 
  if(plugin_list == NULL or exitstatus != EXIT_SUCCESS){
 
1257
  if(plugin_list == NULL or (exitstatus != EXIT_SUCCESS
 
1258
                             and exitstatus != EX_OK)){
1068
1259
    /* Fallback if all plugins failed, none are found or an error
1069
1260
       occured */
1070
1261
    bool bret;
1078
1269
    }
1079
1270
    bret = print_out_password(passwordbuffer, len);
1080
1271
    if(not bret){
1081
 
      perror("print_out_password");
1082
 
      exitstatus = EXIT_FAILURE;
 
1272
      error(0, errno, "print_out_password");
 
1273
      exitstatus = EX_IOERR;
1083
1274
    }
1084
1275
  }
1085
1276
  
1086
1277
  /* Restore old signal handler */
1087
1278
  ret = sigaction(SIGCHLD, &old_sigchld_action, NULL);
1088
1279
  if(ret == -1){
1089
 
    perror("sigaction");
1090
 
    exitstatus = EXIT_FAILURE;
 
1280
    error(0, errno, "sigaction");
 
1281
    exitstatus = EX_OSERR;
1091
1282
  }
1092
1283
  
1093
1284
  if(custom_argv != NULL){
1097
1288
    free(custom_argv);
1098
1289
  }
1099
1290
  
1100
 
  if(dir != NULL){
1101
 
    closedir(dir);
 
1291
  free(direntries);
 
1292
  
 
1293
  if(dir_fd != -1){
 
1294
    close(dir_fd);
1102
1295
  }
1103
1296
  
1104
1297
  /* Kill the processes */
1108
1301
      ret = kill(p->pid, SIGTERM);
1109
1302
      if(ret == -1 and errno != ESRCH){
1110
1303
        /* Set-uid proccesses might not get closed */
1111
 
        perror("kill");
 
1304
        error(0, errno, "kill");
1112
1305
      }
1113
1306
    }
1114
1307
  }
1115
1308
  
1116
1309
  /* Wait for any remaining child processes to terminate */
1117
 
  do{
 
1310
  do {
1118
1311
    ret = wait(NULL);
1119
1312
  } while(ret >= 0);
1120
1313
  if(errno != ECHILD){
1121
 
    perror("wait");
 
1314
    error(0, errno, "wait");
1122
1315
  }
1123
1316
  
1124
1317
  free_plugin_list();
1125
1318
  
1126
1319
  free(plugindir);
 
1320
  free(pluginhelperdir);
1127
1321
  free(argfile);
1128
1322
  
1129
1323
  return exitstatus;