/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: 2019-08-05 21:14:05 UTC
  • Revision ID: teddy@recompile.se-20190805211405-9m6hecekaihpttz9
Override lintian warnings about upgrading from old versions

There are some really things which are imperative that we fix in case
someone were to upgrade from a really old version.  We want to keep
these fixes in the postinst maintainer scripts, even though lintian
complains about such old upgrades not being supported by Debian in
general.  We prefer the code being there, for the sake of the users.

* debian/mandos-client.lintian-overrides
  (maintainer-script-supports-ancient-package-version): New.
  debian/mandos.lintian-overrides
  (maintainer-script-supports-ancient-package-version): - '' -

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
7
 
 * 
8
 
 * This program is free software: you can redistribute it and/or
9
 
 * modify it under the terms of the GNU General Public License as
10
 
 * published by the Free Software Foundation, either version 3 of the
11
 
 * License, or (at your option) any later version.
12
 
 * 
13
 
 * This program is distributed in the hope that it will be useful, but
 
5
 * Copyright © 2008-2018 Teddy Hogeborn
 
6
 * Copyright © 2008-2018 Björn Påhlsson
 
7
 * 
 
8
 * This file is part of Mandos.
 
9
 * 
 
10
 * Mandos is free software: you can redistribute it and/or modify it
 
11
 * under the terms of the GNU General Public License as published by
 
12
 * the Free Software Foundation, either version 3 of the License, or
 
13
 * (at your option) any later version.
 
14
 * 
 
15
 * Mandos is distributed in the hope that it will be useful, but
14
16
 * WITHOUT ANY WARRANTY; without even the implied warranty of
15
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16
18
 * General Public License for more details.
17
19
 * 
18
20
 * You should have received a copy of the GNU General Public License
19
 
 * along with this program.  If not, see
20
 
 * <http://www.gnu.org/licenses/>.
 
21
 * along with Mandos.  If not, see <http://www.gnu.org/licenses/>.
21
22
 * 
22
 
 * Contact the authors at <mandos@fukt.bsnet.se>.
 
23
 * Contact the authors at <mandos@recompile.se>.
23
24
 */
24
25
 
25
26
#define _GNU_SOURCE             /* TEMP_FAILURE_RETRY(), getline(),
26
 
                                   asprintf(), O_CLOEXEC */
 
27
                                   O_CLOEXEC, pipe2() */
27
28
#include <stddef.h>             /* size_t, NULL */
28
29
#include <stdlib.h>             /* malloc(), exit(), EXIT_SUCCESS,
29
30
                                   realloc() */
30
31
#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() */
 
32
#include <stdio.h>              /* fileno(), fprintf(),
 
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,
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
 
76
78
#define PDIR "/lib/mandos/plugins.d"
 
79
#define PHDIR "/lib/mandos/plugin-helpers"
77
80
#define AFILE "/conf/conf.d/mandos/plugin-runner.conf"
78
81
 
79
82
const char *argp_program_version = "plugin-runner " VERSION;
80
 
const char *argp_program_bug_address = "<mandos@fukt.bsnet.se>";
 
83
const char *argp_program_bug_address = "<mandos@recompile.se>";
81
84
 
