/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: 2015-05-23 20:18:34 UTC
  • mto: (237.7.304 trunk)
  • mto: This revision was merged to the branch mainline in revision 325.
  • 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-2010 Teddy Hogeborn
6
 
 * Copyright © 2008-2010 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
19
19
 * along with this program.  If not, see
20
20
 * <http://www.gnu.org/licenses/>.
21
21
 * 
22
 
 * Contact the authors at <mandos@fukt.bsnet.se>.
 
22
 * Contact the authors at <mandos@recompile.se>.
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
 
79
79
#define AFILE "/conf/conf.d/mandos/plugin-runner.conf"
80
80
 
81
81
const char *argp_program_version = "plugin-runner " VERSION;
82
 
const char *argp_program_bug_address = "<mandos@fukt.bsnet.se>";
 
82
const char *argp_program_bug_address = "<mandos@recompile.se>";
83
83
 
84
84
typedef struct plugin{
85
85
  char *name;                   /* can be NULL or any plugin name */
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 */
 
175
__attribute__((nonnull, warn_unused_result))
174
176
static bool add_to_char_array(const char *new, char ***array,
175
177
                              int *len){
176
178
  /* Resize the pointed-to array to hold one more pointer */
 
179
  char **new_array = NULL;
177
180
  do {
178
 
    *array = realloc(*array, sizeof(char *)
179
 
                     * (size_t) ((*len) + 2));
180
 
  } 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);
181
184
  /* Malloc check */
182
 
  if(*array == NULL){
 
185
  if(new_array == NULL){
183
186
    return false;
184
187
  }
 
188
  *array = new_array;
185
189
  /* Make a copy of the new string */
186
190
  char *copy;
187
191
  do {
199
203
}
200
204
 
201
205
/* Add to a plugin's argument vector */
 
206
__attribute__((nonnull(2), warn_unused_result))
202
207
static bool add_argument(plugin *p, const char *arg){
203
208
  if(p == NULL){
204
209
    return false;
207
212
}
208
213
 
209
214
/* Add to a plugin's environment */
 
215
__attribute__((nonnull(2), warn_unused_result))
210
216
static bool add_environment(plugin *p, const char *def, bool replace){
211
217
  if(p == NULL){
212
218
    return false;
214
220
  /* namelen = length of name of environment variable */
215
221
  size_t namelen = (size_t)(strchrnul(def, '=') - def);
216
222
  /* Search for this environment variable */
217
 
  for(char **e = p->environ; *e != NULL; e++){
218
 
    if(strncmp(*e, def, namelen + 1) == 0){
 
223
  for(char **envdef = p->environ; *envdef != NULL; envdef++){
 
224
    if(strncmp(*envdef, def, namelen + 1) == 0){
219
225
      /* It already exists */
220
226
      if(replace){
221
 
        char *new;
 
227
        char *new_envdef;
222
228
        do {
223
 
          new = realloc(*e, strlen(def) + 1);
224
 
        } while(new == NULL and errno == EINTR);
225
 
        if(new == NULL){
 
229
          new_envdef = realloc(*envdef, strlen(def) + 1);
 
230
        } while(new_envdef == NULL and errno == EINTR);
 
231
        if(new_envdef == NULL){
226
232
          return false;
227
233
        }
228
 
        *e = new;
229
 
        strcpy(*e, def);
 
234
        *envdef = new_envdef;
 
235
        strcpy(*envdef, def);
230
236
      }
231
237
      return true;
232
238
    }
234
240
  return add_to_char_array(def, &(p->environ), &(p->envc));
235
241
}
236
242
 
 
243
#ifndef O_CLOEXEC
237
244
/*
238
245
 * Based on the example in the GNU LibC manual chapter 13.13 "File
239
246
 * Descriptor Flags".
240
247
 | [[info:libc:Descriptor%20Flags][File Descriptor Flags]] |
241
248
 */
 
249
__attribute__((warn_unused_result))
242
250
static int set_cloexec_flag(int fd){
243
251
  int ret = (int)TEMP_FAILURE_RETRY(fcntl(fd, F_GETFD, 0));
244
252
  /* If reading the flags failed, return error indication now. */
249
257
  return (int)TEMP_FAILURE_RETRY(fcntl(fd, F_SETFD,
250
258
                                       ret | FD_CLOEXEC));
251
259
}
 
260
#endif  /* not O_CLOEXEC */
252
261
 
253
262
 
254
263
/* Mark processes as completed when they exit, and save their exit
286
295
}
287
296
 
288
297
/* Prints out a password to stdout */
 
298
__attribute__((nonnull, warn_unused_result))
289
299
static bool print_out_password(const char *buffer, size_t length){
290
300
  ssize_t ret;
291
301
  for(size_t written = 0; written < length; written += (size_t)ret){
299
309
}
300
310
 
301
311
/* Removes and free a plugin from the plugin list */
 
312
__attribute__((nonnull))
302
313
static void free_plugin(plugin *plugin_node){
303
314
  
304
315
  for(char **arg = plugin_node->argv; *arg != NULL; arg++){
338
349
  char *plugindir = NULL;
339
350
  char *argfile = NULL;
340
351
  FILE *conffp;
341
 
  size_t d_name_len;
342
 
  DIR *dir = NULL;
343
 
  struct dirent *dirst;
 
352
  struct dirent **direntries = NULL;
344
353
  struct stat st;
345
354
  fd_set rfds_all;
346
355
  int ret, maxfd = 0;
354
363
                                      .sa_flags = SA_NOCLDSTOP };
355
364
  char **custom_argv = NULL;
356
365
  int custom_argc = 0;
 
366
  int dir_fd = -1;
357
367
  
358
368
  /* Establish a signal handler */
359
369
  sigemptyset(&sigchld_action.sa_mask);
416
426
    { .name = NULL }
417
427
  };
418
428
  
 
429
  __attribute__((nonnull(3)))
419
430
  error_t parse_opt(int key, char *arg, struct argp_state *state){
420
431
    errno = 0;
421
432
    switch(key){
422
433
      char *tmp;
423
 
      intmax_t tmpmax;
 
434
      intmax_t tmp_id;
424
435
    case 'g':                   /* --global-options */
425
436
      {
426
437
        char *plugin_option;
429
440
            break;
430
441
          }
431
442
        }
 
443
        errno = 0;
432
444
      }
433
445
      break;
434
446
    case 'G':                   /* --global-env */
435
 
      add_environment(getplugin(NULL), arg, true);
 
447
      if(add_environment(getplugin(NULL), arg, true)){
 
448
        errno = 0;
 
449
      }
436
450
      break;
437
451
    case 'o':                   /* --options-for */
438
452
      {
455
469
            break;
456
470
          }
457
471
        }
 
472
        errno = 0;
458
473
      }
459
474
      break;
460
475
    case 'E':                   /* --env-for */
472
487
          errno = EINVAL;
473
488
          break;
474
489
        }
475
 
        add_environment(getplugin(arg), envdef, true);
 
490
        if(add_environment(getplugin(arg), envdef, true)){
 
491
          errno = 0;
 
492
        }
476
493
      }
477
494
      break;
478
495
    case 'd':                   /* --disable */
480
497
        plugin *p = getplugin(arg);
481
498
        if(p != NULL){
482
499
          p->disabled = true;
 
500
          errno = 0;
483
501
        }
484
502
      }
485
503
      break;
488
506
        plugin *p = getplugin(arg);
489
507
        if(p != NULL){
490
508
          p->disabled = false;
 
509
          errno = 0;
491
510
        }
492
511
      }
493
512
      break;
494
513
    case 128:                   /* --plugin-dir */
495
514
      free(plugindir);
496
515
      plugindir = strdup(arg);
 
516
      if(plugindir != NULL){
 
517
        errno = 0;
 
518
      }
497
519
      break;
498
520
    case 129:                   /* --config-file */
499
521
      /* This is already done by parse_opt_config_file() */
500
522
      break;
501
523
    case 130:                   /* --userid */
502
 
      tmpmax = strtoimax(arg, &tmp, 10);
 
524
      tmp_id = strtoimax(arg, &tmp, 10);
503
525
      if(errno != 0 or tmp == arg or *tmp != '\0'
504
 
         or tmpmax != (uid_t)tmpmax){
 
526
         or tmp_id != (uid_t)tmp_id){
505
527
        argp_error(state, "Bad user ID number: \"%s\", using %"
506
528
                   PRIdMAX, arg, (intmax_t)uid);
507
529
        break;
508
530
      }
509
 
      uid = (uid_t)tmpmax;
 
531
      uid = (uid_t)tmp_id;
 
532
      errno = 0;
510
533
      break;
511
534
    case 131:                   /* --groupid */
512
 
      tmpmax = strtoimax(arg, &tmp, 10);
 
535
      tmp_id = strtoimax(arg, &tmp, 10);
513
536
      if(errno != 0 or tmp == arg or *tmp != '\0'
514
 
         or tmpmax != (gid_t)tmpmax){
 
537
         or tmp_id != (gid_t)tmp_id){
515
538
        argp_error(state, "Bad group ID number: \"%s\", using %"
516
539
                   PRIdMAX, arg, (intmax_t)gid);
517
540
        break;
518
541
      }
519
 
      gid = (gid_t)tmpmax;
 
542
      gid = (gid_t)tmp_id;
 
543
      errno = 0;
520
544
      break;
521
545
    case 132:                   /* --debug */
522
546
      debug = true;
570
594
    case 129:                   /* --config-file */
571
595
      free(argfile);
572
596
      argfile = strdup(arg);
 
597
      if(argfile != NULL){
 
598
        errno = 0;
 
599
      }
573
600
      break;
574
601
    case 130:                   /* --userid */
575
602
    case 131:                   /* --groupid */
658
685
        }
659
686
        
660
687
        custom_argc += 1;
661
 
        custom_argv = realloc(custom_argv, sizeof(char *)
662
 
                              * ((unsigned int) custom_argc + 1));
663
 
        if(custom_argv == NULL){
664
 
          error(0, errno, "realloc");
665
 
          exitstatus = EX_OSERR;
666
 
          free(org_line);
667
 
          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
          }
668
701
        }
