/mandos/release

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

« back to all changes in this revision

Viewing changes to plugin-runner.c

  • Committer: Teddy Hogeborn
  • Date: 2015-05-23 20:18:34 UTC
  • mto: (237.7.304 trunk)
  • mto: This revision was merged to the branch mainline in revision 325.
  • Revision ID: teddy@recompile.se-20150523201834-e89ex4ito93yni8x
mandos: Use multiprocessing module to run checkers.

For a long time, the Mandos server has occasionally logged the message
"ERROR: Child process vanished".  This was never a fatal error, but it
has been annoying and slightly worrying, since a definite cause was
not found.  One potential cause could be the "multiprocessing" and
"subprocess" modules conflicting w.r.t. SIGCHLD.  To avoid this,
change the running of checkers from using subprocess.Popen
asynchronously to instead first create a multiprocessing.Process()
(which is asynchronous) calling a function, and have that function
then call subprocess.call() (which is synchronous).  In this way, the
only thing using any asynchronous subprocesses is the multiprocessing
module.

This makes it necessary to change one small thing in the D-Bus API,
since the subprocesses.call() function does not expose the raw wait(2)
status value.

DBUS-API (CheckerCompleted): Change the second value provided by this
                             D-Bus signal from the raw wait(2) status
                             to the actual terminating signal number.
mandos (subprocess_call_pipe): New function to be called by
                               multiprocessing.Process (starting a
                               separate process).
(Client.last_checker signal): New attribute for signal which
                              terminated last checker.  Like
                              last_checker_status, only not accessible
                              via D-Bus.
(Client.checker_callback): Take new "connection" argument and use it
                           to get returncode; set last_checker_signal.
                           Return False so gobject does not call this
                           callback again.
(Client.start_checker): Start checker using a multiprocessing.Process
                        instead of a subprocess.Popen.
(ClientDBus.checker_callback): Take new "connection" argument.        Call
                               Client.checker_callback early to have
                               it set last_checker_status and
                               last_checker_signal; use those.  Change
                               second value provided to D-Bus signal
                               CheckerCompleted to use
                               last_checker_signal if checker was
                               terminated by signal.
mandos-monitor: Update to reflect DBus API change.
(MandosClientWidget.checker_completed): Take "signal" instead of
                                        "condition" argument.  Use it
                                        accordingly.  Remove dead code
                                        (os.WCOREDUMP case).

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