82
85
typedef struct plugin{
83
86
  char *name;                   /* can be NULL or any plugin name */
103
106
 
104
107
/* Gets an existing plugin based on name,
105
108
   or if none is found, creates a new one */
 
109
__attribute__((warn_unused_result))
106
110
static plugin *getplugin(char *name){
107
111
  /* Check for existing plugin with that name */
108
112
  for(plugin *p = plugin_list; p != NULL; p = p->next){
169
173
}
170
174
 
171
175
/* Helper function for add_argument and add_environment */
 
176
__attribute__((nonnull, warn_unused_result))
172
177
static bool add_to_char_array(const char *new, char ***array,
173
178
                              int *len){
174
179
  /* Resize the pointed-to array to hold one more pointer */
 
180
  char **new_array = NULL;
175
181
  do {
176
 
    *array = realloc(*array, sizeof(char *)
177
 
                     * (size_t) ((*len) + 2));
178
 
  } while(*array == NULL and errno == EINTR);
 
182
    new_array = realloc(*array, sizeof(char *)
 
183
                        * (size_t) ((*len) + 2));
 
184
  } while(new_array == NULL and errno == EINTR);
179
185
  /* Malloc check */
180
 
  if(*array == NULL){
 
186
  if(new_array == NULL){
181
187
    return false;
182
188
  }
 
189
  *array = new_array;
183
190
  /* Make a copy of the new string */
184
191
  char *copy;
185
192
  do {
197
204
}
198
205
 
199
206
/* Add to a plugin's argument vector */
 
207
__attribute__((nonnull(2), warn_unused_result))
200
208
static bool add_argument(plugin *p, const char *arg){
201
209
  if(p == NULL){
202
210
    return false;
205
213
}
206
214
 
207
215
/* Add to a plugin's environment */
 
216
__attribute__((nonnull(2), warn_unused_result))
208
217
static bool add_environment(plugin *p, const char *def, bool replace){
209
218
  if(p == NULL){
210
219
    return false;
212
221
  /* namelen = length of name of environment variable */
213
222
  size_t namelen = (size_t)(strchrnul(def, '=') - def);
214
223
  /* Search for this environment variable */
215
 
  for(char **e = p->environ; *e != NULL; e++){
216
 
    if(strncmp(*e, def, namelen + 1) == 0){
 
224
  for(char **envdef = p->environ; *envdef != NULL; envdef++){
 
225
    if(strncmp(*envdef, def, namelen + 1) == 0){
217
226
      /* It already exists */
218
227
      if(replace){
219
 
        char *new;
 
228
        char *new_envdef;
220
229
        do {
221
 
          new = realloc(*e, strlen(def) + 1);
222
 
        } while(new == NULL and errno == EINTR);
223
 
        if(new == NULL){
 
230
          new_envdef = realloc(*envdef, strlen(def) + 1);
 
231
        } while(new_envdef == NULL and errno == EINTR);
 
232
        if(new_envdef == NULL){
224
233
          return false;
225
234
        }
226
 
        *e = new;
227
 
        strcpy(*e, def);
 
235
        *envdef = new_envdef;
 
236
        strcpy(*envdef, def);
228
237
      }
229
238
      return true;
230
239
    }
232
241
  return add_to_char_array(def, &(p->environ), &(p->envc));
233
242
}
234
243
 
 
244
#ifndef O_CLOEXEC
235
245
/*
236
246
 * Based on the example in the GNU LibC manual chapter 13.13 "File
237
247
 * Descriptor Flags".
238
248
 | [[info:libc:Descriptor%20Flags][File Descriptor Flags]] |
239
249
 */
 
250
__attribute__((warn_unused_result))
240
251
static int set_cloexec_flag(int fd){
241
252
  int ret = (int)TEMP_FAILURE_RETRY(fcntl(fd, F_GETFD, 0));
242
253
  /* If reading the flags failed, return error indication now. */
247
258
  return (int)TEMP_FAILURE_RETRY(fcntl(fd, F_SETFD,
248
259
                                       ret | FD_CLOEXEC));
249
260
}
 
261
#endif  /* not O_CLOEXEC */
250
262
 
251
263
 
252
264
/* Mark processes as completed when they exit, and save their exit
266
278
        /* No child processes */
267
279
        break;
268
280
      }
269
 
      perror("waitpid");
 
281
      error(0, errno, "waitpid");
270
282
    }
271
283
    
272
284
    /* A child exited, find it in process_list */
284
296
}
285
297
 
286
298
/* Prints out a password to stdout */
 
299
__attribute__((nonnull, warn_unused_result))
287
300
static bool print_out_password(const char *buffer, size_t length){
288
301
  ssize_t ret;
289
302
  for(size_t written = 0; written < length; written += (size_t)ret){
297
310
}
298
311
 
299
312
/* Removes and free a plugin from the plugin list */
 
313
__attribute__((nonnull))
300
314
static void free_plugin(plugin *plugin_node){
301
315
  
302
 
  for(char **arg = plugin_node->argv; *arg != NULL; arg++){
 
316
  for(char **arg = (plugin_node->argv)+1; *arg != NULL; arg++){
303
317
    free(*arg);
304
318
  }
 
319
  free(plugin_node->name);
305
320
  free(plugin_node->argv);
306
321
  for(char **env = plugin_node->environ; *env != NULL; env++){
307
322
    free(*env);
334
349
 
335
350
int main(int argc, char *argv[]){
336
351
  char *plugindir = NULL;
 
352
  char *pluginhelperdir = NULL;
337
353
  char *argfile = NULL;
338
354
  FILE *conffp;
339
 
  size_t d_name_len;
340
 
  DIR *dir = NULL;
341
 
  struct dirent *dirst;
 
355
  struct dirent **direntries = NULL;
342
356
  struct stat st;
343
357
  fd_set rfds_all;
344
358
  int ret, maxfd = 0;
352
366
                                      .sa_flags = SA_NOCLDSTOP };
353
367
  char **custom_argv = NULL;
354
368
  int custom_argc = 0;
 
369
  int dir_fd = -1;
355
370
  
356
371
  /* Establish a signal handler */
357
372
  sigemptyset(&sigchld_action.sa_mask);
358
373
  ret = sigaddset(&sigchld_action.sa_mask, SIGCHLD);
359
374
  if(ret == -1){
360
 
    perror("sigaddset");
 
375
    error(0, errno, "sigaddset");
361
376
    exitstatus = EX_OSERR;
362
377
    goto fallback;
363
378
  }
364
379
  ret = sigaction(SIGCHLD, &sigchld_action, &old_sigchld_action);
365
380
  if(ret == -1){
366
 
    perror("sigaction");
 
381
    error(0, errno, "sigaction");
367
382
    exitstatus = EX_OSERR;
368
383
    goto fallback;
369
384
  }
402
417
      .doc = "Group ID the plugins will run as", .group = 3 },
403
418
    { .name = "debug", .key = 132,
404
419
      .doc = "Debug mode", .group = 4 },
 
420
    { .name = "plugin-helper-dir", .key = 133,
 
421
      .arg = "DIRECTORY",
 
422
      .doc = "Specify a different plugin helper directory",
 
423
      .group = 2 },
405
424
    /*
406
425
     * These reproduce what we would get without ARGP_NO_HELP
407
426
     */
414
433
    { .name = NULL }
415
434
  };
416
435
  
 
436
  __attribute__((nonnull(3)))
417
437
  error_t parse_opt(int key, char *arg, struct argp_state *state){
418
438
    errno = 0;
419
439
    switch(key){
420
440
      char *tmp;
421
 
      intmax_t tmpmax;
 
441
      intmax_t tmp_id;
422
442
    case 'g':                   /* --global-options */
423
443
      {
424
444
        char *plugin_option;
427
447
            break;
428
448
          }
429
449
        }
 
450
        errno = 0;
430
451
      }
431
452
      break;
432
453
    case 'G':                   /* --global-env */
433
 
      add_environment(getplugin(NULL), arg, true);
 
454
      if(add_environment(getplugin(NULL), arg, true)){
 
455
        errno = 0;
 
456
      }
434
457
      break;
435
458
    case 'o':                   /* --options-for */
436
459
      {
453
476
            break;
454
477
          }
455
478
        }
 
479
        errno = 0;
456
480
      }
457
481
      break;
458
482
    case 'E':                   /* --env-for */
470
494
          errno = EINVAL;
471
495
          break;
472
496
        }
473
 
        add_environment(getplugin(arg), envdef, true);
 
497
        if(add_environment(getplugin(arg), envdef, true)){
 
498
          errno = 0;
 
499
        }
474
500
      }
475
501
      break;
476
502
    case 'd':                   /* --disable */
478
504
        plugin *p = getplugin(arg);
479
505
        if(p != NULL){
480
506
          p->disabled = true;
 
507
          errno = 0;
481
508
        }
482
509
      }
483
510
      break;
486
513
        plugin *p = getplugin(arg);
487
514
        if(p != NULL){
488
515
          p->disabled = false;
 
516
          errno = 0;
489
517
        }
490
518
      }
491
519
      break;
492
520
    case 128:                   /* --plugin-dir */
493
521
      free(plugindir);
494
522
      plugindir = strdup(arg);
 
523
      if(plugindir != NULL){
 
524
        errno = 0;
 
525
      }
495
526
      break;
496
527
    case 129:                   /* --config-file */
497
528
      /* This is already done by parse_opt_config_file() */
498
529
      break;
499
530
    case 130:                   /* --userid */
500
 
      tmpmax = strtoimax(arg, &tmp, 10);
 
531
      tmp_id = strtoimax(arg, &tmp, 10);
501
532
      if(errno != 0 or tmp == arg or *tmp != '\0'
502
 
         or tmpmax != (uid_t)tmpmax){
 
533
         or tmp_id != (uid_t)tmp_id){
503
534
        argp_error(state, "Bad user ID number: \"%s\", using %"
504
535
                   PRIdMAX, arg, (intmax_t)uid);
505
536
        break;
506
537
      }
507
 
      uid = (uid_t)tmpmax;
 
538
      uid = (uid_t)tmp_id;
 
539
      errno = 0;
508
540
      break;
509
541
    case 131:                   /* --groupid */
510
 
      tmpmax = strtoimax(arg, &tmp, 10);
 
542
      tmp_id = strtoimax(arg, &tmp, 10);
511
543
      if(errno != 0 or tmp == arg or *tmp != '\0'
512
 
         or tmpmax != (gid_t)tmpmax){
 
544
         or tmp_id != (gid_t)tmp_id){
513
545
        argp_error(state, "Bad group ID number: \"%s\", using %"
514
546
                   PRIdMAX, arg, (intmax_t)gid);
515
547
        break;
516
548
      }
517
 
      gid = (gid_t)tmpmax;
 
549
      gid = (gid_t)tmp_id;
 
550
      errno = 0;
518
551
      break;
519
552
    case 132:                   /* --debug */
520
553
      debug = true;
521
554
      break;
 
555
    case 133:                   /* --plugin-helper-dir */
 
556
      free(pluginhelperdir);
 
557
      pluginhelperdir = strdup(arg);
 
558
      if(pluginhelperdir != NULL){
 
559
        errno = 0;
 
560
      }
 
561
      break;
522
562
      /*
523
563
       * These reproduce what we would get without ARGP_NO_HELP
524
564
       */
525
565
    case '?':                   /* --help */
526
566
      state->flags &= ~(unsigned int)ARGP_NO_EXIT; /* force exit */
527
567
      argp_state_help(state, state->out_stream, ARGP_HELP_STD_HELP);
 
568
      __builtin_unreachable();
528
569
    case -3:                    /* --usage */
529
570
      state->flags &= ~(unsigned int)ARGP_NO_EXIT; /* force exit */
530
571
      argp_state_help(state, state->out_stream,
531
572
                      ARGP_HELP_USAGE | ARGP_HELP_EXIT_OK);
 
573
      __builtin_unreachable();
532
574
    case 'V':                   /* --version */
533
575
      fprintf(state->out_stream, "%s\n", argp_program_version);
534
576
      exit(EXIT_SUCCESS);
544
586
      if(arg[0] == '\0'){
545
587
        break;
546
588
      }
 
589
#if __GNUC__ >= 7
 
590
      __attribute__((fallthrough));
 
591
#else
 
592
          /* FALLTHROUGH */
 
593
#endif
547
594
    default:
548
595
      return ARGP_ERR_UNKNOWN;
549
596
    }
