/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 at recompile
  • Date: 2020-04-05 21:30:59 UTC
  • Revision ID: teddy@recompile.se-20200405213059-fb2a61ckqynrmatk
Fix file descriptor leak in mandos-client

When the local network has Mandos servers announcing themselves using
real, globally reachable, IPv6 addresses (i.e. not link-local
addresses), but there is no router on the local network providing IPv6
RA (Router Advertisement) packets, the client cannot reach the server
by normal means, since the client only has a link-local IPv6 address,
and has no usable route to reach the server's global IPv6 address.
(This is not a common situation, and usually only happens when the
router itself reboots and runs a Mandos client, since it cannot then
give RA packets to itself.)  The client code has a solution for
this, which consists of adding a temporary local route to reach the
address of the server during communication, and removing this
temporary route afterwards.

This solution with a temporary route works, but has a file descriptor
leak; it leaks one file descriptor for each addition and for each
removal of a route.  If one server requiring an added route is present
on the network, but no servers gives a password, making the client
retry after the default ten seconds, and we furthermore assume a
default 1024 open files limit, the client runs out of file descriptors
after about 90 minutes, after which time the client process will be
useless and fail to retrieve any passwords, necessitating manual
password entry via the keyboard.

Fix this by eliminating the file descriptor leak in the client.

* plugins.d/mandos-client.c (add_delete_local_route): Do
  close(devnull) also in parent process, also if fork() fails, and on
  any failure in child process.

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
 */
24
25
 
25
26
#define _GNU_SOURCE             /* TEMP_FAILURE_RETRY(), getline(),
26
 
                                   O_CLOEXEC */
 
27
                                   O_CLOEXEC, pipe2() */
27
28
#include <stddef.h>             /* size_t, NULL */
28
 
#include <stdlib.h>             /* malloc(), exit(), EXIT_SUCCESS,
29
 
                                   realloc() */
 
29
#include <stdlib.h>             /* malloc(), reallocarray(), realloc(),
 
30
                                   EXIT_SUCCESS, exit() */
30
31
#include <stdbool.h>            /* bool, true, false */
31
32
#include <stdio.h>              /* fileno(), fprintf(),
32
33
                                   stderr, STDOUT_FILENO, fclose() */
33
 
#include <sys/types.h>          /* DIR, fdopendir(), fstat(), struct
34
 
                                   stat, waitpid(), WIFEXITED(),
35
 
                                   WEXITSTATUS(), wait(), pid_t,
36
 
                                   uid_t, gid_t, getuid(), getgid(),
37
 
                                   dirfd() */
 
34
#include <sys/types.h>          /* fstat(), struct stat, waitpid(),
 
35
                                   WIFEXITED(), WEXITSTATUS(), wait(),
 
36
                                   pid_t, uid_t, gid_t, getuid(),
 
37
                                   getgid() */
38
38
#include <sys/select.h>         /* fd_set, select(), FD_ZERO(),
39
39
                                   FD_SET(), FD_ISSET(), FD_CLR */
40
40
#include <sys/wait.h>           /* wait(), waitpid(), WIFEXITED(),
41
 
                                   WEXITSTATUS(), WTERMSIG(),
42
 
                                   WCOREDUMP() */
 
41
                                   WEXITSTATUS(), WTERMSIG() */
43
42
#include <sys/stat.h>           /* struct stat, fstat(), S_ISREG() */
44
43
#include <iso646.h>             /* and, or, not */
45
 
#include <dirent.h>             /* DIR, struct dirent, fdopendir(),
46
 
                                   readdir(), closedir(), dirfd() */
 
44
#include <dirent.h>             /* struct dirent, scandirat() */
47
45
#include <unistd.h>             /* fcntl(), F_GETFD, F_SETFD,
48
46
                                   FD_CLOEXEC, write(), STDOUT_FILENO,
49
47
                                   struct stat, fstat(), close(),
50
48
                                   setgid(), setuid(), S_ISREG(),
51
 
                                   faccessat() pipe(), fork(),
 
