/mandos/trunk

To get this branch, use:
bzr branch http://bzr.recompile.se/loggerhead/mandos/trunk

« back to all changes in this revision

Viewing changes to plugin-runner.c

  • Committer: Teddy Hogeborn
  • Date: 2011-12-31 20:07:11 UTC
  • mfrom: (535.1.9 wireless-network-hook)
  • Revision ID: teddy@recompile.se-20111231200711-6dli3r8drftem57r
Merge new wireless network hook.  Fix bridge network hook to use
hardware addresses instead of interface names.  Implement and document
new "CONNECT" environment variable for network hooks.

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-2022 Teddy Hogeborn
6
 
 * Copyright © 2008-2022 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
 
5
 * Copyright © 2008-2011 Teddy Hogeborn
 
6
 * Copyright © 2008-2011 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
16
14
 * WITHOUT ANY WARRANTY; without even the implied warranty of
17
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18
16
 * General Public License for more details.
19
17
 * 
20
18
 * You should have received a copy of the GNU General Public License
21
 
 * along with Mandos.  If not, see <http://www.gnu.org/licenses/>.
 
19
 * along with this program.  If not, see
 
20
 * <http://www.gnu.org/licenses/>.
22
21
 * 
23
22
 * Contact the authors at <mandos@recompile.se>.
24
23
 */
25
24
 
26
 
#define _GNU_SOURCE             /* strchrnul(), TEMP_FAILURE_RETRY(),
27
 
                                   getline(), asprintf(), O_CLOEXEC,
28
 
                                   scandirat(), pipe2() */
29
 
#include <argp.h>               /* argp_program_version,
30
 
                                   argp_program_bug_address,
31
 
                                   struct argp_option,
32
 
                                   struct argp_state, argp_error(),
33
 
                                   ARGP_NO_EXIT, argp_state_help,
34
 
                                   ARGP_HELP_STD_HELP,
35
 
                                   ARGP_HELP_USAGE, ARGP_HELP_EXIT_OK,
36
 
                                   ARGP_KEY_ARG, ARGP_ERR_UNKNOWN,
37
 
                                   struct argp, argp_parse(),
38
 
                                   ARGP_IN_ORDER, ARGP_NO_HELP */
39
 
#include <stdbool.h>            /* bool, false, true */
40
 
#include <sys/types.h>          /* pid_t, sig_atomic_t, uid_t, gid_t,
41
 
                                   getuid(), setgid(), setuid() */
 
25
#define _GNU_SOURCE             /* TEMP_FAILURE_RETRY(), getline(),
 
26
                                   asprintf(), O_CLOEXEC */
42
27
#include <stddef.h>             /* size_t, NULL */
43
 
#include <iso646.h>             /* or, and, not */
44
 
#include <string.h>             /* strcmp(), strdup(), strchrnul(),
45
 
                                   strncmp(), strlen(), strcpy(),
46
 
                                   strsep(), strchr(), strsignal() */
47
 
#include <stdlib.h>             /* malloc(), free(), reallocarray(),
48
 
                                   realloc(), EXIT_SUCCESS */
49
 
#include <errno.h>              /* errno, EINTR, ENOMEM, ECHILD,
50
 
                                   error_t, EINVAL, EMFILE, ENFILE,
51
 
                                   ENOENT, ESRCH */
52
 
#include <stdint.h>             /* SIZE_MAX */
53
 
#define _GNU_SOURCE             /* strchrnul(), TEMP_FAILURE_RETRY(),
54
 
                                   getline(), asprintf(), O_CLOEXEC,
55
 
                                   scandirat(), pipe2() */
56
 
#include <unistd.h>             /* TEMP_FAILURE_RETRY(), ssize_t,
57
 
                                   write(), STDOUT_FILENO, uid_t,
58
 
                                   gid_t, getuid(), fchown(), close(),
59
 
                                   symlink(), setgid(), setuid(),
60
 
                                   faccessat(), X_OK, pipe(), pipe2(),
61
 
                                   fork(), _exit(), dup2(), fexecve(),
62
 
                                   read(), getpass() */
 
28
#include <stdlib.h>             /* malloc(), exit(), EXIT_SUCCESS,
 
29
                                   realloc() */
 
30
#include <stdbool.h>            /* bool, true, false */
 
31
#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() */
 
38
#include <sys/select.h>         /* fd_set, select(), FD_ZERO(),
 
39
                                   FD_SET(), FD_ISSET(), FD_CLR */
 
40
#include <sys/wait.h>           /* wait(), waitpid(), WIFEXITED(),
 
41
                                   WEXITSTATUS(), WTERMSIG(),
 
42
                                   WCOREDUMP() */
 
43
#include <sys/stat.h>           /* struct stat, stat(), S_ISREG() */
 
