/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-08-16 03:29:08 UTC
  • Revision ID: teddy@fukt.bsnet.se-20080816032908-ihw7c05r2mnyk389
Add feature to specify custom environment variables for plugins.

* plugin-runner.c (plugin): New members "environ" and "envc" to
                            contain possible custom environment.
  (getplugin): Return NULL on failure instead of doing exit(); all
               callers changed.
  (add_to_char_array): New helper function for "add_argument" and
                       "add_environment".
  (addargument): Renamed to "add_argument".  Return bool.  Call
                 "add_to_char_array" to actually do things.
  (add_environment): New; analogous to "add_argument".
  (addcustomargument): Renamed to "add_to_argv" to avoid confusion
                       with "add_argument".
  (main): New options "--global-envs" and "--envs-for" to specify
          custom environment for plugins.  Print environment for
          plugins in debug mode.  Use asprintf instead of strcpy and
          strcat.  Use execve() for plugins with custom environments.
          Free environment for plugin when freeing plugin list.

Show diffs side-by-side

added added

removed removed

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