/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,
69
70
#define PDIR "/lib/mandos/plugins.d"
70
71
#define AFILE "/conf/conf.d/mandos/plugin-runner.conf"
71
72
 
72
 
const char *argp_program_version = "plugin-runner 1.0";
 
73
const char *argp_program_version = "plugin-runner " VERSION;
73
74
const char *argp_program_bug_address = "<mandos@fukt.bsnet.se>";
74
75
 
75
 
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;
76
83
 
77
 
typedef struct process{
 
84
  /* Variables used for running processes*/
78
85
  pid_t pid;
79
86
  int fd;
80
87
  char *buffer;
83
90
  bool eof;
84
91
  volatile bool completed;
85
92
  volatile int status;
86
 
  struct process *next;
87
 
} process;
88
 
 
89
 
typedef struct plugin{
90
 
  char *name;                   /* can be NULL or any plugin name */
91
 
  char **argv;
92
 
  int argc;
93
 
  char **environ;
94
 
  int envc;
95
 
  bool disabled;
96
93
  struct plugin *next;
97
94
} plugin;
98
95
 
99
 
static plugin *getplugin(char *name, plugin **plugin_list){
100
 
  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){
101
103
    if ((p->name == name)
102
104
        or (p->name and name and (strcmp(p->name, name) == 0))){
103
105
      return p;
118
120
  
119
121
  *new_plugin = (plugin) { .name = copy_name,
120
122
                           .argc = 1,
121
 
                           .envc = 0,
122
123
                           .disabled = false,
123
 
                           .next = *plugin_list };
 
124
                           .next = plugin_list };
124
125
  
125
126
  new_plugin->argv = malloc(sizeof(char *) * 2);
126
127
  if (new_plugin->argv == NULL){
130
131
  }
131
132
  new_plugin->argv[0] = copy_name;
132
133
  new_plugin->argv[1] = NULL;
133
 
 
 
134
  
134
135
  new_plugin->environ = malloc(sizeof(char *));
135
136
  if(new_plugin->environ == NULL){
136
137
    free(copy_name);
139
140
    return NULL;
140
141
  }
141
142
  new_plugin->environ[0] = NULL;
 
143
  
142
144
  /* Append the new plugin to the list */
143
 
  *plugin_list = new_plugin;
 
145
  plugin_list = new_plugin;
144
146
  return new_plugin;
145
147
}
146
148
 
176
178
}
177
179
 
178
180
/* Add to a plugin's environment */
179
 
