/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 plugin-runner.c

  • Committer: Teddy Hogeborn
  • Date: 2008-08-16 03:29:08 UTC
  • Revision ID: teddy@fukt.bsnet.se-20080816032908-ihw7c05r2mnyk389
Add feature to specify custom environment variables for plugins.

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

Show diffs side-by-side

added added

removed removed

Lines of Context:
21
21
 * Contact the authors at <mandos@fukt.bsnet.se>.
22
22
 */
23
23
 
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 */
 
24
#define _GNU_SOURCE             /* TEMP_FAILURE_RETRY(), getline(),
 
25
                                   asprintf() */
 
26
#include <stddef.h>             /* size_t, NULL */
 
27
#include <stdlib.h>             /* malloc(), exit(), EXIT_FAILURE,
 
28
                                   EXIT_SUCCESS, realloc() */
 
29
#include <stdbool.h>            /* bool, true, false */
 
30
#include <stdio.h>              /* perror, popen(), fileno(),
 
31
                                   fprintf(), stderr, STDOUT_FILENO */
 
32
#include <sys/types.h>          /* DIR, opendir(), stat(), struct
 
33
                                   stat, waitpid(), WIFEXITED(),
 
34
                                   WEXITSTATUS(), wait(), pid_t,
 
35
                                   uid_t, gid_t, getuid(), getgid(),
 
36
                                   dirfd() */
 
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() */
 
41
#include <sys/stat.h>           /* struct stat, stat(), S_ISREG() */
 
42
#include <iso646.h>             /* and, or, not */
 
43
#include <dirent.h>             /* DIR, struct dirent, opendir(),
 
44
                                   readdir(), closedir(), dirfd() */
 
45
#include <unistd.h>             /* struct stat, stat(), S_ISREG(),
 
46
                                   fcntl(), setuid(), setgid(),
 
47
                                   F_GETFD, F_SETFD, FD_CLOEXEC,
 
48
                                   access(), pipe(), fork(), close()
 
49
                                   dup2, STDOUT_FILENO, _exit(),
 
50
                                   execv(), write(), read(),
 
51
                                   close() */
 
52
#include <fcntl.h>              /* fcntl(), F_GETFD, F_SETFD,
 
53
                                   FD_CLOEXEC */
 
54
#include <string.h>             /* strsep, strlen(), asprintf() */
 
55
#include <errno.h>              /* errno */
 
56
#include <argp.h>               /* struct argp_option, struct
 
57
                                   argp_state, struct argp,
 
58
                                   argp_parse(), ARGP_ERR_UNKNOWN,
 
59
                                   ARGP_KEY_END, ARGP_KEY_ARG, error_t */
 
60
#include <signal.h>             /* struct sigaction, sigemptyset(),
 
61
                                   sigaddset(), sigaction(),
 
62
                                   sigprocmask(), SIG_BLOCK, SIGCHLD,
 
63
                                   SIG_UNBLOCK, kill() */
 
64
#include <errno.h>              /* errno, EBADF */
 
65
 
 
66
#define BUFFER_SIZE 256
 
67
#define ARGFILE "/conf/conf.d/mandos/plugin-runner.conf"
 
68
 
 
69
const char *argp_program_version = "plugin-runner 1.0";
 
70
const char *argp_program_bug_address = "<mandos@fukt.bsnet.se>";
40
71
 
41
72
struct process;
42
73
 
46
77
  char *buffer;
47
78
  size_t buffer_size;
48
79
  size_t buffer_length;
 
80
  bool eof;
 
81
  bool completed;
 
82
  int status;
49
83
  struct process *next;
50
84
} process;
51
85
 
52
86
typedef struct plugin{
53
 
  char *name;           /* can be "global" and any plugin name */
 
87
  char *name;                   /* can be NULL or any plugin name */
54
88
  char **argv;
55
89
  int argc;
56
 
  bool disable;
 
90
  char **environ;
 
91
  int envc;
 
92
  bool disabled;
57
93
  struct plugin *next;
58
94
} plugin;
59
95
 
60
 
