/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 plugbasedclient.c

  • Committer: Teddy Hogeborn
  • Date: 2008-08-02 10:48:24 UTC
  • Revision ID: teddy@fukt.bsnet.se-20080802104824-fx0miwp9o4g9r31e
* plugbasedclient.c (struct process): New fields "eof", "completed",
                                      and "status".
  (handle_sigchld): New function.
  (main): Initialize "dir" to NULL to only closedir() it if necessary.
          Move "process_list" to be a global variable to be accessible
          by "handle_sigchld".  Make "handle_sigchld" handle SIGCHLD.
          Remove redundant check for NULL "dir".  Free "filename" when
          no longer used.  Block SIGCHLD around fork()/exec().
          Restore normal signals in child.  Only loop while running
          processes exist.  Print process buffer when the process is
          done and it has emitted EOF, not when it only emits EOF.
          Remove processes from list which exit non-cleanly.  In
          cleaning up, closedir() if necessary.  Bug fix: set next
          pointer correctly when freeing process list.

* plugins.d/passprompt.c (main): Do not ignore SIGQUIT.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/*  -*- coding: utf-8; mode: c; mode: orgtbl -*- */
 
1
/*  -*- coding: utf-8 -*- */
2
2
/*
3
3
 * Mandos plugin runner - Run Mandos plugins
4
4
 *
5
 
 * Copyright © 2008-2017 Teddy Hogeborn
6
 
 * Copyright © 2008-2017 Björn Påhlsson
7
 
 * 
8
 
 * This file is part of Mandos.
9
 
 * 
10
 
 * Mandos is free software: you can redistribute it and/or modify it
11
 
 * under the terms of the GNU General Public License as published by
12
 
 * the Free Software Foundation, either version 3 of the License, or
13
 
 * (at your option) any later version.
14
 
 * 
15
 
 * Mandos is distributed in the hope that it will be useful, but
 
5
 * Copyright © 2007-2008 Teddy Hogeborn & Björn Påhlsson
 
6
 * 
 
7
 * This program is free software: you can redistribute it and/or
 
8
 * modify it under the terms of the GNU General Public License as
 
9
 * published by the Free Software Foundation, either version 3 of the
 
10
 * License, or (at your option) any later version.
 
11
 * 
 
12
 * This program is distributed in the hope that it will be useful, but
16
13
 * WITHOUT ANY WARRANTY; without even the implied warranty of
17
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18
15
 * General Public License for more details.
19
16
 * 
20
17
 * You should have received a copy of the GNU General Public License
21
 
 * along with Mandos.  If not, see <http://www.gnu.org/licenses/>.
 
18
 * along with this program.  If not, see
 
19
 * <http://www.gnu.org/licenses/>.
22
20
 * 
23
 
 * Contact the authors at <mandos@recompile.se>.
 
21
 * Contact the authors at <mandos@fukt.bsnet.se>.
24
22
 */
25
23
 
26
 
#define _GNU_SOURCE             /* TEMP_FAILURE_RETRY(), getline(),
27
 
                                   O_CLOEXEC, pipe2() */
28
 
#include <stddef.h>             /* size_t, NULL */
29
 
#include <stdlib.h>             /* malloc(), exit(), EXIT_SUCCESS,
30
 
                                   realloc() */
31
 
#include <stdbool.h>            /* bool, true, false */
32
 
#include <stdio.h>              /* fileno(), fprintf(),
33
 
                                   stderr, STDOUT_FILENO, fclose() */
34
 
#include <sys/types.h>          /* fstat(), struct stat, waitpid(),
35
 
                                   WIFEXITED(), WEXITSTATUS(), wait(),
36
 
                                   pid_t, uid_t, gid_t, getuid(),
37
 
                                   getgid() */
 
24
#define _GNU_SOURCE             /* TEMP_FAILURE_RETRY() */
 
25
 
 
26
#include <stdio.h>              /* popen(), fileno(), fprintf(),
 
27
                                   stderr, STDOUT_FILENO */
 
28
#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 */
38
42
#include <sys/select.h>         /* fd_set, select(), FD_ZERO(),
39
 
                                   FD_SET(), FD_ISSET(), FD_CLR */
40
 
#include <sys/wait.h>           /* wait(), waitpid(), WIFEXITED(),
41
 
                                   WEXITSTATUS(), WTERMSIG() */
42
 
#include <sys/stat.h>           /* struct stat, fstat(), S_ISREG() */
43
 
#include <iso646.h>             /* and, or, not */
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() */
 
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() */
57
48
#include <errno.h>              /* errno */
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*/
 
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{
94
56
  pid_t pid;
95
57
  int fd;
96
58
  char *buffer;
97
59
  size_t buffer_size;
98
60
  size_t buffer_length;
99
61
  bool eof;
100
 
  volatile sig_atomic_t completed;
 
62
  bool completed;
101
63
  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;
102
72
  struct plugin *next;
103
73
} plugin;
104
74
 
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))){
 
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))){
115
79
      return p;
116
80
    }
117
81
  }
118
82
  /* Create a new plugin */
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;
 
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;
155
95
  new_plugin->argv[1] = NULL;
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
 
  
 
96
  new_plugin->argc = 1;
 
97
  new_plugin->disabled = false;
 
98
  new_plugin->next = *plugin_list;
170
99
  /* Append the new plugin to the list */
171
 
  plugin_list = new_plugin;
 
100
  *plugin_list = new_plugin;
172
101
  return new_plugin;
173
102
}
174
103
 
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
 
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
 
245
115
/*
246
116
 * Based on the example in the GNU LibC manual chapter 13.13 "File
247
117
 * Descriptor Flags".
248
 
 | [[info:libc:Descriptor%20Flags][File Descriptor Flags]] |
 
118
 * *Note File Descriptor Flags:(libc)Descriptor Flags.
249
119
 */
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));
 
