/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: 2014-07-25 22:44:20 UTC
  • mto: This revision was merged to the branch mainline in revision 724.
  • Revision ID: teddy@recompile.se-20140725224420-4a5ct2ptt0hsc92z
Require Python 2.7.

This is in preparation for the eventual move to Python 3, which will
happen as soon as all Python modules required by Mandos are available.
The mandos-ctl and mandos-monitor programs are already portable
between Python 2.6 and Python 3 without changes; this change will
bring the requirement up to Python 2.7.

* INSTALL (Prerequisites/Libraries/Mandos Server): Document
                                                   requirement of
                                                   Python 2.7; remove
                                                   Python-argparse
                                                   which is in the
                                                   Python 2.7 standard
                                                   library.
* debian/control (Source: mandos/Build-Depends-Indep): Depend on
                                                       exactly the
                                                       python2.7
                                                       package and all
                                                       the Python 2.7
                                                       versions of the
                                                       python modules.
  (Package: mandos/Depends): - '' - but still depend on python (<=2.7)
                            and the generic versions of the Python
                            modules; this is for mandos-ctl and
                            mandos-monitor, both of which are
                            compatible with Python 3, and use
                            #!/usr/bin/python.
* mandos: Use #!/usr/bin/python2.7 instead of #!/usr/bin/python.

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
 
#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
 
76
79
#define AFILE "/conf/conf.d/mandos/plugin-runner.conf"
77
80
 
78
81
const char *argp_program_version = "plugin-runner " VERSION;
79
 
const char *argp_program_bug_address = "<mandos@fukt.bsnet.se>";
 
82
const char *argp_program_bug_address = "<mandos@recompile.se>";
80
83
 