44
#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() */
63
54
#include <fcntl.h>              /* fcntl(), F_GETFD, F_SETFD,
64
 
                                   FD_CLOEXEC, open(), O_RDONLY,
65
 
                                   O_CLOEXEC, openat() */
66
 
#include <sys/wait.h>           /* waitpid(), WNOHANG, WIFEXITED(),
67
 
                                   WEXITSTATUS(), WIFSIGNALED(),
68
 
                                   WTERMSIG(), wait() */
69
 
#include <error.h>              /* error() */
70
 
#include <stdio.h>              /* FILE, fprintf(), fopen(),
71
 
                                   getline(), fclose(), EOF,
72
 
                                   asprintf(), stderr */
73
 
#include <dirent.h>             /* struct dirent, scandirat(),
74
 
                                   alphasort() */
75
 
#include <sys/stat.h>           /* struct stat, fstat(), S_ISDIR(),
76
 
                                   lstat(), S_ISREG() */
77
 
#include <sys/select.h>         /* fd_set, FD_ZERO(), FD_SETSIZE,
78
 
                                   FD_SET(), select(), FD_CLR(),
79
 
                                   FD_ISSET() */
80
 
#include <signal.h>             /* struct sigaction, SA_NOCLDSTOP,
81
 
                                   sigemptyset(), sigaddset(),
82
 
                                   SIGCHLD, sigprocmask(), SIG_BLOCK,
83
 
                                   SIG_UNBLOCK, kill(), SIGTERM */
 
55
                                   FD_CLOEXEC */
 
56
#include <string.h>             /* strsep, strlen(), asprintf(),
 
57
                                   strsignal(), strcmp(), strncmp() */
 
58
#include <errno.h>              /* errno */
 
59
#include <argp.h>               /* struct argp_option, struct
 
60
                                   argp_state, struct argp,
 
61
                                   argp_parse(), ARGP_ERR_UNKNOWN,
 
62
                                   ARGP_KEY_END, ARGP_KEY_ARG,
 
63
                                   error_t */
 
64
#include <signal.h>             /* struct sigaction, sigemptyset(),
 
65
                                   sigaddset(), sigaction(),
 
66
                                   sigprocmask(), SIG_BLOCK, SIGCHLD,
 
67
                                   SIG_UNBLOCK, kill(), sig_atomic_t
 
68
                                */
 
69
#include <errno.h>              /* errno, EBADF */
 
70
#include <inttypes.h>           /* intmax_t, PRIdMAX, strtoimax() */
84
71
#include <sysexits.h>           /* EX_OSERR, EX_USAGE, EX_IOERR,
85
72
                                   EX_CONFIG, EX_UNAVAILABLE, EX_OK */
86
 
#include <inttypes.h>           /* intmax_t, strtoimax(), PRIdMAX */
87
 
#include <fnmatch.h>            /* fnmatch(), FNM_FILE_NAME,
88
 
                                   FNM_PERIOD, FNM_NOMATCH */
 
73
#include <errno.h>              /* errno */
 
74
#include <error.h>              /* error() */
89
75
 
90
76
#define BUFFER_SIZE 256
91
77
 
92
78
#define PDIR "/lib/mandos/plugins.d"
93
 
#define PHDIR "/lib/mandos/plugin-helpers"
94
79
#define AFILE "/conf/conf.d/mandos/plugin-runner.conf"
95
80
 
96
81
const char *argp_program_version = "plugin-runner " VERSION;
120
105
 
121
106
/* Gets an existing plugin based on name,
122
107
   or if none is found, creates a new one */
123
 
