/mandos/trunk

To get this branch, use:
bzr branch http://bzr.recompile.se/loggerhead/mandos/trunk

« back to all changes in this revision

Viewing changes to plugin-runner.c

  • Committer: Teddy Hogeborn
  • Date: 2008-08-16 03:29:08 UTC
  • Revision ID: teddy@fukt.bsnet.se-20080816032908-ihw7c05r2mnyk389
Add feature to specify custom environment variables for plugins.

* plugin-runner.c (plugin): New members "environ" and "envc" to
                            contain possible custom environment.
  (getplugin): Return NULL on failure instead of doing exit(); all
               callers changed.
  (add_to_char_array): New helper function for "add_argument" and
                       "add_environment".
  (addargument): Renamed to "add_argument".  Return bool.  Call
                 "add_to_char_array" to actually do things.
  (add_environment): New; analogous to "add_argument".
  (addcustomargument): Renamed to "add_to_argv" to avoid confusion
                       with "add_argument".
  (main): New options "--global-envs" and "--envs-for" to specify
          custom environment for plugins.  Print environment for
          plugins in debug mode.  Use asprintf instead of strcpy and
          strcat.  Use execve() for plugins with custom environments.
          Free environment for plugin when freeing plugin list.

Show diffs side-by-side

added added

removed removed

Lines of Context:
56
56
#include <argp.h>               /* struct argp_option, struct
57
57
                                   argp_state, struct argp,
58
58
                                   argp_parse(), ARGP_ERR_UNKNOWN,
59
 
                                   ARGP_KEY_END, ARGP_KEY_ARG,
60
 
                                   error_t */
 
59
                                   ARGP_KEY_END, ARGP_KEY_ARG, error_t */
61
60
#include <signal.h>             /* struct sigaction, sigemptyset(),
62
61
                                   sigaddset(), sigaction(),
63
62
                                   sigprocmask(), SIG_BLOCK, SIGCHLD,
65
64
#include <errno.h>              /* errno, EBADF */
66
65
 
67
66
#define BUFFER_SIZE 256
68
 
 
69
 
#define PDIR "/lib/mandos/plugins.d"
70
 
#define AFILE "/conf/conf.d/mandos/plugin-runner.conf"
 
67
#define ARGFILE "/conf/conf.d/mandos/plugin-runner.conf"
71
68
 
72
69
const char *argp_program_version = "plugin-runner 1.0";
73
70
const char *argp_program_bug_address = "<mandos@fukt.bsnet.se>";
81
78
  size_t buffer_size;
82
79
  size_t buffer_length;
83
80
  bool eof;
84
 
  volatile bool completed;
85
 
  volatile int status;
 
81
  bool completed;
 
82
  int status;
86
83
  struct process *next;
87
84
} process;
88
85
 
108
105
  if (new_plugin == NULL){
109
106
    return NULL;
110
107
  }
111
 
  char *copy_name = NULL;
112
 
  if(name != NULL){
113
 
    copy_name = strdup(name);
114
 
    if(copy_name == NULL){
115
 
      return NULL;
116
 
    }
117
 
  }
