/mandos/trunk

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

« back to all changes in this revision

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