49
                                   faccessat() pipe2(), fork(),
52
50
                                   _exit(), dup2(), fexecve(), read()
53
51
                                */
54
52
#include <fcntl.h>              /* fcntl(), F_GETFD, F_SETFD,
55
 
                                   FD_CLOEXEC, openat() */
 
53
                                   FD_CLOEXEC, openat(), scandirat(),
 
54
                                   pipe2() */
56
55
#include <string.h>             /* strsep, strlen(), strsignal(),
57
56
                                   strcmp(), strncmp() */
58
57
#include <errno.h>              /* errno */
77
76
#define BUFFER_SIZE 256
78
77
 
79
78
#define PDIR "/lib/mandos/plugins.d"
 
79
#define PHDIR "/lib/mandos/plugin-helpers"
80
80
#define AFILE "/conf/conf.d/mandos/plugin-runner.conf"
81
81
 
82
82
const char *argp_program_version = "plugin-runner " VERSION;
179
179
  /* Resize the pointed-to array to hold one more pointer */
180
180
  char **new_array = NULL;
181
181
  do {
182
 
    new_array = realloc(*array, sizeof(char *)
183
 
                        * (size_t) ((*len) + 2));
 
182
#if defined(__GLIBC_PREREQ) and __GLIBC_PREREQ(2, 26)
 
183
    new_array = reallocarray(*array, (size_t)((*len) + 2),
 
184
                             sizeof(char *));
 
185
#else
 
186
    if(((size_t)((*len) + 2)) > (SIZE_MAX / sizeof(char *))){
 
187
      /* overflow */
 
188
      new_array = NULL;
 
189
      errno = ENOMEM;
 
190
    } else {
 
191
      new_array = realloc(*array, (size_t)((*len) + 2)
 
192
                          * sizeof(char *));
 
193
    }
 
194
#endif
184
195
  } while(new_array == NULL and errno == EINTR);
185
196
  /* Malloc check */
186
197
  if(new_array == NULL){
241
252
  return add_to_char_array(def, &(p->environ), &(p->envc));
242
253
}
243
254
 
 
255
#ifndef O_CLOEXEC
244
256
/*
245
257
 * Based on the example in the GNU LibC manual chapter 13.13 "File
246
258
 * Descriptor Flags".
257
269
  return (int)TEMP_FAILURE_RETRY(fcntl(fd, F_SETFD,
258
270
                                       ret | FD_CLOEXEC));
259
271
}
 
272
#endif  /* not O_CLOEXEC */
260
273
 
261
274
 
262
275
/* Mark processes as completed when they exit, and save their exit
311
324
__attribute__((nonnull))
312
325
static void free_plugin(plugin *plugin_node){
313
326
  
314
 
  for(char **arg = plugin_node->argv; *arg != NULL; arg++){
 
327
  for(char **arg = (plugin_node->argv)+1; *arg != NULL; arg++){
315
328
    free(*arg);
316
329
  }
 
330
  free(plugin_node->name);
317
331
  free(plugin_node->argv);
318
332
  for(char **env = plugin_node->environ; *env != NULL; env++){
319
333
    free(*env);
346
360
 
347
361
int main(int argc, char *argv[]){
348
362
  char *plugindir = NULL;
 
363
  char *pluginhelperdir = NULL;
349
364
  char *argfile = NULL;
350
365
  FILE *conffp;
351
 
  DIR *dir = NULL;
352
 
  struct dirent *dirst;
 
366
  struct dirent **direntries = NULL;
353
367
  struct stat st;
354
368
  fd_set rfds_all;
355
369
  int ret, maxfd = 0;
363
377
                                      .sa_flags = SA_NOCLDSTOP };
364
378
  char **custom_argv = NULL;
365
379
  int custom_argc = 0;
366
 
  int dir_fd;
 
380
  int dir_fd = -1;
367
381
  
368
382
  /* Establish a signal handler */
369
383
  sigemptyset(&sigchld_action.sa_mask);
414
428
      .doc = "Group ID the plugins will run as", .group = 3 },
