/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: 2019-07-29 16:35:53 UTC
  • mto: This revision was merged to the branch mainline in revision 384.
  • Revision ID: teddy@recompile.se-20190729163553-1i442i2cbx64c537
Make tests and man page examples match

Make the tests test_manual_page_example[1-5] match exactly what is
written in the manual page, and add comments to manual page as
reminders to keep tests and manual page examples in sync.

* mandos-ctl (Test_commands_from_options.test_manual_page_example_1):
  Remove "--verbose" option, since the manual does not have it as the
  first example, and change assertion to match.
* mandos-ctl.xml (EXAMPLE): Add comments to all examples documenting
  which test function they correspond to.  Also remove unnecessary
  quotes from option arguments in fourth example, and clarify language
  slightly in fifth example.

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
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
 
                                   asprintf(), O_CLOEXEC */
 
27
                                   O_CLOEXEC, pipe2() */
27
28
#include <stddef.h>             /* size_t, NULL */
28
29
#include <stdlib.h>             /* malloc(), exit(), EXIT_SUCCESS,
29
30
                                   realloc() */
30
31
#include <stdbool.h>            /* bool, true, false */
31
32
#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() */
 
33
                                   stderr, STDOUT_FILENO, fclose() */
 
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() */
43
 
#include <sys/stat.h>           /* struct stat, stat(), S_ISREG() */
 
41
                                   WEXITSTATUS(), WTERMSIG() */
 
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
 
78
78
#define PDIR "/lib/mandos/plugins.d"
 
79
#define PHDIR "/lib/mandos/plugin-helpers"
79
80
#define AFILE "/conf/conf.d/mandos/plugin-runner.conf"
80
81
 
81
82
const char *argp_program_version = "plugin-runner " VERSION;
105
106
 
106
107
/* Gets an existing plugin based on name,
107
108
   or if none is found, creates a new one */
 
