/mandos/trunk

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

« back to all changes in this revision

Viewing changes to plugin-runner.c

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

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

Show diffs side-by-side

added added

removed removed

Lines of Context:
21
21
 * Contact the authors at <mandos@fukt.bsnet.se>.
22
22
 */
23
23
 
24
 
#include <stdio.h>              /* popen(), fileno(), fprintf(),
25
 
                                   stderr, STDOUT_FILENO */
 
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() */
26
42
#include <iso646.h>             /* and, or, not */
27
 
#include <sys/types.h>         /* DIR, opendir(), stat(), struct stat,
28
 
                                  waitpid(), WIFEXITED(),
29
 
                                  WEXITSTATUS(), wait() */
30
 
#include <sys/wait.h>           /* wait() */
31
43
#include <dirent.h>             /* DIR, struct dirent, opendir(),
32
 
                                   readdir(), closedir() */
33
 
#include <sys/stat.h>           /* struct stat, stat(), S_ISREG() */
 
44
                                   readdir(), closedir(), dirfd() */
34
45
#include <unistd.h>             /* struct stat, stat(), S_ISREG(),
35
 
                                   fcntl() */
36
 
#include <fcntl.h>              /* fcntl() */
37
 
#include <stddef.h>             /* NULL */
38
 
#include <stdlib.h>             /* EXIT_FAILURE */
39
 
#include <sys/select.h>         /* fd_set, select(), FD_ZERO(),
40
 
                                   FD_SET(), FD_ISSET() */
41
 
#include <string.h>             /* strlen(), strcpy(), strcat() */
42
 
#include <stdbool.h>            /* true */
43
 
#include <sys/wait.h>           /* waitpid(), WIFEXITED(),
44
 
                                   WEXITSTATUS() */
 
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() */
45
55
#include <errno.h>              /* errno */
46
 
#include <argp.h>               /* struct argp_option,
47
 
                                   struct argp_state, struct argp,
48
 
                                   argp_parse() */
 
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>";
49
71
 
50
72
struct process;
51
73
 
55
77
  char *buffer;
56
78
  size_t buffer_size;
57
79
  size_t buffer_length;
 
80
  bool eof;
 
81
  bool completed;
 
82
  int status;
58
83
  struct process *next;
59
84
} process;
60
85
 
62
87
  char *name;                   /* can be NULL or any plugin name */
63
88
  char **argv;
64
89
  int argc;
 
90
  char **environ;
 
91
  int envc;
65
92
  bool disabled;
66
93
  struct plugin *next;
67
94
} plugin;
68
95
 
69
 
