/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
 
  {
 
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);
764
860
    }
765
861
  }
766
862
  
767
863
  /* Lower permissions */
768
 
  setgid(gid);
 
864
  ret = setgid(gid);
769
865
  if(ret == -1){
770
866
    error(0, errno, "setgid");
771
867
  }
776
872
  
777
873
  /* Open plugin directory with close_on_exec flag */
778
874
  {
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
 
    }
 
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
                  );
797
882
    if(dir_fd == -1){
798
883
      error(0, errno, "Could not open plugin dir");
799
884
      exitstatus = EX_UNAVAILABLE;
805
890
    ret = set_cloexec_flag(dir_fd);
806
891
    if(ret < 0){
807
892
      error(0, errno, "set_cloexec_flag");
808
 
      TEMP_FAILURE_RETRY(close(dir_fd));
809
893
      exitstatus = EX_OSERR;
810
894
      goto fallback;
811
895
    }
812
896
#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;
 
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
      }
820
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;
821
931
  }
822
932
  
823
933
  FD_ZERO(&rfds_all);
824
934
  
825
935
  /* 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");
 
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]);
898
942
      continue;
899
943
    }
900
 
    
901
 
    ret = (int)TEMP_FAILURE_RETRY(stat(filename, &st));
 
944
    ret = (int)TEMP_FAILURE_RETRY(fstat(plugin_fd, &st));
902
945
    if(ret == -1){
903
946
      error(0, errno, "stat");
904
 
      free(filename);
 
947
      close(plugin_fd);
 
948
      free(direntries[i]);
905
949
      continue;
906
950
    }
907
951
    
908
952
    /* Ignore non-executable files */
909
953
    if(not S_ISREG(st.st_mode)
910
 
       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)){
911
956
      if(debug){
912
 
        fprintf(stderr, "Ignoring plugin dir entry \"%s\""
913
 
                " 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);
914
961
      }
915
 
      free(filename);
 
962
      close(plugin_fd);
 
963
      free(direntries[i]);
916
964
      continue;
917
965
    }
918
966
    
919
 
    plugin *p = getplugin(dirst->d_name);
 
967
    plugin *p = getplugin(direntries[i]->d_name);
920
968
    if(p == NULL){
921
969
      error(0, errno, "getplugin");
922
 
      free(filename);
 
970
      close(plugin_fd);
 
971
      free(direntries[i]);
923
972
      continue;
924
973
    }
925
974
    if(p->disabled){
926
975
      if(debug){
927
976
        fprintf(stderr, "Ignoring disabled plugin \"%s\"\n",
928
 
                dirst->d_name);
 
977
                direntries[i]->d_name);
929
978
      }
930
 
      free(filename);
 
979
      close(plugin_fd);
 
980
      free(direntries[i]);
931
981
      continue;
932
982
    }
933
983
    {
947
997
        }
948
998
      }
949
999
    }
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. */
 
1000
    /* If this plugin has any environment variables, we need to
 
1001
       duplicate the environment from this process, too. */
953
1002
    if(p->environ[0] != NULL){
954
1003
      for(char **e = environ; *e != NULL; e++){
955
1004
        if(not add_environment(p, *e, false)){
959
1008
    }
960
1009
    
961
1010
    int pipefd[2];
 
1011
#ifndef O_CLOEXEC
962
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 */
963
1016
    if(ret == -1){
964
1017
      error(0, errno, "pipe");
965
1018
      exitstatus = EX_OSERR;
966
 
      goto fallback;
967
 
    }
 
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
968
1032
    /* Ask OS to automatic close the pipe on exec */
969
1033
    ret = set_cloexec_flag(pipefd[0]);
970
1034
    if(ret < 0){
971
1035
      error(0, errno, "set_cloexec_flag");
 
1036
      close(pipefd[0]);
 
1037
      close(pipefd[1]);
972
1038
      exitstatus = EX_OSERR;
 
1039
      free(direntries[i]);
973
1040
      goto fallback;
974
1041
    }
975
1042
    ret = set_cloexec_flag(pipefd[1]);
976
1043
    if(ret < 0){
977
1044
      error(0, errno, "set_cloexec_flag");
 
1045
      close(pipefd[0]);
 
1046
      close(pipefd[1]);
978
1047
      exitstatus = EX_OSERR;
 
1048
      free(direntries[i]);
979
1049
      goto fallback;
980
1050
    }
 
1051
#endif  /* not O_CLOEXEC */
981
1052
    /* Block SIGCHLD until process is safely in process list */
982
1053
    ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_BLOCK,
983
1054
                                              &sigchld_action.sa_mask,
985
1056
    if(ret < 0){
986
1057
      error(0, errno, "sigprocmask");
987
1058
      exitstatus = EX_OSERR;
 
1059
      free(direntries[i]);
988
1060
      goto fallback;
989
1061
    }
990
1062
    /* Starting a new process to be watched */
994
1066
    } while(pid == -1 and errno == EINTR);
995
1067
    if(pid == -1){
996
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]);
997
1073
      exitstatus = EX_OSERR;
 