415
429
    { .name = "debug", .key = 132,
416
430
      .doc = "Debug mode", .group = 4 },
 
431
    { .name = "plugin-helper-dir", .key = 133,
 
432
      .arg = "DIRECTORY",
 
433
      .doc = "Specify a different plugin helper directory",
 
434
      .group = 2 },
417
435
    /*
418
436
     * These reproduce what we would get without ARGP_NO_HELP
419
437
     */
545
563
    case 132:                   /* --debug */
546
564
      debug = true;
547
565
      break;
 
566
    case 133:                   /* --plugin-helper-dir */
 
567
      free(pluginhelperdir);
 
568
      pluginhelperdir = strdup(arg);
 
569
      if(pluginhelperdir != NULL){
 
570
        errno = 0;
 
571
      }
 
572
      break;
548
573
      /*
549
574
       * These reproduce what we would get without ARGP_NO_HELP
550
575
       */
551
576
    case '?':                   /* --help */
552
577
      state->flags &= ~(unsigned int)ARGP_NO_EXIT; /* force exit */
553
578
      argp_state_help(state, state->out_stream, ARGP_HELP_STD_HELP);
 
579
      __builtin_unreachable();
554
580
    case -3:                    /* --usage */
555
581
      state->flags &= ~(unsigned int)ARGP_NO_EXIT; /* force exit */
556
582
      argp_state_help(state, state->out_stream,
557
583
                      ARGP_HELP_USAGE | ARGP_HELP_EXIT_OK);
 
584
      __builtin_unreachable();
558
585
    case 'V':                   /* --version */
559
586
      fprintf(state->out_stream, "%s\n", argp_program_version);
560
587
      exit(EXIT_SUCCESS);
570
597
      if(arg[0] == '\0'){
571
598
        break;
572
599
      }
 
600
#if __GNUC__ >= 7
 
601
      __attribute__((fallthrough));
 
602
#else
 
603
          /* FALLTHROUGH */
 
604
#endif
573
605
    default:
574
606
      return ARGP_ERR_UNKNOWN;
575
607
    }
601
633
    case 130:                   /* --userid */
602
634
    case 131:                   /* --groupid */
603
635
    case 132:                   /* --debug */
 
636
    case 133:                   /* --plugin-helper-dir */
604
637
    case '?':                   /* --help */
605
638
    case -3:                    /* --usage */
606
639
    case 'V':                   /* --version */
686
719
        
687
720
        custom_argc += 1;
