/mandos/trunk

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

« back to all changes in this revision

Viewing changes to plugin-runner.c

  • Committer: Teddy Hogeborn
  • Date: 2008-12-10 01:26:02 UTC
  • mfrom: (237.1.2 mandos)
  • Revision ID: teddy@fukt.bsnet.se-20081210012602-vhz3h75xkj24t340
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>             /* strtok, 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,
66
67
 
67
68
#define BUFFER_SIZE 256
68
69
 
69
 
struct process;
70
 
 
71
 
typedef struct process{
 
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;
 
74
const char *argp_program_bug_address = "<mandos@fukt.bsnet.se>";
 
75
 
 
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;
 
83
 
 
84
  /* Variables used for running processes*/
72
85
  pid_t pid;
73
86
  int fd;
74
87
  char *buffer;
75
88
  size_t buffer_size;
76
89
  size_t buffer_length;
77
90
  bool eof;
78
 
  bool completed;
79
 
  int status;
80
 
  struct process *next;
81
 
} process;
82
 
 
83
 
typedef struct plugin{
84
 
  char *name;                   /* can be NULL or any plugin name */
85
 
  char **argv;
86
 
  int argc;
87
 
  bool disabled;
 
91
  volatile bool completed;
 
92
  volatile int status;
88
93
  struct plugin *next;
89
94
} plugin;
90
95
 
91
 
static plugin *getplugin(char *name, plugin **plugin_list){
92
 
  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){
93
103
    if ((p->name == name)
94
104
        or (p->name and name and (strcmp(p->name, name) == 0))){
95
105
      return p;
98
108
  /* Create a new plugin */
99
109
  plugin *new_plugin = malloc(sizeof(plugin));
100
110
  if (new_plugin == NULL){
101
 
    perror("malloc");
102
 
    exit(EXIT_FAILURE);
103
 
  }
104
 
  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
  
105
126
  new_plugin->argv = malloc(sizeof(char *) * 2);
106
127
  if (new_plugin->argv == NULL){
107
 
    perror("malloc");
108
 
    exit(EXIT_FAILURE);
 
128
    free(copy_name);
 
129
    free(new_plugin);
 
130
    return NULL;
109
131
  }
110
 
  new_plugin->argv[0] = name;
 
132
  new_plugin->argv[0] = copy_name;
111
133
  new_plugin->argv[1] = NULL;
112
 
  new_plugin->argc = 1;
113
 
  new_plugin->disabled = false;
114
 
  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
  
115
144
  /* Append the new plugin to the list */
116
 
  *plugin_list = new_plugin;
 
145
  plugin_list = new_plugin;
117
146
  return new_plugin;
118
147
}
119
148
 
120
 
static void addargument(plugin *p, char *arg){
121
 
  p->argv[p->argc] = arg;
122
 
  p->argv = realloc(p->argv, sizeof(char *) * (size_t)(p->argc + 2));
123
 
  if (p->argv == NULL){
124
 
    perror("malloc");
125
 
    exit(EXIT_FAILURE);
126
 
  }
127
 
  p->argc++;
128
 
  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));
129
203
}
130
204
 
131
205
/*
133
207
 * Descriptor Flags".
134
208
 * *Note File Descriptor Flags:(libc)Descriptor Flags.
135
209
 */
136
 
static int set_cloexec_flag(int fd)
137
 