109
__attribute__((warn_unused_result))
108
110
static plugin *getplugin(char *name){
109
111
  /* Check for existing plugin with that name */
110
112
  for(plugin *p = plugin_list; p != NULL; p = p->next){
171
173
}
172
174
 
173
175
/* Helper function for add_argument and add_environment */
174
 
__attribute__((nonnull))
 
176
__attribute__((nonnull, warn_unused_result))
175
177
static bool add_to_char_array(const char *new, char ***array,
176
178
                              int *len){
177
179
  /* Resize the pointed-to array to hold one more pointer */
 
180
  char **new_array = NULL;
178
181
  do {
179
 
    *array = realloc(*array, sizeof(char *)
180
 
                     * (size_t) ((*len) + 2));
181
 
  } while(*array == NULL and errno == EINTR);
 
182
    new_array = realloc(*array, sizeof(char *)
 
183
                        * (size_t) ((*len) + 2));
 
184
  } while(new_array == NULL and errno == EINTR);
182
185
  /* Malloc check */
183
 
  if(*array == NULL){
 
186
  if(new_array == NULL){
184
187
    return false;
185
188
  }
 
189
  *array = new_array;
186
190
  /* Make a copy of the new string */
187
191
  char *copy;
188
192
  do {
200
204
}
201
205
 
202
206
/* Add to a plugin's argument vector */
203
 
__attribute__((nonnull(2)))
 
207
__attribute__((nonnull(2), warn_unused_result))
204
208
static bool add_argument(plugin *p, const char *arg){
205
209
  if(p == NULL){
206
210
    return false;
209
213
}
210
214
 
211
215
/* Add to a plugin's environment */
212
 
__attribute__((nonnull(2)))
 
216
__attribute__((nonnull(2), warn_unused_result))
213
217
static bool add_environment(plugin *p, const char *def, bool replace){
214
218
  if(p == NULL){
215
219
    return false;
217
221
  /* namelen = length of name of environment variable */
218
222
  size_t namelen = (size_t)(strchrnul(def, '=') - def);
219
223
  /* Search for this environment variable */
220
 
  for(char **e = p->environ; *e != NULL; e++){
221
 
    if(strncmp(*e, def, namelen + 1) == 0){
 
224
  for(char **envdef = p->environ; *envdef != NULL; envdef++){
 
225
    if(strncmp(*envdef, def, namelen + 1) == 0){
222
226
      /* It already exists */
223
227
      if(replace){
224
 
        char *new;
 
228
        char *new_envdef;
225
229
        do {
226
 
          new = realloc(*e, strlen(def) + 1);
227
 
        } while(new == NULL and errno == EINTR);
228
 
        if(new == NULL){
 
230
          new_envdef = realloc(*envdef, strlen(def) + 1);
 
231
        } while(new_envdef == NULL and errno == EINTR);
 
232
        if(new_envdef == NULL){
229
233
          return false;
230
234
        }
231
 
        *e = new;
232
 
        strcpy(*e, def);
 
235
        *envdef = new_envdef;
 
236
        strcpy(*envdef, def);
233
237
      }
234
238
      return true;
235
239
    }
237
241
  return add_to_char_array(def, &(p->environ), &(p->envc));
238
242
}
239
243
 
 
244
#ifndef O_CLOEXEC
240
245
/*
241
246
 * Based on the example in the GNU LibC manual chapter 13.13 "File
242
247
 * Descriptor Flags".
243
248
 | [[info:libc:Descriptor%20Flags][File Descriptor Flags]] |
244
249
 */
 
250
__attribute__((warn_unused_result))
245
251
static int set_cloexec_flag(int fd){
246
252
  int ret = (int)TEMP_FAILURE_RETRY(fcntl(fd, F_GETFD, 0));
247
253
  /* If reading the flags failed, return error indication now. */
252
258
  return (int)TEMP_FAILURE_RETRY(fcntl(fd, F_SETFD,
253
259
                                       ret | FD_CLOEXEC));
254
260
}
 
261
#endif  /* not O_CLOEXEC */
255
262
 
256
263
 
257
264
/* Mark processes as completed when they exit, and save their exit
289
296
}
290
297
 
291
298
/* Prints out a password to stdout */
292
 
__attribute__((nonnull))
 
299
__attribute__((nonnull, warn_unused_result))
293
300
static bool print_out_password(const char *buffer, size_t length){
294
301
  ssize_t ret;
295
302
  for(size_t written = 0; written < length; written += (size_t)ret){
306
313
__attribute__((nonnull))
307
314
static void free_plugin(plugin *plugin_node){
308
315
  
309
 
  for(char **arg = plugin_node->argv; *arg != NULL; arg++){
 
316
  for(char **arg = (plugin_node->argv)+1; *arg != NULL; arg++){
310
317
    free(*arg);
311
318
  }
 
319
  free(plugin_node->name);
312
320
  free(plugin_node->argv);
313
321
  for(char **env = plugin_node->environ; *env != NULL; env++){
314
322
    free(*env);
341
349
 
342
350
int main(int argc, char *argv[]){
343
351
  char *plugindir = NULL;
 
352
  char *pluginhelperdir = NULL;
344
353
  char *argfile = NULL;
345
354
  FILE *conffp;
346
 
  size_t d_name_len;
347
 
  DIR *dir = NULL;
348
 
  struct dirent *dirst;
 
355
  struct dirent **direntries = NULL;
349
356
  struct stat st;
350
357
  fd_set rfds_all;
351
358
  int ret, maxfd = 0;
359
366
                                      .sa_flags = SA_NOCLDSTOP };
360
367
  char **custom_argv = NULL;
361
368
  int custom_argc = 0;
 
369
  int dir_fd = -1;
362
370
  
363
371
  /* Establish a signal handler */
364
372
  sigemptyset(&sigchld_action.sa_mask);
409
417
      .doc = "Group ID the plugins will run as", .group = 3 },
410
418
    { .name = "debug", .key = 132,
411
419
      .doc = "Debug mode", .group = 4 },
 
420
    { .name = "plugin-helper-dir", .key = 133,
 
421
      .arg = "DIRECTORY",
 
422
      .doc = "Specify a different plugin helper directory",
 
423
      .group = 2 },
412
424
    /*
413
425
     * These reproduce what we would get without ARGP_NO_HELP
414
426
     */
435
447
            break;
436
448
          }
437
449
        }
 
450
        errno = 0;
438
451
      }
439
452
      break;
440
453
    case 'G':                   /* --global-env */
441
 
      add_environment(getplugin(NULL), arg, true);
 
454
      if(add_environment(getplugin(NULL), arg, true)){
 
455
        errno = 0;
 
456
      }
442
457
      break;
443
458
    case 'o':                   /* --options-for */
444
459
      {
461
476
            break;
462
477
          }
463
478
        }
 
479
        errno = 0;
464
480
      }
465
481
      break;
466
482
    case 'E':                   /* --env-for */
478
494
          errno = EINVAL;
479
495
          break;
480
496
        }
481
 
        add_environment(getplugin(arg), envdef, true);
 
497
        if(add_environment(getplugin(arg), envdef, true)){
 
498
          errno = 0;
 
499
        }
482
500
      }
483
501
      break;
484
502
    case 'd':                   /* --disable */
486
504
        plugin *p = getplugin(arg);
487
505
        if(p != NULL){
488
506
          p->disabled = true;
 
507
          errno = 0;
489
508
        }
490
509
      }
491
510
      break;
494
513
        plugin *p = getplugin(arg);
495
514
        if(p != NULL){
496
515
          p->disabled = false;
 
516
          errno = 0;
497
517
        }
498
518
      }
499
519
      break;
500
520
    case 128:                   /* --plugin-dir */
501
521
      free(plugindir);
502
522
      plugindir = strdup(arg);
 
523
      if(plugindir != NULL){
 
524
        errno = 0;
 
525
      }
503
526
      break;
504
527
    case 129:                   /* --config-file */
505
528
      /* This is already done by parse_opt_config_file() */
513
536
        break;
514
537
      }
515
538
      uid = (uid_t)tmp_id;
 
539
      errno = 0;
516
540
      break;
517
541
    case 131:                   /* --groupid */
518
542
      tmp_id = strtoimax(arg, &tmp, 10);
523
547
        break;
524
548
      }