plugin *getplugin(char *name, plugin **plugin_list){
 
96
static plugin *getplugin(char *name, plugin **plugin_list){
61
97
  for (plugin *p = *plugin_list; p != NULL; p = p->next){
62
98
    if ((p->name == name)
63
99
        or (p->name and name and (strcmp(p->name, name) == 0))){
67
103
  /* Create a new plugin */
68
104
  plugin *new_plugin = malloc(sizeof(plugin));
69
105
  if (new_plugin == NULL){
70
 
    perror("malloc");
71
 
    exit(EXIT_FAILURE);
 
106
    return NULL;
72
107
  }
73
 
  new_plugin->name = name;
 
108
  *new_plugin = (plugin) { .name = name,
 
109
                           .argc = 1,
 
110
                           .envc = 0,
 
111
                           .disabled = false,
 
112
                           .next = *plugin_list };
 
113
  
74
114
  new_plugin->argv = malloc(sizeof(char *) * 2);
75
115
  if (new_plugin->argv == NULL){
76
 
    perror("malloc");
77
 
    exit(EXIT_FAILURE);
 
116
    free(new_plugin);
 
117
    return NULL;
78
118
  }
79
119
  new_plugin->argv[0] = name;
80
120
  new_plugin->argv[1] = NULL;
81
 
  new_plugin->argc = 1;
82
 
  new_plugin->disable = false;
83
 
  new_plugin->next = *plugin_list;
 
121
 
 
122
  new_plugin->environ = malloc(sizeof(char *));
 
123
  if(new_plugin->environ == NULL){
 
124
    free(new_plugin->argv);
 
125
    free(new_plugin);
 
126
    return NULL;
 
127
  }
 
128
  new_plugin->environ[0] = NULL;
84
129
  /* Append the new plugin to the list */
85
130
  *plugin_list = new_plugin;
86
131
  return new_plugin;
87
132
}
88
133
 
89
 
void addarguments(plugin *p, char *arg){
90
 
  p->argv[p->argc] = arg;
91
 
  p->argv = realloc(p->argv, sizeof(char *) * (size_t)(p->argc + 2));
92
 
  if (p->argv == NULL){
93
 
    perror("malloc");
94
 
    exit(EXIT_FAILURE);
95
 
  }
96
 
  p->argc++;
97
 
  p->argv[p->argc] = NULL;
98
 
}
99
 
        
100
 
#define BUFFER_SIZE 256
101
 
 
102
 
const char *argp_program_version =
103
 
  "plugbasedclient 0.9";
104
 
const char *argp_program_bug_address =
105
 
  "<mandos@fukt.bsnet.se>";
106
 
static char doc[] =
107
 
  "Mandos plugin runner -- Run Mandos plugins";
108
 
/* A description of the arguments we accept. */
109
 
static char args_doc[] = "";
 
134
/* Helper function for add_argument and add_environment */
 
135
static bool add_to_char_array(const char *new, char ***array,
 
136
                              int *len){
 
137
  /* Resize the pointed-to array to hold one more pointer */
 
138
  *array = realloc(*array, sizeof(char *)
 
139
                   * (size_t) ((*len) + 2));
 
140
  /* Malloc check */
 
141
  if(*array == NULL){
 
142
    return false;
 
143
  }
 
144
  /* Make a copy of the new string */
 
145
  char *copy = strdup(new);
 
146
  if(copy == NULL){
 
147
    return false;
 
148
  }
 
149
  /* Insert the copy */
 
150
  (*array)[*len] = copy;
 
151
  (*len)++;
 
152
  /* Add a new terminating NULL pointer to the last element */
 
153
  (*array)[*len] = NULL;
 
154
  return true;
 
155
}
 
156
 
 
157
/* Add to a plugin's argument vector */
 
158
static bool add_argument(plugin *p, const char *arg){
 
159
  if(p == NULL){
 
160
    return false;
 
161
  }
 
162
  return add_to_char_array(arg, &(p->argv), &(p->argc));
 
163
}
 
164
 
 
165
/* Add to a plugin's environment */
 
166
static bool add_environment(plugin *p, const char *def){
 
167
  if(p == NULL){
 
168
    return false;
 
169
  }
 
170
  return add_to_char_array(def, &(p->environ), &(p->envc));
 
171
}
 
172
 
 
173
 
 
174
/*
 
175
 * Based on the example in the GNU LibC manual chapter 13.13 "File
 
176
 * Descriptor Flags".
 
177
 * *Note File Descriptor Flags:(libc)Descriptor Flags.
 
178
 */
 
179
static int set_cloexec_flag(int fd)
 
180
{
 
181
  int ret = fcntl(fd, F_GETFD, 0);
 
182
  /* If reading the flags failed, return error indication now. */
 
183
  if(ret < 0){
 
184
    return ret;
 
185
  }
 
186
  /* Store modified flag word in the descriptor. */
 
187
  return fcntl(fd, F_SETFD, ret | FD_CLOEXEC);
 
188
}
 
189
 
 
190
process *process_list = NULL;
 
191
 
 
192
/* Mark a process as completed when it exits, and save its exit
 
193
   status. */
 
194
void handle_sigchld(__attribute__((unused)) int sig){
 
195
  process *proc = process_list;
 
196
  int status;
 
197
  pid_t pid = wait(&status);
 
198
  if(pid == -1){
 
199
    perror("wait");
 
200
    return;
 
201
  }
 
202
  while(proc != NULL and proc->pid != pid){
 
203
    proc = proc->next;
 
204
  }
 
205
  if(proc == NULL){
 
206
    /* Process not found in process list */
 
207
    return;
 
208
  }
 
209
  proc->status = status;
 
210
  proc->completed = true;
 
211
}
 
212
 
 
213
bool print_out_password(const char *buffer, size_t length){
 
214
  ssize_t ret;
 
215
  if(length>0 and buffer[length-1] == '\n'){
 
216
    length--;
 
217
  }
 
218
  for(size_t written = 0; written < length; written += (size_t)ret){
 
219
    ret = TEMP_FAILURE_RETRY(write(STDOUT_FILENO, buffer + written,
 
220
                                   length - written));
 
221
    if(ret < 0){
 
222
      return false;
 
223
    }
 
224
  }
 
225
  return true;
 
226
}
 
227
 
 
228
char **add_to_argv(char **argv, int *argc, char *arg){
 
229
  if (argv == NULL){
 
230
    *argc = 1;
 
231
    argv = malloc(sizeof(char*) * 2);
 
232
    if(argv == NULL){
 
233
      return NULL;
 
234
    }
 
235
    argv[0] = NULL;     /* Will be set to argv[0] in main before parsing */
 
236
    argv[1] = NULL;
 
237
  }
 
238
  *argc += 1;
 
239
  argv = realloc(argv, sizeof(char *)
 
240
                  * ((unsigned int) *argc + 1));
 
241
  if(argv == NULL){
 
242
    return NULL;
 
243
  }
 
244
  argv[*argc-1] = arg;
 
245
  argv[*argc] = NULL;   
 
246
  return argv;
 
247
}
110
248
 
111
249
int main(int argc, char *argv[]){
112
 
  const char *plugindir = "plugins.d";
 
250
  const char *plugindir = "/lib/mandos/plugins.d";
 
251
  const char *argfile = ARGFILE;
 
252
  FILE *conffp;
113
253
  size_t d_name_len;
114
 
  DIR *dir;
 
254
  DIR *dir = NULL;
115
255
  struct dirent *dirst;
116
256
  struct stat st;
117
 
  fd_set rfds_orig;
 
257
  fd_set rfds_all;
118
258
  int ret, maxfd = 0;
119
 
  process *process_list = NULL;
120
 
 
 
259
  uid_t uid = 65534;
 
260
  gid_t gid = 65534;
 
261
  bool debug = false;
 
262
  int exitstatus = EXIT_SUCCESS;
 
263
  struct sigaction old_sigchld_action;
 
264
  struct sigaction sigchld_action = { .sa_handler = handle_sigchld,
 
265
                                      .sa_flags = SA_NOCLDSTOP };
 
266
  char **custom_argv = NULL;
 
267
  int custom_argc = 0;
 
268
  
 
269
  /* Establish a signal handler */
 
270
  sigemptyset(&sigchld_action.sa_mask);
 
271
  ret = sigaddset(&sigchld_action.sa_mask, SIGCHLD);
 
272
  if(ret < 0){
 
273
    perror("sigaddset");
 
274
    exit(EXIT_FAILURE);
 
275
  }
 
276
  ret = sigaction(SIGCHLD, &sigchld_action, &old_sigchld_action);
 
277
  if(ret < 0){
 
278
    perror("sigaction");
 
279
    exit(EXIT_FAILURE);
 
280
  }
 
281
  
121
282
  /* The options we understand. */
122
283
  struct argp_option options[] = {
123
284
    { .name = "global-options", .key = 'g',
124
 
      .arg = "option[,option[,...]]", .flags = 0,
125
 
      .doc = "Options effecting all plugins" },
 
285
      .arg = "OPTION[,OPTION[,...]]",
 
286
      .doc = "Options passed to all plugins" },
 
287
    { .name = "global-envs", .key = 'e',
 
288
      .arg = "VAR=value",
 
289
      .doc = "Environment variable passed to all plugins" },
126
290
    { .name = "options-for", .key = 'o',
127
 
      .arg = "plugin:option[,option[,...]]", .flags = 0,
128
 
      .doc = "Options effecting only specified plugins" },
129
 
    { .name = "disable-plugin", .key = 'd',
130
 
      .arg = "Plugin[,Plugin[,...]]", .flags = 0,
131
 
      .doc = "Option to disable specififed plugins" },
 
291
      .arg = "PLUGIN:OPTION[,OPTION[,...]]",
 
292
      .doc = "Options passed only to specified plugin" },
 
293
    { .name = "envs-for", .key = 'f',
 
294
      .arg = "PLUGIN:ENV=value",
 
295
      .doc = "Environment variable passed to specified plugin" },
 
296
    { .name = "disable", .key = 'd',
 
297
      .arg = "PLUGIN",
 
298
      .doc = "Disable a specific plugin", .group = 1 },
132
299
    { .name = "plugin-dir", .key = 128,
133
 
      .arg = "Directory", .flags = 0,
134
 
      .doc = "Option to change directory to search for plugins" },
 
300
      .arg = "DIRECTORY",
 
301
      .doc = "Specify a different plugin directory", .group = 2 },
 
302
    { .name = "userid", .key = 129,
 
303
      .arg = "ID", .flags = 0,
 
304
      .doc = "User ID the plugins will run as", .group = 2 },
 
305
    { .name = "groupid", .key = 130,
 
306
      .arg = "ID", .flags = 0,
 
307
      .doc = "Group ID the plugins will run as", .group = 2 },
 
308
    { .name = "debug", .key = 131,
 
309
      .doc = "Debug mode", .group = 3 },
135
310
    { .name = NULL }
136
311
  };
137
312
  
138
313
  error_t parse_opt (int key, char *arg, struct argp_state *state) {
139
 
       /* Get the INPUT argument from `argp_parse', which we
140
 
          know is a pointer to our arguments structure. */
 
314
    /* Get the INPUT argument from `argp_parse', which we know is a
 
315
       pointer to our plugin list pointer. */
141
316
    plugin **plugins = state->input;
142
317
    switch (key) {
143
318
    case 'g':
144
319
      if (arg != NULL){
145
 
        char *p = strtok(arg, ",");
146
 
        do{
147
 
          addarguments(getplugin(NULL, plugins), p);
148
 
          p = strtok(NULL, ",");
149
 
        } while (p);
 
320
        char *p;
 
321
        while((p = strsep(&arg, ",")) != NULL){
 
322
          if(p[0] == '\0'){
 
323
            continue;
 
324
          }
 
325
          if(not add_argument(getplugin(NULL, plugins), p)){
 
326
            perror("add_argument");
 
327
            return ARGP_ERR_UNKNOWN;
 
328
          }
 
329
        }
 
330
      }
 
331
      break;
 
332
    case 'e':
 
333
      if(arg == NULL){
 
334
        break;
 
335
      }
 
336
      {
 
337
        char *envdef = strdup(arg);
 
338
        if(envdef == NULL){
 
339
          break;
 
340
        }
 
341
        if(not add_environment(getplugin(NULL, plugins), envdef)){
 
342
          perror("add_environment");
 
343
        }
150
344
      }
151
345
      break;
152
346
    case 'o':
153
347
      if (arg != NULL){
154
 
        char *name = strtok(arg, ":");
155
 
        char *p = strtok(NULL, ":");
156
 
        p = strtok(p, ",");
157
 
        do{
158
 
          addarguments(getplugin(name, plugins), p);
159
 
          p = strtok(NULL, ",");
160
 
        } while (p);
 
348
        char *p_name = strsep(&arg, ":");
 
349
        if(p_name[0] == '\0'){
 
350
          break;
 
351
        }
 
352
        char *opt = strsep(&arg, ":");
 
353
        if(opt[0] == '\0'){
 
354
          break;
 
355
        }
 
356
        if(opt != NULL){
 
357
          char *p;
 
358
          while((p = strsep(&opt, ",")) != NULL){
 
359
            if(p[0] == '\0'){
 
360
              continue;
 
361
            }
 
362
            if(not add_argument(getplugin(p_name, plugins), p)){
 
363
              perror("add_argument");
 
364
              return ARGP_ERR_UNKNOWN;
 
365
            }
 
366
          }
 
367
        }
 
368
      }
 
369
      break;
 
370
    case 'f':
 
371
      if(arg == NULL){
 
372
        break;
 
373
      }
 
374
      {
 
375
        char *envdef = strchr(arg, ':');
 
376
        if(envdef == NULL){
 
377
          break;
 
378
        }
 
379
        char *p_name = strndup(arg, (size_t) (envdef-arg));
 
380
        if(p_name == NULL){
 
381
          break;
 
382
        }
 
383
        envdef++;
 
384
        if(not add_environment(getplugin(p_name, plugins), envdef)){
 
385
          perror("add_environment");
 
386
        }
161
387
      }
162
388
      break;
163
389
    case 'd':
164
390
      if (arg != NULL){
165
 
        char *p = strtok(arg, ",");
166
 
        do{
167
 
          getplugin(p, plugins)->disable = true;
168
 
          p = strtok(NULL, ",");
169
 
        } while (p);
 
391
        plugin *p = getplugin(arg, plugins);
 
392
        if(p == NULL){
 
393
          return ARGP_ERR_UNKNOWN;
 
394
        }
 
395
        p->disabled = true;
170
396
      }
171
397
      break;
172
398
    case 128:
173
399
      plugindir = arg;
174
400
      break;
 
401
    case 129:
 
402
      uid = (uid_t)strtol(arg, NULL, 10);
 
403
      break;
 
404
    case 130:
 
405
      gid = (gid_t)strtol(arg, NULL, 10);
 
406
      break;
 
407
    case 131:
 
408
      debug = true;
 
409
      break;
175
410
    case ARGP_KEY_ARG:
176
 
      argp_usage (state);
 
411
      fprintf(stderr, "Ignoring unknown argument \"%s\"\n", arg);
177
412
      break;
178
413
    case ARGP_KEY_END:
179
414
      break;
182
417
    }
183
418
    return 0;
184
419
  }
185
 
 
 
420
  
186
421
  plugin *plugin_list = NULL;
187
422
  
188
423
  struct argp argp = { .options = options, .parser = parse_opt,
189
 
                       .args_doc = args_doc, .doc = doc };
190
 
 
191
 
  argp_parse (&argp, argc, argv, 0, 0, &plugin_list);
192
 
 
193
 
/*   for(plugin *p = plugin_list; p != NULL; p=p->next){ */
194
 
/*     fprintf(stderr, "Plugin: %s has %d arguments\n", p->name ? p->name : "Global", p->argc); */
195
 
/*     for(char **a = p->argv + 1; *a != NULL; a++){ */
196
 
/*       fprintf(stderr, "\tArg: %s\n", *a); */
197
 
/*     } */
198
 
/*   } */
199
 
  
200
 
/*   return 0; */
201
 
 
 
424
                       .args_doc = "[+PLUS_SEPARATED_OPTIONS]",
 
425
                       .doc = "Mandos plugin runner -- Run plugins" };
 
