/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: 2019-03-18 22:29:25 UTC
  • Revision ID: teddy@recompile.se-20190318222925-jvhek84dgcfgj6g3
mandos-ctl: Refactor tests

* mandos-ctl: Where the clients names "foo" and "barbar" do not refer
              to the actual mock clients in the TestCommand class,
              change all occurrences of these names to "client1" and
              "client2" (or just "client" when only one is used) .
              Also change all test doubles to use correct terminology;
              some things called mocks are actually stubs or spies,
              and rename all true mocks to have "mock" in their names.
              Also eliminate duplicate values in tests; derive values
              from previously defined values whenever possible.

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 © 2008-2014 Teddy Hogeborn
6
 
 * Copyright © 2008-2014 Björn Påhlsson
7
 
 * 
8
 
 * This program is free software: you can redistribute it and/or
9
 
 * modify it under the terms of the GNU General Public License as
10
 
 * published by the Free Software Foundation, either version 3 of the
11
 
 * License, or (at your option) any later version.
12
 
 * 
13
 
 * This program is distributed in the hope that it will be useful, but
 
5
 * Copyright © 2008-2018 Teddy Hogeborn
 
6
 * Copyright © 2008-2018 Björn Påhlsson
 
7
 * 
 
8
 * This file is part of Mandos.
 
9
 * 
 
10
 * Mandos is free software: you can redistribute it and/or modify it
 
11
 * under the terms of the GNU General Public License as published by
 
12
 * the Free Software Foundation, either version 3 of the License, or
 
13
 * (at your option) any later version.
 
14
 * 
 
15
 * Mandos is distributed in the hope that it will be useful, but
14
16
 * WITHOUT ANY WARRANTY; without even the implied warranty of
15
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16
18
 * General Public License for more details.
17
19
 * 
18
20
 * You should have received a copy of the GNU General Public License
19
 
 * along with this program.  If not, see
20
 
 * <http://www.gnu.org/licenses/>.
 
21
 * along with Mandos.  If not, see <http://www.gnu.org/licenses/>.
21
22
 * 
22
23
 * Contact the authors at <mandos@recompile.se>.
23
24
 */
37
38
#include <sys/select.h>         /* fd_set, select(), FD_ZERO(),
38
39
                                   FD_SET(), FD_ISSET(), FD_CLR */
39
40
#include <sys/wait.h>           /* wait(), waitpid(), WIFEXITED(),
40
 
                                   WEXITSTATUS(), WTERMSIG(),
41
 
                                   WCOREDUMP() */
 
41
                                   WEXITSTATUS(), WTERMSIG() */
42
42
#include <sys/stat.h>           /* struct stat, fstat(), S_ISREG() */
43
43
#include <iso646.h>             /* and, or, not */
44
44
#include <dirent.h>             /* struct dirent, scandirat() */
76
76
#define BUFFER_SIZE 256
77
77
 
78
78
#define PDIR "/lib/mandos/plugins.d"
 
79
#define PHDIR "/lib/mandos/plugin-helpers"
79
80
#define AFILE "/conf/conf.d/mandos/plugin-runner.conf"
80
81
 
81
82
const char *argp_program_version = "plugin-runner " VERSION;
347
348
 
348
349
int main(int argc, char *argv[]){
349
350
  char *plugindir = NULL;
 
351
  char *pluginhelperdir = NULL;
350
352
  char *argfile = NULL;
351
353
  FILE *conffp;
352
354
  struct dirent **direntries = NULL;
414
416
      .doc = "Group ID the plugins will run as", .group = 3 },
415
417
    { .name = "debug", .key = 132,
416
418
      .doc = "Debug mode", .group = 4 },
 
419
    { .name = "plugin-helper-dir", .key = 133,
 
420
      .arg = "DIRECTORY",
 
421
      .doc = "Specify a different plugin helper directory",
 
422
      .group = 2 },
417
423
    /*
418
424
     * These reproduce what we would get without ARGP_NO_HELP
419
425
     */