81
84
typedef struct plugin{
82
85
  char *name;                   /* can be NULL or any plugin name */
102
105
 
103
106
/* Gets an existing plugin based on name,
104
107
   or if none is found, creates a new one */
 
108
__attribute__((warn_unused_result))
105
109
static plugin *getplugin(char *name){
106
110
  /* Check for existing plugin with that name */
107
111
  for(plugin *p = plugin_list; p != NULL; p = p->next){
168
172
}
169
173
 
170
174
/* Helper function for add_argument and add_environment */
 
175
__attribute__((nonnull, warn_unused_result))
171
176
static bool add_to_char_array(const char *new, char ***array,
172
177
                              int *len){
173
178
  /* Resize the pointed-to array to hold one more pointer */
 
179
  char **new_array = NULL;
174
180
  do {
175
 
    *array = realloc(*array, sizeof(char *)
176
 
                     * (size_t) ((*len) + 2));
177
 
  } 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);
178
184
  /* Malloc check */
179
 
  if(*array == NULL){
 
185
  if(new_array == NULL){
180
186
    return false;
181
187
  }
 
188
  *array = new_array;
182
189
  /* Make a copy of the new string */
183
190
  char *copy;
184
191
  do {
196
203
}
197
204
 
198
205
/* Add to a plugin's argument vector */
 
206
__attribute__((nonnull(2), warn_unused_result))
199
207
static bool add_argument(plugin *p, const char *arg){
200
208
  if(p == NULL){
201
209
    return false;
204
212
}
205
213
 
206
214
/* Add to a plugin's environment */
 
215
__attribute__((nonnull(2), warn_unused_result))
207
216
static bool add_environment(plugin *p, const char *def, bool replace){
208
217
  if(p == NULL){
209
218
    return false;
211
220
  /* namelen = length of name of environment variable */
212
221
  size_t namelen = (size_t)(strchrnul(def, '=') - def);
213
222
  /* Search for this environment variable */
214
 
  for(char **e = p->environ; *e != NULL; e++){
215
 
    if(strncmp(*e, def, namelen + 1) == 0){
 
223
  for(char **envdef = p->environ; *envdef != NULL; envdef++){
 
224
    if(strncmp(*envdef, def, namelen + 1) == 0){
216
225
      /* It already exists */
217
226
      if(replace){
218
 
        char *new;
 
227
        char *new_envdef;
219
228
        do {
220
 
          new = realloc(*e, strlen(def) + 1);
221
 
        } while(new == NULL and errno == EINTR);
222
 
        if(new == NULL){
 
229
          new_envdef = realloc(*envdef, strlen(def) + 1);
 
230
        } while(new_envdef == NULL and errno == EINTR);
 
231
        if(new_envdef == NULL){
223
232
          return false;
224
233
        }
225
 
        *e = new;
226
 
        strcpy(*e, def);
 
234
        *envdef = new_envdef;
 
235
        strcpy(*envdef, def);
227
236
      }
228
237
      return true;
229
238
    }
231
240
  return add_to_char_array(def, &(p->environ), &(p->envc));
232
241
}
233
242
 
 
243
#ifndef O_CLOEXEC
234
244
/*
235
245
 * Based on the example in the GNU LibC manual chapter 13.13 "File
236
246
 * Descriptor Flags".
237
247
 | [[info:libc:Descriptor%20Flags][File Descriptor Flags]] |
238
248
 */
 
249
__attribute__((warn_unused_result))
239
250
static int set_cloexec_flag(int fd){
240
251
  int ret = (int)TEMP_FAILURE_RETRY(fcntl(fd, F_GETFD, 0));
241
252
  /* If reading the flags failed, return error indication now. */
246
257
  return (int)TEMP_FAILURE_RETRY(fcntl(fd, F_SETFD,
247
258
                                       ret | FD_CLOEXEC));
248
259
}
 
260
#endif  /* not O_CLOEXEC */
249
261
 
250
262
 
251
263
/* Mark processes as completed when they exit, and save their exit
265
277
        /* No child processes */
266
278
        break;
267
279
      }
268
 
      perror("waitpid");
 
280
      error(0, errno, "waitpid");
269
281
    }
270
282
    
271
283
    /* A child exited, find it in process_list */
283
295
}
284
296
 
285
297
/* Prints out a password to stdout */
 
298
__attribute__((nonnull, warn_unused_result))
286
299
static bool print_out_password(const char *buffer, size_t length){
287
300
  ssize_t ret;
288
301
  for(size_t written = 0; written < length; written += (size_t)ret){
296
309
}
297
310
 
298
311
/* Removes and free a plugin from the plugin list */
 
312
__attribute__((nonnull))
299
313
static void free_plugin(plugin *plugin_node){
300
314
  
301
315
  for(char **arg = plugin_node->argv; *arg != NULL; arg++){
335
349
  char *plugindir = NULL;
336
350
  char *argfile = NULL;
337
351
  FILE *conffp;
338
 
  size_t d_name_len;
339
 
  DIR *dir = NULL;
340
 
  struct dirent *dirst;
 
352
  struct dirent **direntries = NULL;
341
353
  struct stat st;
342
354
  fd_set rfds_all;
343
355
  int ret, maxfd = 0;
351
363
                                      .sa_flags = SA_NOCLDSTOP };
352
364
  char **custom_argv = NULL;
353
365
  int custom_argc = 0;
 
366
  int dir_fd = -1;
354
367
  
355
368
  /* Establish a signal handler */
356
369
  sigemptyset(&sigchld_action.sa_mask);
357
370
  ret = sigaddset(&sigchld_action.sa_mask, SIGCHLD);
358
371
  if(ret == -1){
359
 
    perror("sigaddset");
360
 
    exitstatus = EXIT_FAILURE;
 
372
    error(0, errno, "sigaddset");
 
373
    exitstatus = EX_OSERR;
361
374
    goto fallback;
362
375
  }
363
376
  ret = sigaction(SIGCHLD, &sigchld_action, &old_sigchld_action);
364
377
  if(ret == -1){
365
 
    perror("sigaction");
366
 
    exitstatus = EXIT_FAILURE;
 
378
    error(0, errno, "sigaction");
 
379
    exitstatus = EX_OSERR;
367
380
    goto fallback;
368
381
  }
369
382
  
413
426
    { .name = NULL }
414
427
  };
415
428
  
 
429
  __attribute__((nonnull(3)))
416
430
  error_t parse_opt(int key, char *arg, struct argp_state *state){
417
431
    errno = 0;
418
432
    switch(key){
419
433
      char *tmp;
420
 
      intmax_t tmpmax;
 
434
      intmax_t tmp_id;
421
435
    case 'g':                   /* --global-options */
422
436
      {
423
437
        char *plugin_option;
426
440
            break;
427
441
          }
428
442
        }
 
443
        errno = 0;
429
444
      }
430
445
      break;
431
446
    case 'G':                   /* --global-env */
432
 
      add_environment(getplugin(NULL), arg, true);
 
447
      if(add_environment(getplugin(NULL), arg, true)){
 
448
        errno = 0;
 
449
      }
433
450
      break;
434
451
    case 'o':                   /* --options-for */
435
452
      {
452
469
            break;
453
470
          }
454
471
        }
 
472
        errno = 0;
455
473
      }
456
474
      break;
457
475
    case 'E':                   /* --env-for */
469
487
          errno = EINVAL;
470
488
          break;
471
489
        }
472
 
        add_environment(getplugin(arg), envdef, true);
 
490
        if(add_environment(getplugin(arg), envdef, true)){
 
491
          errno = 0;
 
492
        }
473
493
      }
474
494
      break;
475
495
    case 'd':                   /* --disable */
477
497
        plugin *p = getplugin(arg);
478
498
        if(p != NULL){
479
499
          p->disabled = true;
 
500
          errno = 0;
480
501
        }
481
502
      }
482
503
      break;
485
506
        plugin *p = getplugin(arg);
486
507
        if(p != NULL){
487
508
          p->disabled = false;
 
509
          errno = 0;
488
510
        }
489
511
      }
