/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: 2021-02-01 19:30:45 UTC
  • Revision ID: teddy@recompile.se-20210201193045-lpg6aprpc4srem6k
Fix issue with french translation

Initial white space was missing in both msgid and msgstr of the french
translation, leading to checking tools reporing an incomplete
translation.  The string is a raw command line command, and therefore
did not need translation, so this was never a user-visible issue.

* debian/po/fr.po: Add missing whitespace to the id and translation
  for msgid " mandos-keygen -F/dev/null|grep ^key_id".

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-2020 Teddy Hogeborn
 
6
 * Copyright © 2008-2020 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
 
#include <stdlib.h>             /* malloc(), exit(), EXIT_SUCCESS,
29
 
                                   realloc() */
 
29
#include <stdlib.h>             /* malloc(), reallocarray(), realloc(),
 
30
                                   EXIT_SUCCESS, exit() */
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
                                   lstat(), symlink() */
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
#if defined(__GLIBC_PREREQ) and __GLIBC_PREREQ(2, 26)
 
183
    new_array = reallocarray(*array, (size_t)((*len) + 2),
 
184
                             sizeof(char *));
 
185
#else
 
186
    if(((size_t)((*len) + 2)) > (SIZE_MAX / sizeof(char *))){
 
187
      /* overflow */
 
188
      new_array = NULL;
 
189
      errno = ENOMEM;
 
190
    } else {
 
191
      new_array = realloc(*array, (size_t)((*len) + 2)
 
192
                          * sizeof(char *));
 
193
    }
 
194
#endif
 
195
  } while(new_array == NULL and errno == EINTR);
182
196
  /* Malloc check */