688
721
        {
689
 
          char **new_argv = realloc(custom_argv, sizeof(char *)
690
 
                                    * ((unsigned int)
691
 
                                       custom_argc + 1));
 
722
#if defined(__GLIBC_PREREQ) and __GLIBC_PREREQ(2, 26)
 
723
          char **new_argv = reallocarray(custom_argv, (size_t)custom_argc + 1,
 
724
                                         sizeof(char *));
 
725
#else
 
726
          char **new_argv = NULL;
 
727
          if(((size_t)custom_argc + 1) > (SIZE_MAX / sizeof(char *))){
 
728
            /* overflow */
 
729
            errno = ENOMEM;
 
730
          } else {
 
731
            new_argv = realloc(custom_argv, ((size_t)custom_argc + 1)
 
732
                               * sizeof(char *));
 
733
          }
 
734
#endif
692
735
          if(new_argv == NULL){
693
 
            error(0, errno, "realloc");
 
736
            error(0, errno, "reallocarray");
694
737
            exitstatus = EX_OSERR;
695
738
            free(new_arg);
696
739
            free(org_line);
761
804
    goto fallback;
762
805
  }
763
806
  
 
807
  {
 
808
    char *pluginhelperenv;
 
809
    bool bret = true;
 
810
    ret = asprintf(&pluginhelperenv, "MANDOSPLUGINHELPERDIR=%s",
 
811
                   pluginhelperdir != NULL ? pluginhelperdir : PHDIR);
 
812
    if(ret != -1){
 
813
      bret = add_environment(getplugin(NULL), pluginhelperenv, true);
 
814
    }
 
815
    if(ret == -1 or not bret){
 
816
      error(0, errno, "Failed to set MANDOSPLUGINHELPERDIR"
 
817
            " environment variable to \"%s\" for all plugins\n",
 
818
            pluginhelperdir != NULL ? pluginhelperdir : PHDIR);
 
819
    }
 
820
    if(ret != -1){
 
821
      free(pluginhelperenv);
 
822
    }
 
823
  }
 
824
  
764
825
  if(debug){
765
 
    for(plugin *p = plugin_list; p != NULL; p=p->next){
 
826
    for(plugin *p = plugin_list; p != NULL; p = p->next){
766
827
      fprintf(stderr, "Plugin: %s has %d arguments\n",
767
828
              p->name ? p->name : "Global", p->argc - 1);
768
829
      for(char **a = p->argv; *a != NULL; a++){
777
838
  
778
839
  if(getuid() == 0){
779
840
    /* Work around Debian bug #633582:
780
 
       <http://bugs.debian.org/633582> */
 
841
       <https://bugs.debian.org/633582> */
781
842
    int plugindir_fd = open(/* plugindir or */ PDIR, O_RDONLY);
782
843
    if(plugindir_fd == -1){
783
844
      if(errno != ENOENT){
795
856
          }
796
857
        }
797
858
      }
798
 
      TEMP_FAILURE_RETRY(close(plugindir_fd));
 
859
      close(plugindir_fd);
799
860
    }
800
861
  }
801
862
  
829
890
    ret = set_cloexec_flag(dir_fd);
830
891
    if(ret < 0){
831
892
      error(0, errno, "set_cloexec_flag");
832
 
      TEMP_FAILURE_RETRY(close(dir_fd));
833
893
      exitstatus = EX_OSERR;
834
894
      goto fallback;
835
895
    }
836
896
#endif  /* O_CLOEXEC */
837
 
    
838
 
    dir = fdopendir(dir_fd);
839
 
    if(dir == NULL){
840
 
      error(0, errno, "Could not open plugin dir");
841
 
      TEMP_FAILURE_RETRY(close(dir_fd));
842
 
      exitstatus = EX_OSERR;
843
 
      goto fallback;
 
897
  }
 
898
  
 
899
  int good_name(const struct dirent * const dirent){
 
900
    const char * const patterns[] = { ".*", "#*#", "*~", "*.dpkg-new",
 
901
                                      "*.dpkg-old", "*.dpkg-bak",
 
902
                                      "*.dpkg-divert", NULL };
 
903
#ifdef __GNUC__
 
904
#pragma GCC diagnostic push
 
905
#pragma GCC diagnostic ignored "-Wcast-qual"
 
906
#endif
 
907
    for(const char **pat = (const char **)patterns;
 
908
        *pat != NULL; pat++){
 
909
#ifdef __GNUC__
 
910
#pragma GCC diagnostic pop
 
911
#endif
 
912
      if(fnmatch(*pat, dirent->d_name, FNM_FILE_NAME | FNM_PERIOD)
 
913
         != FNM_NOMATCH){
 
914
        if(debug){
 
915
            fprintf(stderr, "Ignoring plugin dir entry \"%s\""
 
916
                    " matching pattern %s\n", dirent->d_name, *pat);
 
917
        }
 
918
        return 0;
 
919
      }
844
920
    }
 
921
    return 1;
 
922
  }
 
923
  
 
924
  int numplugins = scandirat(dir_fd, ".", &direntries, good_name,
 
925
                             alphasort);
 
926
  if(numplugins == -1){
 
927
    error(0, errno, "Could not scan plugin dir");
 
928
    direntries = NULL;
 
929
    exitstatus = EX_OSERR;
 
930
    goto fallback;
845
931
  }
846
932
  
847
933
  FD_ZERO(&rfds_all);
848
934
  
849
935
  /* Read and execute any executable in the plugin directory*/
850
 
  while(true){
851
 
    do {
852
 
      dirst = readdir(dir);
853
 
    } while(dirst == NULL and errno == EINTR);
854
 
    
855
 
    /* All directory entries have been processed */
856
 
    if(dirst == NULL){
857
 
      if(errno == EBADF){
858
 
        error(0, errno, "readdir");
859
 
        exitstatus = EX_IOERR;
860
 
        goto fallback;
861
 
      }
862
 
      break;
863
 
    }
864
 
    
865
 
    /* Ignore dotfiles, backup files and other junk */
866
 
    {
867
 
      bool bad_name = false;
868
 
      const char * const patterns[] = { ".*", "#*#", "*~",
869
 
                                        "*.dpkg-new", "*.dpkg-old",
870
 
                                        "*.dpkg-bak", "*.dpkg-divert",
871
 
                                        NULL };
872
 
#ifdef __GNUC__
873
 
#pragma GCC diagnostic push
874
 
#pragma GCC diagnostic ignored "-Wcast-qual"
875
 
#endif
876
 
      for(const char **pat = (const char **)patterns;
877
 
          *pat != NULL; pat++){
878
 
#ifdef __GNUC__
879
 
#pragma GCC diagnostic pop
880
 
#endif
881
 
        if(fnmatch(*pat, dirst->d_name,
882
 
                   FNM_FILE_NAME | FNM_PERIOD) != FNM_NOMATCH){
883
 
          if(debug){
884
 
            fprintf(stderr, "Ignoring plugin dir entry \"%s\""
885
 
                    " matching pattern %s\n", dirst->d_name, *pat);
886
 
          }
887
 
          bad_name = true;
888
 
          break;
889
 
        }
890
 
      }
891
 
      if(bad_name){
892
 
        continue;
893
 
      }
894
 
    }
895
 
    
896
 
    int plugin_fd = openat(dir_fd, dirst->d_name, O_RDONLY |
897
 
#ifdef O_CLOEXEC
898
 
                            O_CLOEXEC
899
 
#else  /* not O_CLOEXEC */
900
 
                            0
901
 
#endif  /* not O_CLOEXEC */
902
 
                            );
 
936
  for(int i = 0; i < numplugins; i++){
 
937
    
 
938
    int plugin_fd = openat(dir_fd, direntries[i]->d_name, O_RDONLY);
903
939
    if(plugin_fd == -1){
904
940
      error(0, errno, "Could not open plugin");
905
 
      continue;
906
 
    }
907
 
#ifndef O_CLOEXEC
908
 
  /* Set the FD_CLOEXEC flag on the plugin FD */
909
 
    ret = set_cloexec_flag(plugin_fd);
910
 
    if(ret < 0){
911
 
      error(0, errno, "set_cloexec_flag");
912
 
      TEMP_FAILURE_RETRY(close(plugin_fd));
913
 
      continue;
914
 
    }
915
 
#endif  /* O_CLOEXEC */
 
941
      free(direntries[i]);
 
942
      continue;
 
943
    }
916
944
    ret = (int)TEMP_FAILURE_RETRY(fstat(plugin_fd, &st));
917
945
    if(ret == -1){
918
946
      error(0, errno, "stat");
919
 
      TEMP_FAILURE_RETRY(close(plugin_fd));
 
947
      close(plugin_fd);
 
948
      free(direntries[i]);
920
949
      continue;
921
950
    }
922
951
    
923
952
    /* Ignore non-executable files */
924
953
    if(not S_ISREG(st.st_mode)
925
 
       or (TEMP_FAILURE_RETRY(faccessat(dir_fd, dirst->d_name, X_OK,
926
 
                                        0)) != 0)){
 
954
       or (TEMP_FAILURE_RETRY(faccessat(dir_fd, direntries[i]->d_name,
 
955
                                        X_OK, 0)) != 0)){
927
956
      if(debug){
928
957
        fprintf(stderr, "Ignoring plugin dir entry \"%s/%s\""
929
958
                " with bad type or mode\n",
930
 
                plugindir != NULL ? plugindir : PDIR, dirst->d_name);
 
959
                plugindir != NULL ? plugindir : PDIR,
 
960
                direntries[i]->d_name);
931
961
      }
932
 
      TEMP_FAILURE_RETRY(close(plugin_fd));
 
962
      close(plugin_fd);
 
963
      free(direntries[i]);
933
964
      continue;
934
965
    }
935
966
    
936
 
    plugin *p = getplugin(dirst->d_name);
 
967
    plugin *p = getplugin(direntries[i]->d_name);
937
968
    if(p == NULL){
938
969
      error(0, errno, "getplugin");
939
 
      TEMP_FAILURE_RETRY(close(plugin_fd));
 
970
      close(plugin_fd);
 
971
      free(direntries[i]);
940
972
      continue;
941
973
    }
942
974
    if(p->disabled){
943
975
      if(debug){
944
976
        fprintf(stderr, "Ignoring disabled plugin \"%s\"\n",
945
 
                dirst->d_name);
 
977
                direntries[i]->d_name);
946
978
      }
947
 
      TEMP_FAILURE_RETRY(close(plugin_fd));
 
979
      close(plugin_fd);
 
980
      free(direntries[i]);
948
981
      continue;
949
982
    }
950
983
    {
975
1008
    }
976
1009
    
977
1010
    int pipefd[2];
 
1011
#ifndef O_CLOEXEC
978
1012
    ret = (int)TEMP_FAILURE_RETRY(pipe(pipefd));
 
1013
#else  /* O_CLOEXEC */
 
1014
    ret = (int)TEMP_FAILURE_RETRY(pipe2(pipefd, O_CLOEXEC));
 
1015
#endif  /* O_CLOEXEC */
979
1016
    if(ret == -1){
980
1017
      error(0, errno, "pipe");
981
1018
      exitstatus = EX_OSERR;
 
1019
      free(direntries[i]);
982
1020
      goto fallback;
983
1021
    }
984
1022
    if(pipefd[0] >= FD_SETSIZE){
985
1023
      fprintf(stderr, "pipe()[0] (%d) >= FD_SETSIZE (%d)", pipefd[0],
986
1024
              FD_SETSIZE);
987
 
      TEMP_FAILURE_RETRY(close(pipefd[0]));
988
 
      TEMP_FAILURE_RETRY(close(pipefd[1]));
 
1025
      close(pipefd[0]);
 
1026
      close(pipefd[1]);
989
1027
      exitstatus = EX_OSERR;
 
1028
      free(direntries[i]);
990
1029
      goto fallback;
991
1030
    }
 
1031
#ifndef O_CLOEXEC
992
1032
    /* Ask OS to automatic close the pipe on exec */
993
1033
    ret = set_cloexec_flag(pipefd[0]);
994
1034
    if(ret < 0){
995
1035
      error(0, errno, "set_cloexec_flag");
996
 
      TEMP_FAILURE_RETRY(close(pipefd[0]));
997
 
      TEMP_FAILURE_RETRY(close(pipefd[1]));
 
1036
      close(pipefd[0]);
 
1037
      close(pipefd[1]);
998
1038
      exitstatus = EX_OSERR;
 
1039
      free(direntries[i]);
999
1040
      goto fallback;
1000
1041
    }
1001
1042
    ret = set_cloexec_flag(pipefd[1]);
1002
1043
    if(ret < 0){
1003
1044
      error(0, errno, "set_cloexec_flag");
1004
 
      TEMP_FAILURE_RETRY(close(pipefd[0]));
1005
 
      TEMP_FAILURE_RETRY(close(pipefd[1]));
 
1045
      close(pipefd[0]);
 
1046
      close(pipefd[1]);
1006
1047
      exitstatus = EX_OSERR;
 
1048
      free(direntries[i]);
1007
1049
      goto fallback;
1008
1050
    }
 
1051
#endif  /* not O_CLOEXEC */
1009
1052
    /* Block SIGCHLD until process is safely in process list */
1010
1053
    ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_BLOCK,
1011
1054
                                              &sigchld_action.sa_mask,
1013
1056
    if(ret < 0){
1014
1057
      error(0, errno, "sigprocmask");
1015
1058
      exitstatus = EX_OSERR;
 
1059
      free(direntries[i]);
1016
1060
      goto fallback;
1017
1061
    }
1018
1062
    /* Starting a new process to be watched */
1024
1068
      error(0, errno, "fork");
1025
1069
      TEMP_FAILURE_RETRY(sigprocmask(SIG_UNBLOCK,
1026
1070
                                     &sigchld_action.sa_mask, NULL));
1027
 
      TEMP_FAILURE_RETRY(close(pipefd[0]));
1028
 
      TEMP_FAILURE_RETRY(close(pipefd[1]));
 
1071
      close(pipefd[0]);
 
1072
      close(pipefd[1]);
1029
1073
      exitstatus = EX_OSERR;
 
1074
      free(direntries[i]);
1030
1075
      goto fallback;
1031
1076
    }
1032
1077
    if(pid == 0){
1048
1093
        _exit(EX_OSERR);
1049
1094
      }
1050
1095
      
1051
 
      if(dirfd(dir) < 0){
1052
 
        /* If dir has no file descriptor, we could not set FD_CLOEXEC
1053
 
           above and must now close it manually here. */
1054
 
        closedir(dir);
1055
 
      }
1056
1096
      if(fexecve(plugin_fd, p->argv,
1057
1097
                (p->environ[0] != NULL) ? p->environ : environ) < 0){
1058
1098
        error(0, errno, "fexecve for %s/%s",
1059
 
              plugindir != NULL ? plugindir : PDIR, dirst->d_name);
 
1099
              plugindir != NULL ? plugindir : PDIR,
 
1100
              direntries[i]->d_name);
1060
1101
        _exit(EX_OSERR);
1061
1102
      }