490
512
      break;
491
513
    case 128:                   /* --plugin-dir */
492
514
      free(plugindir);
493
515
      plugindir = strdup(arg);
 
516
      if(plugindir != NULL){
 
517
        errno = 0;
 
518
      }
494
519
      break;
495
520
    case 129:                   /* --config-file */
496
521
      /* This is already done by parse_opt_config_file() */
497
522
      break;
498
523
    case 130:                   /* --userid */
499
 
      tmpmax = strtoimax(arg, &tmp, 10);
 
524
      tmp_id = strtoimax(arg, &tmp, 10);
500
525
      if(errno != 0 or tmp == arg or *tmp != '\0'
501
 
         or tmpmax != (uid_t)tmpmax){
 
526
         or tmp_id != (uid_t)tmp_id){
502
527
        argp_error(state, "Bad user ID number: \"%s\", using %"
503
528
                   PRIdMAX, arg, (intmax_t)uid);
504
529
        break;
505
530
      }
506
 
      uid = (uid_t)tmpmax;
 
531
      uid = (uid_t)tmp_id;
 
532
      errno = 0;
507
533
      break;
508
534
    case 131:                   /* --groupid */
509
 
      tmpmax = strtoimax(arg, &tmp, 10);
 
535
      tmp_id = strtoimax(arg, &tmp, 10);
510
536
      if(errno != 0 or tmp == arg or *tmp != '\0'
511
 
         or tmpmax != (gid_t)tmpmax){
 
537
         or tmp_id != (gid_t)tmp_id){
512
538
        argp_error(state, "Bad group ID number: \"%s\", using %"
513
539
                   PRIdMAX, arg, (intmax_t)gid);
514
540
        break;
515
541
      }
516
 
      gid = (gid_t)tmpmax;
 
542
      gid = (gid_t)tmp_id;
 
543
      errno = 0;
517
544
      break;
518
545
    case 132:                   /* --debug */
519
546
      debug = true;
567
594
    case 129:                   /* --config-file */
568
595
      free(argfile);
569
596
      argfile = strdup(arg);
 
597
      if(argfile != NULL){
 
598
        errno = 0;
 
599
      }
570
600
      break;
571
601
    case 130:                   /* --userid */
572
602
    case 131:                   /* --groupid */
598
628
  case ENOMEM:
599
629
  default:
600
630
    errno = ret;
601
 
    perror("argp_parse");
 
631
    error(0, errno, "argp_parse");
602
632
    exitstatus = EX_OSERR;
603
633
    goto fallback;
604
634
  case EINVAL:
625
655
    custom_argc = 1;
626
656
    custom_argv = malloc(sizeof(char*) * 2);
627
657
    if(custom_argv == NULL){
628
 
      perror("malloc");
629
 
      exitstatus = EXIT_FAILURE;
 
658
      error(0, errno, "malloc");
 
659
      exitstatus = EX_OSERR;
630
660
      goto fallback;
631
661
    }
632
662
    custom_argv[0] = argv[0];
648
678
        }
649
679
        new_arg = strdup(p);
650
680
        if(new_arg == NULL){
651
 
          perror("strdup");
652
 
          exitstatus = EXIT_FAILURE;
 
681
          error(0, errno, "strdup");
 
682
          exitstatus = EX_OSERR;
653
683
          free(org_line);
654
684
          goto fallback;
655
685
        }
656
686
        
657
687
        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;
 
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
          }
