/mandos/trunk

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

« back to all changes in this revision

Viewing changes to plugin-runner.c

  • Committer: Teddy Hogeborn
  • Date: 2016-03-17 20:40:55 UTC
  • Revision ID: teddy@recompile.se-20160317204055-bhsh5xsidq7w5cxu
Client: Fix plymouth agent; broken since 1.7.2.

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

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

Show diffs side-by-side

added added

removed removed

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