1062
1103
      /* no return */
1063
1104
    }
1064
1105
    /* Parent process */
1065
 
    TEMP_FAILURE_RETRY(close(pipefd[1])); /* Close unused write end of
1066
 
                                             pipe */
1067
 
    TEMP_FAILURE_RETRY(close(plugin_fd));
1068
 
    plugin *new_plugin = getplugin(dirst->d_name);
 
1106
    close(pipefd[1]);           /* Close unused write end of pipe */
 
1107
    close(plugin_fd);
 
1108
    plugin *new_plugin = getplugin(direntries[i]->d_name);
1069
1109
    if(new_plugin == NULL){
1070
1110
      error(0, errno, "getplugin");
1071
1111
      ret = (int)(TEMP_FAILURE_RETRY
1075
1115
        error(0, errno, "sigprocmask");
1076
1116
      }
1077
1117
      exitstatus = EX_OSERR;
 
1118
      free(direntries[i]);
1078
1119
      goto fallback;
1079
1120
    }
 
1121
    free(direntries[i]);
1080
1122
    
1081
1123
    new_plugin->pid = pid;
1082
1124
    new_plugin->fd = pipefd[0];
1083
 
    
 
1125
 
 
1126
    if(debug){
 
1127
      fprintf(stderr, "Plugin %s started (PID %" PRIdMAX ")\n",
 
1128
              new_plugin->name, (intmax_t) (new_plugin->pid));
 
1129
    }
 
