/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-2013 Teddy Hogeborn
6
 
 * Copyright © 2008-2013 Björn Påhlsson
7
 
 * 
8
 
 * This program is free software: you can redistribute it and/or
9
 
 * modify it under the terms of the GNU General Public License as
10
 
 * published by the Free Software Foundation, either version 3 of the
11
 
 * License, or (at your option) any later version.
12
 
 * 
13
 
 * This program is distributed in the hope that it will be useful, but
 
5
 * Copyright © 2008-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
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;
105
106
 
106
107
/* Gets an existing plugin based on name,
107
108
   or if none is found, creates a new one */
 
109
__attribute__((warn_unused_result))
108
110
static plugin *getplugin(char *name){
109
111
  /* Check for existing plugin with that name */
110
112
  for(plugin *p = plugin_list; p != NULL; p = p->next){
171
173
}
172
174
 
173
175
/* Helper function for add_argument and add_environment */
174
 
__attribute__((nonnull))
 
176
__attribute__((nonnull, warn_unused_result))
175
177
static bool add_to_char_array(const char *new, char ***array,
176
178
                              int *len){
177
179
  /* Resize the pointed-to array to hold one more pointer */
178
180
  char **new_array = NULL;
179
181
  do {
180
 
    new_array = realloc(*array, sizeof(char *)
181
 
                        * (size_t) ((*len) + 2));
 
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
182
195
  } while(new_array == NULL and errno == EINTR);
183
196
  /* Malloc check */
184
197
  if(new_array == NULL){
202
215
}
203
216
 
204
217
/* Add to a plugin's argument vector */
205
 
__attribute__((nonnull(2)))
 
218
__attribute__((nonnull(2), warn_unused_result))
206
219
static bool add_argument(plugin *p, const char *arg){
207
220
  if(p == NULL){
208
221
    return false;
211
224
}
212
225
 
213
226
/* Add to a plugin's environment */
214
 
__attribute__((nonnull(2)))
 
227
__attribute__((nonnull(2), warn_unused_result))
215
228
static bool add_environment(plugin *p, const char *def, bool replace){
216
229
  if(p == NULL){
217
230
    return false;
239
252
  return add_to_char_array(def, &(p->environ), &(p->envc));
240
253
}
241
254
 
 
255
#ifndef O_CLOEXEC
242
256
/*
243
257
 * Based on the example in the GNU LibC manual chapter 13.13 "File
244
258
 * Descriptor Flags".
245
259
 | [[info:libc:Descriptor%20Flags][File Descriptor Flags]] |
246
260
 */
 
261
__attribute__((warn_unused_result))
247
262
static int set_cloexec_flag(int fd){
248
263
  int ret = (int)TEMP_FAILURE_RETRY(fcntl(fd, F_GETFD, 0));
249
264
  /* If reading the flags failed, return error indication now. */
254
269
  return (int)TEMP_FAILURE_RETRY(fcntl(fd, F_SETFD,
255
270
                                       ret | FD_CLOEXEC));
256
271
}
 
272
#endif  /* not O_CLOEXEC */
257
273
 
258
274
 
259
275
/* Mark processes as completed when they exit, and save their exit
291
307
}
292
308
 
293
309
/* Prints out a password to stdout */
294
 
__attribute__((nonnull))
 
310
__attribute__((nonnull, warn_unused_result))
295
311
static bool print_out_password(const char *buffer, size_t length){
296
312
  ssize_t ret;
297
313
  for(size_t written = 0; written < length; written += (size_t)ret){
308
324
__attribute__((nonnull))
309
325
static void free_plugin(plugin *plugin_node){
310
326
  
311
 
  for(char **arg = plugin_node->argv; *arg != NULL; arg++){
 
327
  for(char **arg = (plugin_node->argv)+1; *arg != NULL; arg++){
312
328
    free(*arg);
313
329
  }
 
330
  free(plugin_node->name);
314
331
  free(plugin_node->argv);
315
332
  for(char **env = plugin_node->environ; *env != NULL; env++){
316
333
    free(*env);
343
360
 
344
361
int main(int argc, char *argv[]){
345
362
  char *plugindir = NULL;
 
363
  char *pluginhelperdir = NULL;
346
364
  char *argfile = NULL;
347
365
  FILE *conffp;
348
 
  size_t d_name_len;
349
 
  DIR *dir = NULL;
350
 
  struct dirent *dirst;
 
366
  struct dirent **direntries = NULL;
351
367
  struct stat st;
352
368
  fd_set rfds_all;
353
369
  int ret, maxfd = 0;
361
377
                                      .sa_flags = SA_NOCLDSTOP };
362
378
  char **custom_argv = NULL;
363
379
  int custom_argc = 0;
 
380
  int dir_fd = -1;
364
381
  
365
382
  /* Establish a signal handler */
366
383
  sigemptyset(&sigchld_action.sa_mask);
411
428
      .doc = "Group ID the plugins will run as", .group = 3 },
412
429
    { .name = "debug", .key = 132,
413
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 },
414
435
    /*
415
436
     * These reproduce what we would get without ARGP_NO_HELP
416
437
     */
437
458
            break;
438
459
          }
439
460
        }
 
461
        errno = 0;
440
462
      }
441
463
      break;
442
464
    case 'G':                   /* --global-env */
443
 
      add_environment(getplugin(NULL), arg, true);
 
465
      if(add_environment(getplugin(NULL), arg, true)){
 
466
        errno = 0;
 
467
      }
444
468
      break;
445
469
    case 'o':                   /* --options-for */
446
470
      {
463
487
            break;
464
488
          }
465
489
        }
 
490
        errno = 0;
466
491
      }
467
492
      break;
468
493
    case 'E':                   /* --env-for */
480
505
          errno = EINVAL;
481
506
          break;
482
507
        }
483
 
        add_environment(getplugin(arg), envdef, true);
 
508
        if(add_environment(getplugin(arg), envdef, true)){
 
509
          errno = 0;
 
510
        }
484
511
      }
485
512
      break;
486
513
    case 'd':                   /* --disable */
488
515
        plugin *p = getplugin(arg);
489
516
        if(p != NULL){
490
517
          p->disabled = true;
 
518
          errno = 0;
491
519
        }
492
520
      }
