/mandos/release

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

« back to all changes in this revision

Viewing changes to plugin-runner.c

  • Committer: Teddy Hogeborn
  • Date: 2016-06-03 17:27:03 UTC
  • mto: (237.7.594 trunk)
  • mto: This revision was merged to the branch mainline in revision 343.
  • Revision ID: teddy@recompile.se-20160603172703-mc6tjor6rhq4xy74
mandos: Bug fix: Do multiprocessing cleanup correctly on exit

* mandos (main): Save module "multiprocessing" and open file "wnull"
                 as scope variables accessible by function cleanup(),
                 since the module and global variable may not be
                 accessible when the cleanup() function is run as
                 scheduled by atexit().

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