669
702
        custom_argv[custom_argc-1] = new_arg;
670
703
        custom_argv[custom_argc] = NULL;
742
775
    }
743
776
  }
744
777
  
745
 
  {
 
778
  if(getuid() == 0){
746
779
    /* Work around Debian bug #633582:
747
780
       <http://bugs.debian.org/633582> */
748
781
    int plugindir_fd = open(/* plugindir or */ PDIR, O_RDONLY);
749
782
    if(plugindir_fd == -1){
750
 
      error(0, errno, "open");
 
783
      if(errno != ENOENT){
 
784
        error(0, errno, "open(\"" PDIR "\")");
 
785
      }
751
786
    } else {
752
787
      ret = (int)TEMP_FAILURE_RETRY(fstat(plugindir_fd, &st));
753
788
      if(ret == -1){
765
800
  }
766
801
  
767
802
  /* Lower permissions */
768
 
  setgid(gid);
 
803
  ret = setgid(gid);
769
804
  if(ret == -1){
770
805
    error(0, errno, "setgid");
771
806
  }
776
811
  
777
812
  /* Open plugin directory with close_on_exec flag */
778
813
  {
779
 
    int dir_fd = -1;
780
 
    if(plugindir == NULL){
781
 
      dir_fd = open(PDIR, O_RDONLY |
782
 
#ifdef O_CLOEXEC
783
 
                    O_CLOEXEC
784
 
#else  /* not O_CLOEXEC */
785
 
                    0
786
 
#endif  /* not O_CLOEXEC */
787
 
                    );
788
 
    } else {
789
 
      dir_fd = open(plugindir, O_RDONLY |
790
 
#ifdef O_CLOEXEC
791
 
                    O_CLOEXEC
792
 
#else  /* not O_CLOEXEC */
793
 
                    0
794
 
#endif  /* not O_CLOEXEC */
795
 
                    );
796
 
    }
 
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
                  );
797
821
    if(dir_fd == -1){
798
822
      error(0, errno, "Could not open plugin dir");
799
823
      exitstatus = EX_UNAVAILABLE;
805
829
    ret = set_cloexec_flag(dir_fd);
806
830
    if(ret < 0){
807
831
      error(0, errno, "set_cloexec_flag");
808
 
      TEMP_FAILURE_RETRY(close(dir_fd));
809
832
      exitstatus = EX_OSERR;
810
833
      goto fallback;
811
834
    }
812
835
#endif  /* O_CLOEXEC */
813
 
    
814
 
    dir = fdopendir(dir_fd);
815
 
    if(dir == NULL){
816
 
      error(0, errno, "Could not open plugin dir");
817
 
      TEMP_FAILURE_RETRY(close(dir_fd));
818
 
      exitstatus = EX_OSERR;
819
 
      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
      }
820
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;
821
880
  }
822
881
  
823
882
  FD_ZERO(&rfds_all);
824
883
  
825
884
  /* Read and execute any executable in the plugin directory*/
826
 
  while(true){
827
 
    do {
828
 
      dirst = readdir(dir);
829
 
    } while(dirst == NULL and errno == EINTR);
830
 
    
831
 
    /* All directory entries have been processed */
832
 
    if(dirst == NULL){
833
 
      if(errno == EBADF){
834
 
        error(0, errno, "readdir");
835
 
        exitstatus = EX_IOERR;
836
 
        goto fallback;
837
 
      }
838
 
      break;
839
 
    }
840
 
    
841
 
    d_name_len = strlen(dirst->d_name);
842
 
    
843
 
    /* Ignore dotfiles, backup files and other junk */
844
 
    {
845
 
      bool bad_name = false;
846
 
      
847
 
      const char const *bad_prefixes[] = { ".", "#", NULL };
848
 
      
849
 
      const char const *bad_suffixes[] = { "~", "#", ".dpkg-new",
850
 
                                           ".dpkg-old",
851
 
                                           ".dpkg-bak",
852
 
                                           ".dpkg-divert", NULL };
853
 
      for(const char **pre = bad_prefixes; *pre != NULL; pre++){
854
 
        size_t pre_len = strlen(*pre);
855
 
        if((d_name_len >= pre_len)
856
 
           and strncmp((dirst->d_name), *pre, pre_len) == 0){
857
 
          if(debug){
858
 
            fprintf(stderr, "Ignoring plugin dir entry \"%s\""
859
 
                    " with bad prefix %s\n", dirst->d_name, *pre);
860
 
          }
861
 
          bad_name = true;
862
 
          break;
863
 
        }
864
 
      }
865
 
      if(bad_name){
866
 
        continue;
867
 
      }
868
 
      for(const char **suf = bad_suffixes; *suf != NULL; suf++){
869
 
        size_t suf_len = strlen(*suf);
870
 
        if((d_name_len >= suf_len)
871
 
           and (strcmp((dirst->d_name) + d_name_len-suf_len, *suf)
872
 
                == 0)){
873
 
          if(debug){
874
 
            fprintf(stderr, "Ignoring plugin dir entry \"%s\""
875
 
                    " with bad suffix %s\n", dirst->d_name, *suf);
876
 
          }
877
 
          bad_name = true;
878
 
          break;
879
 
        }
880
 
      }
881
 
      
882
 
      if(bad_name){
883
 
        continue;
884
 
      }
885
 
    }
886
 
    
887
 
    char *filename;
888
 
    if(plugindir == NULL){
889
 
      ret = (int)TEMP_FAILURE_RETRY(asprintf(&filename, PDIR "/%s",
890
 
                                             dirst->d_name));
891
 
    } else {
892
 
      ret = (int)TEMP_FAILURE_RETRY(asprintf(&filename, "%s/%s",
893
 
                                             plugindir,
894
 
                                             dirst->d_name));
895
 
    }
896
 
    if(ret < 0){
897
 
      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]);
898
891
      continue;
899
892
    }
900
 
    
901
 
    ret = (int)TEMP_FAILURE_RETRY(stat(filename, &st));
 
893
    ret = (int)TEMP_FAILURE_RETRY(fstat(plugin_fd, &st));
902
894
    if(ret == -1){
903
895
      error(0, errno, "stat");
904
 
      free(filename);
 
896
      TEMP_FAILURE_RETRY(close(plugin_fd));
 
897
      free(direntries[i]);
905
898
      continue;
906
899
    }
907
900
    
908
901
    /* Ignore non-executable files */
909
902
    if(not S_ISREG(st.st_mode)
910
 
       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)){
911
905
      if(debug){
912
 
        fprintf(stderr, "Ignoring plugin dir entry \"%s\""
913
 
                " 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);
914
910
      }
915
 
      free(filename);
 
911
      TEMP_FAILURE_RETRY(close(plugin_fd));
 
912
      free(direntries[i]);
916
913
      continue;
917
914
    }
918
915
    
919
 
    plugin *p = getplugin(dirst->d_name);
 
916
    plugin *p = getplugin(direntries[i]->d_name);
920
917
    if(p == NULL){
921
918
      error(0, errno, "getplugin");
922
 
      free(filename);
 
919
      TEMP_FAILURE_RETRY(close(plugin_fd));
 
920
      free(direntries[i]);
923
921
      continue;
924
922
    }
925
923
    if(p->disabled){
926
924
      if(debug){
927
925
        fprintf(stderr, "Ignoring disabled plugin \"%s\"\n",
928
 
                dirst->d_name);
 
926
                direntries[i]->d_name);
929
927
      }
930
 
      free(filename);
 
928
      TEMP_FAILURE_RETRY(close(plugin_fd));
 
929
      free(direntries[i]);
931
930
      continue;
932
931
    }
933
932
    {
947
946
        }
948
947
      }
