/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-12-10 01:26:02 UTC
  • mfrom: (237.1.2 mandos)
  • Revision ID: teddy@fukt.bsnet.se-20081210012602-vhz3h75xkj24t340
First version of a somewhat complete D-Bus server interface.  Also
change user/group name to "_mandos".

* debian/mandos.postinst: Rename old "mandos" user and group to
                          "_mandos"; create "_mandos" user and group
                          if none exist.
* debian/mandos-client.postinst: - '' -

* initramfs-tools-hook: Try "_mandos" before "mandos" as user and
                        group name.

* mandos (_datetime_to_dbus_struct): New; was previously local.
  (Client.started): Renamed to "last_started".  All users changed.
  (Client.started): New; boolean.
  (Client.dbus_object_path): New.
  (Client.check_command): Renamed to "checker_command".  All users
                          changed.
  (Client.__init__): Set and use "self.dbus_object_path".  Set
                     "self.started".
  (Client.start): Update "self.started".  Emit "self.PropertyChanged"
                  signals for both "started" and "last_started".
  (Client.stop): Update "self.started".  Emit "self.PropertyChanged"
                 signal for "started".
  (Client.checker_callback): Take additional "command" argument.  All
                             callers changed. Emit
                             "self.PropertyChanged" signal.
  (Client.bump_timeout): Emit "self.PropertyChanged" signal for
                         "last_checked_ok".
  (Client.start_checker): Emit "self.PropertyChanged" signal for
                          "checker_running".
  (Client.stop_checker): Emit "self.PropertyChanged" signal for
                         "checker_running".
  (Client.still_valid): Bug fix: use "getattr(self, started, False)"
                        instead of "self.started" in case this client
                        object is so new that the "started" attribute
                        has not been created yet.
  (Client.IntervalChanged, Client.CheckerIsRunning, Client.GetChecker,
  Client.GetCreated, Client.GetFingerprint, Client.GetHost,
  Client.GetInterval, Client.GetName, Client.GetStarted,
  Client.GetTimeout, Client.StateChanged, Client.TimeoutChanged):
  Removed; all callers changed.
  (Client.CheckerCompleted): Add "condition" and "command" arguments.
                             All callers changed.
  (Client.GetAllProperties, Client.PropertyChanged): New.
  (Client.StillValid): Renamed to "IsStillValid".
  (Client.StartChecker): Changed to its own function to avoid the
                         return value from "Client.start_checker()".
  (Client.Stop): Changed to its own function to avoid the return value
                 from "Client.stop()".
  (main): Try "_mandos" before "mandos" as user and group name.
          Removed inner function "remove_from_clients".  New inner
          class "MandosServer".

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