/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-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-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 */
 
176
__attribute__((nonnull, warn_unused_result))
174
177
static bool add_to_char_array(const char *new, char ***array,
175
178
                              int *len){
176
179
  /* Resize the pointed-to array to hold one more pointer */
 
180
  char **new_array = NULL;
177
181
  do {
178
 
    *array = realloc(*array, sizeof(char *)
179
 
                     * (size_t) ((*len) + 2));
180
 
  } 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);
181
196
  /* Malloc check */
182
 
  if(*array == NULL){
 
197
  if(new_array == NULL){
183
198
    return false;
184
199
  }
 
200
  *array = new_array;
185
201
  /* Make a copy of the new string */
186
202
  char *copy;
187
203
  do {
199
215
}
200
216
 
201
217
/* Add to a plugin's argument vector */
 
218
__attribute__((nonnull(2), warn_unused_result))
202
219
static bool add_argument(plugin *p, const char *arg){
203
220
  if(p == NULL){
204
221
    return false;
207
224
}
208
225
 
209
226
/* Add to a plugin's environment */
 
227
__attribute__((nonnull(2), warn_unused_result))
210
228
static bool add_environment(plugin *p, const char *def, bool replace){
211
229
  if(p == NULL){
212
230
    return false;
214
232
  /* namelen = length of name of environment variable */
215
233
  size_t namelen = (size_t)(strchrnul(def, '=') - def);
216
234
  /* Search for this environment variable */
217
 
  for(char **e = p->environ; *e != NULL; e++){
218
 
    if(strncmp(*e, def, namelen + 1) == 0){
 
235
  for(char **envdef = p->environ; *envdef != NULL; envdef++){
 
236
    if(strncmp(*envdef, def, namelen + 1) == 0){
219
237
      /* It already exists */
220
238
      if(replace){
221
 
        char *new;
 
239
        char *new_envdef;
222
240
        do {
223
 
          new = realloc(*e, strlen(def) + 1);
224
 
        } while(new == NULL and errno == EINTR);
225
 
        if(new == NULL){
 
241
          new_envdef = realloc(*envdef, strlen(def) + 1);
 
242
        } while(new_envdef == NULL and errno == EINTR);
 
243
        if(new_envdef == NULL){
226
244
          return false;
227
245
        }
228
 
        *e = new;
229
 
        strcpy(*e, def);
 
246
        *envdef = new_envdef;
 
247
        strcpy(*envdef, def);
230
248
      }
231
249
      return true;
232
250
    }
234
252
  return add_to_char_array(def, &(p->environ), &(p->envc));
235
253
}
236
254
 
 
255
#ifndef O_CLOEXEC
237
256
/*
238
257
 * Based on the example in the GNU LibC manual chapter 13.13 "File
239
258
 * Descriptor Flags".
240
259
 | [[info:libc:Descriptor%20Flags][File Descriptor Flags]] |
241
260
 */
 
261
__attribute__((warn_unused_result))
242
262
static int set_cloexec_flag(int fd){
243
263
  int ret = (int)TEMP_FAILURE_RETRY(fcntl(fd, F_GETFD, 0));
244
264
  /* If reading the flags failed, return error indication now. */
249
269
  return (int)TEMP_FAILURE_RETRY(fcntl(fd, F_SETFD,
250
270
                                       ret | FD_CLOEXEC));
251
271
}
 
272
#endif  /* not O_CLOEXEC */
252
273
 
253
274
 
254
275
/* Mark processes as completed when they exit, and save their exit
286
307
}
287
308
 
288
309
/* Prints out a password to stdout */
 
310
__attribute__((nonnull, warn_unused_result))
289
311
static bool print_out_password(const char *buffer, size_t length){
290
312
  ssize_t ret;
291
313
  for(size_t written = 0; written < length; written += (size_t)ret){
299
321
}
300
322
 
301
323
/* Removes and free a plugin from the plugin list */
 
324
__attribute__((nonnull))
302
325
static void free_plugin(plugin *plugin_node){
303
326
  
304
 
  for(char **arg = plugin_node->argv; *arg != NULL; arg++){
 
327
  for(char **arg = (plugin_node->argv)+1; *arg != NULL; arg++){
305
328
    free(*arg);
306
329
  }
 
330
  free(plugin_node->name);
307
331
  free(plugin_node->argv);
308
332
  for(char **env = plugin_node->environ; *env != NULL; env++){
309
333
    free(*env);
336
360
 
337
361
int main(int argc, char *argv[]){
338
362
  char *plugindir = NULL;
 
363
  char *pluginhelperdir = NULL;
339
364
  char *argfile = NULL;
340
365
  FILE *conffp;
341
 
  size_t d_name_len;
342
 
  DIR *dir = NULL;
343
 
  struct dirent *dirst;
 
366
  struct dirent **direntries = NULL;
344
367
  struct stat st;
345
368
  fd_set rfds_all;
346
369
  int ret, maxfd = 0;
354
377
                                      .sa_flags = SA_NOCLDSTOP };
355
378
  char **custom_argv = NULL;
356
379
  int custom_argc = 0;
 
380
  int dir_fd = -1;
357
381
  
358
382
  /* Establish a signal handler */
359
383
  sigemptyset(&sigchld_action.sa_mask);
404
428
      .doc = "Group ID the plugins will run as", .group = 3 },
405
429
    { .name = "debug", .key = 132,
406
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 },
407
435
    /*
408
436
     * These reproduce what we would get without ARGP_NO_HELP
409
437
     */
416
444
    { .name = NULL }
417
445
  };
418
446
  
 
447
  __attribute__((nonnull(3)))
419
448
  error_t parse_opt(int key, char *arg, struct argp_state *state){
420
449
    errno = 0;
421
450
    switch(key){
422
451
      char *tmp;
423
 
      intmax_t tmpmax;
 
452
      intmax_t tmp_id;
424
453
    case 'g':                   /* --global-options */
425
454
      {
426
455
        char *plugin_option;
429
458
            break;
430
459
          }
431
460
        }
 
461
        errno = 0;
432
462
      }
433
463
      break;
434
464
    case 'G':                   /* --global-env */
435
 
      add_environment(getplugin(NULL), arg, true);
 
465
      if(add_environment(getplugin(NULL), arg, true)){
 
466
        errno = 0;
 
467
      }
436
468
      break;
437
469
    case 'o':                   /* --options-for */
438
470
      {
455
487
            break;
456
488
          }
457
489
        }
 
490
        errno = 0;
458
491
      }
459
492
      break;
460
493
    case 'E':                   /* --env-for */
472
505
          errno = EINVAL;
473
506
          break;
474
507
        }
475
 
        add_environment(getplugin(arg), envdef, true);
 
508
        if(add_environment(getplugin(arg), envdef, true)){
 
509
          errno = 0;
 
510
        }
476
511
      }
477
512
      break;
478
513
    case 'd':                   /* --disable */
480
515
        plugin *p = getplugin(arg);
481
516
        if(p != NULL){
482
517
          p->disabled = true;
 
518
          errno = 0;
483
519
        }
484
520
      }
485
521
      break;
488
524
        plugin *p = getplugin(arg);
489
525
        if(p != NULL){
490
526
          p->disabled = false;
 
527
          errno = 0;
491
528
        }
492
529
      }
493
530
      break;
494
531
    case 128:                   /* --plugin-dir */
495
532
      free(plugindir);
496
533
      plugindir = strdup(arg);
 
534
      if(plugindir != NULL){
 
535
        errno = 0;
 
536
      }
497
537
      break;
498
538
    case 129:                   /* --config-file */
499
539
      /* This is already done by parse_opt_config_file() */
500
540
      break;
501
541
    case 130:                   /* --userid */
502
 
      tmpmax = strtoimax(arg, &tmp, 10);
 
542
      tmp_id = strtoimax(arg, &tmp, 10);
503
543
      if(errno != 0 or tmp == arg or *tmp != '\0'
504
 
         or tmpmax != (uid_t)tmpmax){
 
544
         or tmp_id != (uid_t)tmp_id){
505
545
        argp_error(state, "Bad user ID number: \"%s\", using %"
506
546
                   PRIdMAX, arg, (intmax_t)uid);
507
547
        break;
508
548
      }
509
 
      uid = (uid_t)tmpmax;
 
549
      uid = (uid_t)tmp_id;
 
550
      errno = 0;
510
551
      break;
511
552
    case 131:                   /* --groupid */
512
 
      tmpmax = strtoimax(arg, &tmp, 10);
 
553
      tmp_id = strtoimax(arg, &tmp, 10);
513
554
      if(errno != 0 or tmp == arg or *tmp != '\0'
514
 
         or tmpmax != (gid_t)tmpmax){
 
555
         or tmp_id != (gid_t)tmp_id){
515
556
        argp_error(state, "Bad group ID number: \"%s\", using %"
516
557
                   PRIdMAX, arg, (intmax_t)gid);
517
558
        break;
518
559
      }
519
 
      gid = (gid_t)tmpmax;
 
560
      gid = (gid_t)tmp_id;
 
561
      errno = 0;
520
562
      break;
521
563
    case 132:                   /* --debug */
522
564
      debug = true;
523
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;
524
573
      /*
525
574
       * These reproduce what we would get without ARGP_NO_HELP
526
575
       */
527
576
    case '?':                   /* --help */
528
577
      state->flags &= ~(unsigned int)ARGP_NO_EXIT; /* force exit */
529
578
      argp_state_help(state, state->out_stream, ARGP_HELP_STD_HELP);
 
579
      __builtin_unreachable();
530
580
    case -3:                    /* --usage */
531
581
      state->flags &= ~(unsigned int)ARGP_NO_EXIT; /* force exit */
532
582
      argp_state_help(state, state->out_stream,
533
583
                      ARGP_HELP_USAGE | ARGP_HELP_EXIT_OK);
 
584
      __builtin_unreachable();
534
585
    case 'V':                   /* --version */
535
586
      fprintf(state->out_stream, "%s\n", argp_program_version);
536
587
      exit(EXIT_SUCCESS);
546
597
      if(arg[0] == '\0'){
547
598
        break;
548
599
      }
 
600
#if __GNUC__ >= 7
 
601
      __attribute__((fallthrough));
 
602
#else
 
603
          /* FALLTHROUGH */
 
604
#endif
549
605
    default:
550
606
      return ARGP_ERR_UNKNOWN;
551
607
    }
570
626
    case 129:                   /* --config-file */
571
627
      free(argfile);
572
628
      argfile = strdup(arg);
 
629
      if(argfile != NULL){
 
630
        errno = 0;
 
631
      }
573
632
      break;
574
633
    case 130:                   /* --userid */
575
634
    case 131:                   /* --groupid */
576
635
    case 132:                   /* --debug */
 
636
    case 133:                   /* --plugin-helper-dir */
577
637
    case '?':                   /* --help */
578
638
    case -3:                    /* --usage */
579
639
    case 'V':                   /* --version */
658
718
        }
