/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,2009 Teddy Hogeborn
6
 
 * Copyright © 2008,2009 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
 
 * Contact the authors at <mandos@fukt.bsnet.se>.
 
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;
82
 
const char *argp_program_bug_address = "<mandos@fukt.bsnet.se>";
 
83
const char *argp_program_bug_address = "<mandos@recompile.se>";
83
84
 
84
85
typedef struct plugin{
85
86
  char *name;                   /* can be NULL or any plugin name */
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
 
  /* Strip permissions down to nobody */
746
 
  setgid(gid);
 
839
  if(getuid() == 0){
 
840
    /* Work around Debian bug #633582:
 
841
       <https://bugs.debian.org/633582> */
 
842
    int plugindir_fd = open(/* plugindir or */ PDIR, O_RDONLY);
 
843
    if(plugindir_fd == -1){
 
844
      if(errno != ENOENT){
 
845
        error(0, errno, "open(\"" PDIR "\")");
 
846
      }
 
847
    } else {
 
848
      ret = (int)TEMP_FAILURE_RETRY(fstat(plugindir_fd, &st));
 
849
      if(ret == -1){
 
850
        error(0, errno, "fstat");
 
851
      } else {
 
852
        if(S_ISDIR(st.st_mode) and st.st_uid == 0 and st.st_gid == 0){
 
853
          ret = fchown(plugindir_fd, uid, gid);
 
854
          if(ret == -1){
 
855
            error(0, errno, "fchown");
 
856
          }
 
857
        }
 
858
      }
 
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
      }
 
869
    }
 
870
  }
 
871
  
 
872
  /* Lower permissions */
 
873
  ret = setgid(gid);
747
874
  if(ret == -1){
748
875
    error(0, errno, "setgid");
749
876
  }
754
881
  
755
882
  /* Open plugin directory with close_on_exec flag */
756
883
  {
757
 
    int dir_fd = -1;
758
 
    if(plugindir == NULL){
759
 
      dir_fd = open(PDIR, O_RDONLY |
760
 
#ifdef O_CLOEXEC
761
 
                    O_CLOEXEC
762
 
#else  /* not O_CLOEXEC */
763
 
                    0
764
 
#endif  /* not O_CLOEXEC */
765
 
                    );
766
 
    } else {
767
 
      dir_fd = open(plugindir, O_RDONLY |
768
 
#ifdef O_CLOEXEC
769
 
                    O_CLOEXEC
770
 
#else  /* not O_CLOEXEC */
771
 
                    0
772
 
#endif  /* not O_CLOEXEC */
773
 
                    );
774
 
    }
 
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
                  );
775
891
    if(dir_fd == -1){
776
892
      error(0, errno, "Could not open plugin dir");
777
893
      exitstatus = EX_UNAVAILABLE;
783
899
    ret = set_cloexec_flag(dir_fd);
784
900
    if(ret < 0){
785
901
      error(0, errno, "set_cloexec_flag");
786
 
      TEMP_FAILURE_RETRY(close(dir_fd));
787
902
      exitstatus = EX_OSERR;
788
903
      goto fallback;
789
904
    }
790
905
#endif  /* O_CLOEXEC */
791
 
    
792
 
    dir = fdopendir(dir_fd);
793
 
    if(dir == NULL){
794
 
      error(0, errno, "Could not open plugin dir");
795
 
      TEMP_FAILURE_RETRY(close(dir_fd));
796
 
      exitstatus = EX_OSERR;
797
 
      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
      }
798
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;
799
940
  }
800
941
  
801
942
  FD_ZERO(&rfds_all);
802
943
  
803
944
  /* Read and execute any executable in the plugin directory*/