493
521
      break;
496
524
        plugin *p = getplugin(arg);
497
525
        if(p != NULL){
498
526
          p->disabled = false;
 
527
          errno = 0;
499
528
        }
500
529
      }
501
530
      break;
502
531
    case 128:                   /* --plugin-dir */
503
532
      free(plugindir);
504
533
      plugindir = strdup(arg);
 
534
      if(plugindir != NULL){
 
535
        errno = 0;
 
536
      }
505
537
      break;
506
538
    case 129:                   /* --config-file */
507
539
      /* This is already done by parse_opt_config_file() */
515
547
        break;
516
548
      }
517
549
      uid = (uid_t)tmp_id;
 
550
      errno = 0;
518
551
      break;
519
552
    case 131:                   /* --groupid */
520
553
      tmp_id = strtoimax(arg, &tmp, 10);
525
558
        break;
526
559
      }
527
560
      gid = (gid_t)tmp_id;
 
561
      errno = 0;
528
562
      break;
529
563
    case 132:                   /* --debug */
530
564
      debug = true;
531
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;
532
573
      /*
533
574
       * These reproduce what we would get without ARGP_NO_HELP
534
575
       */
535
576
    case '?':                   /* --help */
536
577
      state->flags &= ~(unsigned int)ARGP_NO_EXIT; /* force exit */
537
578
      argp_state_help(state, state->out_stream, ARGP_HELP_STD_HELP);
 
579
      __builtin_unreachable();
538
580
    case -3:                    /* --usage */
539
581
      state->flags &= ~(unsigned int)ARGP_NO_EXIT; /* force exit */