426
  
 
427
  ret = argp_parse (&argp, argc, argv, 0, 0, &plugin_list);
 
428
  if (ret == ARGP_ERR_UNKNOWN){
 
429
    fprintf(stderr, "Unknown error while parsing arguments\n");
 
430
    exitstatus = EXIT_FAILURE;
 
431
    goto end;
 
432
  }
 
433
 
 
434
  conffp = fopen(argfile, "r");
 
435
  if(conffp != NULL){
 
436
    char *org_line = NULL;
 
437
    size_t size = 0;
 
438
    ssize_t sret;
 
439
    char *p, *arg, *new_arg, *line;
 
440
    const char whitespace_delims[] = " \r\t\f\v\n";
 
441
    const char comment_delim[] = "#";
 
442
 
 
443
    while(true){
 
444
      sret = getline(&org_line, &size, conffp);
 
445
      if(sret == -1){
 
446
        break;
 
447
      }
 
448
 
 
449
      line = org_line;
 
450
      arg = strsep(&line, comment_delim);
 
451
      while((p = strsep(&arg, whitespace_delims)) != NULL){
 
452
        if(p[0] == '\0'){
 
453
          continue;
 
454
        }
 
455
        new_arg = strdup(p);
 
456
        custom_argv = add_to_argv(custom_argv, &custom_argc, new_arg);
 
457
        if (custom_argv == NULL){
 
458
          perror("add_to_argv");
 
459
          exitstatus = EXIT_FAILURE;
 
460
          goto end;
 
461
        }
 
462
      }
 
463
    }
 
464
    free(org_line);
 
465
  } else{
 
466
    /* Check for harmful errors and go to fallback. Other errors might
 
467
       not affect opening plugins */
 
468
    if (errno == EMFILE or errno == ENFILE or errno == ENOMEM){
 
469
      perror("fopen");
 
470
      exitstatus = EXIT_FAILURE;
 
471
      goto end;
 
472
    }
 
473
  }
 
