/mandos/release

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

« back to all changes in this revision

Viewing changes to plugbasedclient.c

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