804
 
  while(true){
805
 
    do {
806
 
      dirst = readdir(dir);
807
 
    } while(dirst == NULL and errno == EINTR);
808
 
    
809
 
    /* All directory entries have been processed */
810
 
    if(dirst == NULL){
811
 
      if(errno == EBADF){
812
 
        error(0, errno, "readdir");
813
 
        exitstatus = EX_IOERR;
814
 
        goto fallback;
815
 
      }
816
 
      break;
817
 
    }
818
 
    
819
 
    d_name_len = strlen(dirst->d_name);
820
 
    
821
 
    /* Ignore dotfiles, backup files and other junk */
822
 
    {
823
 
      bool bad_name = false;
824
 
      
825
 
      const char const *bad_prefixes[] = { ".", "#", NULL };
826
 
      
827
 
      const char const *bad_suffixes[] = { "~", "#", ".dpkg-new",
828
 
                                           ".dpkg-old",
829
 
                                           ".dpkg-bak",
830
 
                                           ".dpkg-divert", NULL };
831
 
      for(const char **pre = bad_prefixes; *pre != NULL; pre++){
832
 
        size_t pre_len = strlen(*pre);
833
 
        if((d_name_len >= pre_len)
834
 
           and strncmp((dirst->d_name), *pre, pre_len) == 0){
835
 
          if(debug){
836
 
            fprintf(stderr, "Ignoring plugin dir entry \"%s\""
837
 
                    " with bad prefix %s\n", dirst->d_name, *pre);
838
 
          }
839
 
          bad_name = true;
840
 
          break;
841
 
        }
842
 
      }
843
 
      if(bad_name){
844
 
        continue;
845
 
      }
846
 
      for(const char **suf = bad_suffixes; *suf != NULL; suf++){
847
 
        size_t suf_len = strlen(*suf);
848
 
        if((d_name_len >= suf_len)
849
 
           and (strcmp((dirst->d_name) + d_name_len-suf_len, *suf)
850
 
                == 0)){
851
 
          if(debug){
852
 
            fprintf(stderr, "Ignoring plugin dir entry \"%s\""
853
 
                    " with bad suffix %s\n", dirst->d_name, *suf);
854
 
          }
855
 
          bad_name = true;
856
 
          break;
857
 
        }
858
 
      }
859
 
      
860
 
      if(bad_name){
861
 
        continue;
862
 
      }
863
 
    }
864
 
    
865
 
    char *filename;
866
 
    if(plugindir == NULL){
867
 
      ret = (int)TEMP_FAILURE_RETRY(asprintf(&filename, PDIR "/%s",
868
 
                                             dirst->d_name));
869
 
    } else {
870
 
      ret = (int)TEMP_FAILURE_RETRY(asprintf(&filename, "%s/%s",
871
 
                                             plugindir,
872
 
                                             dirst->d_name));
873
 
    }
874
 
    if(ret < 0){
875
 
      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]);
876
951
      continue;
877
952
    }
878
 
    
879
 
    ret = (int)TEMP_FAILURE_RETRY(stat(filename, &st));
 
953
    ret = (int)TEMP_FAILURE_RETRY(fstat(plugin_fd, &st));
880
954
    if(ret == -1){
881
955
      error(0, errno, "stat");
882
 
      free(filename);
 
956
      close(plugin_fd);
 
957
      free(direntries[i]);
883
958
      continue;
884
959
    }
885
960
    
886
961
    /* Ignore non-executable files */
887
962
    if(not S_ISREG(st.st_mode)
888
 
       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)){
889
965
      if(debug){
890
 
        fprintf(stderr, "Ignoring plugin dir entry \"%s\""
891
 
                " 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);
892
970
      }
893
 
      free(filename);
 
971
      close(plugin_fd);
 
972
      free(direntries[i]);
894
973
      continue;
895
974
    }
896
975
    
897
 
    plugin *p = getplugin(dirst->d_name);
 
976
    plugin *p = getplugin(direntries[i]->d_name);
898
977
    if(p == NULL){
899
978
      error(0, errno, "getplugin");
900
 
      free(filename);
 
979
      close(plugin_fd);
 
980
      free(direntries[i]);
901
981
      continue;
902
982
    }
903
983
    if(p->disabled){
904
984
      if(debug){
905
985
        fprintf(stderr, "Ignoring disabled plugin \"%s\"\n",
906
 
                dirst->d_name);
 
986
                direntries[i]->d_name);
907
987
      }
908
 
      free(filename);
 
988
      close(plugin_fd);
 
989
      free(direntries[i]);
909
990
      continue;
910
991
    }
911
992
    {
925
1006
        }
926
1007
      }