525
549
      gid = (gid_t)tmp_id;
 
550
      errno = 0;
526
551
      break;
527
552
    case 132:                   /* --debug */
528
553
      debug = true;
529
554
      break;
 
555
    case 133:                   /* --plugin-helper-dir */
 
556
      free(pluginhelperdir);
 
557
      pluginhelperdir = strdup(arg);
 
558
      if(pluginhelperdir != NULL){
 
559
        errno = 0;
 
560
      }
 
561
      break;
530
562
      /*
531
563
       * These reproduce what we would get without ARGP_NO_HELP
532
564
       */
533
565
    case '?':                   /* --help */
534
566
      state->flags &= ~(unsigned int)ARGP_NO_EXIT; /* force exit */
535
567
      argp_state_help(state, state->out_stream, ARGP_HELP_STD_HELP);
 
568
      __builtin_unreachable();
536
569
    case -3:                    /* --usage */
537
570
      state->flags &= ~(unsigned int)ARGP_NO_EXIT; /* force exit */
538
571
      argp_state_help(state, state->out_stream,
539
572
                      ARGP_HELP_USAGE | ARGP_HELP_EXIT_OK);
 
573
      __builtin_unreachable();
540
574
    case 'V':                   /* --version */
541
575
      fprintf(state->out_stream, "%s\n", argp_program_version);
542
576
      exit(EXIT_SUCCESS);
552
586
      if(arg[0] == '\0'){
553
587
        break;
554
588
      }
 
589
#if __GNUC__ >= 7
 
590
      __attribute__((fallthrough));
 
591
#else
 
592
          /* FALLTHROUGH */
 
593
#endif
555
594
    default:
556
595
      return ARGP_ERR_UNKNOWN;
557
596
    }
576
615
    case 129:                   /* --config-file */
577
616
      free(argfile);
578
617
      argfile = strdup(arg);
 
618
      if(argfile != NULL){
 
619
        errno = 0;
 
620
      }
579
621
      break;
580
622
    case 130:                   /* --userid */
581
623
    case 131:                   /* --groupid */
582
624
    case 132:                   /* --debug */
 
625
    case 133:                   /* --plugin-helper-dir */
583
626
    case '?':                   /* --help */
584
627
    case -3:                    /* --usage */
585
628
    case 'V':                   /* --version */
664
707
        }
665
708
        
666
709
        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;
 
