/mandos/trunk

To get this branch, use:
bzr branch http://bzr.recompile.se/loggerhead/mandos/trunk

« back to all changes in this revision

Viewing changes to plugin-runner.c

  • Committer: Teddy Hogeborn
  • Date: 2019-07-29 16:35:53 UTC
  • 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-2013 Teddy Hogeborn
6
 
 * Copyright © 2008-2013 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
  
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
 
#pragma GCC diagnostic push
860
 
#pragma GCC diagnostic ignored "-Wcast-qual"
861
 
      for(const char **pre = (const char **)bad_prefixes;
862
 
          *pre != NULL; pre++){
863
 
#pragma GCC diagnostic pop
864
 
        size_t pre_len = strlen(*pre);
865
 
        if((d_name_len >= pre_len)
866
 
           and strncmp((dirst->d_name), *pre, pre_len) == 0){
867
 
          if(debug){
868
 
            fprintf(stderr, "Ignoring plugin dir entry \"%s\""
869
 
                    " with bad prefix %s\n", dirst->d_name, *pre);
870
 
          }
871
 
          bad_name = true;
872
 
          break;
873
 
        }
874
 
      }
875
 
      if(bad_name){
876
 
        continue;
877
 
      }
878
 
#pragma GCC diagnostic push
879
 
#pragma GCC diagnostic ignored "-Wcast-qual"
880
 
      for(const char **suf = (const char **)bad_suffixes;
881
 
          *suf != NULL; suf++){
882
 
#pragma GCC diagnostic pop
883
 
        size_t suf_len = strlen(*suf);
884
 
        if((d_name_len >= suf_len)
885
 
           and (strcmp((dirst->d_name) + d_name_len-suf_len, *suf)
886
 
                == 0)){
887
 
          if(debug){
888
 
            fprintf(stderr, "Ignoring plugin dir entry \"%s\""
889
 
                    " with bad suffix %s\n", dirst->d_name, *suf);
890
 
          }
891
 
          bad_name = true;
892
 
          break;
893
 
        }
894
 
      }
895
 
      
896
 
      if(bad_name){
897
 
        continue;
898
 
      }
899
 
    }
900
 
    
901
 
    char *filename;
902
 
    if(plugindir == NULL){
903
 
      ret = (int)TEMP_FAILURE_RETRY(asprintf(&filename, PDIR "/%s",
904
 
                                             dirst->d_name));
905
 
    } else {
906
 
      ret = (int)TEMP_FAILURE_RETRY(asprintf(&filename, "%s/%s",
907
 
                                             plugindir,
908
 
                                             dirst->d_name));
909
 
    }
910
 
    if(ret < 0){
911
 
      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]);
912
920
      continue;
913
921
    }
914
 
    
915
 
    ret = (int)TEMP_FAILURE_RETRY(stat(filename, &st));
 
922
    ret = (int)TEMP_FAILURE_RETRY(fstat(plugin_fd, &st));
916
923
    if(ret == -1){
917
924
      error(0, errno, "stat");
918
 
      free(filename);
 
925
      close(plugin_fd);
 
926
      free(direntries[i]);
919
927
      continue;
920
928
    }
921
929
    
922
930
    /* Ignore non-executable files */
923
931
    if(not S_ISREG(st.st_mode)
924
 
       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)){
925
934
      if(debug){
926
 
        fprintf(stderr, "Ignoring plugin dir entry \"%s\""
927
 
                " 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);
928
939
      }
929
 
      free(filename);
 
940
      close(plugin_fd);
 
941
      free(direntries[i]);
930
942
      continue;
931
943
    }
932
944
    
933
 
    plugin *p = getplugin(dirst->d_name);
 
945
    plugin *p = getplugin(direntries[i]->d_name);
934
946
    if(p == NULL){
935
947
      error(0, errno, "getplugin");
936
 
      free(filename);
 
948
      close(plugin_fd);
 
949
      free(direntries[i]);
937
950
      continue;
938
951
    }
939
952
    if(p->disabled){
940
953
      if(debug){
941
954
        fprintf(stderr, "Ignoring disabled plugin \"%s\"\n",
942
 
                dirst->d_name);
 
955
                direntries[i]->d_name);
943
956
      }
944
 
      free(filename);
 
957
      close(plugin_fd);
 
958
      free(direntries[i]);
945
959
      continue;
946
960
    }
947
961
    {
961
975
        }
962
976
      }
963
977
    }
964
 
    /* If this plugin has any environment variables, we will call
965
 
       using execve and need to duplicate the environment from this
966
 
       process, too. */
 
978
    /* If this plugin has any environment variables, we need to
 
979
       duplicate the environment from this process, too. */
