/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: 2008-12-10 01:26:02 UTC
  • mfrom: (237.1.2 mandos)
  • Revision ID: teddy@fukt.bsnet.se-20081210012602-vhz3h75xkj24t340
First version of a somewhat complete D-Bus server interface.  Also
change user/group name to "_mandos".

* debian/mandos.postinst: Rename old "mandos" user and group to
                          "_mandos"; create "_mandos" user and group
                          if none exist.
* debian/mandos-client.postinst: - '' -

* initramfs-tools-hook: Try "_mandos" before "mandos" as user and
                        group name.

* mandos (_datetime_to_dbus_struct): New; was previously local.
  (Client.started): Renamed to "last_started".  All users changed.
  (Client.started): New; boolean.
  (Client.dbus_object_path): New.
  (Client.check_command): Renamed to "checker_command".  All users
                          changed.
  (Client.__init__): Set and use "self.dbus_object_path".  Set
                     "self.started".
  (Client.start): Update "self.started".  Emit "self.PropertyChanged"
                  signals for both "started" and "last_started".
  (Client.stop): Update "self.started".  Emit "self.PropertyChanged"
                 signal for "started".
  (Client.checker_callback): Take additional "command" argument.  All
                             callers changed. Emit
                             "self.PropertyChanged" signal.
  (Client.bump_timeout): Emit "self.PropertyChanged" signal for
                         "last_checked_ok".
  (Client.start_checker): Emit "self.PropertyChanged" signal for
                          "checker_running".
  (Client.stop_checker): Emit "self.PropertyChanged" signal for
                         "checker_running".
  (Client.still_valid): Bug fix: use "getattr(self, started, False)"
                        instead of "self.started" in case this client
                        object is so new that the "started" attribute
                        has not been created yet.
  (Client.IntervalChanged, Client.CheckerIsRunning, Client.GetChecker,
  Client.GetCreated, Client.GetFingerprint, Client.GetHost,
  Client.GetInterval, Client.GetName, Client.GetStarted,
  Client.GetTimeout, Client.StateChanged, Client.TimeoutChanged):
  Removed; all callers changed.
  (Client.CheckerCompleted): Add "condition" and "command" arguments.
                             All callers changed.
  (Client.GetAllProperties, Client.PropertyChanged): New.
  (Client.StillValid): Renamed to "IsStillValid".
  (Client.StartChecker): Changed to its own function to avoid the
                         return value from "Client.start_checker()".
  (Client.Stop): Changed to its own function to avoid the return value
                 from "Client.stop()".
  (main): Try "_mandos" before "mandos" as user and group name.
          Removed inner function "remove_from_clients".  New inner
          class "MandosServer".

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