/mandos/release

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

« back to all changes in this revision

Viewing changes to plugin-runner.c

  • Committer: Teddy Hogeborn
  • Date: 2008-08-29 05:53:59 UTC
  • Revision ID: teddy@fukt.bsnet.se-20080829055359-wkdasnyxtylmnxus
* mandos.xml (EXAMPLE): Replaced all occurences of command name with
                        "&COMMANDNAME;".

* plugins.d/password-prompt.c (main): Improved some documentation
                                      strings.  Do perror() of
                                      tcgetattr() fails.  Add debug
                                      output if interrupted by signal.
                                      Loop over write() instead of
                                      using fwrite() when outputting
                                      password.  Add debug output if
                                      getline() returns 0, unless it
                                      was caused by a signal.  Add
                                      exit status code to debug
                                      output.

* plugins.d/password-prompt.xml: Changed all single quotes to double
                                 quotes for consistency.  Removed
                                 <?xml-stylesheet>.
  (ENTITY TIMESTAMP): New.  Automatically updated by Emacs time-stamp
                      by using Emacs local variables.
  (/refentry/refentryinfo/title): Changed to "Mandos Manual".
  (/refentry/refentryinfo/productname): Changed to "Mandos".
  (/refentry/refentryinfo/date): New; set to "&TIMESTAMP;".
  (/refentry/refentryinfo/copyright): Split copyright holders.
  (/refentry/refnamediv/refpurpose): Improved wording.
  (SYNOPSIS): Fix to use correct markup.  Add short options.
  (DESCRIPTION, OPTIONS): Improved wording.
  (OPTIONS): Improved wording.  Use more correct markup.  Document
             short options.
  (EXIT STATUS): Add text.
  (ENVIRONMENT): Document use of "cryptsource" and "crypttarget".
  (FILES): REMOVED.
  (BUGS): Add text.
  (EXAMPLE): Added some examples.
  (SECURITY): Added text.
  (SEE ALSO): Remove reference to mandos(8).  Add reference to
              crypttab(5).

Show diffs side-by-side

added added

removed removed

Lines of Context:
72
72
const char *argp_program_version = "plugin-runner 1.0";
73
73
const char *argp_program_bug_address = "<mandos@fukt.bsnet.se>";
74
74
 
75
 
struct plugin;
76
 
 
77
 