{
 
210
static int set_cloexec_flag(int fd){
138
211
  int ret = fcntl(fd, F_GETFD, 0);
139
212
  /* If reading the flags failed, return error indication now. */
140
213
  if(ret < 0){
144
217
  return fcntl(fd, F_SETFD, ret | FD_CLOEXEC);
145
218
}
146
219
 
147
 
const char *argp_program_version = "plugbasedclient 0.9";
148
 
const char *argp_program_bug_address = "<mandos@fukt.bsnet.se>";
149
 
 
150
 
process *process_list = NULL;
151
 
 
152
 
/* Mark a process as completed when it exits, and save its exit
 
220
 
 
221
/* Mark processes as completed when they exit, and save their exit
153
222
   status. */
154
 
void handle_sigchld(__attribute__((unused)) int sig){
155
 
  process *proc = process_list;
156
 
  int status;
157
 
  pid_t pid = wait(&status);
158
 
  if(pid == -1){
159
 
    perror("wait");
160
 
    return;
161
 
  }
162
 
  while(proc != NULL and proc->pid != pid){
163
 
    proc = proc->next;
164
 
  }
165
 
  if(proc == NULL){
166
 
    /* Process not found in process list */
167
 
    return;
168
 
  }
169
 
  proc->status = status;
170
 
  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
  }
 
251
}
 
252
 
 
253
/* Prints out a password to stdout */
 
254
static bool print_out_password(const char *buffer, size_t length){
 
255
  ssize_t ret;
 
256
  for(size_t written = 0; written < length; written += (size_t)ret){
 
257
    ret = TEMP_FAILURE_RETRY(write(STDOUT_FILENO, buffer + written,
 
258
                                   length - written));
 
259
    if(ret < 0){
 
260
      return false;
 
261
    }
 
262
  }
 
263
  return true;
 
264
}
 
265
 
 
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);
 
278
 
 
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
      }
 
290
    }
 
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
  }
171
300
}
172
301
 