665
701
        }
666
702
        custom_argv[custom_argc-1] = new_arg;
667
703
        custom_argv[custom_argc] = NULL;
671
707
      ret = fclose(conffp);
672
708
    } while(ret == EOF and errno == EINTR);
673
709
    if(ret == EOF){
674
 
      perror("fclose");
675
 
      exitstatus = EXIT_FAILURE;
 
710
      error(0, errno, "fclose");
 
711
      exitstatus = EX_IOERR;
676
712
      goto fallback;
677
713
    }
678
714
    free(org_line);
680
716
    /* Check for harmful errors and go to fallback. Other errors might
681
717
       not affect opening plugins */
682
718
    if(errno == EMFILE or errno == ENFILE or errno == ENOMEM){
683
 
      perror("fopen");
684
 
      exitstatus = EXIT_FAILURE;
 
719
      error(0, errno, "fopen");
 
720
      exitstatus = EX_OSERR;
685
721
      goto fallback;
686
722
    }
687
723
  }
697
733
    case ENOMEM:
698
734
    default:
699
735
      errno = ret;
700
 
      perror("argp_parse");
 
736
      error(0, errno, "argp_parse");
701
737
      exitstatus = EX_OSERR;
702
738
      goto fallback;
703
739
    case EINVAL:
717
753
  case ENOMEM:
718
754
  default:
719
755
    errno = ret;
720
 
    perror("argp_parse");
 
756
    error(0, errno, "argp_parse");
721
757
    exitstatus = EX_OSERR;
722
758
    goto fallback;
723
759
  case EINVAL:
739
775
    }
740
776
  }
741
777
  
742
 
  /* Strip permissions down to nobody */
743
 
  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);
744
804
  if(ret == -1){
745
 
    perror("setgid");
 
805
    error(0, errno, "setgid");
746
806
  }
747
807
  ret = setuid(uid);
748
808
  if(ret == -1){
749
 
    perror("setuid");
 
809
    error(0, errno, "setuid");
750
810
  }
751
811
  
752
812
  /* Open plugin directory with close_on_exec flag */
753
813
  {
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
 
    }
 
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
                  );
772
821
    if(dir_fd == -1){
773
 
      perror("Could not open plugin dir");
774
 
      exitstatus = EXIT_FAILURE;
 
822
      error(0, errno, "Could not open plugin dir");
 
823
      exitstatus = EX_UNAVAILABLE;
775
824
      goto fallback;
776
825
    }
777
826
    
779
828
  /* Set the FD_CLOEXEC flag on the directory */
780
829
    ret = set_cloexec_flag(dir_fd);
781
830
    if(ret < 0){
782
 
      perror("set_cloexec_flag");
783
 
      TEMP_FAILURE_RETRY(close(dir_fd));
784
 
      exitstatus = EXIT_FAILURE;
 
831
      error(0, errno, "set_cloexec_flag");
 
832
      exitstatus = EX_OSERR;
785
833
      goto fallback;
786
834
    }
787
835
#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;
 
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
      }
795
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;
796
880
  }
797
881
  
798
882
  FD_ZERO(&rfds_all);
799
883
  
800
884
  /* 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");
 
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]);
873
891
      continue;
874
892
    }
875
 
    
876
 
    ret = (int)TEMP_FAILURE_RETRY(stat(filename, &st));
 
893
    ret = (int)TEMP_FAILURE_RETRY(fstat(plugin_fd, &st));
877
894
    if(ret == -1){
878
 
      perror("stat");
879
 
      free(filename);
 
895
      error(0, errno, "stat");
 
896
      TEMP_FAILURE_RETRY(close(plugin_fd));
 
897
      free(direntries[i]);
880
898
      continue;
881
899
    }
882
900
    
883
901
    /* Ignore non-executable files */