120
int set_cloexec_flag(int fd)
 
121
{
 
122
  int ret = fcntl(fd, F_GETFD, 0);
253
123
  /* If reading the flags failed, return error indication now. */
254
124
  if(ret < 0){
255
125
    return ret;
256
126
  }
257
127
  /* Store modified flag word in the descriptor. */
258
 
  return (int)TEMP_FAILURE_RETRY(fcntl(fd, F_SETFD,
259
 
                                       ret | FD_CLOEXEC));
 
128
  return fcntl(fd, F_SETFD, ret | FD_CLOEXEC);
260
129
}
261
 
#endif  /* not O_CLOEXEC */
262
 
 
263
 
 
264
 
/* Mark processes as completed when they exit, and save their exit
 
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
265
139
   status. */
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
 
  free(plugin_node->name);
317
 
  for(char **arg = plugin_node->argv; *arg != NULL; arg++){
318
 
    free(*arg);
319
 
  }
320
 
  free(plugin_node->argv);
321
 
  for(char **env = plugin_node->environ; *env != NULL; env++){
322
 
    free(*env);
323
 
  }
324
 
  free(plugin_node->environ);
325
 
  free(plugin_node->buffer);
326
 
  
327
 
  /* Removes the plugin from the singly-linked list */
328
 
  if(plugin_node == plugin_list){
329
 
    /* First one - simple */
330
 
    plugin_list = plugin_list->next;
331
 
  } else {
332
 
    /* Second one or later */
333
 
    for(plugin *p = plugin_list; p != NULL; p = p->next){
334
 
      if(p->next == plugin_node){
335
 
        p->next = plugin_node->next;
336
 
        break;
337
 
      }
338
 
    }
339
 
  }
340
 
  
341
 
  free(plugin_node);
342
 
}
343
 
 
344
 
static void free_plugin_list(void){
345
 
  while(plugin_list != NULL){
346
 
    free_plugin(plugin_list);
347
 
  }
 
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;
348
153
}
349
154
 