1130
 
1084
1131
    /* Unblock SIGCHLD so signal handler can be run if this process
1085
1132
       has already completed */
1086
1133
    ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_UNBLOCK,
1092
1139
      goto fallback;
1093
1140
    }
1094
1141
    
1095
 
#if defined (__GNUC__) and defined (__GLIBC__)
1096
 
#if not __GLIBC_PREREQ(2, 16)
1097
 
#pragma GCC diagnostic push
1098
 
#pragma GCC diagnostic ignored "-Wsign-conversion"
1099
 
#endif
1100
 
#endif
1101
 
    FD_SET(new_plugin->fd, &rfds_all); /* Spurious warning from
1102
 
                                          -Wconversion in GNU libc
1103
 
                                          before 2.16 */
1104
 
#if defined (__GNUC__) and defined (__GLIBC__)
1105
 
#if not __GLIBC_PREREQ(2, 16)
1106
 
#pragma GCC diagnostic pop
1107
 
#endif
1108
 
#endif
 
1142
    FD_SET(new_plugin->fd, &rfds_all);
1109
1143
    
1110
1144
    if(maxfd < new_plugin->fd){
1111
1145
      maxfd = new_plugin->fd;
1112
1146
    }
1113
1147
  }
1114
1148
  
1115
 
  TEMP_FAILURE_RETRY(closedir(dir));