__attribute__((warn_unused_result))
124
108
static plugin *getplugin(char *name){
125
109
  /* Check for existing plugin with that name */
126
110
  for(plugin *p = plugin_list; p != NULL; p = p->next){
187
171
}
188
172
 
189
173
/* Helper function for add_argument and add_environment */
190
 
__attribute__((nonnull, warn_unused_result))
 
174
__attribute__((nonnull))
191
175
static bool add_to_char_array(const char *new, char ***array,
192
176
                              int *len){
193
177
  /* Resize the pointed-to array to hold one more pointer */
194
 
  char **new_array = NULL;
195
178
  do {
196
 
#if defined(__GLIBC_PREREQ) and __GLIBC_PREREQ(2, 26)
197
 
    new_array = reallocarray(*array, (size_t)((*len) + 2),
198
 
                             sizeof(char *));
199
 
#else
200
 
    if(((size_t)((*len) + 2)) > (SIZE_MAX / sizeof(char *))){
201
 
      /* overflow */
202
 
      new_array = NULL;
203
 
      errno = ENOMEM;
204
 
    } else {
205
 
      new_array = realloc(*array, (size_t)((*len) + 2)
206
 
                          * sizeof(char *));
207
 
    }
208
 
#endif
209
 
  } while(new_array == NULL and errno == EINTR);
 
179
    *array = realloc(*array, sizeof(char *)
 
180
                     * (size_t) ((*len) + 2));
 
181
  } while(*array == NULL and errno == EINTR);
210
182
  /* Malloc check */
211
 
  if(new_array == NULL){
 
183
  if(*array == NULL){
212
184
    return false;
213
185
  }
214
 
  *array = new_array;
215
186
  /* Make a copy of the new string */
216
187
  char *copy;
217
188
  do {
229
200
}
230
201
 
231
202
/* Add to a plugin's argument vector */
232
 
__attribute__((nonnull(2), warn_unused_result))
 
203
__attribute__((nonnull(2)))
233
204
static bool add_argument(plugin *p, const char *arg){
234
205
  if(p == NULL){
235
206
    return false;
238
209
}
239
210
 
240
211
/* Add to a plugin's environment */
241
 
__attribute__((nonnull(2), warn_unused_result))
 
212
__attribute__((nonnull(2)))
242
213
static bool add_environment(plugin *p, const char *def, bool replace){
243
214
  if(p == NULL){
244
215
    return false;
246
217
  /* namelen = length of name of environment variable */
247
218
  size_t namelen = (size_t)(strchrnul(def, '=') - def);
248
219
  /* Search for this environment variable */
249
 
  for(char **envdef = p->environ; *envdef != NULL; envdef++){
250
 
    if(strncmp(*envdef, def, namelen + 1) == 0){
 
220
  for(char **e = p->environ; *e != NULL; e++){
 
221
    if(strncmp(*e, def, namelen + 1) == 0){
251
222
      /* It already exists */
252
223
      if(replace){
253
 
        char *new_envdef;
 
224
        char *new;
254
225
        do {
255
 
          new_envdef = realloc(*envdef, strlen(def) + 1);
256
 
        } while(new_envdef == NULL and errno == EINTR);
257
 
        if(new_envdef == NULL){
 
226
          new = realloc(*e, strlen(def) + 1);
 
227
        } while(new == NULL and errno == EINTR);
 
228
        if(new == NULL){
258
229
          return false;
259
230
        }
260
 
        *envdef = new_envdef;
261
 
        strcpy(*envdef, def);
 
231
        *e = new;
 
232
        strcpy(*e, def);
262
233
      }
263
234
      return true;
264
235
    }
266
237
  return add_to_char_array(def, &(p->environ), &(p->envc));
267
238
}
268
239
 
269
 
#ifndef O_CLOEXEC
270
240
/*
271
241
 * Based on the example in the GNU LibC manual chapter 13.13 "File
272
242
 * Descriptor Flags".
273
243
 | [[info:libc:Descriptor%20Flags][File Descriptor Flags]] |
274
244
 */
275
 
__attribute__((warn_unused_result))
276
245
static int set_cloexec_flag(int fd){
277
246
  int ret = (int)TEMP_FAILURE_RETRY(fcntl(fd, F_GETFD, 0));
278
247
  /* If reading the flags failed, return error indication now. */
283
252
  return (int)TEMP_FAILURE_RETRY(fcntl(fd, F_SETFD,
284
253
                                       ret | FD_CLOEXEC));
285
254
}
286
 
#endif  /* not O_CLOEXEC */
287
255
 
288
256
 
289
257
/* Mark processes as completed when they exit, and save their exit
321
289
}
322
290
 
323
291
/* Prints out a password to stdout */
324
 
__attribute__((nonnull, warn_unused_result))
 
292
__attribute__((nonnull))
325
293
static bool print_out_password(const char *buffer, size_t length){
326
294
  ssize_t ret;
327
295
  for(size_t written = 0; written < length; written += (size_t)ret){
338
306
__attribute__((nonnull))
339
307
static void free_plugin(plugin *plugin_node){
340
308
  
341
 
  for(char **arg = (plugin_node->argv)+1; *arg != NULL; arg++){
 
309
  for(char **arg = plugin_node->argv; *arg != NULL; arg++){
342
310
    free(*arg);
343
311
  }
344
 
  free(plugin_node->name);
345
312
  free(plugin_node->argv);
346
313
  for(char **env = plugin_node->environ; *env != NULL; env++){
347
314
    free(*env);
374
341
 
375
342
int main(int argc, char *argv[]){
376
343
  char *plugindir = NULL;
377
 
  char *pluginhelperdir = NULL;
378
344
  char *argfile = NULL;
379
345
  FILE *conffp;
380
 
  struct dirent **direntries = NULL;
 
346
  size_t d_name_len;
 
347
  DIR *dir = NULL;
 
348
  struct dirent *dirst;
381
349
  struct stat st;
382
350
  fd_set rfds_all;
383
351
  int ret, maxfd = 0;
391
359
                                      .sa_flags = SA_NOCLDSTOP };
392
360
  char **custom_argv = NULL;
393
361
  int custom_argc = 0;
394
 
  int dir_fd = -1;
395
362
  
396
363
  /* Establish a signal handler */
397
364
  sigemptyset(&sigchld_action.sa_mask);
442
409
      .doc = "Group ID the plugins will run as", .group = 3 },
443
410
    { .name = "debug", .key = 132,
444
411
      .doc = "Debug mode", .group = 4 },
445
 
    { .name = "plugin-helper-dir", .key = 133,
446
 
      .arg = "DIRECTORY",
447
 
      .doc = "Specify a different plugin helper directory",
448
 
      .group = 2 },
449
412
    /*
450
413
     * These reproduce what we would get without ARGP_NO_HELP
451
414
     */
472
435
            break;
473
436
          }
474
437
        }
475
 
        errno = 0;
476
438
      }
477
439
      break;
478
440
    case 'G':                   /* --global-env */
479
 
      if(add_environment(getplugin(NULL), arg, true)){
480
 
        errno = 0;
481
 
      }
 
441
      add_environment(getplugin(NULL), arg, true);
482
442
      break;
483
443
    case 'o':                   /* --options-for */
484
444
      {
501
461
            break;
502
462
          }
503
463
        }
504
 
        errno = 0;
505
464
      }
506
465
      break;
507
466
    case 'E':                   /* --env-for */
519
478
          errno = EINVAL;
520
479
          break;
521
480
        }
522
 
        if(add_environment(getplugin(arg), envdef, true)){
523
 
          errno = 0;
524
 
        }
 
481
        add_environment(getplugin(arg), envdef, true);
525
482
      }
526
483
      break;
527
484
    case 'd':                   /* --disable */
529
486
        plugin *p = getplugin(arg);
530
487
        if(p != NULL){
531
488
          p->disabled = true;
532
 
          errno = 0;
533
489
        }
534
490
      }
535
491
      break;
538
494
        plugin *p = getplugin(arg);
539
495
        if(p != NULL){
540
496
          p->disabled = false;
541
 
          errno = 0;
542
497
        }
543
498
      }
544
499
      break;
545
500
    case 128:                   /* --plugin-dir */
546
501
      free(plugindir);
547
502
      plugindir = strdup(arg);
548
 
      if(plugindir != NULL){
549
 
        errno = 0;
550
 
      }
551
503
      break;
552
504
    case 129:                   /* --config-file */
553
505
      /* This is already done by parse_opt_config_file() */
561
513
        break;
562
514
      }
563
515
      uid = (uid_t)tmp_id;
564
 
      errno = 0;
565
516
      break;
566
517
    case 131:                   /* --groupid */
567
518
      tmp_id = strtoimax(arg, &tmp, 10);
572
523
        break;
573
524
      }
