/mandos/release

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

« back to all changes in this revision

Viewing changes to plugbasedclient.c

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

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

Show diffs side-by-side

added added

removed removed

Lines of Context:
2
2
/*
3
3
 * Mandos plugin runner - Run Mandos plugins
4
4
 *
5
 
 * Copyright © 2008,2009 Teddy Hogeborn
6
 
 * Copyright © 2008,2009 Björn Påhlsson
 
5
 * Copyright © 2007-2008 Teddy Hogeborn & Björn Påhlsson
7
6
 * 
8
7
 * This program is free software: you can redistribute it and/or
9
8
 * modify it under the terms of the GNU General Public License as
22
21
 * Contact the authors at <mandos@fukt.bsnet.se>.
23
22
 */
24
23
 
25
 
#define _GNU_SOURCE             /* TEMP_FAILURE_RETRY(), getline(),
26
 
                                   asprintf() */
27
 
#include <stddef.h>             /* size_t, NULL */
28
 
#include <stdlib.h>             /* malloc(), exit(), EXIT_FAILURE,
29
 
                                   EXIT_SUCCESS, realloc() */
30
 
#include <stdbool.h>            /* bool, true, false */
31
 
#include <stdio.h>              /* perror, fileno(), fprintf(),
 
24
#define _GNU_SOURCE             /* TEMP_FAILURE_RETRY() */
 
25
 
 
26
#include <stdio.h>              /* popen(), fileno(), fprintf(),
32
27
                                   stderr, STDOUT_FILENO */
33
 
#include <sys/types.h>          /* DIR, opendir(), stat(), struct
34
 
                                   stat, waitpid(), WIFEXITED(),
35
 
                                   WEXITSTATUS(), wait(), pid_t,
36
 
                                   uid_t, gid_t, getuid(), getgid(),
37
 
                                   dirfd() */
38
 
#include <sys/select.h>         /* fd_set, select(), FD_ZERO(),
39
 
                                   FD_SET(), FD_ISSET(), FD_CLR */
40
 
#include <sys/wait.h>           /* wait(), waitpid(), WIFEXITED(),
41
 
                                   WEXITSTATUS() */
42
 
#include <sys/stat.h>           /* struct stat, stat(), S_ISREG() */
43
28
#include <iso646.h>             /* and, or, not */
 
29
#include <sys/types.h>          /* DIR, opendir(), stat(),
 
30
                                   struct stat, waitpid(),
 
31
                                   WIFEXITED(), WEXITSTATUS(),
 
32
                                   wait() */
 
33
#include <sys/wait.h>           /* wait() */
44
34
#include <dirent.h>             /* DIR, struct dirent, opendir(),
45
 
                                   readdir(), closedir(), dirfd() */
 
35
                                   readdir(), closedir() */
 
36
#include <sys/stat.h>           /* struct stat, stat(), S_ISREG() */
46
37
#include <unistd.h>             /* struct stat, stat(), S_ISREG(),
47
 
                                   fcntl(), setuid(), setgid(),
48
 
                                   F_GETFD, F_SETFD, FD_CLOEXEC,
49
 
                                   access(), pipe(), fork(), close()
50
 
                                   dup2(), STDOUT_FILENO, _exit(),
51
 
                                   execv(), write(), read(),
52
 
                                   close() */
53
 
#include <fcntl.h>              /* fcntl(), F_GETFD, F_SETFD,
54
 
                                   FD_CLOEXEC */
55
 
#include <string.h>             /* strsep, strlen(), asprintf() */
 
38
                                   fcntl() */
 
39
#include <fcntl.h>              /* fcntl() */
 
40
#include <stddef.h>             /* NULL */
 
41
#include <stdlib.h>             /* EXIT_FAILURE */
 
42
#include <sys/select.h>         /* fd_set, select(), FD_ZERO(),
 
43
                                   FD_SET(), FD_ISSET() */
 
44
#include <string.h>             /* strlen(), strcpy(), strcat() */
 
45
#include <stdbool.h>            /* true */
 
46
#include <sys/wait.h>           /* waitpid(), WIFEXITED(),
 
47
                                   WEXITSTATUS() */
56
48
#include <errno.h>              /* errno */
57
 
#include <argp.h>               /* struct argp_option, struct
58
 
                                   argp_state, struct argp,
59
 
                                   argp_parse(), ARGP_ERR_UNKNOWN,
60
 
                                   ARGP_KEY_END, ARGP_KEY_ARG,
61
 
                                   error_t */
62
 
#include <signal.h>             /* struct sigaction, sigemptyset(),
63
 
                                   sigaddset(), sigaction(),
64
 
                                   sigprocmask(), SIG_BLOCK, SIGCHLD,
65
 
                                   SIG_UNBLOCK, kill() */
66
 
#include <errno.h>              /* errno, EBADF */
67
 
#include <inttypes.h>           /* intmax_t, SCNdMAX, PRIdMAX,  */
68
 
 
69
 
#define BUFFER_SIZE 256
70
 
 
71
 
#define PDIR "/lib/mandos/plugins.d"
72
 
#define AFILE "/conf/conf.d/mandos/plugin-runner.conf"
73
 
 
74
 
const char *argp_program_version = "plugin-runner " VERSION;
75
 
const char *argp_program_bug_address = "<mandos@fukt.bsnet.se>";
76
 
 
77
 
