/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-03-18 22:29:25 UTC
  • mto: This revision was merged to the branch mainline in revision 382.
  • Revision ID: teddy@recompile.se-20190318222925-jvhek84dgcfgj6g3
mandos-ctl: Refactor tests

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

Show diffs side-by-side

added added

removed removed

Lines of Context:
2
2
/*
3
3
 * Mandos plugin runner - Run Mandos plugins
4
4
 *
5
 
 * Copyright © 2008-2011 Teddy Hogeborn
6
 
 * Copyright © 2008-2011 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){
341
348
 
342
349
int main(int argc, char *argv[]){
343
350
  char *plugindir = NULL;
 
351
  char *pluginhelperdir = NULL;
344
352
  char *argfile = NULL;
345
353
  FILE *conffp;
346
 
  size_t d_name_len;
347
 
  DIR *dir = NULL;
348
 
  struct dirent *dirst;
 
354
  struct dirent **direntries = NULL;
349
355
  struct stat st;
350
356
  fd_set rfds_all;
351
357
  int ret, maxfd = 0;
359
365
                                      .sa_flags = SA_NOCLDSTOP };
360
366
  char **custom_argv = NULL;
361
367
  int custom_argc = 0;
 
368
  int dir_fd = -1;
362
369
  
363
370
  /* Establish a signal handler */
364
371
  sigemptyset(&sigchld_action.sa_mask);
409
416
      .doc = "Group ID the plugins will run as", .group = 3 },
410
417
    { .name = "debug", .key = 132,
411
418
      .doc = "Debug mode", .group = 4 },
 
419
    { .name = "plugin-helper-dir", .key = 133,
 
420
      .arg = "DIRECTORY",
 
421
      .doc = "Specify a different plugin helper directory",
 
422
      .group = 2 },
412
423
    /*
413
424
     * These reproduce what we would get without ARGP_NO_HELP
414
425
     */
426
437
    errno = 0;
427
438
    switch(key){
428
439
      char *tmp;
429
 
      intmax_t tmpmax;
 
440
      intmax_t tmp_id;
430
441
    case 'g':                   /* --global-options */
431
442
      {
432
443
        char *plugin_option;
435
446
            break;
436
447
          }
437
448
        }
 
449
        errno = 0;
438
450
      }
439
451
      break;
440
452
    case 'G':                   /* --global-env */
441
 
      add_environment(getplugin(NULL), arg, true);
 
453
      if(add_environment(getplugin(NULL), arg, true)){
 
454
        errno = 0;
 
455
      }
442
456
      break;
443
457
    case 'o':                   /* --options-for */
444
458
      {
461
475
            break;
462
476
          }
463
477
        }
 
478
        errno = 0;
464
479
      }
465
480
      break;
466
481
    case 'E':                   /* --env-for */
478
493
          errno = EINVAL;
479
494
          break;
480
495
        }
481
 
        add_environment(getplugin(arg), envdef, true);
 
496
        if(add_environment(getplugin(arg), envdef, true)){
 
497
          errno = 0;
 
498
        }
482
499
      }
483
500
      break;
484
501
    case 'd':                   /* --disable */
486
503
        plugin *p = getplugin(arg);
487
504
        if(p != NULL){
488
505
          p->disabled = true;
 
506
          errno = 0;
489
507
        }
490
508
      }
491
509
      break;
494
512
        plugin *p = getplugin(arg);
495
513
        if(p != NULL){
496
514
          p->disabled = false;
 
515
          errno = 0;
497
516
        }
498
517
      }
499
518
      break;
500
519
    case 128:                   /* --plugin-dir */
501
520
      free(plugindir);
502
521
      plugindir = strdup(arg);
 
522
      if(plugindir != NULL){
 
523
        errno = 0;
 
524
      }
503
525
      break;
504
526
    case 129:                   /* --config-file */
505
527
      /* This is already done by parse_opt_config_file() */
506
528
      break;
507
529
    case 130:                   /* --userid */
508
 
      tmpmax = strtoimax(arg, &tmp, 10);
 
530
      tmp_id = strtoimax(arg, &tmp, 10);