540
582
      argp_state_help(state, state->out_stream,
541
583
                      ARGP_HELP_USAGE | ARGP_HELP_EXIT_OK);
 
584
      __builtin_unreachable();
542
585
    case 'V':                   /* --version */
543
586
      fprintf(state->out_stream, "%s\n", argp_program_version);
544
587
      exit(EXIT_SUCCESS);
554
597
      if(arg[0] == '\0'){
555
598
        break;
556
599
      }
 
600
#if __GNUC__ >= 7
 
601
      __attribute__((fallthrough));
 
602
#else
 
603
          /* FALLTHROUGH */
 
604
#endif
557
605
    default:
558
606
      return ARGP_ERR_UNKNOWN;
559
607
    }
578
626
    case 129:                   /* --config-file */
579
627
      free(argfile);
580
628
      argfile = strdup(arg);
 
629
      if(argfile != NULL){
 
630
        errno = 0;
 
631
      }
581
632
      break;
582
633
    case 130:                   /* --userid */
583
634
    case 131:                   /* --groupid */
584
635
    case 132:                   /* --debug */
 
636
    case 133:                   /* --plugin-helper-dir */
585
637
    case '?':                   /* --help */
586
638
    case -3:                    /* --usage */
587
639
    case 'V':                   /* --version */
667
719
        
668
720
        custom_argc += 1;
