/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: 2015-05-23 20:18:34 UTC
  • mto: This revision was merged to the branch mainline in revision 756.
  • Revision ID: teddy@recompile.se-20150523201834-e89ex4ito93yni8x
mandos: Use multiprocessing module to run checkers.

For a long time, the Mandos server has occasionally logged the message
"ERROR: Child process vanished".  This was never a fatal error, but it
has been annoying and slightly worrying, since a definite cause was
not found.  One potential cause could be the "multiprocessing" and
"subprocess" modules conflicting w.r.t. SIGCHLD.  To avoid this,
change the running of checkers from using subprocess.Popen
asynchronously to instead first create a multiprocessing.Process()
(which is asynchronous) calling a function, and have that function
then call subprocess.call() (which is synchronous).  In this way, the
only thing using any asynchronous subprocesses is the multiprocessing
module.

This makes it necessary to change one small thing in the D-Bus API,
since the subprocesses.call() function does not expose the raw wait(2)
status value.

DBUS-API (CheckerCompleted): Change the second value provided by this
                             D-Bus signal from the raw wait(2) status
                             to the actual terminating signal number.
mandos (subprocess_call_pipe): New function to be called by
                               multiprocessing.Process (starting a
                               separate process).
(Client.last_checker signal): New attribute for signal which
                              terminated last checker.  Like
                              last_checker_status, only not accessible
                              via D-Bus.
(Client.checker_callback): Take new "connection" argument and use it
                           to get returncode; set last_checker_signal.
                           Return False so gobject does not call this
                           callback again.
(Client.start_checker): Start checker using a multiprocessing.Process
                        instead of a subprocess.Popen.
(ClientDBus.checker_callback): Take new "connection" argument.        Call
                               Client.checker_callback early to have
                               it set last_checker_status and
                               last_checker_signal; use those.  Change
                               second value provided to D-Bus signal
                               CheckerCompleted to use
                               last_checker_signal if checker was
                               terminated by signal.
mandos-monitor: Update to reflect DBus API change.
(MandosClientWidget.checker_completed): Take "signal" instead of
                                        "condition" argument.  Use it
                                        accordingly.  Remove dead code
                                        (os.WCOREDUMP case).

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-2012 Teddy Hogeborn
6
 
 * Copyright © 2008-2012 Björn Påhlsson
 
5
 * Copyright © 2008-2014 Teddy Hogeborn
 
6
 * Copyright © 2008-2014 Björn Påhlsson
7
7
 * 
8
8
 * This program is free software: you can redistribute it and/or
9
9
 * modify it under the terms of the GNU General Public License as
23
23
 */
24
24
 
25
25
#define _GNU_SOURCE             /* TEMP_FAILURE_RETRY(), getline(),
26
 
                                   asprintf(), O_CLOEXEC */
 
26
                                   O_CLOEXEC, pipe2() */
27
27
#include <stddef.h>             /* size_t, NULL */
28
28
#include <stdlib.h>             /* malloc(), exit(), EXIT_SUCCESS,
29
29
                                   realloc() */
30
30
#include <stdbool.h>            /* bool, true, false */
31
31
#include <stdio.h>              /* fileno(), fprintf(),
32
 
                                   stderr, STDOUT_FILENO */
33
 
#include <sys/types.h>          /* DIR, fdopendir(), stat(), struct
34
 
                                   stat, waitpid(), WIFEXITED(),
35
 
                                   WEXITSTATUS(), wait(), pid_t,
36
 
                                   uid_t, gid_t, getuid(), getgid(),
37
 
                                   dirfd() */
 
32
                                   stderr, STDOUT_FILENO, fclose() */
 
33
#include <sys/types.h>          /* fstat(), struct stat, waitpid(),
 
34
                                   WIFEXITED(), WEXITSTATUS(), wait(),
 
35
                                   pid_t, uid_t, gid_t, getuid(),
 
36
                                   getgid() */
38
37
#include <sys/select.h>         /* fd_set, select(), FD_ZERO(),
39
38
                                   FD_SET(), FD_ISSET(), FD_CLR */
40
39
#include <sys/wait.h>           /* wait(), waitpid(), WIFEXITED(),
41
40
                                   WEXITSTATUS(), WTERMSIG(),
42
41
                                   WCOREDUMP() */
43
 
#include <sys/stat.h>           /* struct stat, stat(), S_ISREG() */
 
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() */
47
 
#include <unistd.h>             /* struct stat, stat(), S_ISREG(),
48
 
                                   fcntl(), setuid(), setgid(),
49
 
                                   F_GETFD, F_SETFD, FD_CLOEXEC,