574
525
      gid = (gid_t)tmp_id;
575
 
      errno = 0;
576
526
      break;
577
527
    case 132:                   /* --debug */
578
528
      debug = true;
579
529
      break;
580
 
    case 133:                   /* --plugin-helper-dir */
581
 
      free(pluginhelperdir);
582
 
      pluginhelperdir = strdup(arg);
583
 
      if(pluginhelperdir != NULL){
584
 
        errno = 0;
585
 
      }
586
 
      break;
587
530
      /*
588
531
       * These reproduce what we would get without ARGP_NO_HELP
589
532
       */
590
533
    case '?':                   /* --help */
591
534
      state->flags &= ~(unsigned int)ARGP_NO_EXIT; /* force exit */
592
535
      argp_state_help(state, state->out_stream, ARGP_HELP_STD_HELP);
593
 
      __builtin_unreachable();
594
536
    case -3:                    /* --usage */
595
537
      state->flags &= ~(unsigned int)ARGP_NO_EXIT; /* force exit */
596
538
      argp_state_help(state, state->out_stream,
597
539
                      ARGP_HELP_USAGE | ARGP_HELP_EXIT_OK);
598
 
      __builtin_unreachable();
599
540
    case 'V':                   /* --version */
600
541
      fprintf(state->out_stream, "%s\n", argp_program_version);
601
542
      exit(EXIT_SUCCESS);
611
552
      if(arg[0] == '\0'){
612
553
        break;
613
554
      }
614
 
#if __GNUC__ >= 7
615
 
      __attribute__((fallthrough));
616
 
#else
617
 
          /* FALLTHROUGH */
618
 
#endif
619
555
    default:
620
556
      return ARGP_ERR_UNKNOWN;
