/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: 2015-05-23 20:18:34 UTC
  • mto: This revision was merged to the branch mainline in revision 756.
  • Revision ID: teddy@recompile.se-20150523201834-e89ex4ito93yni8x
mandos: Use multiprocessing module to run checkers.

For a long time, the Mandos server has occasionally logged the message
"ERROR: Child process vanished".  This was never a fatal error, but it
has been annoying and slightly worrying, since a definite cause was
not found.  One potential cause could be the "multiprocessing" and
"subprocess" modules conflicting w.r.t. SIGCHLD.  To avoid this,
change the running of checkers from using subprocess.Popen
asynchronously to instead first create a multiprocessing.Process()
(which is asynchronous) calling a function, and have that function
then call subprocess.call() (which is synchronous).  In this way, the
only thing using any asynchronous subprocesses is the multiprocessing
module.

This makes it necessary to change one small thing in the D-Bus API,
since the subprocesses.call() function does not expose the raw wait(2)
status value.

DBUS-API (CheckerCompleted): Change the second value provided by this
                             D-Bus signal from the raw wait(2) status
                             to the actual terminating signal number.
mandos (subprocess_call_pipe): New function to be called by
                               multiprocessing.Process (starting a
                               separate process).
(Client.last_checker signal): New attribute for signal which
                              terminated last checker.  Like
                              last_checker_status, only not accessible
                              via D-Bus.
(Client.checker_callback): Take new "connection" argument and use it
                           to get returncode; set last_checker_signal.
                           Return False so gobject does not call this
                           callback again.
(Client.start_checker): Start checker using a multiprocessing.Process
                        instead of a subprocess.Popen.
(ClientDBus.checker_callback): Take new "connection" argument.        Call
                               Client.checker_callback early to have
                               it set last_checker_status and
                               last_checker_signal; use those.  Change
                               second value provided to D-Bus signal
                               CheckerCompleted to use
                               last_checker_signal if checker was
                               terminated by signal.
mandos-monitor: Update to reflect DBus API change.
(MandosClientWidget.checker_completed): Take "signal" instead of
                                        "condition" argument.  Use it
                                        accordingly.  Remove dead code
                                        (os.WCOREDUMP case).

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,2009 Teddy Hogeborn
6
 
 * Copyright © 2008,2009 Björn Påhlsson
 
5
 * Copyright © 2008-2014 Teddy Hogeborn
 
6
 * Copyright © 2008-2014 Björn Påhlsson
7
7
 * 
8
8
 * This program is free software: you can redistribute it and/or
9
9
 * modify it under the terms of the GNU General Public License as
19
19
 * along with this program.  If not, see
20
20
 * <http://www.gnu.org/licenses/>.
21
21
 * 
22
 
 * Contact the authors at <mandos@fukt.bsnet.se>.
 
22
 * Contact the authors at <mandos@recompile.se>.
23
23
 */
24
24
 
25
25
#define _GNU_SOURCE             /* TEMP_FAILURE_RETRY(), getline(),
26
 
                                   asprintf(), O_CLOEXEC */
 
26
                                   O_CLOEXEC, pipe2() */
27
27
#include <stddef.h>             /* size_t, NULL */
28
28
#include <stdlib.h>             /* malloc(), exit(), EXIT_SUCCESS,
29
29
                                   realloc() */
30
30
#include <stdbool.h>            /* bool, true, false */
31
 
#include <stdio.h>              /* perror, 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() */
 
31
#include <stdio.h>              /* fileno(), fprintf(),
 
32
                                   stderr, STDOUT_FILENO, fclose() */
 
33
#include <sys/types.h>          /* fstat(), struct stat, waitpid(),
 
34
                                   WIFEXITED(), WEXITSTATUS(), wait(),
 
35
                                   pid_t, uid_t, gid_t, getuid(),
 
36
                                   getgid() */
38
37
#include <sys/select.h>         /* fd_set, select(), FD_ZERO(),
39
38
                                   FD_SET(), FD_ISSET(), FD_CLR */
40
39
#include <sys/wait.h>           /* wait(), waitpid(), WIFEXITED(),
41
40
                                   WEXITSTATUS(), WTERMSIG(),
42
41
                                   WCOREDUMP() */
43
 
#include <sys/stat.h>           /* struct stat, stat(), S_ISREG() */
 
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,
70
69
#include <inttypes.h>           /* intmax_t, PRIdMAX, strtoimax() */
71
70
#include <sysexits.h>           /* EX_OSERR, EX_USAGE, EX_IOERR,
72
71
                                   EX_CONFIG, EX_UNAVAILABLE, EX_OK */
 
72
#include <errno.h>              /* errno */
 
73
#include <error.h>              /* error() */
 
74
#include <fnmatch.h>            /* fnmatch() */
73
75
 
74
76
#define BUFFER_SIZE 256
75
77
 
77
79
#define AFILE "/conf/conf.d/mandos/plugin-runner.conf"
78
80
 
79
81
const char *argp_program_version = "plugin-runner " VERSION;
80
 
const char *argp_program_bug_address = "<mandos@fukt.bsnet.se>";
 
82
const char *argp_program_bug_address = "<mandos@recompile.se>";
81
83
 