typedef struct plugin{
78
 
  char *name;                   /* can be NULL or any plugin name */
79
 
  char **argv;
80
 
  int argc;
81
 
  char **environ;
82
 
  int envc;
83
 
  bool disabled;
84
 
 
85
 
  /* Variables used for running processes*/
 
49
#include <argp.h>               /* struct argp_option,
 
50
                                   struct argp_state, struct argp,
 
51
                                   argp_parse() */
 
52
 
 
53
struct process;
 
54
 
 
55
typedef struct process{
86
56
  pid_t pid;
87
57
  int fd;
88
58
  char *buffer;
89
59
  size_t buffer_size;
90
60
  size_t buffer_length;
91
61
  bool eof;
92
 
  volatile sig_atomic_t completed;
 
62
  bool completed;
93
63
  int status;
 
64
  struct process *next;
 
65
} process;
 
66
 
 
67
typedef struct plugin{
 
68
  char *name;                   /* can be NULL or any plugin name */
 
69
  char **argv;
 
70
  int argc;
 
71
  bool disabled;
94
72
  struct plugin *next;
95
73
} plugin;
96
74
 
97
 
static plugin *plugin_list = NULL;
98
 
 
99
 
/* Gets an existing plugin based on name,
100
 
   or if none is found, creates a new one */
101
 
static plugin *getplugin(char *name){
102
 
  /* Check for exiting plugin with that name */
103
 
  for(plugin *p = plugin_list; p != NULL; p = p->next){
104
 
    if((p->name == name)
105
 
       or (p->name and name and (strcmp(p->name, name) == 0))){
 
75
plugin *getplugin(char *name, plugin **plugin_list){
 
76
  for (plugin *p = *plugin_list; p != NULL; p = p->next){
 
77
    if ((p->name == name)
 
78
        or (p->name and name and (strcmp(p->name, name) == 0))){
106
79
      return p;
107
80
    }
108
81
  }
109
82
  /* Create a new plugin */
110
83
  plugin *new_plugin = malloc(sizeof(plugin));
111
 
  if(new_plugin == NULL){
112
 
    return NULL;
113
 
  }
114
 
  char *copy_name = NULL;
115
 
  if(name != NULL){
116
 
    copy_name = strdup(name);
117
 
    if(copy_name == NULL){
118
 
      free(new_plugin);
119
 
      return NULL;
120
 
    }
121
 
  }
122
 
  
123
 
  *new_plugin = (plugin){ .name = copy_name,
124
 
                          .argc = 1,
125
 
                          .disabled = false,
126
 
                          .next = plugin_list };
127
 
  
 
84
  if (new_plugin == NULL){
 
85
    perror("malloc");
 
86
    exit(EXIT_FAILURE);
 
87
  }
 
88
  new_plugin->name = name;
128
89
  new_plugin->argv = malloc(sizeof(char *) * 2);
129
 
  if(new_plugin->argv == NULL){
130
 
    free(copy_name);
131
 
    free(new_plugin);
132
 
    return NULL;
 
90
  if (new_plugin->argv == NULL){
 
91
    perror("malloc");
 
92
    exit(EXIT_FAILURE);
133
93
  }
134
 
  new_plugin->argv[0] = copy_name;
 
94
  new_plugin->argv[0] = name;
135
95
  new_plugin->argv[1] = NULL;
136
 
  
137
 
  new_plugin->environ = malloc(sizeof(char *));
138
 
  if(new_plugin->environ == NULL){
139
 
    free(copy_name);
140
 
    free(new_plugin->argv);
141
 
    free(new_plugin);
142
 
    return NULL;
143
 
  }
144
 
  new_plugin->environ[0] = NULL;
145
 
  
 
96
  new_plugin->argc = 1;
 
97
  new_plugin->disabled = false;
 
98
  new_plugin->next = *plugin_list;
146
99
  /* Append the new plugin to the list */
147
 
  plugin_list = new_plugin;
 
100
  *plugin_list = new_plugin;
148
101
  return new_plugin;
149
102
}
150
103
 
151
 
/* Helper function for add_argument and add_environment */
152
 
static bool add_to_char_array(const char *new, char ***array,
153
 
                              int *len){
154
 
  /* Resize the pointed-to array to hold one more pointer */
155
 
  *array = realloc(*array, sizeof(char *)
156
 
                   * (size_t) ((*len) + 2));
157
 
  /* Malloc check */
158
 
  if(*array == NULL){
159
 
    return false;
160
 
  }
161
 
  /* Make a copy of the new string */
162
 
  char *copy = strdup(new);
163
 
  if(copy == NULL){
164
 
    return false;
165
 
  }
166
 
  /* Insert the copy */
167
 
  (*array)[*len] = copy;
168
 
  (*len)++;
169
 
  /* Add a new terminating NULL pointer to the last element */
170
 
  (*array)[*len] = NULL;
171
 
  return true;
172
 
}
173
 
 
174
 
/* Add to a plugin's argument vector */
175
 
static bool add_argument(plugin *p, const char *arg){
176
 
  if(p == NULL){
177
 
    return false;
178
 
  }
179
 
  return add_to_char_array(arg, &(p->argv), &(p->argc));
180
 
}
181
 
 
182
 
/* Add to a plugin's environment */
183
 
static bool add_environment(plugin *p, const char *def, bool replace){
184
 
  if(p == NULL){
185
 
    return false;
186
 
  }
187
 
  /* namelen = length of name of environment variable */
188
 
  size_t namelen = (size_t)(strchrnul(def, '=') - def);
189
 
  /* Search for this environment variable */
190
 
  for(char **e = p->environ; *e != NULL; e++){
191
 
    if(strncmp(*e, def, namelen + 1) == 0){
192
 
      /* It already exists */
193
 
      if(replace){
194
 
        char *new = realloc(*e, strlen(def) + 1);
195
 
        if(new == NULL){
196
 
          return false;
197
 
        }
198
 
        *e = new;
199
 
        strcpy(*e, def);
200
 
      }
201
 
      return true;
202
 
    }
203
 
  }
204
 
  return add_to_char_array(def, &(p->environ), &(p->envc));
 
104
void addargument(plugin *p, char *arg){
 
105
  p->argv[p->argc] = arg;
 
106
  p->argv = realloc(p->argv, sizeof(char *) * (size_t)(p->argc + 2));
 
107
  if (p->argv == NULL){
 
108
    perror("malloc");
 
109
    exit(EXIT_FAILURE);
 
110
  }
 
111
  p->argc++;
 
112
  p->argv[p->argc] = NULL;
205
113
}
206
114
 
207
115
/*
209
117
 * Descriptor Flags".
210
118
 * *Note File Descriptor Flags:(libc)Descriptor Flags.
211
119
 */
212
 
static int set_cloexec_flag(int fd){
 
120
int set_cloexec_flag(int fd)
 
121
{
213
122
  int ret = fcntl(fd, F_GETFD, 0);
214
123
  /* If reading the flags failed, return error indication now. */
215
124
  if(ret < 0){
219
128
  return fcntl(fd, F_SETFD, ret | FD_CLOEXEC);
220
129
}
221
130
 
222
 
 
223
 
/* Mark processes as completed when they exit, and save their exit
 
131
#define BUFFER_SIZE 256
 
132
 
 
133
const char *argp_program_version = "plugbasedclient 0.9";
 
134
const char *argp_program_bug_address = "<mandos@fukt.bsnet.se>";
 
135
 
 
136
process *process_list = NULL;
 
137
 
 
138
/* Mark a process as completed when it exits, and save its exit
224
139
   status. */
225
 
static void handle_sigchld(__attribute__((unused)) int sig){
226
 
  int old_errno = errno;
227
 
  while(true){
228
 
    plugin *proc = plugin_list;
229
 
    int status;
230
 
    pid_t pid = waitpid(-1, &status, WNOHANG);
231
 
    if(pid == 0){
232
 
      /* Only still running child processes */
233
 
      break;
234
 
    }
235
 
    if(pid == -1){
236
 
      if(errno == ECHILD){
237
 
        /* No child processes */
238
 
        break;
239
 
      }
240
 
      perror("waitpid");
241
 
    }
242
 
    
243
 
    /* A child exited, find it in process_list */
244
 
    while(proc != NULL and proc->pid != pid){
245
 
      proc = proc->next;
246
 
    }
247
 
    if(proc == NULL){
248
 
      /* Process not found in process list */
249
 
      continue;
250
 
    }
251
 
    proc->status = status;
252
 
    proc->completed = 1;
253
 
  }
254
 
  errno = old_errno;
255
 
}
256
 
 
257
 
/* Prints out a password to stdout */
258
 
static bool print_out_password(const char *buffer, size_t length){
259
 
  ssize_t ret;
260
 
  for(size_t written = 0; written < length; written += (size_t)ret){
261
 
    ret = TEMP_FAILURE_RETRY(write(STDOUT_FILENO, buffer + written,
262
 
                                   length - written));
263
 
    if(ret < 0){
264
 
      return false;
265
 
    }
266
 
  }
267
 
  return true;
268
 
}
269
 
 
270
 
/* Removes and free a plugin from the plugin list */
271
 
static void free_plugin(plugin *plugin_node){
272
 
  
273
 
  for(char **arg = plugin_node->argv; *arg != NULL; arg++){
274
 
    free(*arg);
275
 
  }
276
 
  free(plugin_node->argv);
277
 
  for(char **env = plugin_node->environ; *env != NULL; env++){
278
 
    free(*env);
279
 
  }
280
 
  free(plugin_node->environ);
281
 
  free(plugin_node->buffer);
282
 
 
283
 
  /* Removes the plugin from the singly-linked list */
284
 
  if(plugin_node == plugin_list){
285
 
    /* First one - simple */
286
 
    plugin_list = plugin_list->next;
287
 
  } else {
288
 
    /* Second one or later */
289
 
    for(plugin *p = plugin_list; p != NULL; p = p->next){
290
 
      if(p->next == plugin_node){
291
 
        p->next = plugin_node->next;
292
 
        break;
293
 
      }
294
 
    }
295
 
  }
296
 
  
297
 
  free(plugin_node);
298
 
}
299
 
 
300
 
static void free_plugin_list(void){
301
 
  while(plugin_list != NULL){
302
 
    free_plugin(plugin_list);
303
 
  }
 
140
void handle_sigchld(__attribute__((unused)) int sig){
 
141
  process *proc = process_list;
 
142
  int status;
 
143
  pid_t pid = wait(&status);
 
144
  while(proc != NULL and proc->pid != pid){
 
145
    proc = proc->next;    
 
146
  }
 
147
  if(proc == NULL){
 
148
    /* Process not found in process list */
 
149
    return;
 
150
  }
 
151
  proc->status = status;
 
152
  proc->completed = true;
304
153
}
305
154
 
306
155
int main(int argc, char *argv[]){
307
 
  char *plugindir = NULL;
308
 
  char *argfile = NULL;
309
 
  FILE *conffp;
 
156
  const char *plugindir = "/conf/conf.d/mandos/plugins.d";
310
157
  size_t d_name_len;
311
158
  DIR *dir = NULL;
312
159
  struct dirent *dirst;
313
160
  struct stat st;
314
161
  fd_set rfds_all;
315
 
  int ret, numchars, maxfd = 0;
316
 
  ssize_t sret;
317
 
  intmax_t tmpmax;
318
 
  uid_t uid = 65534;
319
 
  gid_t gid = 65534;
 
162
  int ret, maxfd = 0;
320
163
  bool debug = false;
321
164
  int exitstatus = EXIT_SUCCESS;
322
 
  struct sigaction old_sigchld_action;
323
 
  struct sigaction sigchld_action = { .sa_handler = handle_sigchld,
324
 
                                      .sa_flags = SA_NOCLDSTOP };
325
 
  char **custom_argv = NULL;
326
 
  int custom_argc = 0;
327
165
  
328
166
  /* Establish a signal handler */
 
167
  struct sigaction old_sigchld_action,
 
168
    sigchld_action = { .sa_handler = handle_sigchld,
 
169
                       .sa_flags = SA_NOCLDSTOP };
329
170
  sigemptyset(&sigchld_action.sa_mask);
330
171
  ret = sigaddset(&sigchld_action.sa_mask, SIGCHLD);
331
 
  if(ret == -1){
 
172
  if(ret < 0){
332
173
    perror("sigaddset");
333
 
    exitstatus = EXIT_FAILURE;
334
 
    goto fallback;
 
174
    exit(EXIT_FAILURE);
335
175
  }
336
176
  ret = sigaction(SIGCHLD, &sigchld_action, &old_sigchld_action);
337
 
  if(ret == -1){
 
177
  if(ret < 0){
338
178
    perror("sigaction");
339
 
    exitstatus = EXIT_FAILURE;
340
 
    goto fallback;
 
179
    exit(EXIT_FAILURE);
341
180
  }
342
181
  
343
182
  /* The options we understand. */
345
184
    { .name = "global-options", .key = 'g',
346
185
      .arg = "OPTION[,OPTION[,...]]",
347
186
      .doc = "Options passed to all plugins" },
348
 
    { .name = "global-env", .key = 'G',
349
 
      .arg = "VAR=value",
350
 
      .doc = "Environment variable passed to all plugins" },
351
187
    { .name = "options-for", .key = 'o',
352
188
      .arg = "PLUGIN:OPTION[,OPTION[,...]]",
353
189
      .doc = "Options passed only to specified plugin" },
354
 
    { .name = "env-for", .key = 'E',
355
 
      .arg = "PLUGIN:ENV=value",
356
 
      .doc = "Environment variable passed to specified plugin" },
357
190
    { .name = "disable", .key = 'd',
358
191
      .arg = "PLUGIN",
359
192
      .doc = "Disable a specific plugin", .group = 1 },
360
 
    { .name = "enable", .key = 'e',
361
 
      .arg = "PLUGIN",
362
 
      .doc = "Enable a specific plugin", .group = 1 },
363
193
    { .name = "plugin-dir", .key = 128,
364
194
      .arg = "DIRECTORY",
365
195
      .doc = "Specify a different plugin directory", .group = 2 },
366
 
    { .name = "config-file", .key = 129,
367
 
      .arg = "FILE",
368
 
      .doc = "Specify a different configuration file", .group = 2 },
369
 
    { .name = "userid", .key = 130,
370
 
      .arg = "ID", .flags = 0,
371
 
      .doc = "User ID the plugins will run as", .group = 3 },
372
 
    { .name = "groupid", .key = 131,
373
 
      .arg = "ID", .flags = 0,
374
 
      .doc = "Group ID the plugins will run as", .group = 3 },
375
 
    { .name = "debug", .key = 132,
376
 
      .doc = "Debug mode", .group = 4 },
 
196
    { .name = "debug", .key = 129,
 
197
      .doc = "Debug mode", .group = 3 },
377
198
    { .name = NULL }
378
199
  };
379
200
  
380
 
  error_t parse_opt(int key, char *arg, __attribute__((unused))
381
 
                    struct argp_state *state){
382
 
    switch(key){
383
 
    case 'g':                   /* --global-options */
384
 
      if(arg != NULL){
385
 
        char *p;
386
 
        while((p = strsep(&arg, ",")) != NULL){
387
 
          if(p[0] == '\0'){
388
 
            continue;
389
 
          }
390
 
          if(not add_argument(getplugin(NULL), p)){
391
 
            perror("add_argument");
392
 
            return ARGP_ERR_UNKNOWN;
393
 
          }
394
 
        }
395
 
      }
396
 
      break;
397
 
    case 'G':                   /* --global-env */
398
 
      if(arg == NULL){
399
 
        break;
400
 
      }
401
 
      if(not add_environment(getplugin(NULL), arg, true)){
402
 
        perror("add_environment");
403
 
      }
404
 
      break;
405
 
    case 'o':                   /* --options-for */
406
 
      if(arg != NULL){
407
 
        char *p_name = strsep(&arg, ":");
408
 
        if(p_name[0] == '\0' or arg == NULL){
409
 
          break;
410
 
        }
411
 
        char *opt = strsep(&arg, ":");
412
 
        if(opt[0] == '\0' or opt == NULL){
413
 
          break;
414
 
        }
415
 
        char *p;
416
 
        while((p = strsep(&opt, ",")) != NULL){
417
 
          if(p[0] == '\0'){
418
 
            continue;
419
 
          }
420
 
          if(not add_argument(getplugin(p_name), p)){
421
 
            perror("add_argument");
422
 
            return ARGP_ERR_UNKNOWN;
423
 
          }
424
 
        }
425
 
      }
426
 
      break;
427
 
    case 'E':                   /* --env-for */
428
 
      if(arg == NULL){
429
 
        break;
430
 
      }
431
 
      {
432
 
        char *envdef = strchr(arg, ':');
433
 
        if(envdef == NULL){
434
 
          break;
435
 
        }
436
 
        *envdef = '\0';
437
 
        if(not add_environment(getplugin(arg), envdef+1, true)){
438
 
          perror("add_environment");
439
 
        }
440
 
      }
441
 
      break;
442
 
    case 'd':                   /* --disable */
443
 
      if(arg != NULL){
444
 
        plugin *p = getplugin(arg);
445
 
        if(p == NULL){
446
 
          return ARGP_ERR_UNKNOWN;
447
 
        }
448
 
        p->disabled = true;
449
 
      }
450
 
      break;
451
 
    case 'e':                   /* --enable */
452
 
      if(arg != NULL){
453
 
        plugin *p = getplugin(arg);
454
 
        if(p == NULL){
455
 
          return ARGP_ERR_UNKNOWN;
456
 
        }
457
 
        p->disabled = false;
458
 
      }
459
 
      break;
460
 
    case 128:                   /* --plugin-dir */
461
 
      free(plugindir);
462
 
      plugindir = strdup(arg);
463
 
      if(plugindir == NULL){
464
 
        perror("strdup");
465
 
      }      
466
 
      break;
467
 
    case 129:                   /* --config-file */
468
 
      /* This is already done by parse_opt_config_file() */
469
 
      break;
470
 
    case 130:                   /* --userid */
471
 
      ret = sscanf(arg, "%" SCNdMAX "%n", &tmpmax, &numchars);
472
 
      if(ret < 1 or tmpmax != (uid_t)tmpmax
473
 
         or arg[numchars] != '\0'){
474
 
        fprintf(stderr, "Bad user ID number: \"%s\", using %"
475
 
                PRIdMAX "\n", arg, (intmax_t)uid);
476
 
      } else {
477
 
        uid = (uid_t)tmpmax;
478
 
      }
479
 
      break;
480
 
    case 131:                   /* --groupid */
481
 
      ret = sscanf(arg, "%" SCNdMAX "%n", &tmpmax, &numchars);
482
 
      if(ret < 1 or tmpmax != (gid_t)tmpmax
483
 
         or arg[numchars] != '\0'){
484
 
        fprintf(stderr, "Bad group ID number: \"%s\", using %"
485
 
                PRIdMAX "\n", arg, (intmax_t)gid);
486
 
      } else {
487
 
        gid = (gid_t)tmpmax;
488
 
      }
489
 
      break;
490
 
    case 132:                   /* --debug */
 
201
  error_t parse_opt (int key, char *arg, struct argp_state *state) {
 
202
    /* Get the INPUT argument from `argp_parse', which we know is a
 
203
       pointer to our plugin list pointer. */
 
204
    plugin **plugins = state->input;
 
205
    switch (key) {
 
206
    case 'g':
 
207
      if (arg != NULL){
 
208
        char *p = strtok(arg, ",");
 
209
        do{
 
210
          addargument(getplugin(NULL, plugins), p);
 
211
          p = strtok(NULL, ",");
 
212
        } while (p);
 
213
      }
 
214
      break;
 
215
    case 'o':
 
216
      if (arg != NULL){
 
217
        char *name = strtok(arg, ":");
 
218
        char *p = strtok(NULL, ":");
 
219
        if(p){
 
220
          p = strtok(p, ",");
 
221
          do{
 
222
            addargument(getplugin(name, plugins), p);
 
223
            p = strtok(NULL, ",");
 
224
          } while (p);
 
225
        }
 
226
      }
 
227
      break;
 
228
    case 'd':
 
229
      if (arg != NULL){
 
230
        getplugin(arg, plugins)->disabled = true;
 
231
      }
 
232
      break;
 
233
    case 128:
 
234
      plugindir = arg;
 
235
      break;
 
236
    case 129:
491
237
      debug = true;
492
238
      break;
493
 
/*
494
 
 * When adding more options before this line, remember to also add a
495
 
 * "case" to the "parse_opt_config_file" function below.
496
 
 */
497
 
    case ARGP_KEY_ARG:
498
 
      /* Cryptsetup always passes an argument, which is an empty
499
 
         string if "none" was specified in /etc/crypttab.  So if
500
 
         argument was empty, we ignore it silently. */
501
 
      if(arg[0] != '\0'){
502
 
        fprintf(stderr, "Ignoring unknown argument \"%s\"\n", arg);
503
 
      }
504
 
      break;
505
 
    case ARGP_KEY_END:
506
 
      break;
507
 
    default:
508
 
      return ARGP_ERR_UNKNOWN;
509
 
    }
510
 
    return 0;
511
 
  }
512
 
  
513
 
  /* This option parser is the same as parse_opt() above, except it
514
 
     ignores everything but the --config-file option. */
515
 
  error_t parse_opt_config_file(int key, char *arg,
516
 
                                __attribute__((unused))
517
 
                                struct argp_state *state){
518
 
    switch(key){
519
 
    case 'g':                   /* --global-options */
520
 
    case 'G':                   /* --global-env */
521
 
    case 'o':                   /* --options-for */
522
 
    case 'E':                   /* --env-for */
523
 
    case 'd':                   /* --disable */
524
 
    case 'e':                   /* --enable */
525
 
    case 128:                   /* --plugin-dir */
526
 
      break;
527
 
    case 129:                   /* --config-file */
528
 
      free(argfile);
529
 
      argfile = strdup(arg);
530
 
      if(argfile == NULL){
531
 
        perror("strdup");
532
 
      }
533
 
      break;      
534
 
    case 130:                   /* --userid */
535
 
    case 131:                   /* --groupid */
536
 
    case 132:                   /* --debug */
537
 
    case ARGP_KEY_ARG:
538
 
    case ARGP_KEY_END:
539
 
      break;
540
 
    default:
541
 
      return ARGP_ERR_UNKNOWN;
542
 
    }
543
 
    return 0;
544
 
  }
545
 
  
546
 
  struct argp argp = { .options = options,
547
 
                       .parser = parse_opt_config_file,
 
239
    case ARGP_KEY_ARG:
 
240
      argp_usage (state);
 
241
      break;
 
242
    case ARGP_KEY_END:
 
243
      break;
 
244
    default:
 
245
      return ARGP_ERR_UNKNOWN;
 
246
    }
 
247
    return 0;
 
248
  }
 
249
  
 
250
  plugin *plugin_list = NULL;
 
251
  
 
252
  struct argp argp = { .options = options, .parser = parse_opt,
548
253
                       .args_doc = "",
549
254
                       .doc = "Mandos plugin runner -- Run plugins" };
550
255
  
551
 
  /* Parse using parse_opt_config_file() in order to get the custom
552
 
     config file location, if any. */
553
 
  ret = argp_parse(&argp, argc, argv, ARGP_IN_ORDER, 0, NULL);
554
 
  if(ret == ARGP_ERR_UNKNOWN){
555
 
    fprintf(stderr, "Unknown error while parsing arguments\n");
556
 
    exitstatus = EXIT_FAILURE;
557
 
    goto fallback;
558
 
  }
559
 
  
560
 
  /* Reset to the normal argument parser */
561
 
  argp.parser = parse_opt;
562
 
  
563
 
  /* Open the configfile if available */
564
 
  if(argfile == NULL){
565
 
    conffp = fopen(AFILE, "r");
566
 
  } else {
567
 
    conffp = fopen(argfile, "r");
568
 
  }  
569
 
  if(conffp != NULL){
570
 
    char *org_line = NULL;
571
 
    char *p, *arg, *new_arg, *line;
572
 
    size_t size = 0;
573
 
    const char whitespace_delims[] = " \r\t\f\v\n";
574
 
    const char comment_delim[] = "#";
575
 
 
576
 
    custom_argc = 1;
577
 
    custom_argv = malloc(sizeof(char*) * 2);
578
 
    if(custom_argv == NULL){
579
 
      perror("malloc");
580
 
      exitstatus = EXIT_FAILURE;
581
 
      goto fallback;
582
 
    }
583
 
    custom_argv[0] = argv[0];
584
 
    custom_argv[1] = NULL;
585
 
 
586
 
    /* for each line in the config file, strip whitespace and ignore
587
 
       commented text */
588
 
    while(true){
589
 
      sret = getline(&org_line, &size, conffp);
590
 
      if(sret == -1){
591
 
        break;
592
 
      }
593
 
 
594
 
      line = org_line;
595
 
      arg = strsep(&line, comment_delim);
596
 
      while((p = strsep(&arg, whitespace_delims)) != NULL){
597
 
        if(p[0] == '\0'){
598
 
          continue;
599
 
        }
600
 
        new_arg = strdup(p);
601
 
        if(new_arg == NULL){
602
 
          perror("strdup");
603
 
          exitstatus = EXIT_FAILURE;
604
 
          free(org_line);
605
 
          goto fallback;
606
 
        }
607
 
        
608
 
        custom_argc += 1;
609
 
        custom_argv = realloc(custom_argv, sizeof(char *)
610
 
                              * ((unsigned int) custom_argc + 1));
611
 
        if(custom_argv == NULL){
612
 
          perror("realloc");
613
 
          exitstatus = EXIT_FAILURE;
614
 
          free(org_line);
615
 
          goto fallback;
616
 
        }
617
 
        custom_argv[custom_argc-1] = new_arg;
618
 
        custom_argv[custom_argc] = NULL;        
619
 
      }
620
 
    }
621
 
    free(org_line);
622
 
  } else {
623
 
    /* Check for harmful errors and go to fallback. Other errors might
624
 
       not affect opening plugins */
625
 
    if(errno == EMFILE or errno == ENFILE or errno == ENOMEM){
626
 
      perror("fopen");
627
 
      exitstatus = EXIT_FAILURE;
628
 
      goto fallback;
629
 
    }
630
 
  }
631
 
  /* If there was any arguments from configuration file,
632
 
     pass them to parser as command arguments */
633
 
  if(custom_argv != NULL){
634
 
    ret = argp_parse(&argp, custom_argc, custom_argv, ARGP_IN_ORDER,
635
 
                     0, NULL);
636
 
    if(ret == ARGP_ERR_UNKNOWN){
637
 
      fprintf(stderr, "Unknown error while parsing arguments\n");
638
 
      exitstatus = EXIT_FAILURE;
639
 
      goto fallback;
640
 
    }
641
 
  }
642
 
  
643
 
  /* Parse actual command line arguments, to let them override the
644
 
     config file */
645
 
  ret = argp_parse(&argp, argc, argv, ARGP_IN_ORDER, 0, NULL);
646
 
  if(ret == ARGP_ERR_UNKNOWN){
647
 
    fprintf(stderr, "Unknown error while parsing arguments\n");
648
 
    exitstatus = EXIT_FAILURE;
649
 
    goto fallback;
650
 
  }
 
256
  argp_parse (&argp, argc, argv, 0, 0, &plugin_list);
651
257
  
652
258
  if(debug){
653
259
    for(plugin *p = plugin_list; p != NULL; p=p->next){
656
262
      for(char **a = p->argv; *a != NULL; a++){
657
263
        fprintf(stderr, "\tArg: %s\n", *a);
658
264
      }
659
 
      fprintf(stderr, "...and %d environment variables\n", p->envc);
660
 
      for(char **a = p->environ; *a != NULL; a++){
661
 
        fprintf(stderr, "\t%s\n", *a);
662
 
      }
663
265
    }
664
266
  }
665
267
  
666
 
  /* Strip permissions down to nobody */
667
 
  setgid(gid);
668
 
  if(ret == -1){
669
 
    perror("setgid");
670
 
  }
671
 
  ret = setuid(uid);
672
 
  if(ret == -1){
673
 
    perror("setuid");
674
 
  }
675
 
  
676
 
  if(plugindir == NULL){
677
 
    dir = opendir(PDIR);
678
 
  } else {
679
 
    dir = opendir(plugindir);
680
 
  }
681
 
  
 
268
  dir = opendir(plugindir);
682
269
  if(dir == NULL){
683
270
    perror("Could not open plugin dir");
684
271
    exitstatus = EXIT_FAILURE;
685
 
    goto fallback;
 
272
    goto end;
686
273
  }
687
274
  
688
275
  /* Set the FD_CLOEXEC flag on the directory, if possible */
693
280
      if(ret < 0){
694
281
        perror("set_cloexec_flag");
695
282
        exitstatus = EXIT_FAILURE;
696
 
        goto fallback;
 
283
        goto end;
697
284
      }
698
285
    }
699
286
  }
700
287
  
701
288
  FD_ZERO(&rfds_all);
702
289
  
703
 
  /* Read and execute any executable in the plugin directory*/
704
290
  while(true){
705
291
    dirst = readdir(dir);
706
292
    
707
 
    /* All directory entries have been processed */
 
293
    // All directory entries have been processed
708
294
    if(dirst == NULL){
709
 
      if(errno == EBADF){
710
 
        perror("readdir");
711
 
        exitstatus = EXIT_FAILURE;
712
 
        goto fallback;
713
 
      }
714
295
      break;
715
296
    }
716
297
    
717
298
    d_name_len = strlen(dirst->d_name);
718
299
    
719
 
    /* Ignore dotfiles, backup files and other junk */
 
300
    // Ignore dotfiles, backup files and other junk
720
301
    {
721
302
      bool bad_name = false;
722
303
      
724
305
      
725
306
      const char const *bad_suffixes[] = { "~", "#", ".dpkg-new",
726
307
                                           ".dpkg-old",
727
 
                                           ".dpkg-bak",
728
308
                                           ".dpkg-divert", NULL };
729
309
      for(const char **pre = bad_prefixes; *pre != NULL; pre++){
730
310
        size_t pre_len = strlen(*pre);
738
318
          break;
739
319
        }
740
320
      }
 
321
      
741
322
      if(bad_name){
742
323
        continue;
743
324
      }
 
325
      
744
326
      for(const char **suf = bad_suffixes; *suf != NULL; suf++){
745
327
        size_t suf_len = strlen(*suf);
746
328
        if((d_name_len >= suf_len)
759
341
        continue;
760
342
      }
761
343
    }
762
 
 
763
 
    char *filename;
764
 
    if(plugindir == NULL){
765
 
      ret = asprintf(&filename, PDIR "/%s", dirst->d_name);
766
 
    } else {
767
 
      ret = asprintf(&filename, "%s/%s", plugindir, dirst->d_name);
768
 
    }
769
 
    if(ret < 0){
770
 
      perror("asprintf");
771
 
      continue;
772
 
    }
773
 
    
774
 
    ret = stat(filename, &st);
775
 
    if(ret == -1){
776
 
      perror("stat");
777
 
      free(filename);
778
 
      continue;
779
 
    }
780
 
 
781
 
    /* Ignore non-executable files */
782
 
    if(not S_ISREG(st.st_mode) or (access(filename, X_OK) != 0)){
 
344
    
 
345
    char *filename = malloc(d_name_len + strlen(plugindir) + 2);
 
346
    if (filename == NULL){
 
347
      perror("malloc");
 
348
      exitstatus = EXIT_FAILURE;
 
349
      goto end;
 
350
    }
 
351
    strcpy(filename, plugindir); /* Spurious warning */
 
352
    strcat(filename, "/");      /* Spurious warning */
 
353
    strcat(filename, dirst->d_name); /* Spurious warning */
 
354
    
 
355
    stat(filename, &st);
 
356
    
 
357
    if (not S_ISREG(st.st_mode) or (access(filename, X_OK) != 0)){
783
358
      if(debug){
784
359
        fprintf(stderr, "Ignoring plugin dir entry \"%s\""
785
360
                " with bad type or mode\n", filename);
787
362
      free(filename);
788
363
      continue;
789
364
    }
790
 
    
791
 
    plugin *p = getplugin(dirst->d_name);
792
 
    if(p == NULL){
793
 
      perror("getplugin");
794
 
      free(filename);
795
 
      continue;
796
 
    }
797
 
    if(p->disabled){
 
365
    if(getplugin(dirst->d_name, &plugin_list)->disabled){
798
366
      if(debug){
799
367
        fprintf(stderr, "Ignoring disabled plugin \"%s\"\n",
800
368
                dirst->d_name);
802
370
      free(filename);
803
371
      continue;
804
372
    }
 
373
    plugin *p = getplugin(dirst->d_name, &plugin_list);
805
374
    {
806
375
      /* Add global arguments to argument list for this plugin */
807
 
      plugin *g = getplugin(NULL);
808
 
      if(g != NULL){
809
 
        for(char **a = g->argv + 1; *a != NULL; a++){
810
 
          if(not add_argument(p, *a)){
811
 
            perror("add_argument");
812
 
          }
813
 
        }
814
 
        /* Add global environment variables */
815
 
        for(char **e = g->environ; *e != NULL; e++){
816
 
          if(not add_environment(p, *e, false)){
817
 
            perror("add_environment");
818
 
          }
819
 
        }
820
 
      }
821
 
    }
822
 
    /* If this plugin has any environment variables, we will call
823
 
       using execve and need to duplicate the environment from this
824
 
       process, too. */
825
 
    if(p->environ[0] != NULL){
826
 
      for(char **e = environ; *e != NULL; e++){
827
 
        if(not add_environment(p, *e, false)){
828
 
          perror("add_environment");
829
 
        }
830
 
      }
831
 
    }
832
 
    
833
 
    int pipefd[2];
 
376
      plugin *g = getplugin(NULL, &plugin_list);
 
377
      for(char **a = g->argv + 1; *a != NULL; a++){
 
378
        addargument(p, *a);
 
379
      }
 
380
    }
 
381
    int pipefd[2]; 
834
382
    ret = pipe(pipefd);
835
 
    if(ret == -1){
 
383
    if (ret == -1){
836
384
      perror("pipe");
837
385
      exitstatus = EXIT_FAILURE;
838
 
      goto fallback;
 
386
      goto end;
839
387
    }
840
 
    /* Ask OS to automatic close the pipe on exec */
841
388
    ret = set_cloexec_flag(pipefd[0]);
842
389
    if(ret < 0){
843
390
      perror("set_cloexec_flag");
844
391
      exitstatus = EXIT_FAILURE;
845
 
      goto fallback;
 
392
      goto end;
846
393
    }
847
394
    ret = set_cloexec_flag(pipefd[1]);
848
395
    if(ret < 0){
849
396
      perror("set_cloexec_flag");
850
397
      exitstatus = EXIT_FAILURE;
851
 
      goto fallback;
 
398
      goto end;
852
399
    }
853
400
    /* Block SIGCHLD until process is safely in process list */
854
 
    ret = sigprocmask(SIG_BLOCK, &sigchld_action.sa_mask, NULL);
 
401
    ret = sigprocmask (SIG_BLOCK, &sigchld_action.sa_mask, NULL);
855
402
    if(ret < 0){
856
403
      perror("sigprocmask");
857
404
      exitstatus = EXIT_FAILURE;
858
 
      goto fallback;
 
405
      goto end;
859
406
    }
860
 
    /* Starting a new process to be watched */
 
407
    // Starting a new process to be watched
861
408
    pid_t pid = fork();
862
 
    if(pid == -1){
863
 
      perror("fork");
864
 
      exitstatus = EXIT_FAILURE;
865
 
      goto fallback;
866
 
    }
867
409
    if(pid == 0){
868
410
      /* this is the child process */
869
411
      ret = sigaction(SIGCHLD, &old_sigchld_action, NULL);
871
413
        perror("sigaction");
872
414
        _exit(EXIT_FAILURE);
873
415
      }
874
 
      ret = sigprocmask(SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
 
416
      ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
875
417
      if(ret < 0){
876
418
        perror("sigprocmask");
877
419
        _exit(EXIT_FAILURE);
878
420
      }
879
 
      
880
 
      ret = dup2(pipefd[1], STDOUT_FILENO); /* replace our stdout */
881
 
      if(ret == -1){
882
 
        perror("dup2");
883
 
        _exit(EXIT_FAILURE);
884
 
      }
 
421
      dup2(pipefd[1], STDOUT_FILENO); /* replace our stdout */
885
422
      
886
423
      if(dirfd(dir) < 0){
887
424
        /* If dir has no file descriptor, we could not set FD_CLOEXEC
888
425
           above and must now close it manually here. */
889
426
        closedir(dir);
890
427
      }
891
 
      if(p->environ[0] == NULL){
892
 
        if(execv(filename, p->argv) < 0){
893
 
          perror("execv");
894
 
          _exit(EXIT_FAILURE);
895
 
        }
896
 
      } else {
897
 
        if(execve(filename, p->argv, p->environ) < 0){
898
 
          perror("execve");
899
 
          _exit(EXIT_FAILURE);
900
 
        }
 
428
      if(execv(filename, p->argv) < 0){
 
429
        perror("execv");
 
430
        _exit(EXIT_FAILURE);
901
431
      }
902
432
      /* no return */
903
433
    }
904
 
    /* Parent process */
905
 
    close(pipefd[1]);           /* Close unused write end of pipe */
 
434
    /* parent process */
906
435
    free(filename);
907
 
    plugin *new_plugin = getplugin(dirst->d_name);
908
 
    if(new_plugin == NULL){
909
 
      perror("getplugin");
910
 
      ret = sigprocmask(SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
 
436
    close(pipefd[1]);           /* close unused write end of pipe */
 
437
    process *new_process = malloc(sizeof(process));
 
438
    if (new_process == NULL){
 
439
      perror("malloc");
 
440
      ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
911
441
      if(ret < 0){
912
 
        perror("sigprocmask");
 
442
        perror("sigprocmask");
913
443
      }
914
444
      exitstatus = EXIT_FAILURE;
915
 
      goto fallback;
 
445
      goto end;
916
446
    }
917
447
    
918
 
    new_plugin->pid = pid;
919
 
    new_plugin->fd = pipefd[0];
920
 
    
 
448
    *new_process = (struct process){ .pid = pid,
 
449
                                     .fd = pipefd[0],
 
450
                                     .next = process_list };
 
451
    // List handling
 
452
    process_list = new_process;
921
453
    /* Unblock SIGCHLD so signal handler can be run if this process
922
454
       has already completed */
923
 
    ret = sigprocmask(SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
 
455
    ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
924
456
    if(ret < 0){
925
457
      perror("sigprocmask");
926
458
      exitstatus = EXIT_FAILURE;
927
 
      goto fallback;
928
 
    }
929
 
    
930
 
    FD_SET(new_plugin->fd, &rfds_all);
931
 
    
932
 
    if(maxfd < new_plugin->fd){
933
 
      maxfd = new_plugin->fd;
934
 
    }
 
459
      goto end;
 
460
    }
 
461
    
 
462
    FD_SET(new_process->fd, &rfds_all);
 
463
    
 
464
    if (maxfd < new_process->fd){
 
465
      maxfd = new_process->fd;
 
466
    }
 
467
    
 
468
  }
 
469
  
 
470
  /* Free the plugin list */
 
471
  for(plugin *next; plugin_list != NULL; plugin_list = next){
 
472
    next = plugin_list->next;
 
473
    free(plugin_list->argv);
 
474
    free(plugin_list);
935
475
  }
936
476
  
937
477
  closedir(dir);
938
478
  dir = NULL;
939
479
  
940
 
  for(plugin *p = plugin_list; p != NULL; p = p->next){
941
 
    if(p->pid != 0){
942
 
      break;
943
 
    }
944
 
    if(p->next == NULL){
945
 
      fprintf(stderr, "No plugin processes started. Incorrect plugin"
946
 
              " directory?\n");
947
 
      free_plugin_list();
948
 
    }
 
480
  if (process_list == NULL){
 
481
    fprintf(stderr, "No plugin processes started, exiting\n");
 
482
    exitstatus = EXIT_FAILURE;
 
483
    goto end;
949
484
  }
950
 
  
951
 
  /* Main loop while running plugins exist */
952
 
  while(plugin_list){
 
485
  while(process_list){
953
486
    fd_set rfds = rfds_all;
954
487
    int select_ret = select(maxfd+1, &rfds, NULL, NULL, NULL);
955
 
    if(select_ret == -1){
 
488
    if (select_ret == -1){
956
489
      perror("select");
957
490
      exitstatus = EXIT_FAILURE;
958
 
      goto fallback;
 
491
      goto end;
959
492
    }
960
493
    /* OK, now either a process completed, or something can be read
961
494
       from one of them */
962
 
    for(plugin *proc = plugin_list; proc != NULL;){
 
495
    for(process *proc = process_list; proc ; proc = proc->next){
963
496
      /* Is this process completely done? */
964
 
      if(proc->completed and proc->eof){
 
497
      if(proc->eof and proc->completed){
965
498
        /* Only accept the plugin output if it exited cleanly */
966
499
        if(not WIFEXITED(proc->status)
967
500
           or WEXITSTATUS(proc->status) != 0){
968
501
          /* Bad exit by plugin */
969
 
 
970
502
          if(debug){
971
503
            if(WIFEXITED(proc->status)){
972
 
              fprintf(stderr, "Plugin %s [%" PRIdMAX "] exited with"
973
 
                      " status %d\n", proc->name,
974
 
                      (intmax_t) (proc->pid),
975
 
                      WEXITSTATUS(proc->status));
976
 
            } else if(WIFSIGNALED(proc->status)){
977
 
              fprintf(stderr, "Plugin %s [%" PRIdMAX "] killed by"
978
 
                      " signal %d\n", proc->name,
979
 
                      (intmax_t) (proc->pid),
980
 
                      WTERMSIG(proc->status));
 
504
              fprintf(stderr, "Plugin %d exited with status %d\n",
 
505
                      proc->pid, WEXITSTATUS(proc->status));
 
506
            } else if(WIFSIGNALED(proc->status)) {
 
507
              fprintf(stderr, "Plugin %d killed by signal %d\n",
 
508
                      proc->pid, WTERMSIG(proc->status));
981
509
            } else if(WCOREDUMP(proc->status)){
982
 
              fprintf(stderr, "Plugin %s [%" PRIdMAX "] dumped"
983
 
                      " core\n", proc->name, (intmax_t) (proc->pid));
 
510
              fprintf(stderr, "Plugin %d dumped core\n", proc->pid);
984
511
            }
985
512
          }
986
 
          
987
513
          /* Remove the plugin */
988
514
          FD_CLR(proc->fd, &rfds_all);
989
 
 
990
515
          /* Block signal while modifying process_list */
991
 
          ret = sigprocmask(SIG_BLOCK, &sigchld_action.sa_mask, NULL);
 
516
          ret = sigprocmask (SIG_BLOCK, &sigchld_action.sa_mask, NULL);
992
517
          if(ret < 0){
993
518
            perror("sigprocmask");
994
519
            exitstatus = EXIT_FAILURE;
995
 
            goto fallback;
996
 
          }
997
 
          
998
 
          plugin *next_plugin = proc->next;
999
 
          free_plugin(proc);
1000
 
          proc = next_plugin;
1001
 
          
 
520
            goto end;
 
521
          }
 
522
          /* Delete this process entry from the list */
 
523
          if(process_list == proc){
 
524
            /* First one - simple */
 
525
            process_list = proc->next;
 
526
          } else {
 
527
            /* Second one or later */
 
528
            for(process *p = process_list; p != NULL; p = p->next){
 
529
              if(p->next == proc){
 
530
                p->next = proc->next;
 
531
                break;
 
532
              }
 
533
            }
 
534
          }
1002
535
          /* We are done modifying process list, so unblock signal */
1003
 
          ret = sigprocmask(SIG_UNBLOCK, &sigchld_action.sa_mask,
1004
 
                            NULL);
 
536
          ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask,
 
537
                             NULL);
1005
538
          if(ret < 0){
1006
539
            perror("sigprocmask");
1007
 
            exitstatus = EXIT_FAILURE;
1008
 
            goto fallback;
1009
 
          }
1010
 
          
1011
 
          if(plugin_list == NULL){
1012
 
            break;
1013
 
          }
1014
 
          
1015
 
          continue;
 
540
          }
 
541
          free(proc->buffer);
 
542
          free(proc);
 
543
          /* We deleted this process from the list, so we can't go
 
544
             proc->next.  Therefore, start over from the beginning of
 
545
             the process list */
 
546
          break;
1016
547
        }
1017
 
        
1018
548
        /* This process exited nicely, so print its buffer */
1019
 
        
1020
 
        bool bret = print_out_password(proc->buffer,
1021
 
                                       proc->buffer_length);
1022
 
        if(not bret){
1023
 
          perror("print_out_password");
1024
 
          exitstatus = EXIT_FAILURE;
 
549
        for(size_t written = 0; written < proc->buffer_length;
 
550
            written += (size_t)ret){
 
551
          ret = TEMP_FAILURE_RETRY(write(STDOUT_FILENO,
 
552
                                         proc->buffer + written,
 
553
                                         proc->buffer_length
 
554
                                         - written));
 
555
          if(ret < 0){
 
556
            perror("write");
 
557
            exitstatus = EXIT_FAILURE;
 
558
            goto end;
 
559
          }
1025
560
        }
1026
 
        goto fallback;
 
561
        goto end;
1027
562
      }
1028
 
      
1029
563
      /* This process has not completed.  Does it have any output? */
1030
564
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){
1031
565
        /* This process had nothing to say at this time */
1032
 
        proc = proc->next;
1033
566
        continue;
1034
567
      }
1035
568
      /* Before reading, make the process' data buffer large enough */
1036
569
      if(proc->buffer_length + BUFFER_SIZE > proc->buffer_size){
1037
570
        proc->buffer = realloc(proc->buffer, proc->buffer_size
1038
571
                               + (size_t) BUFFER_SIZE);
1039
 
        if(proc->buffer == NULL){
 
572
        if (proc->buffer == NULL){
1040
573
          perror("malloc");
1041
574
          exitstatus = EXIT_FAILURE;
1042
 
          goto fallback;
 
575
          goto end;
1043
576
        }
1044
577
        proc->buffer_size += BUFFER_SIZE;
1045
578
      }
1046
579
      /* Read from the process */
1047
 
      sret = read(proc->fd, proc->buffer + proc->buffer_length,
1048
 
                  BUFFER_SIZE);
1049
 
      if(sret < 0){
 
580
      ret = read(proc->fd, proc->buffer + proc->buffer_length,
 
581
                 BUFFER_SIZE);
 
582
      if(ret < 0){
1050
583
        /* Read error from this process; ignore the error */
1051
 
        proc = proc->next;
1052
584
        continue;
1053
585
      }
1054
 
      if(sret == 0){
 
586
      if(ret == 0){
1055
587
        /* got EOF */
1056
588
        proc->eof = true;
1057
589
      } else {
1058
 
        proc->buffer_length += (size_t) sret;
 
590
        proc->buffer_length += (size_t) ret;
1059
591
      }
1060
592
    }
1061
593
  }
1062
 
 
1063
 
 
1064
 
 fallback:
1065
 
  
1066
 
  if(plugin_list == NULL or exitstatus != EXIT_SUCCESS){
1067
 
    /* Fallback if all plugins failed, none are found or an error
1068
 
       occured */
1069
 
    bool bret;
1070
 
    fprintf(stderr, "Going to fallback mode using getpass(3)\n");
1071
 
    char *passwordbuffer = getpass("Password: ");
1072
 
    size_t len = strlen(passwordbuffer);
1073
 
    /* Strip trailing newline */
1074
 
    if(len > 0 and passwordbuffer[len-1] == '\n'){
1075
 
      passwordbuffer[len-1] = '\0'; /* not strictly necessary */
1076
 
      len--;
1077
 
    }
1078
 
    bret = print_out_password(passwordbuffer, len);
1079
 
    if(not bret){
1080
 
      perror("print_out_password");
1081
 
      exitstatus = EXIT_FAILURE;
1082
 
    }
 
594
  if(process_list == NULL){
 
595
    fprintf(stderr, "All plugin processes failed, exiting\n");
 
596
    exitstatus = EXIT_FAILURE;
1083
597
  }
1084
598
  
 
599
 end:
1085
600
  /* Restore old signal handler */
1086
 
  ret = sigaction(SIGCHLD, &old_sigchld_action, NULL);
1087
 
  if(ret == -1){
1088
 
    perror("sigaction");
1089
 
    exitstatus = EXIT_FAILURE;
1090
 
  }
 
601
  sigaction(SIGCHLD, &old_sigchld_action, NULL);
1091
602
  
1092
 
  if(custom_argv != NULL){
1093
 
    for(char **arg = custom_argv+1; *arg != NULL; arg++){
1094
 
      free(*arg);
1095
 
    }
1096
 
    free(custom_argv);
 
603
  /* Free the plugin list */
 
604
  for(plugin *next; plugin_list != NULL; plugin_list = next){
 
605
    next = plugin_list->next;
 
606
    free(plugin_list->argv);
 
607
    free(plugin_list);
1097
608
  }
1098
609
  
1099
610
  if(dir != NULL){
1100
611
    closedir(dir);
1101
612
  }
1102
613
  
1103
 
  /* Kill the processes */
1104
 
  for(plugin *p = plugin_list; p != NULL; p = p->next){
1105
 
    if(p->pid != 0){
1106
 
      close(p->fd);
1107
 
      ret = kill(p->pid, SIGTERM);
1108
 
      if(ret == -1 and errno != ESRCH){
1109
 
        /* Set-uid proccesses might not get closed */
1110
 
        perror("kill");
1111
 
      }
1112
 
    }
 
614
  /* Free the process list and kill the processes */
 
615
  for(process *next; process_list != NULL; process_list = next){
 
616
    next = process_list->next;
 
617
    close(process_list->fd);
 
618
    kill(process_list->pid, SIGTERM);
 
619
    free(process_list->buffer);
 
620
    free(process_list);
1113
621
  }
1114
622
  
1115
623
  /* Wait for any remaining child processes to terminate */
1120
628
    perror("wait");
1121
629
  }
1122
630
  
1123
 
  free_plugin_list();
1124
 
  
1125
 
  free(plugindir);
1126
 
  free(argfile);
1127
 
  
1128
631
  return exitstatus;
1129
632
}