568
615
    case 129:                   /* --config-file */
569
616
      free(argfile);
570
617
      argfile = strdup(arg);
 
618
      if(argfile != NULL){
 
619
        errno = 0;
 
620
      }
571
621
      break;
572
622
    case 130:                   /* --userid */
573
623
    case 131:                   /* --groupid */
574
624
    case 132:                   /* --debug */
 
625
    case 133:                   /* --plugin-helper-dir */
575
626
    case '?':                   /* --help */
576
627
    case -3:                    /* --usage */
577
628
    case 'V':                   /* --version */
599
650
  case ENOMEM:
600
651
  default:
601
652
    errno = ret;
602
 
    perror("argp_parse");
 
653
    error(0, errno, "argp_parse");
603
654
    exitstatus = EX_OSERR;
604
655
    goto fallback;
605
656
  case EINVAL:
626
677
    custom_argc = 1;
627
678
    custom_argv = malloc(sizeof(char*) * 2);
628
679
    if(custom_argv == NULL){
629
 
      perror("malloc");
 
680
      error(0, errno, "malloc");
630
681
      exitstatus = EX_OSERR;
631
682
      goto fallback;
632
683
    }
649
700
        }
650
701
        new_arg = strdup(p);
651
702
        if(new_arg == NULL){
652
 
          perror("strdup");
 
703
          error(0, errno, "strdup");
653
704
          exitstatus = EX_OSERR;
654
705
          free(org_line);
655
706
          goto fallback;
656
707
        }