927
1008
    }
928
 
    /* If this plugin has any environment variables, we will call
929
 
       using execve and need to duplicate the environment from this
930
 
       process, too. */
 
1009
    /* If this plugin has any environment variables, we need to
 
1010
       duplicate the environment from this process, too. */
931
1011
    if(p->environ[0] != NULL){
932
1012
      for(char **e = environ; *e != NULL; e++){
933
1013
        if(not add_environment(p, *e, false)){
937
1017
    }
938
1018
    
939
1019
    int pipefd[2];
 
1020
#ifndef O_CLOEXEC
940
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 */
941
1025
    if(ret == -1){
942
1026
      error(0, errno, "pipe");
943
1027
      exitstatus = EX_OSERR;
944
 
      goto fallback;
945
 
    }
 
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
946
1041
    /* Ask OS to automatic close the pipe on exec */
947
1042
    ret = set_cloexec_flag(pipefd[0]);
948
1043
    if(ret < 0){
949
1044
      error(0, errno, "set_cloexec_flag");
 
1045
      close(pipefd[0]);
 
1046
      close(pipefd[1]);
950
1047
      exitstatus = EX_OSERR;
 
1048
      free(direntries[i]);
951
1049
      goto fallback;
952
1050
    }
953
1051
    ret = set_cloexec_flag(pipefd[1]);
954
1052
    if(ret < 0){
955
1053
      error(0, errno, "set_cloexec_flag");
 
1054
      close(pipefd[0]);
 
1055
      close(pipefd[1]);
956
1056
      exitstatus = EX_OSERR;
 
1057
      free(direntries[i]);
957
1058
      goto fallback;
958
1059
    }
 
1060
#endif  /* not O_CLOEXEC */
959
1061
    /* Block SIGCHLD until process is safely in process list */
960
1062
    ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_BLOCK,
961
1063
                                              &sigchld_action.sa_mask,
963
1065
    if(ret < 0){
964
1066
      error(0, errno, "sigprocmask");
965
1067
      exitstatus = EX_OSERR;
 
1068
      free(direntries[i]);
966
1069
      goto fallback;
967
1070
    }
968
1071
    /* Starting a new process to be watched */
972
1075
    } while(pid == -1 and errno == EINTR);
973
1076
    if(pid == -1){
974
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]);
975
1082
      exitstatus = EX_OSERR;
 
1083
      free(direntries[i]);
976
1084
      goto fallback;
977
1085
    }
978
1086
    if(pid == 0){
994
1102
        _exit(EX_OSERR);
995
1103
      }
996
1104
      
997
 
      if(dirfd(dir) < 0){
998
 
        /* If dir has no file descriptor, we could not set FD_CLOEXEC
999
 
           above and must now close it manually here. */
1000
 
        closedir(dir);
1001
 
      }
1002
 
      if(p->environ[0] == NULL){
1003
 
        if(execv(filename, p->argv) < 0){
1004
 
          error(0, errno, "execv");
1005
 
          _exit(EX_OSERR);
1006
 
        }
1007
 
      } else {
1008
 
        if(execve(filename, p->argv, p->environ) < 0){
1009
 
          error(0, errno, "execve");
1010
 
          _exit(EX_OSERR);
1011
 
        }
 
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);
1012
1111
      }
1013
1112
      /* no return */
1014
1113
    }
1015
1114
    /* Parent process */
1016
 
    TEMP_FAILURE_RETRY(close(pipefd[1])); /* Close unused write end of
1017
 
                                             pipe */
1018
 
    free(filename);
1019
 
    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);
1020
1118
    if(new_plugin == NULL){
1021
1119
      error(0, errno, "getplugin");
1022
1120
      ret = (int)(TEMP_FAILURE_RETRY
1026
1124
        error(0, errno, "sigprocmask");
1027
1125
      }
1028
1126
      exitstatus = EX_OSERR;
 
1127
      free(direntries[i]);
1029
1128
      goto fallback;
1030
1129
    }
 
1130
    free(direntries[i]);
1031
1131
    
1032
1132
    new_plugin->pid = pid;
1033
1133
    new_plugin->fd = pipefd[0];
1034
 
    
 