82
84
typedef struct plugin{
83
85
  char *name;                   /* can be NULL or any plugin name */
103
105
 
104
106
/* Gets an existing plugin based on name,
105
107
   or if none is found, creates a new one */
 
108
__attribute__((warn_unused_result))
106
109
static plugin *getplugin(char *name){
107
110
  /* Check for existing plugin with that name */
108
111
  for(plugin *p = plugin_list; p != NULL; p = p->next){
169
172
}
170
173
 
171
174
/* Helper function for add_argument and add_environment */
 
175
__attribute__((nonnull, warn_unused_result))
172
176
static bool add_to_char_array(const char *new, char ***array,
173
177
                              int *len){
174
178
  /* Resize the pointed-to array to hold one more pointer */
 
179
  char **new_array = NULL;
175
180
  do {
176
 
    *array = realloc(*array, sizeof(char *)
177
 
                     * (size_t) ((*len) + 2));
178
 
  } while(*array == NULL and errno == EINTR);
 
181
    new_array = realloc(*array, sizeof(char *)
 
182
                        * (size_t) ((*len) + 2));
 
183
  } while(new_array == NULL and errno == EINTR);
179
184
  /* Malloc check */
180
 
  if(*array == NULL){
 
185
  if(new_array == NULL){
181
186
    return false;
182
187
  }
 
188
  *array = new_array;
183
189
  /* Make a copy of the new string */
184
190
  char *copy;
185
191
  do {
197
203
}
198
204
 
199
205
/* Add to a plugin's argument vector */
 
206
__attribute__((nonnull(2), warn_unused_result))
200
207
static bool add_argument(plugin *p, const char *arg){
201
208
  if(p == NULL){
202
209
    return false;
205
212
}
206
213
 
207
214
/* Add to a plugin's environment */
 
215
__attribute__((nonnull(2), warn_unused_result))
208
216
static bool add_environment(plugin *p, const char *def, bool replace){
209
217
  if(p == NULL){
210
218
    return false;
212
220
  /* namelen = length of name of environment variable */
213
221
  size_t namelen = (size_t)(strchrnul(def, '=') - def);
214
222
  /* Search for this environment variable */
215
 
  for(char **e = p->environ; *e != NULL; e++){
216
 
    if(strncmp(*e, def, namelen + 1) == 0){
 
223
  for(char **envdef = p->environ; *envdef != NULL; envdef++){
 
224
    if(strncmp(*envdef, def, namelen + 1) == 0){
217
225
      /* It already exists */
218
226
      if(replace){
219
 
        char *new;
 
227
        char *new_envdef;
220
228
        do {
221
 
          new = realloc(*e, strlen(def) + 1);
222
 
        } while(new == NULL and errno == EINTR);
223
 
        if(new == NULL){
 
229
          new_envdef = realloc(*envdef, strlen(def) + 1);
 
230
        } while(new_envdef == NULL and errno == EINTR);
 
231
        if(new_envdef == NULL){
224
232
          return false;
225
233
        }
226
 
        *e = new;
227
 
        strcpy(*e, def);
 
234
        *envdef = new_envdef;
 
235
        strcpy(*envdef, def);
228
236
      }
229
237
      return true;
230
238
    }
232
240
  return add_to_char_array(def, &(p->environ), &(p->envc));
233
241
}
234
242
 
 
243
#ifndef O_CLOEXEC
235
244
/*
236
245
 * Based on the example in the GNU LibC manual chapter 13.13 "File
237
246
 * Descriptor Flags".
238
247
 | [[info:libc:Descriptor%20Flags][File Descriptor Flags]] |
239
248
 */
 
249
__attribute__((warn_unused_result))
240
250
static int set_cloexec_flag(int fd){
241
251
  int ret = (int)TEMP_FAILURE_RETRY(fcntl(fd, F_GETFD, 0));
242
252
  /* If reading the flags failed, return error indication now. */
247
257
  return (int)TEMP_FAILURE_RETRY(fcntl(fd, F_SETFD,
248
258
                                       ret | FD_CLOEXEC));
249
259
}
 
260
#endif  /* not O_CLOEXEC */
250
261
 
251
262
 
252
263
/* Mark processes as completed when they exit, and save their exit
266
277
        /* No child processes */
267
278
        break;
268
279
      }
269
 
      perror("waitpid");
 
280
      error(0, errno, "waitpid");
270
281
    }
271
282
    
272
283
    /* A child exited, find it in process_list */
284
295
}
285
296
 
286
297
/* Prints out a password to stdout */
 
298
__attribute__((nonnull, warn_unused_result))
287
299
static bool print_out_password(const char *buffer, size_t length){
288
300
  ssize_t ret;
289
301
  for(size_t written = 0; written < length; written += (size_t)ret){
297
309
}
298
310
 
299
311
/* Removes and free a plugin from the plugin list */
 
312
__attribute__((nonnull))
300
313
static void free_plugin(plugin *plugin_node){
301
314
  
302
315
  for(char **arg = plugin_node->argv; *arg != NULL; arg++){
336
349
  char *plugindir = NULL;
337
350
  char *argfile = NULL;
338
351
  FILE *conffp;
339
 
  size_t d_name_len;
340
 
  DIR *dir = NULL;
341
 
  struct dirent *dirst;
 
352
  struct dirent **direntries = NULL;
342
353
  struct stat st;
343
354
  fd_set rfds_all;
344
355
  int ret, maxfd = 0;
352
363
                                      .sa_flags = SA_NOCLDSTOP };
353
364
  char **custom_argv = NULL;
354
365
  int custom_argc = 0;
 
366
  int dir_fd = -1;
355
367
  
356
368
  /* Establish a signal handler */
357
369
  sigemptyset(&sigchld_action.sa_mask);
358
370
  ret = sigaddset(&sigchld_action.sa_mask, SIGCHLD);
359
371
  if(ret == -1){
360
 
    perror("sigaddset");
 
372
    error(0, errno, "sigaddset");
361
373
    exitstatus = EX_OSERR;
362
374
    goto fallback;
363
375
  }
364
376
  ret = sigaction(SIGCHLD, &sigchld_action, &old_sigchld_action);
365
377
  if(ret == -1){
366
 
    perror("sigaction");
 
378
    error(0, errno, "sigaction");
367
379
    exitstatus = EX_OSERR;
368
380
    goto fallback;
369
381
  }
414
426
    { .name = NULL }
415
427
  };
416
428
  
 
429
  __attribute__((nonnull(3)))
417
430
  error_t parse_opt(int key, char *arg, struct argp_state *state){
418
431
    errno = 0;
419
432
    switch(key){
420
433
      char *tmp;
421
 
      intmax_t tmpmax;
 
434
      intmax_t tmp_id;
422
435
    case 'g':                   /* --global-options */
423
436
      {
424
437
        char *plugin_option;
427
440
            break;
428
441
          }
429
442
        }
 
443
        errno = 0;
430
444
      }
431
445
      break;
432
446
    case 'G':                   /* --global-env */
433
 
      add_environment(getplugin(NULL), arg, true);
 
447
      if(add_environment(getplugin(NULL), arg, true)){
 
448
        errno = 0;
 
449
      }
434
450
      break;
435
451
    case 'o':                   /* --options-for */
436
452
      {
453
469
            break;
454
470
          }
455
471
        }
 
472
        errno = 0;
456
473
      }
457
474
      break;
458
475
    case 'E':                   /* --env-for */
470
487
          errno = EINVAL;
471
488
          break;
472
489
        }
473
 
        add_environment(getplugin(arg), envdef, true);
 
490
        if(add_environment(getplugin(arg), envdef, true)){
 
491
          errno = 0;
 
492
        }
474
493
      }
475
494
      break;
476
495
    case 'd':                   /* --disable */
478
497
        plugin *p = getplugin(arg);
479
498
        if(p != NULL){
480
499
          p->disabled = true;
 
500
          errno = 0;
481
501
        }
482
502
      }
483
503
      break;
486
506
        plugin *p = getplugin(arg);
487
507
        if(p != NULL){
488
508
          p->disabled = false;
 
509
          errno = 0;
489
510
        }
490
511
      }