669
721
        {
670
 
          char **new_argv = realloc(custom_argv, sizeof(char *)
671
 
                                    * ((unsigned int)
672
 
                                       custom_argc + 1));
 
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
673
735
          if(new_argv == NULL){
674
 
            error(0, errno, "realloc");
 
736
            error(0, errno, "reallocarray");
675
737
            exitstatus = EX_OSERR;
676
738
            free(new_arg);
677
739
            free(org_line);
742
804
    goto fallback;
743
805
  }
744
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
  
745
825
  if(debug){
746
 
    for(plugin *p = plugin_list; p != NULL; p=p->next){
 
826
    for(plugin *p = plugin_list; p != NULL; p = p->next){
747
827
      fprintf(stderr, "Plugin: %s has %d arguments\n",
748
828
              p->name ? p->name : "Global", p->argc - 1);
749
829
      for(char **a = p->argv; *a != NULL; a++){
758
838
  
759
839
  if(getuid() == 0){
760
840
    /* Work around Debian bug #633582:
761
 
       <http://bugs.debian.org/633582> */
 
841
       <https://bugs.debian.org/633582> */
762
842
    int plugindir_fd = open(/* plugindir or */ PDIR, O_RDONLY);
763
843
    if(plugindir_fd == -1){
764
 
      error(0, errno, "open");
 
844
      if(errno != ENOENT){
 
845
        error(0, errno, "open(\"" PDIR "\")");
 
846
      }
765
847
    } else {
766
848
      ret = (int)TEMP_FAILURE_RETRY(fstat(plugindir_fd, &st));
767
849
      if(ret == -1){
774
856
          }
775
857
        }
776
858
      }
777
 
      TEMP_FAILURE_RETRY(close(plugindir_fd));
 
859
      close(plugindir_fd);
778
860
    }
779
861
  }
780
862
  
790
872
  
791
873
  /* Open plugin directory with close_on_exec flag */
792
874
  {
793
 
    int dir_fd = -1;
794
 
    if(plugindir == NULL){
795
 
      dir_fd = open(PDIR, O_RDONLY |
796
 
#ifdef O_CLOEXEC
797
 
                    O_CLOEXEC
798
 
#else  /* not O_CLOEXEC */
799
 
                    0
800
 
#endif  /* not O_CLOEXEC */
801
 
                    );
802
 
    } else {
803
 
      dir_fd = open(plugindir, O_RDONLY |
804
 
#ifdef O_CLOEXEC
805
 
                    O_CLOEXEC
806
 
#else  /* not O_CLOEXEC */
807
 
                    0
808
 
#endif  /* not O_CLOEXEC */
809
 
                    );
810
 
    }
 
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
                  );
811
882
    if(dir_fd == -1){
812
883
      error(0, errno, "Could not open plugin dir");
813
884
      exitstatus = EX_UNAVAILABLE;
819
890
    ret = set_cloexec_flag(dir_fd);
820
891
    if(ret < 0){
821
892
      error(0, errno, "set_cloexec_flag");
822
 
      TEMP_FAILURE_RETRY(close(dir_fd));
823
893
      exitstatus = EX_OSERR;
824
894
      goto fallback;
825
895
    }
826
896
#endif  /* O_CLOEXEC */
827
 
    
828
 
    dir = fdopendir(dir_fd);
829
 
    if(dir == NULL){
830
 
      error(0, errno, "Could not open plugin dir");
831
 
      TEMP_FAILURE_RETRY(close(dir_fd));
832
 
      exitstatus = EX_OSERR;
833
 
      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
      }
834
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;
835
931
  }
836
932
  
837
933
  FD_ZERO(&rfds_all);
838
934
  
839
935
  /* Read and execute any executable in the plugin directory*/
840
 
  while(true){
841
 
    do {
842
 
      dirst = readdir(dir);
843
 
    } while(dirst == NULL and errno == EINTR);
844
 
    
845
 
    /* All directory entries have been processed */
846
 
    if(dirst == NULL){
847
 
      if(errno == EBADF){
848
 
        error(0, errno, "readdir");
849
 
        exitstatus = EX_IOERR;
850
 
        goto fallback;
851
 
      }
852
 
      break;
853
 
    }
854
 
    
855
 
    d_name_len = strlen(dirst->d_name);
856
 
    
857
 
    /* Ignore dotfiles, backup files and other junk */
858
 
    {
859
 
      bool bad_name = false;
860
 
      
861
 
      const char * const bad_prefixes[] = { ".", "#", NULL };
862
 
      
863
 
      const char * const bad_suffixes[] = { "~", "#", ".dpkg-new",
864
 
                                           ".dpkg-old",
865
 
                                           ".dpkg-bak",
866
 
                                           ".dpkg-divert", NULL };
867
 
#ifdef __GNUC__
868
 
#pragma GCC diagnostic push
869
 
#pragma GCC diagnostic ignored "-Wcast-qual"
870
 
#endif
871
 
      for(const char **pre = (const char **)bad_prefixes;
872
 
          *pre != NULL; pre++){
873
 
#ifdef __GNUC__
874
 
#pragma GCC diagnostic pop
875
 
#endif
876
 
        size_t pre_len = strlen(*pre);
877
 
        if((d_name_len >= pre_len)
878
 
           and strncmp((dirst->d_name), *pre, pre_len) == 0){
879
 
          if(debug){
880
 
            fprintf(stderr, "Ignoring plugin dir entry \"%s\""
881
 
                    " with bad prefix %s\n", dirst->d_name, *pre);
882
 
          }
883
 
          bad_name = true;
884
 
          break;
885
 
        }
886
 
      }
887
 
      if(bad_name){
888
 
        continue;
889
 
      }
890
 
#ifdef __GNUC__
891
 
#pragma GCC diagnostic push
892
 
#pragma GCC diagnostic ignored "-Wcast-qual"
893
 
#endif
894
 
      for(const char **suf = (const char **)bad_suffixes;
895
 
          *suf != NULL; suf++){
896
 
#ifdef __GNUC__
897
 
#pragma GCC diagnostic pop
898
 
#endif
899
 
        size_t suf_len = strlen(*suf);
900
 
        if((d_name_len >= suf_len)
901
 
           and (strcmp((dirst->d_name) + d_name_len-suf_len, *suf)
902
 
                == 0)){
903
 
          if(debug){
904
 
            fprintf(stderr, "Ignoring plugin dir entry \"%s\""
905
 
                    " with bad suffix %s\n", dirst->d_name, *suf);
906
 
          }
907
 
          bad_name = true;
908
 
          break;
909
 
        }
910
 
      }
911
 
      
912
 
      if(bad_name){
913
 
        continue;
914
 
      }
915
 
    }
916
 
    
917
 
    char *filename;
918
 
    if(plugindir == NULL){
919
 
      ret = (int)TEMP_FAILURE_RETRY(asprintf(&filename, PDIR "/%s",
920
 
                                             dirst->d_name));
921
 
    } else {
922
 
      ret = (int)TEMP_FAILURE_RETRY(asprintf(&filename, "%s/%s",
923
 
                                             plugindir,
924
 
                                             dirst->d_name));
925
 
    }
926
 
    if(ret < 0){
927
 
      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]);
928
942
      continue;
929
943
    }
930
 
    
931
 
    ret = (int)TEMP_FAILURE_RETRY(stat(filename, &st));
 
944
    ret = (int)TEMP_FAILURE_RETRY(fstat(plugin_fd, &st));
932
945
    if(ret == -1){
933
946
      error(0, errno, "stat");
934
 
      free(filename);
 
947
      close(plugin_fd);
 
948
      free(direntries[i]);
935
949
      continue;
936
950
    }
937
951
    
938
952
    /* Ignore non-executable files */
939
953
    if(not S_ISREG(st.st_mode)
940
 
       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)){
941
956
      if(debug){
942
 
        fprintf(stderr, "Ignoring plugin dir entry \"%s\""
943
 
                " 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);
944
961
      }
945
 
      free(filename);
 
962
      close(plugin_fd);
 
963
      free(direntries[i]);
946
964
      continue;
947
965
    }
948
966
    
949
 
    plugin *p = getplugin(dirst->d_name);
 
967
    plugin *p = getplugin(direntries[i]->d_name);
950
968
    if(p == NULL){
951
969
      error(0, errno, "getplugin");
952
 
      free(filename);
 
970
      close(plugin_fd);
 
971
      free(direntries[i]);
953
972
      continue;
954
973
    }
955
974
    if(p->disabled){
956
975
      if(debug){
957
976
        fprintf(stderr, "Ignoring disabled plugin \"%s\"\n",
958
 
                dirst->d_name);
 
977
                direntries[i]->d_name);
959
978
      }
960
 
      free(filename);
 
979
      close(plugin_fd);
 
980
      free(direntries[i]);
961
981
      continue;
962
982
    }
963
983
    {
977
997
        }
978
998
      }