474
 
 
475
  if(custom_argv != NULL){
 
476
    custom_argv[0] = argv[0];
 
477
    ret = argp_parse (&argp, custom_argc, custom_argv, 0, 0, &plugin_list);
 
478
    if (ret == ARGP_ERR_UNKNOWN){
 
479
      fprintf(stderr, "Unknown error while parsing arguments\n");
 
480
      exitstatus = EXIT_FAILURE;
 
481
      goto end;
 
482
    }
 
483
  }
 
484
  
 
485
  if(debug){
 
486
    for(plugin *p = plugin_list; p != NULL; p=p->next){
 
487
      fprintf(stderr, "Plugin: %s has %d arguments\n",
 
488
              p->name ? p->name : "Global", p->argc - 1);
 
489
      for(char **a = p->argv; *a != NULL; a++){
 
490
        fprintf(stderr, "\tArg: %s\n", *a);
 
491
      }
 
492
      fprintf(stderr, "...and %u environment variables\n", p->envc);
 
493
      for(char **a = p->environ; *a != NULL; a++){
 
494
        fprintf(stderr, "\t%s\n", *a);
 
495
      }
 
496
    }
 
497
  }
 
498
  
 
499
  ret = setuid(uid);
 
500
  if (ret == -1){
 
501
    perror("setuid");
 
502
  }
 