173
302
int main(int argc, char *argv[]){
174
 
  const char *plugindir = "/conf/conf.d/mandos/plugins.d";
 
303
  char *plugindir = NULL;
 
304
  char *argfile = NULL;
 
305
  FILE *conffp;
175
306
  size_t d_name_len;
176
307
  DIR *dir = NULL;
177
308
  struct dirent *dirst;
185
316
  struct sigaction old_sigchld_action;
186
317
  struct sigaction sigchld_action = { .sa_handler = handle_sigchld,
187
318
                                      .sa_flags = SA_NOCLDSTOP };
188
 
  char *plus_options = NULL;
189
 
  char **plus_argv = NULL;
 
319
  char **custom_argv = NULL;
 
320
  int custom_argc = 0;
190
321
  
191
322
  /* Establish a signal handler */
192
323
  sigemptyset(&sigchld_action.sa_mask);
193
324
  ret = sigaddset(&sigchld_action.sa_mask, SIGCHLD);
194
 
  if(ret < 0){
 
325
  if(ret == -1){
195
326
    perror("sigaddset");
196
 
    exit(EXIT_FAILURE);
 
327
    exitstatus = EXIT_FAILURE;
 
328
    goto fallback;
197
329
  }
198
330
  ret = sigaction(SIGCHLD, &sigchld_action, &old_sigchld_action);
199
 
  if(ret < 0){
 
331
  if(ret == -1){
200
332
    perror("sigaction");
201
 
    exit(EXIT_FAILURE);
 
333
    exitstatus = EXIT_FAILURE;
 
334
    goto fallback;
202
335
  }
203
336
  
204
337
  /* The options we understand. */
206
339
    { .name = "global-options", .key = 'g',
207
340
      .arg = "OPTION[,OPTION[,...]]",
208
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" },
209
345
    { .name = "options-for", .key = 'o',
210
346
      .arg = "PLUGIN:OPTION[,OPTION[,...]]",
211
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" },
212
351
    { .name = "disable", .key = 'd',
213
352
      .arg = "PLUGIN",
214
353
      .doc = "Disable a specific plugin", .group = 1 },
 
354
    { .name = "enable", .key = 'e',
 
355
      .arg = "PLUGIN",
 
356
      .doc = "Enable a specific plugin", .group = 1 },
215
357
    { .name = "plugin-dir", .key = 128,
216
358
      .arg = "DIRECTORY",
217
359
      .doc = "Specify a different plugin directory", .group = 2 },
218
 
    { .name = "userid", .key = 129,
219
 
      .arg = "ID", .flags = 0,
220
 
      .doc = "User ID the plugins will run as", .group = 2 },
221
 
    { .name = "groupid", .key = 130,
222
 
      .arg = "ID", .flags = 0,
223
 
      .doc = "Group ID the plugins will run as", .group = 2 },
224
 
    { .name = "debug", .key = 131,
225
 
      .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 },
226
371
    { .name = NULL }
227
372
  };
228
373
  
229
 
  error_t parse_opt (int key, char *arg, struct argp_state *state) {
230
 
    /* Get the INPUT argument from `argp_parse', which we know is a
231
 
       pointer to our plugin list pointer. */
232
 
    plugin **plugins = state->input;
 
374
  error_t parse_opt (int key, char *arg, __attribute__((unused))
 
375
                     struct argp_state *state) {
233
376
    switch (key) {
234
 
    case 'g':
235
 
      if (arg != NULL){
236
 
        char *p = strtok(arg, ",");
237
 
        do{
238
 
          addargument(getplugin(NULL, plugins), p);
239
 
          p = strtok(NULL, ",");
240
 
        } while (p != NULL);
241
 
      }
242
 
      break;
243
 
    case 'o':
244
 
      if (arg != NULL){
245
 
        char *name = strtok(arg, ":");
246
 
        char *p = strtok(NULL, ":");
247
 
        if(p != NULL){
248
 
          p = strtok(p, ",");
249
 
          do{
250
 
            addargument(getplugin(name, plugins), p);
251
 
            p = strtok(NULL, ",");
252
 
          } while (p != NULL);
253
 
        }
254
 
      }
255
 
      break;
256
 
    case 'd':
257
 
      if (arg != NULL){
258
 
        getplugin(arg, plugins)->disabled = true;
259
 
      }
260
 
      break;
261
 
    case 128:
262
 
      plugindir = arg;
263
 
      break;
264
 
    case 129:
 
377
    case 'g':                   /* --global-options */
 
378
      if (arg != NULL){
 
379
        char *p;
 
380
        while((p = strsep(&arg, ",")) != NULL){
 
381
          if(p[0] == '\0'){
 
382
            continue;
 
383
          }
 
384
          if(not add_argument(getplugin(NULL), p)){
 
385
            perror("add_argument");
 
386
            return ARGP_ERR_UNKNOWN;
 
387
          }
 
388
        }
 
389
      }
 
390
      break;
 
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 */
 
400
      if (arg != NULL){
 
401
        char *p_name = strsep(&arg, ":");
 
402
        if(p_name[0] == '\0' or arg == NULL){
 
403
          break;
 
404
        }
 
405
        char *opt = strsep(&arg, ":");
 
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 */
265
465
      uid = (uid_t)strtol(arg, NULL, 10);
266
466
      break;
267
 
    case 130:
 
467
    case 131:                   /* --groupid */
268
468
      gid = (gid_t)strtol(arg, NULL, 10);
269
469
      break;
270
 
    case 131:
 
470
    case 132:                   /* --debug */
271
471
      debug = true;
272
472
      break;
273
473
    case ARGP_KEY_ARG:
274
 
      if(plus_options != NULL or arg == NULL or arg[0] != '+'){
275
 
        argp_usage (state);
276
 
      }
277
 
      plus_options = arg;
278
 
      break;
279
 
    case ARGP_KEY_END:
280
 
      break;
281
 
    default:
282
 
      return ARGP_ERR_UNKNOWN;
283
 
    }
284
 
    return 0;
285
 
  }
286
 
  
287
 
  plugin *plugin_list = NULL;
288
 
  
289
 
  struct argp argp = { .options = options, .parser = parse_opt,
290
 
                       .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 = "",
291
525
                       .doc = "Mandos plugin runner -- Run plugins" };
292
526
  
293
 
  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);
294
530
  if (ret == ARGP_ERR_UNKNOWN){
295
 
    fprintf(stderr, "Unkown error while parsing arguments\n");
 
531
    fprintf(stderr, "Unknown error while parsing arguments\n");
296
532
    exitstatus = EXIT_FAILURE;
297
 
    goto end;
 
533
    goto fallback;
298
534
  }
299
535
  
300
 
  if(plus_options){
301
 
    /* This is a mangled argument in the form of
302
 
     "+--option+--other-option=parameter+--yet-another-option", etc */
303
 
    /* Make new argc and argv vars, and call argp_parse() again. */
304
 
    plus_options++;             /* skip the first '+' character */
305
 
    const char delims[] = "+";
306
 
    char *arg;
307
 
    int new_argc = 1;
308
 
    plus_argv = malloc(sizeof(char*) * 2);
309
 
    if(plus_argv == NULL){
 
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
  }  
 
545
  if(conffp != NULL){
 
546
    char *org_line = NULL;
 
547
    char *p, *arg, *new_arg, *line;
 
548
    size_t size = 0;
 
549
    ssize_t sret;
 
550
    const char whitespace_delims[] = " \r\t\f\v\n";
 
551
    const char comment_delim[] = "#";
 
552
 
 
553
    custom_argc = 1;
 
554
    custom_argv = malloc(sizeof(char*) * 2);
 
555
    if(custom_argv == NULL){
310
556
      perror("malloc");
311
557
      exitstatus = EXIT_FAILURE;
312
 
      goto end;
313
 
    }
314
 
    plus_argv[0] = argv[0];
315
 
    plus_argv[1] = NULL;
316
 
    arg = strtok(plus_options, delims); /* Get first argument */
317
 
    while(arg != NULL){
318
 
      new_argc++;
319
 
      plus_argv = realloc(plus_argv, sizeof(char *)
320
 
                         * ((unsigned int) new_argc + 1));
321
 
      if(plus_argv == NULL){
322
 
        perror("realloc");
323
 
        exitstatus = EXIT_FAILURE;
324
 
        goto end;
325
 
      }
326
 
      plus_argv[new_argc-1] = arg;
327
 
      plus_argv[new_argc] = NULL;
328
 
      arg = strtok(NULL, delims); /* Get next argument */
329
 
    }
330
 
    ret = argp_parse (&argp, new_argc, plus_argv, 0, 0, &plugin_list);
 
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 */
 
565
    while(true){
 
566
      sret = getline(&org_line, &size, conffp);
 
567
      if(sret == -1){
 
568
        break;
 
569
      }
 
570
 
 
571
      line = org_line;
 
572
      arg = strsep(&line, comment_delim);
 
573
      while((p = strsep(&arg, whitespace_delims)) != NULL){
 
574
        if(p[0] == '\0'){
 
575
          continue;
 
576
        }
 
577
        new_arg = strdup(p);
 
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;        
 
596
      }
 
597
    }
 
598
    free(org_line);
 
599
  } else {
 
600
    /* Check for harmful errors and go to fallback. Other errors might
 
601
       not affect opening plugins */
 
602
    if (errno == EMFILE or errno == ENFILE or errno == ENOMEM){
 
603
      perror("fopen");
 
604
      exitstatus = EXIT_FAILURE;
 
605
      goto fallback;
 
606
    }
 
607
  }
 
608
  /* If there was any arguments from configuration file,
 
609
     pass them to parser as command arguments */
 
610
  if(custom_argv != NULL){
 
611
    ret = argp_parse (&argp, custom_argc, custom_argv, ARGP_IN_ORDER,
 
612
                      0, NULL);
331
613
    if (ret == ARGP_ERR_UNKNOWN){
332
 
      fprintf(stderr, "Unkown error while parsing arguments\n");
 
614
      fprintf(stderr, "Unknown error while parsing arguments\n");
333
615
      exitstatus = EXIT_FAILURE;
334
 
      goto end;
 
616
      goto fallback;
335
617
    }
336
618
  }
337
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
  
338
629
  if(debug){
339
630
    for(plugin *p = plugin_list; p != NULL; p=p->next){
340
631
      fprintf(stderr, "Plugin: %s has %d arguments\n",
342
633
      for(char **a = p->argv; *a != NULL; a++){
343
634
        fprintf(stderr, "\tArg: %s\n", *a);
344
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
      }
345
640
    }
346
641
  }
347
642
  
 
643
  /* Strip permissions down to nobody */
348
644
  ret = setuid(uid);
349
645
  if (ret == -1){
350
646
    perror("setuid");
351
 
  }
352
 
  
 
647
  }  
353
648
  setgid(gid);
354
649
  if (ret == -1){
355
650
    perror("setgid");
356
651
  }
357
652
  
358
 
  dir = opendir(plugindir);
 
653
  if (plugindir == NULL){
 
654
    dir = opendir(PDIR);
 
655
  } else {
 
656
    dir = opendir(plugindir);
 
657
  }
 
658
  
359
659
  if(dir == NULL){
360
660
    perror("Could not open plugin dir");
361
661
    exitstatus = EXIT_FAILURE;
362
 
    goto end;
 
662
    goto fallback;
363
663
  }
364
664
  
365
665
  /* Set the FD_CLOEXEC flag on the directory, if possible */
370
670
      if(ret < 0){
371
671
        perror("set_cloexec_flag");
372
672
        exitstatus = EXIT_FAILURE;
373
 
        goto end;
 
673
        goto fallback;
374
674
      }
375
675
    }
376
676
  }
377
677
  
378
678
  FD_ZERO(&rfds_all);
379
679
  
 
680
  /* Read and execute any executable in the plugin directory*/
380
681
  while(true){
381
682
    dirst = readdir(dir);
382
683
    
383
 
    // All directory entries have been processed
 
684
    /* All directory entries have been processed */
384
685
    if(dirst == NULL){
385
686
      if (errno == EBADF){
386
687
        perror("readdir");
387
688
        exitstatus = EXIT_FAILURE;
388
 
        goto end;
 
689
        goto fallback;
389
690
      }
390
691
      break;
391
692
    }
392
693
    
393
694
    d_name_len = strlen(dirst->d_name);
394
695
    
395
 
    // Ignore dotfiles, backup files and other junk
 
696
    /* Ignore dotfiles, backup files and other junk */
396
697
    {
397
698
      bool bad_name = false;
398
699
      
400
701
      
401
702
      const char const *bad_suffixes[] = { "~", "#", ".dpkg-new",
402
703
                                           ".dpkg-old",
 
704
                                           ".dpkg-bak",
403
705
                                           ".dpkg-divert", NULL };
404
706
      for(const char **pre = bad_prefixes; *pre != NULL; pre++){
405
707
        size_t pre_len = strlen(*pre);
413
715
          break;
414
716
        }
415
717
      }
416
 
      
417
718
      if(bad_name){
418
719
        continue;
419
720
      }
420
 
      
421
721
      for(const char **suf = bad_suffixes; *suf != NULL; suf++){
422
722
        size_t suf_len = strlen(*suf);
423
723
        if((d_name_len >= suf_len)
436
736
        continue;
437
737
      }
438
738
    }
439
 
    
440
 
    char *filename = malloc(d_name_len + strlen(plugindir) + 2);
441
 
    if (filename == NULL){
442
 
      perror("malloc");
443
 
      exitstatus = EXIT_FAILURE;
444
 
      goto end;
445
 
    }
446
 
    strcpy(filename, plugindir); /* Spurious warning */
447
 
    strcat(filename, "/");      /* Spurious warning */
448
 
    strcat(filename, dirst->d_name); /* Spurious warning */
 
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");
 
748
      continue;
 
749
    }
449
750
    
450
751
    ret = stat(filename, &st);
451
752
    if (ret == -1){
452
753
      perror("stat");
453
 
      exitstatus = EXIT_FAILURE;
454
 
      goto end;
 
754
      free(filename);
 
755
      continue;
455
756
    }
456
 
    
 
757
 
 
758
    /* Ignore non-executable files */
457
759
    if (not S_ISREG(st.st_mode) or (access(filename, X_OK) != 0)){
458
760
      if(debug){
459
761
        fprintf(stderr, "Ignoring plugin dir entry \"%s\""
462
764
      free(filename);
463
765
      continue;
464
766
    }
465
 
    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){
466
775
      if(debug){
467
776
        fprintf(stderr, "Ignoring disabled plugin \"%s\"\n",
468
777
                dirst->d_name);
470
779
      free(filename);
471
780
      continue;
472
781
    }
473
 
    plugin *p = getplugin(dirst->d_name, &plugin_list);
474
782
    {
475
783
      /* Add global arguments to argument list for this plugin */
476
 
      plugin *g = getplugin(NULL, &plugin_list);
477
 
      for(char **a = g->argv + 1; *a != NULL; a++){
478
 
        addargument(p, *a);
479
 
      }
480
 
    }
481
 
    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];
482
811
    ret = pipe(pipefd);
483
812
    if (ret == -1){
484
813
      perror("pipe");
485
814
      exitstatus = EXIT_FAILURE;
486
 
      goto end;
 
815
      goto fallback;
487
816
    }
 
817
    /* Ask OS to automatic close the pipe on exec */
488
818
    ret = set_cloexec_flag(pipefd[0]);
489
819
    if(ret < 0){
490
820
      perror("set_cloexec_flag");
491
821
      exitstatus = EXIT_FAILURE;
492
 
      goto end;
 
822
      goto fallback;
493
823
    }
494
824
    ret = set_cloexec_flag(pipefd[1]);
495
825
    if(ret < 0){
496
826
      perror("set_cloexec_flag");
497
827
      exitstatus = EXIT_FAILURE;
498
 
      goto end;
 
828
      goto fallback;
499
829
    }
500
830
    /* Block SIGCHLD until process is safely in process list */
501
831
    ret = sigprocmask (SIG_BLOCK, &sigchld_action.sa_mask, NULL);
502
832
    if(ret < 0){
503
833
      perror("sigprocmask");
504
834
      exitstatus = EXIT_FAILURE;
505
 
      goto end;
 
835
      goto fallback;
506
836
    }
507
 
    // Starting a new process to be watched
 
837
    /* Starting a new process to be watched */
508
838
    pid_t pid = fork();
 
839
    if(pid == -1){
 
840
      perror("fork");
 
841
      exitstatus = EXIT_FAILURE;
 
842
      goto fallback;
 
843
    }
509
844
    if(pid == 0){
510
845
      /* this is the child process */
511
846
      ret = sigaction(SIGCHLD, &old_sigchld_action, NULL);
513
848
        perror("sigaction");
514
849
        _exit(EXIT_FAILURE);
515
850
      }
516
 
      ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
 
851
      ret = sigprocmask(SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
517
852
      if(ret < 0){
518
853
        perror("sigprocmask");
519
854
        _exit(EXIT_FAILURE);
520
855
      }
521
 
 
 
856
      
522
857
      ret = dup2(pipefd[1], STDOUT_FILENO); /* replace our stdout */
523
858
      if(ret == -1){
524
859
        perror("dup2");
530
865
           above and must now close it manually here. */
531
866
        closedir(dir);
532
867
      }
533
 
      if(execv(filename, p->argv) < 0){
534
 
        perror("execv");
535
 
        _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
        }
536
878
      }
537
879
      /* no return */
538
880
    }
539
 
    /* parent process */
 
881
    /* Parent process */
 
882
    close(pipefd[1]);           /* Close unused write end of pipe */
540
883
    free(filename);
541
 
    close(pipefd[1]);           /* close unused write end of pipe */
542
 
    process *new_process = malloc(sizeof(process));
543
 
    if (new_process == NULL){
544
 
      perror("malloc");
 
884
    plugin *new_plugin = getplugin(dirst->d_name);
 
885
    if (new_plugin == NULL){
 
886
      perror("getplugin");
545
887
      ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
546
888
      if(ret < 0){
547
 
        perror("sigprocmask");
 
889
        perror("sigprocmask");
548
890
      }
549
891
      exitstatus = EXIT_FAILURE;
550
 
      goto end;
 
892
      goto fallback;
551
893
    }
552
894
    
553
 
    *new_process = (struct process){ .pid = pid,
554
 
                                     .fd = pipefd[0],
555
 
                                     .next = process_list };
556
 
    // List handling
557
 
    process_list = new_process;
 
895
    new_plugin->pid = pid;
 
896
    new_plugin->fd = pipefd[0];
 
897
    
558
898
    /* Unblock SIGCHLD so signal handler can be run if this process
559
899
       has already completed */
560
900
    ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
561
901
    if(ret < 0){
562
902
      perror("sigprocmask");
563
903
      exitstatus = EXIT_FAILURE;
564
 
      goto end;
565
 
    }
566
 
    
567
 
    FD_SET(new_process->fd, &rfds_all);
568
 
    
569
 
    if (maxfd < new_process->fd){
570
 
      maxfd = new_process->fd;
571
 
    }
572
 
    
573
 
  }
574
 
  
575
 
  /* Free the plugin list */
576
 
  for(plugin *next; plugin_list != NULL; plugin_list = next){
577
 
    next = plugin_list->next;
578
 
    free(plugin_list->argv);
579
 
    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
    }
580
912
  }
581
913
  
582
914
  closedir(dir);
583
915
  dir = NULL;
584
 
    
585
 
  if (process_list == NULL){
586
 
    fprintf(stderr, "No plugin processes started, exiting\n");
587
 
    exitstatus = EXIT_FAILURE;
588
 
    goto end;
 
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
    }
589
926
  }
590
 
  while(process_list){
 
927
  
 
928
  /* Main loop while running plugins exist */
 
929
  while(plugin_list){
591
930
    fd_set rfds = rfds_all;
592
931
    int select_ret = select(maxfd+1, &rfds, NULL, NULL, NULL);
593
932
    if (select_ret == -1){
594
933
      perror("select");
595
934
      exitstatus = EXIT_FAILURE;
596
 
      goto end;
 
935
      goto fallback;
597
936
    }
598
937
    /* OK, now either a process completed, or something can be read
599
938
       from one of them */
600
 
    for(process *proc = process_list; proc ; proc = proc->next){
 
939
    for(plugin *proc = plugin_list; proc != NULL;){
601
940
      /* Is this process completely done? */
602
941
      if(proc->eof and proc->completed){
603
942
        /* Only accept the plugin output if it exited cleanly */
604
943
        if(not WIFEXITED(proc->status)
605
944
           or WEXITSTATUS(proc->status) != 0){
606
945
          /* Bad exit by plugin */
 
946
 
607
947
          if(debug){
608
948
            if(WIFEXITED(proc->status)){
609
 
              fprintf(stderr, "Plugin %d exited with status %d\n",
610
 
                      proc->pid, WEXITSTATUS(proc->status));
 
949
              fprintf(stderr, "Plugin %u exited with status %d\n",
 
950
                      (unsigned int) (proc->pid),
 
951
                      WEXITSTATUS(proc->status));
611
952
            } else if(WIFSIGNALED(proc->status)) {
612
 
              fprintf(stderr, "Plugin %d killed by signal %d\n",
613
 
                      proc->pid, WTERMSIG(proc->status));
 
953
              fprintf(stderr, "Plugin %u killed by signal %d\n",
 
954
                      (unsigned int) (proc->pid),
 
955
                      WTERMSIG(proc->status));
614
956
            } else if(WCOREDUMP(proc->status)){
615
 
              fprintf(stderr, "Plugin %d dumped core\n", proc->pid);
 
957
              fprintf(stderr, "Plugin %d dumped core\n",
 
958
                      (unsigned int) (proc->pid));
616
959
            }
617
960
          }
 
961
          
618
962
          /* Remove the plugin */
619
963
          FD_CLR(proc->fd, &rfds_all);
 
964
 
620
965
          /* Block signal while modifying process_list */
621
 
          ret = sigprocmask (SIG_BLOCK, &sigchld_action.sa_mask, NULL);
 
966
          ret = sigprocmask(SIG_BLOCK, &sigchld_action.sa_mask, NULL);
622
967
          if(ret < 0){
623
968
            perror("sigprocmask");
624
969
            exitstatus = EXIT_FAILURE;
625
 
            goto end;
626
 
          }
627
 
          /* Delete this process entry from the list */
628
 
          if(process_list == proc){
629
 
            /* First one - simple */
630
 
            process_list = proc->next;
631
 
          } else {
632
 
            /* Second one or later */
633
 
            for(process *p = process_list; p != NULL; p = p->next){
634
 
              if(p->next == proc){
635
 
                p->next = proc->next;
636
 
                break;
637
 
              }
638
 
            }
639
 
          }
 
970
            goto fallback;
 
971
          }
 
972
          
 
973
          plugin *next_plugin = proc->next;
 
974
          free_plugin(proc);
 
975
          proc = next_plugin;
 
976
          
640
977
          /* We are done modifying process list, so unblock signal */
641
978
          ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask,
642
979
                             NULL);
643
980
          if(ret < 0){
644
981
            perror("sigprocmask");
645
 
          }
646
 
          free(proc->buffer);
647
 
          free(proc);
648
 
          /* We deleted this process from the list, so we can't go
649
 
             proc->next.  Therefore, start over from the beginning of
650
 
             the process list */
651
 
          break;
 
982
            exitstatus = EXIT_FAILURE;
 
983
            goto fallback;
 
984
          }
 
985
          
 
986
          if(plugin_list == NULL){
 
987
            break;
 
988
          }
 
989
          
 
990
          continue;
652
991
        }
 
992
        
653
993
        /* This process exited nicely, so print its buffer */
654
 
        for(size_t written = 0; written < proc->buffer_length;
655
 
            written += (size_t)ret){
656
 
          ret = TEMP_FAILURE_RETRY(write(STDOUT_FILENO,
657
 
                                         proc->buffer + written,
658
 
                                         proc->buffer_length
659
 
                                         - written));
660
 
          if(ret < 0){
661
 
            perror("write");
662
 
            exitstatus = EXIT_FAILURE;
663
 
            goto end;
664
 
          }
 
994
        
 
995
        bool bret = print_out_password(proc->buffer,
 
996
                                       proc->buffer_length);
 
997
        if(not bret){
 
998
          perror("print_out_password");
 
999
          exitstatus = EXIT_FAILURE;
665
1000
        }
666
 
        goto end;
 
1001
        goto fallback;
667
1002
      }
 
1003
      
668
1004
      /* This process has not completed.  Does it have any output? */
669
1005
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){
670
1006
        /* This process had nothing to say at this time */
 
1007
        proc = proc->next;
671
1008
        continue;
672
1009
      }
673
1010
      /* Before reading, make the process' data buffer large enough */
677
1014
        if (proc->buffer == NULL){
678
1015
          perror("malloc");
679
1016
          exitstatus = EXIT_FAILURE;
680
 
          goto end;
 
1017
          goto fallback;
681
1018
        }
682
1019
        proc->buffer_size += BUFFER_SIZE;
683
1020
      }
686
1023
                 BUFFER_SIZE);
687
1024
      if(ret < 0){
688
1025
        /* Read error from this process; ignore the error */
 
1026
        proc = proc->next;
689
1027
        continue;
690
1028
      }
691
1029
      if(ret == 0){
696
1034
      }
697
1035
    }