typedef struct plugin{
78
 
  char *name;                   /* can be NULL or any plugin name */
79
 
  char **argv;
80
 
  int argc;
81
 
  char **environ;
82
 
  int envc;
83
 
  bool disabled;
84
 
 
85
 
  /* Variables used for running processes*/
 
75
struct process;
 
76
 
 
77
typedef struct process{
86
78
  pid_t pid;
87
79
  int fd;
88
80
  char *buffer;
91
83
  bool eof;
92
84
  volatile bool completed;
93
85
  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;
94
96
  struct plugin *next;
95
97
} plugin;
96
98
 
97
 
static plugin *plugin_list = NULL;
98
 
 
99
 
/* Gets a existing plugin based on name,
100
 
   or if none is found, creates a new one */
101
 
static plugin *getplugin(char *name){
102
 
  /* Check for exiting plugin with that name */
103
 
  for (plugin *p = plugin_list; p != NULL; p = p->next){
 
99
static plugin *getplugin(char *name, plugin **plugin_list){
 
100
  for (plugin *p = *plugin_list; p != NULL; p = p->next){
104
101
    if ((p->name == name)
105
102
        or (p->name and name and (strcmp(p->name, name) == 0))){
106
103
      return p;
118
115
      return NULL;
119
116
    }
120
117
  }
121
 
 
 
118
  
122
119
  *new_plugin = (plugin) { .name = copy_name,
123
120
                           .argc = 1,
 
121
                           .envc = 0,
124
122
                           .disabled = false,
125
 
                           .next = plugin_list };
 
123
                           .next = *plugin_list };
126
124
  
127
125
  new_plugin->argv = malloc(sizeof(char *) * 2);
128
126
  if (new_plugin->argv == NULL){
141
139
    return NULL;
142
140
  }
143
141
  new_plugin->environ[0] = NULL;
144
 
 
145
142
  /* Append the new plugin to the list */
146
 
  plugin_list = new_plugin;
 
143
  *plugin_list = new_plugin;
147
144
  return new_plugin;
148
145
}
149
146
 
186
183
  return add_to_char_array(def, &(p->environ), &(p->envc));
187
184
}
188
185
 
 
186
 
189
187
/*
190
188
 * Based on the example in the GNU LibC manual chapter 13.13 "File
191
189
 * Descriptor Flags".
202
200
  return fcntl(fd, F_SETFD, ret | FD_CLOEXEC);
203
201
}
204
202
 
 
203
process *process_list = NULL;
205
204
 
206
205
/* Mark processes as completed when they exit, and save their exit
207
206
   status. */
208
207
void handle_sigchld(__attribute__((unused)) int sig){
209
208
  while(true){
210
 
    plugin *proc = plugin_list;
 
209
    process *proc = process_list;
211
210
    int status;
212
211
    pid_t pid = waitpid(-1, &status, WNOHANG);
213
212
    if(pid == 0){
235
234
  }
236
235
}
237
236
 
238
 
/* Prints out a password to stdout */
239
237
bool print_out_password(const char *buffer, size_t length){
240
238
  ssize_t ret;
241
239
  if(length>0 and buffer[length-1] == '\n'){
251
249
  return true;
252
250
}
253
251
 
254
 
/* Removes and free a plugin from the plugin list */
255
 
static void free_plugin(plugin *plugin_node){
256
 
  
257
 
  for(char **arg = plugin_node->argv; *arg != NULL; arg++){
258
 
    free(*arg);
259
 
  }
260
 
  free(plugin_node->argv);
261
 
  for(char **env = plugin_node->environ; *env != NULL; env++){
262
 
    free(*env);
263
 
  }
264
 
  free(plugin_node->environ);
265
 
  free(plugin_node->buffer);
266
 
 
267
 
  /* Removes the plugin from the singly-linked list */
268
 
  if(plugin_node == plugin_list){
269
 
    /* First one - simple */
270
 
    plugin_list = plugin_list->next;
271
 
  } else {
272
 
    /* Second one or later */
273
 
    for(plugin *p = plugin_list; p != NULL; p = p->next){
274
 
      if(p->next == plugin_node){
275
 
        p->next = plugin_node->next;
276
 
        break;
277
 
      }
278
 
    }
279
 
  }
280
 
  
281
 
  free(plugin_node);
282
 
}
283
 
 
284
 
static void free_plugin_list(void){
285
 
  while(plugin_list != NULL){
286
 
    free_plugin(plugin_list);
 
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);
287
264
  }
288
265
}
289
266
 
356
333
    { .name = NULL }
357
334
  };
358
335
  
359
 
  error_t parse_opt (int key, char *arg, __attribute__((unused))
360
 
                     struct argp_state *state) {
 
336
  error_t parse_opt (int key, char *arg, struct argp_state *state) {
361
337
    /* Get the INPUT argument from `argp_parse', which we know is a
362
338
       pointer to our plugin list pointer. */
 
339
    plugin **plugins = state->input;
363
340
    switch (key) {
364
 
    case 'g':                   /* --global-options */
 
341
    case 'g':
365
342
      if (arg != NULL){
366
343
        char *p;
367
344
        while((p = strsep(&arg, ",")) != NULL){
368
345
          if(p[0] == '\0'){
369
346
            continue;
370
347
          }
371
 
          if(not add_argument(getplugin(NULL), p)){
 
348
          if(not add_argument(getplugin(NULL, plugins), p)){
372
349
            perror("add_argument");
373
350
            return ARGP_ERR_UNKNOWN;
374
351
          }
375
352
        }
376
353
      }
377
354
      break;
378
 
    case 'e':                   /* --global-envs */
 
355
    case 'e':
379
356
      if(arg == NULL){
380
357
        break;
381
358
      }
384
361
        if(envdef == NULL){
385
362
          break;
386
363
        }
387
 
        if(not add_environment(getplugin(NULL), envdef)){
 
364
        if(not add_environment(getplugin(NULL, plugins), envdef)){
388
365
          perror("add_environment");
389
366
        }
390
367
      }
391
368
      break;
392
 
    case 'o':                   /* --options-for */
 
369
    case 'o':
393
370
      if (arg != NULL){
394
371
        char *p_name = strsep(&arg, ":");
395
372
        if(p_name[0] == '\0'){
405
382
            if(p[0] == '\0'){
406
383
              continue;
407
384
            }
408
 
            if(not add_argument(getplugin(p_name), p)){
 
385
            if(not add_argument(getplugin(p_name, plugins), p)){
409
386
              perror("add_argument");
410
387
              return ARGP_ERR_UNKNOWN;
411
388
            }
413
390
        }
414
391
      }
415
392
      break;
416
 
    case 'f':                   /* --envs-for */
 
393
    case 'f':
417
394
      if(arg == NULL){
418
395
        break;
419
396
      }
427
404
          break;
428
405
        }
429
406
        envdef++;
430
 
        if(not add_environment(getplugin(p_name), envdef)){
 
407
        if(not add_environment(getplugin(p_name, plugins), envdef)){
431
408
          perror("add_environment");
432
409
        }
433
410
      }
434
411
      break;
435
 
    case 'd':                   /* --disable */
 
412
    case 'd':
436
413
      if (arg != NULL){
437
 
        plugin *p = getplugin(arg);
 
414
        plugin *p = getplugin(arg, plugins);
438
415
        if(p == NULL){
439
416
          return ARGP_ERR_UNKNOWN;
440
417
        }
441
418
        p->disabled = true;
442
419
      }
443
420
      break;
444
 
    case 128:                   /* --plugin-dir */
 
421
    case 128:
445
422
      plugindir = strdup(arg);
446
423
      if(plugindir == NULL){
447
424
        perror("strdup");
448
425
      }      
449
426
      break;
450
 
    case 129:                   /* --config-file */
 
427
    case 129:
451
428
      argfile = strdup(arg);
452
429
      if(argfile == NULL){
453
430
        perror("strdup");
454
431
      }
455
432
      break;      
456
 
    case 130:                   /* --userid */
 
433
    case 130:
457
434
      uid = (uid_t)strtol(arg, NULL, 10);
458
435
      break;
459
 
    case 131:                   /* --groupid */
 
436
    case 131:
460
437
      gid = (gid_t)strtol(arg, NULL, 10);
461
438
      break;
462
 
    case 132:                   /* --debug */
 
439
    case 132:
463
440
      debug = true;
464
441
      break;
465
442
    case ARGP_KEY_ARG:
473
450
    return 0;
474
451
  }
475
452
  
 
453
  plugin *plugin_list = NULL;
 
454
  
476
455
  struct argp argp = { .options = options, .parser = parse_opt,
477
456
                       .args_doc = "[+PLUS_SEPARATED_OPTIONS]",
478
457
                       .doc = "Mandos plugin runner -- Run plugins" };
479
458
  
480
 
  ret = argp_parse (&argp, argc, argv, 0, 0, NULL);
 
459
  ret = argp_parse (&argp, argc, argv, 0, 0, &plugin_list);
481
460
  if (ret == ARGP_ERR_UNKNOWN){
482
461
    fprintf(stderr, "Unknown error while parsing arguments\n");
483
462
    exitstatus = EXIT_FAILURE;
484
463
    goto fallback;
485
464
  }
486
465
 
487
 
  /* Opens the configfile if aviable */
488
466
  if (argfile == NULL){
489
467
    conffp = fopen(AFILE, "r");
490
468
  } else {
491
469
    conffp = fopen(argfile, "r");
492
 
  }  
 
470
  }
 
471
  
493
472
  if(conffp != NULL){
494
473
    char *org_line = NULL;
495
474
    char *p, *arg, *new_arg, *line;
507
486
    }
508
487
    custom_argv[0] = argv[0];
509
488
    custom_argv[1] = NULL;
510
 
 
511
 
    /* for each line in the config file, strip whitespace and ignore
512
 
       commented text */
 
489
    
513
490
    while(true){
514
491
      sret = getline(&org_line, &size, conffp);
515
492
      if(sret == -1){
553
530
      goto fallback;
554
531
    }
555
532
  }
556
 
  /* If there was any arguments from configuration file,
557
 
     pass them to parser as command arguments */
 
533
 
558
534
  if(custom_argv != NULL){
559
 
    ret = argp_parse (&argp, custom_argc, custom_argv, 0, 0, NULL);
 
535
    ret = argp_parse (&argp, custom_argc, custom_argv, 0, 0, &plugin_list);
560
536
    if (ret == ARGP_ERR_UNKNOWN){
561
537
      fprintf(stderr, "Unknown error while parsing arguments\n");
562
538
      exitstatus = EXIT_FAILURE;
577
553
      }
578
554
    }
579
555
  }
580
 
 
581
 
  /* Strip permissions down to nobody */
 
556
  
582
557
  ret = setuid(uid);
583
558
  if (ret == -1){
584
559
    perror("setuid");
585
 
  }  
 
560
  }
 
561
  
586
562
  setgid(gid);
587
563
  if (ret == -1){
588
564
    perror("setgid");
614
590
  }
615
591
  
616
592
  FD_ZERO(&rfds_all);
617
 
 
618
 
  /* Read and execute any executable in the plugin directory*/
 
593
  
619
594
  while(true){
620
595
    dirst = readdir(dir);
621
596
    
652
627
          break;
653
628
        }
654
629
      }
 