503
  
 
504
  setgid(gid);
 
505
  if (ret == -1){
 
506
    perror("setgid");
 
507
  }
 
508
  
202
509
  dir = opendir(plugindir);
203
 
  
204
510
  if(dir == NULL){
205
 
    fprintf(stderr, "Can not open directory\n");
206
 
    return EXIT_FAILURE;
207
 
  }
208
 
  
209
 
  FD_ZERO(&rfds_orig);
 
511
    perror("Could not open plugin dir");
 
512
    exitstatus = EXIT_FAILURE;
 
513
    goto end;
 
514
  }
 
515
  
 
516
  /* Set the FD_CLOEXEC flag on the directory, if possible */
 
517
  {
 
518
    int dir_fd = dirfd(dir);
 
519
    if(dir_fd >= 0){
 
520
      ret = set_cloexec_flag(dir_fd);
 
521
      if(ret < 0){
 
522
        perror("set_cloexec_flag");
 
523
        exitstatus = EXIT_FAILURE;
 
524
        goto end;
 
525
      }
 
526
    }
 
527
  }
 
528
  
 
529
  FD_ZERO(&rfds_all);
210
530
  
211
531
  while(true){
212
532
    dirst = readdir(dir);
213
533
    
214
534
    // All directory entries have been processed
215
535
    if(dirst == NULL){
 
536
      if (errno == EBADF){
 
537
        perror("readdir");
 
538
        exitstatus = EXIT_FAILURE;
 
539
        goto end;
 
540
      }
216
541
      break;
217
542
    }
218
543
    
219
544
    d_name_len = strlen(dirst->d_name);
220
545
    
221
 
    // Ignore dotfiles and backup files
222
 
    if (dirst->d_name[0] == '.'
223
 
        or dirst->d_name[d_name_len - 1] == '~'){
224
 
      continue;
225
 
    }
226
 
 
227
 
    char *filename = malloc(d_name_len + strlen(plugindir) + 2);
228
 
    strcpy(filename, plugindir);
229
 
    strcat(filename, "/");
230
 
    strcat(filename, dirst->d_name);    
231
 
 
232
 
    stat(filename, &st);
233
 
 
234
 
    if (S_ISREG(st.st_mode)
235
 
        and (access(filename, X_OK) == 0)
236
 
        and not (getplugin(dirst->d_name, &plugin_list)->disable)){
237
 
      // Starting a new process to be watched
238
 
      process *new_process = malloc(sizeof(process));
239
 
      int pipefd[2]; 
240
 
      ret = pipe(pipefd);
241
 
      if (ret == -1){
242
 
        perror(argv[0]);
243
 
        goto end;
244
 
      }
245
 
      new_process->pid = fork();
246
 
      if(new_process->pid == 0){
247
 
        /* this is the child process */
 
546
    // Ignore dotfiles, backup files and other junk
 
547
    {
 
548
      bool bad_name = false;
 
549
      
 
550
      const char const *bad_prefixes[] = { ".", "#", NULL };
 
551
      
 
552
      const char const *bad_suffixes[] = { "~", "#", ".dpkg-new",
 
553
                                           ".dpkg-old",
 
554
                                           ".dpkg-divert", NULL };
 
555
      for(const char **pre = bad_prefixes; *pre != NULL; pre++){
 
556
        size_t pre_len = strlen(*pre);
 
557
        if((d_name_len >= pre_len)
 
558
           and strncmp((dirst->d_name), *pre, pre_len) == 0){
 
559
          if(debug){
 
560
            fprintf(stderr, "Ignoring plugin dir entry \"%s\""
 
561
                    " with bad prefix %s\n", dirst->d_name, *pre);
 
562
          }
 
563
          bad_name = true;
 
564
          break;
 
565
        }
 
566
      }
 
567
      
 
568
      if(bad_name){
 
569
        continue;
 
570
      }
 
571
      
 
572
      for(const char **suf = bad_suffixes; *suf != NULL; suf++){
 
573
        size_t suf_len = strlen(*suf);
 
574
        if((d_name_len >= suf_len)
 
575
           and (strcmp((dirst->d_name)+d_name_len-suf_len, *suf)
 
576
                == 0)){
 
577
          if(debug){
 
578
            fprintf(stderr, "Ignoring plugin dir entry \"%s\""
 
579
                    " with bad suffix %s\n", dirst->d_name, *suf);
 
580
          }
 
581
          bad_name = true;
 
582
          break;
 
583
        }
 
584
      }
 
585
      
 
586
      if(bad_name){
 
587
        continue;
 
588
      }
 
589
    }
 
590
 
 
591
    char *filename;
 
592
    ret = asprintf(&filename, "%s/%s", plugindir, dirst->d_name);
 
593
    if(ret < 0){
 
594
      perror("asprintf");
 
595
      continue;
 
596
    }
 
597
    
 
598
    ret = stat(filename, &st);
 
599
    if (ret == -1){
 
600
      perror("stat");
 
601
      free(filename);
 
602
      continue;
 
603
    }
 
604
    
 
605
    if (not S_ISREG(st.st_mode) or (access(filename, X_OK) != 0)){
 
606
      if(debug){
 
607
        fprintf(stderr, "Ignoring plugin dir entry \"%s\""
 
608
                " with bad type or mode\n", filename);
 
609
      }
 
610
      free(filename);
 
611
      continue;
 
612
    }
 
613
    plugin *p = getplugin(dirst->d_name, &plugin_list);
 
614
    if(p == NULL){
 
615
      perror("getplugin");
 
616
      free(filename);
 
617
      continue;
 
618
    }
 
619
    if(p->disabled){
 
620
      if(debug){
 
621
        fprintf(stderr, "Ignoring disabled plugin \"%s\"\n",
 
622
                dirst->d_name);
 
623
      }
 
624
      free(filename);
 
625
      continue;
 
626
    }
 
627
    {
 
628
      /* Add global arguments to argument list for this plugin */
 
629
      plugin *g = getplugin(NULL, &plugin_list);
 
630
      if(g != NULL){
 
631
        for(char **a = g->argv + 1; *a != NULL; a++){
 
632
          if(not add_argument(p, *a)){
 
633
            perror("add_argument");
 
634
          }
 
635
        }
 
636
        /* Add global environment variables */
 
637
        for(char **e = g->environ; *e != NULL; e++){
 
638
          if(not add_environment(p, *e)){
 
639
            perror("add_environment");
 
640
          }
 
641
        }
 
642
      }
 
643
    }
 
644
    /* If this plugin has any environment variables, we will call
 
645
       using execve and need to duplicate the environment from this
 
646
       process, too. */
 
647
    if(p->environ[0] != NULL){
 
648
      for(char **e = environ; *e != NULL; e++){
 
649
        char *copy = strdup(*e);
 
650
        if(copy == NULL){
 
651
          perror("strdup");
 
652
          continue;
 
653
        }
 
654
        if(not add_environment(p, copy)){
 
655
          perror("add_environment");
 
656
        }
 
657
      }
 
658
    }
 
659
    
 
660
    int pipefd[2]; 
 
661
    ret = pipe(pipefd);
 
662
    if (ret == -1){
 
663
      perror("pipe");
 
664
      exitstatus = EXIT_FAILURE;
 
665
      goto end;
 
666
    }
 
667
    ret = set_cloexec_flag(pipefd[0]);
 
668
    if(ret < 0){
 
669
      perror("set_cloexec_flag");
 
670
      exitstatus = EXIT_FAILURE;
 
671
      goto end;
 
672
    }
 
673
    ret = set_cloexec_flag(pipefd[1]);
 
674
    if(ret < 0){
 
675
      perror("set_cloexec_flag");
 
676
      exitstatus = EXIT_FAILURE;
 
677
      goto end;
 
678
    }
 
679
    /* Block SIGCHLD until process is safely in process list */
 
680
    ret = sigprocmask (SIG_BLOCK, &sigchld_action.sa_mask, NULL);
 
681
    if(ret < 0){
 
682
      perror("sigprocmask");
 
683
      exitstatus = EXIT_FAILURE;
 
684
      goto end;
 
685
    }
 
686
    // Starting a new process to be watched
 
687
    pid_t pid = fork();
 
688
    if(pid == -1){
 
689
      perror("fork");
 
690
      exitstatus = EXIT_FAILURE;
 
691
      goto end;
 
692
    }
 
693
    if(pid == 0){
 
694
      /* this is the child process */
 
695
      ret = sigaction(SIGCHLD, &old_sigchld_action, NULL);
 
696
      if(ret < 0){
 
697
        perror("sigaction");
 
698
        _exit(EXIT_FAILURE);
 
699
      }
 
700
      ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
 
701
      if(ret < 0){
 
702
        perror("sigprocmask");
 
703
        _exit(EXIT_FAILURE);
 
704
      }
 
705
 
 
706
      ret = dup2(pipefd[1], STDOUT_FILENO); /* replace our stdout */
 
707
      if(ret == -1){
 
708
        perror("dup2");
 
709
        _exit(EXIT_FAILURE);
 
710
      }
 
711
      
 
712
      if(dirfd(dir) < 0){
 
713
        /* If dir has no file descriptor, we could not set FD_CLOEXEC
 
714
           above and must now close it manually here. */
248
715
        closedir(dir);
249
 
        close(pipefd[0]);       /* close unused read end of pipe */
250
 
        dup2(pipefd[1], STDOUT_FILENO); /* replace our stdout */
251
 
 
252
 
        plugin *p = getplugin(dirst->d_name, &plugin_list);
253
 
        plugin *g = getplugin(NULL, &plugin_list);
254
 
        for(char **a = g->argv + 1; *a != NULL; a++){
255
 
          addarguments(p, *a);
256
 
        }
 
716
      }
 
717
      if(p->environ[0] == NULL){
257
718
        if(execv(filename, p->argv) < 0){
258
 
          perror(argv[0]);
259
 
          close(pipefd[1]);
260
 
          exit(EXIT_FAILURE);
261
 
        }
262
 
        /* no return */
263
 
      }
264
 
      close(pipefd[1]);         /* close unused write end of pipe */
265
 
      new_process->fd = pipefd[0];
266
 
      new_process->buffer = malloc(BUFFER_SIZE);
267
 
      if (new_process->buffer == NULL){
268
 
        perror(argv[0]);
269
 
        goto end;
270
 
      }
271
 
      new_process->buffer_size = BUFFER_SIZE;
272
 
      new_process->buffer_length = 0;
273
 
      FD_SET(new_process->fd, &rfds_orig);
274
 
      
275
 
      if (maxfd < new_process->fd){
276
 
        maxfd = new_process->fd;
277
 
      }
278
 
      
279
 
      //List handling
280
 
      new_process->next = process_list;
281
 
      process_list = new_process;
282
 
    }
 
719
          perror("execv");
 
720
          _exit(EXIT_FAILURE);
 
721
        }
 
722
      } else {
 
723
        if(execve(filename, p->argv, p->environ) < 0){
 
724
          perror("execve");
 
725
          _exit(EXIT_FAILURE);
 
726
        }
 
727
      }
 
728
      /* no return */
 
729
    }
 
