/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:
2
2
/*
3
3
 * Mandos plugin runner - Run Mandos plugins
4
4
 *
5
 
 * Copyright © 2008,2009 Teddy Hogeborn
6
 
 * Copyright © 2008,2009 Björn Påhlsson
 
5
 * Copyright © 2007-2008 Teddy Hogeborn & Björn Påhlsson
7
6
 * 
8
7
 * This program is free software: you can redistribute it and/or
9
8
 * modify it under the terms of the GNU General Public License as
28
27
#include <stdlib.h>             /* malloc(), exit(), EXIT_FAILURE,
29
28
                                   EXIT_SUCCESS, realloc() */
30
29
#include <stdbool.h>            /* bool, true, false */
31
 
#include <stdio.h>              /* perror, fileno(), fprintf(),
32
 
                                   stderr, STDOUT_FILENO */
 
30
#include <stdio.h>              /* perror, popen(), fileno(),
 
31
                                   fprintf(), stderr, STDOUT_FILENO */
33
32
#include <sys/types.h>          /* DIR, opendir(), stat(), struct
34
33
                                   stat, waitpid(), WIFEXITED(),
35
34
                                   WEXITSTATUS(), wait(), pid_t,
47
46
                                   fcntl(), setuid(), setgid(),
48
47
                                   F_GETFD, F_SETFD, FD_CLOEXEC,
49
48
                                   access(), pipe(), fork(), close()
50
 
                                   dup2(), STDOUT_FILENO, _exit(),
 
49
                                   dup2, STDOUT_FILENO, _exit(),
51
50
                                   execv(), write(), read(),
52
51
                                   close() */
53
52
#include <fcntl.h>              /* fcntl(), F_GETFD, F_SETFD,
57
56
#include <argp.h>               /* struct argp_option, struct
58
57
                                   argp_state, struct argp,
59
58
                                   argp_parse(), ARGP_ERR_UNKNOWN,
60
 
                                   ARGP_KEY_END, ARGP_KEY_ARG,
61
 
                                   error_t */
 
59
                                   ARGP_KEY_END, ARGP_KEY_ARG, error_t */
62
60
#include <signal.h>             /* struct sigaction, sigemptyset(),
63
61
                                   sigaddset(), sigaction(),
64
62
                                   sigprocmask(), SIG_BLOCK, SIGCHLD,
66
64
#include <errno.h>              /* errno, EBADF */
67
65
 
68
66
#define BUFFER_SIZE 256
69
 
 
70
 
#define PDIR "/lib/mandos/plugins.d"
71
 
#define AFILE "/conf/conf.d/mandos/plugin-runner.conf"
72
 
 
73
 
const char *argp_program_version = "plugin-runner " VERSION;
 
67
#define ARGFILE "/conf/conf.d/mandos/plugin-runner.conf"
 
68
 
 
69
const char *argp_program_version = "plugin-runner 1.0";
74
70
const char *argp_program_bug_address = "<mandos@fukt.bsnet.se>";
75
71
 
 
72
struct process;
 
73
 
 
74
typedef struct process{
 
75
  pid_t pid;
 
76
  int fd;
 
77
  char *buffer;
 
78
  size_t buffer_size;
 
79
  size_t buffer_length;
 
80
  bool eof;
 
81
  bool completed;
 
82
  int status;
 
83
  struct process *next;
 
84
} process;
 
85
 
76
86
typedef struct plugin{
77
87
  char *name;                   /* can be NULL or any plugin name */
78
88
  char **argv;
80
90
  char **environ;
81
91
  int envc;
82
92
  bool disabled;
83
 
 
84
 
  /* Variables used for running processes*/
85
 
  pid_t pid;
86
 
  int fd;
87
 
  char *buffer;
88
 
  size_t buffer_size;
89
 
  size_t buffer_length;
90
 
  bool eof;
91
 
  volatile bool completed;
92
 
  volatile int status;
93
93
  struct plugin *next;
94
94
} plugin;
95
95
 
96
 
static plugin *plugin_list = NULL;
97
 
 
98
 
/* Gets an existing plugin based on name,
99
 
   or if none is found, creates a new one */
100
 