183
 
  if(*array == NULL){
 
197
  if(new_array == NULL){
184
198
    return false;
185
199
  }
 
200
  *array = new_array;
186
201
  /* Make a copy of the new string */
187
202
  char *copy;
188
203
  do {
200
215
}
201
216
 
202
217
/* Add to a plugin's argument vector */
203
 
__attribute__((nonnull(2)))
 
218
__attribute__((nonnull(2), warn_unused_result))
204
219
static bool add_argument(plugin *p, const char *arg){
205
220
  if(p == NULL){
206
221
    return false;
209
224
}
210
225
 
211
226
/* Add to a plugin's environment */
212
 
__attribute__((nonnull(2)))
 
227
__attribute__((nonnull(2), warn_unused_result))
213
228
static bool add_environment(plugin *p, const char *def, bool replace){
214
229
  if(p == NULL){
215
230
    return false;
217
232
  /* namelen = length of name of environment variable */
218
233
  size_t namelen = (size_t)(strchrnul(def, '=') - def);
219
234
  /* Search for this environment variable */
220
 
  for(char **e = p->environ; *e != NULL; e++){
221
 
    if(strncmp(*e, def, namelen + 1) == 0){
 
235
  for(char **envdef = p->environ; *envdef != NULL; envdef++){
 
236
    if(strncmp(*envdef, def, namelen + 1) == 0){
222
237
      /* It already exists */
223
238
      if(replace){
224
 
        char *new;
 
239
        char *new_envdef;
225
240
        do {
226
 
          new = realloc(*e, strlen(def) + 1);
227
 
        } while(new == NULL and errno == EINTR);
228
 
        if(new == NULL){
 
241
          new_envdef = realloc(*envdef, strlen(def) + 1);
 
242
        } while(new_envdef == NULL and errno == EINTR);
 
243
        if(new_envdef == NULL){
229
244
          return false;
230
245
        }
231
 
        *e = new;
232
 
        strcpy(*e, def);
 
246
        *envdef = new_envdef;
 
247
        strcpy(*envdef, def);
233
248
      }
234
249
      return true;
235
250
    }
237
252
  return add_to_char_array(def, &(p->environ), &(p->envc));
238
253
}
239
254
 
 
255
#ifndef O_CLOEXEC
240
256
/*
241
257
 * Based on the example in the GNU LibC manual chapter 13.13 "File
242
258
 * Descriptor Flags".
243
259
 | [[info:libc:Descriptor%20Flags][File Descriptor Flags]] |
244
260
 */
 
261
__attribute__((warn_unused_result))
245
262
static int set_cloexec_flag(int fd){
246
263
  int ret = (int)TEMP_FAILURE_RETRY(fcntl(fd, F_GETFD, 0));
247
264
  /* If reading the flags failed, return error indication now. */
252
269
  return (int)TEMP_FAILURE_RETRY(fcntl(fd, F_SETFD,
253
270
                                       ret | FD_CLOEXEC));
254
271
}
 
272
#endif  /* not O_CLOEXEC */
255
273
 
256
274
 
257
275
/* Mark processes as completed when they exit, and save their exit
289
307
}
290
308
 
291
309
/* Prints out a password to stdout */
292
 
__attribute__((nonnull))
 
310
__attribute__((nonnull, warn_unused_result))
293
311
static bool print_out_password(const char *buffer, size_t length){
294
312
  ssize_t ret;
295
313
  for(size_t written = 0; written < length; written += (size_t)ret){
306
324
__attribute__((nonnull))
307
325
static void free_plugin(plugin *plugin_node){
308
326
  
309
 
  for(char **arg = plugin_node->argv; *arg != NULL; arg++){
 
327
  for(char **arg = (plugin_node->argv)+1; *arg != NULL; arg++){
310
328
    free(*arg);
311
329
  }
 
330
  free(plugin_node->name);
312
331
  free(plugin_node->argv);
313
332
  for(char **env = plugin_node->environ; *env != NULL; env++){
314
333
    free(*env);
341
360
 
342
361
int main(int argc, char *argv[]){
343
362
  char *plugindir = NULL;
 
363
  char *pluginhelperdir = NULL;
344
364
  char *argfile = NULL;
345
365
  FILE *conffp;
346
 
  size_t d_name_len;
347
 
  DIR *dir = NULL;
348
 
  struct dirent *dirst;
 
366
  struct dirent **direntries = NULL;
349
367
  struct stat st;
350
368
  fd_set rfds_all;
351
369
  int ret, maxfd = 0;
359
377
                                      .sa_flags = SA_NOCLDSTOP };
360
378
  char **custom_argv = NULL;
361
379
  int custom_argc = 0;
 
380
  int dir_fd = -1;
362
381
  
363
382
  /* Establish a signal handler */
364
383
  sigemptyset(&sigchld_action.sa_mask);
409
428
      .doc = "Group ID the plugins will run as", .group = 3 },
410
429
    { .name = "debug", .key = 132,
411
430
      .doc = "Debug mode", .group = 4 },
 
431
    { .name = "plugin-helper-dir", .key = 133,
 
432
      .arg = "DIRECTORY",
 
433
      .doc = "Specify a different plugin helper directory",
 
434
      .group = 2 },
412
435
    /*
413
436
     * These reproduce what we would get without ARGP_NO_HELP
414
437
     */
435
458
            break;
436
459
          }
437
460
        }
 
461
        errno = 0;
438
462
      }
439
463
      break;
440
464
    case 'G':                   /* --global-env */
441
 
      add_environment(getplugin(NULL), arg, true);
 
465
      if(add_environment(getplugin(NULL), arg, true)){
 
466
        errno = 0;
 
467
      }
442
468
      break;
443
469
    case 'o':                   /* --options-for */
444
470
      {
461
487
            break;
462
488
          }
463
489
        }
 
490
        errno = 0;
464
491
      }
465
492
      break;
466
493
    case 'E':                   /* --env-for */
478
505
          errno = EINVAL;
479
506
          break;
480
507
        }
481
 
        add_environment(getplugin(arg), envdef, true);
 
508
        if(add_environment(getplugin(arg), envdef, true)){
 
509
          errno = 0;
 
510
        }
482
511
      }
483
512
      break;
484
513
    case 'd':                   /* --disable */
486
515
        plugin *p = getplugin(arg);
487
516
        if(p != NULL){
488
517
          p->disabled = true;
 
518
          errno = 0;
489
519
        }
490
520
      }
491
521
      break;
494
524
        plugin *p = getplugin(arg);
495
525
        if(p != NULL){
496
526
          p->disabled = false;
 
527
          errno = 0;
497
528
        }
498
529
      }
499
530
      break;
500
531
    case 128:                   /* --plugin-dir */
501
532
      free(plugindir);
502
533
      plugindir = strdup(arg);
 
534
      if(plugindir != NULL){
 
535
        errno = 0;
 
536
      }
503
537
      break;
504
538
    case 129:                   /* --config-file */
505
539
      /* This is already done by parse_opt_config_file() */
513
547
        break;
514
548
      }
