/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 at recompile
  • Date: 2020-04-05 21:30:59 UTC
  • Revision ID: teddy@recompile.se-20200405213059-fb2a61ckqynrmatk
Fix file descriptor leak in mandos-client

When the local network has Mandos servers announcing themselves using
real, globally reachable, IPv6 addresses (i.e. not link-local
addresses), but there is no router on the local network providing IPv6
RA (Router Advertisement) packets, the client cannot reach the server
by normal means, since the client only has a link-local IPv6 address,
and has no usable route to reach the server's global IPv6 address.
(This is not a common situation, and usually only happens when the
router itself reboots and runs a Mandos client, since it cannot then
give RA packets to itself.)  The client code has a solution for
this, which consists of adding a temporary local route to reach the
address of the server during communication, and removing this
temporary route afterwards.

This solution with a temporary route works, but has a file descriptor
leak; it leaks one file descriptor for each addition and for each
removal of a route.  If one server requiring an added route is present
on the network, but no servers gives a password, making the client
retry after the default ten seconds, and we furthermore assume a
default 1024 open files limit, the client runs out of file descriptors
after about 90 minutes, after which time the client process will be
useless and fail to retrieve any passwords, necessitating manual
password entry via the keyboard.

Fix this by eliminating the file descriptor leak in the client.

* plugins.d/mandos-client.c (add_delete_local_route): Do
  close(devnull) also in parent process, also if fork() fails, and on
  any failure in child process.

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-2010 Teddy Hogeborn
6
 
 * Copyright © 2008-2010 Björn Påhlsson
7
 
 * 
8
 
 * This program is free software: you can redistribute it and/or
9
 
 * modify it under the terms of the GNU General Public License as
10
 
 * published by the Free Software Foundation, either version 3 of the
11
 
 * License, or (at your option) any later version.
12
 
 * 
13
 
 * This program is distributed in the hope that it will be useful, but
 
5
 * Copyright © 2008-2018 Teddy Hogeborn
 
6
 * Copyright © 2008-2018 Björn Påhlsson
 
7
 * 
 
8
 * This file is part of Mandos.
 
9
 * 
 
10
 * Mandos is free software: you can redistribute it and/or modify it
 
11
 * under the terms of the GNU General Public License as published by
 
12
 * the Free Software Foundation, either version 3 of the License, or
 
13
 * (at your option) any later version.
 
14
 * 
 
15
 * Mandos is distributed in the hope that it will be useful, but
14
16
 * WITHOUT ANY WARRANTY; without even the implied warranty of
15
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16
18
 * General Public License for more details.
17
19
 * 
18
20
 * You should have received a copy of the GNU General Public License
19
 
 * along with this program.  If not, see
20
 
 * <http://www.gnu.org/licenses/>.
 
21
 * along with Mandos.  If not, see <http://www.gnu.org/licenses/>.
21
22
 * 
22
 
 * 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
                                */
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
  
 
863
  /* Lower permissions */
 
864
  ret = setgid(gid);
747
865
  if(ret == -1){
748
866
    error(0, errno, "setgid");
749
867
  }
754
872
  
755
873
  /* Open plugin directory with close_on_exec flag */
756
874
  {
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
 
    }
 
875
    dir_fd = open(plugindir != NULL ? plugindir : PDIR, O_RDONLY |
 
876
#ifdef O_CLOEXEC
 
877
                  O_CLOEXEC
 
878
#else  /* not O_CLOEXEC */
 
879
                  0
 
880
#endif  /* not O_CLOEXEC */
 
881
                  );
775
882
    if(dir_fd == -1){
776
883
      error(0, errno, "Could not open plugin dir");
777
884
      exitstatus = EX_UNAVAILABLE;
783
890
    ret = set_cloexec_flag(dir_fd);
784
891
    if(ret < 0){
785
892
      error(0, errno, "set_cloexec_flag");
786
 
      TEMP_FAILURE_RETRY(close(dir_fd));
787
893
      exitstatus = EX_OSERR;
788
894
      goto fallback;
789
895
    }
790
896
#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;
 
897
  }
 