50
 
                                   access(), pipe(), fork(), close()
51
 
                                   dup2(), STDOUT_FILENO, _exit(),
52
 
                                   execv(), write(), read(),
53
 
                                   close() */
 
44
#include <dirent.h>             /* struct dirent, scandirat() */
 
45
#include <unistd.h>             /* fcntl(), F_GETFD, F_SETFD,
 
46
                                   FD_CLOEXEC, write(), STDOUT_FILENO,
 
47
                                   struct stat, fstat(), close(),
 
48
                                   setgid(), setuid(), S_ISREG(),
 
49
                                   faccessat() pipe2(), fork(),
 
50
                                   _exit(), dup2(), fexecve(), read()
 
51
                                */
54
52
#include <fcntl.h>              /* fcntl(), F_GETFD, F_SETFD,
55
 
                                   FD_CLOEXEC */
56
 
#include <string.h>             /* strsep, strlen(), asprintf(),
57
 
                                   strsignal(), strcmp(), strncmp() */
 
53
                                   FD_CLOEXEC, openat(), scandirat(),
 
54
                                   pipe2() */
 
55
#include <string.h>             /* strsep, strlen(), strsignal(),
 
56
                                   strcmp(), strncmp() */
58
57
#include <errno.h>              /* errno */
59
58
#include <argp.h>               /* struct argp_option, struct
60
59
                                   argp_state, struct argp,
72
71
                                   EX_CONFIG, EX_UNAVAILABLE, EX_OK */
73
72
#include <errno.h>              /* errno */
74
73
#include <error.h>              /* error() */
 
74
#include <fnmatch.h>            /* fnmatch() */
75
75
 
76
76
#define BUFFER_SIZE 256
77
77
 
105
105
 
106
106
/* Gets an existing plugin based on name,
107
107
   or if none is found, creates a new one */
 