515
549
      uid = (uid_t)tmp_id;
 
550
      errno = 0;
516
551
      break;
517
552
    case 131:                   /* --groupid */
518
553
      tmp_id = strtoimax(arg, &tmp, 10);
523
558
        break;
524
559
      }
525
560
      gid = (gid_t)tmp_id;
 
561
      errno = 0;
526
562
      break;
527
563
    case 132:                   /* --debug */
528
564
      debug = true;
529
565
      break;
 
566
    case 133:                   /* --plugin-helper-dir */
 
567
      free(pluginhelperdir);
 
568
      pluginhelperdir = strdup(arg);
 
569
      if(pluginhelperdir != NULL){
 
570
        errno = 0;
 
571
      }
 
572
      break;
530
573
      /*
531
574
       * These reproduce what we would get without ARGP_NO_HELP
532
575
       */
533
576
    case '?':                   /* --help */
534
577
      state->flags &= ~(unsigned int)ARGP_NO_EXIT; /* force exit */
535
578
      argp_state_help(state, state->out_stream, ARGP_HELP_STD_HELP);
 
579
      __builtin_unreachable();
536
580
    case -3:                    /* --usage */
537
581
      state->flags &= ~(unsigned int)ARGP_NO_EXIT; /* force exit */
538
582
      argp_state_help(state, state->out_stream,
539
583
                      ARGP_HELP_USAGE | ARGP_HELP_EXIT_OK);
 
584
      __builtin_unreachable();
540
585
    case 'V':                   /* --version */
541
586
      fprintf(state->out_stream, "%s\n", argp_program_version);
542
587
      exit(EXIT_SUCCESS);
552
597
      if(arg[0] == '\0'){
553
598
        break;
554
599
      }
 
600
#if __GNUC__ >= 7
 
601
      __attribute__((fallthrough));
 
602
#else
 
603
          /* FALLTHROUGH */
 
604
#endif
555
605
    default:
556
606
      return ARGP_ERR_UNKNOWN;
557
607
    }
576
626
    case 129:                   /* --config-file */
577
627
      free(argfile);
578
628
      argfile = strdup(arg);
 
629
      if(argfile != NULL){
 
630
        errno = 0;
 
631
      }
579
632
      break;
580
633
    case 130:                   /* --userid */
581
634
    case 131:                   /* --groupid */
582
635
    case 132:                   /* --debug */
 
636
    case 133:                   /* --plugin-helper-dir */
583
637
    case '?':                   /* --help */
584
638
    case -3:                    /* --usage */
585
639
    case 'V':                   /* --version */
664
718
        }
665
719
        
666
720
        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;
 
721
        {
 
722
#if defined(__GLIBC_PREREQ) and __GLIBC_PREREQ(2, 26)
 
723
          char **new_argv = reallocarray(custom_argv, (size_t)custom_argc + 1,
 
724
                                         sizeof(char *));
 
725
#else
 
726
          char **new_argv = NULL;
 
727
          if(((size_t)custom_argc + 1) > (SIZE_MAX / sizeof(char *))){
 
728
            /* overflow */
 
729
            errno = ENOMEM;
 
730
          } else {
 
731
            new_argv = realloc(custom_argv, ((size_t)custom_argc + 1)
 
732
                               * sizeof(char *));
 
733
          }
 
734
#endif
 
735
          if(new_argv == NULL){
 
736
            error(0, errno, "reallocarray");
 
737
            exitstatus = EX_OSERR;
 
738
            free(new_arg);
 
739
            free(org_line);
 
740
            goto fallback;
 
741
          } else {
 
742
            custom_argv = new_argv;
 
743
          }
674
744
        }