350
155
int main(int argc, char *argv[]){
351
 
  char *plugindir = NULL;
352
 
  char *pluginhelperdir = NULL;
353
 
  char *argfile = NULL;
354
 
  FILE *conffp;
355
 
  struct dirent **direntries = NULL;
 
156
  const char *plugindir = "/conf/conf.d/mandos/plugins.d";
 
157
  size_t d_name_len;
 
158
  DIR *dir = NULL;
 
159
  struct dirent *dirst;
356
160
  struct stat st;
357
161
  fd_set rfds_all;
358
162
  int ret, maxfd = 0;
359
 
  ssize_t sret;
360
 
  uid_t uid = 65534;
361
 
  gid_t gid = 65534;
362
163
  bool debug = false;
363
164
  int exitstatus = EXIT_SUCCESS;
364
 
  struct sigaction old_sigchld_action;
365
 
  struct sigaction sigchld_action = { .sa_handler = handle_sigchld,
366
 
                                      .sa_flags = SA_NOCLDSTOP };
367
 
  char **custom_argv = NULL;
368
 
  int custom_argc = 0;
369
 
  int dir_fd = -1;
370
165
  
371
166
  /* Establish a signal handler */
 
167
  struct sigaction old_sigchld_action,
 
168
    sigchld_action = { .sa_handler = handle_sigchld,
 
169
                       .sa_flags = SA_NOCLDSTOP };
372
170
  sigemptyset(&sigchld_action.sa_mask);
373
171
  ret = sigaddset(&sigchld_action.sa_mask, SIGCHLD);
374
 
  if(ret == -1){
375
 
    error(0, errno, "sigaddset");
376
 
    exitstatus = EX_OSERR;
377
 
    goto fallback;
 
172
  if(ret < 0){
 
173
    perror("sigaddset");
 
174
    exit(EXIT_FAILURE);
378
175
  }
379
176
  ret = sigaction(SIGCHLD, &sigchld_action, &old_sigchld_action);
380
 
  if(ret == -1){
381
 
    error(0, errno, "sigaction");
382
 
    exitstatus = EX_OSERR;
383
 
    goto fallback;
 
177
  if(ret < 0){
 
178
    perror("sigaction");
 
179
    exit(EXIT_FAILURE);
384
180
  }
385
181
  
386
182
  /* The options we understand. */
388
184
    { .name = "global-options", .key = 'g',
389
185
      .arg = "OPTION[,OPTION[,...]]",
390
186
      .doc = "Options passed to all plugins" },
391
 
    { .name = "global-env", .key = 'G',
392
 
      .arg = "VAR=value",
393
 
      .doc = "Environment variable passed to all plugins" },
394
187
    { .name = "options-for", .key = 'o',
395
188
      .arg = "PLUGIN:OPTION[,OPTION[,...]]",
396
189
      .doc = "Options passed only to specified plugin" },
397
 
    { .name = "env-for", .key = 'E',
398
 
      .arg = "PLUGIN:ENV=value",
399
 
      .doc = "Environment variable passed to specified plugin" },
400
190
    { .name = "disable", .key = 'd',
401
191
      .arg = "PLUGIN",
402
192
      .doc = "Disable a specific plugin", .group = 1 },
403
 
    { .name = "enable", .key = 'e',
404
 
      .arg = "PLUGIN",
405
 
      .doc = "Enable a specific plugin", .group = 1 },
406
193
    { .name = "plugin-dir", .key = 128,
407
194
      .arg = "DIRECTORY",
408
195
      .doc = "Specify a different plugin directory", .group = 2 },
409
 
    { .name = "config-file", .key = 129,
410
 
      .arg = "FILE",
411
 
      .doc = "Specify a different configuration file", .group = 2 },
412
 
    { .name = "userid", .key = 130,
413
 
      .arg = "ID", .flags = 0,
414
 
      .doc = "User ID the plugins will run as", .group = 3 },
415
 
    { .name = "groupid", .key = 131,
416
 
      .arg = "ID", .flags = 0,
417
 
      .doc = "Group ID the plugins will run as", .group = 3 },
418
 
    { .name = "debug", .key = 132,
419
 
      .doc = "Debug mode", .group = 4 },
420
 
    { .name = "plugin-helper-dir", .key = 133,
421
 
      .arg = "DIRECTORY",
422
 
      .doc = "Specify a different plugin helper directory",
423
 
      .group = 2 },
424
 
    /*
425
 
     * These reproduce what we would get without ARGP_NO_HELP
426
 
     */
427
 
    { .name = "help", .key = '?',
428
 
      .doc = "Give this help list", .group = -1 },
429
 
    { .name = "usage", .key = -3,
430
 
      .doc = "Give a short usage message", .group = -1 },
431
 
    { .name = "version", .key = 'V',
432
 
      .doc = "Print program version", .group = -1 },
 
196
    { .name = "debug", .key = 129,
 
197
      .doc = "Debug mode", .group = 3 },
433
198
    { .name = NULL }
434
199
  };
435
200
  
436
 
  __attribute__((nonnull(3)))
437
 
  error_t parse_opt(int key, char *arg, struct argp_state *state){
438
 
    errno = 0;
439
 
    switch(key){
440
 
      char *tmp;
441
 
      intmax_t tmp_id;
442
 
    case 'g':                   /* --global-options */
443
 
      {
444
 
        char *plugin_option;
445
 
        while((plugin_option = strsep(&arg, ",")) != NULL){
446
 
          if(not add_argument(getplugin(NULL), plugin_option)){
447
 
            break;
448
 
          }
449
 
        }
450
 
        errno = 0;
451
 
      }
452
 
      break;
453
 
    case 'G':                   /* --global-env */
454
 
      if(add_environment(getplugin(NULL), arg, true)){
455
 
        errno = 0;
456
 
      }
457
 
      break;
458
 
    case 'o':                   /* --options-for */
459
 
      {
460
 
        char *option_list = strchr(arg, ':');
461
 
        if(option_list == NULL){
462
 
          argp_error(state, "No colon in \"%s\"", arg);
463
 
          errno = EINVAL;
464
 
          break;
465
 
        }
466
 
        *option_list = '\0';
467
 
        option_list++;
468
 
        if(arg[0] == '\0'){
469
 
          argp_error(state, "Empty plugin name");
470
 
          errno = EINVAL;
471
 
          break;
472
 
        }
473
 
        char *option;
474
 
        while((option = strsep(&option_list, ",")) != NULL){
475
 
          if(not add_argument(getplugin(arg), option)){
476
 
            break;
477
 
          }
478
 
        }
479
 
        errno = 0;
480
 
      }
481
 
      break;
482
 
    case 'E':                   /* --env-for */
483
 
      {
484
 
        char *envdef = strchr(arg, ':');
485
 
        if(envdef == NULL){
486
 
          argp_error(state, "No colon in \"%s\"", arg);
487
 
          errno = EINVAL;
488
 
          break;
489
 
        }
490
 
        *envdef = '\0';
491
 
        envdef++;
492
 
        if(arg[0] == '\0'){
493
 
          argp_error(state, "Empty plugin name");
494
 
          errno = EINVAL;
495
 
          break;
496
 
        }
497
 
        if(add_environment(getplugin(arg), envdef, true)){
498
 
          errno = 0;
499
 
        }
500
 
      }
501
 
      break;
502
 
    case 'd':                   /* --disable */
503
 
      {
504
 
        plugin *p = getplugin(arg);
505
 
        if(p != NULL){
506
 
          p->disabled = true;
507
 
          errno = 0;
508
 
        }
509
 
      }
510
 
      break;
511
 
    case 'e':                   /* --enable */
512
 
      {
513
 
        plugin *p = getplugin(arg);
514
 
        if(p != NULL){
515
 
          p->disabled = false;
516
 
          errno = 0;
517
 
        }
518
 
      }
519
 
      break;
520
 
    case 128:                   /* --plugin-dir */
521
 
      free(plugindir);
522
 
      plugindir = strdup(arg);
523
 
      if(plugindir != NULL){
524
 
        errno = 0;
525
 
      }
526
 
      break;
527
 
    case 129:                   /* --config-file */
528
 
      /* This is already done by parse_opt_config_file() */
529
 
      break;
530
 
    case 130:                   /* --userid */
531
 
      tmp_id = strtoimax(arg, &tmp, 10);
532
 
      if(errno != 0 or tmp == arg or *tmp != '\0'
533
 
         or tmp_id != (uid_t)tmp_id){
534
 
        argp_error(state, "Bad user ID number: \"%s\", using %"
535
 
                   PRIdMAX, arg, (intmax_t)uid);
536
 
        break;
537
 
      }
538
 
      uid = (uid_t)tmp_id;
539
 
      errno = 0;
540
 
      break;
541
 
    case 131:                   /* --groupid */
542
 
      tmp_id = strtoimax(arg, &tmp, 10);
543
 
      if(errno != 0 or tmp == arg or *tmp != '\0'
544
 
         or tmp_id != (gid_t)tmp_id){
545
 
        argp_error(state, "Bad group ID number: \"%s\", using %"
546
 
                   PRIdMAX, arg, (intmax_t)gid);
547
 
        break;
548
 
      }
549
 
      gid = (gid_t)tmp_id;
550
 
      errno = 0;
551
 
      break;
552
 
    case 132:                   /* --debug */
 
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:
553
237
      debug = true;
554
238
      break;
555
 
    case 133:                   /* --plugin-helper-dir */
556
 
      free(pluginhelperdir);
557
 
      pluginhelperdir = strdup(arg);
558
 
      if(pluginhelperdir != NULL){
559
 
        errno = 0;
560
 
      }
561
 
      break;
562
 
      /*
563
 
       * These reproduce what we would get without ARGP_NO_HELP
564
 
       */
565
 
    case '?':                   /* --help */
566
 
      state->flags &= ~(unsigned int)ARGP_NO_EXIT; /* force exit */
567
 
      argp_state_help(state, state->out_stream, ARGP_HELP_STD_HELP);
568
 
    case -3:                    /* --usage */
569
 
      state->flags &= ~(unsigned int)ARGP_NO_EXIT; /* force exit */
570
 
      argp_state_help(state, state->out_stream,
571
 
                      ARGP_HELP_USAGE | ARGP_HELP_EXIT_OK);
572
 
    case 'V':                   /* --version */
573
 
      fprintf(state->out_stream, "%s\n", argp_program_version);
574
 
      exit(EXIT_SUCCESS);
575
 
      break;
576
 
/*
577
 
 * When adding more options before this line, remember to also add a
578
 
 * "case" to the "parse_opt_config_file" function below.
579
 
 */
580
 
    case ARGP_KEY_ARG:
581
 
      /* Cryptsetup always passes an argument, which is an empty
582
 
         string if "none" was specified in /etc/crypttab.  So if
583
 
         argument was empty, we ignore it silently. */
584
 
      if(arg[0] == '\0'){
585
 
        break;
586
 
      }
587
 
    default:
588
 
      return ARGP_ERR_UNKNOWN;
589
 
    }
590
 
    return errno;               /* Set to 0 at start */
591
 
  }
592
 
  
593
 
  /* This option parser is the same as parse_opt() above, except it
594
 
     ignores everything but the --config-file option. */
595
 
  error_t parse_opt_config_file(int key, char *arg,
596
 
                                __attribute__((unused))
597
 
                                struct argp_state *state){
598
 
    errno = 0;
599
 
    switch(key){
600
 
    case 'g':                   /* --global-options */
601
 
    case 'G':                   /* --global-env */
602
 
    case 'o':                   /* --options-for */
603
 
    case 'E':                   /* --env-for */
604
 
    case 'd':                   /* --disable */
605
 
    case 'e':                   /* --enable */
606
 
    case 128:                   /* --plugin-dir */
607
 
      break;
608
 
    case 129:                   /* --config-file */
609
 
      free(argfile);
610
 
      argfile = strdup(arg);
611
 
      if(argfile != NULL){
612
 
        errno = 0;
613
 
      }
614
 
      break;
615
 
    case 130:                   /* --userid */
616
 
    case 131:                   /* --groupid */
617
 
    case 132:                   /* --debug */
618
 
    case 133:                   /* --plugin-helper-dir */
619
 
    case '?':                   /* --help */
620
 
    case -3:                    /* --usage */
621
 
    case 'V':                   /* --version */
622
 
    case ARGP_KEY_ARG:
623
 
      break;
624
 
    default:
625
 
      return ARGP_ERR_UNKNOWN;
626
 
    }
627
 
    return errno;
628
 
  }
629
 
  
630
 
  struct argp argp = { .options = options,
631
 
                       .parser = parse_opt_config_file,
 
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,
632
253
                       .args_doc = "",
633
254
                       .doc = "Mandos plugin runner -- Run plugins" };
634
255
  
635
 
  /* Parse using parse_opt_config_file() in order to get the custom
636
 
     config file location, if any. */
637
 
  ret = argp_parse(&argp, argc, argv,
638
 
                   ARGP_IN_ORDER | ARGP_NO_EXIT | ARGP_NO_HELP,
639
 
                   NULL, NULL);
640
 
  switch(ret){
641
 
  case 0:
642
 
    break;
643
 
  case ENOMEM:
644
 
  default:
645
 
    errno = ret;
646
 
    error(0, errno, "argp_parse");
647
 
    exitstatus = EX_OSERR;
648
 
    goto fallback;
649
 
  case EINVAL:
650
 
    exitstatus = EX_USAGE;
651
 
    goto fallback;
652
 
  }
653
 
  
654
 
  /* Reset to the normal argument parser */
655
 
  argp.parser = parse_opt;
656
 
  
657
 
  /* Open the configfile if available */
658
 
  if(argfile == NULL){
659
 
    conffp = fopen(AFILE, "r");
660
 
  } else {
661
 
    conffp = fopen(argfile, "r");
662
 
  }
663
 
  if(conffp != NULL){
664
 
    char *org_line = NULL;
665
 
    char *p, *arg, *new_arg, *line;
666
 
    size_t size = 0;
667
 
    const char whitespace_delims[] = " \r\t\f\v\n";
668
 
    const char comment_delim[] = "#";
669
 
    
670
 
    custom_argc = 1;
671
 
    custom_argv = malloc(sizeof(char*) * 2);
672
 
    if(custom_argv == NULL){
673
 
      error(0, errno, "malloc");
674
 
      exitstatus = EX_OSERR;
675
 
      goto fallback;
676
 
    }
677
 
    custom_argv[0] = argv[0];
678
 
    custom_argv[1] = NULL;
679
 
    
680
 
    /* for each line in the config file, strip whitespace and ignore
681
 
       commented text */
682
 
    while(true){
683
 
      sret = getline(&org_line, &size, conffp);
684
 
      if(sret == -1){
685
 
        break;
686
 
      }
687
 
      
688
 
      line = org_line;
689
 
      arg = strsep(&line, comment_delim);
690
 
      while((p = strsep(&arg, whitespace_delims)) != NULL){
691
 
        if(p[0] == '\0'){
692
 
          continue;
693
 
        }
694
 
        new_arg = strdup(p);
695
 
        if(new_arg == NULL){
696
 
          error(0, errno, "strdup");
697
 
          exitstatus = EX_OSERR;
698
 
          free(org_line);
699
 
          goto fallback;
700
 
        }
701
 
        
702
 
        custom_argc += 1;
703
 
        {
704
 
          char **new_argv = realloc(custom_argv, sizeof(char *)
705
 
                                    * ((size_t)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
 
  }
 
256
  argp_parse (&argp, argc, argv, 0, 0, &plugin_list);
795
257
  
796
258
  if(debug){
797
 
    for(plugin *p = plugin_list; p != NULL; p = p->next){
 
259
    for(plugin *p = plugin_list; p != NULL; p=p->next){
798
260
      fprintf(stderr, "Plugin: %s has %d arguments\n",
799
261
              p->name ? p->name : "Global", p->argc - 1);
800
262
      for(char **a = p->argv; *a != NULL; a++){
801
263
        fprintf(stderr, "\tArg: %s\n", *a);
802
264
      }
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
 
       <https://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 */
 
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 */
845
276
  {
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;
 
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;
890
284
      }
891
285
    }
892
 
    return 1;
893
 
  }
894
 
  
895
 
  int numplugins = scandirat(dir_fd, ".", &direntries, good_name,
896
 
                             alphasort);
897
 
  if(numplugins == -1){
898
 
    error(0, errno, "Could not scan plugin dir");
899
 
    direntries = NULL;
900
 
    exitstatus = EX_OSERR;
901
 
    goto fallback;
902
286
  }
903
287
  
904
288
  FD_ZERO(&rfds_all);
905
289
  
906
 
  /* Read and execute any executable in the plugin directory*/
907
 
  for(int i = 0; i < numplugins; i++){
908
 
    
909
 
    int plugin_fd = openat(dir_fd, direntries[i]->d_name, O_RDONLY);
910
 
    if(plugin_fd == -1){
911
 
      error(0, errno, "Could not open plugin");
912
 
      free(direntries[i]);
913
 
      continue;
914
 
    }
915
 
    ret = (int)TEMP_FAILURE_RETRY(fstat(plugin_fd, &st));
916
 
    if(ret == -1){
917
 
      error(0, errno, "stat");
918
 
      close(plugin_fd);
919
 
      free(direntries[i]);
920
 
      continue;
921
 
    }
922
 
    
923
 
    /* Ignore non-executable files */
924
 
    if(not S_ISREG(st.st_mode)
925
 
       or (TEMP_FAILURE_RETRY(faccessat(dir_fd, direntries[i]->d_name,
926
 
                                        X_OK, 0)) != 0)){
 
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)){
927
358
      if(debug){
928
 
        fprintf(stderr, "Ignoring plugin dir entry \"%s/%s\""
929
 
                " with bad type or mode\n",
930
 
                plugindir != NULL ? plugindir : PDIR,
931
 
                direntries[i]->d_name);
 
359
        fprintf(stderr, "Ignoring plugin dir entry \"%s\""
 
360
                " with bad type or mode\n", filename);
932
361
      }
933
 
      close(plugin_fd);
934
 
      free(direntries[i]);
935
 
      continue;
936
 
    }
937
 
    
938
 
    plugin *p = getplugin(direntries[i]->d_name);
939
 
    if(p == NULL){
940
 
      error(0, errno, "getplugin");
941
 
      close(plugin_fd);
942
 
      free(direntries[i]);
943
 
      continue;
944
 
    }
945
 
    if(p->disabled){
 
362
      free(filename);
 
363
      continue;
 
364
    }
 
365
    if(getplugin(dirst->d_name, &plugin_list)->disabled){
946
366
      if(debug){
947
367
        fprintf(stderr, "Ignoring disabled plugin \"%s\"\n",
948
 
                direntries[i]->d_name);
 
368
                dirst->d_name);
949
369
      }
950
 
      close(plugin_fd);
951
 
      free(direntries[i]);
 
370
      free(filename);
952
371
      continue;
953
372
    }
 
373
    plugin *p = getplugin(dirst->d_name, &plugin_list);
954
374
    {
955
375
      /* Add global arguments to argument list for this plugin */
956
 
      plugin *g = getplugin(NULL);
957
 
      if(g != NULL){
958
 
        for(char **a = g->argv + 1; *a != NULL; a++){
959
 
          if(not add_argument(p, *a)){
960
 
            error(0, errno, "add_argument");
961
 
          }
962
 
        }
963
 
        /* Add global environment variables */
964
 
        for(char **e = g->environ; *e != NULL; e++){
965
 
          if(not add_environment(p, *e, false)){
966
 
            error(0, errno, "add_environment");
967
 
          }
968
 
        }
969
 
      }
970
 
    }
971
 
    /* If this plugin has any environment variables, we need to
972
 
       duplicate the environment from this process, too. */
973
 
    if(p->environ[0] != NULL){
974
 
      for(char **e = environ; *e != NULL; e++){
975
 
        if(not add_environment(p, *e, false)){
976
 
          error(0, errno, "add_environment");
977
 
        }
978
 
      }
979
 
    }
980
 
    
981
 
    int pipefd[2];
982
 
#ifndef O_CLOEXEC
983
 
    ret = (int)TEMP_FAILURE_RETRY(pipe(pipefd));
984
 
#else  /* O_CLOEXEC */
985
 
    ret = (int)TEMP_FAILURE_RETRY(pipe2(pipefd, O_CLOEXEC));
986
 
#endif  /* O_CLOEXEC */
987
 
    if(ret == -1){
988
 
      error(0, errno, "pipe");
989
 
      exitstatus = EX_OSERR;
990
 
      free(direntries[i]);
991
 
      goto fallback;
992
 
    }
993
 
    if(pipefd[0] >= FD_SETSIZE){
994
 
      fprintf(stderr, "pipe()[0] (%d) >= FD_SETSIZE (%d)", pipefd[0],
995
 
              FD_SETSIZE);
996
 
      close(pipefd[0]);
997
 
      close(pipefd[1]);
998
 
      exitstatus = EX_OSERR;
999
 
      free(direntries[i]);
1000
 
      goto fallback;
1001
 
    }
1002
 
#ifndef O_CLOEXEC
1003
 
    /* Ask OS to automatic close the pipe on exec */
 
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
    }
1004
388
    ret = set_cloexec_flag(pipefd[0]);
1005
389
    if(ret < 0){
1006
 
      error(0, errno, "set_cloexec_flag");
1007
 
      close(pipefd[0]);
1008
 
      close(pipefd[1]);
1009
 
      exitstatus = EX_OSERR;
1010
 
      free(direntries[i]);
1011
 
      goto fallback;
 
390
      perror("set_cloexec_flag");
 
391
      exitstatus = EXIT_FAILURE;
 
392
      goto end;
1012
393
    }
1013
394
    ret = set_cloexec_flag(pipefd[1]);
1014
395
    if(ret < 0){
1015
 
      error(0, errno, "set_cloexec_flag");
1016
 
      close(pipefd[0]);
1017
 
      close(pipefd[1]);
1018
 
      exitstatus = EX_OSERR;
1019
 
      free(direntries[i]);
1020
 
      goto fallback;
 
396
      perror("set_cloexec_flag");
 
397
      exitstatus = EXIT_FAILURE;
 
398
      goto end;
1021
399
    }
1022
 
#endif  /* not O_CLOEXEC */
1023
400
    /* Block SIGCHLD until process is safely in process list */
1024
 
    ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_BLOCK,
1025
 
                                              &sigchld_action.sa_mask,
1026
 
                                              NULL));
 
401
    ret = sigprocmask (SIG_BLOCK, &sigchld_action.sa_mask, NULL);
1027
402
    if(ret < 0){
1028
 
      error(0, errno, "sigprocmask");
1029
 
      exitstatus = EX_OSERR;
1030
 
      free(direntries[i]);
1031
 
      goto fallback;
1032
 
    }
1033
 
    /* Starting a new process to be watched */
1034
 
    pid_t pid;
1035
 
    do {
1036
 
      pid = fork();
1037
 
    } while(pid == -1 and errno == EINTR);
1038
 
    if(pid == -1){
1039
 
      error(0, errno, "fork");
1040
 
      TEMP_FAILURE_RETRY(sigprocmask(SIG_UNBLOCK,
1041
 
                                     &sigchld_action.sa_mask, NULL));
1042
 
      close(pipefd[0]);
1043
 
      close(pipefd[1]);
1044
 
      exitstatus = EX_OSERR;
1045
 
      free(direntries[i]);
1046
 
      goto fallback;
1047
 
    }
 
403
      perror("sigprocmask");
 
404
      exitstatus = EXIT_FAILURE;
 
405
      goto end;
 
406
    }
 
407
    // Starting a new process to be watched
 
408
    pid_t pid = fork();
1048
409
    if(pid == 0){
1049
410
      /* this is the child process */
1050
411
      ret = sigaction(SIGCHLD, &old_sigchld_action, NULL);
1051
412
      if(ret < 0){
1052
 
        error(0, errno, "sigaction");
1053
 
        _exit(EX_OSERR);
 
413
        perror("sigaction");
 
414
        _exit(EXIT_FAILURE);
1054
415
      }
1055
 
      ret = sigprocmask(SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
 
416
      ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
1056
417
      if(ret < 0){
1057
 
        error(0, errno, "sigprocmask");
1058
 
        _exit(EX_OSERR);
1059
 
      }
1060
 
      
1061
 
      ret = dup2(pipefd[1], STDOUT_FILENO); /* replace our stdout */
1062
 
      if(ret == -1){
1063
 
        error(0, errno, "dup2");
1064
 
        _exit(EX_OSERR);
1065
 
      }
1066
 
      
1067
 
      if(fexecve(plugin_fd, p->argv,
1068
 
                (p->environ[0] != NULL) ? p->environ : environ) < 0){
1069
 
        error(0, errno, "fexecve for %s/%s",
1070
 
              plugindir != NULL ? plugindir : PDIR,
1071
 
              direntries[i]->d_name);
1072
 
        _exit(EX_OSERR);
 
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);
1073
431
      }
1074
432
      /* no return */
1075
433
    }
1076
 
    /* Parent process */
1077
 
    close(pipefd[1]);           /* Close unused write end of pipe */
1078
 
    close(plugin_fd);
1079
 
    plugin *new_plugin = getplugin(direntries[i]->d_name);
1080
 
    if(new_plugin == NULL){
1081
 
      error(0, errno, "getplugin");
1082
 
      ret = (int)(TEMP_FAILURE_RETRY
1083
 
                  (sigprocmask(SIG_UNBLOCK, &sigchld_action.sa_mask,
1084
 
                               NULL)));
 
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);
1085
441
      if(ret < 0){
1086
 
        error(0, errno, "sigprocmask");
 
442
        perror("sigprocmask");
1087
443
      }
1088
 
      exitstatus = EX_OSERR;
1089
 
      free(direntries[i]);
1090
 
      goto fallback;
 
444
      exitstatus = EXIT_FAILURE;
 
445
      goto end;
1091
446
    }
1092
 
    free(direntries[i]);
1093
 
    
1094
 
    new_plugin->pid = pid;
1095
 
    new_plugin->fd = pipefd[0];
1096
 
    
 
447
    
 
448
    *new_process = (struct process){ .pid = pid,
 
449
                                     .fd = pipefd[0],
 
450
                                     .next = process_list };
 
451
    // List handling
 
452
    process_list = new_process;
1097
453
    /* Unblock SIGCHLD so signal handler can be run if this process
1098
454
       has already completed */
1099
 
    ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_UNBLOCK,
1100
 
                                              &sigchld_action.sa_mask,
1101
 
                                              NULL));
 