1134
 
 
1135
    if(debug){
 
1136
      fprintf(stderr, "Plugin %s started (PID %" PRIdMAX ")\n",
 
1137
              new_plugin->name, (intmax_t) (new_plugin->pid));
 
1138
    }
 
1139
 
1035
1140
    /* Unblock SIGCHLD so signal handler can be run if this process
1036
1141
       has already completed */
1037
1142
    ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_UNBLOCK,
1043
1148
      goto fallback;
1044
1149
    }
1045
1150
    
1046
 
    FD_SET(new_plugin->fd, &rfds_all); /* Spurious warning from
1047
 
                                          -Wconversion */
 
1151
    FD_SET(new_plugin->fd, &rfds_all);
1048
1152
    
1049
1153
    if(maxfd < new_plugin->fd){
1050
1154
      maxfd = new_plugin->fd;
1051
1155
    }
1052
1156
  }
1053
1157
  
1054
 
  TEMP_FAILURE_RETRY(closedir(dir));
1055
 
  dir = NULL;
 
1158
  free(direntries);
 
1159
  direntries = NULL;
 
1160
  close(dir_fd);
 
1161
  dir_fd = -1;
1056
1162
  free_plugin(getplugin(NULL));
1057
1163
  
1058
1164
  for(plugin *p = plugin_list; p != NULL; p = p->next){
1097
1203
                      (intmax_t) (proc->pid),
1098
1204
                      WTERMSIG(proc->status),
1099
1205
                      strsignal(WTERMSIG(proc->status)));
1100
 
            } else if(WCOREDUMP(proc->status)){
1101
 
              fprintf(stderr, "Plugin %s [%" PRIdMAX "] dumped"
1102
 
                      " core\n", proc->name, (intmax_t) (proc->pid));
1103
1206
            }
1104
1207
          }
1105
1208
          
1106
1209
          /* Remove the plugin */
1107
 
          FD_CLR(proc->fd, &rfds_all); /* Spurious warning from
1108
 
                                          -Wconversion */
 
1210
          FD_CLR(proc->fd, &rfds_all);
1109
1211
          
1110
1212
          /* Block signal while modifying process_list */
1111
1213
          ret = (int)TEMP_FAILURE_RETRY(sigprocmask
1151
1253
      }
1152
1254
      
1153
1255
      /* This process has not completed.  Does it have any output? */
1154
 
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){ /* Spurious
1155
 
                                                         warning from
1156
 
                                                         -Wconversion */
 
1256
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){
1157
1257
        /* This process had nothing to say at this time */
1158
1258
        proc = proc->next;
1159
1259
        continue;
1160
1260
      }
1161
1261
      /* Before reading, make the process' data buffer large enough */
1162
1262
      if(proc->buffer_length + BUFFER_SIZE > proc->buffer_size){
1163
 
        proc->buffer = realloc(proc->buffer, proc->buffer_size
1164
 
                               + (size_t) BUFFER_SIZE);
1165
 
        if(proc->buffer == NULL){
 
1263
        char *new_buffer = realloc(proc->buffer, proc->buffer_size
 
1264
                                   + (size_t) BUFFER_SIZE);
 
1265
        if(new_buffer == NULL){
1166
1266
          error(0, errno, "malloc");
1167
1267
          exitstatus = EX_OSERR;
1168
1268
          goto fallback;
1169
1269
        }
 
1270
        proc->buffer = new_buffer;
1170
1271
        proc->buffer_size += BUFFER_SIZE;
1171
1272
      }
1172
1273
      /* Read from the process */
1225
1326
    free(custom_argv);
1226
1327
  }
1227
1328
  
1228
 
  if(dir != NULL){
1229
 
    closedir(dir);
 
1329
  free(direntries);
 
1330
  
 
1331
  if(dir_fd != -1){
 
1332
    close(dir_fd);
1230
1333
  }
1231
1334
  
1232
1335
  /* Kill the processes */
1252
1355
  free_plugin_list();
1253
1356
  
1254
1357
  free(plugindir);
 
1358
  free(pluginhelperdir);
1255
1359
  free(argfile);
1256
1360
  
1257
1361
  return exitstatus;