949
948
    }
950
 
    /* If this plugin has any environment variables, we will call
951
 
       using execve and need to duplicate the environment from this
952
 
       process, too. */
 
949
    /* If this plugin has any environment variables, we need to
 
950
       duplicate the environment from this process, too. */
953
951
    if(p->environ[0] != NULL){
954
952
      for(char **e = environ; *e != NULL; e++){
955
953
        if(not add_environment(p, *e, false)){
959
957
    }
960
958
    
961
959
    int pipefd[2];
 
960
#ifndef O_CLOEXEC
962
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 */
963
965
    if(ret == -1){
964
966
      error(0, errno, "pipe");
965
967
      exitstatus = EX_OSERR;
966
 
      goto fallback;
967
 
    }
 
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
968
981
    /* Ask OS to automatic close the pipe on exec */
969
982
    ret = set_cloexec_flag(pipefd[0]);
970
983
    if(ret < 0){
971
984
      error(0, errno, "set_cloexec_flag");
 
985
      TEMP_FAILURE_RETRY(close(pipefd[0]));
 
986
      TEMP_FAILURE_RETRY(close(pipefd[1]));
972
987
      exitstatus = EX_OSERR;
 
988
      free(direntries[i]);
973
989
      goto fallback;
974
990
    }
975
991
    ret = set_cloexec_flag(pipefd[1]);
976
992
    if(ret < 0){
977
993
      error(0, errno, "set_cloexec_flag");
 
994
      TEMP_FAILURE_RETRY(close(pipefd[0]));
 
995
      TEMP_FAILURE_RETRY(close(pipefd[1]));
978
996
      exitstatus = EX_OSERR;
 
997
      free(direntries[i]);
979
998
      goto fallback;
980
999
    }
 
1000
#endif  /* not O_CLOEXEC */
981
1001
    /* Block SIGCHLD until process is safely in process list */
982
1002
    ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_BLOCK,
983
1003
                                              &sigchld_action.sa_mask,
985
1005
    if(ret < 0){
986
1006
      error(0, errno, "sigprocmask");
987
1007
      exitstatus = EX_OSERR;
 
1008
      free(direntries[i]);
988
1009
      goto fallback;
989
1010
    }
990
1011
    /* Starting a new process to be watched */
994
1015
    } while(pid == -1 and errno == EINTR);
995
1016
    if(pid == -1){
996
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]));
997
1022
      exitstatus = EX_OSERR;
 