710
        {
 
711
          char **new_argv = realloc(custom_argv, sizeof(char *)
 
712
                                    * ((size_t)custom_argc + 1));
 
713
          if(new_argv == NULL){
 
714
            error(0, errno, "realloc");
 
715
            exitstatus = EX_OSERR;
 
716
            free(new_arg);
 
717
            free(org_line);
 
718
            goto fallback;
 
719
          } else {
 
720
            custom_argv = new_argv;
 
721
          }
674
722
        }
675
723
        custom_argv[custom_argc-1] = new_arg;
676
724
        custom_argv[custom_argc] = NULL;
734
782
    goto fallback;
735
783
  }
736
784
  
 
785
  {
 
786
    char *pluginhelperenv;
 
787
    bool bret = true;
 
788
    ret = asprintf(&pluginhelperenv, "MANDOSPLUGINHELPERDIR=%s",
 
789
                   pluginhelperdir != NULL ? pluginhelperdir : PHDIR);
 
790
    if(ret != -1){
 
791
      bret = add_environment(getplugin(NULL), pluginhelperenv, true);
 
792
    }
 
793
    if(ret == -1 or not bret){
 
794
      error(0, errno, "Failed to set MANDOSPLUGINHELPERDIR"
 
795
            " environment variable to \"%s\" for all plugins\n",
 
796
            pluginhelperdir != NULL ? pluginhelperdir : PHDIR);
 
797
    }
 
798
    if(ret != -1){
 
799
      free(pluginhelperenv);
 
800
    }
 
801
  }
 
802
  