659
719
        
660
720
        custom_argc += 1;
661
 
        custom_argv = realloc(custom_argv, sizeof(char *)
662
 
                              * ((unsigned int) custom_argc + 1));
663
 
        if(custom_argv == NULL){
664
 
          error(0, errno, "realloc");
665
 
          exitstatus = EX_OSERR;
666
 
          free(org_line);
667
 
          goto fallback;
 
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
          }
668
744
        }
669
745
        custom_argv[custom_argc-1] = new_arg;
670
746
        custom_argv[custom_argc] = NULL;
728
804
    goto fallback;
729
805
  }
730
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
  
731
825
  if(debug){
732
 
    for(plugin *p = plugin_list; p != NULL; p=p->next){
 
826
    for(plugin *p = plugin_list; p != NULL; p = p->next){
733
827
      fprintf(stderr, "Plugin: %s has %d arguments\n",
734
828
              p->name ? p->name : "Global", p->argc - 1);
735
829
      for(char **a = p->argv; *a != NULL; a++){
742
836
    }
743
837
  }
744
838
  
745
 
  {
 
839
  if(getuid() == 0){
746
840
    /* Work around Debian bug #633582:
747
 
       <http://bugs.debian.org/633582> */
 
841
       <https://bugs.debian.org/633582> */
748
842
    int plugindir_fd = open(/* plugindir or */ PDIR, O_RDONLY);
749
843
    if(plugindir_fd == -1){
750
 
      error(0, errno, "open");
 
844
      if(errno != ENOENT){
 
845
        error(0, errno, "open(\"" PDIR "\")");
 
846
      }
751
847
    } else {
752
848
      ret = (int)TEMP_FAILURE_RETRY(fstat(plugindir_fd, &st));
753
849
      if(ret == -1){
760
856
          }
761
857
        }
762
858
      }
763
 
      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
      }
764
869
    }
765
870
  }