static plugin *getplugin(char *name){
101
 
  /* Check for exiting plugin with that name */
102
 
  for (plugin *p = plugin_list; p != NULL; p = p->next){
 
96
static plugin *getplugin(char *name, plugin **plugin_list){
 
97
  for (plugin *p = *plugin_list; p != NULL; p = p->next){
103
98
    if ((p->name == name)
104
99
        or (p->name and name and (strcmp(p->name, name) == 0))){
105
100
      return p;
110
105
  if (new_plugin == NULL){
111
106
    return NULL;
112
107
  }
113
 
  char *copy_name = NULL;
114
 
  if(name != NULL){
115
 
    copy_name = strdup(name);
116
 
    if(copy_name == NULL){
117
 
      return NULL;
118
 
    }
119
 
  }
120
 
  
121
 
  *new_plugin = (plugin) { .name = copy_name,
 
108
  *new_plugin = (plugin) { .name = name,
122
109
                           .argc = 1,
 
110
                           .envc = 0,
123
111
                           .disabled = false,
124
 
                           .next = plugin_list };
 
112
                           .next = *plugin_list };
125
113
  
126
114
  new_plugin->argv = malloc(sizeof(char *) * 2);
127
115
  if (new_plugin->argv == NULL){
128
 
    free(copy_name);
129
116
    free(new_plugin);
130
117
    return NULL;
131
118
  }
132
 
  new_plugin->argv[0] = copy_name;
 
119
  new_plugin->argv[0] = name;
133
120
  new_plugin->argv[1] = NULL;
134
 
  
 
121
 
135
122
  new_plugin->environ = malloc(sizeof(char *));
136
123
  if(new_plugin->environ == NULL){
137
 
    free(copy_name);
138
124
    free(new_plugin->argv);
139
125
    free(new_plugin);
140
126
    return NULL;
141
127
  }
142
128
  new_plugin->environ[0] = NULL;
143
 
  
144
129
  /* Append the new plugin to the list */
145
 
  plugin_list = new_plugin;
 
130
  *plugin_list = new_plugin;
146
131
  return new_plugin;
147
132
}
148
133
 
178
163
}
179
164
 
180
165
/* Add to a plugin's environment */
181
 
static bool add_environment(plugin *p, const char *def, bool replace){
 
166
static bool add_environment(plugin *p, const char *def){
182
167
  if(p == NULL){
183
168
    return false;
184
169
  }
185
 
  /* namelen = length of name of environment variable */
186
 
  size_t namelen = (size_t)(strchrnul(def, '=') - def);
187
 
  /* Search for this environment variable */
188
 
  for(char **e = p->environ; *e != NULL; e++){
189
 
    if(strncmp(*e, def, namelen + 1) == 0){
190
 
      /* It already exists */
191
 
      if(replace){
192
 
        char *new = realloc(*e, strlen(def) + 1);
193
 
        if(new == NULL){
194
 
          return false;
195
 
        }
196
 
        *e = new;
197
 
        strcpy(*e, def);
198
 
      }
199
 
      return true;
200
 
    }
201
 
  }
202
170
  return add_to_char_array(def, &(p->environ), &(p->envc));
203
171
}
204
172
 
 
173
 
205
174
/*
206
175
 * Based on the example in the GNU LibC manual chapter 13.13 "File
207
176
 * Descriptor Flags".
208
177
 * *Note File Descriptor Flags:(libc)Descriptor Flags.
209
178
 */
210
 
static int set_cloexec_flag(int fd){
 
179
static int set_cloexec_flag(int fd)
 
180
{
211
181
  int ret = fcntl(fd, F_GETFD, 0);
212
182
  /* If reading the flags failed, return error indication now. */
213
183
  if(ret < 0){
217
187
  return fcntl(fd, F_SETFD, ret | FD_CLOEXEC);
218
188
}
219
189
 
 
190
process *process_list = NULL;
220
191
 
221
 
/* Mark processes as completed when they exit, and save their exit
 
192
/* Mark a process as completed when it exits, and save its exit
222
193
   status. */
223
 
static void handle_sigchld(__attribute__((unused)) int sig){
224
 
  while(true){
225
 
    plugin *proc = plugin_list;
226
 
    int status;
227
 
    pid_t pid = waitpid(-1, &status, WNOHANG);
228
 
    if(pid == 0){
229
 
      /* Only still running child processes */
230
 
      break;
231
 
    }
232
 
    if(pid == -1){
233
 
      if (errno != ECHILD){
234
 
        perror("waitpid");
235
 
      }
236
 
      /* No child processes */
237
 
      break;
238
 
    }
239
 
    
240
 
    /* A child exited, find it in process_list */
241
 
    while(proc != NULL and proc->pid != pid){
242
 
      proc = proc->next;
243
 
    }
244
 
    if(proc == NULL){
245
 
      /* Process not found in process list */
246
 
      continue;
247
 
    }
248
 
    proc->status = status;
249
 
    proc->completed = true;
250
 
  }
 
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;
251
211
}
252
212
 
253
 
/* Prints out a password to stdout */
254
 
static bool print_out_password(const char *buffer, size_t length){
 
213
bool print_out_password(const char *buffer, size_t length){
255
214
  ssize_t ret;
 
215
  if(length>0 and buffer[length-1] == '\n'){
 
216
    length--;
 
217
  }
256
218
  for(size_t written = 0; written < length; written += (size_t)ret){
257
219
    ret = TEMP_FAILURE_RETRY(write(STDOUT_FILENO, buffer + written,
258
220
                                   length - written));
263
225
  return true;
264
226
}
265
227
 
266
 
/* Removes and free a plugin from the plugin list */
267
 
static void free_plugin(plugin *plugin_node){
268
 
  
269
 
  for(char **arg = plugin_node->argv; *arg != NULL; arg++){
270
 
    free(*arg);
271
 
  }
272
 
  free(plugin_node->argv);
273
 
  for(char **env = plugin_node->environ; *env != NULL; env++){
274
 
    free(*env);
275
 
  }
276
 
  free(plugin_node->environ);
277
 
  free(plugin_node->buffer);
278
 
 
279
 
  /* Removes the plugin from the singly-linked list */
280
 
  if(plugin_node == plugin_list){
281
 
    /* First one - simple */
282
 
    plugin_list = plugin_list->next;
283
 
  } else {
284
 
    /* Second one or later */
285
 
    for(plugin *p = plugin_list; p != NULL; p = p->next){
286
 
      if(p->next == plugin_node){
287
 
        p->next = plugin_node->next;
288
 
        break;
289
 
      }
 
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;
290
234
    }
291
 
  }
292
 
  
293
 
  free(plugin_node);
294
 
}
295
 
 
296
 
static void free_plugin_list(void){
297
 
  while(plugin_list != NULL){
298
 
    free_plugin(plugin_list);
299
 
  }
 
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;
300
247
}
301
248
 
302
249
int main(int argc, char *argv[]){
303
 
  char *plugindir = NULL;
304
 
  char *argfile = NULL;
 
250
  const char *plugindir = "/lib/mandos/plugins.d";
 
251
  const char *argfile = ARGFILE;
305
252
  FILE *conffp;
306
253
  size_t d_name_len;
307
254
  DIR *dir = NULL;
322
269
  /* Establish a signal handler */
323
270
  sigemptyset(&sigchld_action.sa_mask);
324
271
  ret = sigaddset(&sigchld_action.sa_mask, SIGCHLD);
325
 
  if(ret == -1){
 
272
  if(ret < 0){
326
273
    perror("sigaddset");
327
 
    exitstatus = EXIT_FAILURE;
328
 
    goto fallback;
 
274
    exit(EXIT_FAILURE);
329
275
  }
330
276
  ret = sigaction(SIGCHLD, &sigchld_action, &old_sigchld_action);
331
 
  if(ret == -1){
 
277
  if(ret < 0){
332
278
    perror("sigaction");
333
 
    exitstatus = EXIT_FAILURE;
334
 
    goto fallback;
 
279
    exit(EXIT_FAILURE);
335
280
  }
336
281
  
337
282
  /* The options we understand. */
339
284
    { .name = "global-options", .key = 'g',
340
285
      .arg = "OPTION[,OPTION[,...]]",
341
286
      .doc = "Options passed to all plugins" },
342
 
    { .name = "global-env", .key = 'G',
 
287
    { .name = "global-envs", .key = 'e',
343
288
      .arg = "VAR=value",
344
289
      .doc = "Environment variable passed to all plugins" },
345
290
    { .name = "options-for", .key = 'o',
346
291
      .arg = "PLUGIN:OPTION[,OPTION[,...]]",
347
292
      .doc = "Options passed only to specified plugin" },
348
 
    { .name = "env-for", .key = 'E',
 
293
    { .name = "envs-for", .key = 'f',
349
294
      .arg = "PLUGIN:ENV=value",
350
295
      .doc = "Environment variable passed to specified plugin" },
351
296
    { .name = "disable", .key = 'd',
352
297
      .arg = "PLUGIN",
353
298
      .doc = "Disable a specific plugin", .group = 1 },
354
 
    { .name = "enable", .key = 'e',
355
 
      .arg = "PLUGIN",
356
 
      .doc = "Enable a specific plugin", .group = 1 },
357
299
    { .name = "plugin-dir", .key = 128,
358
300
      .arg = "DIRECTORY",
359
301
      .doc = "Specify a different plugin directory", .group = 2 },
360
 
    { .name = "config-file", .key = 129,
361
 
      .arg = "FILE",
362
 
      .doc = "Specify a different configuration file", .group = 2 },
363
 
    { .name = "userid", .key = 130,
364
 
      .arg = "ID", .flags = 0,
365
 
      .doc = "User ID the plugins will run as", .group = 3 },
366
 
    { .name = "groupid", .key = 131,
367
 
      .arg = "ID", .flags = 0,
368
 
      .doc = "Group ID the plugins will run as", .group = 3 },
369
 
    { .name = "debug", .key = 132,
370
 
      .doc = "Debug mode", .group = 4 },
 
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 },
371
310
    { .name = NULL }
372
311
  };
373
312
  
374
 
  error_t parse_opt (int key, char *arg, __attribute__((unused))
375
 
                     struct argp_state *state) {
 
313
  error_t parse_opt (int key, char *arg, struct argp_state *state) {
 
314
    /* Get the INPUT argument from `argp_parse', which we know is a
 
315
       pointer to our plugin list pointer. */
 
316
    plugin **plugins = state->input;
376
317
    switch (key) {
377
 
    case 'g':                   /* --global-options */
 
318
    case 'g':
378
319
      if (arg != NULL){
379
320
        char *p;
380
321
        while((p = strsep(&arg, ",")) != NULL){
381
322
          if(p[0] == '\0'){
382
323
            continue;
383
324
          }
384
 
          if(not add_argument(getplugin(NULL), p)){
 
325
          if(not add_argument(getplugin(NULL, plugins), p)){
385
326
            perror("add_argument");
386
327
            return ARGP_ERR_UNKNOWN;
387
328
          }
388
329
        }
389
330
      }
390
331
      break;
391
 
    case 'G':                   /* --global-env */
 
332
    case 'e':
392
333
      if(arg == NULL){
393
334
        break;
394
335
      }
395
 
      if(not add_environment(getplugin(NULL), arg, true)){
396
 
        perror("add_environment");
 
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
        }
397
344
      }
398
345
      break;
399
 
    case 'o':                   /* --options-for */
 
346
    case 'o':
400
347
      if (arg != NULL){
401
348
        char *p_name = strsep(&arg, ":");
402
 
        if(p_name[0] == '\0' or arg == NULL){
 
349
        if(p_name[0] == '\0'){
403
350
          break;
404
351
        }
405
352
        char *opt = strsep(&arg, ":");
406
 
        if(opt[0] == '\0' or opt == NULL){
 
353
        if(opt[0] == '\0'){
407
354
          break;
408
355
        }
409
 
        char *p;
410
 
        while((p = strsep(&opt, ",")) != NULL){
411
 
          if(p[0] == '\0'){
412
 
            continue;
413
 
          }
414
 
          if(not add_argument(getplugin(p_name), p)){
415
 
            perror("add_argument");
416
 
            return ARGP_ERR_UNKNOWN;
 
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
            }
417
366
          }
418
367
        }
419
368
      }
420
369
      break;
421
 
    case 'E':                   /* --env-for */
 
370
    case 'f':
422
371
      if(arg == NULL){
423
372
        break;
424
373
      }
427
376
        if(envdef == NULL){
428
377
          break;
429
378
        }
430
 
        *envdef = '\0';
431
 
        if(not add_environment(getplugin(arg), envdef+1, true)){
 
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)){
432
385
          perror("add_environment");
433
386
        }
434
387
      }
435
388
      break;
436
 
    case 'd':                   /* --disable */
 
389
    case 'd':
437
390
      if (arg != NULL){
438
 
        plugin *p = getplugin(arg);
 
391
        plugin *p = getplugin(arg, plugins);
439
392
        if(p == NULL){
440
393
          return ARGP_ERR_UNKNOWN;
441
394
        }
442
395
        p->disabled = true;
443
396
      }
444
397
      break;
445
 
    case 'e':                   /* --enable */
446
 
      if (arg != NULL){
447
 
        plugin *p = getplugin(arg);
448
 
        if(p == NULL){
449
 
          return ARGP_ERR_UNKNOWN;
450
 
        }
451
 
        p->disabled = false;
452
 
      }
453
 
      break;
454
 
    case 128:                   /* --plugin-dir */
455
 
      free(plugindir);
456
 
      plugindir = strdup(arg);
457
 
      if(plugindir == NULL){
458
 
        perror("strdup");
459
 
      }      
460
 
      break;
461
 
    case 129:                   /* --config-file */
462
 
      /* This is already done by parse_opt_config_file() */
463
 
      break;
464
 
    case 130:                   /* --userid */
 
398
    case 128:
 
399
      plugindir = arg;
 
400
      break;
 
401
    case 129:
465
402
      uid = (uid_t)strtol(arg, NULL, 10);
466
403
      break;
467
 
    case 131:                   /* --groupid */
 
404
    case 130:
468
405
      gid = (gid_t)strtol(arg, NULL, 10);
469
406
      break;
470
 
    case 132:                   /* --debug */
 
407
    case 131:
471
408
      debug = true;
472
409
      break;
473
410
    case ARGP_KEY_ARG:
474
 
      /* Cryptsetup always passes an argument, which is an empty
475
 
         string if "none" was specified in /etc/crypttab.  So if
476
 
         argument was empty, we ignore it silently. */
477
 
      if(arg[0] != '\0'){
478
 
        fprintf(stderr, "Ignoring unknown argument \"%s\"\n", arg);
479
 
      }
480
 
      break;
481
 
    case ARGP_KEY_END:
482
 
      break;
483
 
    default:
484
 
      return ARGP_ERR_UNKNOWN;
485
 
    }
486
 
    return 0;
487
 
  }
488
 
  
489
 
  /* This option parser is the same as parse_opt() above, except it
490
 
     ignores everything but the --config-file option. */
491
 
  error_t parse_opt_config_file (int key, char *arg,
492
 
                                 __attribute__((unused))
493
 
                                 struct argp_state *state) {
494
 
    switch (key) {
495
 
    case 'g':                   /* --global-options */
496
 
    case 'G':                   /* --global-env */
497
 
    case 'o':                   /* --options-for */
498
 
    case 'E':                   /* --env-for */
499
 
    case 'd':                   /* --disable */
500
 
    case 'e':                   /* --enable */
501
 
    case 128:                   /* --plugin-dir */
502
 
      break;
503
 
    case 129:                   /* --config-file */
504
 
      free(argfile);
505
 
      argfile = strdup(arg);
506
 
      if(argfile == NULL){
507
 
        perror("strdup");
508
 
      }
509
 
      break;      
510
 
    case 130:                   /* --userid */
511
 
    case 131:                   /* --groupid */
512
 
    case 132:                   /* --debug */
513
 
    case ARGP_KEY_ARG:
514
 
    case ARGP_KEY_END:
515
 
      break;
516
 
    default:
517
 
      return ARGP_ERR_UNKNOWN;
518
 
    }
519
 
    return 0;
520
 
  }
521
 
  
522
 
  struct argp argp = { .options = options,
523
 
                       .parser = parse_opt_config_file,
524
 
                       .args_doc = "",
 
411
      fprintf(stderr, "Ignoring unknown argument \"%s\"\n", arg);
 
412
      break;
 
413
    case ARGP_KEY_END:
 
414
      break;
 
415
    default:
 
416
      return ARGP_ERR_UNKNOWN;
 
417
    }
 
418
    return 0;
 
419
  }
 
420
  
 
421
  plugin *plugin_list = NULL;
 
422
  
 
423
  struct argp argp = { .options = options, .parser = parse_opt,
 
424
                       .args_doc = "[+PLUS_SEPARATED_OPTIONS]",
525
425
                       .doc = "Mandos plugin runner -- Run plugins" };
526
426
  
527
 
  /* Parse using the parse_opt_config_file in order to get the custom
528
 
     config file location, if any. */
529
 
  ret = argp_parse (&argp, argc, argv, ARGP_IN_ORDER, 0, NULL);
 
427
  ret = argp_parse (&argp, argc, argv, 0, 0, &plugin_list);
530
428
  if (ret == ARGP_ERR_UNKNOWN){
531
429
    fprintf(stderr, "Unknown error while parsing arguments\n");
532
430
    exitstatus = EXIT_FAILURE;
533
 
    goto fallback;
 
431
    goto end;
534
432
  }
535
 
  
536
 
  /* Reset to the normal argument parser */
537
 
  argp.parser = parse_opt;
538
 
  
539
 
  /* Open the configfile if available */
540
 
  if (argfile == NULL){
541
 
    conffp = fopen(AFILE, "r");
542
 
  } else {
543
 
    conffp = fopen(argfile, "r");
544
 
  }  
 
433
 
 
434
  conffp = fopen(argfile, "r");
545
435
  if(conffp != NULL){
546
436
    char *org_line = NULL;
547
 
    char *p, *arg, *new_arg, *line;
548
437
    size_t size = 0;
549
438
    ssize_t sret;
 
439
    char *p, *arg, *new_arg, *line;
550
440
    const char whitespace_delims[] = " \r\t\f\v\n";
551
441
    const char comment_delim[] = "#";
552
442
 
553
 
    custom_argc = 1;
554
 
    custom_argv = malloc(sizeof(char*) * 2);
555
 
    if(custom_argv == NULL){
556
 
      perror("malloc");
557
 
      exitstatus = EXIT_FAILURE;
558
 
      goto fallback;
559
 
    }
560
 
    custom_argv[0] = argv[0];
561
 
    custom_argv[1] = NULL;
562
 
 
563
 
    /* for each line in the config file, strip whitespace and ignore
564
 
       commented text */
565
443
    while(true){
566
444
      sret = getline(&org_line, &size, conffp);
567
445
      if(sret == -1){
575
453
          continue;
576
454
        }
577
455
        new_arg = strdup(p);
578
 
        if(new_arg == NULL){
579
 
          perror("strdup");
580
 
          exitstatus = EXIT_FAILURE;
581
 
          free(org_line);
582
 
          goto fallback;
583
 
        }
584
 
        
585
 
        custom_argc += 1;
586
 
        custom_argv = realloc(custom_argv, sizeof(char *)
587
 
                              * ((unsigned int) custom_argc + 1));
588
 
        if(custom_argv == NULL){
589
 
          perror("realloc");
590
 
          exitstatus = EXIT_FAILURE;
591
 
          free(org_line);
592
 
          goto fallback;
593
 
        }
594
 
        custom_argv[custom_argc-1] = new_arg;
595
 
        custom_argv[custom_argc] = NULL;        
 
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
        }
596
462
      }
597
463
    }
598
464
    free(org_line);
599
 
  } else {
 
465
  } else{
600
466
    /* Check for harmful errors and go to fallback. Other errors might
601
467
       not affect opening plugins */
602
468
    if (errno == EMFILE or errno == ENFILE or errno == ENOMEM){
603
469
      perror("fopen");
604
470
      exitstatus = EXIT_FAILURE;
605
 
      goto fallback;
 
471
      goto end;
606
472
    }
607
473
  }
608
 
  /* If there was any arguments from configuration file,
609
 
     pass them to parser as command arguments */
 
474
 
610
475
  if(custom_argv != NULL){
611
 
    ret = argp_parse (&argp, custom_argc, custom_argv, ARGP_IN_ORDER,
612
 
                      0, NULL);
 
476
    custom_argv[0] = argv[0];
 
477
    ret = argp_parse (&argp, custom_argc, custom_argv, 0, 0, &plugin_list);
613
478
    if (ret == ARGP_ERR_UNKNOWN){
614
479
      fprintf(stderr, "Unknown error while parsing arguments\n");
615
480
      exitstatus = EXIT_FAILURE;
616
 
      goto fallback;
 
481
      goto end;
617
482
    }
618
483
  }
619
484
  
620
 
  /* Parse actual command line arguments, to let them override the
621
 
     config file */
622
 
  ret = argp_parse (&argp, argc, argv, ARGP_IN_ORDER, 0, NULL);
623
 
  if (ret == ARGP_ERR_UNKNOWN){
624
 
    fprintf(stderr, "Unknown error while parsing arguments\n");
625
 
    exitstatus = EXIT_FAILURE;
626
 
    goto fallback;
627
 
  }
628
 
  
629
485
  if(debug){
630
486
    for(plugin *p = plugin_list; p != NULL; p=p->next){
631
487
      fprintf(stderr, "Plugin: %s has %d arguments\n",
640
496
    }
641
497
  }
642
498
  
643
 
  /* Strip permissions down to nobody */
644
499
  ret = setuid(uid);
645
500
  if (ret == -1){
646
501
    perror("setuid");
647
 
  }  
 
502
  }
 
503
  
648
504
  setgid(gid);
649
505
  if (ret == -1){
650
506
    perror("setgid");
651
507
  }
652
508
  
653
 
  if (plugindir == NULL){
654
 
    dir = opendir(PDIR);
655
 
  } else {
656
 
    dir = opendir(plugindir);
657
 
  }
658
 
  
 
509
  dir = opendir(plugindir);
659
510
  if(dir == NULL){
660
511
    perror("Could not open plugin dir");
661
512
    exitstatus = EXIT_FAILURE;
662
 
    goto fallback;
 
513
    goto end;
663
514
  }
664
515
  
665
516
  /* Set the FD_CLOEXEC flag on the directory, if possible */
670
521
      if(ret < 0){
671
522
        perror("set_cloexec_flag");
672
523
        exitstatus = EXIT_FAILURE;
673
 
        goto fallback;
 
524
        goto end;
674
525
      }
675
526
    }
676
527
  }
677
528
  
678
529
  FD_ZERO(&rfds_all);
679
530
  
680
 
  /* Read and execute any executable in the plugin directory*/
681
531
  while(true){
682
532
    dirst = readdir(dir);
683
533
    
684
 
    /* All directory entries have been processed */
 
534
    // All directory entries have been processed
685
535
    if(dirst == NULL){
686
536
      if (errno == EBADF){
687
537
        perror("readdir");
688
538
        exitstatus = EXIT_FAILURE;
689
 
        goto fallback;
 
539
        goto end;
690
540
      }
691
541
      break;
692
542
    }
693
543
    
694
544
    d_name_len = strlen(dirst->d_name);
695
545
    
696
 
    /* Ignore dotfiles, backup files and other junk */
 
546
    // Ignore dotfiles, backup files and other junk
697
547
    {
698
548
      bool bad_name = false;
699
549
      
701
551
      
702
552
      const char const *bad_suffixes[] = { "~", "#", ".dpkg-new",
703
553
                                           ".dpkg-old",
704
 
                                           ".dpkg-bak",
705
554
                                           ".dpkg-divert", NULL };
706
555
      for(const char **pre = bad_prefixes; *pre != NULL; pre++){
707
556
        size_t pre_len = strlen(*pre);
715
564
          break;
716
565
        }
717
566
      }
 
567
      
718
568
      if(bad_name){
719
569
        continue;
720
570
      }
 
571
      
721
572
      for(const char **suf = bad_suffixes; *suf != NULL; suf++){
722
573
        size_t suf_len = strlen(*suf);
723
574
        if((d_name_len >= suf_len)
738
589
    }
739
590
 
740
591
    char *filename;
741
 
    if(plugindir == NULL){
742
 
      ret = asprintf(&filename, PDIR "/%s", dirst->d_name);
743
 
    } else {
744
 
      ret = asprintf(&filename, "%s/%s", plugindir, dirst->d_name);
745
 
    }
 
592
    ret = asprintf(&filename, "%s/%s", plugindir, dirst->d_name);
746
593
    if(ret < 0){
747
594
      perror("asprintf");
748
595
      continue;
754
601
      free(filename);
755
602
      continue;
756
603
    }
757
 
 
758
 
    /* Ignore non-executable files */
 
604
    
759
605
    if (not S_ISREG(st.st_mode) or (access(filename, X_OK) != 0)){
760
606
      if(debug){
761
607
        fprintf(stderr, "Ignoring plugin dir entry \"%s\""
764
610
      free(filename);
765
611
      continue;
766
612
    }
767
 
    
768
 
    plugin *p = getplugin(dirst->d_name);
 
613
    plugin *p = getplugin(dirst->d_name, &plugin_list);
769
614
    if(p == NULL){
770
615
      perror("getplugin");
771
616
      free(filename);
781
626
    }
782
627
    {
783
628
      /* Add global arguments to argument list for this plugin */
784
 
      plugin *g = getplugin(NULL);
 
629
      plugin *g = getplugin(NULL, &plugin_list);
785
630
      if(g != NULL){
786
631
        for(char **a = g->argv + 1; *a != NULL; a++){
787
632
          if(not add_argument(p, *a)){
790
635
        }
791
636
        /* Add global environment variables */
792
637
        for(char **e = g->environ; *e != NULL; e++){
793
 
          if(not add_environment(p, *e, false)){
 
638
          if(not add_environment(p, *e)){
794
639
            perror("add_environment");
795
640
          }
796
641
        }
801
646
       process, too. */
802
647
    if(p->environ[0] != NULL){
803
648
      for(char **e = environ; *e != NULL; e++){
804
 
        if(not add_environment(p, *e, false)){
 
649
        char *copy = strdup(*e);
 
650
        if(copy == NULL){
 
651
          perror("strdup");
 
652
          continue;
 
653
        }
 
654
        if(not add_environment(p, copy)){
805
655
          perror("add_environment");
806
656
        }
807
657
      }
808
658
    }
809
659
    
810
 
    int pipefd[2];
 
660
    int pipefd[2]; 
811
661
    ret = pipe(pipefd);
812
662
    if (ret == -1){
813
663
      perror("pipe");
814
664
      exitstatus = EXIT_FAILURE;
815
 
      goto fallback;
 
665
      goto end;
816
666
    }
817
 
    /* Ask OS to automatic close the pipe on exec */
818
667
    ret = set_cloexec_flag(pipefd[0]);
819
668
    if(ret < 0){
820
669
      perror("set_cloexec_flag");
821
670
      exitstatus = EXIT_FAILURE;
822
 
      goto fallback;
 
671
      goto end;
823
672
    }
824
673
    ret = set_cloexec_flag(pipefd[1]);
825
674
    if(ret < 0){
826
675
      perror("set_cloexec_flag");
827
676
      exitstatus = EXIT_FAILURE;
828
 
      goto fallback;
 
677
      goto end;
829
678
    }
830
679
    /* Block SIGCHLD until process is safely in process list */
831
680
    ret = sigprocmask (SIG_BLOCK, &sigchld_action.sa_mask, NULL);
832
681
    if(ret < 0){
833
682
      perror("sigprocmask");
834
683
      exitstatus = EXIT_FAILURE;
835
 
      goto fallback;
 
684
      goto end;
836
685
    }
837
 
    /* Starting a new process to be watched */
 
686
    // Starting a new process to be watched
838
687
    pid_t pid = fork();
839
688
    if(pid == -1){
840
689
      perror("fork");
841
690
      exitstatus = EXIT_FAILURE;
842
 
      goto fallback;
 
691
      goto end;
843
692
    }
844
693
    if(pid == 0){
845
694
      /* this is the child process */
848
697
        perror("sigaction");
849
698
        _exit(EXIT_FAILURE);
850
699
      }
851
 
      ret = sigprocmask(SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
 
700
      ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
852
701
      if(ret < 0){
853
702
        perror("sigprocmask");
854
703
        _exit(EXIT_FAILURE);
855
704
      }
856
 
      
 
705
 
857
706
      ret = dup2(pipefd[1], STDOUT_FILENO); /* replace our stdout */
858
707
      if(ret == -1){
859
708
        perror("dup2");
878
727
      }
879
728
      /* no return */
880
729
    }
881
 
    /* Parent process */
882
 
    close(pipefd[1]);           /* Close unused write end of pipe */
 
730
    /* parent process */
883
731
    free(filename);
884
 
    plugin *new_plugin = getplugin(dirst->d_name);
885
 
    if (new_plugin == NULL){
886
 
      perror("getplugin");
 
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");
887
736
      ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
888
737
      if(ret < 0){
889
 
        perror("sigprocmask");
 
738
        perror("sigprocmask");
890
739
      }
891
740
      exitstatus = EXIT_FAILURE;
892
 
      goto fallback;
 
741
      goto end;
893
742
    }
894
743
    
895
 
    new_plugin->pid = pid;
896
 
    new_plugin->fd = pipefd[0];
897
 
    
 
744
    *new_process = (struct process){ .pid = pid,
 
745
                                     .fd = pipefd[0],
 
746
                                     .next = process_list };
 
747
    // List handling
 
748
    process_list = new_process;
898
749
    /* Unblock SIGCHLD so signal handler can be run if this process
899
750
       has already completed */
900
751
    ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
901
752
    if(ret < 0){
902
753
      perror("sigprocmask");
903
754
      exitstatus = EXIT_FAILURE;
904
 
      goto fallback;
905
 
    }
906
 
    
907
 
    FD_SET(new_plugin->fd, &rfds_all);
908
 
    
909
 
    if (maxfd < new_plugin->fd){
910
 
      maxfd = new_plugin->fd;
911
 
    }
 
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);
912
776
  }
913
777
  
914
778
  closedir(dir);
915
779
  dir = NULL;
916
 
  
917
 
  for(plugin *p = plugin_list; p != NULL; p = p->next){
918
 
    if(p->pid != 0){
919
 
      break;
920
 
    }
921
 
    if(p->next == NULL){
922
 
      fprintf(stderr, "No plugin processes started. Incorrect plugin"
923
 
              " directory?\n");
924
 
      free_plugin_list();
925
 
    }
 
780
    
 
781
  if (process_list == NULL){
 
782
    fprintf(stderr, "No plugin processes started. Incorrect plugin"
 
783
            " directory?\n");
 
784
    process_list = NULL;
926
785
  }
927
 
  
928
 
  /* Main loop while running plugins exist */
929
 
  while(plugin_list){
 
786
  while(process_list){
930
787
    fd_set rfds = rfds_all;
931
788
    int select_ret = select(maxfd+1, &rfds, NULL, NULL, NULL);
932
789
    if (select_ret == -1){
933
790
      perror("select");
934
791
      exitstatus = EXIT_FAILURE;
935
 
      goto fallback;
 
792
      goto end;
936
793
    }
937
794
    /* OK, now either a process completed, or something can be read
938
795
       from one of them */
939
 
    for(plugin *proc = plugin_list; proc != NULL;){
 
796
    for(process *proc = process_list; proc ; proc = proc->next){
940
797
      /* Is this process completely done? */
941
798
      if(proc->eof and proc->completed){
942
799
        /* Only accept the plugin output if it exited cleanly */
943
800
        if(not WIFEXITED(proc->status)
944
801
           or WEXITSTATUS(proc->status) != 0){
945
802
          /* Bad exit by plugin */
946
 
 
947
803
          if(debug){
948
804
            if(WIFEXITED(proc->status)){
949
805
              fprintf(stderr, "Plugin %u exited with status %d\n",
958
814
                      (unsigned int) (proc->pid));
959
815
            }
960
816
          }
961
 
          
962
817
          /* Remove the plugin */
963
818
          FD_CLR(proc->fd, &rfds_all);
964
 
 
965
819
          /* Block signal while modifying process_list */
966
 
          ret = sigprocmask(SIG_BLOCK, &sigchld_action.sa_mask, NULL);
 
820
          ret = sigprocmask (SIG_BLOCK, &sigchld_action.sa_mask, NULL);
967
821
          if(ret < 0){
968
822
            perror("sigprocmask");
969
823
            exitstatus = EXIT_FAILURE;
970
 
            goto fallback;
971
 
          }
972
 
          
973
 
          plugin *next_plugin = proc->next;
974
 
          free_plugin(proc);
975
 
          proc = next_plugin;
976
 
          
 
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;
 
836
              }
 
837
            }
 
838
          }
977
839
          /* We are done modifying process list, so unblock signal */
978
840
          ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask,
979
841
                             NULL);
980
842
          if(ret < 0){
981
843
            perror("sigprocmask");
982
 
            exitstatus = EXIT_FAILURE;
983
 
            goto fallback;
984
 
          }
985
 
          
986
 
          if(plugin_list == NULL){
987
 
            break;
988
 
          }
989
 
          
990
 
          continue;
 
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;
991
851
        }
992
 
        
993
852
        /* This process exited nicely, so print its buffer */
994
 
        
995
 
        bool bret = print_out_password(proc->buffer,
996
 
                                       proc->buffer_length);
 
853
 
 
854
        bool bret = print_out_password(proc->buffer, proc->buffer_length);
997
855
        if(not bret){
998
856
          perror("print_out_password");
999
857
          exitstatus = EXIT_FAILURE;
1000
858
        }
1001
 
        goto fallback;
 
859
        goto end;
1002
860
      }
1003
 
      
1004
861
      /* This process has not completed.  Does it have any output? */
1005
862
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){
1006
863
        /* This process had nothing to say at this time */
1007
 
        proc = proc->next;
1008
864
        continue;
1009
865
      }
1010
866
      /* Before reading, make the process' data buffer large enough */
1014
870
        if (proc->buffer == NULL){
1015
871
          perror("malloc");
1016
872
          exitstatus = EXIT_FAILURE;
1017
 
          goto fallback;
 
873
          goto end;
1018
874
        }
1019
875
        proc->buffer_size += BUFFER_SIZE;
1020
876
      }
1023
879
                 BUFFER_SIZE);
1024
880
      if(ret < 0){
1025
881
        /* Read error from this process; ignore the error */
1026
 
        proc = proc->next;
1027
882
        continue;
1028
883
      }
1029
884
      if(ret == 0){
1036
891
  }
1037
892
 
1038
893
 
1039
 
 fallback:
 
894
 end:
1040
895
  
1041
 
  if(plugin_list == NULL or exitstatus != EXIT_SUCCESS){
1042
 
    /* Fallback if all plugins failed, none are found or an error
1043
 
       occured */
 
896
  if(process_list == NULL or exitstatus != EXIT_SUCCESS){
 
897
    /* Fallback if all plugins failed, none are found or an error occured */
1044
898
    bool bret;
1045
899
    fprintf(stderr, "Going to fallback mode using getpass(3)\n");
1046
900
    char *passwordbuffer = getpass("Password: ");
1047
 
    size_t len = strlen(passwordbuffer);
1048
 
    /* Strip trailing newline */
1049
 
    if(len > 0 and passwordbuffer[len-1] == '\n'){
1050
 
      passwordbuffer[len-1] = '\0'; /* not strictly necessary */
1051
 
      len--;
1052
 
    }
1053
 
    bret = print_out_password(passwordbuffer, len);
 
901
    bret = print_out_password(passwordbuffer, strlen(passwordbuffer));
1054
902
    if(not bret){
1055
903
      perror("print_out_password");
1056
904
      exitstatus = EXIT_FAILURE;
 
905
      goto end;
1057
906
    }
1058
907
  }
1059
908
  
1060
909
  /* Restore old signal handler */
1061
 
  ret = sigaction(SIGCHLD, &old_sigchld_action, NULL);
1062
 
  if(ret == -1){
1063
 
    perror("sigaction");
1064
 
    exitstatus = EXIT_FAILURE;
1065
 
  }
1066
 
  
1067
 
  if(custom_argv != NULL){
1068
 
    for(char **arg = custom_argv+1; *arg != NULL; arg++){
1069
 
      free(*arg);
 
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);
 
921
      }
1070
922
    }
1071
 
    free(custom_argv);
 
923
    free(plugin_list->environ);
 
924
    free(plugin_list);
1072
925
  }
1073
926
  
1074
927
  if(dir != NULL){
1075
928
    closedir(dir);
1076
929
  }
1077
930
  
1078
 
  /* Kill the processes */
1079
 
  for(plugin *p = plugin_list; p != NULL; p = p->next){
1080
 
    if(p->pid != 0){
1081
 
      close(p->fd);
1082
 
      ret = kill(p->pid, SIGTERM);
1083
 
      if(ret == -1 and errno != ESRCH){
1084
 
        /* Set-uid proccesses might not get closed */
1085
 
        perror("kill");
1086
 
      }
 
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");
1087
939
    }
 
940
    free(process_list->buffer);
 
941
    free(process_list);
1088
942
  }
1089
943
  
1090
944
  /* Wait for any remaining child processes to terminate */
1095
949
    perror("wait");
1096
950
  }
1097
951
  
1098
 
  free_plugin_list();
1099
 
  
1100
 
  free(plugindir);
1101
 
  free(argfile);
1102
 
  
1103
952
  return exitstatus;
1104
953
}