1023
      free(direntries[i]);
998
1024
      goto fallback;
999
1025
    }
1000
1026
    if(pid == 0){
1016
1042
        _exit(EX_OSERR);
1017
1043
      }
1018
1044
      
1019
 
      if(dirfd(dir) < 0){
1020
 
        /* If dir has no file descriptor, we could not set FD_CLOEXEC
1021
 
           above and must now close it manually here. */
1022
 
        closedir(dir);
1023
 
      }
1024
 
      if(p->environ[0] == NULL){
1025
 
        if(execv(filename, p->argv) < 0){
1026
 
          error(0, errno, "execv for %s", filename);
1027
 
          _exit(EX_OSERR);
1028
 
        }
1029
 
      } else {
1030
 
        if(execve(filename, p->argv, p->environ) < 0){
1031
 
          error(0, errno, "execve for %s", filename);
1032
 
          _exit(EX_OSERR);
1033
 
        }
 
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);
1034
1051
      }
1035
1052
      /* no return */
1036
1053
    }
1037
1054
    /* Parent process */
1038
1055
    TEMP_FAILURE_RETRY(close(pipefd[1])); /* Close unused write end of
1039
1056
                                             pipe */
1040
 
    free(filename);
1041
 
    plugin *new_plugin = getplugin(dirst->d_name);
 
