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