/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-2012 Teddy Hogeborn
6
 
 * Copyright © 2008-2012 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 */
 
180
  char **new_array = NULL;
178
181
  do {
179
 
    *array = realloc(*array, sizeof(char *)
180
 
                     * (size_t) ((*len) + 2));
181
 
  } 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);
182
196
  /* Malloc check */
183
 
  if(*array == NULL){
 
197
  if(new_array == NULL){
184
198
    return false;
185
199
  }
 
200
  *array = new_array;
186
201
  /* Make a copy of the new string */
187
202
  char *copy;
188
203
  do {
200
215
}
201
216
 
202
217
/* Add to a plugin's argument vector */
203
 
__attribute__((nonnull(2)))
 
218
__attribute__((nonnull(2), warn_unused_result))
204
219
static bool add_argument(plugin *p, const char *arg){
205
220
  if(p == NULL){
206
221
    return false;
209
224
}
210
225
 
211
226
/* Add to a plugin's environment */
212
 
__attribute__((nonnull(2)))
 
227
__attribute__((nonnull(2), warn_unused_result))
213
228
static bool add_environment(plugin *p, const char *def, bool replace){
214
229
  if(p == NULL){
215
230
    return false;
217
232
  /* namelen = length of name of environment variable */
218
233
  size_t namelen = (size_t)(strchrnul(def, '=') - def);
219
234
  /* Search for this environment variable */
220
 
  for(char **e = p->environ; *e != NULL; e++){
221
 
    if(strncmp(*e, def, namelen + 1) == 0){
 
235
  for(char **envdef = p->environ; *envdef != NULL; envdef++){
 
236
    if(strncmp(*envdef, def, namelen + 1) == 0){
222
237
      /* It already exists */
223
238
      if(replace){
224
 
        char *new;
 
239
        char *new_envdef;
225
240
        do {
226
 
          new = realloc(*e, strlen(def) + 1);
227
 
        } while(new == NULL and errno == EINTR);
228
 
        if(new == NULL){
 
241
          new_envdef = realloc(*envdef, strlen(def) + 1);
 
242
        } while(new_envdef == NULL and errno == EINTR);
 
243
        if(new_envdef == NULL){
229
244
          return false;
230
245
        }
231
 
        *e = new;
232
 
        strcpy(*e, def);
 
246
        *envdef = new_envdef;
 
247
        strcpy(*envdef, def);
233
248
      }
234
249
      return true;
235
250
    }
237
252
  return add_to_char_array(def, &(p->environ), &(p->envc));
238
253
}
239
254
 
 
255
#ifndef O_CLOEXEC
240
256
/*
241
257
 * Based on the example in the GNU LibC manual chapter 13.13 "File
242
258
 * Descriptor Flags".
243
259
 | [[info:libc:Descriptor%20Flags][File Descriptor Flags]] |
244
260
 */
 
261
__attribute__((warn_unused_result))
245
262
static int set_cloexec_flag(int fd){
246
263
  int ret = (int)TEMP_FAILURE_RETRY(fcntl(fd, F_GETFD, 0));
247
264
  /* If reading the flags failed, return error indication now. */
252
269
  return (int)TEMP_FAILURE_RETRY(fcntl(fd, F_SETFD,
253
270
                                       ret | FD_CLOEXEC));
254
271
}
 
272
#endif  /* not O_CLOEXEC */
255
273
 
256
274
 
257
275
/* Mark processes as completed when they exit, and save their exit
289
307
}
290
308
 
291
309
/* Prints out a password to stdout */
292
 
__attribute__((nonnull))
 
310
__attribute__((nonnull, warn_unused_result))
293
311
static bool print_out_password(const char *buffer, size_t length){
294
312
  ssize_t ret;
295
313
  for(size_t written = 0; written < length; written += (size_t)ret){
306
324
__attribute__((nonnull))
307
325
static void free_plugin(plugin *plugin_node){
308
326
  
309
 
  for(char **arg = plugin_node->argv; *arg != NULL; arg++){
 
327
  for(char **arg = (plugin_node->argv)+1; *arg != NULL; arg++){
310
328
    free(*arg);
311
329
  }
 
330
  free(plugin_node->name);
312
331
  free(plugin_node->argv);
313
332
  for(char **env = plugin_node->environ; *env != NULL; env++){
314
333
    free(*env);
341
360
 
342
361
int main(int argc, char *argv[]){
343
362
  char *plugindir = NULL;
 
363
  char *pluginhelperdir = NULL;
344
364
  char *argfile = NULL;
345
365
  FILE *conffp;
346
 
  size_t d_name_len;
347
 
  DIR *dir = NULL;
348
 
  struct dirent *dirst;
 
366
  struct dirent **direntries = NULL;
349
367
  struct stat st;
350
368
  fd_set rfds_all;
351
369
  int ret, maxfd = 0;
359
377
                                      .sa_flags = SA_NOCLDSTOP };
360
378
  char **custom_argv = NULL;
361
379
  int custom_argc = 0;
 
380
  int dir_fd = -1;
362
381
  
363
382
  /* Establish a signal handler */
364
383
  sigemptyset(&sigchld_action.sa_mask);
409
428
      .doc = "Group ID the plugins will run as", .group = 3 },
410
429
    { .name = "debug", .key = 132,
411
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 },
412
435
    /*
413
436
     * These reproduce what we would get without ARGP_NO_HELP
414
437
     */
435
458
            break;
436
459
          }
437
460
        }
 
461
        errno = 0;
438
462
      }
439
463
      break;
440
464
    case 'G':                   /* --global-env */
441
 
      add_environment(getplugin(NULL), arg, true);
 
465
      if(add_environment(getplugin(NULL), arg, true)){
 
466
        errno = 0;
 
467
      }
442
468
      break;
443
469
    case 'o':                   /* --options-for */
444
470
      {
461
487
            break;
462
488
          }
463
489
        }
 
490
        errno = 0;
464
491
      }
465
492
      break;
466
493
    case 'E':                   /* --env-for */
478
505
          errno = EINVAL;
479
506
          break;
480
507
        }
481
 
        add_environment(getplugin(arg), envdef, true);
 
508
        if(add_environment(getplugin(arg), envdef, true)){
 
509
          errno = 0;
 
510
        }
482
511
      }
483
512
      break;
484
513
    case 'd':                   /* --disable */
486
515
        plugin *p = getplugin(arg);
487
516
        if(p != NULL){
488
517
          p->disabled = true;
 
518
          errno = 0;
489
519
        }
490
520
      }
491
521
      break;
494
524
        plugin *p = getplugin(arg);
495
525
        if(p != NULL){
496
526
          p->disabled = false;
 
527
          errno = 0;
497
528
        }
498
529
      }
499
530
      break;
500
531
    case 128:                   /* --plugin-dir */
501
532
      free(plugindir);
502
533
      plugindir = strdup(arg);
 
534
      if(plugindir != NULL){
 
535
        errno = 0;
 
536
      }
503
537
      break;
504
538
    case 129:                   /* --config-file */
505
539
      /* This is already done by parse_opt_config_file() */
513
547
        break;
514
548
      }