657
708
        
658
709
        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;
 
710
        {
 
711
          char **new_argv = realloc(custom_argv, sizeof(char *)
 
712
                                    * ((size_t)custom_argc + 1));
 
713
          if(new_argv == NULL){
 
714
            error(0, errno, "realloc");
 
715
            exitstatus = EX_OSERR;
 
716
            free(new_arg);
 
717
            free(org_line);
 
718
            goto fallback;
 
719
          } else {
 
720
            custom_argv = new_argv;
 
721
          }
666
722
        }
667
723
        custom_argv[custom_argc-1] = new_arg;
668
724
        custom_argv[custom_argc] = NULL;
672
728
      ret = fclose(conffp);
673
729
    } while(ret == EOF and errno == EINTR);
674
730
    if(ret == EOF){
675
 
      perror("fclose");
 
731
      error(0, errno, "fclose");
676
732
      exitstatus = EX_IOERR;
677
733
      goto fallback;
678
734
    }
681
737
    /* Check for harmful errors and go to fallback. Other errors might
682
738
       not affect opening plugins */
683
739
    if(errno == EMFILE or errno == ENFILE or errno == ENOMEM){
684
 
      perror("fopen");
 
740
      error(0, errno, "fopen");
685
741
      exitstatus = EX_OSERR;
686
742
      goto fallback;
687
743
    }
698
754
    case ENOMEM:
699
755
    default:
700
756
      errno = ret;
701
 
      perror("argp_parse");
 
757
      error(0, errno, "argp_parse");
702
758
      exitstatus = EX_OSERR;
703
759
      goto fallback;
704
760
    case EINVAL:
718
774
  case ENOMEM:
719
775
  default:
720
776
    errno = ret;
721
 
    perror("argp_parse");
 
777
    error(0, errno, "argp_parse");
722
778
    exitstatus = EX_OSERR;
723
779
    goto fallback;
724
780
  case EINVAL:
726
782
    goto fallback;
727
783
  }
728
784
  
 
785
  {
 
786
    char *pluginhelperenv;
 
787
    bool bret = true;
 
788
    ret = asprintf(&pluginhelperenv, "MANDOSPLUGINHELPERDIR=%s",
 
789
                   pluginhelperdir != NULL ? pluginhelperdir : PHDIR);
 
790
    if(ret != -1){
 
791
      bret = add_environment(getplugin(NULL), pluginhelperenv, true);
 
792
    }
 
793
    if(ret == -1 or not bret){
 
794
      error(0, errno, "Failed to set MANDOSPLUGINHELPERDIR"
 
795
            " environment variable to \"%s\" for all plugins\n",
 
796
            pluginhelperdir != NULL ? pluginhelperdir : PHDIR);
 
797
    }
 
798
    if(ret != -1){
 
799
      free(pluginhelperenv);
 
800
    }
 
801
  }
 
802
  
