/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 plugin-runner.c

First version of a somewhat complete D-Bus server interface.  Also
change user/group name to "_mandos".

* debian/mandos.postinst: Rename old "mandos" user and group to
                          "_mandos"; create "_mandos" user and group
                          if none exist.
* debian/mandos-client.postinst: - '' -

* initramfs-tools-hook: Try "_mandos" before "mandos" as user and
                        group name.

* mandos (_datetime_to_dbus_struct): New; was previously local.
  (Client.started): Renamed to "last_started".  All users changed.
  (Client.started): New; boolean.
  (Client.dbus_object_path): New.
  (Client.check_command): Renamed to "checker_command".  All users
                          changed.
  (Client.__init__): Set and use "self.dbus_object_path".  Set
                     "self.started".
  (Client.start): Update "self.started".  Emit "self.PropertyChanged"
                  signals for both "started" and "last_started".
  (Client.stop): Update "self.started".  Emit "self.PropertyChanged"
                 signal for "started".
  (Client.checker_callback): Take additional "command" argument.  All
                             callers changed. Emit
                             "self.PropertyChanged" signal.
  (Client.bump_timeout): Emit "self.PropertyChanged" signal for
                         "last_checked_ok".
  (Client.start_checker): Emit "self.PropertyChanged" signal for
                          "checker_running".
  (Client.stop_checker): Emit "self.PropertyChanged" signal for
                         "checker_running".
  (Client.still_valid): Bug fix: use "getattr(self, started, False)"
                        instead of "self.started" in case this client
                        object is so new that the "started" attribute
                        has not been created yet.
  (Client.IntervalChanged, Client.CheckerIsRunning, Client.GetChecker,
  Client.GetCreated, Client.GetFingerprint, Client.GetHost,
  Client.GetInterval, Client.GetName, Client.GetStarted,
  Client.GetTimeout, Client.StateChanged, Client.TimeoutChanged):
  Removed; all callers changed.
  (Client.CheckerCompleted): Add "condition" and "command" arguments.
                             All callers changed.
  (Client.GetAllProperties, Client.PropertyChanged): New.
  (Client.StillValid): Renamed to "IsStillValid".
  (Client.StartChecker): Changed to its own function to avoid the
                         return value from "Client.start_checker()".
  (Client.Stop): Changed to its own function to avoid the return value
                 from "Client.stop()".
  (main): Try "_mandos" before "mandos" as user and group name.
          Removed inner function "remove_from_clients".  New inner
          class "MandosServer".

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