/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 plugin;
76
 
 
77
76
typedef struct plugin{
78
77
  char *name;                   /* can be NULL or any plugin name */
79
78
  char **argv;
96
95
 
97
96
static plugin *plugin_list = NULL;
98
97
 
99
 
/* Gets a existing plugin based on name,
 
98
/* Gets an existing plugin based on name,
100
99
   or if none is found, creates a new one */
101
100
static plugin *getplugin(char *name){
102
101
  /* Check for exiting plugin with that name */
118
117
      return NULL;
119
118
    }
120
119
  }
121
 
 
 
120
  
122
121
  *new_plugin = (plugin) { .name = copy_name,
123
122
                           .argc = 1,
124
123
                           .disabled = false,
187
186
  size_t namelen = (size_t)(strchrnul(def, '=') - def);
188
187
  /* Search for this environment variable */
189
188
  for(char **e = p->environ; *e != NULL; e++){
190
 
    if(strncmp(*e, def, namelen+1) == 0){
 
189
    if(strncmp(*e, def, namelen + 1) == 0){
191
190
      /* It already exists */
192
191
      if(replace){
193
 
        char *new = realloc(*e, strlen(def));
 
192
        char *new = realloc(*e, strlen(def) + 1);
194
193
        if(new == NULL){
195
194
          return false;
196
195
        }
208
207
 * Descriptor Flags".
209
208
 * *Note File Descriptor Flags:(libc)Descriptor Flags.
210
209
 */
211
 
static int set_cloexec_flag(int fd)
212
 
{
 
210
static int set_cloexec_flag(int fd){
213
211
  int ret = fcntl(fd, F_GETFD, 0);
214
212
  /* If reading the flags failed, return error indication now. */
215
213
  if(ret < 0){
222
220
 
223
221
/* Mark processes as completed when they exit, and save their exit
224
222
   status. */
225
 
void handle_sigchld(__attribute__((unused)) int sig){
 
223
static void handle_sigchld(__attribute__((unused)) int sig){
226
224
  while(true){
227
225
    plugin *proc = plugin_list;
228
226
    int status;
238
236
      /* No child processes */
239
237
      break;
240
238
    }
241
 
 
 
239
    
242
240
    /* A child exited, find it in process_list */
243
241
    while(proc != NULL and proc->pid != pid){
244
242
      proc = proc->next;
253
251
}
254
252
 
255
253
/* Prints out a password to stdout */
256
 
bool print_out_password(const char *buffer, size_t length){
 
254
static bool print_out_password(const char *buffer, size_t length){
257
255
  ssize_t ret;
258
 
  if(length>0 and buffer[length-1] == '\n'){
259
 
    length--;
260
 
  }
261
256
  for(size_t written = 0; written < length; written += (size_t)ret){
262
257
    ret = TEMP_FAILURE_RETRY(write(STDOUT_FILENO, buffer + written,
263
258
                                   length - written));
344
339
    { .name = "global-options", .key = 'g',
345
340
      .arg = "OPTION[,OPTION[,...]]",
346
341
      .doc = "Options passed to all plugins" },
347
 
    { .name = "global-env", .key = 'e',
 
342
    { .name = "global-env", .key = 'G',
348
343
      .arg = "VAR=value",
349
344
      .doc = "Environment variable passed to all plugins" },
350
345
    { .name = "options-for", .key = 'o',
351
346
      .arg = "PLUGIN:OPTION[,OPTION[,...]]",
352
347
      .doc = "Options passed only to specified plugin" },
353
 
    { .name = "env-for", .key = 'f',
 
348
    { .name = "env-for", .key = 'E',
354
349
      .arg = "PLUGIN:ENV=value",
355
350
      .doc = "Environment variable passed to specified plugin" },
356
351
    { .name = "disable", .key = 'd',
357
352
      .arg = "PLUGIN",
358
353
      .doc = "Disable a specific plugin", .group = 1 },
 
354
    { .name = "enable", .key = 'e',
 
355
      .arg = "PLUGIN",
 
356
      .doc = "Enable a specific plugin", .group = 1 },
359
357
    { .name = "plugin-dir", .key = 128,
360
358
      .arg = "DIRECTORY",
361
359
      .doc = "Specify a different plugin directory", .group = 2 },
375
373
  
376
374
  error_t parse_opt (int key, char *arg, __attribute__((unused))
377
375
                     struct argp_state *state) {
378
 
    /* Get the INPUT argument from `argp_parse', which we know is a
379
 
       pointer to our plugin list pointer. */
380
376
    switch (key) {
381
377
    case 'g':                   /* --global-options */
382
378
      if (arg != NULL){
392
388
        }
393
389
      }
394
390
      break;
395
 
    case 'e':                   /* --global-env */
 
391
    case 'G':                   /* --global-env */
396
392
      if(arg == NULL){
397
393
        break;
398
394
      }
399
 
      {
400
 
        char *envdef = strdup(arg);
401
 
        if(envdef == NULL){
402
 
          break;
403
 
        }
404
 
        if(not add_environment(getplugin(NULL), envdef, true)){
405
 
          perror("add_environment");
406
 
        }
 
395
      if(not add_environment(getplugin(NULL), arg, true)){
 
396
        perror("add_environment");
407
397
      }
408
398
      break;
409
399
    case 'o':                   /* --options-for */
410
400
      if (arg != NULL){
411
401
        char *p_name = strsep(&arg, ":");
412
 
        if(p_name[0] == '\0'){
 
402
        if(p_name[0] == '\0' or arg == NULL){
413
403
          break;
414
404
        }
415
405
        char *opt = strsep(&arg, ":");
416
 
        if(opt[0] == '\0'){
 
406
        if(opt[0] == '\0' or opt == NULL){
417
407
          break;
418
408
        }
419
 
        if(opt != NULL){
420
 
          char *p;
421
 
          while((p = strsep(&opt, ",")) != NULL){
422
 
            if(p[0] == '\0'){
423
 
              continue;
424
 
            }
425
 
            if(not add_argument(getplugin(p_name), p)){
426
 
              perror("add_argument");
427
 
              return ARGP_ERR_UNKNOWN;
428
 
            }
 
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;
429
417
          }
430
418
        }
431
419
      }
432
420
      break;
433
 
    case 'f':                   /* --env-for */
 
421
    case 'E':                   /* --env-for */
434
422
      if(arg == NULL){
435
423
        break;
436
424
      }
439
427
        if(envdef == NULL){
440
428
          break;
441
429
        }
442
 
        char *p_name = strndup(arg, (size_t) (envdef-arg));
443
 
        if(p_name == NULL){
444
 
          break;
445
 
        }
446
 
        envdef++;
447
 
        if(not add_environment(getplugin(p_name), envdef, true)){
 
430
        *envdef = '\0';
 
431
        if(not add_environment(getplugin(arg), envdef+1, true)){
448
432
          perror("add_environment");
449
433
        }
450
434
      }
458
442
        p->disabled = true;
459
443
      }
460
444
      break;
 
445
    case 'e':                   /* --enable */
 
446
      if (arg != NULL){
 
447
        plugin *p = getplugin(arg);
 
448
        if(p == NULL){
 
449
          return ARGP_ERR_UNKNOWN;
 
450
        }
 
451
        p->disabled = false;
 
452
      }
 
453
      break;
461
454
    case 128:                   /* --plugin-dir */
 
455
      free(plugindir);
462
456
      plugindir = strdup(arg);
463
457
      if(plugindir == NULL){
464
458
        perror("strdup");
465
459
      }      
466
460
      break;
467
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);
468
505
      argfile = strdup(arg);
469
506
      if(argfile == NULL){
470
507
        perror("strdup");
471
508
      }
472
509
      break;      
473
510
    case 130:                   /* --userid */
474
 
      uid = (uid_t)strtol(arg, NULL, 10);
475
 
      break;
476
511
    case 131:                   /* --groupid */
477
 
      gid = (gid_t)strtol(arg, NULL, 10);
478
 
      break;
479
512
    case 132:                   /* --debug */
480
 
      debug = true;
481
 
      break;
482
513
    case ARGP_KEY_ARG:
483
 
      fprintf(stderr, "Ignoring unknown argument \"%s\"\n", arg);
484
 
      break;
485
514
    case ARGP_KEY_END:
486
515
      break;
487
516
    default:
490
519
    return 0;
491
520
  }
492
521
  
493
 
  struct argp argp = { .options = options, .parser = parse_opt,
 
522
  struct argp argp = { .options = options,
 
523
                       .parser = parse_opt_config_file,
494
524
                       .args_doc = "",
495
525
                       .doc = "Mandos plugin runner -- Run plugins" };
496
526
  
 
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);
 
530
  if (ret == ARGP_ERR_UNKNOWN){
 
531
    fprintf(stderr, "Unknown error while parsing arguments\n");
 
532
    exitstatus = EXIT_FAILURE;
 
533
    goto fallback;
 
534
  }
 
535
  
 
536
  /* Reset to the normal argument parser */
 
537
  argp.parser = parse_opt;
 
538
  
497
539
  /* Open the configfile if available */
498
540
  if (argfile == NULL){
499
541
    conffp = fopen(AFILE, "r");
659
701
      
660
702
      const char const *bad_suffixes[] = { "~", "#", ".dpkg-new",
661
703
                                           ".dpkg-old",
 
704
                                           ".dpkg-bak",
662
705
                                           ".dpkg-divert", NULL };
663
706
      for(const char **pre = bad_prefixes; *pre != NULL; pre++){
664
707
        size_t pre_len = strlen(*pre);
695
738
    }
696
739
 
697
740
    char *filename;
698
 
    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
    }
699
746
    if(ret < 0){
700
747
      perror("asprintf");
701
748
      continue;
801
848
        perror("sigaction");
802
849
        _exit(EXIT_FAILURE);
803
850
      }
804
 
      ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
 
851
      ret = sigprocmask(SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
805
852
      if(ret < 0){
806
853
        perror("sigprocmask");
807
854
        _exit(EXIT_FAILURE);
808
855
      }
809
 
 
 
856
      
810
857
      ret = dup2(pipefd[1], STDOUT_FILENO); /* replace our stdout */
811
858
      if(ret == -1){
812
859
        perror("dup2");
862
909
    if (maxfd < new_plugin->fd){
863
910
      maxfd = new_plugin->fd;
864
911
    }
865
 
    
866
912
  }
867
913
  
868
914
  closedir(dir);
869
915
  dir = NULL;
870
 
 
 
916
  
871
917
  for(plugin *p = plugin_list; p != NULL; p = p->next){
872
918
    if(p->pid != 0){
873
919
      break;
878
924
      free_plugin_list();
879
925
    }
880
926
  }
881
 
 
 
927
  
882
928
  /* Main loop while running plugins exist */
883
929
  while(plugin_list){
884
930
    fd_set rfds = rfds_all;
890
936
    }
891
937
    /* OK, now either a process completed, or something can be read
892
938
       from one of them */
893
 
    for(plugin *proc = plugin_list; proc != NULL; proc = proc->next){
 
939
    for(plugin *proc = plugin_list; proc != NULL;){
894
940
      /* Is this process completely done? */
895
941
      if(proc->eof and proc->completed){
896
942
        /* Only accept the plugin output if it exited cleanly */
923
969
            exitstatus = EXIT_FAILURE;
924
970
            goto fallback;
925
971
          }
 
972
          
 
973
          plugin *next_plugin = proc->next;
926
974
          free_plugin(proc);
 
975
          proc = next_plugin;
 
976
          
927
977
          /* We are done modifying process list, so unblock signal */
928
978
          ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask,
929
979
                             NULL);
936
986
          if(plugin_list == NULL){
937
987
            break;
938
988
          }
 
989
          
939
990
          continue;
940
991
        }
941
992
        
942
993
        /* This process exited nicely, so print its buffer */
943
 
 
 
994
        
944
995
        bool bret = print_out_password(proc->buffer,
945
996
                                       proc->buffer_length);
946
997
        if(not bret){
953
1004
      /* This process has not completed.  Does it have any output? */
954
1005
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){
955
1006
        /* This process had nothing to say at this time */
 
1007
        proc = proc->next;
956
1008
        continue;
957
1009
      }
958
1010
      /* Before reading, make the process' data buffer large enough */
971
1023
                 BUFFER_SIZE);
972
1024
      if(ret < 0){
973
1025
        /* Read error from this process; ignore the error */
 
1026
        proc = proc->next;
974
1027
        continue;
975
1028
      }
976
1029
      if(ret == 0){
991
1044
    bool bret;
992
1045
    fprintf(stderr, "Going to fallback mode using getpass(3)\n");
993
1046
    char *passwordbuffer = getpass("Password: ");
994
 
    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);
995
1054
    if(not bret){
996
1055
      perror("print_out_password");
997
1056
      exitstatus = EXIT_FAILURE;
1004
1063
    perror("sigaction");
1005
1064
    exitstatus = EXIT_FAILURE;
1006
1065
  }
1007
 
 
 
1066
  
1008
1067
  if(custom_argv != NULL){
1009
1068
    for(char **arg = custom_argv+1; *arg != NULL; arg++){
1010
1069
      free(*arg);
1016
1075
    closedir(dir);
1017
1076
  }
1018
1077
  
1019
 
  /* Free the process list and kill the processes */
 
1078
  /* Kill the processes */
1020
1079
  for(plugin *p = plugin_list; p != NULL; p = p->next){
1021
1080
    if(p->pid != 0){
1022
1081
      close(p->fd);
1035
1094
  if(errno != ECHILD){
1036
1095
    perror("wait");
1037
1096
  }
1038
 
 
 
1097
  
1039
1098
  free_plugin_list();
1040
1099
  
1041
1100
  free(plugindir);