621
557
    }
640
576
    case 129:                   /* --config-file */
641
577
      free(argfile);
642
578
      argfile = strdup(arg);
643
 
      if(argfile != NULL){
644
 
        errno = 0;
645
 
      }
646
579
      break;
647
580
    case 130:                   /* --userid */
648
581
    case 131:                   /* --groupid */
649
582
    case 132:                   /* --debug */
650
 
    case 133:                   /* --plugin-helper-dir */
651
583
    case '?':                   /* --help */
652
584
    case -3:                    /* --usage */
653
585
    case 'V':                   /* --version */
732
664
        }
733
665
        
734
666
        custom_argc += 1;
735
 
        {
736
 
#if defined(__GLIBC_PREREQ) and __GLIBC_PREREQ(2, 26)
737
 
          char **new_argv = reallocarray(custom_argv,
738
 
                                         (size_t)custom_argc + 1,
739
 
                                         sizeof(char *));
740
 
#else
741
 
          char **new_argv = NULL;
742
 
          if(((size_t)custom_argc + 1) > (SIZE_MAX / sizeof(char *))){
743
 
            /* overflow */
744
 
            errno = ENOMEM;
745
 
          } else {
746
 
            new_argv = realloc(custom_argv, ((size_t)custom_argc + 1)
747
 
                               * sizeof(char *));
748
 
          }
749
 
#endif
750
 
          if(new_argv == NULL){
751
 
            error(0, errno, "reallocarray");
752
 
            exitstatus = EX_OSERR;
753
 
            free(new_arg);
754
 
            free(org_line);
755
 
            goto fallback;
756
 
          } else {
757
 
            custom_argv = new_argv;
758
 
          }
 
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;
759
674
        }
760
675
        custom_argv[custom_argc-1] = new_arg;
761
676
        custom_argv[custom_argc] = NULL;
819
734
    goto fallback;
820
735
  }
821
736
  
822
 
  {
823
 
    char *pluginhelperenv;
824
 
    bool bret = true;
825
 
    ret = asprintf(&pluginhelperenv, "MANDOSPLUGINHELPERDIR=%s",
826
 
                   pluginhelperdir != NULL ? pluginhelperdir : PHDIR);
827
 
    if(ret != -1){
828
 
      bret = add_environment(getplugin(NULL), pluginhelperenv, true);
829
 
    }
830
 
    if(ret == -1 or not bret){
831
 
      error(0, errno, "Failed to set MANDOSPLUGINHELPERDIR"
832
 
            " environment variable to \"%s\" for all plugins\n",
833
 
            pluginhelperdir != NULL ? pluginhelperdir : PHDIR);
834
 
    }
835
 
    if(ret != -1){
836
 
      free(pluginhelperenv);
837
 
    }
838
 
  }