509
531
      if(errno != 0 or tmp == arg or *tmp != '\0'
510
 
         or tmpmax != (uid_t)tmpmax){
 
532
         or tmp_id != (uid_t)tmp_id){
511
533
        argp_error(state, "Bad user ID number: \"%s\", using %"
512
534
                   PRIdMAX, arg, (intmax_t)uid);
513
535
        break;
514
536
      }
515
 
      uid = (uid_t)tmpmax;
 
537
      uid = (uid_t)tmp_id;
 
538
      errno = 0;
516
539
      break;
517
540
    case 131:                   /* --groupid */
518
 
      tmpmax = strtoimax(arg, &tmp, 10);
 
541
      tmp_id = strtoimax(arg, &tmp, 10);
519
542
      if(errno != 0 or tmp == arg or *tmp != '\0'
520
 
         or tmpmax != (gid_t)tmpmax){
 
543
         or tmp_id != (gid_t)tmp_id){
521
544
        argp_error(state, "Bad group ID number: \"%s\", using %"
522
545
                   PRIdMAX, arg, (intmax_t)gid);
523
546
        break;
524
547
      }
525
 
      gid = (gid_t)tmpmax;
 
548
      gid = (gid_t)tmp_id;
 
549
      errno = 0;
526
550
      break;
527
551
    case 132:                   /* --debug */
528
552
      debug = true;
529
553
      break;
 
554
    case 133:                   /* --plugin-helper-dir */
 
555
      free(pluginhelperdir);
 
556
      pluginhelperdir = strdup(arg);
 
557
      if(pluginhelperdir != NULL){
 
558
        errno = 0;
 
559
      }
 
560
      break;
530
561
      /*
531
562
       * These reproduce what we would get without ARGP_NO_HELP
532
563
       */
533
564
    case '?':                   /* --help */
534
565
      state->flags &= ~(unsigned int)ARGP_NO_EXIT; /* force exit */
535
566
      argp_state_help(state, state->out_stream, ARGP_HELP_STD_HELP);
 
567
      __builtin_unreachable();
536
568
    case -3:                    /* --usage */
537
569
      state->flags &= ~(unsigned int)ARGP_NO_EXIT; /* force exit */
538
570
      argp_state_help(state, state->out_stream,
539
571
                      ARGP_HELP_USAGE | ARGP_HELP_EXIT_OK);
 
572
      __builtin_unreachable();
540
573
    case 'V':                   /* --version */
541
574
      fprintf(state->out_stream, "%s\n", argp_program_version);
542
575
      exit(EXIT_SUCCESS);
552
585
      if(arg[0] == '\0'){
553
586
        break;
554
587
      }
 
588
      /* FALLTHROUGH */
555
589
    default:
556
590
      return ARGP_ERR_UNKNOWN;
557
591
    }
576
610
    case 129:                   /* --config-file */
577
611
      free(argfile);
578
612
      argfile = strdup(arg);
 
613
      if(argfile != NULL){
 
614
        errno = 0;
 
615
      }
579
616
      break;
580
617
    case 130:                   /* --userid */
581
618
    case 131:                   /* --groupid */
582
619
    case 132:                   /* --debug */
 
620
    case 133:                   /* --plugin-helper-dir */
583
621
    case '?':                   /* --help */
584
622
    case -3:                    /* --usage */
585
623
    case 'V':                   /* --version */
664
702
        }
665
703
        
666
704
        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;
 
705
        {
 
706
          char **new_argv = realloc(custom_argv, sizeof(char *)
 
707
                                    * ((size_t)custom_argc + 1));
 
708
          if(new_argv == NULL){
 
709
            error(0, errno, "realloc");
 
710
            exitstatus = EX_OSERR;
 
711
            free(new_arg);
 
712
            free(org_line);
 
713
            goto fallback;
 
714
          } else {
 
715
            custom_argv = new_argv;
 
716
          }
674
717
        }
675
718
        custom_argv[custom_argc-1] = new_arg;
676
719
        custom_argv[custom_argc] = NULL;
734
777
    goto fallback;