675
745
        custom_argv[custom_argc-1] = new_arg;
676
746
        custom_argv[custom_argc] = NULL;
734
804
    goto fallback;
735
805
  }
736
806
  
 
807
  {
 
808
    char *pluginhelperenv;
 
809
    bool bret = true;
 
810
    ret = asprintf(&pluginhelperenv, "MANDOSPLUGINHELPERDIR=%s",
 
811
                   pluginhelperdir != NULL ? pluginhelperdir : PHDIR);
 
812
    if(ret != -1){
 
813
      bret = add_environment(getplugin(NULL), pluginhelperenv, true);
 
814
    }
 
815
    if(ret == -1 or not bret){
 
816
      error(0, errno, "Failed to set MANDOSPLUGINHELPERDIR"
 
817
            " environment variable to \"%s\" for all plugins\n",
 
818
            pluginhelperdir != NULL ? pluginhelperdir : PHDIR);
 
819
    }
 
820
    if(ret != -1){
 
821
      free(pluginhelperenv);
 
822
    }
 
823
  }
 
824
  
737
825
  if(debug){
738
 
    for(plugin *p = plugin_list; p != NULL; p=p->next){
 
826
    for(plugin *p = plugin_list; p != NULL; p = p->next){
739
827
      fprintf(stderr, "Plugin: %s has %d arguments\n",
740
828
              p->name ? p->name : "Global", p->argc - 1);
741
829
      for(char **a = p->argv; *a != NULL; a++){
750
838
  
751
839
  if(getuid() == 0){
752
840
    /* Work around Debian bug #633582:
753
 
       <http://bugs.debian.org/633582> */
 
841
       <https://bugs.debian.org/633582> */
754
842
    int plugindir_fd = open(/* plugindir or */ PDIR, O_RDONLY);
755
843
    if(plugindir_fd == -1){
756
 
      error(0, errno, "open");
 
844
      if(errno != ENOENT){
 
845
        error(0, errno, "open(\"" PDIR "\")");
 
846
      }
757
847
    } else {
758
848
      ret = (int)TEMP_FAILURE_RETRY(fstat(plugindir_fd, &st));
759
849
      if(ret == -1){
766
856
          }
767
857
        }
768
858
      }
769
 
      TEMP_FAILURE_RETRY(close(plugindir_fd));
 
859
      close(plugindir_fd);
 
860
    }
 
861
 
 
862
    /* Work around Debian bug #981302
 
863
       <https://bugs.debian.org/981302> */
 
864
    if(lstat("/dev/fd", &st) != 0 and errno == ENOENT){
 
865
      ret = symlink("/proc/self/fd", "/dev/fd");
 
866
      if(ret == -1){
 
867
        error(0, errno, "Failed to create /dev/fd symlink");
 
868
      }
770
869
    }
771
870
  }
772
871
  
782
881
  
783
882
  /* Open plugin directory with close_on_exec flag */
784
883
  {
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
 
    }
 
884
    dir_fd = open(plugindir != NULL ? plugindir : PDIR, O_RDONLY |
 
885
#ifdef O_CLOEXEC
 
886
                  O_CLOEXEC
 
887
#else  /* not O_CLOEXEC */
 
888
                  0
 
889
#endif  /* not O_CLOEXEC */
 
890
                  );
803
891
    if(dir_fd == -1){
804
892
      error(0, errno, "Could not open plugin dir");
805
893
      exitstatus = EX_UNAVAILABLE;
811
899
    ret = set_cloexec_flag(dir_fd);
812
900
    if(ret < 0){
813
901
      error(0, errno, "set_cloexec_flag");
814
 
      TEMP_FAILURE_RETRY(close(dir_fd));
815
902
      exitstatus = EX_OSERR;
816
903
      goto fallback;
817
904
    }
818
905
#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;
 
906
  }
 
907
  
 
908
  int good_name(const struct dirent * const dirent){
 
909
    const char * const patterns[] = { ".*", "#*#", "*~", "*.dpkg-new",
 
910
                                      "*.dpkg-old", "*.dpkg-bak",
 
911
                                      "*.dpkg-divert", NULL };
 
912
#ifdef __GNUC__
 
913
#pragma GCC diagnostic push
 
914
#pragma GCC diagnostic ignored "-Wcast-qual"
 
915
#endif
 
916
    for(const char **pat = (const char **)patterns;
 
917
        *pat != NULL; pat++){
 
918
#ifdef __GNUC__
 
919
#pragma GCC diagnostic pop
 
920
#endif
 
921
      if(fnmatch(*pat, dirent->d_name, FNM_FILE_NAME | FNM_PERIOD)
 
922
         != FNM_NOMATCH){
 
923
        if(debug){
 
924
            fprintf(stderr, "Ignoring plugin dir entry \"%s\""
 
925
                    " matching pattern %s\n", dirent->d_name, *pat);
 
926
        }
 
927
        return 0;
 
928
      }
826
929
    }
 
930
    return 1;
 
931
  }
 
932
  
 
933
  int numplugins = scandirat(dir_fd, ".", &direntries, good_name,
 
934
                             alphasort);
 
935
  if(numplugins == -1){
 
936
    error(0, errno, "Could not scan plugin dir");
 
937
    direntries = NULL;
 
938
    exitstatus = EX_OSERR;
 
939
    goto fallback;
827
940
  }
828
941
  
829
942
  FD_ZERO(&rfds_all);
830
943
  
831
944
  /* 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");
 
945
  for(int i = 0; i < numplugins; i++){
 
946
    
 
947
    int plugin_fd = openat(dir_fd, direntries[i]->d_name, O_RDONLY);
 
948
    if(plugin_fd == -1){
 
949
      error(0, errno, "Could not open plugin");
 
950
      free(direntries[i]);
904
951
      continue;
905
952
    }
906
 
    
907
 
    ret = (int)TEMP_FAILURE_RETRY(stat(filename, &st));
 
953
    ret = (int)TEMP_FAILURE_RETRY(fstat(plugin_fd, &st));
908
954
    if(ret == -1){
909
955
      error(0, errno, "stat");
910
 
      free(filename);
 
956
      close(plugin_fd);
 
957
      free(direntries[i]);
911
958
      continue;
912
959
    }
913
960
    
914
961
    /* Ignore non-executable files */
915
962
    if(not S_ISREG(st.st_mode)
916
 
       or (TEMP_FAILURE_RETRY(access(filename, X_OK)) != 0)){
 
963
       or (TEMP_FAILURE_RETRY(faccessat(dir_fd, direntries[i]->d_name,
 
964
                                        X_OK, 0)) != 0)){
917
965
      if(debug){
918
 
        fprintf(stderr, "Ignoring plugin dir entry \"%s\""
919
 
                " with bad type or mode\n", filename);
 
966
        fprintf(stderr, "Ignoring plugin dir entry \"%s/%s\""
 
967
                " with bad type or mode\n",
 
968
                plugindir != NULL ? plugindir : PDIR,
 
969
                direntries[i]->d_name);
920
970
      }
921
 
      free(filename);
 
971
      close(plugin_fd);
 
972
      free(direntries[i]);
922
973
      continue;
923
974
    }
924
975
    
925
 
    plugin *p = getplugin(dirst->d_name);
 
976
    plugin *p = getplugin(direntries[i]->d_name);
926
977
    if(p == NULL){
927
978
      error(0, errno, "getplugin");
928
 
      free(filename);
 
979
      close(plugin_fd);
 
980
      free(direntries[i]);
929
981
      continue;
930
982
    }
931
983
    if(p->disabled){
932
984
      if(debug){
933
985
        fprintf(stderr, "Ignoring disabled plugin \"%s\"\n",
934
 
                dirst->d_name);
 
986
                direntries[i]->d_name);
935
987
      }
936
 
      free(filename);
 
988
      close(plugin_fd);
 
989
      free(direntries[i]);
937
990
      continue;
938
991
    }
939
992
    {
953
1006
        }
954
1007
      }