839
 
  
840
737
  if(debug){
841
 
    for(plugin *p = plugin_list; p != NULL; p = p->next){
 
738
    for(plugin *p = plugin_list; p != NULL; p=p->next){
842
739
      fprintf(stderr, "Plugin: %s has %d arguments\n",
843
740
              p->name ? p->name : "Global", p->argc - 1);
844
741
      for(char **a = p->argv; *a != NULL; a++){
853
750
  
854
751
  if(getuid() == 0){
855
752
    /* Work around Debian bug #633582:
856
 
       <https://bugs.debian.org/633582> */
 
753
       <http://bugs.debian.org/633582> */
857
754
    int plugindir_fd = open(/* plugindir or */ PDIR, O_RDONLY);
858
755
    if(plugindir_fd == -1){
859
 
      if(errno != ENOENT){
860
 
        error(0, errno, "open(\"" PDIR "\")");
861
 
      }
 
756
      error(0, errno, "open");
862
757
    } else {
863
758
      ret = (int)TEMP_FAILURE_RETRY(fstat(plugindir_fd, &st));
864
759
      if(ret == -1){
871
766
          }
872
767
        }
873
768
      }
874
 
      close(plugindir_fd);
875
 
    }
876
 
 
877
 
    /* Work around Debian bug #981302
878
 
       <https://bugs.debian.org/981302> */
879
 
    if(lstat("/dev/fd", &st) != 0 and errno == ENOENT){
880
 
      ret = symlink("/proc/self/fd", "/dev/fd");
881
 
      if(ret == -1){
882
 
        error(0, errno, "Failed to create /dev/fd symlink");
883
 
      }
 
769
      TEMP_FAILURE_RETRY(close(plugindir_fd));
884
770
    }
885
771
  }
886
772
  
887
773
  /* Lower permissions */
888
 
  ret = setgid(gid);
 
774
  setgid(gid);
889
775
  if(ret == -1){
890
776
    error(0, errno, "setgid");
891
777
  }
896
782
  
897
783
  /* Open plugin directory with close_on_exec flag */
898
784
  {
899
 
    dir_fd = open(plugindir != NULL ? plugindir : PDIR, O_RDONLY |
900
 
#ifdef O_CLOEXEC
901
 
                  O_CLOEXEC
902
 
#else  /* not O_CLOEXEC */
903
 
                  0
904
 
#endif  /* not O_CLOEXEC */
905
 
                  );
 
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
    }
906
803
    if(dir_fd == -1){
907
804
      error(0, errno, "Could not open plugin dir");
908
805
      exitstatus = EX_UNAVAILABLE;
914
811
    ret = set_cloexec_flag(dir_fd);
915
812
    if(ret < 0){
916
813
      error(0, errno, "set_cloexec_flag");
 
814
      TEMP_FAILURE_RETRY(close(dir_fd));
917
815
      exitstatus = EX_OSERR;
918
816
      goto fallback;
919
817
    }
920
818
#endif  /* O_CLOEXEC */
921
 
  }
922
 
  
923
 
  int good_name(const struct dirent * const dirent){
924
 
    const char * const patterns[] = { ".*", "#*#", "*~", "*.dpkg-new",
925
 
                                      "*.dpkg-old", "*.dpkg-bak",
926
 
                                      "*.dpkg-divert", NULL };
927
 
#ifdef __GNUC__
928
 
#pragma GCC diagnostic push
929
 
#pragma GCC diagnostic ignored "-Wcast-qual"
930
 
#endif
931
 
    for(const char **pat = (const char **)patterns;
932
 
        *pat != NULL; pat++){
933
 
#ifdef __GNUC__
934
 
#pragma GCC diagnostic pop
935
 
#endif
936
 
      if(fnmatch(*pat, dirent->d_name, FNM_FILE_NAME | FNM_PERIOD)
937
 
         != FNM_NOMATCH){
938
 
        if(debug){
939
 
            fprintf(stderr, "Ignoring plugin dir entry \"%s\""
940
 
                    " matching pattern %s\n", dirent->d_name, *pat);
941
 
        }
942
 
        return 0;
943
 
      }
 
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;
944
826
    }
945
 
    return 1;
946
 
  }
947
 
  
948
 
  int numplugins = scandirat(dir_fd, ".", &direntries, good_name,
949
 
                             alphasort);
950
 
  if(numplugins == -1){
951
 
    error(0, errno, "Could not scan plugin dir");
952
 
    direntries = NULL;
953
 
    exitstatus = EX_OSERR;
954
 
    goto fallback;
955
827
  }
956
828
  
957
829
  FD_ZERO(&rfds_all);
958
830
  
959
831
  /* Read and execute any executable in the plugin directory*/
960
 
  for(int i = 0; i < numplugins; i++){
961
 
    
962
 
    int plugin_fd = openat(dir_fd, direntries[i]->d_name, O_RDONLY);
963
 
    if(plugin_fd == -1){
964
 
      error(0, errno, "Could not open plugin");
965
 
      free(direntries[i]);
 
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");
966
904
      continue;
967
905
    }
968
 
    ret = (int)TEMP_FAILURE_RETRY(fstat(plugin_fd, &st));
 
906
    
 
907
    ret = (int)TEMP_FAILURE_RETRY(stat(filename, &st));
969
908
    if(ret == -1){
970
909
      error(0, errno, "stat");
971
 
      close(plugin_fd);
972
 
      free(direntries[i]);
 
910
      free(filename);
973
911
      continue;
974
912
    }
975
913
    
976
914
    /* Ignore non-executable files */
977
915
    if(not S_ISREG(st.st_mode)
978
 
       or (TEMP_FAILURE_RETRY(faccessat(dir_fd, direntries[i]->d_name,
979
 
                                        X_OK, 0)) != 0)){
 
916
       or (TEMP_FAILURE_RETRY(access(filename, X_OK)) != 0)){
980
917
      if(debug){
981
 
        fprintf(stderr, "Ignoring plugin dir entry \"%s/%s\""
982
 
                " with bad type or mode\n",
983
 
                plugindir != NULL ? plugindir : PDIR,
984
 
                direntries[i]->d_name);
 
918
        fprintf(stderr, "Ignoring plugin dir entry \"%s\""
 
919
                " with bad type or mode\n", filename);
985
920
      }
986
 
      close(plugin_fd);
987
 
      free(direntries[i]);
 
921
      free(filename);
988
922
      continue;
989
923
    }
990
924
    
991
 
    plugin *p = getplugin(direntries[i]->d_name);
 
925
    plugin *p = getplugin(dirst->d_name);
992
926
    if(p == NULL){
993
927
      error(0, errno, "getplugin");
994
 
      close(plugin_fd);
995
 
      free(direntries[i]);
 
928
      free(filename);
996
929
      continue;
997
930
    }
998
931
    if(p->disabled){
999
932
      if(debug){
1000
933
        fprintf(stderr, "Ignoring disabled plugin \"%s\"\n",
1001
 
                direntries[i]->d_name);
 
934
                dirst->d_name);
1002
935
      }
1003
 
      close(plugin_fd);
1004
 
      free(direntries[i]);
 
936
      free(filename);
1005
937
      continue;
1006
938
    }
1007
939
    {
1021
953
        }
1022
954
      }
1023
955
    }