plugin *getplugin(char *name, plugin **plugin_list){
 
96
static plugin *getplugin(char *name, plugin **plugin_list){
70
97
  for (plugin *p = *plugin_list; p != NULL; p = p->next){
71
98
    if ((p->name == name)
72
99
        or (p->name and name and (strcmp(p->name, name) == 0))){
76
103
  /* Create a new plugin */
77
104
  plugin *new_plugin = malloc(sizeof(plugin));
78
105
  if (new_plugin == NULL){
79
 
    perror("malloc");
80
 
    exit(EXIT_FAILURE);
 
106
    return NULL;
81
107
  }
82
 
  new_plugin->name = name;
 
108
  *new_plugin = (plugin) { .name = name,
 
109
                           .argc = 1,
 
110
                           .envc = 0,
 
111
                           .disabled = false,
 
112
                           .next = *plugin_list };
 
113
  
83
114
  new_plugin->argv = malloc(sizeof(char *) * 2);
84
115
  if (new_plugin->argv == NULL){
85
 
    perror("malloc");
86
 
    exit(EXIT_FAILURE);
 
116
    free(new_plugin);
 
117
    return NULL;
87
118
  }
88
119
  new_plugin->argv[0] = name;
89
120
  new_plugin->argv[1] = NULL;
90
 
  new_plugin->argc = 1;
91
 
  new_plugin->disabled = false;
92
 
  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;
93
129
  /* Append the new plugin to the list */
94
130
  *plugin_list = new_plugin;
95
131
  return new_plugin;
96
132
}
97
133
 
98
 
void addargument(plugin *p, char *arg){
99
 
  p->argv[p->argc] = arg;
100
 
  p->argv = realloc(p->argv, sizeof(char *) * (size_t)(p->argc + 2));
101
 
  if (p->argv == NULL){
102
 
    perror("malloc");
103
 
    exit(EXIT_FAILURE);
104
 
  }
105
 
  p->argc++;
106
 
  p->argv[p->argc] = NULL;
107
 
}
108
 
 
109
 
#define BUFFER_SIZE 256
110
 
 
111
 
const char *argp_program_version =
112
 
  "plugbasedclient 0.9";
113
 
const char *argp_program_bug_address =
114
 
  "<mandos@fukt.bsnet.se>";
 
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
}
115
248
 
116
249
int main(int argc, char *argv[]){
117
 
  const char *plugindir = "/conf/conf.d/mandos/plugins.d";
 
250
  const char *plugindir = "/lib/mandos/plugins.d";
 
251
  const char *argfile = ARGFILE;
 
252
  FILE *conffp;
118
253
  size_t d_name_len;
119
 
  DIR *dir;
 
254
  DIR *dir = NULL;
120
255
  struct dirent *dirst;
121
256
  struct stat st;
122
257
  fd_set rfds_all;
123
258
  int ret, maxfd = 0;
124
 
  process *process_list = NULL;
 
259
  uid_t uid = 65534;
 
260
  gid_t gid = 65534;
125
261
  bool debug = false;
126
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
  }
127
281
  
128
282
  /* The options we understand. */
129
283
  struct argp_option options[] = {
130
284
    { .name = "global-options", .key = 'g',
131
285
      .arg = "OPTION[,OPTION[,...]]",
132
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" },
133
290
    { .name = "options-for", .key = 'o',
134
291
      .arg = "PLUGIN:OPTION[,OPTION[,...]]",
135
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" },
136
296
    { .name = "disable", .key = 'd',
137
297
      .arg = "PLUGIN",
138
298
      .doc = "Disable a specific plugin", .group = 1 },
139
299
    { .name = "plugin-dir", .key = 128,
140
300
      .arg = "DIRECTORY",
141
301
      .doc = "Specify a different plugin directory", .group = 2 },
142
 
    { .name = "debug", .key = 129,
 
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,
143
309
      .doc = "Debug mode", .group = 3 },
144
310
    { .name = NULL }
145
311
  };
146
312
  
147
313
  error_t parse_opt (int key, char *arg, struct argp_state *state) {
148
 
       /* Get the INPUT argument from `argp_parse', which we
149
 
          know is a pointer to our plugin list pointer. */
 
314
    /* Get the INPUT argument from `argp_parse', which we know is a
 
315
       pointer to our plugin list pointer. */
150
316
    plugin **plugins = state->input;
151
317
    switch (key) {
152
318
    case 'g':
153
319
      if (arg != NULL){
154
 
        char *p = strtok(arg, ",");
155
 
        do{
156
 
          addargument(getplugin(NULL, plugins), p);
157
 
          p = strtok(NULL, ",");
158
 
        } 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
        }
159
344
      }
160
345
      break;
161
346
    case 'o':
162
347
      if (arg != NULL){
163
 
        char *name = strtok(arg, ":");
164
 
        char *p = strtok(NULL, ":");
165
 
        if(p){
166
 
          p = strtok(p, ",");
167
 
          do{
168
 
            addargument(getplugin(name, plugins), p);
169
 
            p = strtok(NULL, ",");
170
 
          } 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");
171
386
        }
172
387
      }
173
388
      break;
174
389
    case 'd':
175
390
      if (arg != NULL){
176
 
        getplugin(arg, plugins)->disabled = true;
 
391
        plugin *p = getplugin(arg, plugins);
 
392
        if(p == NULL){
 
393
          return ARGP_ERR_UNKNOWN;
 
394
        }
 
395
        p->disabled = true;
177
396
      }
178
397
      break;
179
398
    case 128:
180
399
      plugindir = arg;
181
400
      break;
182
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:
183
408
      debug = true;
184
409
      break;
185
410
    case ARGP_KEY_ARG:
186
 
      argp_usage (state);
 
411
      fprintf(stderr, "Ignoring unknown argument \"%s\"\n", arg);
187
412
      break;
188
413
    case ARGP_KEY_END:
189
414
      break;
196
421
  plugin *plugin_list = NULL;
197
422
  
198
423
  struct argp argp = { .options = options, .parser = parse_opt,
199
 
                       .args_doc = "",
 
424
                       .args_doc = "[+PLUS_SEPARATED_OPTIONS]",
200
425
                       .doc = "Mandos plugin runner -- Run plugins" };
201
426
  
202
 
  argp_parse (&argp, argc, argv, 0, 0, &plugin_list);
 
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
  }
203
484
  
204
485
  if(debug){
205
486
    for(plugin *p = plugin_list; p != NULL; p=p->next){
206
487
      fprintf(stderr, "Plugin: %s has %d arguments\n",
207
 
              p->name ? p->name : "Global", p->argc);
 
488
              p->name ? p->name : "Global", p->argc - 1);
208
489
      for(char **a = p->argv; *a != NULL; a++){
209
490
        fprintf(stderr, "\tArg: %s\n", *a);
210
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
      }
211
496
    }
212
497
  }
213
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
  
214
509
  dir = opendir(plugindir);
 
510
  if(dir == NULL){
 
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 */
215
517
  {
216
 
    /* Set the FD_CLOEXEC flag on the directory */
217
 
    int fd = dirfd(dir);
218
 
    ret = fcntl (fd, F_GETFD, 0);
219
 
    if(ret < 0){
220
 
      perror("fcntl F_GETFD");
221
 
      goto end;
222
 
    }
223
 
    ret = fcntl(fd, F_SETFD, FD_CLOEXEC | ret);
224
 
    if(ret < 0){
225
 
      perror("fcntl F_SETFD");
226
 
      goto end;
227
 
    }
228
 
  }
229
 
  
230
 
  if(dir == NULL){
231
 
    fprintf(stderr, "Can not open directory\n");
232
 
    return EXIT_FAILURE;
 
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
    }
233
527
  }
234
528
  
235
529
  FD_ZERO(&rfds_all);
239
533
    
240
534
    // All directory entries have been processed
241
535
    if(dirst == NULL){
 
536
      if (errno == EBADF){
 
537
        perror("readdir");
 
538
        exitstatus = EXIT_FAILURE;
 
539
        goto end;
 
540
      }
242
541
      break;
243
542
    }
244
543
    
258
557
        if((d_name_len >= pre_len)
259
558
           and strncmp((dirst->d_name), *pre, pre_len) == 0){
260
559
          if(debug){
261
 
            fprintf(stderr, "Ignoring plugin dir entry name \"%s\""
 
560
            fprintf(stderr, "Ignoring plugin dir entry \"%s\""
262
561
                    " with bad prefix %s\n", dirst->d_name, *pre);
263
562
          }
264
563
          bad_name = true;
276
575
           and (strcmp((dirst->d_name)+d_name_len-suf_len, *suf)
277
576
                == 0)){
278
577
          if(debug){
279
 
            fprintf(stderr, "Ignoring plugin dir entry name \"%s\""
 
578
            fprintf(stderr, "Ignoring plugin dir entry \"%s\""
280
579
                    " with bad suffix %s\n", dirst->d_name, *suf);
281
580
          }
282
581
          bad_name = true;
288
587
        continue;
289
588
      }
290
589
    }
291
 
    
292
 
    char *filename = malloc(d_name_len + strlen(plugindir) + 2);
293
 
    if (filename == NULL){
294
 
      perror("malloc");
295
 
      exitstatus =EXIT_FAILURE;
296
 
      goto end;
297
 
    }
298
 
    strcpy(filename, plugindir);
299
 
    strcat(filename, "/");
300
 
    strcat(filename, dirst->d_name);    
301
 
 
302
 
    stat(filename, &st);
303
 
 
 
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
    
304
605
    if (not S_ISREG(st.st_mode) or (access(filename, X_OK) != 0)){
305
606
      if(debug){
306
 
        fprintf(stderr, "Ignoring plugin dir entry name \"%s\""
 
607
        fprintf(stderr, "Ignoring plugin dir entry \"%s\""
307
608
                " with bad type or mode\n", filename);
308
609
      }
309
 
      continue;
310
 
    }
311
 
    if(getplugin(dirst->d_name, &plugin_list)->disabled){
 
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){
312
620
      if(debug){
313
 
        fprintf(stderr, "Ignoring disabled plugin \"%s\"",
 
621
        fprintf(stderr, "Ignoring disabled plugin \"%s\"\n",
314
622
                dirst->d_name);
315
623
      }
 
624
      free(filename);
316
625
      continue;
317
626
    }
318
 
    // Starting a new process to be watched
 
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
    
319
660
    int pipefd[2]; 
320
661
    ret = pipe(pipefd);
321
662
    if (ret == -1){
322
 
      perror(argv[0]);
323
 
      goto end;
324
 
    }
 
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
325
687
    pid_t pid = fork();
 
688
    if(pid == -1){
 
689
      perror("fork");
 
690
      exitstatus = EXIT_FAILURE;
 
691
      goto end;
 
692
    }
326
693
    if(pid == 0){
327
694
      /* this is the child process */
328
 
      closedir(dir);
329
 
      close(pipefd[0]); /* close unused read end of pipe */
330
 
      dup2(pipefd[1], STDOUT_FILENO); /* replace our stdout */
 
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
      }
331
711
      
332
 
      plugin *p = getplugin(dirst->d_name, &plugin_list);
333
 
      plugin *g = getplugin(NULL, &plugin_list);
334
 
      for(char **a = g->argv + 1; *a != NULL; a++){
335
 
        addargument(p, *a);
 
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. */
 
715
        closedir(dir);
336
716
      }
337
 
      if(execv(filename, p->argv) < 0){
338
 
        perror(argv[0]);
339
 
        close(pipefd[1]);
340
 
        _exit(EXIT_FAILURE);
 
717
      if(p->environ[0] == NULL){
 
718
        if(execv(filename, p->argv) < 0){
 
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
        }
341
727
      }
342
728
      /* no return */
343
729
    }
 
730
    /* parent process */
 
731
    free(filename);
344
732
    close(pipefd[1]);           /* close unused write end of pipe */
345
733
    process *new_process = malloc(sizeof(process));
346
734
    if (new_process == NULL){
347
735
      perror("malloc");
348
 
      exitstatus = EXIT_FAILURE;
349
 
      goto end;
350
 
    }
351
 
    
352
 
    new_process->fd = pipefd[0];
353
 
    new_process->buffer = malloc(BUFFER_SIZE);
354
 
    if (new_process->buffer == NULL){
355
 
      perror("malloc");
356
 
      exitstatus = EXIT_FAILURE;
357
 
      goto end;
358
 
    }
359
 
    new_process->buffer_size = BUFFER_SIZE;
360
 
    new_process->buffer_length = 0;
 
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
    
361
758
    FD_SET(new_process->fd, &rfds_all);
362
 
      
 
759
    
363
760
    if (maxfd < new_process->fd){
364
761
      maxfd = new_process->fd;
365
762
    }
366
763
    
367
 
    //List handling
368
 
    new_process->next = process_list;
369
 
    process_list = new_process;
 
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);
370
776
  }
371
777
  
372
778
  closedir(dir);
373
 
  
374
 
  if (process_list != NULL){
375
 
    while(true){
376
 
      fd_set rfds = rfds_all;
377
 
      int select_ret = select(maxfd+1, &rfds, NULL, NULL, NULL);
378
 
      if (select_ret == -1){
379
 
        perror(argv[0]);
380
 
        goto end;
381
 
      }else{    
382
 
        for(process *process_itr = process_list; process_itr != NULL;
383
 
            process_itr = process_itr->next){
384
 
          if(FD_ISSET(process_itr->fd, &rfds)){
385
 
            if(process_itr->buffer_length + BUFFER_SIZE
386
 
               > process_itr->buffer_size){
387
 
                process_itr->buffer = realloc(process_itr->buffer,
388
 
                                              process_itr->buffer_size
389
 
                                              + (size_t) BUFFER_SIZE);
390
 
                if (process_itr->buffer == NULL){
391
 
                  perror(argv[0]);
392
 
                  goto end;
393
 
                }
394
 
                process_itr->buffer_size += BUFFER_SIZE;
395
 
            }
396
 
            ret = read(process_itr->fd, process_itr->buffer
397
 
                       + process_itr->buffer_length, BUFFER_SIZE);
398
 
            if(ret < 0){
399
 
              /* Read error from this process; ignore it */
400
 
              continue;
401
 
            }
402
 
            process_itr->buffer_length += (size_t) ret;
403
 
            if(ret == 0){
404
 
              /* got EOF */
405
 
              /* wait for process exit */
406
 
              int status;
407
 
              waitpid(process_itr->pid, &status, 0);
408
 
              if(WIFEXITED(status) and WEXITSTATUS(status) == 0){
409
 
                for(size_t written = 0;
410
 
                    written < process_itr->buffer_length;){
411
 
                  ret = write(STDOUT_FILENO,
412
 
                              process_itr->buffer + written,
413
 
                              process_itr->buffer_length - written);
414
 
                  if(ret < 0){
415
 
                    perror(argv[0]);
416
 
                    goto end;
417
 
                  }
418
 
                  written += (size_t)ret;
419
 
                }
420
 
                goto end;
421
 
              } else {
422
 
                FD_CLR(process_itr->fd, &rfds_all);
 
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;
423
836
              }
424
837
            }
425
838
          }
426
 
        }
 
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;
427
889
      }
428
890
    }
429
891
  }
430
 
  
 
892
 
 
893
 
431
894
 end:
432
 
  for(process *process_itr = process_list; process_itr != NULL;
433
 
      process_itr = process_itr->next){
434
 
    close(process_itr->fd);
435
 
    kill(process_itr->pid, SIGTERM);
436
 
    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
    }
437
907
  }
438
908
  
439
 
  while(true){
440
 
    int status;
441
 
    ret = wait(&status);
442
 
    if (ret == -1){
443
 
      if(errno != ECHILD){
444
 
        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);
445
921
      }
446
 
      break;
447
 
    }
448
 
  }  
 
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
  
449
952
  return exitstatus;
450
953
}