735
778
  }
736
779
  
 
780
  {
 
781
    char *pluginhelperenv;
 
782
    bool bret = true;
 
783
    ret = asprintf(&pluginhelperenv, "MANDOSPLUGINHELPERDIR=%s",
 
784
                   pluginhelperdir != NULL ? pluginhelperdir : PHDIR);
 
785
    if(ret != -1){
 
786
      bret = add_environment(getplugin(NULL), pluginhelperenv, true);
 
787
    }
 
788
    if(ret == -1 or not bret){
 
789
      error(0, errno, "Failed to set MANDOSPLUGINHELPERDIR"
 
790
            " environment variable to \"%s\" for all plugins\n",
 
791
            pluginhelperdir != NULL ? pluginhelperdir : PHDIR);
 
792
    }
 
793
    if(ret != -1){
 
794
      free(pluginhelperenv);
 
795
    }
 
796
  }
 
797
  
737
798
  if(debug){
738
 
    for(plugin *p = plugin_list; p != NULL; p=p->next){
 
799
    for(plugin *p = plugin_list; p != NULL; p = p->next){
739
800
      fprintf(stderr, "Plugin: %s has %d arguments\n",
740
801
              p->name ? p->name : "Global", p->argc - 1);
741
802
      for(char **a = p->argv; *a != NULL; a++){
750
811
  
751
812
  if(getuid() == 0){
752
813
    /* Work around Debian bug #633582:
753
 
       <http://bugs.debian.org/633582> */
 
814
       <https://bugs.debian.org/633582> */
754
815
    int plugindir_fd = open(/* plugindir or */ PDIR, O_RDONLY);
755
816
    if(plugindir_fd == -1){
756
 
      error(0, errno, "open");
 
817
      if(errno != ENOENT){
 
818
        error(0, errno, "open(\"" PDIR "\")");
 
819
      }
757
820
    } else {
758
821
      ret = (int)TEMP_FAILURE_RETRY(fstat(plugindir_fd, &st));
759
822
      if(ret == -1){
766
829
          }
767
830
        }
768
831
      }
769
 
      TEMP_FAILURE_RETRY(close(plugindir_fd));
 
832
      close(plugindir_fd);
770
833
    }
771
834
  }
772
835
  
773
836
  /* Lower permissions */
774
 
  setgid(gid);
 
837
  ret = setgid(gid);
775
838
  if(ret == -1){
776
839
    error(0, errno, "setgid");
777
840
  }
782
845
  
783
846
  /* Open plugin directory with close_on_exec flag */
784
847
  {
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
 
    }
 
848
    dir_fd = open(plugindir != NULL ? plugindir : PDIR, O_RDONLY |
 
849
#ifdef O_CLOEXEC
 
850
                  O_CLOEXEC
 
851
#else  /* not O_CLOEXEC */
 
852
                  0
 
853
#endif  /* not O_CLOEXEC */
 
854
                  );
803
855
    if(dir_fd == -1){
804
856
      error(0, errno, "Could not open plugin dir");
805
857
      exitstatus = EX_UNAVAILABLE;
811
863
    ret = set_cloexec_flag(dir_fd);
812
864
    if(ret < 0){
813
865
      error(0, errno, "set_cloexec_flag");
814
 
      TEMP_FAILURE_RETRY(close(dir_fd));
815
866
      exitstatus = EX_OSERR;
816
867
      goto fallback;
817
868
    }
818
869
#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;
 
870
  }
 
871
  
 
872
  int good_name(const struct dirent * const dirent){
 
873
    const char * const patterns[] = { ".*", "#*#", "*~", "*.dpkg-new",
 
874
                                      "*.dpkg-old", "*.dpkg-bak",
 
875
                                      "*.dpkg-divert", NULL };
 
876
#ifdef __GNUC__
 
877
#pragma GCC diagnostic push
 
878
#pragma GCC diagnostic ignored "-Wcast-qual"
 
879
#endif
 
880
    for(const char **pat = (const char **)patterns;
 
881
        *pat != NULL; pat++){
 
882
#ifdef __GNUC__
 
883
#pragma GCC diagnostic pop
 
884
#endif
 
885
      if(fnmatch(*pat, dirent->d_name, FNM_FILE_NAME | FNM_PERIOD)
 
886
         != FNM_NOMATCH){
 
887
        if(debug){
 
888
            fprintf(stderr, "Ignoring plugin dir entry \"%s\""
 
889
                    " matching pattern %s\n", dirent->d_name, *pat);
 
890
        }
 
891
        return 0;
 
892
      }
826
893
    }
 
894
    return 1;
 
895
  }
 
896
  
 
897
  int numplugins = scandirat(dir_fd, ".", &direntries, good_name,
 
898
                             alphasort);
 
899
  if(numplugins == -1){
 
900
    error(0, errno, "Could not scan plugin dir");
 
901
    direntries = NULL;
 
902
    exitstatus = EX_OSERR;
 
903
    goto fallback;
827
904
  }
828
905
  
829
906
  FD_ZERO(&rfds_all);
830
907
  
831
908
  /* 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");
 
909
  for(int i = 0; i < numplugins; i++){
 
910
    
 
911
    int plugin_fd = openat(dir_fd, direntries[i]->d_name, O_RDONLY);
 
912
    if(plugin_fd == -1){
 
913
      error(0, errno, "Could not open plugin");
 
914
      free(direntries[i]);
904
915
      continue;
905
916
    }
906
 
    
907
 
    ret = (int)TEMP_FAILURE_RETRY(stat(filename, &st));
 
917
    ret = (int)TEMP_FAILURE_RETRY(fstat(plugin_fd, &st));
908
918
    if(ret == -1){
909
919
      error(0, errno, "stat");
910
 
      free(filename);
 
920
      close(plugin_fd);
 
921
      free(direntries[i]);
911
922
      continue;
912
923
    }
913
924
    
914
925
    /* Ignore non-executable files */
915
926
    if(not S_ISREG(st.st_mode)
916
 
       or (TEMP_FAILURE_RETRY(access(filename, X_OK)) != 0)){
 
927
       or (TEMP_FAILURE_RETRY(faccessat(dir_fd, direntries[i]->d_name,
 
928
                                        X_OK, 0)) != 0)){
917
929
      if(debug){
918
 
        fprintf(stderr, "Ignoring plugin dir entry \"%s\""
919
 
                " with bad type or mode\n", filename);
 
930
        fprintf(stderr, "Ignoring plugin dir entry \"%s/%s\""
 
931
                " with bad type or mode\n",
 
932
                plugindir != NULL ? plugindir : PDIR,
 
933
                direntries[i]->d_name);
920
934
      }
921
 
      free(filename);
 
935
      close(plugin_fd);
 
936
      free(direntries[i]);
922
937
      continue;
923
938
    }
924
939
    
925
 
    plugin *p = getplugin(dirst->d_name);
 
940
    plugin *p = getplugin(direntries[i]->d_name);
926
941
    if(p == NULL){
927
942
      error(0, errno, "getplugin");
928
 
      free(filename);
 
943
      close(plugin_fd);
 
944
      free(direntries[i]);
929
945
      continue;
930
946
    }
931
947
    if(p->disabled){
932
948
      if(debug){
933
949
        fprintf(stderr, "Ignoring disabled plugin \"%s\"\n",
934
 
                dirst->d_name);
 
950
                direntries[i]->d_name);
935
951
      }
936
 
      free(filename);
 
952
      close(plugin_fd);
 
953
      free(direntries[i]);
937
954
      continue;
938
955
    }
939
956
    {
953
970
        }
954
971
      }