1024
 
    /* If this plugin has any environment variables, we need to
1025
 
       duplicate the environment from this process, too. */
 
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. */
1026
959
    if(p->environ[0] != NULL){
1027
960
      for(char **e = environ; *e != NULL; e++){
1028
961
        if(not add_environment(p, *e, false)){
1032
965
    }
1033
966
    
1034
967
    int pipefd[2];
1035
 
#ifndef O_CLOEXEC
1036
968
    ret = (int)TEMP_FAILURE_RETRY(pipe(pipefd));
1037
 
#else  /* O_CLOEXEC */
1038
 
    ret = (int)TEMP_FAILURE_RETRY(pipe2(pipefd, O_CLOEXEC));
1039
 
#endif  /* O_CLOEXEC */
1040
969
    if(ret == -1){
1041
970
      error(0, errno, "pipe");
1042
971
      exitstatus = EX_OSERR;
1043
 
      free(direntries[i]);
1044
 
      goto fallback;
1045
 
    }
1046
 
    if(pipefd[0] >= FD_SETSIZE){
1047
 
      fprintf(stderr, "pipe()[0] (%d) >= FD_SETSIZE (%d)", pipefd[0],
1048
 
              FD_SETSIZE);
1049
 
      close(pipefd[0]);
1050
 
      close(pipefd[1]);
1051
 
      exitstatus = EX_OSERR;
1052
 
      free(direntries[i]);
1053
 
      goto fallback;
1054
 
    }
1055
 
#ifndef O_CLOEXEC
 
972
      goto fallback;
 
973
    }
1056
974
    /* Ask OS to automatic close the pipe on exec */
1057
975
    ret = set_cloexec_flag(pipefd[0]);
1058
976
    if(ret < 0){
1059
977
      error(0, errno, "set_cloexec_flag");
1060
 
      close(pipefd[0]);
1061
 
      close(pipefd[1]);
1062
978
      exitstatus = EX_OSERR;
1063
 
      free(direntries[i]);
1064
979
      goto fallback;
1065
980
    }
1066
981
    ret = set_cloexec_flag(pipefd[1]);
1067
982
    if(ret < 0){
1068
983
      error(0, errno, "set_cloexec_flag");
1069
 
      close(pipefd[0]);
1070
 
      close(pipefd[1]);
1071
984
      exitstatus = EX_OSERR;
1072
 
      free(direntries[i]);
1073
985
      goto fallback;
1074
986
    }
1075
 
#endif  /* not O_CLOEXEC */
1076
987
    /* Block SIGCHLD until process is safely in process list */
1077
988
    ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_BLOCK,
1078
989
                                              &sigchld_action.sa_mask,
1080
991
    if(ret < 0){
1081
992
      error(0, errno, "sigprocmask");
1082
993
      exitstatus = EX_OSERR;
1083
 
      free(direntries[i]);
1084
994
      goto fallback;
1085
995
    }
1086
996
    /* Starting a new process to be watched */
1090
1000
    } while(pid == -1 and errno == EINTR);
1091
1001
    if(pid == -1){
1092
1002
      error(0, errno, "fork");
1093
 
      TEMP_FAILURE_RETRY(sigprocmask(SIG_UNBLOCK,
1094
 
                                     &sigchld_action.sa_mask, NULL));
1095
 
      close(pipefd[0]);
1096
 
      close(pipefd[1]);
1097
1003
      exitstatus = EX_OSERR;
1098
 
      free(direntries[i]);
1099
1004
      goto fallback;
1100
1005
    }
1101
1006
    if(pid == 0){
1117
1022
        _exit(EX_OSERR);
1118
1023
      }
1119
1024
      
1120
 
      if(fexecve(plugin_fd, p->argv,
1121
 
                (p->environ[0] != NULL) ? p->environ : environ) < 0){
1122
 
        error(0, errno, "fexecve for %s/%s",
1123
 
              plugindir != NULL ? plugindir : PDIR,
1124
 
              direntries[i]->d_name);
1125
 
        _exit(EX_OSERR);
 
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
        }
1126
1040
      }
1127
1041
      /* no return */