729
803
  if(debug){
730
 
    for(plugin *p = plugin_list; p != NULL; p=p->next){
 
804
    for(plugin *p = plugin_list; p != NULL; p = p->next){
731
805
      fprintf(stderr, "Plugin: %s has %d arguments\n",
732
806
              p->name ? p->name : "Global", p->argc - 1);
733
807
      for(char **a = p->argv; *a != NULL; a++){
740
814
    }
741
815
  }
742
816
  
743
 
  /* Strip permissions down to nobody */
744
 
  setgid(gid);
 
817
  if(getuid() == 0){
 
818
    /* Work around Debian bug #633582:
 
819
       <https://bugs.debian.org/633582> */
 
820
    int plugindir_fd = open(/* plugindir or */ PDIR, O_RDONLY);
 
821
    if(plugindir_fd == -1){
 
822
      if(errno != ENOENT){
 
823
        error(0, errno, "open(\"" PDIR "\")");
 
824
      }
 
825
    } else {
 
826
      ret = (int)TEMP_FAILURE_RETRY(fstat(plugindir_fd, &st));
 
827
      if(ret == -1){
 
828
        error(0, errno, "fstat");
 
829
      } else {
 
830
        if(S_ISDIR(st.st_mode) and st.st_uid == 0 and st.st_gid == 0){
 
831
          ret = fchown(plugindir_fd, uid, gid);
 
832
          if(ret == -1){
 
833
            error(0, errno, "fchown");
 
834
          }
 
835
        }
 
836
      }
 
837
      close(plugindir_fd);
 
838
    }
 
839
  }
 
840
  
 
841
  /* Lower permissions */
 
842
  ret = setgid(gid);
745
843
  if(ret == -1){
746
 
    perror("setgid");
 
844
    error(0, errno, "setgid");
747
845
  }
748
846
  ret = setuid(uid);
749
847
  if(ret == -1){
750
 
    perror("setuid");
 
848
    error(0, errno, "setuid");
751
849
  }
752
850
  
753
851
  /* Open plugin directory with close_on_exec flag */
754
852
  {
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
 
    }
 
853
    dir_fd = open(plugindir != NULL ? plugindir : PDIR, O_RDONLY |
 
854
#ifdef O_CLOEXEC
 
855
                  O_CLOEXEC
 
856
#else  /* not O_CLOEXEC */
 
857
                  0
 
858
#endif  /* not O_CLOEXEC */
 
859
                  );
773
860
    if(dir_fd == -1){
774
 
      perror("Could not open plugin dir");
 
861
      error(0, errno, "Could not open plugin dir");
775
862
      exitstatus = EX_UNAVAILABLE;
776
863
      goto fallback;
777
864
    }
780
867
  /* Set the FD_CLOEXEC flag on the directory */
781
868
    ret = set_cloexec_flag(dir_fd);
782
869
    if(ret < 0){
783
 
      perror("set_cloexec_flag");
784
 
      TEMP_FAILURE_RETRY(close(dir_fd));
 
870
      error(0, errno, "set_cloexec_flag");
785
871
      exitstatus = EX_OSERR;
786
872
      goto fallback;
787
873
    }
788
874
#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;
 
875
  }
 
876
  
 
877
  int good_name(const struct dirent * const dirent){
 
878
    const char * const patterns[] = { ".*", "#*#", "*~", "*.dpkg-new",
 
879
                                      "*.dpkg-old", "*.dpkg-bak",
 
880
                                      "*.dpkg-divert", NULL };
 
881
#ifdef __GNUC__
 
882
#pragma GCC diagnostic push
 
883
#pragma GCC diagnostic ignored "-Wcast-qual"
 
884
#endif
 
885
    for(const char **pat = (const char **)patterns;
 
886
        *pat != NULL; pat++){
 
887
#ifdef __GNUC__
 
888
#pragma GCC diagnostic pop
 
889
#endif
 
890
      if(fnmatch(*pat, dirent->d_name, FNM_FILE_NAME | FNM_PERIOD)
 
891
         != FNM_NOMATCH){
 
892
        if(debug){
 
893
            fprintf(stderr, "Ignoring plugin dir entry \"%s\""
 
894
                    " matching pattern %s\n", dirent->d_name, *pat);
 
895
        }
 
896
        return 0;
 
897
      }
796
898
    }
 
899
    return 1;
 
900
  }
 
901
  
 
902
  int numplugins = scandirat(dir_fd, ".", &direntries, good_name,
 
903
                             alphasort);
 
904
  if(numplugins == -1){
 
905
    error(0, errno, "Could not scan plugin dir");
 
906
    direntries = NULL;
 
907
    exitstatus = EX_OSERR;
 
908
    goto fallback;
797
909
  }
798
910
  
799
911
  FD_ZERO(&rfds_all);
800
912
  
801
913
  /* 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");
 
914
  for(int i = 0; i < numplugins; i++){
 
915
    
 
916
    int plugin_fd = openat(dir_fd, direntries[i]->d_name, O_RDONLY);
 
917
    if(plugin_fd == -1){
 
918
      error(0, errno, "Could not open plugin");
 
919
      free(direntries[i]);
874
920
      continue;
875
921
    }
876
 
    
877
 
    ret = (int)TEMP_FAILURE_RETRY(stat(filename, &st));
 
922
    ret = (int)TEMP_FAILURE_RETRY(fstat(plugin_fd, &st));
878
923
    if(ret == -1){
879
 
      perror("stat");
880
 
      free(filename);
 
924
      error(0, errno, "stat");
 
925
      close(plugin_fd);
 
926
      free(direntries[i]);
881
927
      continue;
882
928
    }
883
929
    
884
930
    /* Ignore non-executable files */
885
931
    if(not S_ISREG(st.st_mode)
886
 
       or (TEMP_FAILURE_RETRY(access(filename, X_OK)) != 0)){
 
932
       or (TEMP_FAILURE_RETRY(faccessat(dir_fd, direntries[i]->d_name,
 
933
                                        X_OK, 0)) != 0)){
887
934
      if(debug){
888
 
        fprintf(stderr, "Ignoring plugin dir entry \"%s\""
889
 
                " with bad type or mode\n", filename);
 
935
        fprintf(stderr, "Ignoring plugin dir entry \"%s/%s\""
 
936
                " with bad type or mode\n",
 
937
                plugindir != NULL ? plugindir : PDIR,
 
938
                direntries[i]->d_name);
890
939
      }
891
 
      free(filename);
 
940
      close(plugin_fd);
 
941
      free(direntries[i]);
892
942
      continue;
893
943
    }
894
944
    
895
 
    plugin *p = getplugin(dirst->d_name);
 
945
    plugin *p = getplugin(direntries[i]->d_name);
896
946
    if(p == NULL){
897
 
      perror("getplugin");
898
 
      free(filename);
 
947
      error(0, errno, "getplugin");
 
948
      close(plugin_fd);
 
949
      free(direntries[i]);
899
950
      continue;
900
951
    }
901
952
    if(p->disabled){
902
953
      if(debug){
903
954
        fprintf(stderr, "Ignoring disabled plugin \"%s\"\n",
904
 
                dirst->d_name);
 
955
                direntries[i]->d_name);
905
956
      }
906
 
      free(filename);
 
957
      close(plugin_fd);
 
958
      free(direntries[i]);
907
959
      continue;
908
960
    }