737
803
  if(debug){
738
 
    for(plugin *p = plugin_list; p != NULL; p=p->next){
 
804
    for(plugin *p = plugin_list; p != NULL; p = p->next){
739
805
      fprintf(stderr, "Plugin: %s has %d arguments\n",
740
806
              p->name ? p->name : "Global", p->argc - 1);
741
807
      for(char **a = p->argv; *a != NULL; a++){
750
816
  
751
817
  if(getuid() == 0){
752
818
    /* Work around Debian bug #633582:
753
 
       <http://bugs.debian.org/633582> */
 
819
       <https://bugs.debian.org/633582> */
754
820
    int plugindir_fd = open(/* plugindir or */ PDIR, O_RDONLY);
755
821
    if(plugindir_fd == -1){
756
 
      error(0, errno, "open");
 
822
      if(errno != ENOENT){
 
823
        error(0, errno, "open(\"" PDIR "\")");
 
824
      }
757
825
    } else {
758
826
      ret = (int)TEMP_FAILURE_RETRY(fstat(plugindir_fd, &st));
759
827
      if(ret == -1){
766
834
          }
767
835
        }
768
836
      }
769
 
      TEMP_FAILURE_RETRY(close(plugindir_fd));
 
837
      close(plugindir_fd);
770
838
    }
771
839
  }
772
840
  
773
841
  /* Lower permissions */
774
 
  setgid(gid);
 
842
  ret = setgid(gid);
775
843
  if(ret == -1){
776
844
    error(0, errno, "setgid");
777
845
  }
782
850
  
783
851
  /* Open plugin directory with close_on_exec flag */
784
852
  {
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
 
    }
 
853
    dir_fd = open(plugindir != NULL ? plugindir : PDIR, O_RDONLY |
 
854
#ifdef O_CLOEXEC
 
855
                  O_CLOEXEC
 
856
#else  /* not O_CLOEXEC */
 
857
                  0
 
858
#endif  /* not O_CLOEXEC */
 
859
                  );
803
860
    if(dir_fd == -1){
804
861
      error(0, errno, "Could not open plugin dir");
805
862
      exitstatus = EX_UNAVAILABLE;
811
868
    ret = set_cloexec_flag(dir_fd);
812
869
    if(ret < 0){
813
870
      error(0, errno, "set_cloexec_flag");
814
 
      TEMP_FAILURE_RETRY(close(dir_fd));
815
871
      exitstatus = EX_OSERR;
816
872
      goto fallback;
817
873
    }
818
874
#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;
 
875
  }
 
876
  
 
877
  int good_name(const struct dirent * const dirent){
 
878
    const char * const patterns[] = { ".*", "#*#", "*~", "*.dpkg-new",
 
879
                                      "*.dpkg-old", "*.dpkg-bak",
 
880
                                      "*.dpkg-divert", NULL };
 
881
#ifdef __GNUC__
 
882
#pragma GCC diagnostic push
 
883
#pragma GCC diagnostic ignored "-Wcast-qual"
 
884
#endif
 
885
    for(const char **pat = (const char **)patterns;
 
886
        *pat != NULL; pat++){
 
887
#ifdef __GNUC__
 
888
#pragma GCC diagnostic pop
 
889
#endif
 
890
      if(fnmatch(*pat, dirent->d_name, FNM_FILE_NAME | FNM_PERIOD)
 
891
         != FNM_NOMATCH){
 
892
        if(debug){
 
893
            fprintf(stderr, "Ignoring plugin dir entry \"%s\""
 
894
                    " matching pattern %s\n", dirent->d_name, *pat);
 
895
        }
 
896
        return 0;
 
897
      }
826
898
    }
 
899
    return 1;
 
900
  }
 
901
  
 
902
  int numplugins = scandirat(dir_fd, ".", &direntries, good_name,
 
903
                             alphasort);
 
904
  if(numplugins == -1){
 
905
    error(0, errno, "Could not scan plugin dir");
 
906
    direntries = NULL;
 
907
    exitstatus = EX_OSERR;
 
908
    goto fallback;
827
909
  }
828
910
  
829
911
  FD_ZERO(&rfds_all);
830
912
  
831
913
  /* 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");
 
914
  for(int i = 0; i < numplugins; i++){
 
915
    
 
916
    int plugin_fd = openat(dir_fd, direntries[i]->d_name, O_RDONLY);
 
917
    if(plugin_fd == -1){
 
918
      error(0, errno, "Could not open plugin");
 
919
      free(direntries[i]);
904
920
      continue;
905
921
    }
906
 
    
907
 
    ret = (int)TEMP_FAILURE_RETRY(stat(filename, &st));
 
922
    ret = (int)TEMP_FAILURE_RETRY(fstat(plugin_fd, &st));
908
923
    if(ret == -1){
909
924
      error(0, errno, "stat");
910
 
      free(filename);
 
925
      close(plugin_fd);
 
926
      free(direntries[i]);
911
927
      continue;
912
928
    }
913
929
    
914
930
    /* Ignore non-executable files */
915
931
    if(not S_ISREG(st.st_mode)
916
 
       or (TEMP_FAILURE_RETRY(access(filename, X_OK)) != 0)){
 
932
       or (TEMP_FAILURE_RETRY(faccessat(dir_fd, direntries[i]->d_name,
 
933
                                        X_OK, 0)) != 0)){
917
934
      if(debug){
918
 
        fprintf(stderr, "Ignoring plugin dir entry \"%s\""
919
 
                " with bad type or mode\n", filename);
 
935
        fprintf(stderr, "Ignoring plugin dir entry \"%s/%s\""
 
936
                " with bad type or mode\n",
 
937
                plugindir != NULL ? plugindir : PDIR,
 
938
                direntries[i]->d_name);
920
939
      }
921
 
      free(filename);
 
940
      close(plugin_fd);
 
941
      free(direntries[i]);
922
942
      continue;
923
943
    }
924
944
    
925
 
    plugin *p = getplugin(dirst->d_name);
 
945
    plugin *p = getplugin(direntries[i]->d_name);
926
946
    if(p == NULL){
927
947
      error(0, errno, "getplugin");
928
 
      free(filename);
 
948
      close(plugin_fd);
 
949
      free(direntries[i]);
929
950
      continue;
930
951
    }
931
952
    if(p->disabled){
932
953
      if(debug){
933
954
        fprintf(stderr, "Ignoring disabled plugin \"%s\"\n",
934
 
                dirst->d_name);
 
955
                direntries[i]->d_name);
935
956
      }
936
 
      free(filename);
 
957
      close(plugin_fd);
 
958
      free(direntries[i]);
937
959
      continue;
938
960
    }
939
961
    {
953
975
        }
954
976
      }
955
977
    }
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. */
 
978
    /* If this plugin has any environment variables, we need to
 
979
       duplicate the environment from this process, too. */