766
871
  
767
872
  /* Lower permissions */
768
 
  setgid(gid);
 
873
  ret = setgid(gid);
769
874
  if(ret == -1){
770
875
    error(0, errno, "setgid");
771
876
  }
776
881
  
777
882
  /* Open plugin directory with close_on_exec flag */
778
883
  {
779
 
    int dir_fd = -1;
780
 
    if(plugindir == NULL){
781
 
      dir_fd = open(PDIR, O_RDONLY |
782
 
#ifdef O_CLOEXEC
783
 
                    O_CLOEXEC
784
 
#else  /* not O_CLOEXEC */
785
 
                    0
786
 
#endif  /* not O_CLOEXEC */
787
 
                    );
788
 
    } else {
789
 
      dir_fd = open(plugindir, O_RDONLY |
790
 
#ifdef O_CLOEXEC
791
 
                    O_CLOEXEC
792
 
#else  /* not O_CLOEXEC */
793
 
                    0
794
 
#endif  /* not O_CLOEXEC */
795
 
                    );
796
 
    }
 
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
                  );
797
891
    if(dir_fd == -1){
798
892
      error(0, errno, "Could not open plugin dir");
799
893
      exitstatus = EX_UNAVAILABLE;
805
899
    ret = set_cloexec_flag(dir_fd);
806
900
    if(ret < 0){
807
901
      error(0, errno, "set_cloexec_flag");
808
 
      TEMP_FAILURE_RETRY(close(dir_fd));
809
902
      exitstatus = EX_OSERR;
810
903
      goto fallback;
811
904
    }
812
905
#endif  /* O_CLOEXEC */
813
 
    
814
 
    dir = fdopendir(dir_fd);
815
 
    if(dir == NULL){
816
 
      error(0, errno, "Could not open plugin dir");
817
 
      TEMP_FAILURE_RETRY(close(dir_fd));
818
 
      exitstatus = EX_OSERR;
819
 
      goto fallback;
 
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
      }
820
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;
821
940
  }