909
961
    {
912
964
      if(g != NULL){
913
965
        for(char **a = g->argv + 1; *a != NULL; a++){
914
966
          if(not add_argument(p, *a)){
915
 
            perror("add_argument");
 
967
            error(0, errno, "add_argument");
916
968
          }
917
969
        }
918
970
        /* Add global environment variables */
919
971
        for(char **e = g->environ; *e != NULL; e++){
920
972
          if(not add_environment(p, *e, false)){
921
 
            perror("add_environment");
 
973
            error(0, errno, "add_environment");
922
974
          }
923
975
        }
924
976
      }
925
977
    }
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. */
 
978
    /* If this plugin has any environment variables, we need to
 
979
       duplicate the environment from this process, too. */
929
980
    if(p->environ[0] != NULL){
930
981
      for(char **e = environ; *e != NULL; e++){
931
982
        if(not add_environment(p, *e, false)){
932
 
          perror("add_environment");
 
983
          error(0, errno, "add_environment");
933
984
        }
934
985
      }
935
986
    }
936
987
    
937
988
    int pipefd[2];
 
989
#ifndef O_CLOEXEC
938
990
    ret = (int)TEMP_FAILURE_RETRY(pipe(pipefd));
 
991
#else  /* O_CLOEXEC */
 
992
    ret = (int)TEMP_FAILURE_RETRY(pipe2(pipefd, O_CLOEXEC));
 
993
#endif  /* O_CLOEXEC */
939
994
    if(ret == -1){
940
 
      perror("pipe");
941
 
      exitstatus = EX_OSERR;
942
 
      goto fallback;
943
 
    }
 
995
      error(0, errno, "pipe");
 
996
      exitstatus = EX_OSERR;
 
997
      free(direntries[i]);
 
998
      goto fallback;
 
999
    }
 
1000
    if(pipefd[0] >= FD_SETSIZE){
 
1001
      fprintf(stderr, "pipe()[0] (%d) >= FD_SETSIZE (%d)", pipefd[0],
 
1002
              FD_SETSIZE);
 
1003
      close(pipefd[0]);
 
1004
      close(pipefd[1]);
 
1005
      exitstatus = EX_OSERR;
 
1006
      free(direntries[i]);
 
1007
      goto fallback;
 
1008
    }
 
1009
#ifndef O_CLOEXEC
944
1010
    /* Ask OS to automatic close the pipe on exec */
945
1011
    ret = set_cloexec_flag(pipefd[0]);
946
1012
    if(ret < 0){
947
 
      perror("set_cloexec_flag");
 
1013
      error(0, errno, "set_cloexec_flag");
 
1014
      close(pipefd[0]);
 
1015
      close(pipefd[1]);
948
1016
      exitstatus = EX_OSERR;
 
1017
      free(direntries[i]);
949
1018
      goto fallback;
950
1019
    }
951
1020
    ret = set_cloexec_flag(pipefd[1]);
952
1021
    if(ret < 0){
953
 
      perror("set_cloexec_flag");
 
1022
      error(0, errno, "set_cloexec_flag");
 
1023
      close(pipefd[0]);
 
1024
      close(pipefd[1]);
954
1025
      exitstatus = EX_OSERR;
 
1026
      free(direntries[i]);
955
1027
      goto fallback;
956
1028
    }
 
1029
#endif  /* not O_CLOEXEC */
957
1030
    /* Block SIGCHLD until process is safely in process list */
958
1031
    ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_BLOCK,
959
1032
                                              &sigchld_action.sa_mask,
960
1033
                                              NULL));
961
1034
    if(ret < 0){
962
 
      perror("sigprocmask");
 
1035
      error(0, errno, "sigprocmask");
963
1036
      exitstatus = EX_OSERR;
 
1037
      free(direntries[i]);
964
1038
      goto fallback;
965
1039
    }