730
    /* parent process */
 
731
    free(filename);
 
732
    close(pipefd[1]);           /* close unused write end of pipe */
 
733
    process *new_process = malloc(sizeof(process));
 
734
    if (new_process == NULL){
 
735
      perror("malloc");
 
736
      ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
 
737
      if(ret < 0){
 
738
        perror("sigprocmask");
 
739
      }
 
740
      exitstatus = EXIT_FAILURE;
 
741
      goto end;
 
742
    }
 
743
    
 
744
    *new_process = (struct process){ .pid = pid,
 
745
                                     .fd = pipefd[0],
 
746
                                     .next = process_list };
 
747
    // List handling
 
748
    process_list = new_process;
 
749
    /* Unblock SIGCHLD so signal handler can be run if this process
 
750
       has already completed */
 
751
    ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
 
752
    if(ret < 0){
 
753
      perror("sigprocmask");
 
754
      exitstatus = EXIT_FAILURE;
 
755
      goto end;
 
756
    }
 
757
    
 
758
    FD_SET(new_process->fd, &rfds_all);
 
759
    
 
760
    if (maxfd < new_process->fd){
 
761
      maxfd = new_process->fd;
 
762
    }
 
763
    
 
764
  }
 
765
  
 
766
  /* Free the plugin list */
 