884
902
    if(not S_ISREG(st.st_mode)
885
 
       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)){
886
905
      if(debug){
887
 
        fprintf(stderr, "Ignoring plugin dir entry \"%s\""
888
 
                " 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);
889
910
      }
890
 
      free(filename);
 
911
      TEMP_FAILURE_RETRY(close(plugin_fd));
 
912
      free(direntries[i]);
891
913
      continue;
892
914
    }
893
915
    
894
 
    plugin *p = getplugin(dirst->d_name);
 
916
    plugin *p = getplugin(direntries[i]->d_name);
895
917
    if(p == NULL){
896
 
      perror("getplugin");
897
 
      free(filename);
 
918
      error(0, errno, "getplugin");
 
919
      TEMP_FAILURE_RETRY(close(plugin_fd));
 
920
      free(direntries[i]);
898
921
      continue;
899
922
    }
900
923
    if(p->disabled){
901
924
      if(debug){
902
925
        fprintf(stderr, "Ignoring disabled plugin \"%s\"\n",
903
 
                dirst->d_name);
 
926
                direntries[i]->d_name);
904
927
      }
905
 
      free(filename);
 
928
      TEMP_FAILURE_RETRY(close(plugin_fd));
 
929
      free(direntries[i]);
906
930
      continue;
907
931
    }
908
932
    {
911
935
      if(g != NULL){
912
936
        for(char **a = g->argv + 1; *a != NULL; a++){
913
937
          if(not add_argument(p, *a)){
914
 
            perror("add_argument");
 
938
            error(0, errno, "add_argument");
915
939
          }
916
940
        }
917
941
        /* Add global environment variables */
918
942
        for(char **e = g->environ; *e != NULL; e++){
919
943
          if(not add_environment(p, *e, false)){
920
 
            perror("add_environment");
 
944
            error(0, errno, "add_environment");
921
945
          }
922
946
        }
923
947
      }
924
948
    }
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. */
 
949
    /* If this plugin has any environment variables, we need to
 
950
       duplicate the environment from this process, too. */
928
951
    if(p->environ[0] != NULL){
929
952
      for(char **e = environ; *e != NULL; e++){
930
953
        if(not add_environment(p, *e, false)){
931
 
          perror("add_environment");
 
954
          error(0, errno, "add_environment");
932
955
        }
933
956
      }
934
957
    }
935
958
    
936
959
    int pipefd[2];
 
960
#ifndef O_CLOEXEC
937
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 */
938
965
    if(ret == -1){
939
 
      perror("pipe");
940
 
      exitstatus = EXIT_FAILURE;
941
 
      goto fallback;
942
 
    }
 
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
943
981
    /* Ask OS to automatic close the pipe on exec */
944
982
    ret = set_cloexec_flag(pipefd[0]);
945
983
    if(ret < 0){
946
 
      perror("set_cloexec_flag");
947
 
      exitstatus = EXIT_FAILURE;
 
984
      error(0, errno, "set_cloexec_flag");
 
985
      TEMP_FAILURE_RETRY(close(pipefd[0]));
 
986
      TEMP_FAILURE_RETRY(close(pipefd[1]));
 
987
      exitstatus = EX_OSERR;
 
988
      free(direntries[i]);
948
989
      goto fallback;
949
990
    }
950
991
    ret = set_cloexec_flag(pipefd[1]);
951
992
    if(ret < 0){
952
 
      perror("set_cloexec_flag");
953
 
      exitstatus = EXIT_FAILURE;
 
993
      error(0, errno, "set_cloexec_flag");
 
994
      TEMP_FAILURE_RETRY(close(pipefd[0]));
 
995
      TEMP_FAILURE_RETRY(close(pipefd[1]));
 
996
      exitstatus = EX_OSERR;
 
997
      free(direntries[i]);
954
998
      goto fallback;
955
999
    }
 
1000
#endif  /* not O_CLOEXEC */
956
1001
    /* Block SIGCHLD until process is safely in process list */
957
1002
    ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_BLOCK,
958
1003
                                              &sigchld_action.sa_mask,
959
1004
                                              NULL));
960
1005
    if(ret < 0){
961
 
      perror("sigprocmask");
962
 
      exitstatus = EXIT_FAILURE;
 
1006
      error(0, errno, "sigprocmask");
 
1007
      exitstatus = EX_OSERR;
 
1008
      free(direntries[i]);
963
1009
      goto fallback;
964
1010
    }
965
1011
    /* Starting a new process to be watched */
968
1014
      pid = fork();
969
1015
    } while(pid == -1 and errno == EINTR);
970
1016
    if(pid == -1){
971
 
      perror("fork");
972
 
      exitstatus = EXIT_FAILURE;
 
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]));
 
1022
      exitstatus = EX_OSERR;
 
1023
      free(direntries[i]);
973
1024
      goto fallback;
974
1025
    }