898
  
 
899
  int good_name(const struct dirent * const dirent){
 
900
    const char * const patterns[] = { ".*", "#*#", "*~", "*.dpkg-new",
 
901
                                      "*.dpkg-old", "*.dpkg-bak",
 
902
                                      "*.dpkg-divert", NULL };
 
903
#ifdef __GNUC__
 
904
#pragma GCC diagnostic push
 
905
#pragma GCC diagnostic ignored "-Wcast-qual"
 
906
#endif
 
907
    for(const char **pat = (const char **)patterns;
 
908
        *pat != NULL; pat++){
 
909
#ifdef __GNUC__
 
910
#pragma GCC diagnostic pop
 
911
#endif
 
912
      if(fnmatch(*pat, dirent->d_name, FNM_FILE_NAME | FNM_PERIOD)
 
913
         != FNM_NOMATCH){
 
914
        if(debug){
 
915
            fprintf(stderr, "Ignoring plugin dir entry \"%s\""
 
916
                    " matching pattern %s\n", dirent->d_name, *pat);
 
917
        }
 
918
        return 0;
 
919
      }
798
920
    }
 
921
    return 1;
 
922
  }
 
923
  
 
924
  int numplugins = scandirat(dir_fd, ".", &direntries, good_name,
 
925
                             alphasort);
 
926
  if(numplugins == -1){
 
927
    error(0, errno, "Could not scan plugin dir");
 
928
    direntries = NULL;
 
929
    exitstatus = EX_OSERR;
 
930
    goto fallback;
799
931
  }
800
932
  
801
933
  FD_ZERO(&rfds_all);
802
934
  
803
935
  /* 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");
 
936
  for(int i = 0; i < numplugins; i++){
 
937
    
 
938
    int plugin_fd = openat(dir_fd, direntries[i]->d_name, O_RDONLY);
 
939
    if(plugin_fd == -1){
 
940
      error(0, errno, "Could not open plugin");
 
941
      free(direntries[i]);
876
942
      continue;
877
943
    }
878
 
    
879
 
    ret = (int)TEMP_FAILURE_RETRY(stat(filename, &st));
 
944
    ret = (int)TEMP_FAILURE_RETRY(fstat(plugin_fd, &st));
880
945
    if(ret == -1){
881
946
      error(0, errno, "stat");
882
 
      free(filename);
 
947
      close(plugin_fd);
 
948
      free(direntries[i]);
883
949
      continue;
884
950
    }
885
951
    
886
952
    /* Ignore non-executable files */
887
953
    if(not S_ISREG(st.st_mode)
888
 
       or (TEMP_FAILURE_RETRY(access(filename, X_OK)) != 0)){
 
954
       or (TEMP_FAILURE_RETRY(faccessat(dir_fd, direntries[i]->d_name,
 
955
                                        X_OK, 0)) != 0)){
889
956
      if(debug){
890
 
        fprintf(stderr, "Ignoring plugin dir entry \"%s\""
891
 
                " with bad type or mode\n", filename);
 
957
        fprintf(stderr, "Ignoring plugin dir entry \"%s/%s\""
 
958
                " with bad type or mode\n",
 
959
                plugindir != NULL ? plugindir : PDIR,
 
960
                direntries[i]->d_name);
892
961
      }
893
 
      free(filename);
 
962
      close(plugin_fd);
 
963
      free(direntries[i]);
894
964
      continue;
895
965
    }
896
966
    
897
 
    plugin *p = getplugin(dirst->d_name);
 
967
    plugin *p = getplugin(direntries[i]->d_name);
898
968
    if(p == NULL){
899
969
      error(0, errno, "getplugin");
900
 
      free(filename);
 
970
      close(plugin_fd);
 
971
      free(direntries[i]);
901
972
      continue;
902
973
    }
903
974
    if(p->disabled){
904
975
      if(debug){
905
976
        fprintf(stderr, "Ignoring disabled plugin \"%s\"\n",
906
 
                dirst->d_name);
 
977
                direntries[i]->d_name);
907
978
      }
908
 
      free(filename);
 
979
      close(plugin_fd);
 
980
      free(direntries[i]);
909
981
      continue;
910
982
    }
911
983
    {
925
997
        }
926
998
      }
927
999
    }
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. */
 
1000
    /* If this plugin has any environment variables, we need to
 
1001
       duplicate the environment from this process, too. */