959
980
    if(p->environ[0] != NULL){
960
981
      for(char **e = environ; *e != NULL; e++){
961
982
        if(not add_environment(p, *e, false)){
965
986
    }
966
987
    
967
988
    int pipefd[2];
 
989
#ifndef O_CLOEXEC
968
990
    ret = (int)TEMP_FAILURE_RETRY(pipe(pipefd));
 
991
#else  /* O_CLOEXEC */
 
992
    ret = (int)TEMP_FAILURE_RETRY(pipe2(pipefd, O_CLOEXEC));
 
993
#endif  /* O_CLOEXEC */
969
994
    if(ret == -1){
970
995
      error(0, errno, "pipe");
971
996
      exitstatus = EX_OSERR;
972
 
      goto fallback;
973
 
    }
 
997
      free(direntries[i]);
 
998
      goto fallback;
 
999
    }
 
1000
    if(pipefd[0] >= FD_SETSIZE){
 
1001
      fprintf(stderr, "pipe()[0] (%d) >= FD_SETSIZE (%d)", pipefd[0],
 
1002
              FD_SETSIZE);
 
1003
      close(pipefd[0]);
 
1004
      close(pipefd[1]);
 
1005
      exitstatus = EX_OSERR;
 
1006
      free(direntries[i]);
 
1007
      goto fallback;
 
1008
    }
 
1009
#ifndef O_CLOEXEC
974
1010
    /* Ask OS to automatic close the pipe on exec */
975
1011
    ret = set_cloexec_flag(pipefd[0]);
976
1012
    if(ret < 0){
977
1013
      error(0, errno, "set_cloexec_flag");
 
1014
      close(pipefd[0]);
 
1015
      close(pipefd[1]);
978
1016
      exitstatus = EX_OSERR;
 
1017
      free(direntries[i]);
979
1018
      goto fallback;
980
1019
    }
981
1020
    ret = set_cloexec_flag(pipefd[1]);
982
1021
    if(ret < 0){
983
1022
      error(0, errno, "set_cloexec_flag");
 
1023
      close(pipefd[0]);
 
1024
      close(pipefd[1]);
984
1025
      exitstatus = EX_OSERR;
 
1026
      free(direntries[i]);
985
1027
      goto fallback;
986
1028
    }
 
1029
#endif  /* not O_CLOEXEC */
987
1030
    /* Block SIGCHLD until process is safely in process list */
988
1031
    ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_BLOCK,
989
1032
                                              &sigchld_action.sa_mask,
991
1034
    if(ret < 0){
992
1035
      error(0, errno, "sigprocmask");
993
1036
      exitstatus = EX_OSERR;
 
1037
      free(direntries[i]);
994
1038
      goto fallback;
995
1039
    }
996
1040
    /* Starting a new process to be watched */
1000
1044
    } while(pid == -1 and errno == EINTR);
1001
1045
    if(pid == -1){
1002
1046
      error(0, errno, "fork");
 
1047
      TEMP_FAILURE_RETRY(sigprocmask(SIG_UNBLOCK,
 
1048
                                     &sigchld_action.sa_mask, NULL));
 
1049
      close(pipefd[0]);
 
1050
      close(pipefd[1]);
1003
1051
      exitstatus = EX_OSERR;
 
1052
      free(direntries[i]);
1004
1053
      goto fallback;
1005
1054
    }
1006
1055
    if(pid == 0){
1022
1071
        _exit(EX_OSERR);
1023
1072
      }
1024
1073
      
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
 
        }
 
1074
      if(fexecve(plugin_fd, p->argv,
 
1075
                (p->environ[0] != NULL) ? p->environ : environ) < 0){
 
1076
        error(0, errno, "fexecve for %s/%s",
 
1077
              plugindir != NULL ? plugindir : PDIR,
 
1078
              direntries[i]->d_name);
 
1079
        _exit(EX_OSERR);
1040
1080
      }
1041
1081
      /* no return */
1042
1082
    }
1043
1083
    /* Parent process */
1044
 
    TEMP_FAILURE_RETRY(close(pipefd[1])); /* Close unused write end of
1045
 
                                             pipe */
1046
 
    free(filename);
1047
 
    plugin *new_plugin = getplugin(dirst->d_name);
 
1084
    close(pipefd[1]);           /* Close unused write end of pipe */
 
1085
    close(plugin_fd);
 
1086
    plugin *new_plugin = getplugin(direntries[i]->d_name);