108
__attribute__((warn_unused_result))
108
109
static plugin *getplugin(char *name){
109
110
  /* Check for existing plugin with that name */
110
111
  for(plugin *p = plugin_list; p != NULL; p = p->next){
171
172
}
172
173
 
173
174
/* Helper function for add_argument and add_environment */
174
 
__attribute__((nonnull))
 
175
__attribute__((nonnull, warn_unused_result))
175
176
static bool add_to_char_array(const char *new, char ***array,
176
177
                              int *len){
177
178
  /* Resize the pointed-to array to hold one more pointer */
 
179
  char **new_array = NULL;
178
180
  do {
179
 
    *array = realloc(*array, sizeof(char *)
180
 
                     * (size_t) ((*len) + 2));
181
 
  } while(*array == NULL and errno == EINTR);
 
181
    new_array = realloc(*array, sizeof(char *)
 
182
                        * (size_t) ((*len) + 2));
 
183
  } while(new_array == NULL and errno == EINTR);
182
184
  /* Malloc check */
183
 
  if(*array == NULL){
 
185
  if(new_array == NULL){
184
186
    return false;
185
187
  }
 
188
  *array = new_array;
186
189
  /* Make a copy of the new string */
187
190
  char *copy;
188
191
  do {
200
203
}
201
204
 
202
205
/* Add to a plugin's argument vector */
203
 
__attribute__((nonnull(2)))
 
206
__attribute__((nonnull(2), warn_unused_result))
204
207
static bool add_argument(plugin *p, const char *arg){
205
208
  if(p == NULL){
206
209
    return false;
209
212
}
210
213
 
211
214
/* Add to a plugin's environment */
212
 
__attribute__((nonnull(2)))
 
215
__attribute__((nonnull(2), warn_unused_result))
213
216
static bool add_environment(plugin *p, const char *def, bool replace){
214
217
  if(p == NULL){
215
218
    return false;
217
220
  /* namelen = length of name of environment variable */
218
221
  size_t namelen = (size_t)(strchrnul(def, '=') - def);
219
222
  /* Search for this environment variable */
220
 
  for(char **e = p->environ; *e != NULL; e++){
221
 
    if(strncmp(*e, def, namelen + 1) == 0){
 
223
  for(char **envdef = p->environ; *envdef != NULL; envdef++){
 
224
    if(strncmp(*envdef, def, namelen + 1) == 0){
222
225
      /* It already exists */
223
226
      if(replace){
224
 
        char *new;
 
227
        char *new_envdef;
225
228
        do {
226
 
          new = realloc(*e, strlen(def) + 1);
227
 
        } while(new == NULL and errno == EINTR);
228
 
        if(new == NULL){
 
229
          new_envdef = realloc(*envdef, strlen(def) + 1);
 
230
        } while(new_envdef == NULL and errno == EINTR);
 
231
        if(new_envdef == NULL){
229
232
          return false;
230
233
        }
231
 
        *e = new;
232
 
        strcpy(*e, def);
 
234
        *envdef = new_envdef;
 
235
        strcpy(*envdef, def);
233
236
      }
234
237
      return true;
235
238
    }
237
240
  return add_to_char_array(def, &(p->environ), &(p->envc));
238
241
}
239
242
 
 
243
#ifndef O_CLOEXEC
240
244
/*
241
245
 * Based on the example in the GNU LibC manual chapter 13.13 "File
242
246
 * Descriptor Flags".
243
247
 | [[info:libc:Descriptor%20Flags][File Descriptor Flags]] |
244
248
 */
 
249
__attribute__((warn_unused_result))
245
250
static int set_cloexec_flag(int fd){
246
251
  int ret = (int)TEMP_FAILURE_RETRY(fcntl(fd, F_GETFD, 0));
247
252
  /* If reading the flags failed, return error indication now. */
252
257
  return (int)TEMP_FAILURE_RETRY(fcntl(fd, F_SETFD,
253
258
                                       ret | FD_CLOEXEC));
254
259
}
 
260
#endif  /* not O_CLOEXEC */
255
261
 
256
262
 
257
263
/* Mark processes as completed when they exit, and save their exit
289
295
}
290
296
 
291
297
/* Prints out a password to stdout */
292
 
__attribute__((nonnull))
 
298
__attribute__((nonnull, warn_unused_result))
293
299
static bool print_out_password(const char *buffer, size_t length){
294
300
  ssize_t ret;
295
301
  for(size_t written = 0; written < length; written += (size_t)ret){
343
349
  char *plugindir = NULL;
344
350
  char *argfile = NULL;
345
351
  FILE *conffp;
346
 
  size_t d_name_len;
347
 
  DIR *dir = NULL;
348
 
  struct dirent *dirst;
 
352
  struct dirent **direntries = NULL;
349
353
  struct stat st;
350
354
  fd_set rfds_all;
351
355
  int ret, maxfd = 0;
359
363
                                      .sa_flags = SA_NOCLDSTOP };
360
364
  char **custom_argv = NULL;
361
365
  int custom_argc = 0;
 
366
  int dir_fd = -1;
362
367
  
363
368
  /* Establish a signal handler */
364
369
  sigemptyset(&sigchld_action.sa_mask);
435
440
            break;
436
441
          }
437
442
        }
 
443
        errno = 0;
438
444
      }
439
445
      break;
440
446
    case 'G':                   /* --global-env */
441
 
      add_environment(getplugin(NULL), arg, true);
 
447
      if(add_environment(getplugin(NULL), arg, true)){
 
448
        errno = 0;
 
449
      }
442
450
      break;
443
451
    case 'o':                   /* --options-for */
444
452
      {
461
469
            break;
462
470
          }
463
471
        }
 
472
        errno = 0;
464
473
      }
465
474
      break;
466
475
    case 'E':                   /* --env-for */
478
487
          errno = EINVAL;
479
488
          break;
480
489
        }
481
 
        add_environment(getplugin(arg), envdef, true);
 
490
        if(add_environment(getplugin(arg), envdef, true)){
 
491
          errno = 0;
 
492
        }
482
493
      }
483
494
      break;
484
495
    case 'd':                   /* --disable */
486
497
        plugin *p = getplugin(arg);
487
498
        if(p != NULL){
488
499
          p->disabled = true;
 
500
          errno = 0;
489
501
        }
490
502
      }
491
503
      break;
494
506
        plugin *p = getplugin(arg);
495
507
        if(p != NULL){
496
508
          p->disabled = false;
 
509
          errno = 0;
497
510
        }
498
511
      }
499
512
      break;
500
513
    case 128:                   /* --plugin-dir */
501
514
      free(plugindir);
502
515
      plugindir = strdup(arg);
 
516
      if(plugindir != NULL){
 
517
        errno = 0;
 
518
      }
503
519
      break;
504
520
    case 129:                   /* --config-file */
505
521
      /* This is already done by parse_opt_config_file() */
513
529
        break;
514
530
      }
515
531
      uid = (uid_t)tmp_id;
 
532
      errno = 0;
516
533
      break;
517
534
    case 131:                   /* --groupid */
518
535
      tmp_id = strtoimax(arg, &tmp, 10);
523
540
        break;
524
541
      }
525
542
      gid = (gid_t)tmp_id;
 
543
      errno = 0;
