/mandos/trunk

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

« back to all changes in this revision

Viewing changes to plugbasedclient.c

  • Committer: Teddy Hogeborn
  • Date: 2008-08-02 10:48:24 UTC
  • Revision ID: teddy@fukt.bsnet.se-20080802104824-fx0miwp9o4g9r31e
* plugbasedclient.c (struct process): New fields "eof", "completed",
                                      and "status".
  (handle_sigchld): New function.
  (main): Initialize "dir" to NULL to only closedir() it if necessary.
          Move "process_list" to be a global variable to be accessible
          by "handle_sigchld".  Make "handle_sigchld" handle SIGCHLD.
          Remove redundant check for NULL "dir".  Free "filename" when
          no longer used.  Block SIGCHLD around fork()/exec().
          Restore normal signals in child.  Only loop while running
          processes exist.  Print process buffer when the process is
          done and it has emitted EOF, not when it only emits EOF.
          Remove processes from list which exit non-cleanly.  In
          cleaning up, closedir() if necessary.  Bug fix: set next
          pointer correctly when freeing process list.

* plugins.d/passprompt.c (main): Do not ignore SIGQUIT.

Show diffs side-by-side

added added

removed removed

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