491
512
      break;
492
513
    case 128:                   /* --plugin-dir */
493
514
      free(plugindir);
494
515
      plugindir = strdup(arg);
 
516
      if(plugindir != NULL){
 
517
        errno = 0;
 
518
      }
495
519
      break;
496
520
    case 129:                   /* --config-file */
497
521
      /* This is already done by parse_opt_config_file() */
498
522
      break;
499
523
    case 130:                   /* --userid */
500
 
      tmpmax = strtoimax(arg, &tmp, 10);
 
524
      tmp_id = strtoimax(arg, &tmp, 10);
501
525
      if(errno != 0 or tmp == arg or *tmp != '\0'
502
 
         or tmpmax != (uid_t)tmpmax){
 
526
         or tmp_id != (uid_t)tmp_id){
503
527
        argp_error(state, "Bad user ID number: \"%s\", using %"
504
528
                   PRIdMAX, arg, (intmax_t)uid);
505
529
        break;
506
530
      }
507
 
      uid = (uid_t)tmpmax;
 
531
      uid = (uid_t)tmp_id;
 
532
      errno = 0;
508
533
      break;
509
534
    case 131:                   /* --groupid */
510
 
      tmpmax = strtoimax(arg, &tmp, 10);
 
535
      tmp_id = strtoimax(arg, &tmp, 10);
511
536
      if(errno != 0 or tmp == arg or *tmp != '\0'
512
 
         or tmpmax != (gid_t)tmpmax){
 
537
         or tmp_id != (gid_t)tmp_id){
513
538
        argp_error(state, "Bad group ID number: \"%s\", using %"
514
539
                   PRIdMAX, arg, (intmax_t)gid);
515
540
        break;
516
541
      }
517
 
      gid = (gid_t)tmpmax;
 
542
      gid = (gid_t)tmp_id;
 
543
      errno = 0;
518
544
      break;
519
545
    case 132:                   /* --debug */
520
546
      debug = true;
568
594
    case 129:                   /* --config-file */
569
595
      free(argfile);
570
596
      argfile = strdup(arg);
 
597
      if(argfile != NULL){
 
598
        errno = 0;
 
599
      }
571
600
      break;
572
601
    case 130:                   /* --userid */
573
602
    case 131:                   /* --groupid */
599
628
  case ENOMEM:
600
629
  default:
601
630
    errno = ret;
602
 
    perror("argp_parse");
 
631
    error(0, errno, "argp_parse");
603
632
    exitstatus = EX_OSERR;
604
633
    goto fallback;
605
634
  case EINVAL:
626
655
    custom_argc = 1;
627
656
    custom_argv = malloc(sizeof(char*) * 2);
628
657
    if(custom_argv == NULL){
629
 
      perror("malloc");
 
658
      error(0, errno, "malloc");
630
659
      exitstatus = EX_OSERR;
631
660
      goto fallback;
632
661
    }
649
678
        }
650
679
        new_arg = strdup(p);
651
680
        if(new_arg == NULL){
652
 
          perror("strdup");
 
681
          error(0, errno, "strdup");
653
682
          exitstatus = EX_OSERR;
654
683
          free(org_line);
655
684
          goto fallback;
656
685
        }
657
686
        
658
687
        custom_argc += 1;
659
 
        custom_argv = realloc(custom_argv, sizeof(char *)
660
 
                              * ((unsigned int) custom_argc + 1));
661
 
        if(custom_argv == NULL){
662
 
          perror("realloc");
663
 
          exitstatus = EX_OSERR;
664
 
          free(org_line);
665
 
          goto fallback;
 
688
        {
 
689
          char **new_argv = realloc(custom_argv, sizeof(char *)
 
690
                                    * ((unsigned int)
 
691
                                       custom_argc + 1));
 
692
          if(new_argv == NULL){
 
693
            error(0, errno, "realloc");
 
694
            exitstatus = EX_OSERR;
 
695
            free(new_arg);
 
696
            free(org_line);
 
697
            goto fallback;
 
698
          } else {
 
699
            custom_argv = new_argv;
 
700
          }
666
701
        }
667
702
        custom_argv[custom_argc-1] = new_arg;
668
703
        custom_argv[custom_argc] = NULL;
672
707
      ret = fclose(conffp);
673
708
    } while(ret == EOF and errno == EINTR);