118
 
  
119
 
  *new_plugin = (plugin) { .name = copy_name,
 
108
  *new_plugin = (plugin) { .name = name,
120
109
                           .argc = 1,
121
110
                           .envc = 0,
122
111
                           .disabled = false,
124
113
  
125
114
  new_plugin->argv = malloc(sizeof(char *) * 2);
126
115
  if (new_plugin->argv == NULL){
127
 
    free(copy_name);
128
116
    free(new_plugin);
129
117
    return NULL;
130
118
  }
131
 
  new_plugin->argv[0] = copy_name;
 
119
  new_plugin->argv[0] = name;
132
120
  new_plugin->argv[1] = NULL;
133
121
 
134
122
  new_plugin->environ = malloc(sizeof(char *));
135
123
  if(new_plugin->environ == NULL){
136
 
    free(copy_name);
137
124
    free(new_plugin->argv);
138
125
    free(new_plugin);
139
126
    return NULL;
202
189
 
203
190
process *process_list = NULL;
204
191
 
205
 
/* Mark processes as completed when they exit, and save their exit
 
192
/* Mark a process as completed when it exits, and save its exit
206
193
   status. */
207
194
void handle_sigchld(__attribute__((unused)) int sig){
208
 
  while(true){
209
 
    process *proc = process_list;
210
 
    int status;
211
 
    pid_t pid = waitpid(-1, &status, WNOHANG);
212
 
    if(pid == 0){
213
 
      /* Only still running child processes */
214
 
      break;
215
 
    }
216
 
    if(pid == -1){
217
 
      if (errno != ECHILD){
218
 
        perror("waitpid");
219
 
      }
220
 
      /* No child processes */
221
 
      break;
222
 
    }
223
 
 
224
 
    /* A child exited, find it in process_list */
225
 
    while(proc != NULL and proc->pid != pid){
226
 
      proc = proc->next;
227
 
    }
228
 
    if(proc == NULL){
229
 
      /* Process not found in process list */
230
 
      continue;
231
 
    }
232
 
    proc->status = status;
233
 
    proc->completed = true;
234
 
  }
 
195
  process *proc = process_list;
 
196
  int status;
 
197
  pid_t pid = wait(&status);
 
198
  if(pid == -1){
 
199
    perror("wait");
 
200
    return;
 
201
  }
 
202
  while(proc != NULL and proc->pid != pid){
 
203
    proc = proc->next;
 
204
  }
 
205
  if(proc == NULL){
 
206
    /* Process not found in process list */
 
207
    return;
 
208
  }
 
209
  proc->status = status;
 
210
  proc->completed = true;
235
211
}
236
212
 
237
213
bool print_out_password(const char *buffer, size_t length){
249
225
  return true;
250
226
}
251
227
 
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);
264
 
  }
 
228
char **add_to_argv(char **argv, int *argc, char *arg){
 
229
  if (argv == NULL){
 
230
    *argc = 1;
 
231
    argv = malloc(sizeof(char*) * 2);
 
232
    if(argv == NULL){
 
233
      return NULL;
 
234
    }
 
235
    argv[0] = NULL;     /* Will be set to argv[0] in main before parsing */
 
236
    argv[1] = NULL;
 
237
  }
 
238
  *argc += 1;
 
239
  argv = realloc(argv, sizeof(char *)
 
240
                  * ((unsigned int) *argc + 1));
 
241
  if(argv == NULL){
 
242
    return NULL;
 
243
  }
 
244
  argv[*argc-1] = arg;
 
245
  argv[*argc] = NULL;   
 
246
  return argv;
265
247
}
266
248
 
267
249
int main(int argc, char *argv[]){
268
 
  char *plugindir = NULL;
269
 
  char *argfile = NULL;
 
250
  const char *plugindir = "/lib/mandos/plugins.d";
 
251
  const char *argfile = ARGFILE;
270
252
  FILE *conffp;
271
253
  size_t d_name_len;
272
254
  DIR *dir = NULL;
287
269
  /* Establish a signal handler */
288
270
  sigemptyset(&sigchld_action.sa_mask);
289
271
  ret = sigaddset(&sigchld_action.sa_mask, SIGCHLD);
290
 
  if(ret == -1){
 
272
  if(ret < 0){
291
273
    perror("sigaddset");
292
 
    exitstatus = EXIT_FAILURE;
293
 
    goto fallback;
 
274
    exit(EXIT_FAILURE);
294
275
  }
295
276
  ret = sigaction(SIGCHLD, &sigchld_action, &old_sigchld_action);
296
 
  if(ret == -1){
 
277
  if(ret < 0){
297
278
    perror("sigaction");
298
 
    exitstatus = EXIT_FAILURE;
299
 
    goto fallback;
 
279
    exit(EXIT_FAILURE);
300
280
  }
301
281
  
302
282
  /* The options we understand. */
319
299
    { .name = "plugin-dir", .key = 128,
320
300
      .arg = "DIRECTORY",
321
301
      .doc = "Specify a different plugin directory", .group = 2 },
322
 
    { .name = "config-file", .key = 129,
323
 
      .arg = "FILE",
324
 
      .doc = "Specify a different configuration file", .group = 2 },
325
 
    { .name = "userid", .key = 130,
326
 
      .arg = "ID", .flags = 0,
327
 
      .doc = "User ID the plugins will run as", .group = 3 },
328
 
    { .name = "groupid", .key = 131,
329
 
      .arg = "ID", .flags = 0,
330
 
      .doc = "Group ID the plugins will run as", .group = 3 },
331
 
    { .name = "debug", .key = 132,
332
 
      .doc = "Debug mode", .group = 4 },
 
302
    { .name = "userid", .key = 129,
 
303
      .arg = "ID", .flags = 0,
 
304
      .doc = "User ID the plugins will run as", .group = 2 },
 
305
    { .name = "groupid", .key = 130,
 
306
      .arg = "ID", .flags = 0,
 
307
      .doc = "Group ID the plugins will run as", .group = 2 },
 
308
    { .name = "debug", .key = 131,
 
309
      .doc = "Debug mode", .group = 3 },
333
310
    { .name = NULL }
334
311
  };
335
312
  
419
396
      }
420
397
      break;
421
398
    case 128:
422
 
      plugindir = strdup(arg);
423
 
      if(plugindir == NULL){
424
 
        perror("strdup");
425
 
      }      
 
399
      plugindir = arg;
426
400
      break;
427
401
    case 129:
428
 
      argfile = strdup(arg);
429
 
      if(argfile == NULL){
430
 
        perror("strdup");
431
 
      }
432
 
      break;      
 
402
      uid = (uid_t)strtol(arg, NULL, 10);
 
403
      break;
433
404
    case 130:
434
 
      uid = (uid_t)strtol(arg, NULL, 10);
 
405
      gid = (gid_t)strtol(arg, NULL, 10);
435
406
      break;
436
407
    case 131:
437
 
      gid = (gid_t)strtol(arg, NULL, 10);
438
 
      break;
439
 
    case 132:
440
408
      debug = true;
441
409
      break;
442
410
    case ARGP_KEY_ARG:
460
428
  if (ret == ARGP_ERR_UNKNOWN){
461
429
    fprintf(stderr, "Unknown error while parsing arguments\n");
462
430
    exitstatus = EXIT_FAILURE;
463
 
    goto fallback;
 
431
    goto end;
464
432
  }
465
433
 
466
 
  if (argfile == NULL){
467
 
    conffp = fopen(AFILE, "r");
468
 
  } else {
469
 
    conffp = fopen(argfile, "r");
470
 
  }
471
 
  
 
434
  conffp = fopen(argfile, "r");
472
435
  if(conffp != NULL){
473
436
    char *org_line = NULL;
474
 
    char *p, *arg, *new_arg, *line;
475
437
    size_t size = 0;
476
438
    ssize_t sret;
 
439
    char *p, *arg, *new_arg, *line;
477
440
    const char whitespace_delims[] = " \r\t\f\v\n";
478
441
    const char comment_delim[] = "#";
479
442
 
480
 
    custom_argc = 1;
481
 
    custom_argv = malloc(sizeof(char*) * 2);
482
 
    if(custom_argv == NULL){
483
 
      perror("malloc");
484
 
      exitstatus = EXIT_FAILURE;
485
 
      goto fallback;
486
 
    }
487
 
    custom_argv[0] = argv[0];
488
 
    custom_argv[1] = NULL;
489
 
    
490
443
    while(true){
491
444
      sret = getline(&org_line, &size, conffp);
492
445
      if(sret == -1){
500
453
          continue;
501
454
        }
502
455
        new_arg = strdup(p);
503
 
        if(new_arg == NULL){
504
 
          perror("strdup");
505
 
          exitstatus = EXIT_FAILURE;
506
 
          free(org_line);
507
 
          goto fallback;
508
 
        }
509
 
        
510
 
        custom_argc += 1;
511
 
        custom_argv = realloc(custom_argv, sizeof(char *)
512
 
                              * ((unsigned int) custom_argc + 1));
513
 
        if(custom_argv == NULL){
514
 
          perror("realloc");
515
 
          exitstatus = EXIT_FAILURE;
516
 
          free(org_line);
517
 
          goto fallback;
518
 
        }
519
 
        custom_argv[custom_argc-1] = new_arg;
520
 
        custom_argv[custom_argc] = NULL;        
 
456
        custom_argv = add_to_argv(custom_argv, &custom_argc, new_arg);
 
457
        if (custom_argv == NULL){
 
458
          perror("add_to_argv");
 
459
          exitstatus = EXIT_FAILURE;
 
460
          goto end;
 
461
        }
521
462
      }
522
463
    }
523
464
    free(org_line);
527
468
    if (errno == EMFILE or errno == ENFILE or errno == ENOMEM){
528
469
      perror("fopen");
529
470
      exitstatus = EXIT_FAILURE;
530
 
      goto fallback;
 
471
      goto end;
531
472
    }
532
473
  }
533
474
 
534
475
  if(custom_argv != NULL){
 
476
    custom_argv[0] = argv[0];
535
477
    ret = argp_parse (&argp, custom_argc, custom_argv, 0, 0, &plugin_list);
536
478
    if (ret == ARGP_ERR_UNKNOWN){
537
479
      fprintf(stderr, "Unknown error while parsing arguments\n");
538
480
      exitstatus = EXIT_FAILURE;
539
 
      goto fallback;
 
481
      goto end;
540
482
    }
541
483
  }
542
484
  
563
505
  if (ret == -1){
564
506
    perror("setgid");
565
507
  }
566
 
 
567
 
  if (plugindir == NULL){
568
 
    dir = opendir(PDIR);
569
 
  } else {
570
 
    dir = opendir(plugindir);
571
 
  }
572
508
  
 
509
  dir = opendir(plugindir);
573
510
  if(dir == NULL){
574
511
    perror("Could not open plugin dir");
575
512
    exitstatus = EXIT_FAILURE;
576
 
    goto fallback;
 
513
    goto end;
577
514
  }
578
515
  
579
516
  /* Set the FD_CLOEXEC flag on the directory, if possible */
584
521
      if(ret < 0){
585
522
        perror("set_cloexec_flag");
586
523
        exitstatus = EXIT_FAILURE;
587
 
        goto fallback;
 
524
        goto end;
588
525
      }
589
526
    }
