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