515
549
      uid = (uid_t)tmp_id;
 
550
      errno = 0;
516
551
      break;
517
552
    case 131:                   /* --groupid */
518
553
      tmp_id = strtoimax(arg, &tmp, 10);
523
558
        break;
524
559
      }
525
560
      gid = (gid_t)tmp_id;
 
561
      errno = 0;
526
562
      break;
527
563
    case 132:                   /* --debug */
528
564
      debug = true;
529
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;
530
573
      /*
531
574
       * These reproduce what we would get without ARGP_NO_HELP
532
575
       */
533
576
    case '?':                   /* --help */
534
577
      state->flags &= ~(unsigned int)ARGP_NO_EXIT; /* force exit */
535
578
      argp_state_help(state, state->out_stream, ARGP_HELP_STD_HELP);
 
579
      __builtin_unreachable();
536
580
    case -3:                    /* --usage */
537
581
      state->flags &= ~(unsigned int)ARGP_NO_EXIT; /* force exit */
538
582
      argp_state_help(state, state->out_stream,
539
583
                      ARGP_HELP_USAGE | ARGP_HELP_EXIT_OK);
 
584
      __builtin_unreachable();
540
585
    case 'V':                   /* --version */
541
586
      fprintf(state->out_stream, "%s\n", argp_program_version);
542
587
      exit(EXIT_SUCCESS);
552
597
      if(arg[0] == '\0'){
553
598
        break;
554
599
      }
 
600
#if __GNUC__ >= 7
 
601
      __attribute__((fallthrough));
 
602
#else
 
603
          /* FALLTHROUGH */
 
604
#endif
555
605
    default:
556
606
      return ARGP_ERR_UNKNOWN;
557
607
    }
576
626
    case 129:                   /* --config-file */
577
627
      free(argfile);
578
628
      argfile = strdup(arg);
 
629
      if(argfile != NULL){
 
630
        errno = 0;
 
631
      }
579
632
      break;
580
633
    case 130:                   /* --userid */
581
634
    case 131:                   /* --groupid */
582
635
    case 132:                   /* --debug */
 
636
    case 133:                   /* --plugin-helper-dir */
583
637
    case '?':                   /* --help */
584
638
    case -3:                    /* --usage */
585
639
    case 'V':                   /* --version */
664
718
        }
665
719
        
666
720
        custom_argc += 1;
667
 
        custom_argv = realloc(custom_argv, sizeof(char *)
668
 
                              * ((unsigned int) custom_argc + 1));
669
 
        if(custom_argv == NULL){
670
 
          error(0, errno, "realloc");
671
 
          exitstatus = EX_OSERR;
672
 
          free(org_line);
673
 
          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
          }
674
744
        }
675
745
        custom_argv[custom_argc-1] = new_arg;
676
746
        custom_argv[custom_argc] = NULL;
734
804
    goto fallback;
735
805
  }
736
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
  
737
825
  if(debug){
738
 
    for(plugin *p = plugin_list; p != NULL; p=p->next){
 
826
    for(plugin *p = plugin_list; p != NULL; p = p->next){
739
827
      fprintf(stderr, "Plugin: %s has %d arguments\n",
740
828
              p->name ? p->name : "Global", p->argc - 1);
741
829
      for(char **a = p->argv; *a != NULL; a++){
750
838
  
751
839
  if(getuid() == 0){
752
840
    /* Work around Debian bug #633582:
753
 
       <http://bugs.debian.org/633582> */
 
841
       <https://bugs.debian.org/633582> */
754
842
    int plugindir_fd = open(/* plugindir or */ PDIR, O_RDONLY);
755
843
    if(plugindir_fd == -1){
756
 
      error(0, errno, "open");
 
844
      if(errno != ENOENT){
 
845
        error(0, errno, "open(\"" PDIR "\")");
 
846
      }
757
847
    } else {
758
848
      ret = (int)TEMP_FAILURE_RETRY(fstat(plugindir_fd, &st));
759
849
      if(ret == -1){
766
856
          }
767
857
        }
768
858
      }
769
 
      TEMP_FAILURE_RETRY(close(plugindir_fd));
 
859
      close(plugindir_fd);
770
860
    }
771
861
  }
772
862
  
773
863
  /* Lower permissions */
774
 
  setgid(gid);
 
864
  ret = setgid(gid);
775
865
  if(ret == -1){
776
866
    error(0, errno, "setgid");
777
867
  }
782
872
  
783
873
  /* Open plugin directory with close_on_exec flag */
784
874
  {
785
 
    int dir_fd = -1;
786
 
    if(plugindir == NULL){
787
 
      dir_fd = open(PDIR, O_RDONLY |
788
 
#ifdef O_CLOEXEC
789
 
                    O_CLOEXEC
790
 
#else  /* not O_CLOEXEC */
791
 
                    0
792
 
#endif  /* not O_CLOEXEC */
793
 
                    );
794
 
    } else {
795
 
      dir_fd = open(plugindir, O_RDONLY |
796
 
#ifdef O_CLOEXEC
797
 
                    O_CLOEXEC
798
 
#else  /* not O_CLOEXEC */
799
 
                    0
800
 
#endif  /* not O_CLOEXEC */
801
 
                    );
802
 
    }
 
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
                  );
803
882
    if(dir_fd == -1){
804
883
      error(0, errno, "Could not open plugin dir");
805
884
      exitstatus = EX_UNAVAILABLE;
811
890
    ret = set_cloexec_flag(dir_fd);
812
891
    if(ret < 0){
813
892
      error(0, errno, "set_cloexec_flag");
814
 
      TEMP_FAILURE_RETRY(close(dir_fd));
815
893
      exitstatus = EX_OSERR;
816
894
      goto fallback;
817
895
    }
818
896
#endif  /* O_CLOEXEC */
819
 
    
820
 
    dir = fdopendir(dir_fd);
821
 
    if(dir == NULL){
822
 
      error(0, errno, "Could not open plugin dir");
823
 
      TEMP_FAILURE_RETRY(close(dir_fd));
824
 
      exitstatus = EX_OSERR;
825
 
      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
      }
826
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;
827
931
  }
828
932
  
829
933
  FD_ZERO(&rfds_all);
830
934
  
831
935
  /* Read and execute any executable in the plugin directory*/
832
 
  while(true){
833
 
    do {
834
 
      dirst = readdir(dir);
835
 
    } while(dirst == NULL and errno == EINTR);
836
 
    
837
 
    /* All directory entries have been processed */
838
 
    if(dirst == NULL){
839
 
      if(errno == EBADF){
840
 
        error(0, errno, "readdir");
841
 
        exitstatus = EX_IOERR;
842
 
        goto fallback;
843
 
      }
844
 
      break;
845
 
    }
846
 
    
847
 
    d_name_len = strlen(dirst->d_name);
848
 
    
849
 
    /* Ignore dotfiles, backup files and other junk */
850
 
    {
851
 
      bool bad_name = false;
852
 
      
853
 
      const char const *bad_prefixes[] = { ".", "#", NULL };
854
 
      
855
 
      const char const *bad_suffixes[] = { "~", "#", ".dpkg-new",
856
 
                                           ".dpkg-old",
857
 
                                           ".dpkg-bak",
858
 
                                           ".dpkg-divert", NULL };
859
 
      for(const char **pre = bad_prefixes; *pre != NULL; pre++){
860
 
        size_t pre_len = strlen(*pre);
861
 
        if((d_name_len >= pre_len)
862
 
           and strncmp((dirst->d_name), *pre, pre_len) == 0){
863
 
          if(debug){
864
 
            fprintf(stderr, "Ignoring plugin dir entry \"%s\""
865
 
                    " with bad prefix %s\n", dirst->d_name, *pre);
866
 
          }
867
 
          bad_name = true;
868
 
          break;
869
 
        }
870
 
      }
871
 
      if(bad_name){
872
 
        continue;
873
 
      }
874
 
      for(const char **suf = bad_suffixes; *suf != NULL; suf++){
875
 
        size_t suf_len = strlen(*suf);
876
 
        if((d_name_len >= suf_len)
877
 
           and (strcmp((dirst->d_name) + d_name_len-suf_len, *suf)
878
 
                == 0)){
879
 
          if(debug){
880
 
            fprintf(stderr, "Ignoring plugin dir entry \"%s\""
881
 
                    " with bad suffix %s\n", dirst->d_name, *suf);
882
 
          }
883
 
          bad_name = true;
884
 
          break;
885
 
        }
886
 
      }
887
 
      
888
 
      if(bad_name){
889
 
        continue;
890
 
      }
891
 
    }
892
 
    
893
 
    char *filename;
894
 
    if(plugindir == NULL){
895
 
      ret = (int)TEMP_FAILURE_RETRY(asprintf(&filename, PDIR "/%s",
896
 
                                             dirst->d_name));
897
 
    } else {
898
 
      ret = (int)TEMP_FAILURE_RETRY(asprintf(&filename, "%s/%s",
899
 
                                             plugindir,
900
 
                                             dirst->d_name));
901
 
    }
902
 
    if(ret < 0){
903
 
      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]);
904
942
      continue;
905
943
    }
906
 
    
907
 
    ret = (int)TEMP_FAILURE_RETRY(stat(filename, &st));
 
944
    ret = (int)TEMP_FAILURE_RETRY(fstat(plugin_fd, &st));
908
945
    if(ret == -1){
909
946
      error(0, errno, "stat");
910
 
      free(filename);
 
947
      close(plugin_fd);
 
948
      free(direntries[i]);
911
949
      continue;
912
950
    }
913
951
    
914
952
    /* Ignore non-executable files */
915
953
    if(not S_ISREG(st.st_mode)
916
 
       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)){
917
956
      if(debug){
918
 
        fprintf(stderr, "Ignoring plugin dir entry \"%s\""
919
 
                " 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);
920
961
      }
921
 
      free(filename);
 
962
      close(plugin_fd);
 
963
      free(direntries[i]);
922
964
      continue;
923
965
    }
924
966
    
925
 
    plugin *p = getplugin(dirst->d_name);
 
967
    plugin *p = getplugin(direntries[i]->d_name);
926
968
    if(p == NULL){
927
969
      error(0, errno, "getplugin");
928
 
      free(filename);
 
970
      close(plugin_fd);
 
971
      free(direntries[i]);
929
972
      continue;
930
973
    }
931
974
    if(p->disabled){
932
975
      if(debug){
933
976
        fprintf(stderr, "Ignoring disabled plugin \"%s\"\n",
934
 
                dirst->d_name);
 
977
                direntries[i]->d_name);
935
978
      }
936
 
      free(filename);
 
979
      close(plugin_fd);
 
980
      free(direntries[i]);
937
981
      continue;
938
982
    }