590
527
  }
599
536
      if (errno == EBADF){
600
537
        perror("readdir");
601
538
        exitstatus = EXIT_FAILURE;
602
 
        goto fallback;
 
539
        goto end;
603
540
      }
604
541
      break;
605
542
    }
720
657
      }
721
658
    }
722
659
    
723
 
    int pipefd[2];
 
660
    int pipefd[2]; 
724
661
    ret = pipe(pipefd);
725
662
    if (ret == -1){
726
663
      perror("pipe");
727
664
      exitstatus = EXIT_FAILURE;
728
 
      goto fallback;
 
665
      goto end;
729
666
    }
730
667
    ret = set_cloexec_flag(pipefd[0]);
731
668
    if(ret < 0){
732
669
      perror("set_cloexec_flag");
733
670
      exitstatus = EXIT_FAILURE;
734
 
      goto fallback;
 
671
      goto end;
735
672
    }
736
673
    ret = set_cloexec_flag(pipefd[1]);
737
674
    if(ret < 0){
738
675
      perror("set_cloexec_flag");
739
676
      exitstatus = EXIT_FAILURE;
740
 
      goto fallback;
 
677
      goto end;
741
678
    }
742
679
    /* Block SIGCHLD until process is safely in process list */
743
680
    ret = sigprocmask (SIG_BLOCK, &sigchld_action.sa_mask, NULL);