967
980
    if(p->environ[0] != NULL){
968
981
      for(char **e = environ; *e != NULL; e++){
969
982
        if(not add_environment(p, *e, false)){
973
986
    }
974
987
    
975
988
    int pipefd[2];
 
989
#ifndef O_CLOEXEC
976
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 */
977
994
    if(ret == -1){
978
995
      error(0, errno, "pipe");
979
996
      exitstatus = EX_OSERR;
980
 
      goto fallback;
981
 
    }
 
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
982
1010
    /* Ask OS to automatic close the pipe on exec */
983
1011
    ret = set_cloexec_flag(pipefd[0]);
984
1012
    if(ret < 0){
985
1013
      error(0, errno, "set_cloexec_flag");
 
1014
      close(pipefd[0]);
 
1015
      close(pipefd[1]);
986
1016
      exitstatus = EX_OSERR;
 
1017
      free(direntries[i]);
987
1018
      goto fallback;
988
1019
    }
989
1020
    ret = set_cloexec_flag(pipefd[1]);
990
1021
    if(ret < 0){
991
1022
      error(0, errno, "set_cloexec_flag");
 
1023
      close(pipefd[0]);
 
1024
      close(pipefd[1]);
992
1025
      exitstatus = EX_OSERR;
 
1026
      free(direntries[i]);
993
1027
      goto fallback;
994
1028
    }
 
1029
#endif  /* not O_CLOEXEC */
995
1030
    /* Block SIGCHLD until process is safely in process list */
996
1031
    ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_BLOCK,
997
1032
                                              &sigchld_action.sa_mask,
999
1034
    if(ret < 0){
1000
1035
      error(0, errno, "sigprocmask");
1001
1036
      exitstatus = EX_OSERR;
 
1037
      free(direntries[i]);
1002
1038
      goto fallback;
1003
1039
    }
1004
1040
    /* Starting a new process to be watched */
1008
1044
    } while(pid == -1 and errno == EINTR);
1009
1045
    if(pid == -1){
1010
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]);
1011
1051
      exitstatus = EX_OSERR;
 
1052
      free(direntries[i]);
1012
1053
      goto fallback;
1013
1054
    }
1014
1055
    if(pid == 0){
1030
1071
        _exit(EX_OSERR);
1031
1072
      }
1032
1073
      
1033
 
      if(dirfd(dir) < 0){
1034
 
        /* If dir has no file descriptor, we could not set FD_CLOEXEC
1035
 
           above and must now close it manually here. */
1036
 
        closedir(dir);
1037
 
      }
1038
 
      if(p->environ[0] == NULL){
1039
 
        if(execv(filename, p->argv) < 0){
1040
 
          error(0, errno, "execv for %s", filename);
1041
 
          _exit(EX_OSERR);
1042
 
        }
1043
 
      } else {
1044
 
        if(execve(filename, p->argv, p->environ) < 0){
1045
 
          error(0, errno, "execve for %s", filename);
1046
 
          _exit(EX_OSERR);
1047
 
        }
 
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);
1048
1080
      }
1049
1081
      /* no return */
1050
1082
    }
1051
1083
    /* Parent process */
1052
 
    TEMP_FAILURE_RETRY(close(pipefd[1])); /* Close unused write end of
1053
 
                                             pipe */
1054
 
    free(filename);
1055
 
    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);
1056
1087
    if(new_plugin == NULL){
1057
1088
      error(0, errno, "getplugin");
1058
1089
      ret = (int)(TEMP_FAILURE_RETRY
1062
1093
        error(0, errno, "sigprocmask");
1063
1094
      }
1064
1095
      exitstatus = EX_OSERR;
 
1096
      free(direntries[i]);
1065
1097
      goto fallback;
1066
1098
    }
 
1099
    free(direntries[i]);
1067
1100
    
1068
1101
    new_plugin->pid = pid;
1069
1102
    new_plugin->fd = pipefd[0];
1070
 
    
 
1103
 
 
1104
    if(debug){
 
1105
      fprintf(stderr, "Plugin %s started (PID %" PRIdMAX ")\n",
 
1106
              new_plugin->name, (intmax_t) (new_plugin->pid));
 
1107
    }
 
1108
 
1071
1109
    /* Unblock SIGCHLD so signal handler can be run if this process
1072
1110
       has already completed */
1073
1111
    ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_UNBLOCK,
1079
1117
      goto fallback;
1080
1118
    }
1081
1119
    
1082
 
#if defined (__GNUC__) and defined (__GLIBC__)
1083
 
#if not __GLIBC_PREREQ(2, 16)
1084
 
#pragma GCC diagnostic push
1085
 
#pragma GCC diagnostic ignored "-Wsign-conversion"
1086
 
#endif
1087
 
#endif
1088
 
    FD_SET(new_plugin->fd, &rfds_all); /* Spurious warning from
1089
 
                                          -Wconversion in GNU libc
1090
 
                                          before 2.16 */
1091
 
#if defined (__GNUC__) and defined (__GLIBC__)
1092
 
#if not __GLIBC_PREREQ(2, 16)
1093
 
