/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 */
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;
 
90
  char **environ;
 
91
  int envc;
 
92
  bool disabled;
56
93
  struct plugin *next;
57
94
} plugin;
58
95
 
59
 
plugin *getplugin(char *name, plugin **plugin_list){
 
96
static plugin *getplugin(char *name, plugin **plugin_list){
60
97
  for (plugin *p = *plugin_list; p != NULL; p = p->next){
61
98
    if ((p->name == name)
62
99
        or (p->name and name and (strcmp(p->name, name) == 0))){
66
103
  /* Create a new plugin */
67
104
  plugin *new_plugin = malloc(sizeof(plugin));
68
105
  if (new_plugin == NULL){
69
 
    perror("malloc");
70
 
    exit(EXIT_FAILURE);
 
106
    return NULL;
71
107
  }
72
 
  new_plugin->name = name;
 
108
  *new_plugin = (plugin) { .name = name,
 
109
                           .argc = 1,
 
110
                           .envc = 0,
 
111
                           .disabled = false,
 
112
                           .next = *plugin_list };
 
113
  
73
114
  new_plugin->argv = malloc(sizeof(char *) * 2);
74
115
  if (new_plugin->argv == NULL){
75
 
    perror("malloc");
76
 
    exit(EXIT_FAILURE);
 
116
    free(new_plugin);
 
117
    return NULL;
77
118
  }
78
119
  new_plugin->argv[0] = name;
79
120
  new_plugin->argv[1] = NULL;
80
 
  new_plugin->argc = 1;
 
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;
81
129
  /* Append the new plugin to the list */
82
 
  new_plugin->next = *plugin_list;
83
130
  *plugin_list = new_plugin;
84
131
  return new_plugin;
85
132
}
86
133
 
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[] = "";
 
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
}
108
248
 
109
249
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;
 
250
  const char *plugindir = "/lib/mandos/plugins.d";
 
251
  const char *argfile = ARGFILE;
 
252
  FILE *conffp;
 
253
  size_t d_name_len;
 
254
  DIR *dir = NULL;
113
255
  struct dirent *dirst;
114
256
  struct stat st;
115
 
  fd_set rfds_orig;
 
257
  fd_set rfds_all;
116
258
  int ret, maxfd = 0;
117
 
  process *process_list = NULL;
 
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
  }
118
281
  
119
282
  /* The options we understand. */
120
283
  struct argp_option options[] = {
121
284
    { .name = "global-options", .key = 'g',
122
 
      .arg = "option[,option[,...]]", .flags = 0,
123
 
      .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" },
124
290
    { .name = "options-for", .key = 'o',
125
 
      .arg = "plugin:option[,option[,...]]", .flags = 0,
126
 
      .doc = "Options effecting only specified 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 },
 
299
    { .name = "plugin-dir", .key = 128,
 
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 },
127
310
    { .name = NULL }
128
311
  };
129
312
  
130
313
  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. */
 
314
    /* Get the INPUT argument from `argp_parse', which we know is a
 
315
       pointer to our plugin list pointer. */
133
316
    plugin **plugins = state->input;
134
317
    switch (key) {
135
318
    case 'g':
136
319
      if (arg != NULL){
137
 
        char *p = strtok(arg, ",");
138
 
        do{
139
 
          addarguments(getplugin(NULL, plugins), p);
140
 
          p = strtok(NULL, ",");
141
 
        } 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
        }
142
344
      }
143
345
      break;
144
346
    case 'o':
145
347
      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
 
      }
 
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
        }
 
387
      }
 
388
      break;
 
389
    case 'd':
 
390
      if (arg != NULL){
 
391
        plugin *p = getplugin(arg, plugins);
 
392
        if(p == NULL){
 
393
          return ARGP_ERR_UNKNOWN;
 
394
        }
 
395
        p->disabled = true;
 
396
      }
 
397
      break;
 
398
    case 128:
 
399
      plugindir = arg;
 
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;
154
409
      break;
155
410
    case ARGP_KEY_ARG:
156
 
      argp_usage (state);
 
411
      fprintf(stderr, "Ignoring unknown argument \"%s\"\n", arg);
157
412
      break;
158
413
    case ARGP_KEY_END:
159
414
      break;
162
417
    }
163
418
    return 0;
164
419
  }
165
 
 
 
420
  
166
421
  plugin *plugin_list = NULL;
167
422
  
168
423
  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; */
 
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
  }
181
508
  
182
509
  dir = opendir(plugindir);
183
 
  
184
510
  if(dir == NULL){
185
 
    fprintf(stderr, "Can not open directory\n");
186
 
    return EXIT_FAILURE;
187
 
  }
188
 
  
189
 
  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);
190
530
  
191
531
  while(true){
192
532
    dirst = readdir(dir);
193
533
    
194
534
    // All directory entries have been processed
195
535
    if(dirst == NULL){
 
536
      if (errno == EBADF){
 
537
        perror("readdir");
 
538
        exitstatus = EXIT_FAILURE;
 
539
        goto end;
 
540
      }
196
541
      break;
197
542
    }
198
543
    
199
544
    d_name_len = strlen(dirst->d_name);
200
545
    
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 */
 
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. */
226
715
        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);
239
 
        for(char **a = g->argv + 1; *a != NULL; a++){
240
 
          addarguments(p, *a);
241
 
        }
 
716
      }
 
717
      if(p->environ[0] == NULL){
242
718
        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
 
    }
 
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);
268
776
  }
269
777
  
270
778
  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);
 
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;
321
836
              }
322
837
            }
323
838
          }
324
 
        }
 
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;
325
889
      }
326
890
    }
327
891
  }
328
 
  
 
892
 
 
893
 
329
894
 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);
 
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
    }
335
907
  }
336
908
  
337
 
  while(true){
338
 
    int status;
339
 
    ret = wait(&status);
340
 
    if (ret == -1){
341
 
      if(errno != ECHILD){
342
 
        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);
343
921
      }
344
 
      break;
345
 
    }
346
 
  }  
347
 
  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;
348
953
}