767
  for(plugin *next; plugin_list != NULL; plugin_list = next){
 
768
    next = plugin_list->next;
 
769
    free(plugin_list->argv);
 
770
    if(plugin_list->environ[0] != NULL){
 
771
      for(char **e = plugin_list->environ; *e != NULL; e++){
 
772
        free(*e);
 
773
      }
 
774
    }
 
775
    free(plugin_list);
283
776
  }
284
777
  
285
778
  closedir(dir);
286
 
  
287
 
  if (process_list != NULL){
288
 
    while(true){
289
 
      fd_set rfds = rfds_orig;
290
 
      int select_ret = select(maxfd+1, &rfds, NULL, NULL, NULL);
291
 
      if (select_ret == -1){
292
 
        perror(argv[0]);
293
 
        goto end;
294
 
      }else{    
295
 
        for(process *process_itr = process_list; process_itr != NULL;
296
 
            process_itr = process_itr->next){
297
 
          if(FD_ISSET(process_itr->fd, &rfds)){
298
 
            if(process_itr->buffer_length + BUFFER_SIZE
299
 
               > process_itr->buffer_size){
300
 
                process_itr->buffer = realloc(process_itr->buffer,
301
 
                                              process_itr->buffer_size
302
 
                                              + (size_t) BUFFER_SIZE);
303
 
                if (process_itr->buffer == NULL){
304
 
                  perror(argv[0]);
305
 
                  goto end;
306
 
                }
307
 
                process_itr->buffer_size += BUFFER_SIZE;
308
 
            }
309
 
            ret = read(process_itr->fd, process_itr->buffer
310
 
                       + process_itr->buffer_length, BUFFER_SIZE);
311
 
            if(ret < 0){
312
 
              /* Read error from this process; ignore it */
313
 
              continue;
314
 
            }
315
 
            process_itr->buffer_length += (size_t) ret;
316
 
            if(ret == 0){
317
 
              /* got EOF */
318
 
              /* wait for process exit */
319
 
              int status;
320
 
              waitpid(process_itr->pid, &status, 0);
321
 
              if(WIFEXITED(status) and WEXITSTATUS(status) == 0){
322
 
                for(size_t written = 0;
323
 
                    written < process_itr->buffer_length;){
324
 
                  ret = write(STDOUT_FILENO,
325
 
                              process_itr->buffer + written,
326
 
                              process_itr->buffer_length - written);
327
 
                  if(ret < 0){
328
 
                    perror(argv[0]);
329
 
                    goto end;
330
 
                  }
331
 
                  written += (size_t)ret;
332
 
                }
333
 
                goto end;
334
 
              } else {
335
 
                FD_CLR(process_itr->fd, &rfds_orig);
 
779
  dir = NULL;
 
780
    
 
781
  if (process_list == NULL){
 
782
    fprintf(stderr, "No plugin processes started. Incorrect plugin"
 
783
            " directory?\n");
 
784
    process_list = NULL;
 
785
  }
 
786
  while(process_list){
 
787
    fd_set rfds = rfds_all;
 
788
    int select_ret = select(maxfd+1, &rfds, NULL, NULL, NULL);
 
789
    if (select_ret == -1){
 
790
      perror("select");
 
791
      exitstatus = EXIT_FAILURE;
 
792
      goto end;
 
793
    }
 
794
    /* OK, now either a process completed, or something can be read
 
795
       from one of them */
 
796
    for(process *proc = process_list; proc ; proc = proc->next){
 
797
      /* Is this process completely done? */
 
798
      if(proc->eof and proc->completed){
 
799
        /* Only accept the plugin output if it exited cleanly */
 
800
        if(not WIFEXITED(proc->status)
 
801
           or WEXITSTATUS(proc->status) != 0){
 
802
          /* Bad exit by plugin */
 
803
          if(debug){
 
804
            if(WIFEXITED(proc->status)){
 
805
              fprintf(stderr, "Plugin %u exited with status %d\n",
 
806
                      (unsigned int) (proc->pid),
 
807
                      WEXITSTATUS(proc->status));
 
808
            } else if(WIFSIGNALED(proc->status)) {
 
809
              fprintf(stderr, "Plugin %u killed by signal %d\n",
 
810
                      (unsigned int) (proc->pid),
 
811
                      WTERMSIG(proc->status));
 
812
            } else if(WCOREDUMP(proc->status)){
 
813
              fprintf(stderr, "Plugin %d dumped core\n",
 
814
                      (unsigned int) (proc->pid));
 
815
            }
 
816
          }
 
817
          /* Remove the plugin */
 
818
          FD_CLR(proc->fd, &rfds_all);
 
819
          /* Block signal while modifying process_list */
 
820
          ret = sigprocmask (SIG_BLOCK, &sigchld_action.sa_mask, NULL);
 
821
          if(ret < 0){
 
822
            perror("sigprocmask");
 
823
            exitstatus = EXIT_FAILURE;
 
824
            goto end;
 
825
          }
 
826
          /* Delete this process entry from the list */
 
827
          if(process_list == proc){
 
828
            /* First one - simple */
 
829
            process_list = proc->next;
 
830
          } else {
 
831
            /* Second one or later */
 
832
            for(process *p = process_list; p != NULL; p = p->next){
 
833
              if(p->next == proc){
 
834
                p->next = proc->next;
 
835
                break;
336
836
              }
337
837
            }
338
838
          }
339
 
        }
 
839
          /* We are done modifying process list, so unblock signal */
 
840
          ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask,
 
841
                             NULL);
 
842
          if(ret < 0){
 
843
            perror("sigprocmask");
 
844
          }
 
845
          free(proc->buffer);
 
846
          free(proc);
 
847
          /* We deleted this process from the list, so we can't go
 
848
             proc->next.  Therefore, start over from the beginning of
 
849
             the process list */
 
850
          break;
 
851
        }
 
852
        /* This process exited nicely, so print its buffer */
 
853
 
 
854
        bool bret = print_out_password(proc->buffer, proc->buffer_length);
 
855
        if(not bret){
 
856
          perror("print_out_password");
 
857
          exitstatus = EXIT_FAILURE;
 
858
        }
 
859
        goto end;
 
860
      }
 
861
      /* This process has not completed.  Does it have any output? */
 
862
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){
 
863
        /* This process had nothing to say at this time */
 
864
        continue;
 
865
      }
 
866
      /* Before reading, make the process' data buffer large enough */
 
867
      if(proc->buffer_length + BUFFER_SIZE > proc->buffer_size){
 
868
        proc->buffer = realloc(proc->buffer, proc->buffer_size
 
869
                               + (size_t) BUFFER_SIZE);
 
870
        if (proc->buffer == NULL){
 
871
          perror("malloc");
 
872
          exitstatus = EXIT_FAILURE;
 
873
          goto end;
 
874
        }
 
875
        proc->buffer_size += BUFFER_SIZE;
 
876
      }
 
877
      /* Read from the process */
 
878
      ret = read(proc->fd, proc->buffer + proc->buffer_length,
 
879
                 BUFFER_SIZE);
 
880
      if(ret < 0){
 
881
        /* Read error from this process; ignore the error */
 
882
        continue;
 
883
      }
 
884
      if(ret == 0){
 
885
        /* got EOF */
 
886
        proc->eof = true;
 
887
      } else {
 
888
        proc->buffer_length += (size_t) ret;
340
889
      }
341
890
    }
342
891
  }
343
 
  
 
892
 
 
893
 
344
894
 end:
345
 
  for(process *process_itr = process_list; process_itr != NULL;
346
 
      process_itr = process_itr->next){
347
 
    close(process_itr->fd);
348
 
    kill(process_itr->pid, SIGTERM);
349
 
    free(process_itr->buffer);
 
895
  
 
896
  if(process_list == NULL or exitstatus != EXIT_SUCCESS){
 
897
    /* Fallback if all plugins failed, none are found or an error occured */
 
898
    bool bret;
 
899
    fprintf(stderr, "Going to fallback mode using getpass(3)\n");
 
900
    char *passwordbuffer = getpass("Password: ");
 
901
    bret = print_out_password(passwordbuffer, strlen(passwordbuffer));
 
902
    if(not bret){
 
903
      perror("print_out_password");
 
904
      exitstatus = EXIT_FAILURE;
 
905
      goto end;
 
906
    }
350
907
  }
351
908
  
352
 
  while(true){
353
 
    int status;
354
 
    ret = wait(&status);
355
 
    if (ret == -1){
356
 
      if(errno != ECHILD){
357
 
        perror("wait");
 
909
  /* Restore old signal handler */
 
910
  sigaction(SIGCHLD, &old_sigchld_action, NULL);
 
911
  
 
912
  free(custom_argv);
 
913
  
 
914
  /* Free the plugin list */
 
915
  for(plugin *next; plugin_list != NULL; plugin_list = next){
 
916
    next = plugin_list->next;
 
917
    free(plugin_list->argv);
 
918
    if(plugin_list->environ[0] != NULL){
 
919
      for(char **e = plugin_list->environ; *e != NULL; e++){
 
920
        free(*e);
358
921
      }
359
 
      break;
360
 
    }
361
 
  }  
362
 
  return EXIT_SUCCESS;
 
922
    }
 
923
    free(plugin_list->environ);
 
924
    free(plugin_list);
 
925
  }
 
926
  
 
927
  if(dir != NULL){
 
928
    closedir(dir);
 
929
  }
 
930
  
 
931
  /* Free the process list and kill the processes */
 
932
  for(process *next; process_list != NULL; process_list = next){
 
933
    next = process_list->next;
 
934
    close(process_list->fd);
 
935
    ret = kill(process_list->pid, SIGTERM);
 
936
    if(ret == -1 and errno != ESRCH){
 
937
      /* set-uid proccesses migth not get closed */
 
938
      perror("kill");
 
939
    }
 
940
    free(process_list->buffer);
 
941
    free(process_list);
 
942
  }
 
943
  
 
944
  /* Wait for any remaining child processes to terminate */
 
945
  do{
 
946
    ret = wait(NULL);
 
947
  } while(ret >= 0);
 
948
  if(errno != ECHILD){
 
949
    perror("wait");
 
950
  }
 
951
  
 
952
  return exitstatus;
363
953
}