1057
    TEMP_FAILURE_RETRY(close(plugin_fd));
 
1058
    plugin *new_plugin = getplugin(direntries[i]->d_name);
1042
1059
    if(new_plugin == NULL){
1043
1060
      error(0, errno, "getplugin");
1044
1061
      ret = (int)(TEMP_FAILURE_RETRY
1048
1065
        error(0, errno, "sigprocmask");
1049
1066
      }
1050
1067
      exitstatus = EX_OSERR;
 
1068
      free(direntries[i]);
1051
1069
      goto fallback;
1052
1070
    }
 
1071
    free(direntries[i]);
1053
1072
    
1054
1073
    new_plugin->pid = pid;
1055
1074
    new_plugin->fd = pipefd[0];
1065
1084
      goto fallback;
1066
1085
    }
1067
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
1068
1093
    FD_SET(new_plugin->fd, &rfds_all); /* Spurious warning from
1069
 
                                          -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
1070
1101
    
1071
1102
    if(maxfd < new_plugin->fd){
1072
1103
      maxfd = new_plugin->fd;
1073
1104
    }
1074
1105
  }
1075
1106
  
1076
 
  TEMP_FAILURE_RETRY(closedir(dir));
1077
 
  dir = NULL;
 
1107
  free(direntries);
 
1108
  direntries = NULL;
 
1109
  TEMP_FAILURE_RETRY(close(dir_fd));
 
1110
  dir_fd = -1;
1078
1111
  free_plugin(getplugin(NULL));
1079
1112
  
1080
1113
  for(plugin *p = plugin_list; p != NULL; p = p->next){
1126
1159
          }
1127
1160
          
1128
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
1129
1168
          FD_CLR(proc->fd, &rfds_all); /* Spurious warning from
1130
 
                                          -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
1131
1176
          
1132
1177
          /* Block signal while modifying process_list */