1048
1087
    if(new_plugin == NULL){
1049
1088
      error(0, errno, "getplugin");
1050
1089
      ret = (int)(TEMP_FAILURE_RETRY
1054
1093
        error(0, errno, "sigprocmask");
1055
1094
      }
1056
1095
      exitstatus = EX_OSERR;
 
1096
      free(direntries[i]);
1057
1097
      goto fallback;
1058
1098
    }
 
1099
    free(direntries[i]);
1059
1100
    
1060
1101
    new_plugin->pid = pid;
1061
1102
    new_plugin->fd = pipefd[0];
1062
 
    
 
1103
 
 
1104
    if(debug){
 
1105
      fprintf(stderr, "Plugin %s started (PID %" PRIdMAX ")\n",
 
1106
              new_plugin->name, (intmax_t) (new_plugin->pid));
 
1107
    }
 
1108
 
1063
1109
    /* Unblock SIGCHLD so signal handler can be run if this process
1064
1110
       has already completed */
1065
1111
    ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_UNBLOCK,
1071
1117
      goto fallback;
1072
1118
    }
1073
1119
    
1074
 
    FD_SET(new_plugin->fd, &rfds_all); /* Spurious warning from
1075
 
                                          -Wconversion */
 
1120
    FD_SET(new_plugin->fd, &rfds_all);
1076
1121
    
1077
1122
    if(maxfd < new_plugin->fd){
1078
1123
      maxfd = new_plugin->fd;
1079
1124
    }
1080
1125
  }
1081
1126
  
1082
 
  TEMP_FAILURE_RETRY(closedir(dir));
1083
 
  dir = NULL;
 
1127
  free(direntries);
 
1128
  direntries = NULL;
 
1129
  close(dir_fd);
 
1130
  dir_fd = -1;
1084
1131
  free_plugin(getplugin(NULL));
1085
1132
  
1086
1133
  for(plugin *p = plugin_list; p != NULL; p = p->next){
1125
1172
                      (intmax_t) (proc->pid),
1126
1173
                      WTERMSIG(proc->status),
1127
1174
                      strsignal(WTERMSIG(proc->status)));
1128
 
            } else if(WCOREDUMP(proc->status)){
1129
 
              fprintf(stderr, "Plugin %s [%" PRIdMAX "] dumped"
1130
 
                      " core\n", proc->name, (intmax_t) (proc->pid));
1131
1175
            }
1132
1176
          }
1133
1177
          
1134
1178
          /* Remove the plugin */
1135
 
          FD_CLR(proc->fd, &rfds_all); /* Spurious warning from
1136
 
                                          -Wconversion */
 
1179
          FD_CLR(proc->fd, &rfds_all);
1137
1180
          
1138
1181
          /* Block signal while modifying process_list */
1139
1182
          ret = (int)TEMP_FAILURE_RETRY(sigprocmask
1179
1222
      }
1180
1223
      
1181
1224
      /* This process has not completed.  Does it have any output? */
1182
 
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){ /* Spurious
1183
 
                                                         warning from
1184
 
                                                         -Wconversion */
 
1225
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){
1185
1226
        /* This process had nothing to say at this time */
1186
1227
        proc = proc->next;
1187
1228
        continue;
1188
1229
      }
1189
1230
      /* Before reading, make the process' data buffer large enough */
1190
1231
      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){
 
1232
        char *new_buffer = realloc(proc->buffer, proc->buffer_size
 
1233
                                   + (size_t) BUFFER_SIZE);
 
1234
        if(new_buffer == NULL){
1194
1235
          error(0, errno, "malloc");
1195
1236
          exitstatus = EX_OSERR;
1196
1237
          goto fallback;
1197
1238
        }
 
1239
        proc->buffer = new_buffer;
1198
1240
        proc->buffer_size += BUFFER_SIZE;
1199
1241
      }
1200
1242
      /* Read from the process */
1253
1295
    free(custom_argv);
1254
1296
  }
1255
1297
  
1256
 
  if(dir != NULL){
1257
 
    closedir(dir);
 
1298
  free(direntries);
 
1299
  
 
1300
  if(dir_fd != -1){
 
1301
    close(dir_fd);
1258
1302
  }
1259
1303
  
1260
1304
  /* Kill the processes */
1280
1324
  free_plugin_list();
1281
1325
  
1282
1326
  free(plugindir);
 
1327
  free(pluginhelperdir);
1283
1328
  free(argfile);
1284
1329
  
1285
1330
  return exitstatus;