955
972
    }
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. */
 
973
    /* If this plugin has any environment variables, we need to
 
974
       duplicate the environment from this process, too. */
959
975
    if(p->environ[0] != NULL){
960
976
      for(char **e = environ; *e != NULL; e++){
961
977
        if(not add_environment(p, *e, false)){
965
981
    }
966
982
    
967
983
    int pipefd[2];
 
984
#ifndef O_CLOEXEC
968
985
    ret = (int)TEMP_FAILURE_RETRY(pipe(pipefd));
 
986
#else  /* O_CLOEXEC */
 
987
    ret = (int)TEMP_FAILURE_RETRY(pipe2(pipefd, O_CLOEXEC));
 
988
#endif  /* O_CLOEXEC */
969
989
    if(ret == -1){
970
990
      error(0, errno, "pipe");
971
991
      exitstatus = EX_OSERR;
972
 
      goto fallback;
973
 
    }
 
992
      free(direntries[i]);
 
993
      goto fallback;
 
994
    }
 
995
    if(pipefd[0] >= FD_SETSIZE){
 
996
      fprintf(stderr, "pipe()[0] (%d) >= FD_SETSIZE (%d)", pipefd[0],
 
997
              FD_SETSIZE);
 
998
      close(pipefd[0]);
 
999
      close(pipefd[1]);
 
1000
      exitstatus = EX_OSERR;
 
1001
      free(direntries[i]);
 
1002
      goto fallback;
 
1003
    }
 
1004
#ifndef O_CLOEXEC
974
1005
    /* Ask OS to automatic close the pipe on exec */
975
1006
    ret = set_cloexec_flag(pipefd[0]);
976
1007
    if(ret < 0){
977
1008
      error(0, errno, "set_cloexec_flag");
 
1009
      close(pipefd[0]);
 
1010
      close(pipefd[1]);
978
1011
      exitstatus = EX_OSERR;
 
1012
      free(direntries[i]);
979
1013
      goto fallback;
980
1014
    }
981
1015
    ret = set_cloexec_flag(pipefd[1]);
982
1016
    if(ret < 0){
983
1017
      error(0, errno, "set_cloexec_flag");
 
1018
      close(pipefd[0]);
 
1019
      close(pipefd[1]);
984
1020
      exitstatus = EX_OSERR;
 
1021
      free(direntries[i]);
985
1022
      goto fallback;
986
1023
    }
 
1024
#endif  /* not O_CLOEXEC */
987
1025
    /* Block SIGCHLD until process is safely in process list */
988
1026
    ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_BLOCK,
989
1027
                                              &sigchld_action.sa_mask,
991
1029
    if(ret < 0){
992
1030
      error(0, errno, "sigprocmask");
993
1031
      exitstatus = EX_OSERR;
 
1032
      free(direntries[i]);
994
1033
      goto fallback;
995
1034
    }
996
1035
    /* Starting a new process to be watched */
1000
1039
    } while(pid == -1 and errno == EINTR);