1074
      free(direntries[i]);
998
1075
      goto fallback;
999
1076
    }
1000
1077
    if(pid == 0){
1016
1093
        _exit(EX_OSERR);
1017
1094
      }
1018
1095
      
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
 
        }
 
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);
1034
1102
      }
1035
1103
      /* no return */
1036
1104
    }
1037
1105
    /* 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);
 
1106
    close(pipefd[1]);           /* Close unused write end of pipe */
 
1107
    close(plugin_fd);
 
1108
    plugin *new_plugin = getplugin(direntries[i]->d_name);
1042
1109
    if(new_plugin == NULL){
1043
1110
      error(0, errno, "getplugin");
1044
1111
      ret = (int)(TEMP_FAILURE_RETRY
1048
1115
        error(0, errno, "sigprocmask");
1049
1116
      }
1050
1117
      exitstatus = EX_OSERR;
 
1118
      free(direntries[i]);
1051
1119
      goto fallback;
1052
1120
    }
 
1121
    free(direntries[i]);
1053
1122
    
1054
1123
    new_plugin->pid = pid;
1055
1124
    new_plugin->fd = pipefd[0];
1056
 
    
 
1125
 
 
1126
    if(debug){
 
1127
      fprintf(stderr, "Plugin %s started (PID %" PRIdMAX ")\n",
 
1128
              new_plugin->name, (intmax_t) (new_plugin->pid));
 
1129
    }
 
1130
 
1057
1131
    /* Unblock SIGCHLD so signal handler can be run if this process
1058
1132
       has already completed */
1059
1133
    ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_UNBLOCK,
1065
1139
      goto fallback;
1066
1140
    }
1067
1141
    
1068
 
    FD_SET(new_plugin->fd, &rfds_all); /* Spurious warning from
1069
 
                                          -Wconversion */
 
1142
    FD_SET(new_plugin->fd, &rfds_all);
1070
1143
    
1071
1144
    if(maxfd < new_plugin->fd){
1072
1145
      maxfd = new_plugin->fd;
1073
1146
    }
1074
1147
  }
1075
1148
  
1076
 
  TEMP_FAILURE_RETRY(closedir(dir));
1077
 
  dir = NULL;
 
1149
  free(direntries);
 
1150
  direntries = NULL;
 
1151
  close(dir_fd);
 
1152
  dir_fd = -1;
1078
1153
  free_plugin(getplugin(NULL));
1079
1154
  
1080
1155
  for(plugin *p = plugin_list; p != NULL; p = p->next){
1119
1194
                      (intmax_t) (proc->pid),
1120
1195
                      WTERMSIG(proc->status),
1121
1196
                      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
1197
            }
1126
1198
          }
1127
1199
          
1128
1200
          /* Remove the plugin */
1129
 
          FD_CLR(proc->fd, &rfds_all); /* Spurious warning from
1130
 
                                          -Wconversion */
 
1201
          FD_CLR(proc->fd, &rfds_all);
1131
1202
          
1132
1203
          /* Block signal while modifying process_list */
1133
1204
          ret = (int)TEMP_FAILURE_RETRY(sigprocmask
1173
1244
      }
1174
1245
      
1175
1246
      /* 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 */
 
1247
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){
1179
1248
        /* This process had nothing to say at this time */
1180
1249
        proc = proc->next;
1181
1250
        continue;
1182
1251
      }
1183
1252
      /* Before reading, make the process' data buffer large enough */
1184
1253
      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){
 
1254
        char *new_buffer = realloc(proc->buffer, proc->buffer_size
 
1255
                                   + (size_t) BUFFER_SIZE);
 
1256
        if(new_buffer == NULL){
1188
1257
          error(0, errno, "malloc");
1189
1258
          exitstatus = EX_OSERR;
1190
1259
          goto fallback;
1191
1260
        }
 
1261
        proc->buffer = new_buffer;
1192
1262
        proc->buffer_size += BUFFER_SIZE;
1193
1263
      }
1194
1264
      /* Read from the process */
1247
1317
    free(custom_argv);
1248
1318
  }
1249
1319
  
1250
 
  if(dir != NULL){
1251
 
    closedir(dir);
 
1320
  free(direntries);
 
1321
  
 
1322
  if(dir_fd != -1){
 
1323
    close(dir_fd);
1252
1324
  }
1253
1325
  
1254
1326
  /* Kill the processes */
1274
1346
  free_plugin_list();
1275
1347
  
1276
1348
  free(plugindir);
 
1349
  free(pluginhelperdir);
1277
1350
  free(argfile);
1278
1351
  
1279
1352
  return exitstatus;