674
709
    if(ret == EOF){
675
 
      perror("fclose");
 
710
      error(0, errno, "fclose");
676
711
      exitstatus = EX_IOERR;
677
712
      goto fallback;
678
713
    }
681
716
    /* Check for harmful errors and go to fallback. Other errors might
682
717
       not affect opening plugins */
683
718
    if(errno == EMFILE or errno == ENFILE or errno == ENOMEM){
684
 
      perror("fopen");
 
719
      error(0, errno, "fopen");
685
720
      exitstatus = EX_OSERR;
686
721
      goto fallback;
687
722
    }
698
733
    case ENOMEM:
699
734
    default:
700
735
      errno = ret;
701
 
      perror("argp_parse");
 
736
      error(0, errno, "argp_parse");
702
737
      exitstatus = EX_OSERR;
703
738
      goto fallback;
704
739
    case EINVAL:
718
753
  case ENOMEM:
719
754
  default:
720
755
    errno = ret;
721
 
    perror("argp_parse");
 
756
    error(0, errno, "argp_parse");
722
757
    exitstatus = EX_OSERR;
723
758
    goto fallback;
724
759
  case EINVAL:
740
775
    }
741
776
  }
742
777
  
743
 
  /* Strip permissions down to nobody */
744
 
  setgid(gid);
 
778
  if(getuid() == 0){
 
779
    /* Work around Debian bug #633582:
 
780
       <http://bugs.debian.org/633582> */
 
781
    int plugindir_fd = open(/* plugindir or */ PDIR, O_RDONLY);
 
782
    if(plugindir_fd == -1){
 
783
      if(errno != ENOENT){
 
784
        error(0, errno, "open(\"" PDIR "\")");
 
785
      }
 
786
    } else {
 
787
      ret = (int)TEMP_FAILURE_RETRY(fstat(plugindir_fd, &st));
 
788
      if(ret == -1){
 
789
        error(0, errno, "fstat");
 
790
      } else {
 
791
        if(S_ISDIR(st.st_mode) and st.st_uid == 0 and st.st_gid == 0){
 
792
          ret = fchown(plugindir_fd, uid, gid);
 
793
          if(ret == -1){
 
794
            error(0, errno, "fchown");
 
795
          }
 
796
        }
 
797
      }
 
798
      TEMP_FAILURE_RETRY(close(plugindir_fd));
 
799
    }
 
800
  }
 
801
  
 
802
  /* Lower permissions */
 
803
  ret = setgid(gid);
745
804
  if(ret == -1){
746
 
    perror("setgid");
 
805
    error(0, errno, "setgid");
747
806
  }
748
807
  ret = setuid(uid);
749
808
  if(ret == -1){
750
 
    perror("setuid");
 
809
    error(0, errno, "setuid");
751
810
  }
752
811
  
753
812
  /* Open plugin directory with close_on_exec flag */