955
1008
    }
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. */
 
1009
    /* If this plugin has any environment variables, we need to
 
1010
       duplicate the environment from this process, too. */
959
1011
    if(p->environ[0] != NULL){
960
1012
      for(char **e = environ; *e != NULL; e++){
961
1013
        if(not add_environment(p, *e, false)){
965
1017
    }
966
1018
    
967
1019
    int pipefd[2];
 
1020
#ifndef O_CLOEXEC
968
1021
    ret = (int)TEMP_FAILURE_RETRY(pipe(pipefd));
 
1022
#else  /* O_CLOEXEC */
 
1023
    ret = (int)TEMP_FAILURE_RETRY(pipe2(pipefd, O_CLOEXEC));
 
1024
#endif  /* O_CLOEXEC */
969
1025
    if(ret == -1){
970
1026
      error(0, errno, "pipe");
971
1027
      exitstatus = EX_OSERR;
972
 
      goto fallback;
973
 
    }
 
1028
      free(direntries[i]);
 
1029
      goto fallback;
 
1030
    }
 
1031
    if(pipefd[0] >= FD_SETSIZE){
 
1032
      fprintf(stderr, "pipe()[0] (%d) >= FD_SETSIZE (%d)", pipefd[0],
 
1033
              FD_SETSIZE);
 
1034
      close(pipefd[0]);
 
1035
      close(pipefd[1]);
 
1036
      exitstatus = EX_OSERR;
 
1037
      free(direntries[i]);
 
1038
      goto fallback;
 
1039
    }
 
1040
#ifndef O_CLOEXEC
974
1041
    /* Ask OS to automatic close the pipe on exec */
975
1042
    ret = set_cloexec_flag(pipefd[0]);
976
1043
    if(ret < 0){
977
1044
      error(0, errno, "set_cloexec_flag");
 
1045
      close(pipefd[0]);
 
1046
      close(pipefd[1]);
978
1047
      exitstatus = EX_OSERR;
 
1048
      free(direntries[i]);
979
1049
      goto fallback;
980
1050
    }
981
1051
    ret = set_cloexec_flag(pipefd[1]);
982
1052
    if(ret < 0){
983
1053
      error(0, errno, "set_cloexec_flag");
 
1054
      close(pipefd[0]);
 
1055
      close(pipefd[1]);
984
1056
      exitstatus = EX_OSERR;
 
1057
      free(direntries[i]);
985
1058
      goto fallback;
986
1059
    }
 
1060
#endif  /* not O_CLOEXEC */
987
1061
    /* Block SIGCHLD until process is safely in process list */
988
1062
    ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_BLOCK,
989
1063
                                              &sigchld_action.sa_mask,
991
1065
    if(ret < 0){
992
1066
      error(0, errno, "sigprocmask");
993
1067
      exitstatus = EX_OSERR;
 
1068
      free(direntries[i]);
994
1069
      goto fallback;
995
1070
    }
996
1071
    /* Starting a new process to be watched */
1000
1075
    } while(pid == -1 and errno == EINTR);