966
1040
    /* Starting a new process to be watched */
969
1043
      pid = fork();
970
1044
    } while(pid == -1 and errno == EINTR);
971
1045
    if(pid == -1){
972
 
      perror("fork");
 
1046
      error(0, errno, "fork");
 
1047
      TEMP_FAILURE_RETRY(sigprocmask(SIG_UNBLOCK,
 
1048
                                     &sigchld_action.sa_mask, NULL));
 
1049
      close(pipefd[0]);
 
1050
      close(pipefd[1]);
973
1051
      exitstatus = EX_OSERR;
 
1052
      free(direntries[i]);
974
1053
      goto fallback;
975
1054
    }
976
1055
    if(pid == 0){
977
1056
      /* this is the child process */
978
1057
      ret = sigaction(SIGCHLD, &old_sigchld_action, NULL);
979
1058
      if(ret < 0){
980
 
        perror("sigaction");
 
1059
        error(0, errno, "sigaction");
981
1060
        _exit(EX_OSERR);
982
1061
      }
983
1062
      ret = sigprocmask(SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
984
1063
      if(ret < 0){
985
 
        perror("sigprocmask");
 
1064
        error(0, errno, "sigprocmask");
986
1065
        _exit(EX_OSERR);
987
1066
      }
988
1067
      
989
1068
      ret = dup2(pipefd[1], STDOUT_FILENO); /* replace our stdout */
990
1069
      if(ret == -1){
991
 
        perror("dup2");
 
1070
        error(0, errno, "dup2");
992
1071
        _exit(EX_OSERR);
993
1072
      }
994
1073
      
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
 
        }
 
1074
      if(fexecve(plugin_fd, p->argv,
 
1075
                (p->environ[0] != NULL) ? p->environ : environ) < 0){
 
1076
        error(0, errno, "fexecve for %s/%s",
 
1077
              plugindir != NULL ? plugindir : PDIR,
 
1078
              direntries[i]->d_name);
 
1079
        _exit(EX_OSERR);
1010
1080
      }
1011
1081
      /* no return */
1012
1082
    }
1013
1083
    /* Parent process */
1014
 
    TEMP_FAILURE_RETRY(close(pipefd[1])); /* Close unused write end of
1015
 
                                             pipe */
1016
 
    free(filename);
1017
 
    plugin *new_plugin = getplugin(dirst->d_name);
 
1084
    close(pipefd[1]);           /* Close unused write end of pipe */
 
1085
    close(plugin_fd);
 
1086
    plugin *new_plugin = getplugin(direntries[i]->d_name);
1018
1087
    if(new_plugin == NULL){
1019
 
      perror("getplugin");
 
1088
      error(0, errno, "getplugin");
1020
1089
      ret = (int)(TEMP_FAILURE_RETRY
1021
1090
                  (sigprocmask(SIG_UNBLOCK, &sigchld_action.sa_mask,
1022
1091
                               NULL)));
1023
1092
      if(ret < 0){
1024
 
        perror("sigprocmask");
 
1093
        error(0, errno, "sigprocmask");
1025
1094
      }
1026
1095
      exitstatus = EX_OSERR;
 
1096
      free(direntries[i]);
1027
1097
      goto fallback;
1028
1098
    }
 
1099
    free(direntries[i]);
1029
1100
    
1030
1101
    new_plugin->pid = pid;
1031
1102
    new_plugin->fd = pipefd[0];
1032
 
    
 
1103
 
 
1104
    if(debug){
 
1105
      fprintf(stderr, "Plugin %s started (PID %" PRIdMAX ")\n",
 
1106
              new_plugin->name, (intmax_t) (new_plugin->pid));
 
1107
    }
 
1108
 
1033
1109
    /* Unblock SIGCHLD so signal handler can be run if this process
1034
1110
       has already completed */
1035
1111
    ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_UNBLOCK,
1036
1112
                                              &sigchld_action.sa_mask,
1037
1113
                                              NULL));
1038
1114
    if(ret < 0){
1039
 
      perror("sigprocmask");
 
1115
      error(0, errno, "sigprocmask");
1040
1116
      exitstatus = EX_OSERR;
1041
1117
      goto fallback;
1042
1118
    }
1043
1119
    
1044
 
    FD_SET(new_plugin->fd, &rfds_all); /* Spurious warning from
1045
 
                                          -Wconversion */
 
1120
    FD_SET(new_plugin->fd, &rfds_all);
1046
1121
    
1047
1122
    if(maxfd < new_plugin->fd){
1048
1123
      maxfd = new_plugin->fd;
1049
1124
    }
1050
1125
  }
1051
1126
  
1052
 
  TEMP_FAILURE_RETRY(closedir(dir));
1053
 
  dir = NULL;
 
1127
  free(direntries);
 
1128
  direntries = NULL;
 
1129
  close(dir_fd);
 
1130
  dir_fd = -1;
1054
1131
  free_plugin(getplugin(NULL));
1055
1132
  