1001
1040
    if(pid == -1){
1002
1041
      error(0, errno, "fork");
 
1042
      TEMP_FAILURE_RETRY(sigprocmask(SIG_UNBLOCK,
 
1043
                                     &sigchld_action.sa_mask, NULL));
 
1044
      close(pipefd[0]);
 
1045
      close(pipefd[1]);
1003
1046
      exitstatus = EX_OSERR;
 
1047
      free(direntries[i]);
1004
1048
      goto fallback;
1005
1049
    }
1006
1050
    if(pid == 0){
1022
1066
        _exit(EX_OSERR);
1023
1067
      }
1024
1068
      
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
 
        }
 
1069
      if(fexecve(plugin_fd, p->argv,
 
1070
                (p->environ[0] != NULL) ? p->environ : environ) < 0){
 
1071
        error(0, errno, "fexecve for %s/%s",
 
1072
              plugindir != NULL ? plugindir : PDIR,
 
1073
              direntries[i]->d_name);
 
1074
        _exit(EX_OSERR);
1040
1075
      }
1041
1076
      /* no return */
1042
1077
    }
1043
1078
    /* Parent process */
1044
 
    TEMP_FAILURE_RETRY(close(pipefd[1])); /* Close unused write end of
1045
 
                                             pipe */
1046
 
    free(filename);
1047
 
    plugin *new_plugin = getplugin(dirst->d_name);
 
1079
    close(pipefd[1]);           /* Close unused write end of pipe */
 
1080
    close(plugin_fd);
 
1081
    plugin *new_plugin = getplugin(direntries[i]->d_name);