545
551
    case 132:                   /* --debug */
546
552
      debug = true;
547
553
      break;
 
554
    case 133:                   /* --plugin-helper-dir */
 
555
      free(pluginhelperdir);
 
556
      pluginhelperdir = strdup(arg);
 
557
      if(pluginhelperdir != NULL){
 
558
        errno = 0;
 
559
      }
 
560
      break;
548
561
      /*
549
562
       * These reproduce what we would get without ARGP_NO_HELP
550
563
       */
551
564
    case '?':                   /* --help */
552
565
      state->flags &= ~(unsigned int)ARGP_NO_EXIT; /* force exit */
553
566
      argp_state_help(state, state->out_stream, ARGP_HELP_STD_HELP);
 
567
      __builtin_unreachable();
554
568
    case -3:                    /* --usage */
555
569
      state->flags &= ~(unsigned int)ARGP_NO_EXIT; /* force exit */
556
570
      argp_state_help(state, state->out_stream,
557
571
                      ARGP_HELP_USAGE | ARGP_HELP_EXIT_OK);
 
572
      __builtin_unreachable();
558
573
    case 'V':                   /* --version */
559
574
      fprintf(state->out_stream, "%s\n", argp_program_version);
560
575
      exit(EXIT_SUCCESS);
570
585
      if(arg[0] == '\0'){
571
586
        break;
572
587
      }
 
588
      /* FALLTHROUGH */
573
589
    default:
574
590
      return ARGP_ERR_UNKNOWN;
575
591
    }
601
617
    case 130:                   /* --userid */
602
618
    case 131:                   /* --groupid */
603
619
    case 132:                   /* --debug */
 
620
    case 133:                   /* --plugin-helper-dir */
604
621
    case '?':                   /* --help */
605
622
    case -3:                    /* --usage */
606
623
    case 'V':                   /* --version */
687
704
        custom_argc += 1;
688
705
        {
689
706
          char **new_argv = realloc(custom_argv, sizeof(char *)
690
 
                                    * ((unsigned int)
691
 
                                       custom_argc + 1));
 
707
                                    * ((size_t)custom_argc + 1));
692
708
          if(new_argv == NULL){
693
709
            error(0, errno, "realloc");
694
710
            exitstatus = EX_OSERR;
761
777
    goto fallback;
762
778
  }
763
779
  
 
780
  {
 
781
    char *pluginhelperenv;
 
782
    bool bret = true;
 
783
    ret = asprintf(&pluginhelperenv, "MANDOSPLUGINHELPERDIR=%s",
 
784
                   pluginhelperdir != NULL ? pluginhelperdir : PHDIR);
 
785
    if(ret != -1){
 
786
      bret = add_environment(getplugin(NULL), pluginhelperenv, true);
 
787
    }
 
788
    if(ret == -1 or not bret){
 
789
      error(0, errno, "Failed to set MANDOSPLUGINHELPERDIR"
 
790
            " environment variable to \"%s\" for all plugins\n",
 
791
            pluginhelperdir != NULL ? pluginhelperdir : PHDIR);
 
792
    }
 
793
    if(ret != -1){
 
794
      free(pluginhelperenv);
 
795
    }
 
796
  }
 
797
  