#pragma GCC diagnostic pop
1094
 
#endif
1095
 
#endif
 
1120
    FD_SET(new_plugin->fd, &rfds_all);
1096
1121
    
1097
1122
    if(maxfd < new_plugin->fd){
1098
1123
      maxfd = new_plugin->fd;
1099
1124
    }
1100
1125
  }
1101
1126
  
1102
 
  TEMP_FAILURE_RETRY(closedir(dir));
1103
 
  dir = NULL;
 
1127
  free(direntries);
 
1128
  direntries = NULL;
 
1129
  close(dir_fd);
 
1130
  dir_fd = -1;
1104
1131
  free_plugin(getplugin(NULL));
1105
1132
  
1106
1133
  for(plugin *p = plugin_list; p != NULL; p = p->next){
1145
1172
                      (intmax_t) (proc->pid),
1146
1173
                      WTERMSIG(proc->status),
1147
1174
                      strsignal(WTERMSIG(proc->status)));
1148
 
            } else if(WCOREDUMP(proc->status)){
1149
 
              fprintf(stderr, "Plugin %s [%" PRIdMAX "] dumped"
1150
 
                      " core\n", proc->name, (intmax_t) (proc->pid));
1151
1175
            }
1152
1176
          }
1153
1177
          
1154
1178
          /* Remove the plugin */
1155
 
#if defined (__GNUC__) and defined (__GLIBC__)
1156
 
#if not __GLIBC_PREREQ(2, 16)
1157
 
#pragma GCC diagnostic push
1158
 
#pragma GCC diagnostic ignored "-Wsign-conversion"
1159
 
#endif
1160
 
#endif
1161
 
          FD_CLR(proc->fd, &rfds_all); /* Spurious warning from
1162
 
                                          -Wconversion in GNU libc
1163
 
                                          before 2.16 */
1164
 
#if defined (__GNUC__) and defined (__GLIBC__)
1165
 
#if not __GLIBC_PREREQ(2, 16)
1166
 
#pragma GCC diagnostic pop
1167
 
#endif
1168
 
#endif
 
1179
          FD_CLR(proc->fd, &rfds_all);
1169
1180
          
1170
1181
          /* Block signal while modifying process_list */
1171
1182
          ret = (int)TEMP_FAILURE_RETRY(sigprocmask
1211
1222
      }
1212
1223
      
1213
1224
      /* This process has not completed.  Does it have any output? */
1214
 
#if defined (__GNUC__) and defined (__GLIBC__)
1215
 
#if not __GLIBC_PREREQ(2, 16)
1216
 
#pragma GCC diagnostic push
1217
 
#pragma GCC diagnostic ignored "-Wsign-conversion"
1218
 
#endif
1219
 
#endif
1220
 
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){ /* Spurious
1221
 
                                                         warning from
1222
 
                                                         -Wconversion
1223
 
                                                         in GNU libc
1224
 
                                                         before
1225
 
                                                         2.16 */
1226
 
#if defined (__GNUC__) and defined (__GLIBC__)
1227
 
#if not __GLIBC_PREREQ(2, 16)
1228
 
#pragma GCC diagnostic pop
1229
 
#endif
1230
 
#endif
 
1225
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){
1231
1226
        /* This process had nothing to say at this time */
1232
1227
        proc = proc->next;
1233
1228
        continue;
1234
1229
      }
1235
1230
      /* Before reading, make the process' data buffer large enough */
1236
1231
      if(proc->buffer_length + BUFFER_SIZE > proc->buffer_size){
1237
 
        proc->buffer = realloc(proc->buffer, proc->buffer_size
1238
 
                               + (size_t) BUFFER_SIZE);
1239
 
        if(proc->buffer == NULL){
 
1232
        char *new_buffer = realloc(proc->buffer, proc->buffer_size
 
1233
                                   + (size_t) BUFFER_SIZE);
 
1234
        if(new_buffer == NULL){
1240
1235
          error(0, errno, "malloc");
1241
1236
          exitstatus = EX_OSERR;
1242
1237
          goto fallback;
1243
1238
        }
 
1239
        proc->buffer = new_buffer;
1244
1240
        proc->buffer_size += BUFFER_SIZE;
1245
1241
      }
1246
1242
      /* Read from the process */
1299
1295
    free(custom_argv);
1300
1296
  }
1301
1297
  
1302
 
  if(dir != NULL){
1303
 
    closedir(dir);
 
1298
  free(direntries);
 
1299
  
 
1300
  if(dir_fd != -1){
 
1301
    close(dir_fd);
1304
1302
  }
1305
1303
  
1306
1304
  /* Kill the processes */
1326
1324
  free_plugin_list();
1327
1325
  
1328
1326
  free(plugindir);
 
1327
  free(pluginhelperdir);
1329
1328
  free(argfile);
1330
1329
  
1331
1330
  return exitstatus;