979
999
    }
980
 
    /* If this plugin has any environment variables, we will call
981
 
       using execve and need to duplicate the environment from this
982
 
       process, too. */
 
1000
    /* If this plugin has any environment variables, we need to
 
1001
       duplicate the environment from this process, too. */
983
1002
    if(p->environ[0] != NULL){
984
1003
      for(char **e = environ; *e != NULL; e++){
985
1004
        if(not add_environment(p, *e, false)){
989
1008
    }
990
1009
    
991
1010
    int pipefd[2];
 
1011
#ifndef O_CLOEXEC
992
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 */
993
1016
    if(ret == -1){
994
1017
      error(0, errno, "pipe");
995
1018
      exitstatus = EX_OSERR;
996
 
      goto fallback;
997
 
    }
 
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
998
1032
    /* Ask OS to automatic close the pipe on exec */
999
1033
    ret = set_cloexec_flag(pipefd[0]);
1000
1034
    if(ret < 0){
1001
1035
      error(0, errno, "set_cloexec_flag");
 
1036
      close(pipefd[0]);
 
1037
      close(pipefd[1]);
1002
1038
      exitstatus = EX_OSERR;
 
1039
      free(direntries[i]);
1003
1040
      goto fallback;
1004
1041
    }
1005
1042
    ret = set_cloexec_flag(pipefd[1]);
1006
1043
    if(ret < 0){
1007
1044
      error(0, errno, "set_cloexec_flag");
 
1045
      close(pipefd[0]);
 
1046
      close(pipefd[1]);
1008
1047
      exitstatus = EX_OSERR;
 
1048
      free(direntries[i]);
1009
1049
      goto fallback;
1010
1050
    }
 
1051
#endif  /* not O_CLOEXEC */
1011
1052
    /* Block SIGCHLD until process is safely in process list */
1012
1053
    ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_BLOCK,
1013
1054
                                              &sigchld_action.sa_mask,
1015
1056
    if(ret < 0){
1016
1057
      error(0, errno, "sigprocmask");
1017
1058
      exitstatus = EX_OSERR;
 
1059
      free(direntries[i]);
1018
1060
      goto fallback;
1019
1061
    }
1020
1062
    /* Starting a new process to be watched */
1024
1066
    } while(pid == -1 and errno == EINTR);
1025
1067
    if(pid == -1){
1026
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]);
1027
1073
      exitstatus = EX_OSERR;
 