975
1026
    if(pid == 0){
976
1027
      /* this is the child process */
977
1028
      ret = sigaction(SIGCHLD, &old_sigchld_action, NULL);
978
1029
      if(ret < 0){
979
 
        perror("sigaction");
980
 
        _exit(EXIT_FAILURE);
 
1030
        error(0, errno, "sigaction");
 
1031
        _exit(EX_OSERR);
981
1032
      }
982
1033
      ret = sigprocmask(SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
983
1034
      if(ret < 0){
984
 
        perror("sigprocmask");
985
 
        _exit(EXIT_FAILURE);
 
1035
        error(0, errno, "sigprocmask");
 
1036
        _exit(EX_OSERR);
986
1037
      }
987
1038
      
988
1039
      ret = dup2(pipefd[1], STDOUT_FILENO); /* replace our stdout */
989
1040
      if(ret == -1){
990
 
        perror("dup2");
991
 
        _exit(EXIT_FAILURE);
 
1041
        error(0, errno, "dup2");
 
1042
        _exit(EX_OSERR);
992
1043
      }
993
1044
      
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
 
        }
 
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);
1009
1051
      }
1010
1052
      /* no return */
1011
1053
    }
1012
1054
    /* Parent process */
1013
1055
    TEMP_FAILURE_RETRY(close(pipefd[1])); /* Close unused write end of
1014
1056
                                             pipe */
1015
 
    free(filename);
1016
 
    plugin *new_plugin = getplugin(dirst->d_name);
 
1057
    TEMP_FAILURE_RETRY(close(plugin_fd));
 
1058
    plugin *new_plugin = getplugin(direntries[i]->d_name);
1017
1059
    if(new_plugin == NULL){
1018
 
      perror("getplugin");
 
1060
      error(0, errno, "getplugin");
1019
1061
      ret = (int)(TEMP_FAILURE_RETRY
1020
1062
                  (sigprocmask(SIG_UNBLOCK, &sigchld_action.sa_mask,
1021
1063
                               NULL)));
1022
1064
      if(ret < 0){
1023
 
        perror("sigprocmask");
 
1065
        error(0, errno, "sigprocmask");
1024
1066
      }
1025
 
      exitstatus = EXIT_FAILURE;
 
1067
      exitstatus = EX_OSERR;
 
1068
      free(direntries[i]);
1026
1069
      goto fallback;
1027
1070
    }
 
1071
    free(direntries[i]);
1028
1072
    
1029
1073
    new_plugin->pid = pid;
1030
1074
    new_plugin->fd = pipefd[0];
1035
1079
                                              &sigchld_action.sa_mask,
1036
1080
                                              NULL));
1037
1081
    if(ret < 0){
1038
 
      perror("sigprocmask");
1039
 
      exitstatus = EXIT_FAILURE;
 
1082
      error(0, errno, "sigprocmask");
 
1083
      exitstatus = EX_OSERR;
1040
1084
      goto fallback;
1041
1085
    }
1042
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
1043
1093
    FD_SET(new_plugin->fd, &rfds_all); /* Spurious warning from
1044
 
                                          -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
1045
1101
    
1046
1102
    if(maxfd < new_plugin->fd){
1047
1103
      maxfd = new_plugin->fd;
1048
1104
    }
1049
1105
  }
1050
1106
  
1051
 
  TEMP_FAILURE_RETRY(closedir(dir));
1052
 
  dir = NULL;
 
1107
  free(direntries);
 
1108
  direntries = NULL;
 
1109
  TEMP_FAILURE_RETRY(close(dir_fd));
 
1110
  dir_fd = -1;
1053
1111
  free_plugin(getplugin(NULL));
1054
1112
  
1055
1113
  for(plugin *p = plugin_list; p != NULL; p = p->next){
1068
1126
    fd_set rfds = rfds_all;
1069
1127
    int select_ret = select(maxfd+1, &rfds, NULL, NULL, NULL);
1070
1128
    if(select_ret == -1 and errno != EINTR){
1071
 
      perror("select");
1072
 
      exitstatus = EXIT_FAILURE;
 
1129
      error(0, errno, "select");
 
1130
      exitstatus = EX_OSERR;
1073
1131
      goto fallback;
1074
1132
    }
1075
1133
    /* OK, now either a process completed, or something can be read
1101
1159
          }
1102
1160
          
1103
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
1104
1168
          FD_CLR(proc->fd, &rfds_all); /* Spurious warning from
1105
 
                                          -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
1106
1176
          
1107
1177
          /* Block signal while modifying process_list */