1048
1082
    if(new_plugin == NULL){
1049
1083
      error(0, errno, "getplugin");
1050
1084
      ret = (int)(TEMP_FAILURE_RETRY
1054
1088
        error(0, errno, "sigprocmask");
1055
1089
      }
1056
1090
      exitstatus = EX_OSERR;
 
1091
      free(direntries[i]);
1057
1092
      goto fallback;
1058
1093
    }
 
1094
    free(direntries[i]);
1059
1095
    
1060
1096
    new_plugin->pid = pid;
1061
1097
    new_plugin->fd = pipefd[0];
1062
 
    
 
1098
 
 
1099
    if(debug){
 
1100
      fprintf(stderr, "Plugin %s started (PID %" PRIdMAX ")\n",
 
1101
              new_plugin->name, (intmax_t) (new_plugin->pid));
 
1102
    }
 
1103
 
1063
1104
    /* Unblock SIGCHLD so signal handler can be run if this process
1064
1105
       has already completed */
1065
1106
    ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_UNBLOCK,
1071
1112
      goto fallback;
1072
1113
    }
1073
1114
    
1074
 
    FD_SET(new_plugin->fd, &rfds_all); /* Spurious warning from
1075
 
                                          -Wconversion */
 
1115
    FD_SET(new_plugin->fd, &rfds_all);
1076
1116
    
1077
1117
    if(maxfd < new_plugin->fd){
1078
1118
      maxfd = new_plugin->fd;
1079
1119
    }
1080
1120
  }
1081
1121
  
1082
 
  TEMP_FAILURE_RETRY(closedir(dir));
1083
 
  dir = NULL;
 
1122
  free(direntries);
 
1123
  direntries = NULL;
 
1124
  close(dir_fd);
 
1125
  dir_fd = -1;
1084
1126
  free_plugin(getplugin(NULL));
1085
1127
  
1086
1128
  for(plugin *p = plugin_list; p != NULL; p = p->next){
1125
1167
                      (intmax_t) (proc->pid),
1126
1168
                      WTERMSIG(proc->status),
1127
1169
                      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
1170
            }
1132
1171
          }
1133
1172
          
1134
1173
          /* Remove the plugin */
1135
 
          FD_CLR(proc->fd, &rfds_all); /* Spurious warning from
1136
 
                                          -Wconversion */
 
1174
          FD_CLR(proc->fd, &rfds_all);
1137
1175
          
1138
1176
          /* Block signal while modifying process_list */
1139
1177
          ret = (int)TEMP_FAILURE_RETRY(sigprocmask
1179
1217
      }
1180
1218
      
1181
1219
      /* 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 */
 
1220
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){
1185
1221
        /* This process had nothing to say at this time */
1186
1222
        proc = proc->next;
1187
1223
        continue;
1188
1224
      }
1189
1225
      /* Before reading, make the process' data buffer large enough */
1190
1226
      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){
 
1227
        char *new_buffer = realloc(proc->buffer, proc->buffer_size
 
1228
                                   + (size_t) BUFFER_SIZE);
 
1229
        if(new_buffer == NULL){
1194
1230
          error(0, errno, "malloc");
1195
1231
          exitstatus = EX_OSERR;
1196
1232
          goto fallback;
1197
1233
        }
 
1234
        proc->buffer = new_buffer;
1198
1235
        proc->buffer_size += BUFFER_SIZE;
1199
1236
      }
1200
1237
      /* Read from the process */
1253
1290
    free(custom_argv);
1254
1291
  }
1255
1292
  
1256
 
  if(dir != NULL){
1257
 
    closedir(dir);
 
1293
  free(direntries);
 
1294
  
 
1295
  if(dir_fd != -1){
 
1296
    close(dir_fd);
1258
1297
  }
1259
1298
  
1260
1299
  /* Kill the processes */
1280
1319
  free_plugin_list();
1281
1320
  
1282
1321
  free(plugindir);
 
1322
  free(pluginhelperdir);
1283
1323
  free(argfile);
1284
1324
  
1285
1325
  return exitstatus;