1056
1133
  for(plugin *p = plugin_list; p != NULL; p = p->next){
1069
1146
    fd_set rfds = rfds_all;
1070
1147
    int select_ret = select(maxfd+1, &rfds, NULL, NULL, NULL);
1071
1148
    if(select_ret == -1 and errno != EINTR){
1072
 
      perror("select");
 
1149
      error(0, errno, "select");
1073
1150
      exitstatus = EX_OSERR;
1074
1151
      goto fallback;
1075
1152
    }
1095
1172
                      (intmax_t) (proc->pid),
1096
1173
                      WTERMSIG(proc->status),
1097
1174
                      strsignal(WTERMSIG(proc->status)));
1098
 
            } else if(WCOREDUMP(proc->status)){
1099
 
              fprintf(stderr, "Plugin %s [%" PRIdMAX "] dumped"
1100
 
                      " core\n", proc->name, (intmax_t) (proc->pid));
1101
1175
            }
1102
1176
          }
1103
1177
          
1104
1178
          /* Remove the plugin */
1105
 
          FD_CLR(proc->fd, &rfds_all); /* Spurious warning from
1106
 
                                          -Wconversion */
 
1179
          FD_CLR(proc->fd, &rfds_all);
1107
1180
          
1108
1181
          /* Block signal while modifying process_list */
1109
1182
          ret = (int)TEMP_FAILURE_RETRY(sigprocmask
1111
1184
                                         &sigchld_action.sa_mask,
1112
1185
                                         NULL));
1113
1186
          if(ret < 0){
1114
 
            perror("sigprocmask");
 
1187
            error(0, errno, "sigprocmask");
1115
1188
            exitstatus = EX_OSERR;
1116
1189
            goto fallback;
1117
1190
          }
1125
1198
                      (sigprocmask(SIG_UNBLOCK,
1126
1199
                                   &sigchld_action.sa_mask, NULL)));
1127
1200
          if(ret < 0){
1128
 
            perror("sigprocmask");
 
1201
            error(0, errno, "sigprocmask");
1129
1202
            exitstatus = EX_OSERR;
1130
1203
            goto fallback;
1131
1204
          }
1142
1215
        bool bret = print_out_password(proc->buffer,
1143
1216
                                       proc->buffer_length);
1144
1217
        if(not bret){
1145
 
          perror("print_out_password");
 
1218
          error(0, errno, "print_out_password");
1146
1219
          exitstatus = EX_IOERR;
1147
1220
        }
1148
1221
        goto fallback;
1149
1222
      }
1150
1223
      
1151
1224
      /* This process has not completed.  Does it have any output? */
1152
 
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){ /* Spurious
1153
 
                                                         warning from
1154
 
                                                         -Wconversion */
 
1225
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){
1155
1226
        /* This process had nothing to say at this time */
1156
1227
        proc = proc->next;
1157
1228
        continue;
1158
1229
      }
1159
1230
      /* Before reading, make the process' data buffer large enough */
1160
1231
      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");
 
1232
        char *new_buffer = realloc(proc->buffer, proc->buffer_size
 
1233
                                   + (size_t) BUFFER_SIZE);
 
1234
        if(new_buffer == NULL){
 
1235
          error(0, errno, "malloc");
1165
1236
          exitstatus = EX_OSERR;
1166
1237
          goto fallback;
1167
1238
        }
 
1239
        proc->buffer = new_buffer;
1168
1240
        proc->buffer_size += BUFFER_SIZE;
1169
1241
      }
1170
1242
      /* Read from the process */
1204
1276
    }
1205
1277
    bret = print_out_password(passwordbuffer, len);
1206
1278
    if(not bret){
1207
 
      perror("print_out_password");
 
1279
      error(0, errno, "print_out_password");
1208
1280
      exitstatus = EX_IOERR;
1209
1281
    }
1210
1282
  }
1212
1284
  /* Restore old signal handler */
1213
1285
  ret = sigaction(SIGCHLD, &old_sigchld_action, NULL);
1214
1286
  if(ret == -1){
1215
 
    perror("sigaction");
 
1287
    error(0, errno, "sigaction");
1216
1288
    exitstatus = EX_OSERR;
1217
1289
  }
1218
1290
  
1223
1295
    free(custom_argv);
1224
1296
  }
1225
1297
  
1226
 
  if(dir != NULL){
1227
 
    closedir(dir);
 
1298
  free(direntries);
 
1299
  
 
1300
  if(dir_fd != -1){
 
1301
    close(dir_fd);
1228
1302
  }
1229
1303
  
1230
1304
  /* Kill the processes */
1234
1308
      ret = kill(p->pid, SIGTERM);
1235
1309
      if(ret == -1 and errno != ESRCH){
1236
1310
        /* Set-uid proccesses might not get closed */
1237
 
        perror("kill");
 
1311
        error(0, errno, "kill");
1238
1312
      }
1239
1313
    }
1240
1314
  }
1244
1318
    ret = wait(NULL);
1245
1319
  } while(ret >= 0);
1246
1320
  if(errno != ECHILD){
1247
 
    perror("wait");
 
1321
    error(0, errno, "wait");
1248
1322
  }
1249
1323
  
1250
1324
  free_plugin_list();
1251
1325
  
1252
1326
  free(plugindir);
 
1327
  free(pluginhelperdir);
1253
1328
  free(argfile);
1254
1329
  
1255
1330
  return exitstatus;