764
798
  if(debug){
765
 
    for(plugin *p = plugin_list; p != NULL; p=p->next){
 
799
    for(plugin *p = plugin_list; p != NULL; p = p->next){
766
800
      fprintf(stderr, "Plugin: %s has %d arguments\n",
767
801
              p->name ? p->name : "Global", p->argc - 1);
768
802
      for(char **a = p->argv; *a != NULL; a++){
777
811
  
778
812
  if(getuid() == 0){
779
813
    /* Work around Debian bug #633582:
780
 
       <http://bugs.debian.org/633582> */
 
814
       <https://bugs.debian.org/633582> */
781
815
    int plugindir_fd = open(/* plugindir or */ PDIR, O_RDONLY);
782
816
    if(plugindir_fd == -1){
783
817
      if(errno != ENOENT){
795
829
          }
796
830
        }
797
831
      }
798
 
      TEMP_FAILURE_RETRY(close(plugindir_fd));
 
832
      close(plugindir_fd);
799
833
    }
800
834
  }
801
835
  
860
894
    return 1;
861
895
  }
862
896
  
863
 
#ifdef __GLIBC__
864
 
#if __GLIBC_PREREQ(2, 15)
865
897
  int numplugins = scandirat(dir_fd, ".", &direntries, good_name,
866
898
                             alphasort);
867
 
#else  /* not __GLIBC_PREREQ(2, 15) */
868
 
  int numplugins = scandir(plugindir != NULL ? plugindir : PDIR,
869
 
                           &direntries, good_name, alphasort);
870
 
#endif  /* not __GLIBC_PREREQ(2, 15) */
871
 
#else   /* not __GLIBC__ */
872
 
  int numplugins = scandir(plugindir != NULL ? plugindir : PDIR,
873
 
                           &direntries, good_name, alphasort);
874
 
#endif  /* not __GLIBC__ */
875
899
  if(numplugins == -1){
876
900
    error(0, errno, "Could not scan plugin dir");
877
901
    direntries = NULL;
887
911
    int plugin_fd = openat(dir_fd, direntries[i]->d_name, O_RDONLY);
888
912
    if(plugin_fd == -1){
889
913
      error(0, errno, "Could not open plugin");
 
914
      free(direntries[i]);
890
915
      continue;
891
916
    }
892
917
    ret = (int)TEMP_FAILURE_RETRY(fstat(plugin_fd, &st));
893
918
    if(ret == -1){
894
919
      error(0, errno, "stat");
895
 
      TEMP_FAILURE_RETRY(close(plugin_fd));
 
920
      close(plugin_fd);
 
921
      free(direntries[i]);
896
922
      continue;
897
923
    }
898
924
    
906
932
                plugindir != NULL ? plugindir : PDIR,
907
933
                direntries[i]->d_name);
908
934
      }
909
 
      TEMP_FAILURE_RETRY(close(plugin_fd));
 
935
      close(plugin_fd);
 
936
      free(direntries[i]);
910
937
      continue;
911
938
    }
912
939
    
913
940
    plugin *p = getplugin(direntries[i]->d_name);
914
941
    if(p == NULL){
915
942
      error(0, errno, "getplugin");
916
 
      TEMP_FAILURE_RETRY(close(plugin_fd));
 
943
      close(plugin_fd);
 
944
      free(direntries[i]);
917
945
      continue;
918
946
    }
919
947
    if(p->disabled){
921
949
        fprintf(stderr, "Ignoring disabled plugin \"%s\"\n",
922
950
                direntries[i]->d_name);
923
951
      }
924
 
      TEMP_FAILURE_RETRY(close(plugin_fd));
 
952
      close(plugin_fd);
 
953
      free(direntries[i]);
925
954
      continue;
926
955
    }