1133
1178
          ret = (int)TEMP_FAILURE_RETRY(sigprocmask
1173
1218
      }
1174
1219
      
1175
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
1176
1227
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){ /* Spurious
1177
1228
                                                         warning from
1178
 
                                                         -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
1179
1238
        /* This process had nothing to say at this time */
1180
1239
        proc = proc->next;
1181
1240
        continue;
1182
1241
      }
1183
1242
      /* Before reading, make the process' data buffer large enough */
1184
1243
      if(proc->buffer_length + BUFFER_SIZE > proc->buffer_size){
1185
 
        proc->buffer = realloc(proc->buffer, proc->buffer_size
1186
 
                               + (size_t) BUFFER_SIZE);
1187
 
        if(proc->buffer == NULL){
 
1244
        char *new_buffer = realloc(proc->buffer, proc->buffer_size
 
1245
                                   + (size_t) BUFFER_SIZE);
 
1246
        if(new_buffer == NULL){
1188
1247
          error(0, errno, "malloc");
1189
1248
          exitstatus = EX_OSERR;
1190
1249
          goto fallback;
1191
1250
        }
 
1251
        proc->buffer = new_buffer;
1192
1252
        proc->buffer_size += BUFFER_SIZE;
1193
1253
      }
1194
1254
      /* Read from the process */
1247
1307
    free(custom_argv);
1248
1308
  }
1249
1309
  
1250
 
  if(dir != NULL){
1251
 
    closedir(dir);
 
1310
  free(direntries);
 
1311
  
 
1312
  if(dir_fd != -1){
 
1313
    TEMP_FAILURE_RETRY(close(dir_fd));
1252
1314
  }
1253
1315
  
1254
1316
  /* Kill the processes */