939
983
    {
953
997
        }
954
998
      }
955
999
    }
956
 
    /* If this plugin has any environment variables, we will call
957
 
       using execve and need to duplicate the environment from this
958
 
       process, too. */
 
1000
    /* If this plugin has any environment variables, we need to
 
1001
       duplicate the environment from this process, too. */
959
1002
    if(p->environ[0] != NULL){
960
1003
      for(char **e = environ; *e != NULL; e++){
961
1004
        if(not add_environment(p, *e, false)){
965
1008
    }
966
1009
    
967
1010
    int pipefd[2];
 
1011
#ifndef O_CLOEXEC
968
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 */
969
1016
    if(ret == -1){
970
1017
      error(0, errno, "pipe");
971
1018
      exitstatus = EX_OSERR;
972
 
      goto fallback;
973
 
    }
 
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
974
1032
    /* Ask OS to automatic close the pipe on exec */
975
1033
    ret = set_cloexec_flag(pipefd[0]);
976
1034
    if(ret < 0){
977
1035
      error(0, errno, "set_cloexec_flag");
 
1036
      close(pipefd[0]);
 
1037
      close(pipefd[1]);
978
1038
      exitstatus = EX_OSERR;
 
1039
      free(direntries[i]);
979
1040
      goto fallback;
980
1041
    }
981
1042
    ret = set_cloexec_flag(pipefd[1]);
982
1043
    if(ret < 0){
983
1044
      error(0, errno, "set_cloexec_flag");
 
1045
      close(pipefd[0]);
 
1046
      close(pipefd[1]);
984
1047
      exitstatus = EX_OSERR;
 
1048
      free(direntries[i]);
985
1049
      goto fallback;
986
1050
    }
 
1051
#endif  /* not O_CLOEXEC */
987
1052
    /* Block SIGCHLD until process is safely in process list */
988
1053
    ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_BLOCK,
989
1054
                                              &sigchld_action.sa_mask,
991
1056
    if(ret < 0){
992
1057
      error(0, errno, "sigprocmask");
993
1058
      exitstatus = EX_OSERR;
 
1059
      free(direntries[i]);
994
1060
      goto fallback;
995
1061
    }
996
1062
    /* Starting a new process to be watched */
1000
1066
    } while(pid == -1 and errno == EINTR);
1001
1067
    if(pid == -1){
1002
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]);
1003
1073
      exitstatus = EX_OSERR;
 