927
956
    {
960
989
    if(ret == -1){
961
990
      error(0, errno, "pipe");
962
991
      exitstatus = EX_OSERR;
 
992
      free(direntries[i]);
963
993
      goto fallback;
964
994
    }
965
995
    if(pipefd[0] >= FD_SETSIZE){
966
996
      fprintf(stderr, "pipe()[0] (%d) >= FD_SETSIZE (%d)", pipefd[0],
967
997
              FD_SETSIZE);
968
 
      TEMP_FAILURE_RETRY(close(pipefd[0]));
969
 
      TEMP_FAILURE_RETRY(close(pipefd[1]));
 
998
      close(pipefd[0]);
 
999
      close(pipefd[1]);
970
1000
      exitstatus = EX_OSERR;
 
1001
      free(direntries[i]);
971
1002
      goto fallback;
972
1003
    }
973
1004
#ifndef O_CLOEXEC
975
1006
    ret = set_cloexec_flag(pipefd[0]);
976
1007
    if(ret < 0){
977
1008
      error(0, errno, "set_cloexec_flag");
978
 
      TEMP_FAILURE_RETRY(close(pipefd[0]));
979
 
      TEMP_FAILURE_RETRY(close(pipefd[1]));
 
1009
      close(pipefd[0]);
 
1010
      close(pipefd[1]);
980
1011
      exitstatus = EX_OSERR;
 
1012
      free(direntries[i]);
981
1013
      goto fallback;
982
1014
    }
983
1015
    ret = set_cloexec_flag(pipefd[1]);
984
1016
    if(ret < 0){
985
1017
      error(0, errno, "set_cloexec_flag");
986
 
      TEMP_FAILURE_RETRY(close(pipefd[0]));
987
 
      TEMP_FAILURE_RETRY(close(pipefd[1]));
 
1018
      close(pipefd[0]);
 
1019
      close(pipefd[1]);
988
1020
      exitstatus = EX_OSERR;
 
1021
      free(direntries[i]);
989
1022
      goto fallback;
990
1023
    }
991
1024
#endif  /* not O_CLOEXEC */
996
1029
    if(ret < 0){
997
1030
      error(0, errno, "sigprocmask");
998
1031
      exitstatus = EX_OSERR;
 
1032
      free(direntries[i]);
999
1033
      goto fallback;
1000
1034
    }
1001
1035
    /* Starting a new process to be watched */
1007
1041
      error(0, errno, "fork");
1008
1042
      TEMP_FAILURE_RETRY(sigprocmask(SIG_UNBLOCK,
1009
1043
                                     &sigchld_action.sa_mask, NULL));
1010
 
      TEMP_FAILURE_RETRY(close(pipefd[0]));
1011
 
      TEMP_FAILURE_RETRY(close(pipefd[1]));
 
1044
      close(pipefd[0]);
 
1045
      close(pipefd[1]);
1012
1046
      exitstatus = EX_OSERR;
 
1047
      free(direntries[i]);
1013
1048
      goto fallback;
1014
1049
    }
1015
1050
    if(pid == 0){
1041
1076
      /* no return */
1042
1077
    }
1043
1078
    /* Parent process */
1044
 
    TEMP_FAILURE_RETRY(close(pipefd[1])); /* Close unused write end of
1045
 
                                             pipe */
1046
 
    TEMP_FAILURE_RETRY(close(plugin_fd));
 
1079
    close(pipefd[1]);           /* Close unused write end of pipe */
 
1080
    close(plugin_fd);
1047
1081
    plugin *new_plugin = getplugin(direntries[i]->d_name);
1048
1082
    if(new_plugin == NULL){
1049
1083
      error(0, errno, "getplugin");
1054
1088
        error(0, errno, "sigprocmask");
1055
1089
      }
1056
1090
      exitstatus = EX_OSERR;
 
1091
      free(direntries[i]);
1057
1092
      goto fallback;
1058
1093
    }
 
1094
    free(direntries[i]);
1059
1095
    
1060
1096
    new_plugin->pid = pid;
1061
1097
    new_plugin->fd = pipefd[0];
1062
 
    
 
1098
 
 
1099
    if(debug){
 
1100
      fprintf(stderr, "Plugin %s started (PID %" PRIdMAX ")\n",
 
1101
              new_plugin->name, (intmax_t) (new_plugin->pid));
 
1102
    }
 
1103
 
1063
1104
    /* Unblock SIGCHLD so signal handler can be run if this process
1064
1105
       has already completed */
1065
1106
    ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_UNBLOCK,
1071
1112
      goto fallback;
1072
1113
    }
1073
1114
    
1074
 
#if defined (__GNUC__) and defined (__GLIBC__)
1075
 
#if not __GLIBC_PREREQ(2, 16)
1076
 
#pragma GCC diagnostic push
1077
 
#pragma GCC diagnostic ignored "-Wsign-conversion"
1078
 
#endif
1079
 
