/mandos/trunk

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

« back to all changes in this revision

Viewing changes to plugin-runner.c

  • Committer: Teddy Hogeborn
  • Date: 2019-07-29 16:35:53 UTC
  • Revision ID: teddy@recompile.se-20190729163553-1i442i2cbx64c537
Make tests and man page examples match

Make the tests test_manual_page_example[1-5] match exactly what is
written in the manual page, and add comments to manual page as
reminders to keep tests and manual page examples in sync.

* mandos-ctl (Test_commands_from_options.test_manual_page_example_1):
  Remove "--verbose" option, since the manual does not have it as the
  first example, and change assertion to match.
* mandos-ctl.xml (EXAMPLE): Add comments to all examples documenting
  which test function they correspond to.  Also remove unnecessary
  quotes from option arguments in fourth example, and clarify language
  slightly in fifth example.

Show diffs side-by-side

added added

removed removed

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