1116
 
  dir = NULL;
 
1149
  free(direntries);
 
1150
  direntries = NULL;
 
1151
  close(dir_fd);
 
1152
  dir_fd = -1;
1117
1153
  free_plugin(getplugin(NULL));
1118
1154
  
1119
1155
  for(plugin *p = plugin_list; p != NULL; p = p->next){
1158
1194
                      (intmax_t) (proc->pid),
1159
1195
                      WTERMSIG(proc->status),
1160
1196
                      strsignal(WTERMSIG(proc->status)));
1161
 
            } else if(WCOREDUMP(proc->status)){
1162
 
              fprintf(stderr, "Plugin %s [%" PRIdMAX "] dumped"
1163
 
                      " core\n", proc->name, (intmax_t) (proc->pid));
1164
1197
            }
1165
1198
          }
1166
1199
          
1167
1200
          /* Remove the plugin */
1168
 
#if defined (__GNUC__) and defined (__GLIBC__)
1169
 
#if not __GLIBC_PREREQ(2, 16)
1170
 
#pragma GCC diagnostic push
1171
 
#pragma GCC diagnostic ignored "-Wsign-conversion"
1172
 
#endif
1173
 
#endif
1174
 
          FD_CLR(proc->fd, &rfds_all); /* Spurious warning from
1175
 
                                          -Wconversion in GNU libc
1176
 
                                          before 2.16 */