#endif
1080
 
    FD_SET(new_plugin->fd, &rfds_all); /* Spurious warning from
1081
 
                                          -Wconversion in GNU libc
1082
 
                                          before 2.16 */
1083
 
#if defined (__GNUC__) and defined (__GLIBC__)
1084
 
#if not __GLIBC_PREREQ(2, 16)
1085
 
#pragma GCC diagnostic pop
1086
 
#endif
1087
 
#endif
 
1115
    FD_SET(new_plugin->fd, &rfds_all);
1088
1116
    
1089
1117
    if(maxfd < new_plugin->fd){
1090
1118
      maxfd = new_plugin->fd;
1093
1121
  
1094
1122
  free(direntries);
1095
1123
  direntries = NULL;
1096
 
  TEMP_FAILURE_RETRY(close(dir_fd));
 
1124
  close(dir_fd);
1097
1125
  dir_fd = -1;
1098
1126
  free_plugin(getplugin(NULL));
1099
1127
  
1139
1167
                      (intmax_t) (proc->pid),
1140
1168
                      WTERMSIG(proc->status),
1141
1169
                      strsignal(WTERMSIG(proc->status)));
1142
 
            } else if(WCOREDUMP(proc->status)){
1143
 
              fprintf(stderr, "Plugin %s [%" PRIdMAX "] dumped"
1144
 
                      " core\n", proc->name, (intmax_t) (proc->pid));
1145
1170
            }
1146
1171
          }
1147
1172
          
1148
1173
          /* Remove the plugin */
1149
 
#if defined (__GNUC__) and defined (__GLIBC__)
1150
 
#if not __GLIBC_PREREQ(2, 16)
1151
 
#pragma GCC diagnostic push
1152
 
#pragma GCC diagnostic ignored "-Wsign-conversion"
1153
 
#endif
1154
 
#endif
1155
 
          FD_CLR(proc->fd, &rfds_all); /* Spurious warning from
1156
 
                                          -Wconversion in GNU libc
1157
 
                                          before 2.16 */
1158
 
#if defined (__GNUC__) and defined (__GLIBC__)
1159
 
#if not __GLIBC_PREREQ(2, 16)
1160
 
#pragma GCC diagnostic pop
1161
 
#endif
1162
 
#endif
 
1174
          FD_CLR(proc->fd, &rfds_all);
1163
1175
          
1164
1176
          /* Block signal while modifying process_list */
1165
1177
          ret = (int)TEMP_FAILURE_RETRY(sigprocmask
1205
1217
      }
1206
1218
      
1207
1219
      /* This process has not completed.  Does it have any output? */
1208
 
#if defined (__GNUC__) and defined (__GLIBC__)
1209
 
#if not __GLIBC_PREREQ(2, 16)
1210
 
#pragma GCC diagnostic push
1211
 
#pragma GCC diagnostic ignored "-Wsign-conversion"
1212
 
#endif
1213
 
#endif
1214
 
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){ /* Spurious
1215
 
                                                         warning from
1216
 
                                                         -Wconversion
1217
 
                                                         in GNU libc
1218
 
                                                         before
1219
 
                                                         2.16 */
1220
 
#if defined (__GNUC__) and defined (__GLIBC__)
1221
 
#if not __GLIBC_PREREQ(2, 16)
1222
 
#pragma GCC diagnostic pop
1223
 
#endif
1224
 
#endif
 
1220
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){
1225
1221
        /* This process had nothing to say at this time */
1226
1222
        proc = proc->next;
1227
1223
        continue;
1297
1293
  free(direntries);
1298
1294
  
1299
1295
  if(dir_fd != -1){
1300
 
    TEMP_FAILURE_RETRY(close(dir_fd));
 
1296
    close(dir_fd);
1301
1297
  }
1302
1298
  
1303
1299
  /* Kill the processes */
1323
1319
  free_plugin_list();
1324
1320
  
1325
1321
  free(plugindir);
 
1322
  free(pluginhelperdir);
1326
1323
  free(argfile);
1327
1324
  
1328
1325
  return exitstatus;