744
681
    if(ret < 0){
745
682
      perror("sigprocmask");
746
683
      exitstatus = EXIT_FAILURE;
747
 
      goto fallback;
 
684
      goto end;
748
685
    }
749
686
    // Starting a new process to be watched
750
687
    pid_t pid = fork();
751
688
    if(pid == -1){
752
689
      perror("fork");
753
690
      exitstatus = EXIT_FAILURE;
754
 
      goto fallback;
 
691
      goto end;
755
692
    }
756
693
    if(pid == 0){
757
694
      /* this is the child process */
801
738
        perror("sigprocmask");
802
739
      }
803
740
      exitstatus = EXIT_FAILURE;
804
 
      goto fallback;
 
741
      goto end;
805
742
    }
806
743
    
807
744
    *new_process = (struct process){ .pid = pid,
815
752
    if(ret < 0){
816
753
      perror("sigprocmask");
817
754
      exitstatus = EXIT_FAILURE;
818
 
      goto fallback;
 
755
      goto end;
819
756
    }
820
757
    
821
758
    FD_SET(new_process->fd, &rfds_all);
825
762
    }
826
763
    
827
764
  }
828
 
 
829
 
  free_plugin_list(plugin_list);
830
 
  plugin_list = NULL;
 
765
  
 
766
  /* Free the plugin list */
 