931
1002
    if(p->environ[0] != NULL){
932
1003
      for(char **e = environ; *e != NULL; e++){
933
1004
        if(not add_environment(p, *e, false)){
937
1008
    }
938
1009
    
939
1010
    int pipefd[2];
 
1011
#ifndef O_CLOEXEC
940
1012
    ret = (int)TEMP_FAILURE_RETRY(pipe(pipefd));
 
1013
#else  /* O_CLOEXEC */
 
1014
    ret = (int)TEMP_FAILURE_RETRY(pipe2(pipefd, O_CLOEXEC));
 
1015
#endif  /* O_CLOEXEC */
941
1016
    if(ret == -1){
942
1017
      error(0, errno, "pipe");
943
1018
      exitstatus = EX_OSERR;
944
 
      goto fallback;
945
 
    }
 
1019
      free(direntries[i]);
 
1020
      goto fallback;
 
1021
    }
 
1022
    if(pipefd[0] >= FD_SETSIZE){
 
1023
      fprintf(stderr, "pipe()[0] (%d) >= FD_SETSIZE (%d)", pipefd[0],
 
1024
              FD_SETSIZE);
 
1025
      close(pipefd[0]);
 
1026
      close(pipefd[1]);
 
1027
      exitstatus = EX_OSERR;
 
1028
      free(direntries[i]);
 
1029
      goto fallback;
 
1030
    }
 
1031
#ifndef O_CLOEXEC
946
1032
    /* Ask OS to automatic close the pipe on exec */
947
1033
    ret = set_cloexec_flag(pipefd[0]);
948
1034
    if(ret < 0){
949
1035
      error(0, errno, "set_cloexec_flag");
 
1036
      close(pipefd[0]);
 
1037
      close(pipefd[1]);
950
1038
      exitstatus = EX_OSERR;
 
1039
      free(direntries[i]);
951
1040
      goto fallback;
952
1041
    }
953
1042
    ret = set_cloexec_flag(pipefd[1]);
954
1043
    if(ret < 0){
955
1044
      error(0, errno, "set_cloexec_flag");
 
1045
      close(pipefd[0]);
 
1046
      close(pipefd[1]);
956
1047
      exitstatus = EX_OSERR;
 
1048
      free(direntries[i]);
957
1049
      goto fallback;
958
1050
    }
 
1051
#endif  /* not O_CLOEXEC */
959
1052
    /* Block SIGCHLD until process is safely in process list */
960
1053
    ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_BLOCK,
961
1054
                                              &sigchld_action.sa_mask,
963
1056
    if(ret < 0){
964
1057
      error(0, errno, "sigprocmask");
965
1058
      exitstatus = EX_OSERR;
 
1059
      free(direntries[i]);
966
1060
      goto fallback;
967
1061
    }
968
1062
    /* Starting a new process to be watched */
972
1066
    } while(pid == -1 and errno == EINTR);
973
1067
    if(pid == -1){
974
1068
      error(0, errno, "fork");
 
1069
      TEMP_FAILURE_RETRY(sigprocmask(SIG_UNBLOCK,
 
1070
                                     &sigchld_action.sa_mask, NULL));
 
1071
      close(pipefd[0]);
 
1072
      close(pipefd[1]);
975
1073
      exitstatus = EX_OSERR;
 
1074
      free(direntries[i]);
976
1075
      goto fallback;
977
1076
    }
978
1077
    if(pid == 0){
994
1093
        _exit(EX_OSERR);
995
1094
      }
996
1095
      
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 for %s", filename);
1005
 
          _exit(EX_OSERR);
1006
 
        }
1007
 
      } else {
1008
 
        if(execve(filename, p->argv, p->environ) < 0){
1009
 
          error(0, errno, "execve for %s", filename);
1010
 
          _exit(EX_OSERR);
1011
 
        }
 
1096
      if(fexecve(plugin_fd, p->argv,
 
1097
                (p->environ[0] != NULL) ? p->environ : environ) < 0){
 
1098
        error(0, errno, "fexecve for %s/%s",
 
1099
              plugindir != NULL ? plugindir : PDIR,
 
1100
              direntries[i]->d_name);
 
1101
        _exit(EX_OSERR);
1012
1102
      }
1013
1103
      /* no return */
1014
1104
    }