526
544
      break;
527
545
    case 132:                   /* --debug */
528
546
      debug = true;
576
594
    case 129:                   /* --config-file */
577
595
      free(argfile);
578
596
      argfile = strdup(arg);
 
597
      if(argfile != NULL){
 
598
        errno = 0;
 
599
      }
579
600
      break;
580
601
    case 130:                   /* --userid */
581
602
    case 131:                   /* --groupid */
664
685
        }
665
686
        
666
687
        custom_argc += 1;
667
 
        custom_argv = realloc(custom_argv, sizeof(char *)
668
 
                              * ((unsigned int) custom_argc + 1));
669
 
        if(custom_argv == NULL){
670
 
          error(0, errno, "realloc");
671
 
          exitstatus = EX_OSERR;
672
 
          free(org_line);
673
 
          goto fallback;
 
688
        {
 
689
          char **new_argv = realloc(custom_argv, sizeof(char *)
 
690
                                    * ((unsigned int)
 
691
                                       custom_argc + 1));
 
692
          if(new_argv == NULL){
 
693
            error(0, errno, "realloc");
 
694
            exitstatus = EX_OSERR;
 
695
            free(new_arg);
 
696
            free(org_line);
 
697
            goto fallback;
 
698
          } else {
 
699
            custom_argv = new_argv;
 
700
          }
674
701
        }
675
702
        custom_argv[custom_argc-1] = new_arg;
676
703
        custom_argv[custom_argc] = NULL;
753
780
       <http://bugs.debian.org/633582> */
754
781
    int plugindir_fd = open(/* plugindir or */ PDIR, O_RDONLY);
755
782
    if(plugindir_fd == -1){
756
 
      error(0, errno, "open");
 
783
      if(errno != ENOENT){
 
784
        error(0, errno, "open(\"" PDIR "\")");
 
785
      }
757
786
    } else {
758
787
      ret = (int)TEMP_FAILURE_RETRY(fstat(plugindir_fd, &st));
759
788
      if(ret == -1){
771
800
  }
772
801
  
773
802
  /* Lower permissions */
774
 
  setgid(gid);
 
803
  ret = setgid(gid);
775
804
  if(ret == -1){
776
805
    error(0, errno, "setgid");
777
806
  }
782
811
  
783
812
  /* Open plugin directory with close_on_exec flag */
784
813
  {
785
 
    int dir_fd = -1;
786
 
    if(plugindir == NULL){
787
 
      dir_fd = open(PDIR, O_RDONLY |
788
 
#ifdef O_CLOEXEC
789
 
                    O_CLOEXEC
790
 
#else  /* not O_CLOEXEC */
791
 
                    0
792
 
#endif  /* not O_CLOEXEC */
793
 
                    );
794
 
    } else {
795
 
      dir_fd = open(plugindir, O_RDONLY |
796
 
#ifdef O_CLOEXEC
797
 
                    O_CLOEXEC
798
 
#else  /* not O_CLOEXEC */
799
 
                    0
800
 
#endif  /* not O_CLOEXEC */
801
 
                    );
802
 
    }
 
814
    dir_fd = open(plugindir != NULL ? plugindir : PDIR, O_RDONLY |
 
815
#ifdef O_CLOEXEC
 
816
                  O_CLOEXEC
 
817
#else  /* not O_CLOEXEC */
 
818
                  0
 
819
#endif  /* not O_CLOEXEC */
 
820
                  );
803
821
    if(dir_fd == -1){
804
822
      error(0, errno, "Could not open plugin dir");
805
823
      exitstatus = EX_UNAVAILABLE;
811
829
    ret = set_cloexec_flag(dir_fd);
812
830
    if(ret < 0){
813
831
      error(0, errno, "set_cloexec_flag");
814
 
      TEMP_FAILURE_RETRY(close(dir_fd));
815
832
      exitstatus = EX_OSERR;
816
833
      goto fallback;
817
834
    }
818
835
#endif  /* O_CLOEXEC */
819
 
    
820
 
    dir = fdopendir(dir_fd);
821
 
    if(dir == NULL){
822
 
      error(0, errno, "Could not open plugin dir");
823
 
      TEMP_FAILURE_RETRY(close(dir_fd));
824
 
      exitstatus = EX_OSERR;
825
 
      goto fallback;
 
836
  }
 
837
  
 
838
  int good_name(const struct dirent * const dirent){
 
839
    const char * const patterns[] = { ".*", "#*#", "*~", "*.dpkg-new",
 
840
                                      "*.dpkg-old", "*.dpkg-bak",
 
841
                                      "*.dpkg-divert", NULL };
 
842
#ifdef __GNUC__
 
843
#pragma GCC diagnostic push
 
844
#pragma GCC diagnostic ignored "-Wcast-qual"
 
845
#endif
 
846
    for(const char **pat = (const char **)patterns;
 
847
        *pat != NULL; pat++){
 
848
#ifdef __GNUC__
 
849
#pragma GCC diagnostic pop
 
850
#endif
 
851
      if(fnmatch(*pat, dirent->d_name, FNM_FILE_NAME | FNM_PERIOD)
 
852
         != FNM_NOMATCH){
 
853
        if(debug){
 
854
            fprintf(stderr, "Ignoring plugin dir entry \"%s\""
 
855
                    " matching pattern %s\n", dirent->d_name, *pat);
 
856
        }
 
857
        return 0;
 
858
      }
826
859
    }
 
860
    return 1;
 
861
  }
 
862
  
 
863
#ifdef __GLIBC__
 
864
#if __GLIBC_PREREQ(2, 15)
 
865
  int numplugins = scandirat(dir_fd, ".", &direntries, good_name,
 
866
                             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
  if(numplugins == -1){
 
876
    error(0, errno, "Could not scan plugin dir");
 
877
    direntries = NULL;
 
878
    exitstatus = EX_OSERR;
 
879
    goto fallback;
827
880
  }
828
881
  
829
882
  FD_ZERO(&rfds_all);
830
883
  
831
884
  /* Read and execute any executable in the plugin directory*/
832
 
  while(true){
833
 
    do {
834
 
      dirst = readdir(dir);
835
 
    } while(dirst == NULL and errno == EINTR);
836
 
    
837
 
    /* All directory entries have been processed */
838
 
    if(dirst == NULL){
839
 
      if(errno == EBADF){
840
 
        error(0, errno, "readdir");
841
 
        exitstatus = EX_IOERR;
842
 
        goto fallback;
843
 
      }
844
 
      break;
845
 
    }
846
 
    
847
 
    d_name_len = strlen(dirst->d_name);
848
 
    
849
 
    /* Ignore dotfiles, backup files and other junk */
850
 
    {
851
 
      bool bad_name = false;
852
 
      
853
 
      const char const *bad_prefixes[] = { ".", "#", NULL };
854
 
      
855
 
      const char const *bad_suffixes[] = { "~", "#", ".dpkg-new",
856
 
                                           ".dpkg-old",
857
 
                                           ".dpkg-bak",
858
 
                                           ".dpkg-divert", NULL };
859
 
      for(const char **pre = bad_prefixes; *pre != NULL; pre++){
860
 
        size_t pre_len = strlen(*pre);
861
 
        if((d_name_len >= pre_len)
862
 
           and strncmp((dirst->d_name), *pre, pre_len) == 0){
863
 
          if(debug){
864
 
            fprintf(stderr, "Ignoring plugin dir entry \"%s\""
865
 
                    " with bad prefix %s\n", dirst->d_name, *pre);
866
 
          }
867
 
          bad_name = true;
868
 
          break;
869
 
        }
870
 
      }
871
 
      if(bad_name){
872
 
        continue;
873
 
      }
874
 
      for(const char **suf = bad_suffixes; *suf != NULL; suf++){
875
 
        size_t suf_len = strlen(*suf);
876
 
        if((d_name_len >= suf_len)
877
 
           and (strcmp((dirst->d_name) + d_name_len-suf_len, *suf)
878
 
                == 0)){
879
 
          if(debug){
880
 
            fprintf(stderr, "Ignoring plugin dir entry \"%s\""
881
 
                    " with bad suffix %s\n", dirst->d_name, *suf);
882
 
          }
883
 
          bad_name = true;
884
 
          break;
885
 
        }
886
 
      }
887
 
      
888
 
      if(bad_name){
889
 
        continue;
890
 
      }
891
 
    }
892
 
    
893
 
    char *filename;
894
 
    if(plugindir == NULL){
895
 
      ret = (int)TEMP_FAILURE_RETRY(asprintf(&filename, PDIR "/%s",
896
 
                                             dirst->d_name));
897
 
    } else {
898
 
      ret = (int)TEMP_FAILURE_RETRY(asprintf(&filename, "%s/%s",
899
 
                                             plugindir,
900
 
                                             dirst->d_name));
901
 
    }
902
 
    if(ret < 0){
903
 
      error(0, errno, "asprintf");
 
885
  for(int i = 0; i < numplugins; i++){
 
886
    
 
887
    int plugin_fd = openat(dir_fd, direntries[i]->d_name, O_RDONLY);
 
888
    if(plugin_fd == -1){
 
889
      error(0, errno, "Could not open plugin");
 
890
      free(direntries[i]);
904
891
      continue;
905
892
    }
906
 
    
907
 
    ret = (int)TEMP_FAILURE_RETRY(stat(filename, &st));
 
893
    ret = (int)TEMP_FAILURE_RETRY(fstat(plugin_fd, &st));
908
894
    if(ret == -1){
909
895
      error(0, errno, "stat");
910
 
      free(filename);
 
896
      TEMP_FAILURE_RETRY(close(plugin_fd));
 
897
      free(direntries[i]);
911
898
      continue;
912
899
    }
913
900
    
914
901
    /* Ignore non-executable files */
915
902
    if(not S_ISREG(st.st_mode)
916
 
       or (TEMP_FAILURE_RETRY(access(filename, X_OK)) != 0)){
 
903
       or (TEMP_FAILURE_RETRY(faccessat(dir_fd, direntries[i]->d_name,
 
904
                                        X_OK, 0)) != 0)){
917
905
      if(debug){
918
 
        fprintf(stderr, "Ignoring plugin dir entry \"%s\""
919
 
                " with bad type or mode\n", filename);
 
906
        fprintf(stderr, "Ignoring plugin dir entry \"%s/%s\""
 
907
                " with bad type or mode\n",
 
908
                plugindir != NULL ? plugindir : PDIR,
 
909
                direntries[i]->d_name);
920
910
      }
921
 
      free(filename);
 
911
      TEMP_FAILURE_RETRY(close(plugin_fd));
 
912
      free(direntries[i]);
922
913
      continue;
923
914
    }
924
915
    
925
 
    plugin *p = getplugin(dirst->d_name);
 
916
    plugin *p = getplugin(direntries[i]->d_name);
926
917
    if(p == NULL){
927
918
      error(0, errno, "getplugin");
928
 
      free(filename);
 
919
      TEMP_FAILURE_RETRY(close(plugin_fd));
 
920
      free(direntries[i]);
929
921
      continue;
930
922
    }
931
923
    if(p->disabled){
932
924
      if(debug){
933
925
        fprintf(stderr, "Ignoring disabled plugin \"%s\"\n",
934
 
                dirst->d_name);
 
926
                direntries[i]->d_name);
935
927
      }
936
 
      free(filename);
 
928
      TEMP_FAILURE_RETRY(close(plugin_fd));
 
929
      free(direntries[i]);
937
930
      continue;
938
931
    }
939
932
    {
953
946
        }
954
947
      }
955
948
    }
956
 
    /* If this plugin has any environment variables, we will call
957
 
       using execve and need to duplicate the environment from this
958
 
       process, too. */
 
949
    /* If this plugin has any environment variables, we need to
 
950
       duplicate the environment from this process, too. */
959
951
    if(p->environ[0] != NULL){
960
952
      for(char **e = environ; *e != NULL; e++){
961
953
        if(not add_environment(p, *e, false)){
965
957
    }
966
958
    
967
959
    int pipefd[2];
 
960
#ifndef O_CLOEXEC
968
961
    ret = (int)TEMP_FAILURE_RETRY(pipe(pipefd));
 
962
#else  /* O_CLOEXEC */
 
963
    ret = (int)TEMP_FAILURE_RETRY(pipe2(pipefd, O_CLOEXEC));
 
964
#endif  /* O_CLOEXEC */
969
965
    if(ret == -1){
970
966
      error(0, errno, "pipe");
971
967
      exitstatus = EX_OSERR;
972
 
      goto fallback;
973
 
    }
 
968
      free(direntries[i]);
 
969
      goto fallback;
 
970
    }
 
971
    if(pipefd[0] >= FD_SETSIZE){
 
972
      fprintf(stderr, "pipe()[0] (%d) >= FD_SETSIZE (%d)", pipefd[0],
 
973
              FD_SETSIZE);
 
974
      TEMP_FAILURE_RETRY(close(pipefd[0]));
 
975
      TEMP_FAILURE_RETRY(close(pipefd[1]));
 
976
      exitstatus = EX_OSERR;
 
977
      free(direntries[i]);
 
978
      goto fallback;
 
979
    }
 
980
#ifndef O_CLOEXEC
974
981
    /* Ask OS to automatic close the pipe on exec */
975
982
    ret = set_cloexec_flag(pipefd[0]);
976
983
    if(ret < 0){
977
984
      error(0, errno, "set_cloexec_flag");
 
985
      TEMP_FAILURE_RETRY(close(pipefd[0]));
 
986
      TEMP_FAILURE_RETRY(close(pipefd[1]));
978
987
      exitstatus = EX_OSERR;
 
988
      free(direntries[i]);
979
989
      goto fallback;
980
990
    }
981
991
    ret = set_cloexec_flag(pipefd[1]);
982
992
    if(ret < 0){
983
993
      error(0, errno, "set_cloexec_flag");
 
994
      TEMP_FAILURE_RETRY(close(pipefd[0]));
 
995
      TEMP_FAILURE_RETRY(close(pipefd[1]));
984
996
      exitstatus = EX_OSERR;
 
997
      free(direntries[i]);
985
998
      goto fallback;
986
999
    }
 
1000
#endif  /* not O_CLOEXEC */
987
1001
    /* Block SIGCHLD until process is safely in process list */
988
1002
    ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_BLOCK,
989
1003
                                              &sigchld_action.sa_mask,
991
1005
    if(ret < 0){
992
1006
      error(0, errno, "sigprocmask");
993
1007
      exitstatus = EX_OSERR;
 
1008
      free(direntries[i]);
994
1009
      goto fallback;
995
1010
    }
996
1011
    /* Starting a new process to be watched */
1000
1015
    } while(pid == -1 and errno == EINTR);
1001
1016
    if(pid == -1){
1002
1017
      error(0, errno, "fork");
 
1018
      TEMP_FAILURE_RETRY(sigprocmask(SIG_UNBLOCK,
 
1019
                                     &sigchld_action.sa_mask, NULL));
 
1020
      TEMP_FAILURE_RETRY(close(pipefd[0]));
 
1021
      TEMP_FAILURE_RETRY(close(pipefd[1]));
1003
1022
      exitstatus = EX_OSERR;
 
1023
      free(direntries[i]);
1004
1024
      goto fallback;
1005
1025
    }
1006
1026
    if(pid == 0){
1022
1042
        _exit(EX_OSERR);
1023
1043
      }
1024
1044
      
1025
 
      if(dirfd(dir) < 0){
1026
 
        /* If dir has no file descriptor, we could not set FD_CLOEXEC
1027
 
           above and must now close it manually here. */
1028
 
        closedir(dir);
1029
 
      }
1030
 
      if(p->environ[0] == NULL){
1031
 
        if(execv(filename, p->argv) < 0){
1032
 
          error(0, errno, "execv for %s", filename);
1033
 
          _exit(EX_OSERR);
1034
 
        }
1035
 
      } else {
1036
 
        if(execve(filename, p->argv, p->environ) < 0){
1037
 
          error(0, errno, "execve for %s", filename);
1038
 
          _exit(EX_OSERR);
1039
 
        }
 
1045
      if(fexecve(plugin_fd, p->argv,
 
1046
                (p->environ[0] != NULL) ? p->environ : environ) < 0){
 
1047
        error(0, errno, "fexecve for %s/%s",
 
1048
              plugindir != NULL ? plugindir : PDIR,
 
1049
              direntries[i]->d_name);
 
1050
        _exit(EX_OSERR);
1040
1051
      }
1041
1052
      /* no return */
1042
1053
    }
1043
1054
    /* Parent process */
1044
1055
    TEMP_FAILURE_RETRY(close(pipefd[1])); /* Close unused write end of
1045
1056
                                             pipe */
1046
 
    free(filename);
1047
 
    plugin *new_plugin = getplugin(dirst->d_name);
 
1057
    TEMP_FAILURE_RETRY(close(plugin_fd));
 
1058
    plugin *new_plugin = getplugin(direntries[i]->d_name);
1048
1059
    if(new_plugin == NULL){
1049
1060
      error(0, errno, "getplugin");
1050
1061
      ret = (int)(TEMP_FAILURE_RETRY
1054
1065
        error(0, errno, "sigprocmask");
1055
1066
      }
1056
1067
      exitstatus = EX_OSERR;
 
1068
      free(direntries[i]);
1057
1069
      goto fallback;
1058
1070
    }
 
1071
    free(direntries[i]);
1059
1072
    
1060
1073
    new_plugin->pid = pid;
1061
1074
    new_plugin->fd = pipefd[0];
1071
1084
      goto fallback;
1072
1085
    }
1073
1086
    
 
1087
#if defined (__GNUC__) and defined (__GLIBC__)
 
1088
#if not __GLIBC_PREREQ(2, 16)
 
1089
#pragma GCC diagnostic push
 
1090
#pragma GCC diagnostic ignored "-Wsign-conversion"
 
1091
#endif
 
1092
#endif
1074
1093
    FD_SET(new_plugin->fd, &rfds_all); /* Spurious warning from
1075
 
                                          -Wconversion */
 
1094
                                          -Wconversion in GNU libc
 
1095
                                          before 2.16 */
 
1096
#if defined (__GNUC__) and defined (__GLIBC__)
 
1097
#if not __GLIBC_PREREQ(2, 16)
 
1098
#pragma GCC diagnostic pop
 
1099
#endif
 
1100
#endif
1076
1101
    
1077
1102
    if(maxfd < new_plugin->fd){
1078
1103
      maxfd = new_plugin->fd;
1079
1104
    }
1080
1105
  }
1081
1106
  
1082
 
  TEMP_FAILURE_RETRY(closedir(dir));
1083
 
  dir = NULL;
 
1107
  free(direntries);
 
1108
  direntries = NULL;
 
1109
  TEMP_FAILURE_RETRY(close(dir_fd));
 
1110
  dir_fd = -1;
1084
1111
  free_plugin(getplugin(NULL));
1085
1112
  
1086
1113
  for(plugin *p = plugin_list; p != NULL; p = p->next){
1132
1159
          }
1133
1160
          
1134
1161
          /* Remove the plugin */
 
1162
#if defined (__GNUC__) and defined (__GLIBC__)
 
1163
#if not __GLIBC_PREREQ(2, 16)
 
1164
#pragma GCC diagnostic push
 
1165
#pragma GCC diagnostic ignored "-Wsign-conversion"
 
1166
#endif
 
1167
#endif
1135
1168
          FD_CLR(proc->fd, &rfds_all); /* Spurious warning from
1136
 
                                          -Wconversion */
 
1169
                                          -Wconversion in GNU libc
 
1170
                                          before 2.16 */
 
1171
#if defined (__GNUC__) and defined (__GLIBC__)
 
1172
#if not __GLIBC_PREREQ(2, 16)
 
1173
#pragma GCC diagnostic pop
 
1174
#endif
 
1175
#endif
1137
1176
          
1138
1177
          /* Block signal while modifying process_list */
1139
1178
          ret = (int)TEMP_FAILURE_RETRY(sigprocmask
1179
1218
      }
1180
1219
      
1181
1220
      /* This process has not completed.  Does it have any output? */
 
1221
#if defined (__GNUC__) and defined (__GLIBC__)
 
1222
#if not __GLIBC_PREREQ(2, 16)
 
1223
#pragma GCC diagnostic push
 
1224
#pragma GCC diagnostic ignored "-Wsign-conversion"
 
1225
#endif
 
1226
#endif
1182
1227
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){ /* Spurious
1183
1228
                                                         warning from
1184
 
                                                         -Wconversion */
 
1229
                                                         -Wconversion
 
1230
                                                         in GNU libc
 
1231
                                                         before
 
1232
                                                         2.16 */
 
1233
#if defined (__GNUC__) and defined (__GLIBC__)
 
1234
#if not __GLIBC_PREREQ(2, 16)
 
1235
#pragma GCC diagnostic pop
 
1236
#endif
 
1237
#endif
1185
1238
        /* This process had nothing to say at this time */
1186
1239
        proc = proc->next;
1187
1240
        continue;
1188
1241
      }
1189
1242
      /* Before reading, make the process' data buffer large enough */
1190
1243
      if(proc->buffer_length + BUFFER_SIZE > proc->buffer_size){
1191
 
        proc->buffer = realloc(proc->buffer, proc->buffer_size
1192
 
                               + (size_t) BUFFER_SIZE);
1193
 
        if(proc->buffer == NULL){
 
1244
        char *new_buffer = realloc(proc->buffer, proc->buffer_size
 
1245
                                   + (size_t) BUFFER_SIZE);
 
1246
        if(new_buffer == NULL){
1194
1247
          error(0, errno, "malloc");
1195
1248
          exitstatus = EX_OSERR;
1196
1249
          goto fallback;
1197
1250
        }
 
1251
        proc->buffer = new_buffer;
1198
1252
        proc->buffer_size += BUFFER_SIZE;
1199
1253
      }
1200
1254
      /* Read from the process */
1253
1307
    free(custom_argv);
1254
1308
  }
1255
1309
  
1256
 
  if(dir != NULL){
1257
 
    closedir(dir);
 
1310
  free(direntries);
 
1311
  
 
1312
  if(dir_fd != -1){
 
1313
    TEMP_FAILURE_RETRY(close(dir_fd));
1258
1314
  }
1259
1315
  
1260
1316
  /* Kill the processes */