1001
1076
    if(pid == -1){
1002
1077
      error(0, errno, "fork");
 
1078
      TEMP_FAILURE_RETRY(sigprocmask(SIG_UNBLOCK,
 
1079
                                     &sigchld_action.sa_mask, NULL));
 
1080
      close(pipefd[0]);
 
1081
      close(pipefd[1]);
1003
1082
      exitstatus = EX_OSERR;
 
1083
      free(direntries[i]);
1004
1084
      goto fallback;
1005
1085
    }
1006
1086
    if(pid == 0){
1022
1102
        _exit(EX_OSERR);
1023
1103
      }
1024
1104
      
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
 
        }
 
1105
      if(fexecve(plugin_fd, p->argv,
 
1106
                (p->environ[0] != NULL) ? p->environ : environ) < 0){
 
1107
        error(0, errno, "fexecve for %s/%s",
 
1108
              plugindir != NULL ? plugindir : PDIR,
 
1109
              direntries[i]->d_name);
 
1110
        _exit(EX_OSERR);
1040
1111
      }
1041
1112
      /* no return */
1042
1113
    }
1043
1114
    /* 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);
 
1115
    close(pipefd[1]);           /* Close unused write end of pipe */
 
1116
    close(plugin_fd);
 
1117
    plugin *new_plugin = getplugin(direntries[i]->d_name);