1074
      free(direntries[i]);
1028
1075
      goto fallback;
1029
1076
    }
1030
1077
    if(pid == 0){
1046
1093
        _exit(EX_OSERR);
1047
1094
      }
1048
1095
      
1049
 
      if(dirfd(dir) < 0){
1050
 
        /* If dir has no file descriptor, we could not set FD_CLOEXEC
1051
 
           above and must now close it manually here. */
1052
 
        closedir(dir);
1053
 
      }
1054
 
      if(p->environ[0] == NULL){
1055
 
        if(execv(filename, p->argv) < 0){
1056
 
          error(0, errno, "execv for %s", filename);
1057
 
          _exit(EX_OSERR);
1058
 
        }
1059
 
      } else {
1060
 
        if(execve(filename, p->argv, p->environ) < 0){
1061
 
          error(0, errno, "execve for %s", filename);
1062
 
          _exit(EX_OSERR);
1063
 
        }
 
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);
1064
1102
      }
1065
1103
      /* no return */
1066
1104
    }
1067
1105
    /* Parent process */
1068
 
    TEMP_FAILURE_RETRY(close(pipefd[1])); /* Close unused write end of
1069
 
                                             pipe */
1070
 
    free(filename);
1071
 
    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);
1072
1109
    if(new_plugin == NULL){
1073
1110
      error(0, errno, "getplugin");
1074
1111
      ret = (int)(TEMP_FAILURE_RETRY
1078
1115
        error(0, errno, "sigprocmask");
1079
1116
      }
1080
1117
      exitstatus = EX_OSERR;
 
1118
      free(direntries[i]);
1081
1119
      goto fallback;
1082
1120
    }
 
1121
    free(direntries[i]);
1083
1122
    
1084
1123
    new_plugin->pid = pid;
1085
1124
    new_plugin->fd = pipefd[0];
1086
 
    
 
1125
 
 
1126
    if(debug){
 
1127
      fprintf(stderr, "Plugin %s started (PID %" PRIdMAX ")\n",
 
1128
              new_plugin->name, (intmax_t) (new_plugin->pid));
 
1129
    }
 
1130
 
1087
1131
    /* Unblock SIGCHLD so signal handler can be run if this process
1088
1132
       has already completed */
1089
1133
    ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_UNBLOCK,
1095
1139
      goto fallback;
1096
1140
    }
1097
1141
    
1098
 
#if defined (__GNUC__) and defined (__GLIBC__)
1099
 
#if not __GLIBC_PREREQ(2, 16)
1100
 
#pragma GCC diagnostic push
1101
 
#pragma GCC diagnostic ignored "-Wsign-conversion"
1102
 
#endif
1103
 
#endif
1104
 
    FD_SET(new_plugin->fd, &rfds_all); /* Spurious warning from
1105
 
                                          -Wconversion in GNU libc
1106
 
                                          before 2.16 */
1107
 
#if defined (__GNUC__) and defined (__GLIBC__)
1108
 
#if not __GLIBC_PREREQ(2, 16)
1109
 