822
941
  
823
942
  FD_ZERO(&rfds_all);
824
943
  
825
944
  /* Read and execute any executable in the plugin directory*/
826
 
  while(true){
827
 
    do {
828
 
      dirst = readdir(dir);
829
 
    } while(dirst == NULL and errno == EINTR);
830
 
    
831
 
    /* All directory entries have been processed */
832
 
    if(dirst == NULL){
833
 
      if(errno == EBADF){
834
 
        error(0, errno, "readdir");
835
 
        exitstatus = EX_IOERR;
836
 
        goto fallback;
837
 
      }
838
 
      break;
839
 
    }
840
 
    
841
 
    d_name_len = strlen(dirst->d_name);
842
 
    
843
 
    /* Ignore dotfiles, backup files and other junk */
844
 
    {
845
 
      bool bad_name = false;
846
 
      
847
 
      const char const *bad_prefixes[] = { ".", "#", NULL };
848
 
      
849
 
      const char const *bad_suffixes[] = { "~", "#", ".dpkg-new",
850
 
                                           ".dpkg-old",
851
 
                                           ".dpkg-bak",
852
 
                                           ".dpkg-divert", NULL };
853
 
      for(const char **pre = bad_prefixes; *pre != NULL; pre++){
854
 
        size_t pre_len = strlen(*pre);
855
 
        if((d_name_len >= pre_len)
856
 
           and strncmp((dirst->d_name), *pre, pre_len) == 0){
857
 
          if(debug){
858
 
            fprintf(stderr, "Ignoring plugin dir entry \"%s\""
859
 
                    " with bad prefix %s\n", dirst->d_name, *pre);
860
 
          }
861
 
          bad_name = true;
862
 
          break;
863
 
        }
864
 
      }
865
 
      if(bad_name){
866
 
        continue;
867
 
      }
868
 
      for(const char **suf = bad_suffixes; *suf != NULL; suf++){
869
 
        size_t suf_len = strlen(*suf);
870
 
        if((d_name_len >= suf_len)
871
 
           and (strcmp((dirst->d_name) + d_name_len-suf_len, *suf)
872
 
                == 0)){
873
 
          if(debug){
874
 
            fprintf(stderr, "Ignoring plugin dir entry \"%s\""
875
 
                    " with bad suffix %s\n", dirst->d_name, *suf);
876
 
          }
877
 
          bad_name = true;
878
 
          break;
879
 
        }
880
 
      }
881
 
      
882
 
      if(bad_name){
883
 
        continue;
884
 
      }
885
 
    }
886
 
    
887
 
    char *filename;
888
 
    if(plugindir == NULL){
889
 
      ret = (int)TEMP_FAILURE_RETRY(asprintf(&filename, PDIR "/%s",
890
 
                                             dirst->d_name));
891
 
    } else {
892
 
      ret = (int)TEMP_FAILURE_RETRY(asprintf(&filename, "%s/%s",
893
 
                                             plugindir,
894
 
                                             dirst->d_name));
895
 
    }
896
 
    if(ret < 0){
897
 
      error(0, errno, "asprintf");
 
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]);
898
951
      continue;
