/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: 2015-07-20 03:03:33 UTC
  • Revision ID: teddy@recompile.se-20150720030333-203m2aeblypcsfte
Bug fix for GnuTLS 3: be compatible with old 2048-bit DSA keys.

The mandos-keygen program in Mandos version 1.6.0 and older generated
2048-bit DSA keys, and when GnuTLS uses these it has trouble
connecting using the Mandos default priority string.  This was
previously fixed in Mandos 1.6.2, but the bug reappeared when using
GnuTLS 3, so the default priority string has to change again; this
time also the Mandos client has to change its default, so now the
server and the client should use the same default priority string:

SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA:+SIGN-DSA-SHA256

* mandos (main/server_defaults): Changed default priority string.
* mandos-options.xml (/section/para[id="priority_compat"]): Removed.
  (/section/para[id="priority"]): Changed default priority string.
* mandos.conf ([DEFAULT]/priority): - '' -
* mandos.conf.xml (OPTIONS/priority): Refer to the id "priority"
                                      instead of "priority_compat".
* mandos.xml (OPTIONS/--priority): - '' -
* plugins.d/mandos-client.c (main): Changed default priority string.

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