1108
1178
          ret = (int)TEMP_FAILURE_RETRY(sigprocmask
1110
1180
                                         &sigchld_action.sa_mask,
1111
1181
                                         NULL));
1112
1182
          if(ret < 0){
1113
 
            perror("sigprocmask");
1114
 
            exitstatus = EXIT_FAILURE;
 
1183
            error(0, errno, "sigprocmask");
 
1184
            exitstatus = EX_OSERR;
1115
1185
            goto fallback;
1116
1186
          }
1117
1187
          
1124
1194
                      (sigprocmask(SIG_UNBLOCK,
1125
1195
                                   &sigchld_action.sa_mask, NULL)));
1126
1196
          if(ret < 0){
1127
 
            perror("sigprocmask");
1128
 
            exitstatus = EXIT_FAILURE;
 
1197
            error(0, errno, "sigprocmask");
 
1198
            exitstatus = EX_OSERR;
1129
1199
            goto fallback;
1130
1200
          }
1131
1201
          
1141
1211
        bool bret = print_out_password(proc->buffer,
1142
1212
                                       proc->buffer_length);
1143
1213
        if(not bret){
1144
 
          perror("print_out_password");
1145
 
          exitstatus = EXIT_FAILURE;
 
1214
          error(0, errno, "print_out_password");
 
1215
          exitstatus = EX_IOERR;
1146
1216
        }
1147
1217
        goto fallback;
1148
1218
      }
1149
1219
      
1150
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
1151
1227
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){ /* Spurious
1152
1228
                                                         warning from
1153
 
                                                         -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
1154
1238
        /* This process had nothing to say at this time */
1155
1239
        proc = proc->next;
1156
1240
        continue;
1157
1241
      }
1158
1242
      /* Before reading, make the process' data buffer large enough */
1159
1243
      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;
 
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");
 
1248
          exitstatus = EX_OSERR;
1165
1249
          goto fallback;
1166
1250
        }
 
1251
        proc->buffer = new_buffer;
1167
1252
        proc->buffer_size += BUFFER_SIZE;
1168
1253
      }
1169
1254
      /* Read from the process */
1188
1273
  
1189
1274
 fallback:
1190
1275
  
1191
 
  if(plugin_list == NULL or exitstatus != EXIT_SUCCESS){
 
1276
  if(plugin_list == NULL or (exitstatus != EXIT_SUCCESS
 
1277
                             and exitstatus != EX_OK)){
1192
1278
    /* Fallback if all plugins failed, none are found or an error
1193
1279
       occured */
1194
1280
    bool bret;
1202
1288
    }
1203
1289
    bret = print_out_password(passwordbuffer, len);
1204
1290
    if(not bret){
1205
 
      perror("print_out_password");
1206
 
      exitstatus = EXIT_FAILURE;
 
1291
      error(0, errno, "print_out_password");
 
1292
      exitstatus = EX_IOERR;
1207
1293
    }
1208
1294
  }
1209
1295
  
1210
1296
  /* Restore old signal handler */
1211
1297
  ret = sigaction(SIGCHLD, &old_sigchld_action, NULL);
1212
1298
  if(ret == -1){
1213
 
    perror("sigaction");
1214
 
    exitstatus = EXIT_FAILURE;
 
1299
    error(0, errno, "sigaction");
 
1300
    exitstatus = EX_OSERR;
1215
1301
  }
1216
1302
  
1217
1303
  if(custom_argv != NULL){
1221
1307
    free(custom_argv);
1222
1308
  }
1223
1309
  
1224
 
  if(dir != NULL){
1225
 
    closedir(dir);
 
1310
  free(direntries);
 
1311
  
 
1312
  if(dir_fd != -1){
 
1313
    TEMP_FAILURE_RETRY(close(dir_fd));
1226
1314
  }
1227
1315
  
1228
1316
  /* Kill the processes */
1232
1320
      ret = kill(p->pid, SIGTERM);
1233
1321
      if(ret == -1 and errno != ESRCH){
1234
1322
        /* Set-uid proccesses might not get closed */
1235
 
        perror("kill");
 
1323
        error(0, errno, "kill");
1236
1324
      }
1237
1325
    }
1238
1326
  }
1242
1330
    ret = wait(NULL);
1243
1331
  } while(ret >= 0);
1244
1332
  if(errno != ECHILD){
1245
 
    perror("wait");
 
1333
    error(0, errno, "wait");
1246
1334
  }
1247
1335
  
1248
1336
  free_plugin_list();