899
952
    }
900
 
    
901
 
    ret = (int)TEMP_FAILURE_RETRY(stat(filename, &st));
 
953
    ret = (int)TEMP_FAILURE_RETRY(fstat(plugin_fd, &st));
902
954
    if(ret == -1){
903
955
      error(0, errno, "stat");
904
 
      free(filename);
 
956
      close(plugin_fd);
 
957
      free(direntries[i]);
905
958
      continue;
906
959
    }
907
960
    
908
961
    /* Ignore non-executable files */
909
962
    if(not S_ISREG(st.st_mode)
910
 
       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)){
911
965
      if(debug){
912
 
        fprintf(stderr, "Ignoring plugin dir entry \"%s\""
913
 
                " 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);
914
970
      }
915
 
      free(filename);
 
971
      close(plugin_fd);
 
972
      free(direntries[i]);
916
973
      continue;
917
974
    }
918
975
    
919
 
    plugin *p = getplugin(dirst->d_name);
 
976
    plugin *p = getplugin(direntries[i]->d_name);
920
977
    if(p == NULL){
921
978
      error(0, errno, "getplugin");
922
 
      free(filename);
 
979
      close(plugin_fd);
 
980
      free(direntries[i]);
923
981
      continue;
924
982
    }
925
983
    if(p->disabled){
926
984
      if(debug){
927
985
        fprintf(stderr, "Ignoring disabled plugin \"%s\"\n",
928
 
                dirst->d_name);
 
986
                direntries[i]->d_name);
929
987
      }
930
 
      free(filename);
 
988
      close(plugin_fd);
 
989
      free(direntries[i]);
931
990
      continue;
932
991
    }
933
992
    {
947
1006
        }
948
1007
      }
949
1008
    }
950
 
    /* If this plugin has any environment variables, we will call
951
 
       using execve and need to duplicate the environment from this
952
 
       process, too. */
 
1009
    /* If this plugin has any environment variables, we need to
 
1010
       duplicate the environment from this process, too. */