1177
 
#if defined (__GNUC__) and defined (__GLIBC__)
1178
 
#if not __GLIBC_PREREQ(2, 16)
1179
 
#pragma GCC diagnostic pop
1180
 
#endif
1181
 
#endif
 
1201
          FD_CLR(proc->fd, &rfds_all);
1182
1202
          
1183
1203
          /* Block signal while modifying process_list */
1184
1204
          ret = (int)TEMP_FAILURE_RETRY(sigprocmask
1224
1244
      }
1225
1245
      
1226
1246
      /* This process has not completed.  Does it have any output? */
1227
 
#if defined (__GNUC__) and defined (__GLIBC__)
1228
 
#if not __GLIBC_PREREQ(2, 16)
1229
 
#pragma GCC diagnostic push
1230
 
#pragma GCC diagnostic ignored "-Wsign-conversion"
1231
 
#endif
1232
 
#endif
1233
 
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){ /* Spurious
1234
 
                                                         warning from
1235
 
                                                         -Wconversion
1236
 
                                                         in GNU libc
1237
 
                                                         before
1238
 
                                                         2.16 */
1239
 
#if defined (__GNUC__) and defined (__GLIBC__)
1240
 
#if not __GLIBC_PREREQ(2, 16)
1241
 
#pragma GCC diagnostic pop
1242
 
#endif
1243
 
#endif
 
1247
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){
1244
1248
        /* This process had nothing to say at this time */
1245
1249
        proc = proc->next;
1246
1250
        continue;
1313
1317
    free(custom_argv);
1314
1318
  }
1315
1319
  
1316
 
  if(dir != NULL){
1317
 
    closedir(dir);
 
1320
  free(direntries);
 
1321
  
 
1322
  if(dir_fd != -1){
 
1323
    close(dir_fd);
1318
1324
  }
1319
1325
  
1320
1326
  /* Kill the processes */
1340
1346
  free_plugin_list();
1341
1347
  
1342
1348
  free(plugindir);
 
1349
  free(pluginhelperdir);
1343
1350
  free(argfile);
1344
1351
  
1345
1352
  return exitstatus;