754
813
  {
755
 
    int dir_fd = -1;
756
 
    if(plugindir == NULL){
757
 
      dir_fd = open(PDIR, O_RDONLY |
758
 
#ifdef O_CLOEXEC
759
 
                    O_CLOEXEC
760
 
#else  /* not O_CLOEXEC */
761
 
                    0
762
 
#endif  /* not O_CLOEXEC */
763
 
                    );
764
 
    } else {
765
 
      dir_fd = open(plugindir, O_RDONLY |
766
 
#ifdef O_CLOEXEC
767
 
                    O_CLOEXEC
768
 
#else  /* not O_CLOEXEC */
769
 
                    0
770
 
#endif  /* not O_CLOEXEC */
771
 
                    );
772
 
    }
 
814
    dir_fd = open(plugindir != NULL ? plugindir : PDIR, O_RDONLY |
 
815
#ifdef O_CLOEXEC
 
816
                  O_CLOEXEC
 
817
#else  /* not O_CLOEXEC */
 
818
                  0
 
819
#endif  /* not O_CLOEXEC */
 
820
                  );
773
821
    if(dir_fd == -1){
774
 
      perror("Could not open plugin dir");
 
822
      error(0, errno, "Could not open plugin dir");
775
823
      exitstatus = EX_UNAVAILABLE;
776
824
      goto fallback;
777
825
    }
780
828
  /* Set the FD_CLOEXEC flag on the directory */
781
829
    ret = set_cloexec_flag(dir_fd);
782
830
    if(ret < 0){
783
 
      perror("set_cloexec_flag");
784
 
      TEMP_FAILURE_RETRY(close(dir_fd));
 
831
      error(0, errno, "set_cloexec_flag");
785
832
      exitstatus = EX_OSERR;
786
833
      goto fallback;
787
834
    }
788
835
#endif  /* O_CLOEXEC */
789
 
    
790
 
    dir = fdopendir(dir_fd);
791
 
    if(dir == NULL){
792
 
      perror("Could not open plugin dir");
793
 
      TEMP_FAILURE_RETRY(close(dir_fd));
794
 
      exitstatus = EX_OSERR;
795
 
      goto fallback;
 
836
  }
 
837
  
 
838
  int good_name(const struct dirent * const dirent){
 
839
    const char * const patterns[] = { ".*", "#*#", "*~", "*.dpkg-new",
 
840
                                      "*.dpkg-old", "*.dpkg-bak",
 
841
                                      "*.dpkg-divert", NULL };
 
842
#ifdef __GNUC__
 
843
#pragma GCC diagnostic push
 
844
#pragma GCC diagnostic ignored "-Wcast-qual"
 
845
#endif
 
846
    for(const char **pat = (const char **)patterns;
 
847
        *pat != NULL; pat++){
 
848
#ifdef __GNUC__
 
849
#pragma GCC diagnostic pop
 
850
#endif
 
851
      if(fnmatch(*pat, dirent->d_name, FNM_FILE_NAME | FNM_PERIOD)
 
852
         != FNM_NOMATCH){
 
853
        if(debug){
 
854
            fprintf(stderr, "Ignoring plugin dir entry \"%s\""
 
855
                    " matching pattern %s\n", dirent->d_name, *pat);
 
856
        }
 
857
        return 0;
 
858
      }
796
859
    }
 
860
    return 1;
 
861
  }
 
862
  
 
863
#ifdef __GLIBC__
 
864
#if __GLIBC_PREREQ(2, 15)
 
865
  int numplugins = scandirat(dir_fd, ".", &direntries, good_name,
 
866
                             alphasort);
 
867
#else  /* not __GLIBC_PREREQ(2, 15) */
 
868
  int numplugins = scandir(plugindir != NULL ? plugindir : PDIR,
 
869
                           &direntries, good_name, alphasort);
 
870
#endif  /* not __GLIBC_PREREQ(2, 15) */
 
871
#else   /* not __GLIBC__ */
 
872
  int numplugins = scandir(plugindir != NULL ? plugindir : PDIR,
 
873
                           &direntries, good_name, alphasort);
 
874
#endif  /* not __GLIBC__ */
 
875
  if(numplugins == -1){
 
876
    error(0, errno, "Could not scan plugin dir");
 
877
    direntries = NULL;
 
878
    exitstatus = EX_OSERR;
 
879
    goto fallback;
797
880
  }
798
881
  
799
882
  FD_ZERO(&rfds_all);
800
883
  
801
884
  /* Read and execute any executable in the plugin directory*/
802
 
  while(true){
803
 
    do {
804
 
      dirst = readdir(dir);
805
 
    } while(dirst == NULL and errno == EINTR);
806
 
    
807
 
    /* All directory entries have been processed */
808
 
    if(dirst == NULL){
809
 
      if(errno == EBADF){
810
 
        perror("readdir");
811
 
        exitstatus = EX_IOERR;
812
 
        goto fallback;
813
 
      }
814
 
      break;
815
 
    }
816
 
    
817
 
    d_name_len = strlen(dirst->d_name);
818
 
    
819
 
    /* Ignore dotfiles, backup files and other junk */
820
 
    {
821
 
      bool bad_name = false;
822
 
      
823
 
      const char const *bad_prefixes[] = { ".", "#", NULL };
824
 
      
825
 
      const char const *bad_suffixes[] = { "~", "#", ".dpkg-new",
826
 
                                           ".dpkg-old",
827
 
                                           ".dpkg-bak",
828
 
                                           ".dpkg-divert", NULL };
829
 
      for(const char **pre = bad_prefixes; *pre != NULL; pre++){
830
 
        size_t pre_len = strlen(*pre);
831
 
        if((d_name_len >= pre_len)
832
 
           and strncmp((dirst->d_name), *pre, pre_len) == 0){
833
 
          if(debug){
834
 
            fprintf(stderr, "Ignoring plugin dir entry \"%s\""
835
 
                    " with bad prefix %s\n", dirst->d_name, *pre);
836
 
          }
837
 
          bad_name = true;
838
 
          break;
839
 
        }
840
 
      }
841
 
      if(bad_name){
842
 
        continue;
843
 
      }
844
 
      for(const char **suf = bad_suffixes; *suf != NULL; suf++){
845
 
        size_t suf_len = strlen(*suf);
846
 
        if((d_name_len >= suf_len)
847
 
           and (strcmp((dirst->d_name) + d_name_len-suf_len, *suf)
848
 
                == 0)){
849
 
          if(debug){
850
 
            fprintf(stderr, "Ignoring plugin dir entry \"%s\""
851
 
                    " with bad suffix %s\n", dirst->d_name, *suf);
852
 
          }
853
 
          bad_name = true;
854
 
          break;
855
 
        }
856
 
      }
857
 
      
858
 
      if(bad_name){
859
 
        continue;
860
 
      }
861
 
    }
862
 
    
863
 
    char *filename;
864
 
    if(plugindir == NULL){
865
 
      ret = (int)TEMP_FAILURE_RETRY(asprintf(&filename, PDIR "/%s",
866
 
                                             dirst->d_name));
867
 
    } else {
868
 
      ret = (int)TEMP_FAILURE_RETRY(asprintf(&filename, "%s/%s",
869
 
                                             plugindir,
870
 
                                             dirst->d_name));
871
 
    }
872
 
    if(ret < 0){
873
 
      perror("asprintf");
 
885
  for(int i = 0; i < numplugins; i++){
 
886
    
 
887
    int plugin_fd = openat(dir_fd, direntries[i]->d_name, O_RDONLY);
 
888
    if(plugin_fd == -1){
 
889
      error(0, errno, "Could not open plugin");
 
890
      free(direntries[i]);
874
891
      continue;
875
892
    }
876
 
    
877
 
    ret = (int)TEMP_FAILURE_RETRY(stat(filename, &st));
 
893
    ret = (int)TEMP_FAILURE_RETRY(fstat(plugin_fd, &st));
878
894
    if(ret == -1){
879
 
      perror("stat");
880
 
      free(filename);
 
895
      error(0, errno, "stat");
 
896
      TEMP_FAILURE_RETRY(close(plugin_fd));
 
897
      free(direntries[i]);
881
898
      continue;
882
899
    }
883
900
    
884
901
    /* Ignore non-executable files */
885
902
    if(not S_ISREG(st.st_mode)
886
 
       or (TEMP_FAILURE_RETRY(access(filename, X_OK)) != 0)){
 
903
       or (TEMP_FAILURE_RETRY(faccessat(dir_fd, direntries[i]->d_name,
 
904
                                        X_OK, 0)) != 0)){
887
905
      if(debug){
888
 
        fprintf(stderr, "Ignoring plugin dir entry \"%s\""
889
 
                " with bad type or mode\n", filename);
 
906
        fprintf(stderr, "Ignoring plugin dir entry \"%s/%s\""
 
907
                " with bad type or mode\n",
 
908
                plugindir != NULL ? plugindir : PDIR,
 
909
                direntries[i]->d_name);
890
910
      }
891
 
      free(filename);
 
911
      TEMP_FAILURE_RETRY(close(plugin_fd));
 
912
      free(direntries[i]);
892
913
      continue;
893
914
    }
894
915
    
895
 
    plugin *p = getplugin(dirst->d_name);
 
916
    plugin *p = getplugin(direntries[i]->d_name);
896
917
    if(p == NULL){
897
 
      perror("getplugin");
898
 
      free(filename);
 
918
      error(0, errno, "getplugin");
 
919
      TEMP_FAILURE_RETRY(close(plugin_fd));
 
920
      free(direntries[i]);
899
921
      continue;
900
922
    }
901
923
    if(p->disabled){
902
924
      if(debug){
903
925
        fprintf(stderr, "Ignoring disabled plugin \"%s\"\n",
904
 
                dirst->d_name);
 
926
                direntries[i]->d_name);
905
927
      }
906
 
      free(filename);
 
928
      TEMP_FAILURE_RETRY(close(plugin_fd));
 
929
      free(direntries[i]);
907
930
      continue;
908
931
    }
909
932
    {
912
935
      if(g != NULL){
913
936
        for(char **a = g->argv + 1; *a != NULL; a++){
914
937
          if(not add_argument(p, *a)){
915
 
            perror("add_argument");
 
938
            error(0, errno, "add_argument");
916
939
          }
917
940
        }
918
941
        /* Add global environment variables */
919
942
        for(char **e = g->environ; *e != NULL; e++){
920
943
          if(not add_environment(p, *e, false)){
921
 
            perror("add_environment");
 
944
            error(0, errno, "add_environment");
922
945
          }
923
946
        }
924
947
      }
925
948
    }
926
 
    /* If this plugin has any environment variables, we will call
927
 
       using execve and need to duplicate the environment from this
928
 
       process, too. */
 
949
    /* If this plugin has any environment variables, we need to
 
950
       duplicate the environment from this process, too. */
929
951
    if(p->environ[0] != NULL){
930
952
      for(char **e = environ; *e != NULL; e++){
931
953
        if(not add_environment(p, *e, false)){
932
 
          perror("add_environment");
 
954
          error(0, errno, "add_environment");
933
955
        }
934
956
      }
935
957
    }
936
958
    
937
959
    int pipefd[2];
 
960
#ifndef O_CLOEXEC
938
961
    ret = (int)TEMP_FAILURE_RETRY(pipe(pipefd));
 
962
#else  /* O_CLOEXEC */
 
963
    ret = (int)TEMP_FAILURE_RETRY(pipe2(pipefd, O_CLOEXEC));
 
964
#endif  /* O_CLOEXEC */
939
965
    if(ret == -1){
940
 
      perror("pipe");
941
 
      exitstatus = EX_OSERR;
942
 
      goto fallback;
943
 
    }
 
966
      error(0, errno, "pipe");
 
967
      exitstatus = EX_OSERR;
 
968
      free(direntries[i]);
 
969
      goto fallback;
 
970
    }
 
971
    if(pipefd[0] >= FD_SETSIZE){
 
972
      fprintf(stderr, "pipe()[0] (%d) >= FD_SETSIZE (%d)", pipefd[0],
 
973
              FD_SETSIZE);
 
974
      TEMP_FAILURE_RETRY(close(pipefd[0]));
 
975
      TEMP_FAILURE_RETRY(close(pipefd[1]));
 
976
      exitstatus = EX_OSERR;
 
977
      free(direntries[i]);
 
978
      goto fallback;
 
979
    }
 
980
#ifndef O_CLOEXEC
944
981
    /* Ask OS to automatic close the pipe on exec */
945
982
    ret = set_cloexec_flag(pipefd[0]);
946
983
    if(ret < 0){
947
 
      perror("set_cloexec_flag");
 
984
      error(0, errno, "set_cloexec_flag");
 
985
      TEMP_FAILURE_RETRY(close(pipefd[0]));
 
986
      TEMP_FAILURE_RETRY(close(pipefd[1]));
948
987
      exitstatus = EX_OSERR;
 
988
      free(direntries[i]);
949
989
      goto fallback;
950
990
    }
951
991
    ret = set_cloexec_flag(pipefd[1]);
952
992
    if(ret < 0){
953
 
      perror("set_cloexec_flag");
 
993
      error(0, errno, "set_cloexec_flag");
 
994
      TEMP_FAILURE_RETRY(close(pipefd[0]));
 
995
      TEMP_FAILURE_RETRY(close(pipefd[1]));
954
996
      exitstatus = EX_OSERR;
 
997
      free(direntries[i]);
955
998
      goto fallback;
956
999
    }
 
1000
#endif  /* not O_CLOEXEC */
957
1001
    /* Block SIGCHLD until process is safely in process list */
958
1002
    ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_BLOCK,
959
1003
                                              &sigchld_action.sa_mask,
960
1004
                                              NULL));