1074
      free(direntries[i]);
1004
1075
      goto fallback;
1005
1076
    }
1006
1077
    if(pid == 0){
1022
1093
        _exit(EX_OSERR);
1023
1094
      }
1024
1095
      
1025
 
      if(dirfd(dir) < 0){
1026
 
        /* If dir has no file descriptor, we could not set FD_CLOEXEC
1027
 
           above and must now close it manually here. */
1028
 
        closedir(dir);
1029
 
      }
1030
 
      if(p->environ[0] == NULL){
1031
 
        if(execv(filename, p->argv) < 0){
1032
 
          error(0, errno, "execv for %s", filename);
1033
 
          _exit(EX_OSERR);
1034
 
        }
1035
 
      } else {
1036
 
        if(execve(filename, p->argv, p->environ) < 0){
1037
 
          error(0, errno, "execve for %s", filename);
1038
 
          _exit(EX_OSERR);
1039
 
        }
 
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);
1040
1102
      }
1041
1103
      /* no return */
1042
1104
    }
1043
1105
    /* Parent process */
1044
 
    TEMP_FAILURE_RETRY(close(pipefd[1])); /* Close unused write end of
1045
 
                                             pipe */
1046
 
    free(filename);
1047
 
    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);
1048
1109
    if(new_plugin == NULL){
1049
1110
      error(0, errno, "getplugin");
1050
1111
      ret = (int)(TEMP_FAILURE_RETRY
1054
1115
        error(0, errno, "sigprocmask");
1055
1116
      }
1056
1117
      exitstatus = EX_OSERR;
 
1118
      free(direntries[i]);
1057
1119
      goto fallback;
1058
1120
    }
 