1128
1042
    }
1129
1043
    /* Parent process */
1130
 
    close(pipefd[1]);           /* Close unused write end of pipe */
1131
 
    close(plugin_fd);
1132
 
    plugin *new_plugin = getplugin(direntries[i]->d_name);
 
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);
1133
1048
    if(new_plugin == NULL){
1134
1049
      error(0, errno, "getplugin");
1135
1050
      ret = (int)(TEMP_FAILURE_RETRY
1139
1054
        error(0, errno, "sigprocmask");
1140
1055
      }
1141
1056
      exitstatus = EX_OSERR;
1142
 
      free(direntries[i]);
1143
1057
      goto fallback;
1144
1058
    }
1145
 
    free(direntries[i]);
1146
1059
    
1147
1060
    new_plugin->pid = pid;
1148
1061
    new_plugin->fd = pipefd[0];
1149
 
 
1150
 
    if(debug){
1151
 
      fprintf(stderr, "Plugin %s started (PID %" PRIdMAX ")\n",
1152
 
              new_plugin->name, (intmax_t) (new_plugin->pid));
1153
 
    }
1154
 
 
 
1062
    
1155
1063
    /* Unblock SIGCHLD so signal handler can be run if this process
1156
1064
       has already completed */
1157
1065
    ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_UNBLOCK,
1163
1071
      goto fallback;
1164
1072
    }
1165
1073
    
1166
 
    FD_SET(new_plugin->fd, &rfds_all);
 
1074
    FD_SET(new_plugin->fd, &rfds_all); /* Spurious warning from
 
1075
                                          -Wconversion */
1167
1076
    
1168
1077
    if(maxfd < new_plugin->fd){
1169
1078
      maxfd = new_plugin->fd;
1170
1079
    }
1171
1080
  }
1172
1081
  
1173
 
  free(direntries);
1174
 
  direntries = NULL;
1175
 
  close(dir_fd);
1176
 
  dir_fd = -1;
 
1082
  TEMP_FAILURE_RETRY(closedir(dir));
 
1083
  dir = NULL;
1177
1084
  free_plugin(getplugin(NULL));
1178
1085
  
1179
1086
  for(plugin *p = plugin_list; p != NULL; p = p->next){
1218
1125
                      (intmax_t) (proc->pid),
1219
1126
                      WTERMSIG(proc->status),
1220
1127
                      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));
1221
1131
            }
1222
1132
          }
1223
1133
          
1224
1134
          /* Remove the plugin */
1225
 
          FD_CLR(proc->fd, &rfds_all);
 
1135
          FD_CLR(proc->fd, &rfds_all); /* Spurious warning from
 
1136
                                          -Wconversion */
1226
1137
          
1227
1138
          /* Block signal while modifying process_list */
1228
1139
          ret = (int)TEMP_FAILURE_RETRY(sigprocmask
1268
1179
      }
1269
1180
      
1270
1181
      /* This process has not completed.  Does it have any output? */
1271
 
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){
 
1182
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){ /* Spurious
 
1183
                                                         warning from
 
1184
                                                         -Wconversion */
1272
1185
        /* This process had nothing to say at this time */
1273
1186
        proc = proc->next;
1274
1187
        continue;
1275
1188
      }
1276
1189
      /* Before reading, make the process' data buffer large enough */
1277
1190
      if(proc->buffer_length + BUFFER_SIZE > proc->buffer_size){
1278
 
        char *new_buffer = realloc(proc->buffer, proc->buffer_size
1279
 
                                   + (size_t) BUFFER_SIZE);
1280
 
        if(new_buffer == NULL){
 
1191
        proc->buffer = realloc(proc->buffer, proc->buffer_size
 
1192
                               + (size_t) BUFFER_SIZE);
 
1193
        if(proc->buffer == NULL){
1281
1194
          error(0, errno, "malloc");
1282
1195
          exitstatus = EX_OSERR;
1283
1196
          goto fallback;
1284
1197
        }
1285
 
        proc->buffer = new_buffer;
1286
1198
        proc->buffer_size += BUFFER_SIZE;
1287
1199
      }
1288
1200
      /* Read from the process */
1341
1253
    free(custom_argv);
1342
1254
  }
1343
1255
  
1344
 
  free(direntries);
1345
 
  
1346
 
  if(dir_fd != -1){
1347
 
    close(dir_fd);
 
1256
  if(dir != NULL){
 
1257
    closedir(dir);
1348
1258
  }
1349
1259
  
1350
1260
  /* Kill the processes */
1370
1280
  free_plugin_list();
1371
1281
  
1372
1282
  free(plugindir);
1373
 
  free(pluginhelperdir);
1374
1283
  free(argfile);
1375
1284
  
1376
1285
  return exitstatus;