630
      
655
631
      if(bad_name){
656
632
        continue;
657
633
      }
 
634
      
658
635
      for(const char **suf = bad_suffixes; *suf != NULL; suf++){
659
636
        size_t suf_len = strlen(*suf);
660
637
        if((d_name_len >= suf_len)
687
664
      free(filename);
688
665
      continue;
689
666
    }
690
 
 
691
 
    /* Ignore non-executable files */
 
667
    
692
668
    if (not S_ISREG(st.st_mode) or (access(filename, X_OK) != 0)){
693
669
      if(debug){
694
670
        fprintf(stderr, "Ignoring plugin dir entry \"%s\""
697
673
      free(filename);
698
674
      continue;
699
675
    }
700
 
    
701
 
    plugin *p = getplugin(dirst->d_name);
 
676
    plugin *p = getplugin(dirst->d_name, &plugin_list);
702
677
    if(p == NULL){
703
678
      perror("getplugin");
704
679
      free(filename);
714
689
    }
715
690
    {
716
691
      /* Add global arguments to argument list for this plugin */
717
 
      plugin *g = getplugin(NULL);
 
692
      plugin *g = getplugin(NULL, &plugin_list);
718
693
      if(g != NULL){
719
694
        for(char **a = g->argv + 1; *a != NULL; a++){
720
695
          if(not add_argument(p, *a)){
752
727
      exitstatus = EXIT_FAILURE;
753
728
      goto fallback;
754
729
    }
755
 
    /* Ask OS to automatic close the pipe on exec */
756
730
    ret = set_cloexec_flag(pipefd[0]);
757
731
    if(ret < 0){
758
732
      perror("set_cloexec_flag");
816
790
      }
817
791
      /* no return */
818
792
    }
819
 
    /* Parent process */
820
 
    close(pipefd[1]);           /* Close unused write end of pipe */
 
793
    /* parent process */
821
794
    free(filename);
822
 
    plugin *new_plugin = getplugin(dirst->d_name);
823
 
    if (new_plugin == NULL){
824
 
      perror("getplugin");
 
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");
825
799
      ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
826
800
      if(ret < 0){
827
 
        perror("sigprocmask");
 
801
        perror("sigprocmask");
828
802
      }
829
803
      exitstatus = EXIT_FAILURE;
830
804
      goto fallback;
831
805
    }
832
806
    
833
 
    new_plugin->pid = pid;
834
 
    new_plugin->fd = pipefd[0];
835
 
    
 
807
    *new_process = (struct process){ .pid = pid,
 
808
                                     .fd = pipefd[0],
 
809
                                     .next = process_list };
 
810
    // List handling
 
811
    process_list = new_process;
836
812
    /* Unblock SIGCHLD so signal handler can be run if this process
837
813
       has already completed */
838
814
    ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
842
818
      goto fallback;
843
819
    }
844
820
    
845
 
    FD_SET(new_plugin->fd, &rfds_all);
 
821
    FD_SET(new_process->fd, &rfds_all);
846
822
    
847
 
    if (maxfd < new_plugin->fd){
848
 
      maxfd = new_plugin->fd;
 
823
    if (maxfd < new_process->fd){
 
824
      maxfd = new_process->fd;
849
825
    }
850
826
    
851
827
  }
 
828
 
 
829
  free_plugin_list(plugin_list);
 
830
  plugin_list = NULL;
852
831
  
853
832
  closedir(dir);
854
833
  dir = NULL;
855
 
 
856
 
  for(plugin *p = plugin_list; p != NULL; p = p->next){
857
 
    if(p->pid != 0){
858
 
      break;
859
 
    }
860
 
    if(p->next == NULL){
861
 
      fprintf(stderr, "No plugin processes started. Incorrect plugin"
862
 
              " directory?\n");
863
 
      free_plugin_list();
864
 
    }
 
834
    
 
835
  if (process_list == NULL){
 
836
    fprintf(stderr, "No plugin processes started. Incorrect plugin"
 
837
            " directory?\n");
 
838
    process_list = NULL;
865
839
  }
866
 
 
867
 
  /* Main loop while running plugins exist */
868
 
  while(plugin_list){
 
840
  while(process_list){
869
841
    fd_set rfds = rfds_all;
870
842
    int select_ret = select(maxfd+1, &rfds, NULL, NULL, NULL);
871
843
    if (select_ret == -1){
875
847
    }
876
848
    /* OK, now either a process completed, or something can be read
877
849
       from one of them */
878
 
    for(plugin *proc = plugin_list; proc != NULL; proc = proc->next){
 
850
    for(process *proc = process_list; proc ; proc = proc->next){
879
851
      /* Is this process completely done? */
880
852
      if(proc->eof and proc->completed){
881
853
        /* Only accept the plugin output if it exited cleanly */
882
854
        if(not WIFEXITED(proc->status)
883
855
           or WEXITSTATUS(proc->status) != 0){
884
856
          /* Bad exit by plugin */
885
 
 
886
857
          if(debug){
887
858
            if(WIFEXITED(proc->status)){
888
859
              fprintf(stderr, "Plugin %u exited with status %d\n",
897
868
                      (unsigned int) (proc->pid));
898
869
            }
899
870
          }
900
 
          
901
871
          /* Remove the plugin */
902
872
          FD_CLR(proc->fd, &rfds_all);
903
 
 
904
873
          /* Block signal while modifying process_list */
905
874
          ret = sigprocmask(SIG_BLOCK, &sigchld_action.sa_mask, NULL);
906
875
          if(ret < 0){
908
877
            exitstatus = EXIT_FAILURE;
909
878
            goto fallback;
910
879
          }
911
 
          free_plugin(proc);
 
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
          }
912
893
          /* We are done modifying process list, so unblock signal */
913
894
          ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask,
914
895
                             NULL);
915
896
          if(ret < 0){
916
897
            perror("sigprocmask");
917
 
            exitstatus = EXIT_FAILURE;
918
 
            goto fallback;
919
 
          }
920
 
          
921
 
          if(plugin_list == NULL){
922
 
            break;
923
 
          }
924
 
          continue;
 
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;
925
905
        }
926
 
        
927
906
        /* This process exited nicely, so print its buffer */
928
907
 
929
908
        bool bret = print_out_password(proc->buffer,
934
913
        }
935
914
        goto fallback;
936
915
      }
937
 
      
938
916
      /* This process has not completed.  Does it have any output? */
939
917
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){
940
918
        /* This process had nothing to say at this time */
970
948
 
971
949
 fallback:
972
950
  
973
 
  if(plugin_list == NULL or exitstatus != EXIT_SUCCESS){
 
951
  if(process_list == NULL or exitstatus != EXIT_SUCCESS){
974
952
    /* Fallback if all plugins failed, none are found or an error
975
953
       occured */
976
954
    bool bret;
996
974
    }
997
975
    free(custom_argv);
998
976
  }
 
977
  free_plugin_list(plugin_list);
999
978
  
1000
979
  if(dir != NULL){
1001
980
    closedir(dir);
1002
981
  }
1003
982
  
1004
983
  /* Free the process list and kill the processes */
1005
 
  for(plugin *p = plugin_list; p != NULL; p = p->next){
1006
 
    if(p->pid != 0){
1007
 
      close(p->fd);
1008
 
      ret = kill(p->pid, SIGTERM);
1009
 
      if(ret == -1 and errno != ESRCH){
1010
 
        /* Set-uid proccesses might not get closed */
1011
 
        perror("kill");
1012
 
      }
 
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");
1013
991
    }
 
992
    free(process_list->buffer);
 
993
    free(process_list);
1014
994
  }
1015
995
  
1016
996
  /* Wait for any remaining child processes to terminate */
1021
1001
    perror("wait");
1022
1002
  }
1023
1003
 
1024
 
  free_plugin_list();
1025
 
  
1026
1004
  free(plugindir);
1027
1005
  free(argfile);
1028
1006