953
1011
    if(p->environ[0] != NULL){
954
1012
      for(char **e = environ; *e != NULL; e++){
955
1013
        if(not add_environment(p, *e, false)){
959
1017
    }
960
1018
    
961
1019
    int pipefd[2];
 
1020
#ifndef O_CLOEXEC
962
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 */
963
1025
    if(ret == -1){
964
1026
      error(0, errno, "pipe");
965
1027
      exitstatus = EX_OSERR;
966
 
      goto fallback;
967
 
    }
 
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
968
1041
    /* Ask OS to automatic close the pipe on exec */
969
1042
    ret = set_cloexec_flag(pipefd[0]);
970
1043
    if(ret < 0){
971
1044
      error(0, errno, "set_cloexec_flag");
 
1045
      close(pipefd[0]);
 
1046
      close(pipefd[1]);
972
1047
      exitstatus = EX_OSERR;
 
1048
      free(direntries[i]);
973
1049
      goto fallback;
974
1050
    }
975
1051
    ret = set_cloexec_flag(pipefd[1]);
976
1052
    if(ret < 0){
977
1053
      error(0, errno, "set_cloexec_flag");
 
1054
      close(pipefd[0]);
 
1055
      close(pipefd[1]);
978
1056
      exitstatus = EX_OSERR;
 
1057
      free(direntries[i]);
979
1058
      goto fallback;
980
1059
    }
 
1060
#endif  /* not O_CLOEXEC */
981
1061
    /* Block SIGCHLD until process is safely in process list */
982
1062
    ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_BLOCK,
983
1063
                                              &sigchld_action.sa_mask,
985
1065
    if(ret < 0){
986
1066
      error(0, errno, "sigprocmask");
987
1067
      exitstatus = EX_OSERR;
 
1068
      free(direntries[i]);
988
1069
      goto fallback;
989
1070
    }
990
1071
    /* Starting a new process to be watched */
994
1075
    } while(pid == -1 and errno == EINTR);
995
1076
    if(pid == -1){
996
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]);
997
1082
      exitstatus = EX_OSERR;
 
1083
      free(direntries[i]);
998
1084
      goto fallback;
999
1085
    }
1000
1086
    if(pid == 0){
1016
1102
        _exit(EX_OSERR);
1017
1103
      }
1018
1104
      
1019
 
      if(dirfd(dir) < 0){
1020
 
        /* If dir has no file descriptor, we could not set FD_CLOEXEC
1021
 
           above and must now close it manually here. */
1022
 
        closedir(dir);
1023
 
      }
1024
 
      if(p->environ[0] == NULL){
1025
 
        if(execv(filename, p->argv) < 0){
1026
 
          error(0, errno, "execv for %s", filename);
1027
 
          _exit(EX_OSERR);
1028
 
        }
1029
 
      } else {
1030
 
        if(execve(filename, p->argv, p->environ) < 0){
1031
 
          error(0, errno, "execve for %s", filename);
1032
 
          _exit(EX_OSERR);
1033
 
        }
 
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);
1034
1111
      }
1035
1112
      /* no return */
1036
1113
    }
1037
1114
    /* Parent process */
1038
 
    TEMP_FAILURE_RETRY(close(pipefd[1])); /* Close unused write end of
1039
 
                                             pipe */
1040
 
    free(filename);
1041
 
    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);
1042
1118
    if(new_plugin == NULL){
1043
1119
      error(0, errno, "getplugin");
1044
1120
      ret = (int)(TEMP_FAILURE_RETRY
1048
1124
        error(0, errno, "sigprocmask");
1049
1125
      }
1050
1126
      exitstatus = EX_OSERR;
 
1127
      free(direntries[i]);
1051
1128
      goto fallback;
1052
1129
    }
 
1130
    free(direntries[i]);
1053
1131
    
1054
1132
    new_plugin->pid = pid;
1055
1133
    new_plugin->fd = pipefd[0];
1056
 
    
 
