/mandos/trunk

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

« back to all changes in this revision

Viewing changes to plugin-runner.c

  • Committer: Teddy Hogeborn
  • Date: 2016-06-03 17:27:03 UTC
  • Revision ID: teddy@recompile.se-20160603172703-mc6tjor6rhq4xy74
mandos: Bug fix: Do multiprocessing cleanup correctly on exit

* mandos (main): Save module "multiprocessing" and open file "wnull"
                 as scope variables accessible by function cleanup(),
                 since the module and global variable may not be
                 accessible when the cleanup() function is run as
                 scheduled by atexit().

Show diffs side-by-side

added added

removed removed

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