767
  for(plugin *next; plugin_list != NULL; plugin_list = next){
 
768
    next = plugin_list->next;
 
769
    free(plugin_list->argv);
 
770
    if(plugin_list->environ[0] != NULL){
 
771
      for(char **e = plugin_list->environ; *e != NULL; e++){
 
772
        free(*e);
 
773
      }
 
774
    }
 
775
    free(plugin_list);
 
776
  }
831
777
  
832
778
  closedir(dir);
833
779
  dir = NULL;
843
789
    if (select_ret == -1){
844
790
      perror("select");
845
791
      exitstatus = EXIT_FAILURE;
846
 
      goto fallback;
 
792
      goto end;
847
793
    }
848
794
    /* OK, now either a process completed, or something can be read
849
795
       from one of them */
871
817
          /* Remove the plugin */
872
818
          FD_CLR(proc->fd, &rfds_all);
873
819
          /* Block signal while modifying process_list */
874
 
          ret = sigprocmask(SIG_BLOCK, &sigchld_action.sa_mask, NULL);
 
820
          ret = sigprocmask (SIG_BLOCK, &sigchld_action.sa_mask, NULL);
875
821
          if(ret < 0){
876
822
            perror("sigprocmask");
877
823
            exitstatus = EXIT_FAILURE;
878
 
            goto fallback;
 
824
            goto end;
879
825
          }
880
826
          /* Delete this process entry from the list */
881
827
          if(process_list == proc){
905
851
        }
906
852
        /* This process exited nicely, so print its buffer */
907
853
 
908
 
        bool bret = print_out_password(proc->buffer,
909
 
                                       proc->buffer_length);
 
854
        bool bret = print_out_password(proc->buffer, proc->buffer_length);
910
855
        if(not bret){
911
856
          perror("print_out_password");
912
857
          exitstatus = EXIT_FAILURE;
913
858
        }
914
 
        goto fallback;
 
859
        goto end;
915
860
      }
916
861
      /* This process has not completed.  Does it have any output? */
917
862
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){
925
870
        if (proc->buffer == NULL){
926
871
          perror("malloc");
927
872
          exitstatus = EXIT_FAILURE;
928
 
          goto fallback;
 
873
          goto end;
929
874
        }
930
875
        proc->buffer_size += BUFFER_SIZE;
931
876
      }
946
891
  }
947
892
 
948
893
 
949
 
 fallback:
 
894
 end:
950
895
  
951
896
  if(process_list == NULL or exitstatus != EXIT_SUCCESS){
952
 
    /* Fallback if all plugins failed, none are found or an error
953
 
       occured */
 
897
    /* Fallback if all plugins failed, none are found or an error occured */
954
898
    bool bret;
955
899
    fprintf(stderr, "Going to fallback mode using getpass(3)\n");
956
900
    char *passwordbuffer = getpass("Password: ");
958
902
    if(not bret){
959
903
      perror("print_out_password");
960
904
      exitstatus = EXIT_FAILURE;
 
905
      goto end;
961
906
    }
962
907
  }
963
908
  
964
909
  /* Restore old signal handler */
965
 
  ret = sigaction(SIGCHLD, &old_sigchld_action, NULL);
966
 
  if(ret == -1){
967
 
    perror("sigaction");
968
 
    exitstatus = EXIT_FAILURE;
969
 
  }
970
 
 
971
 
  if(custom_argv != NULL){
972
 
    for(char **arg = custom_argv+1; *arg != NULL; arg++){
973
 
      free(*arg);
 
910
  sigaction(SIGCHLD, &old_sigchld_action, NULL);
 
911
  
 
912
  free(custom_argv);
 
913
  
 
914
  /* Free the plugin list */
 
915
  for(plugin *next; plugin_list != NULL; plugin_list = next){
 
916
    next = plugin_list->next;
 
917
    free(plugin_list->argv);
 
918
    if(plugin_list->environ[0] != NULL){
 
919
      for(char **e = plugin_list->environ; *e != NULL; e++){
 
920
        free(*e);
 
921
      }
974
922
    }
975
 
    free(custom_argv);
 
923
    free(plugin_list->environ);
 
924
    free(plugin_list);
976
925
  }
977
 
  free_plugin_list(plugin_list);
978
926
  
979
927
  if(dir != NULL){
980
928
    closedir(dir);
1000
948
  if(errno != ECHILD){
1001
949
    perror("wait");
1002
950
  }
1003
 
 
1004
 
  free(plugindir);
1005
 
  free(argfile);
1006
951
  
1007
952
  return exitstatus;
1008
953
}