1121
    free(direntries[i]);
1059
1122
    
1060
1123
    new_plugin->pid = pid;
1061
1124
    new_plugin->fd = pipefd[0];
1062
 
    
 
1125
 
 
1126
    if(debug){
 
1127
      fprintf(stderr, "Plugin %s started (PID %" PRIdMAX ")\n",
 
1128
              new_plugin->name, (intmax_t) (new_plugin->pid));
 
1129
    }
 
1130
 
1063
1131
    /* Unblock SIGCHLD so signal handler can be run if this process
1064
1132
       has already completed */
1065
1133
    ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_UNBLOCK,
1071
1139
      goto fallback;
1072
1140
    }
1073
1141
    
1074
 
    FD_SET(new_plugin->fd, &rfds_all); /* Spurious warning from
1075
 
                                          -Wconversion */
 
1142
    FD_SET(new_plugin->fd, &rfds_all);
1076
1143
    
1077
1144
    if(maxfd < new_plugin->fd){
1078
1145
      maxfd = new_plugin->fd;
1079
1146
    }
1080
1147
  }
1081
1148
  
1082
 
  TEMP_FAILURE_RETRY(closedir(dir));
1083
 
  dir = NULL;
 
1149
  free(direntries);
 
1150
  direntries = NULL;
 
1151
  close(dir_fd);
 
1152
  dir_fd = -1;
1084
1153
  free_plugin(getplugin(NULL));
1085
1154
  
1086
1155
  for(plugin *p = plugin_list; p != NULL; p = p->next){
1125
1194
                      (intmax_t) (proc->pid),
1126
1195
                      WTERMSIG(proc->status),
1127
1196
                      strsignal(WTERMSIG(proc->status)));
1128
 
            } else if(WCOREDUMP(proc->status)){
1129
 
              fprintf(stderr, "Plugin %s [%" PRIdMAX "] dumped"
1130
 
                      " core\n", proc->name, (intmax_t) (proc->pid));
1131
1197
            }
1132
1198
          }
1133
1199
          
1134
1200
          /* Remove the plugin */
1135
 
          FD_CLR(proc->fd, &rfds_all); /* Spurious warning from
1136
 
                                          -Wconversion */
 
1201
          FD_CLR(proc->fd, &rfds_all);
1137
1202
          
1138
1203
          /* Block signal while modifying process_list */
1139
1204
          ret = (int)TEMP_FAILURE_RETRY(sigprocmask
1179
1244
      }
1180
1245
      
1181
1246
      /* This process has not completed.  Does it have any output? */
1182
 
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){ /* Spurious
1183
 
                                                         warning from
1184
 
                                                         -Wconversion */
 
1247
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){
1185
1248
        /* This process had nothing to say at this time */
1186
1249
        proc = proc->next;
1187
1250
        continue;
1188
1251
      }
1189
1252
      /* Before reading, make the process' data buffer large enough */
1190
1253
      if(proc->buffer_length + BUFFER_SIZE > proc->buffer_size){
1191
 
        proc->buffer = realloc(proc->buffer, proc->buffer_size
1192
 
                               + (size_t) BUFFER_SIZE);
1193
 
        if(proc->buffer == NULL){
 
1254
        char *new_buffer = realloc(proc->buffer, proc->buffer_size
 
1255
                                   + (size_t) BUFFER_SIZE);
 
1256
        if(new_buffer == NULL){
1194
1257
          error(0, errno, "malloc");
1195
1258
          exitstatus = EX_OSERR;
1196
1259
          goto fallback;
1197
1260
        }
 
1261
        proc->buffer = new_buffer;
1198
1262
        proc->buffer_size += BUFFER_SIZE;
1199
1263
      }
1200
1264
      /* Read from the process */
1253
1317
    free(custom_argv);
1254
1318
  }
1255
1319
  
1256
 
  if(dir != NULL){
1257
 
    closedir(dir);
 
1320
  free(direntries);
 
1321
  
 
1322
  if(dir_fd != -1){
 
1323
    close(dir_fd);
1258
1324
  }
1259
1325
  
1260
1326
  /* Kill the processes */
1280
1346
  free_plugin_list();
1281
1347
  
1282
1348
  free(plugindir);
 
1349
  free(pluginhelperdir);
1283
1350
  free(argfile);
1284
1351
  
1285
1352
  return exitstatus;