1048
1118
    if(new_plugin == NULL){
1049
1119
      error(0, errno, "getplugin");
1050
1120
      ret = (int)(TEMP_FAILURE_RETRY
1054
1124
        error(0, errno, "sigprocmask");
1055
1125
      }
1056
1126
      exitstatus = EX_OSERR;
 
1127
      free(direntries[i]);
1057
1128
      goto fallback;
1058
1129
    }
 
1130
    free(direntries[i]);
1059
1131
    
1060
1132
    new_plugin->pid = pid;
1061
1133
    new_plugin->fd = pipefd[0];
1062
 
    
 
1134
 
 
1135
    if(debug){
 
1136
      fprintf(stderr, "Plugin %s started (PID %" PRIdMAX ")\n",
 
1137
              new_plugin->name, (intmax_t) (new_plugin->pid));
 
1138
    }
 
1139
 
1063
1140
    /* Unblock SIGCHLD so signal handler can be run if this process
1064
1141
       has already completed */
1065
1142
    ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_UNBLOCK,
1071
1148
      goto fallback;
1072
1149
    }
1073
1150
    
1074
 
    FD_SET(new_plugin->fd, &rfds_all); /* Spurious warning from
1075
 
                                          -Wconversion */
 
1151
    FD_SET(new_plugin->fd, &rfds_all);
1076
1152
    
1077
1153
    if(maxfd < new_plugin->fd){
1078
1154
      maxfd = new_plugin->fd;
1079
1155
    }
1080
1156
  }
1081
1157
  
1082
 
  TEMP_FAILURE_RETRY(closedir(dir));
1083
 
  dir = NULL;
 
1158
  free(direntries);
 
1159
  direntries = NULL;
 
1160
  close(dir_fd);
 
1161
  dir_fd = -1;
1084
1162
  free_plugin(getplugin(NULL));
1085
1163
  
1086
1164
  for(plugin *p = plugin_list; p != NULL; p = p->next){
1125
1203
                      (intmax_t) (proc->pid),
1126
1204
                      WTERMSIG(proc->status),
1127
1205
                      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
1206
            }
1132
1207
          }
1133
1208
          
1134
1209
          /* Remove the plugin */
1135
 
          FD_CLR(proc->fd, &rfds_all); /* Spurious warning from
1136
 
                                          -Wconversion */
 
1210
          FD_CLR(proc->fd, &rfds_all);
1137
1211
          
1138
1212
          /* Block signal while modifying process_list */
1139
1213
          ret = (int)TEMP_FAILURE_RETRY(sigprocmask
1179
1253
      }
1180
1254
      
1181
1255
      /* 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 */
 
1256
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){
1185
1257
        /* This process had nothing to say at this time */
1186
1258
        proc = proc->next;
1187
1259
        continue;
1188
1260
      }
1189
1261
      /* Before reading, make the process' data buffer large enough */
1190
1262
      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){
 
1263
        char *new_buffer = realloc(proc->buffer, proc->buffer_size
 
1264
                                   + (size_t) BUFFER_SIZE);
 
1265
        if(new_buffer == NULL){
1194
1266
          error(0, errno, "malloc");
1195
1267
          exitstatus = EX_OSERR;
1196
1268
          goto fallback;
1197
1269
        }
 
1270
        proc->buffer = new_buffer;
1198
1271
        proc->buffer_size += BUFFER_SIZE;
1199
1272
      }
1200
1273
      /* Read from the process */
1253
1326
    free(custom_argv);
1254
1327
  }
1255
1328
  
1256
 
  if(dir != NULL){
1257
 
    closedir(dir);
 
1329
  free(direntries);
 
1330
  
 
1331
  if(dir_fd != -1){
 
1332
    close(dir_fd);
1258
1333
  }
1259
1334
  
1260
1335
  /* Kill the processes */
1280
1355
  free_plugin_list();
1281
1356
  
1282
1357
  free(plugindir);
 
1358
  free(pluginhelperdir);
1283
1359
  free(argfile);
1284
1360
  
1285
1361
  return exitstatus;