1015
1105
    /* 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);
 
1106
    close(pipefd[1]);           /* Close unused write end of pipe */
 
1107
    close(plugin_fd);
 
1108
    plugin *new_plugin = getplugin(direntries[i]->d_name);
1020
1109
    if(new_plugin == NULL){
1021
1110
      error(0, errno, "getplugin");
1022
1111
      ret = (int)(TEMP_FAILURE_RETRY
1026
1115
        error(0, errno, "sigprocmask");
1027
1116
      }
1028
1117
      exitstatus = EX_OSERR;
 
1118
      free(direntries[i]);
1029
1119
      goto fallback;
1030
1120
    }
 
1121
    free(direntries[i]);
1031
1122
    
1032
1123
    new_plugin->pid = pid;
1033
1124
    new_plugin->fd = pipefd[0];
1034
 
    
 
1125
 
 
1126
    if(debug){
 
1127
      fprintf(stderr, "Plugin %s started (PID %" PRIdMAX ")\n",
 
1128
              new_plugin->name, (intmax_t) (new_plugin->pid));
 
1129
    }
 
1130
 
1035
1131
    /* Unblock SIGCHLD so signal handler can be run if this process
1036
1132
       has already completed */
1037
1133
    ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_UNBLOCK,
1043
1139
      goto fallback;
1044
1140
    }
1045
1141
    
1046
 
    FD_SET(new_plugin->fd, &rfds_all); /* Spurious warning from
1047
 
                                          -Wconversion */
 
1142
    FD_SET(new_plugin->fd, &rfds_all);
1048
1143
    
1049
1144
    if(maxfd < new_plugin->fd){
1050
1145
      maxfd = new_plugin->fd;
1051
1146
    }
1052
1147
  }
1053
1148
  
1054
 
  TEMP_FAILURE_RETRY(closedir(dir));
1055
 
  dir = NULL;
 
1149
  free(direntries);
 
1150
  direntries = NULL;
 
1151
  close(dir_fd);
 
1152
  dir_fd = -1;
1056
1153
  free_plugin(getplugin(NULL));
1057
1154
  
1058
1155
  for(plugin *p = plugin_list; p != NULL; p = p->next){
1097
1194
                      (intmax_t) (proc->pid),
1098
1195
                      WTERMSIG(proc->status),
1099
1196
                      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
1197
            }
1104
1198
          }
1105
1199
          
1106
1200
          /* Remove the plugin */
1107
 
          FD_CLR(proc->fd, &rfds_all); /* Spurious warning from
1108
 
                                          -Wconversion */
 
1201
          FD_CLR(proc->fd, &rfds_all);
1109
1202
          
1110
1203
          /* Block signal while modifying process_list */
1111
1204
          ret = (int)TEMP_FAILURE_RETRY(sigprocmask
1151
1244
      }
1152
1245
      
1153
1246
      /* 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 */
 
1247
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){
1157
1248
        /* This process had nothing to say at this time */
1158
1249
        proc = proc->next;
1159
1250
        continue;
1160
1251
      }
1161
1252
      /* Before reading, make the process' data buffer large enough */
1162
1253
      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){
 
1254
        char *new_buffer = realloc(proc->buffer, proc->buffer_size
 
1255
                                   + (size_t) BUFFER_SIZE);
 
1256
        if(new_buffer == NULL){
1166
1257
          error(0, errno, "malloc");
1167
1258
          exitstatus = EX_OSERR;
1168
1259
          goto fallback;
1169
1260
        }
 
1261
        proc->buffer = new_buffer;
1170
1262
        proc->buffer_size += BUFFER_SIZE;
1171
1263
      }
1172
1264
      /* Read from the process */
1225
1317
    free(custom_argv);
1226
1318
  }
1227
1319
  
1228
 
  if(dir != NULL){
1229
 
    closedir(dir);
 
1320
  free(direntries);
 
1321
  
 
1322
  if(dir_fd != -1){
 
1323
    close(dir_fd);
1230
1324
  }
1231
1325
  
1232
1326
  /* Kill the processes */
1252
1346
  free_plugin_list();
1253
1347
  
1254
1348
  free(plugindir);
 
1349
  free(pluginhelperdir);
1255
1350
  free(argfile);
1256
1351
  
1257
1352
  return exitstatus;