/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-07-20 06:33:48 UTC
  • Revision ID: teddy@fukt.bsnet.se-20080720063348-jscgy5p0itrgvlo8
* mandos-clients.conf ([foo]): Uncommented.
  ([foo]/secret): New.
  ([foo]/secfile): Commented out.
  ([foo]/checker): Changed to "fping -q -- %%(fqdn)s".
  ([foo]/timeout): New.

* server.py: New modeline for Python and Emacs.  Set a logging format.
  (Client.__init__): Bug fix: Choose either the value from the options
                     object or pass the argument through string_to_delta
                     for both "timeout" and "interval".
  (Client.checker_callback): Bug fix: Do not log spurious "Checker for
                             <foo> failed" messages.
  (Client.start_checker): Moved "Starting checker" log message down to
                          just before actually starting the subprocess.
                          Do not redirect the subprocesses' stdout to a
                          pipe.
  (peer_certificate, fingerprint): Added docstrings.
  (entry_group_state_changed): Call "killme()" instead of
                               "main_loop.quit()".
  (daemon, killme): New functions.
  (exitstatus, main_loop_started): New global variables.
  (__main__): Removed the "--cert", "--key", "--ca", and "--crl"
              options.  Removed the sleep command from the default
              checker.  Add a console logger in debug mode.  Call
              "killme()" instead of "main_loop.quit()" when there are no
              more clients.  Call "daemon()" if not in debug mode.
              Register "cleanup()" to run at exit.  Ignore some
              signals.  Catch DBusException to detect another running
              server and exit cleanly.  Exit with "exitstatus".
  (cleanup): New function.

Show diffs side-by-side

added added

removed removed

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