961
1005
    if(ret < 0){
962
 
      perror("sigprocmask");
 
1006
      error(0, errno, "sigprocmask");
963
1007
      exitstatus = EX_OSERR;
 
1008
      free(direntries[i]);
964
1009
      goto fallback;
965
1010
    }
966
1011
    /* Starting a new process to be watched */
969
1014
      pid = fork();
970
1015
    } while(pid == -1 and errno == EINTR);
971
1016
    if(pid == -1){
972
 
      perror("fork");
 
1017
      error(0, errno, "fork");
 
1018
      TEMP_FAILURE_RETRY(sigprocmask(SIG_UNBLOCK,
 
1019
                                     &sigchld_action.sa_mask, NULL));
 
1020
      TEMP_FAILURE_RETRY(close(pipefd[0]));
 
1021
      TEMP_FAILURE_RETRY(close(pipefd[1]));
973
1022
      exitstatus = EX_OSERR;
 
1023
      free(direntries[i]);
974
1024
      goto fallback;
975
1025
    }
976
1026
    if(pid == 0){
977
1027
      /* this is the child process */
978
1028
      ret = sigaction(SIGCHLD, &old_sigchld_action, NULL);
979
1029
      if(ret < 0){
980
 
        perror("sigaction");
 
1030
        error(0, errno, "sigaction");
981
1031
        _exit(EX_OSERR);
982
1032
      }
983
1033
      ret = sigprocmask(SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
984
1034
      if(ret < 0){
985
 
        perror("sigprocmask");
 
1035
        error(0, errno, "sigprocmask");
986
1036
        _exit(EX_OSERR);
987
1037
      }
988
1038
      
989
1039
      ret = dup2(pipefd[1], STDOUT_FILENO); /* replace our stdout */
990
1040
      if(ret == -1){
991
 
        perror("dup2");
 
1041
        error(0, errno, "dup2");
992
1042
        _exit(EX_OSERR);
993
1043
      }
994
1044
      
995
 
      if(dirfd(dir) < 0){
996
 
        /* If dir has no file descriptor, we could not set FD_CLOEXEC
997
 
           above and must now close it manually here. */
998
 
        closedir(dir);
999
 
      }
1000
 
      if(p->environ[0] == NULL){
1001
 
        if(execv(filename, p->argv) < 0){
1002
 
          perror("execv");
1003
 
          _exit(EX_OSERR);
1004
 
        }
1005
 
      } else {
1006
 
        if(execve(filename, p->argv, p->environ) < 0){
1007
 
          perror("execve");
1008
 
          _exit(EX_OSERR);
1009
 
        }
 
1045
      if(fexecve(plugin_fd, p->argv,
 
1046
                (p->environ[0] != NULL) ? p->environ : environ) < 0){
 
1047
        error(0, errno, "fexecve for %s/%s",
 
1048
              plugindir != NULL ? plugindir : PDIR,
 
1049
              direntries[i]->d_name);
 
1050
        _exit(EX_OSERR);
1010
1051
      }
1011
1052
      /* no return */
1012
1053
    }
1013
1054
    /* Parent process */
1014
1055
    TEMP_FAILURE_RETRY(close(pipefd[1])); /* Close unused write end of
1015
1056
                                             pipe */
1016
 
    free(filename);
1017
 
    plugin *new_plugin = getplugin(dirst->d_name);
 
1057
    TEMP_FAILURE_RETRY(close(plugin_fd));
 
1058
    plugin *new_plugin = getplugin(direntries[i]->d_name);
1018
1059
    if(new_plugin == NULL){
1019
 
      perror("getplugin");
 
1060
      error(0, errno, "getplugin");
1020
1061
      ret = (int)(TEMP_FAILURE_RETRY
1021
1062
                  (sigprocmask(SIG_UNBLOCK, &sigchld_action.sa_mask,
1022
1063
                               NULL)));
1023
1064
      if(ret < 0){
1024
 
        perror("sigprocmask");
 
1065
        error(0, errno, "sigprocmask");
1025
1066
      }
1026
1067
      exitstatus = EX_OSERR;
 
1068
      free(direntries[i]);
1027
1069
      goto fallback;
1028
1070
    }
 
1071
    free(direntries[i]);
1029
1072
    
1030
1073
    new_plugin->pid = pid;
1031
1074
    new_plugin->fd = pipefd[0];
1036
1079
                                              &sigchld_action.sa_mask,
1037
1080
                                              NULL));