698
1036
  }
699
 
  if(process_list == NULL){
700
 
    fprintf(stderr, "All plugin processes failed, exiting\n");
701
 
    exitstatus = EXIT_FAILURE;
 
1037
 
 
1038
 
 
1039
 fallback:
 
1040
  
 
1041
  if(plugin_list == NULL or exitstatus != EXIT_SUCCESS){
 
1042
    /* Fallback if all plugins failed, none are found or an error
 
1043
       occured */
 
1044
    bool bret;
 
1045
    fprintf(stderr, "Going to fallback mode using getpass(3)\n");
 
1046
    char *passwordbuffer = getpass("Password: ");
 
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);
 
1054
    if(not bret){
 
1055
      perror("print_out_password");
 
1056
      exitstatus = EXIT_FAILURE;
 
1057
    }
702
1058
  }
703
1059
  
704
 
 end:
705
1060
  /* Restore old signal handler */
706
 
  sigaction(SIGCHLD, &old_sigchld_action, NULL);
707
 
  
708
 
  free(plus_argv);
709
 
  
710
 
  /* Free the plugin list */
711
 
  for(plugin *next; plugin_list != NULL; plugin_list = next){
712
 
    next = plugin_list->next;
713
 
    free(plugin_list->argv);
714
 
    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);
715
1072
  }
716
1073
  
717
1074
  if(dir != NULL){
718
1075
    closedir(dir);
719
1076
  }
720
1077
  
721
 
  /* Free the process list and kill the processes */
722
 
  for(process *next; process_list != NULL; process_list = next){
723
 
    next = process_list->next;
724
 
    close(process_list->fd);
725
 
    ret = kill(process_list->pid, SIGTERM);
726
 
    if(ret == -1 and errno != ESRCH){
727
 
      /* set-uid proccesses migth not get closed */
728
 
      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
      }
729
1087
    }
730
 
    free(process_list->buffer);
731
 
    free(process_list);
732
1088
  }
733
1089
  
734
1090
  /* Wait for any remaining child processes to terminate */
739
1095
    perror("wait");
740
1096
  }
741
1097
  
 
1098
  free_plugin_list();
 
1099
  
 
1100
  free(plugindir);
 
1101
  free(argfile);
 
1102
  
742
1103
  return exitstatus;
743
1104
}