static bool add_environment(plugin *p, const char *def){
 
181
static bool add_environment(plugin *p, const char *def, bool replace){
180
182
  if(p == NULL){
181
183
    return false;
182
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
  }
183
202
  return add_to_char_array(def, &(p->environ), &(p->envc));
184
203
}
185
204
 
186
 
 
187
205
/*
188
206
 * Based on the example in the GNU LibC manual chapter 13.13 "File
189
207
 * Descriptor Flags".
190
208
 * *Note File Descriptor Flags:(libc)Descriptor Flags.
191
209
 */
192
 
static int set_cloexec_flag(int fd)
193
 
{
 
210
static int set_cloexec_flag(int fd){
194
211
  int ret = fcntl(fd, F_GETFD, 0);
195
212
  /* If reading the flags failed, return error indication now. */
196
213
  if(ret < 0){
200
217
  return fcntl(fd, F_SETFD, ret | FD_CLOEXEC);
201
218
}
202
219
 
203
 
process *process_list = NULL;
204
220
 
205
221
/* Mark processes as completed when they exit, and save their exit
206
222
   status. */
207
 
void handle_sigchld(__attribute__((unused)) int sig){
 
223
static void handle_sigchld(__attribute__((unused)) int sig){
208
224
  while(true){
209
 
    process *proc = process_list;
 
225
    plugin *proc = plugin_list;
210
226
    int status;
211
227
    pid_t pid = waitpid(-1, &status, WNOHANG);
212
228
    if(pid == 0){
220
236
      /* No child processes */
221
237
      break;
222
238
    }
223
 
 
 
239
    
224
240
    /* A child exited, find it in process_list */
225
241
    while(proc != NULL and proc->pid != pid){
226
242
      proc = proc->next;
234
250
  }
235
251
}
236
252
 
237
 
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){
238
255
  ssize_t ret;
239
 
  if(length>0 and buffer[length-1] == '\n'){
240
 
    length--;
241
 
  }
242
256
  for(size_t written = 0; written < length; written += (size_t)ret){
243
257
    ret = TEMP_FAILURE_RETRY(write(STDOUT_FILENO, buffer + written,
244
258
                                   length - written));
249
263
  return true;
250
264
}
251
265
 
252
 
static void free_plugin_list(plugin *plugin_list){
253
 
  for(plugin *next; plugin_list != NULL; plugin_list = next){
254
 
    next = plugin_list->next;
255
 
    for(char **arg = plugin_list->argv; *arg != NULL; arg++){
256
 
      free(*arg);
257
 
    }
258
 
    free(plugin_list->argv);
259
 
    for(char **env = plugin_list->environ; *env != NULL; env++){
260
 
      free(*env);
261
 
    }
262
 
    free(plugin_list->environ);
263
 
    free(plugin_list);
 
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
      }
 
290
    }
 
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);
264
299
  }
265
300
}
266
301
 
304
339
    { .name = "global-options", .key = 'g',
305
340
      .arg = "OPTION[,OPTION[,...]]",
306
341
      .doc = "Options passed to all plugins" },
307
 
    { .name = "global-envs", .key = 'e',
 
342
    { .name = "global-env", .key = 'G',
308
343
      .arg = "VAR=value",
309
344
      .doc = "Environment variable passed to all plugins" },
310
345
    { .name = "options-for", .key = 'o',
311
346
      .arg = "PLUGIN:OPTION[,OPTION[,...]]",
312
347
      .doc = "Options passed only to specified plugin" },
313
 
    { .name = "envs-for", .key = 'f',
 
348
    { .name = "env-for", .key = 'E',
314
349
      .arg = "PLUGIN:ENV=value",
315
350
      .doc = "Environment variable passed to specified plugin" },
316
351
    { .name = "disable", .key = 'd',
317
352
      .arg = "PLUGIN",
318
353
      .doc = "Disable a specific plugin", .group = 1 },
 
354
    { .name = "enable", .key = 'e',
 
355
      .arg = "PLUGIN",
 
356
      .doc = "Enable a specific plugin", .group = 1 },
319
357
    { .name = "plugin-dir", .key = 128,
320
358
      .arg = "DIRECTORY",
321
359
      .doc = "Specify a different plugin directory", .group = 2 },
333
371
    { .name = NULL }
334
372
  };
335
373
  
336
 
  error_t parse_opt (int key, char *arg, struct argp_state *state) {
337
 
    /* Get the INPUT argument from `argp_parse', which we know is a
338
 
       pointer to our plugin list pointer. */
339
 
    plugin **plugins = state->input;
 
374
  error_t parse_opt (int key, char *arg, __attribute__((unused))
 
375
                     struct argp_state *state) {
340
376
    switch (key) {
341
 
    case 'g':
 
377
    case 'g':                   /* --global-options */
342
378
      if (arg != NULL){
343
379
        char *p;
344
380
        while((p = strsep(&arg, ",")) != NULL){
345
381
          if(p[0] == '\0'){
346
382
            continue;
347
383
          }
348
 
          if(not add_argument(getplugin(NULL, plugins), p)){
 
384
          if(not add_argument(getplugin(NULL), p)){
349
385
            perror("add_argument");
350
386
            return ARGP_ERR_UNKNOWN;
351
387
          }
352
388
        }
353
389
      }
354
390
      break;
355
 
    case 'e':
 
391
    case 'G':                   /* --global-env */
356
392
      if(arg == NULL){
357
393
        break;
358
394
      }
359
 
      {
360
 
        char *envdef = strdup(arg);
361
 
        if(envdef == NULL){
362
 
          break;
363
 
        }
364
 
        if(not add_environment(getplugin(NULL, plugins), envdef)){
365
 
          perror("add_environment");
366
 
        }
 
395
      if(not add_environment(getplugin(NULL), arg, true)){
 
396
        perror("add_environment");
367
397
      }
368
398
      break;
369
 
    case 'o':
 
399
    case 'o':                   /* --options-for */
370
400
      if (arg != NULL){
371
401
        char *p_name = strsep(&arg, ":");
372
 
        if(p_name[0] == '\0'){
 
402
        if(p_name[0] == '\0' or arg == NULL){
373
403
          break;
374
404
        }
375
405
        char *opt = strsep(&arg, ":");
376
 
        if(opt[0] == '\0'){
 
406
        if(opt[0] == '\0' or opt == NULL){
377
407
          break;
378
408
        }
379
 
        if(opt != NULL){
380
 
          char *p;
381
 
          while((p = strsep(&opt, ",")) != NULL){
382
 
            if(p[0] == '\0'){
383
 
              continue;
384
 
            }
385
 
            if(not add_argument(getplugin(p_name, plugins), p)){
386
 
              perror("add_argument");
387
 
              return ARGP_ERR_UNKNOWN;
388
 
            }
 
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;
389
417
          }
390
418
        }
391
419
      }
392
420
      break;
393
 
    case 'f':
 
421
    case 'E':                   /* --env-for */
394
422
      if(arg == NULL){
395
423
        break;
396
424
      }
399
427
        if(envdef == NULL){
400
428
          break;
401
429
        }
402
 
        char *p_name = strndup(arg, (size_t) (envdef-arg));
403
 
        if(p_name == NULL){
404
 
          break;
405
 
        }
406
 
        envdef++;
407
 
        if(not add_environment(getplugin(p_name, plugins), envdef)){
 
430
        *envdef = '\0';
 
431
        if(not add_environment(getplugin(arg), envdef+1, true)){
408
432
          perror("add_environment");
409
433
        }
410
434
      }
411
435
      break;
412
 
    case 'd':
 
436
    case 'd':                   /* --disable */
413
437
      if (arg != NULL){
414
 
        plugin *p = getplugin(arg, plugins);
 
438
        plugin *p = getplugin(arg);
415
439
        if(p == NULL){
416
440
          return ARGP_ERR_UNKNOWN;
417
441
        }
418
442
        p->disabled = true;
419
443
      }
420
444
      break;
421
 
    case 128:
 
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);
422
456
      plugindir = strdup(arg);
423
457
      if(plugindir == NULL){
424
458
        perror("strdup");
425
459
      }      
426
460
      break;
427
 
    case 129:
 
461
    case 129:                   /* --config-file */
 
462
      /* This is already done by parse_opt_config_file() */
 
463
      break;
 
464
    case 130:                   /* --userid */
 
465
      uid = (uid_t)strtol(arg, NULL, 10);
 
466
      break;
 
467
    case 131:                   /* --groupid */
 
468
      gid = (gid_t)strtol(arg, NULL, 10);
 
469
      break;
 
470
    case 132:                   /* --debug */
 
471
      debug = true;
 
472
      break;
 
473
    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);
428
505
      argfile = strdup(arg);
429
506
      if(argfile == NULL){
430
507
        perror("strdup");
431
508
      }
432
509
      break;      
433
 
    case 130:
434
 
      uid = (uid_t)strtol(arg, NULL, 10);
435
 
      break;
436
 
    case 131:
437
 
      gid = (gid_t)strtol(arg, NULL, 10);
438
 
      break;
439
 
    case 132:
440
 
      debug = true;
441
 
      break;
 
510
    case 130:                   /* --userid */
 
511
    case 131:                   /* --groupid */
 
512
    case 132:                   /* --debug */
442
513
    case ARGP_KEY_ARG:
443
 
      fprintf(stderr, "Ignoring unknown argument \"%s\"\n", arg);
444
 
      break;
445
514
    case ARGP_KEY_END:
446
515
      break;
447
516
    default:
450
519
    return 0;
451
520
  }
452
521
  
453
 
  plugin *plugin_list = NULL;
454
 
  
455
 
  struct argp argp = { .options = options, .parser = parse_opt,
456
 
                       .args_doc = "[+PLUS_SEPARATED_OPTIONS]",
 
522
  struct argp argp = { .options = options,
 
523
                       .parser = parse_opt_config_file,
 
524
                       .args_doc = "",
457
525
                       .doc = "Mandos plugin runner -- Run plugins" };
458
526
  
459
 
  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);
460
530
  if (ret == ARGP_ERR_UNKNOWN){
461
531
    fprintf(stderr, "Unknown error while parsing arguments\n");
462
532
    exitstatus = EXIT_FAILURE;
463
533
    goto fallback;
464
534
  }
465
 
 
 
535
  
 
536
  /* Reset to the normal argument parser */
 
537
  argp.parser = parse_opt;
 
538
  
 
539
  /* Open the configfile if available */
466
540
  if (argfile == NULL){
467
541
    conffp = fopen(AFILE, "r");
468
542
  } else {
469
543
    conffp = fopen(argfile, "r");
470
 
  }
471
 
  
 
544
  }  
472
545
  if(conffp != NULL){
473
546
    char *org_line = NULL;
474
547
    char *p, *arg, *new_arg, *line;
486
559
    }
487
560
    custom_argv[0] = argv[0];
488
561
    custom_argv[1] = NULL;
489
 
    
 
562
 
 
563
    /* for each line in the config file, strip whitespace and ignore
 
564
       commented text */
490
565
    while(true){
491
566
      sret = getline(&org_line, &size, conffp);
492
567
      if(sret == -1){
521
596
      }
522
597
    }
523
598
    free(org_line);
524
 
  } else{
 
599
  } else {
525
600
    /* Check for harmful errors and go to fallback. Other errors might
526
601
       not affect opening plugins */
527
602
    if (errno == EMFILE or errno == ENFILE or errno == ENOMEM){
530
605
      goto fallback;
531
606
    }
532
607
  }
533
 
 
 
608
  /* If there was any arguments from configuration file,
 
609
     pass them to parser as command arguments */
534
610
  if(custom_argv != NULL){
535
 
    ret = argp_parse (&argp, custom_argc, custom_argv, 0, 0, &plugin_list);
 
611
    ret = argp_parse (&argp, custom_argc, custom_argv, ARGP_IN_ORDER,
 
612
                      0, NULL);
536
613
    if (ret == ARGP_ERR_UNKNOWN){
537
614
      fprintf(stderr, "Unknown error while parsing arguments\n");
538
615
      exitstatus = EXIT_FAILURE;
540
617
    }
541
618
  }
542
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
  
543
629
  if(debug){
544
630
    for(plugin *p = plugin_list; p != NULL; p=p->next){
545
631
      fprintf(stderr, "Plugin: %s has %d arguments\n",
554
640
    }
555
641
  }
556
642
  
 
643
  /* Strip permissions down to nobody */
557
644
  ret = setuid(uid);
558
645
  if (ret == -1){
559
646
    perror("setuid");
560
 
  }
561
 
  
 
647
  }  
562
648
  setgid(gid);
563
649
  if (ret == -1){
564
650
    perror("setgid");
565
651
  }
566
 
 
 
652
  
567
653
  if (plugindir == NULL){
568
654
    dir = opendir(PDIR);
569
655
  } else {
591
677
  
592
678
  FD_ZERO(&rfds_all);
593
679
  
 
680
  /* Read and execute any executable in the plugin directory*/
594
681
  while(true){
595
682
    dirst = readdir(dir);
596
683
    
597
 
    // All directory entries have been processed
 
684
    /* All directory entries have been processed */
598
685
    if(dirst == NULL){
599
686
      if (errno == EBADF){
600
687
        perror("readdir");
606
693
    
607
694
    d_name_len = strlen(dirst->d_name);
608
695
    
609
 
    // Ignore dotfiles, backup files and other junk
 
696
    /* Ignore dotfiles, backup files and other junk */
610
697
    {
611
698
      bool bad_name = false;
612
699
      
614
701
      
615
702
      const char const *bad_suffixes[] = { "~", "#", ".dpkg-new",
616
703
                                           ".dpkg-old",
 
704
                                           ".dpkg-bak",
617
705
                                           ".dpkg-divert", NULL };
618
706
      for(const char **pre = bad_prefixes; *pre != NULL; pre++){
619
707
        size_t pre_len = strlen(*pre);
627
715
          break;
628
716
        }
629
717
      }
630
 
      
631
718
      if(bad_name){
632
719
        continue;
633
720
      }
634
 
      
635
721
      for(const char **suf = bad_suffixes; *suf != NULL; suf++){
636
722
        size_t suf_len = strlen(*suf);
637
723
        if((d_name_len >= suf_len)
652
738
    }
653
739
 
654
740
    char *filename;
655
 
    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
    }
656
746
    if(ret < 0){
657
747
      perror("asprintf");
658
748
      continue;
664
754
      free(filename);
665
755
      continue;
666
756
    }
667
 
    
 
757
 
 
758
    /* Ignore non-executable files */
668
759
    if (not S_ISREG(st.st_mode) or (access(filename, X_OK) != 0)){
669
760
      if(debug){
670
761
        fprintf(stderr, "Ignoring plugin dir entry \"%s\""
673
764
      free(filename);
674
765
      continue;
675
766
    }
676
 
    plugin *p = getplugin(dirst->d_name, &plugin_list);
 
767
    
 
768
    plugin *p = getplugin(dirst->d_name);
677
769
    if(p == NULL){
678
770
      perror("getplugin");
679
771
      free(filename);
689
781
    }
690
782
    {
691
783
      /* Add global arguments to argument list for this plugin */
692
 
      plugin *g = getplugin(NULL, &plugin_list);
 
784
      plugin *g = getplugin(NULL);
693
785
      if(g != NULL){
694
786
        for(char **a = g->argv + 1; *a != NULL; a++){
695
787
          if(not add_argument(p, *a)){
698
790
        }
699
791
        /* Add global environment variables */
700
792
        for(char **e = g->environ; *e != NULL; e++){
701
 
          if(not add_environment(p, *e)){
 
793
          if(not add_environment(p, *e, false)){
702
794
            perror("add_environment");
703
795
          }
704
796
        }
709
801
       process, too. */
710
802
    if(p->environ[0] != NULL){
711
803
      for(char **e = environ; *e != NULL; e++){
712
 
        char *copy = strdup(*e);
713
 
        if(copy == NULL){
714
 
          perror("strdup");
715
 
          continue;
716
 
        }
717
 
        if(not add_environment(p, copy)){
 
804
        if(not add_environment(p, *e, false)){
718
805
          perror("add_environment");
719
806
        }
720
807
      }
727
814
      exitstatus = EXIT_FAILURE;
728
815
      goto fallback;
729
816
    }
 
817
    /* Ask OS to automatic close the pipe on exec */
730
818
    ret = set_cloexec_flag(pipefd[0]);
731
819
    if(ret < 0){
732
820
      perror("set_cloexec_flag");
746
834
      exitstatus = EXIT_FAILURE;
747
835
      goto fallback;
748
836
    }
749
 
    // Starting a new process to be watched
 
837
    /* Starting a new process to be watched */
750
838
    pid_t pid = fork();
751
839
    if(pid == -1){
752
840
      perror("fork");
760
848
        perror("sigaction");
761
849
        _exit(EXIT_FAILURE);
762
850
      }
763
 
      ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
 
851
      ret = sigprocmask(SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
764
852
      if(ret < 0){
765
853
        perror("sigprocmask");
766
854
        _exit(EXIT_FAILURE);
767
855
      }
768
 
 
 
856
      
769
857
      ret = dup2(pipefd[1], STDOUT_FILENO); /* replace our stdout */
770
858
      if(ret == -1){
771
859
        perror("dup2");
790
878
      }
791
879
      /* no return */
792
880
    }
793
 
    /* parent process */
 
881
    /* Parent process */
 
882
    close(pipefd[1]);           /* Close unused write end of pipe */
794
883
    free(filename);
795
 
    close(pipefd[1]);           /* close unused write end of pipe */
796
 
    process *new_process = malloc(sizeof(process));
797
 
    if (new_process == NULL){
798
 
      perror("malloc");
 
884
    plugin *new_plugin = getplugin(dirst->d_name);
 
885
    if (new_plugin == NULL){
 
886
      perror("getplugin");
799
887
      ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
800
888
      if(ret < 0){
801
 
        perror("sigprocmask");
 
889
        perror("sigprocmask");
802
890
      }
803
891
      exitstatus = EXIT_FAILURE;
804
892
      goto fallback;
805
893
    }
806
894
    
807
 
    *new_process = (struct process){ .pid = pid,
808
 
                                     .fd = pipefd[0],
809
 
                                     .next = process_list };
810
 
    // List handling
811
 
    process_list = new_process;
 
895
    new_plugin->pid = pid;
 
896
    new_plugin->fd = pipefd[0];
 
897
    
812
898
    /* Unblock SIGCHLD so signal handler can be run if this process
813
899
       has already completed */
814
900
    ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
818
904
      goto fallback;
819
905
    }
820
906
    
821
 
    FD_SET(new_process->fd, &rfds_all);
 
907
    FD_SET(new_plugin->fd, &rfds_all);
822
908
    
823
 
    if (maxfd < new_process->fd){
824
 
      maxfd = new_process->fd;
 
909
    if (maxfd < new_plugin->fd){
 
910
      maxfd = new_plugin->fd;
825
911
    }
826
 
    
827
912
  }
828
 
 
829
 
  free_plugin_list(plugin_list);
830
 
  plugin_list = NULL;
831
913
  
832
914
  closedir(dir);
833
915
  dir = NULL;
834
 
    
835
 
  if (process_list == NULL){
836
 
    fprintf(stderr, "No plugin processes started. Incorrect plugin"
837
 
            " directory?\n");
838
 
    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
    }
839
926
  }
840
 
  while(process_list){
 
927
  
 
928
  /* Main loop while running plugins exist */
 
929
  while(plugin_list){
841
930
    fd_set rfds = rfds_all;
842
931
    int select_ret = select(maxfd+1, &rfds, NULL, NULL, NULL);
843
932
    if (select_ret == -1){
847
936
    }
848
937
    /* OK, now either a process completed, or something can be read
849
938
       from one of them */
850
 
    for(process *proc = process_list; proc ; proc = proc->next){
 
939
    for(plugin *proc = plugin_list; proc != NULL;){
851
940
      /* Is this process completely done? */
852
941
      if(proc->eof and proc->completed){
853
942
        /* Only accept the plugin output if it exited cleanly */
854
943
        if(not WIFEXITED(proc->status)
855
944
           or WEXITSTATUS(proc->status) != 0){
856
945
          /* Bad exit by plugin */
 
946
 
857
947
          if(debug){
858
948
            if(WIFEXITED(proc->status)){
859
949
              fprintf(stderr, "Plugin %u exited with status %d\n",
868
958
                      (unsigned int) (proc->pid));
869
959
            }
870
960
          }
 
961
          
871
962
          /* Remove the plugin */
872
963
          FD_CLR(proc->fd, &rfds_all);
 
964
 
873
965
          /* Block signal while modifying process_list */
874
966
          ret = sigprocmask(SIG_BLOCK, &sigchld_action.sa_mask, NULL);
875
967
          if(ret < 0){
877
969
            exitstatus = EXIT_FAILURE;
878
970
            goto fallback;
879
971
          }
880
 
          /* Delete this process entry from the list */
881
 
          if(process_list == proc){
882
 
            /* First one - simple */
883
 
            process_list = proc->next;
884
 
          } else {
885
 
            /* Second one or later */
886
 
            for(process *p = process_list; p != NULL; p = p->next){
887
 
              if(p->next == proc){
888
 
                p->next = proc->next;
889
 
                break;
890
 
              }
891
 
            }
892
 
          }
 
972
          
 
973
          plugin *next_plugin = proc->next;
 
974
          free_plugin(proc);
 
975
          proc = next_plugin;
 
976
          
893
977
          /* We are done modifying process list, so unblock signal */
894
978
          ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask,
895
979
                             NULL);
896
980
          if(ret < 0){
897
981
            perror("sigprocmask");
898
 
          }
899
 
          free(proc->buffer);
900
 
          free(proc);
901
 
          /* We deleted this process from the list, so we can't go
902
 
             proc->next.  Therefore, start over from the beginning of
903
 
             the process list */
904
 
          break;
 
982
            exitstatus = EXIT_FAILURE;
 
983
            goto fallback;
 
984
          }
 
985
          
 
986
          if(plugin_list == NULL){
 
987
            break;
 
988
          }
 
989
          
 
990
          continue;
905
991
        }
 
992
        
906
993
        /* This process exited nicely, so print its buffer */
907
 
 
 
994
        
908
995
        bool bret = print_out_password(proc->buffer,
909
996
                                       proc->buffer_length);
910
997
        if(not bret){
913
1000
        }
914
1001
        goto fallback;
915
1002
      }
 
1003
      
916
1004
      /* This process has not completed.  Does it have any output? */
917
1005
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){
918
1006
        /* This process had nothing to say at this time */
 
1007
        proc = proc->next;
919
1008
        continue;
920
1009
      }
921
1010
      /* Before reading, make the process' data buffer large enough */
934
1023
                 BUFFER_SIZE);
935
1024
      if(ret < 0){
936
1025
        /* Read error from this process; ignore the error */
 
1026
        proc = proc->next;
937
1027
        continue;
938
1028
      }
939
1029
      if(ret == 0){
948
1038
 
949
1039
 fallback:
950
1040
  
951
 
  if(process_list == NULL or exitstatus != EXIT_SUCCESS){
 
1041
  if(plugin_list == NULL or exitstatus != EXIT_SUCCESS){
952
1042
    /* Fallback if all plugins failed, none are found or an error
953
1043
       occured */
954
1044
    bool bret;
955
1045
    fprintf(stderr, "Going to fallback mode using getpass(3)\n");
956
1046
    char *passwordbuffer = getpass("Password: ");
957
 
    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);
958
1054
    if(not bret){
959
1055
      perror("print_out_password");
960
1056
      exitstatus = EXIT_FAILURE;
967
1063
    perror("sigaction");
968
1064
    exitstatus = EXIT_FAILURE;
969
1065
  }
970
 
 
 
1066
  
971
1067
  if(custom_argv != NULL){
972
1068
    for(char **arg = custom_argv+1; *arg != NULL; arg++){
973
1069
      free(*arg);
974
1070
    }
975
1071
    free(custom_argv);
976
1072
  }
977
 
  free_plugin_list(plugin_list);
978
1073
  
979
1074
  if(dir != NULL){
980
1075
    closedir(dir);
981
1076
  }
982
1077
  
983
 
  /* Free the process list and kill the processes */
984
 
  for(process *next; process_list != NULL; process_list = next){
985
 
    next = process_list->next;
986
 
    close(process_list->fd);
987
 
    ret = kill(process_list->pid, SIGTERM);
988
 
    if(ret == -1 and errno != ESRCH){
989
 
      /* set-uid proccesses migth not get closed */
990
 
      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
      }
991
1087
    }
992
 
    free(process_list->buffer);
993
 
    free(process_list);
994
1088
  }
995
1089
  
996
1090
  /* Wait for any remaining child processes to terminate */
1000
1094
  if(errno != ECHILD){
1001
1095
    perror("wait");
1002
1096
  }
1003
 
 
 
1097
  
 
1098
  free_plugin_list();
 
1099
  
1004
1100
  free(plugindir);
1005
1101
  free(argfile);
1006
1102