/mandos/release

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

« back to all changes in this revision

Viewing changes to plugin-runner.c

  • Committer: Teddy Hogeborn
  • Date: 2016-03-17 20:40:55 UTC
  • mto: (237.7.594 trunk)
  • mto: This revision was merged to the branch mainline in revision 341.
  • Revision ID: teddy@recompile.se-20160317204055-bhsh5xsidq7w5cxu
Client: Fix plymouth agent; broken since 1.7.2.

Fix an very old memory bug in the plymouth agent (which has been
present since its apperance in version 1.2), but which was only
recently detected at run time due to the new -fsanitize=address
compile- time flag, which has been used since version 1.7.2.  This
detection of a memory access violation causes the program to abort,
making the Plymouth graphical boot system unable to accept interactive
input of passwords when using the Mandos client.

* plugins.d/plymouth.c (exec_and_wait): Fix memory allocation bug when
  allocating new_argv.  Also tolerate a zero-length argv.

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