/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-08-16 03:29:08 UTC
  • Revision ID: teddy@fukt.bsnet.se-20080816032908-ihw7c05r2mnyk389
Add feature to specify custom environment variables for plugins.

* plugin-runner.c (plugin): New members "environ" and "envc" to
                            contain possible custom environment.
  (getplugin): Return NULL on failure instead of doing exit(); all
               callers changed.
  (add_to_char_array): New helper function for "add_argument" and
                       "add_environment".
  (addargument): Renamed to "add_argument".  Return bool.  Call
                 "add_to_char_array" to actually do things.
  (add_environment): New; analogous to "add_argument".
  (addcustomargument): Renamed to "add_to_argv" to avoid confusion
                       with "add_argument".
  (main): New options "--global-envs" and "--envs-for" to specify
          custom environment for plugins.  Print environment for
          plugins in debug mode.  Use asprintf instead of strcpy and
          strcat.  Use execve() for plugins with custom environments.
          Free environment for plugin when freeing plugin list.

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