1038
1081
    if(ret < 0){
1039
 
      perror("sigprocmask");
 
1082
      error(0, errno, "sigprocmask");
1040
1083
      exitstatus = EX_OSERR;
1041
1084
      goto fallback;
1042
1085
    }
1043
1086
    
 
1087
#if defined (__GNUC__) and defined (__GLIBC__)
 
1088
#if not __GLIBC_PREREQ(2, 16)
 
1089
#pragma GCC diagnostic push
 
1090
#pragma GCC diagnostic ignored "-Wsign-conversion"
 
1091
#endif
 
1092
#endif
1044
1093
    FD_SET(new_plugin->fd, &rfds_all); /* Spurious warning from
1045
 
                                          -Wconversion */
 
1094
                                          -Wconversion in GNU libc
 
1095
                                          before 2.16 */
 
1096
#if defined (__GNUC__) and defined (__GLIBC__)
 
1097
#if not __GLIBC_PREREQ(2, 16)
 
1098
#pragma GCC diagnostic pop
 
1099
#endif
 
1100
#endif
1046
1101
    
1047
1102
    if(maxfd < new_plugin->fd){
1048
1103
      maxfd = new_plugin->fd;
1049
1104
    }
1050
1105
  }
1051
1106
  
1052
 
  TEMP_FAILURE_RETRY(closedir(dir));
1053
 
  dir = NULL;
 
1107
  free(direntries);
 
1108
  direntries = NULL;
 
1109
  TEMP_FAILURE_RETRY(close(dir_fd));
 
1110
  dir_fd = -1;
1054
1111
  free_plugin(getplugin(NULL));
1055
1112
  
1056
1113
  for(plugin *p = plugin_list; p != NULL; p = p->next){
1069
1126
    fd_set rfds = rfds_all;
1070
1127
    int select_ret = select(maxfd+1, &rfds, NULL, NULL, NULL);
1071
1128
    if(select_ret == -1 and errno != EINTR){
1072
 
      perror("select");
 
1129
      error(0, errno, "select");
1073
1130
      exitstatus = EX_OSERR;
1074
1131
      goto fallback;
1075
1132
    }
1102
1159
          }