#pragma GCC diagnostic pop
1110
 
#endif
1111
 
#endif
 
1142
    FD_SET(new_plugin->fd, &rfds_all);
1112
1143
    
1113
1144
    if(maxfd < new_plugin->fd){
1114
1145
      maxfd = new_plugin->fd;
1115
1146
    }
1116
1147
  }
1117
1148
  
1118
 
  TEMP_FAILURE_RETRY(closedir(dir));
1119
 
  dir = NULL;
 
1149
  free(direntries);
 
1150
  direntries = NULL;
 
1151
  close(dir_fd);
 
1152
  dir_fd = -1;
1120
1153
  free_plugin(getplugin(NULL));
1121
1154
  
1122
1155
  for(plugin *p = plugin_list; p != NULL; p = p->next){
1161
1194
                      (intmax_t) (proc->pid),
1162
1195
                      WTERMSIG(proc->status),
1163
1196
                      strsignal(WTERMSIG(proc->status)));
1164
 
            } else if(WCOREDUMP(proc->status)){
1165
 
              fprintf(stderr, "Plugin %s [%" PRIdMAX "] dumped"
1166
 
                      " core\n", proc->name, (intmax_t) (proc->pid));
1167
1197
            }
1168
1198
          }
1169
1199
          
1170
1200
          /* Remove the plugin */
1171
 
#if defined (__GNUC__) and defined (__GLIBC__)
1172
 
#if not __GLIBC_PREREQ(2, 16)
1173
 
#pragma GCC diagnostic push
1174
 
#pragma GCC diagnostic ignored "-Wsign-conversion"
1175
 
#endif
1176
 
#endif
1177
 
          FD_CLR(proc->fd, &rfds_all); /* Spurious warning from
1178
 
                                          -Wconversion in GNU libc
1179
 
                                          before 2.16 */
1180
 
#if defined (__GNUC__) and defined (__GLIBC__)
1181
 
#if not __GLIBC_PREREQ(2, 16)
1182
 
#pragma GCC diagnostic pop
1183
 
#endif
1184
 
#endif
 
1201
          FD_CLR(proc->fd, &rfds_all);
1185
1202
          
1186
1203
          /* Block signal while modifying process_list */
1187
1204
          ret = (int)TEMP_FAILURE_RETRY(sigprocmask
1227
1244
      }
1228
1245
      
1229
1246
      /* This process has not completed.  Does it have any output? */
1230
 
#if defined (__GNUC__) and defined (__GLIBC__)
1231
 
#if not __GLIBC_PREREQ(2, 16)
1232
 
#pragma GCC diagnostic push
1233
 
#pragma GCC diagnostic ignored "-Wsign-conversion"
1234
 
#endif
1235
 
#endif
1236
 
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){ /* Spurious
1237
 
                                                         warning from
1238
 
                                                         -Wconversion
1239
 
                                                         in GNU libc
1240
 
                                                         before
1241
 
                                                         2.16 */
1242
 
#if defined (__GNUC__) and defined (__GLIBC__)
1243
 
#if not __GLIBC_PREREQ(2, 16)
1244
 
#pragma GCC diagnostic pop
1245
 
#endif
1246
 
#endif
 
1247
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){
1247
1248
        /* This process had nothing to say at this time */
1248
1249
        proc = proc->next;
1249
1250
        continue;
1316
1317
    free(custom_argv);
1317
1318
  }
1318
1319
  
1319
 
  if(dir != NULL){
1320
 
    closedir(dir);
 
1320
  free(direntries);
 
1321
  
 
1322
  if(dir_fd != -1){
 
1323
    close(dir_fd);
1321
1324
  }
1322
1325
  
1323
1326
  /* Kill the processes */
1343
1346
  free_plugin_list();
1344
1347
  
1345
1348
  free(plugindir);
 
1349
  free(pluginhelperdir);
1346
1350
  free(argfile);
1347
1351
  
1348
1352
  return exitstatus;