1134
 
 
1135
    if(debug){
 
1136
      fprintf(stderr, "Plugin %s started (PID %" PRIdMAX ")\n",
 
1137
              new_plugin->name, (intmax_t) (new_plugin->pid));
 
1138
    }
 
1139
 
1057
1140
    /* Unblock SIGCHLD so signal handler can be run if this process
1058
1141
       has already completed */
1059
1142
    ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_UNBLOCK,
1065
1148
      goto fallback;
1066
1149
    }
1067
1150
    
1068
 
    FD_SET(new_plugin->fd, &rfds_all); /* Spurious warning from
1069
 
                                          -Wconversion */
 
1151
    FD_SET(new_plugin->fd, &rfds_all);
1070
1152
    
1071
1153
    if(maxfd < new_plugin->fd){
1072
1154
      maxfd = new_plugin->fd;
1073
1155
    }
1074
1156
  }
1075
1157
  
1076
 
  TEMP_FAILURE_RETRY(closedir(dir));
1077
 
  dir = NULL;
 
1158
  free(direntries);
 
1159
  direntries = NULL;
 
1160
  close(dir_fd);
 
1161
  dir_fd = -1;
1078
1162
  free_plugin(getplugin(NULL));
1079
1163
  
1080
1164
  for(plugin *p = plugin_list; p != NULL; p = p->next){
1119
1203
                      (intmax_t) (proc->pid),
1120
1204
                      WTERMSIG(proc->status),
1121
1205
                      strsignal(WTERMSIG(proc->status)));
1122
 
            } else if(WCOREDUMP(proc->status)){
1123
 
              fprintf(stderr, "Plugin %s [%" PRIdMAX "] dumped"
1124
 
                      " core\n", proc->name, (intmax_t) (proc->pid));
1125
1206
            }
1126
1207
          }
1127
1208
          
1128
1209
          /* Remove the plugin */
1129
 
          FD_CLR(proc->fd, &rfds_all); /* Spurious warning from
1130
 
                                          -Wconversion */
 
1210
          FD_CLR(proc->fd, &rfds_all);
1131
1211
          
1132
1212
          /* Block signal while modifying process_list */
1133
1213
          ret = (int)TEMP_FAILURE_RETRY(sigprocmask
1173
1253
      }
1174
1254
      
1175
1255
      /* This process has not completed.  Does it have any output? */
1176
 
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){ /* Spurious
1177
 
                                                         warning from
1178
 
                                                         -Wconversion */
 
1256
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){
1179
1257
        /* This process had nothing to say at this time */
1180
1258
        proc = proc->next;
1181
1259
        continue;
1182
1260
      }
1183
1261
      /* Before reading, make the process' data buffer large enough */
1184
1262
      if(proc->buffer_length + BUFFER_SIZE > proc->buffer_size){
1185
 
        proc->buffer = realloc(proc->buffer, proc->buffer_size
1186
 
                               + (size_t) BUFFER_SIZE);
1187
 
        if(proc->buffer == NULL){
 
1263
        char *new_buffer = realloc(proc->buffer, proc->buffer_size
 
1264
                                   + (size_t) BUFFER_SIZE);
 
1265
        if(new_buffer == NULL){
1188
1266
          error(0, errno, "malloc");
1189
1267
          exitstatus = EX_OSERR;
1190
1268
          goto fallback;
1191
1269
        }
 
1270
        proc->buffer = new_buffer;
1192
1271
        proc->buffer_size += BUFFER_SIZE;
1193
1272
      }
1194
1273
      /* Read from the process */
1247
1326
    free(custom_argv);
1248
1327
  }
1249
1328
  
1250
 
  if(dir != NULL){
1251
 
    closedir(dir);
 
1329
  free(direntries);
 
1330
  
 
1331
  if(dir_fd != -1){
 
1332
    close(dir_fd);
1252
1333
  }
1253
1334
  
1254
1335
  /* Kill the processes */
1274
1355
  free_plugin_list();
1275
1356
  
1276
1357
  free(plugindir);
 
1358
  free(pluginhelperdir);
1277
1359
  free(argfile);
1278
1360
  
1279
1361
  return exitstatus;