1103
1160
          
1104
1161
          /* Remove the plugin */
 
1162
#if defined (__GNUC__) and defined (__GLIBC__)
 
1163
#if not __GLIBC_PREREQ(2, 16)
 
1164
#pragma GCC diagnostic push
 
1165
#pragma GCC diagnostic ignored "-Wsign-conversion"
 
1166
#endif
 
1167
#endif
1105
1168
          FD_CLR(proc->fd, &rfds_all); /* Spurious warning from
1106
 
                                          -Wconversion */
 
1169
                                          -Wconversion in GNU libc
 
1170
                                          before 2.16 */
 
1171
#if defined (__GNUC__) and defined (__GLIBC__)
 
1172
#if not __GLIBC_PREREQ(2, 16)
 
1173
#pragma GCC diagnostic pop
 
1174
#endif
 
1175
#endif
1107
1176
          
1108
1177
          /* Block signal while modifying process_list */
1109
1178
          ret = (int)TEMP_FAILURE_RETRY(sigprocmask
1111
1180
                                         &sigchld_action.sa_mask,
1112
1181
                                         NULL));
1113
1182
          if(ret < 0){
1114
 
            perror("sigprocmask");
 
1183
            error(0, errno, "sigprocmask");
1115
1184
            exitstatus = EX_OSERR;
1116
1185
            goto fallback;
1117
1186
          }
1125
1194
                      (sigprocmask(SIG_UNBLOCK,
1126
1195
                                   &sigchld_action.sa_mask, NULL)));
1127
1196
          if(ret < 0){
1128
 
            perror("sigprocmask");
 
1197
            error(0, errno, "sigprocmask");
1129
1198
            exitstatus = EX_OSERR;
1130
1199
            goto fallback;
1131
1200
          }
1142
1211
        bool bret = print_out_password(proc->buffer,
1143
1212
                                       proc->buffer_length);
1144
1213
        if(not bret){
1145
 
          perror("print_out_password");
 
1214
          error(0, errno, "print_out_password");
1146
1215
          exitstatus = EX_IOERR;
1147
1216
        }
1148
1217
        goto fallback;
1149
1218
      }
1150
1219
      
1151
1220
      /* This process has not completed.  Does it have any output? */
 
1221
#if defined (__GNUC__) and defined (__GLIBC__)
 
1222
#if not __GLIBC_PREREQ(2, 16)
 
1223
#pragma GCC diagnostic push
 
1224
#pragma GCC diagnostic ignored "-Wsign-conversion"
 
1225
#endif
 
1226
#endif
1152
1227
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){ /* Spurious
1153
1228
                                                         warning from
1154
 
                                                         -Wconversion */
 
1229
                                                         -Wconversion
 
1230
                                                         in GNU libc
 
1231
                                                         before
 
1232
                                                         2.16 */
 
1233
#if defined (__GNUC__) and defined (__GLIBC__)
 
1234
#if not __GLIBC_PREREQ(2, 16)
 
1235
#pragma GCC diagnostic pop
 
1236
#endif
 
1237
#endif
1155
1238
        /* This process had nothing to say at this time */
1156
1239
        proc = proc->next;
1157
1240
        continue;
1158
1241
      }
1159
1242
      /* Before reading, make the process' data buffer large enough */
1160
1243
      if(proc->buffer_length + BUFFER_SIZE > proc->buffer_size){
1161
 
        proc->buffer = realloc(proc->buffer, proc->buffer_size
1162
 
                               + (size_t) BUFFER_SIZE);
1163
 
        if(proc->buffer == NULL){
1164
 
          perror("malloc");
 
1244
        char *new_buffer = realloc(proc->buffer, proc->buffer_size
 
1245
                                   + (size_t) BUFFER_SIZE);
 
1246
        if(new_buffer == NULL){
 
1247
          error(0, errno, "malloc");
1165
1248
          exitstatus = EX_OSERR;
1166
1249
          goto fallback;
1167
1250
        }
 
1251
        proc->buffer = new_buffer;
1168
1252
        proc->buffer_size += BUFFER_SIZE;
1169
1253
      }
1170
1254
      /* Read from the process */
1204
1288
    }
1205
1289
    bret = print_out_password(passwordbuffer, len);
1206
1290
    if(not bret){
1207
 
      perror("print_out_password");
 
1291
      error(0, errno, "print_out_password");
1208
1292
      exitstatus = EX_IOERR;
1209
1293
    }
1210
1294
  }
1212
1296
  /* Restore old signal handler */
1213
1297
  ret = sigaction(SIGCHLD, &old_sigchld_action, NULL);
1214
1298
  if(ret == -1){
1215
 
    perror("sigaction");
 
1299
    error(0, errno, "sigaction");
1216
1300
    exitstatus = EX_OSERR;
1217
1301
  }
1218
1302
  
1223
1307
    free(custom_argv);
1224
1308
  }
1225
1309
  
1226
 
  if(dir != NULL){
1227
 
    closedir(dir);
 
1310
  free(direntries);
 
1311
  
 
1312
  if(dir_fd != -1){
 
1313
    TEMP_FAILURE_RETRY(close(dir_fd));
1228
1314
  }
1229
1315
  
1230
1316
  /* Kill the processes */
1234
1320
      ret = kill(p->pid, SIGTERM);
1235
1321
      if(ret == -1 and errno != ESRCH){
1236
1322
        /* Set-uid proccesses might not get closed */
1237
 
        perror("kill");
 
1323
        error(0, errno, "kill");
1238
1324
      }
1239
1325
    }
1240
1326
  }
1244
1330
    ret = wait(NULL);
1245
1331
  } while(ret >= 0);
1246
1332
  if(errno != ECHILD){
1247
 
    perror("wait");
 
1333
    error(0, errno, "wait");
1248
1334
  }
1249
1335
  
1250
1336
  free_plugin_list();