455
    ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
1102
456
    if(ret < 0){
1103
 
      error(0, errno, "sigprocmask");
1104
 
      exitstatus = EX_OSERR;
1105
 
      goto fallback;
1106
 
    }
1107
 
    
1108
 
    FD_SET(new_plugin->fd, &rfds_all);
1109
 
    
1110
 
    if(maxfd < new_plugin->fd){
1111
 
      maxfd = new_plugin->fd;
1112
 
    }
1113
 
  }
1114
 
  
1115
 
  free(direntries);
1116
 
  direntries = NULL;
1117
 
  close(dir_fd);
1118
 
  dir_fd = -1;
1119
 
  free_plugin(getplugin(NULL));
1120
 
  
1121
 
  for(plugin *p = plugin_list; p != NULL; p = p->next){
1122
 
    if(p->pid != 0){
1123
 
      break;
1124
 
    }
1125
 
    if(p->next == NULL){
1126
 
      fprintf(stderr, "No plugin processes started. Incorrect plugin"
1127
 
              " directory?\n");
1128
 
      free_plugin_list();
1129
 
    }
1130
 
  }
1131
 
  
1132
 
  /* Main loop while running plugins exist */
1133
 
  while(plugin_list){
 
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){
1134
486
    fd_set rfds = rfds_all;
1135
487
    int select_ret = select(maxfd+1, &rfds, NULL, NULL, NULL);
1136
 
    if(select_ret == -1 and errno != EINTR){
1137
 
      error(0, errno, "select");
1138
 
      exitstatus = EX_OSERR;
1139
 
      goto fallback;
 
488
    if (select_ret == -1){
 
489
      perror("select");
 
490
      exitstatus = EXIT_FAILURE;
 
491
      goto end;
1140
492
    }
1141
493
    /* OK, now either a process completed, or something can be read
1142
494
       from one of them */
1143
 
    for(plugin *proc = plugin_list; proc != NULL;){
 
495
    for(process *proc = process_list; proc ; proc = proc->next){
1144
496
      /* Is this process completely done? */
1145
 
      if(proc->completed and proc->eof){
 
497
      if(proc->eof and proc->completed){
1146
498
        /* Only accept the plugin output if it exited cleanly */
1147
499
        if(not WIFEXITED(proc->status)
1148
500
           or WEXITSTATUS(proc->status) != 0){
1149
501
          /* Bad exit by plugin */
1150
 
          
1151
502
          if(debug){
1152
503
            if(WIFEXITED(proc->status)){
1153
 
              fprintf(stderr, "Plugin %s [%" PRIdMAX "] exited with"
1154
 
                      " status %d\n", proc->name,
1155
 
                      (intmax_t) (proc->pid),
1156
 
                      WEXITSTATUS(proc->status));
1157
 
            } else if(WIFSIGNALED(proc->status)){
1158
 
              fprintf(stderr, "Plugin %s [%" PRIdMAX "] killed by"
1159
 
                      " signal %d: %s\n", proc->name,
1160
 
                      (intmax_t) (proc->pid),
1161
 
                      WTERMSIG(proc->status),
1162
 
                      strsignal(WTERMSIG(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));
 
509
            } else if(WCOREDUMP(proc->status)){
 
510
              fprintf(stderr, "Plugin %d dumped core\n", proc->pid);
1163
511
            }
1164
512
          }
1165
 
          
1166
513
          /* Remove the plugin */
1167
514
          FD_CLR(proc->fd, &rfds_all);
1168
 
          
1169
515
          /* Block signal while modifying process_list */
1170
 
          ret = (int)TEMP_FAILURE_RETRY(sigprocmask
1171
 
                                        (SIG_BLOCK,
1172
 
                                         &sigchld_action.sa_mask,
1173
 
                                         NULL));
 
516
          ret = sigprocmask (SIG_BLOCK, &sigchld_action.sa_mask, NULL);
1174
517
          if(ret < 0){
1175
 
            error(0, errno, "sigprocmask");
1176
 
            exitstatus = EX_OSERR;
1177
 
            goto fallback;
1178
 
          }
1179
 
          
1180
 
          plugin *next_plugin = proc->next;
1181
 
          free_plugin(proc);
1182
 
          proc = next_plugin;
1183
 
          
 
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
          }
1184
535
          /* We are done modifying process list, so unblock signal */
1185
 
          ret = (int)(TEMP_FAILURE_RETRY
1186
 
                      (sigprocmask(SIG_UNBLOCK,
1187
 
                                   &sigchld_action.sa_mask, NULL)));
 
536
          ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask,
 
537
                             NULL);
1188
538
          if(ret < 0){
1189
 
            error(0, errno, "sigprocmask");
1190
 
            exitstatus = EX_OSERR;
1191
 
            goto fallback;
1192
 
          }
1193
 
          
1194
 
          if(plugin_list == NULL){
1195
 
            break;
1196
 
          }
1197
 
          
1198
 
          continue;
 
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;
1199
547
        }
1200
 
        
1201
548
        /* This process exited nicely, so print its buffer */
1202
 
        
1203
 
        bool bret = print_out_password(proc->buffer,
1204
 
                                       proc->buffer_length);
1205
 
        if(not bret){
1206
 
          error(0, errno, "print_out_password");
1207
 
          exitstatus = EX_IOERR;
 
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
          }
1208
560
        }
1209
 
        goto fallback;
 
561
        goto end;
1210
562
      }
1211
 
      
1212
563
      /* This process has not completed.  Does it have any output? */
1213
564
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){
1214
565
        /* This process had nothing to say at this time */
1215
 
        proc = proc->next;
1216
566
        continue;
1217
567
      }
1218
568
      /* Before reading, make the process' data buffer large enough */
1219
569
      if(proc->buffer_length + BUFFER_SIZE > proc->buffer_size){
1220
 
        char *new_buffer = realloc(proc->buffer, proc->buffer_size
1221
 
                                   + (size_t) BUFFER_SIZE);
1222
 
        if(new_buffer == NULL){
1223
 
          error(0, errno, "malloc");
1224
 
          exitstatus = EX_OSERR;
1225
 
          goto fallback;
 
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;
1226
576
        }
1227
 
        proc->buffer = new_buffer;
1228
577
        proc->buffer_size += BUFFER_SIZE;
1229
578
      }
1230
579
      /* Read from the process */
1231
 
      sret = TEMP_FAILURE_RETRY(read(proc->fd,
1232
 
                                     proc->buffer
1233
 
                                     + proc->buffer_length,
1234
 
                                     BUFFER_SIZE));
1235
 
      if(sret < 0){
 
580
      ret = read(proc->fd, proc->buffer + proc->buffer_length,
 
581
                 BUFFER_SIZE);
 
582
      if(ret < 0){
1236
583
        /* Read error from this process; ignore the error */
1237
 
        proc = proc->next;
1238
584
        continue;
1239
585
      }
1240
 
      if(sret == 0){
 
586
      if(ret == 0){
1241
587
        /* got EOF */
1242
588
        proc->eof = true;
1243
589
      } else {
1244
 
        proc->buffer_length += (size_t) sret;
 
590
        proc->buffer_length += (size_t) ret;
1245
591
      }
1246
592
    }
1247
593
  }
1248
 
  
1249
 
  
1250
 
 fallback:
1251
 
  
1252
 
  if(plugin_list == NULL or (exitstatus != EXIT_SUCCESS
1253
 
                             and exitstatus != EX_OK)){
1254
 
    /* Fallback if all plugins failed, none are found or an error
1255
 
       occured */
1256
 
    bool bret;
1257
 
    fprintf(stderr, "Going to fallback mode using getpass(3)\n");
1258
 
    char *passwordbuffer = getpass("Password: ");
1259
 
    size_t len = strlen(passwordbuffer);
1260
 
    /* Strip trailing newline */
1261
 
    if(len > 0 and passwordbuffer[len-1] == '\n'){
1262
 
      passwordbuffer[len-1] = '\0'; /* not strictly necessary */
1263
 
      len--;
1264
 
    }
1265
 
    bret = print_out_password(passwordbuffer, len);
1266
 
    if(not bret){
1267
 
      error(0, errno, "print_out_password");
1268
 
      exitstatus = EX_IOERR;
1269
 
    }
 
594
  if(process_list == NULL){
 
595
    fprintf(stderr, "All plugin processes failed, exiting\n");
 
596
    exitstatus = EXIT_FAILURE;
1270
597
  }
1271
598
  
 
599
 end:
1272
600
  /* Restore old signal handler */
1273
 
  ret = sigaction(SIGCHLD, &old_sigchld_action, NULL);
1274
 
  if(ret == -1){
1275
 
    error(0, errno, "sigaction");
1276
 
    exitstatus = EX_OSERR;
1277
 
  }
1278
 
  
1279
 
  if(custom_argv != NULL){
1280
 
    for(char **arg = custom_argv+1; *arg != NULL; arg++){
1281
 
      free(*arg);
1282
 
    }
1283
 
    free(custom_argv);
1284
 
  }
1285
 
  
1286
 
  free(direntries);
1287
 
  
1288
 
  if(dir_fd != -1){
1289
 
    close(dir_fd);
1290
 
  }
1291
 
  
1292
 
  /* Kill the processes */
1293
 
  for(plugin *p = plugin_list; p != NULL; p = p->next){
1294
 
    if(p->pid != 0){
1295
 
      close(p->fd);
1296
 
      ret = kill(p->pid, SIGTERM);
1297
 
      if(ret == -1 and errno != ESRCH){
1298
 
        /* Set-uid proccesses might not get closed */
1299
 
        error(0, errno, "kill");
1300
 
      }
1301
 
    }
 
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);
1302
621
  }
1303
622
  
1304
623
  /* Wait for any remaining child processes to terminate */
1305
 
  do {
 
624
  do{
1306
625
    ret = wait(NULL);
1307
626
  } while(ret >= 0);
1308
627
  if(errno != ECHILD){
1309
 
    error(0, errno, "wait");
 
628
    perror("wait");
1310
629
  }
1311
630
  
1312
 
  free_plugin_list();
1313
 
  
1314
 
  free(plugindir);
1315
 
  free(pluginhelperdir);
1316
 
  free(argfile);
1317
 
  
1318
631
  return exitstatus;
1319
632
}