/mandos/trunk

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

« back to all changes in this revision

Viewing changes to plugin-runner.c

  • Committer: Teddy Hogeborn
  • Date: 2019-03-18 22:29:25 UTC
  • Revision ID: teddy@recompile.se-20190318222925-jvhek84dgcfgj6g3
mandos-ctl: Refactor tests

* mandos-ctl: Where the clients names "foo" and "barbar" do not refer
              to the actual mock clients in the TestCommand class,
              change all occurrences of these names to "client1" and
              "client2" (or just "client" when only one is used) .
              Also change all test doubles to use correct terminology;
              some things called mocks are actually stubs or spies,
              and rename all true mocks to have "mock" in their names.
              Also eliminate duplicate values in tests; derive values
              from previously defined values whenever possible.

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