/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: 2008-12-10 01:26:02 UTC
  • mfrom: (237.1.2 mandos)
  • Revision ID: teddy@fukt.bsnet.se-20081210012602-vhz3h75xkj24t340
First version of a somewhat complete D-Bus server interface.  Also
change user/group name to "_mandos".

* debian/mandos.postinst: Rename old "mandos" user and group to
                          "_mandos"; create "_mandos" user and group
                          if none exist.
* debian/mandos-client.postinst: - '' -

* initramfs-tools-hook: Try "_mandos" before "mandos" as user and
                        group name.

* mandos (_datetime_to_dbus_struct): New; was previously local.
  (Client.started): Renamed to "last_started".  All users changed.
  (Client.started): New; boolean.
  (Client.dbus_object_path): New.
  (Client.check_command): Renamed to "checker_command".  All users
                          changed.
  (Client.__init__): Set and use "self.dbus_object_path".  Set
                     "self.started".
  (Client.start): Update "self.started".  Emit "self.PropertyChanged"
                  signals for both "started" and "last_started".
  (Client.stop): Update "self.started".  Emit "self.PropertyChanged"
                 signal for "started".
  (Client.checker_callback): Take additional "command" argument.  All
                             callers changed. Emit
                             "self.PropertyChanged" signal.
  (Client.bump_timeout): Emit "self.PropertyChanged" signal for
                         "last_checked_ok".
  (Client.start_checker): Emit "self.PropertyChanged" signal for
                          "checker_running".
  (Client.stop_checker): Emit "self.PropertyChanged" signal for
                         "checker_running".
  (Client.still_valid): Bug fix: use "getattr(self, started, False)"
                        instead of "self.started" in case this client
                        object is so new that the "started" attribute
                        has not been created yet.
  (Client.IntervalChanged, Client.CheckerIsRunning, Client.GetChecker,
  Client.GetCreated, Client.GetFingerprint, Client.GetHost,
  Client.GetInterval, Client.GetName, Client.GetStarted,
  Client.GetTimeout, Client.StateChanged, Client.TimeoutChanged):
  Removed; all callers changed.
  (Client.CheckerCompleted): Add "condition" and "command" arguments.
                             All callers changed.
  (Client.GetAllProperties, Client.PropertyChanged): New.
  (Client.StillValid): Renamed to "IsStillValid".
  (Client.StartChecker): Changed to its own function to avoid the
                         return value from "Client.start_checker()".
  (Client.Stop): Changed to its own function to avoid the return value
                 from "Client.stop()".
  (main): Try "_mandos" before "mandos" as user and group name.
          Removed inner function "remove_from_clients".  New inner
          class "MandosServer".

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/*  -*- coding: utf-8; mode: c; mode: orgtbl -*- */
 
1
/*  -*- coding: utf-8 -*- */
2
2
/*
3
3
 * Mandos plugin runner - Run Mandos plugins
4
4
 *
5
 
 * Copyright © 2008-2020 Teddy Hogeborn
6
 
 * Copyright © 2008-2020 Björn Påhlsson
7
 
 * 
8
 
 * This file is part of Mandos.
9
 
 * 
10
 
 * Mandos is free software: you can redistribute it and/or modify it
11
 
 * under the terms of the GNU General Public License as published by
12
 
 * the Free Software Foundation, either version 3 of the License, or
13
 
 * (at your option) any later version.
14
 
 * 
15
 
 * Mandos is distributed in the hope that it will be useful, but
 
5
 * Copyright © 2008 Teddy Hogeborn
 
6
 * Copyright © 2008 Björn Påhlsson
 
7
 * 
 
8
 * This program is free software: you can redistribute it and/or
 
9
 * modify it under the terms of the GNU General Public License as
 
10
 * published by the Free Software Foundation, either version 3 of the
 
11
 * License, or (at your option) any later version.
 
12
 * 
 
13
 * This program is distributed in the hope that it will be useful, but
16
14
 * WITHOUT ANY WARRANTY; without even the implied warranty of
17
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18
16
 * General Public License for more details.
19
17
 * 
20
18
 * You should have received a copy of the GNU General Public License
21
 
 * along with Mandos.  If not, see <http://www.gnu.org/licenses/>.
 
19
 * along with this program.  If not, see
 
20
 * <http://www.gnu.org/licenses/>.
22
21
 * 
23
 
 * Contact the authors at <mandos@recompile.se>.
 
22
 * Contact the authors at <mandos@fukt.bsnet.se>.
24
23
 */
25
24
 
26
25
#define _GNU_SOURCE             /* TEMP_FAILURE_RETRY(), getline(),
27
 
                                   O_CLOEXEC, pipe2() */
 
26
                                   asprintf() */
28
27
#include <stddef.h>             /* size_t, NULL */
29
 
#include <stdlib.h>             /* malloc(), reallocarray(), realloc(),
30
 
                                   EXIT_SUCCESS, exit() */
 
28
#include <stdlib.h>             /* malloc(), exit(), EXIT_FAILURE,
 
29
                                   EXIT_SUCCESS, realloc() */
31
30
#include <stdbool.h>            /* bool, true, false */
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() */
 
31
#include <stdio.h>              /* perror, fileno(), fprintf(),
 
32
                                   stderr, STDOUT_FILENO */
 
33
#include <sys/types.h>          /* DIR, opendir(), stat(), struct
 
34
                                   stat, waitpid(), WIFEXITED(),
 
35
                                   WEXITSTATUS(), wait(), pid_t,
 
36
                                   uid_t, gid_t, getuid(), getgid(),
 
37
                                   dirfd() */
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
 
#include <sys/stat.h>           /* struct stat, fstat(), S_ISREG() */
 
41
                                   WEXITSTATUS() */
 
42
#include <sys/stat.h>           /* struct stat, stat(), S_ISREG() */
43
43
#include <iso646.h>             /* and, or, not */
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
 
                                   lstat(), symlink() */
 
44
#include <dirent.h>             /* DIR, struct dirent, opendir(),
 
45
                                   readdir(), closedir(), dirfd() */
 
46
#include <unistd.h>             /* struct stat, stat(), S_ISREG(),
 
47
                                   fcntl(), setuid(), setgid(),
 
48
                                   F_GETFD, F_SETFD, FD_CLOEXEC,
 
49
                                   access(), pipe(), fork(), close()
 
50
                                   dup2(), STDOUT_FILENO, _exit(),
 
51
                                   execv(), write(), read(),
 
52
                                   close() */
52
53
#include <fcntl.h>              /* fcntl(), F_GETFD, F_SETFD,
53
 
                                   FD_CLOEXEC, openat(), scandirat(),
54
 
                                   pipe2() */
55
 
#include <string.h>             /* strsep, strlen(), strsignal(),
56
 
                                   strcmp(), strncmp() */
 
54
                                   FD_CLOEXEC */
 
55
#include <string.h>             /* strsep, strlen(), asprintf() */
57
56
#include <errno.h>              /* errno */
58
57
#include <argp.h>               /* struct argp_option, struct
59
58
                                   argp_state, struct argp,
63
62
#include <signal.h>             /* struct sigaction, sigemptyset(),
64
63
                                   sigaddset(), sigaction(),
65
64
                                   sigprocmask(), SIG_BLOCK, SIGCHLD,
66
 
                                   SIG_UNBLOCK, kill(), sig_atomic_t
67
 
                                */
 
65
                                   SIG_UNBLOCK, kill() */
68
66
#include <errno.h>              /* errno, EBADF */
69
 
#include <inttypes.h>           /* intmax_t, PRIdMAX, strtoimax() */
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() */
75
67
 
76
68
#define BUFFER_SIZE 256
77
69
 
78
70
#define PDIR "/lib/mandos/plugins.d"
79
 
#define PHDIR "/lib/mandos/plugin-helpers"
80
71
#define AFILE "/conf/conf.d/mandos/plugin-runner.conf"
81
72
 
82
73
const char *argp_program_version = "plugin-runner " VERSION;
83
 
const char *argp_program_bug_address = "<mandos@recompile.se>";
 
74
const char *argp_program_bug_address = "<mandos@fukt.bsnet.se>";
84
75
 
85
76
typedef struct plugin{
86
77
  char *name;                   /* can be NULL or any plugin name */
89
80
  char **environ;
90
81
  int envc;
91
82
  bool disabled;
92
 
  
 
83
 
93
84
  /* Variables used for running processes*/
94
85
  pid_t pid;
95
86
  int fd;
97
88
  size_t buffer_size;
98
89
  size_t buffer_length;
99
90
  bool eof;
100
 
  volatile sig_atomic_t completed;
101
 
  int status;
 
91
  volatile bool completed;
 
92
  volatile int status;
102
93
  struct plugin *next;
103
94
} plugin;
104
95
 
106
97
 
107
98
/* Gets an existing plugin based on name,
108
99
   or if none is found, creates a new one */
109
 
__attribute__((warn_unused_result))
110
100
static plugin *getplugin(char *name){
111
 
  /* Check for existing plugin with that name */
112
 
  for(plugin *p = plugin_list; p != NULL; p = p->next){
113
 
    if((p->name == name)
114
 
       or (p->name and name and (strcmp(p->name, name) == 0))){
 
101
  /* Check for exiting plugin with that name */
 
102
  for (plugin *p = plugin_list; p != NULL; p = p->next){
 
103
    if ((p->name == name)
 
104
        or (p->name and name and (strcmp(p->name, name) == 0))){
115
105
      return p;
116
106
    }
117
107
  }
118
108
  /* Create a new plugin */
119
 
  plugin *new_plugin = NULL;
120
 
  do {
121
 
    new_plugin = malloc(sizeof(plugin));
122
 
  } while(new_plugin == NULL and errno == EINTR);
123
 
  if(new_plugin == NULL){
 
109
  plugin *new_plugin = malloc(sizeof(plugin));
 
110
  if (new_plugin == NULL){
124
111
    return NULL;
125
112
  }
126
113
  char *copy_name = NULL;
127
114
  if(name != NULL){
128
 
    do {
129
 
      copy_name = strdup(name);
130
 
    } while(copy_name == NULL and errno == EINTR);
 
115
    copy_name = strdup(name);
131
116
    if(copy_name == NULL){
132
 
      int e = errno;
133
 
      free(new_plugin);
134
 
      errno = e;
135
117
      return NULL;
136
118
    }
137
119
  }
138
120
  
139
 
  *new_plugin = (plugin){ .name = copy_name,
140
 
                          .argc = 1,
141
 
                          .disabled = false,
142
 
                          .next = plugin_list };
 
121
  *new_plugin = (plugin) { .name = copy_name,
 
122
                           .argc = 1,
 
123
                           .disabled = false,
 
124
                           .next = plugin_list };
143
125
  
144
 
  do {
145
 
    new_plugin->argv = malloc(sizeof(char *) * 2);
146
 
  } while(new_plugin->argv == NULL and errno == EINTR);
147
 
  if(new_plugin->argv == NULL){
148
 
    int e = errno;
 
126
  new_plugin->argv = malloc(sizeof(char *) * 2);
 
127
  if (new_plugin->argv == NULL){
149
128
    free(copy_name);
150
129
    free(new_plugin);
151
 
    errno = e;
152
130
    return NULL;
153
131
  }
154
132
  new_plugin->argv[0] = copy_name;
155
133
  new_plugin->argv[1] = NULL;
156
134
  
157
 
  do {
158
 
    new_plugin->environ = malloc(sizeof(char *));
159
 
  } while(new_plugin->environ == NULL and errno == EINTR);
 
135
  new_plugin->environ = malloc(sizeof(char *));
160
136
  if(new_plugin->environ == NULL){
161
 
    int e = errno;
162
137
    free(copy_name);
163
138
    free(new_plugin->argv);
164
139
    free(new_plugin);
165
 
    errno = e;
166
140
    return NULL;
167
141
  }
168
142
  new_plugin->environ[0] = NULL;
173
147
}
174
148
 
175
149
/* Helper function for add_argument and add_environment */
176
 
__attribute__((nonnull, warn_unused_result))
177
150
static bool add_to_char_array(const char *new, char ***array,
178
151
                              int *len){
179
152
  /* Resize the pointed-to array to hold one more pointer */
180
 
  char **new_array = NULL;
181
 
  do {
182
 
#if defined(__GLIBC_PREREQ) and __GLIBC_PREREQ(2, 26)
183
 
    new_array = reallocarray(*array, (size_t)((*len) + 2),
184
 
                             sizeof(char *));
185
 
#else
186
 
    if(((size_t)((*len) + 2)) > (SIZE_MAX / sizeof(char *))){
187
 
      /* overflow */
188
 
      new_array = NULL;
189
 
      errno = ENOMEM;
190
 
    } else {
191
 
      new_array = realloc(*array, (size_t)((*len) + 2)
192
 
                          * sizeof(char *));
193
 
    }
194
 
#endif
195
 
  } while(new_array == NULL and errno == EINTR);
 
153
  *array = realloc(*array, sizeof(char *)
 
154
                   * (size_t) ((*len) + 2));
196
155
  /* Malloc check */
197
 
  if(new_array == NULL){
 
156
  if(*array == NULL){
198
157
    return false;
199
158
  }
200
 
  *array = new_array;
201
159
  /* Make a copy of the new string */
202
 
  char *copy;
203
 
  do {
204
 
    copy = strdup(new);
205
 
  } while(copy == NULL and errno == EINTR);
 
160
  char *copy = strdup(new);
206
161
  if(copy == NULL){
207
162
    return false;
208
163
  }
215
170
}
216
171
 
217
172
/* Add to a plugin's argument vector */
218
 
__attribute__((nonnull(2), warn_unused_result))
219
173
static bool add_argument(plugin *p, const char *arg){
220
174
  if(p == NULL){
221
175
    return false;
224
178
}
225
179
 
226
180
/* Add to a plugin's environment */
227
 
__attribute__((nonnull(2), warn_unused_result))
228
181
static bool add_environment(plugin *p, const char *def, bool replace){
229
182
  if(p == NULL){
230
183
    return false;
232
185
  /* namelen = length of name of environment variable */
233
186
  size_t namelen = (size_t)(strchrnul(def, '=') - def);
234
187
  /* Search for this environment variable */
235
 
  for(char **envdef = p->environ; *envdef != NULL; envdef++){
236
 
    if(strncmp(*envdef, def, namelen + 1) == 0){
 
188
  for(char **e = p->environ; *e != NULL; e++){
 
189
    if(strncmp(*e, def, namelen + 1) == 0){
237
190
      /* It already exists */
238
191
      if(replace){
239
 
        char *new_envdef;
240
 
        do {
241
 
          new_envdef = realloc(*envdef, strlen(def) + 1);
242
 
        } while(new_envdef == NULL and errno == EINTR);
243
 
        if(new_envdef == NULL){
 
192
        char *new = realloc(*e, strlen(def) + 1);
 
193
        if(new == NULL){
244
194
          return false;
245
195
        }
246
 
        *envdef = new_envdef;
247
 
        strcpy(*envdef, def);
 
196
        *e = new;
 
197
        strcpy(*e, def);
248
198
      }
249
199
      return true;
250
200
    }
252
202
  return add_to_char_array(def, &(p->environ), &(p->envc));
253
203
}
254
204
 
255
 
#ifndef O_CLOEXEC
256
205
/*
257
206
 * Based on the example in the GNU LibC manual chapter 13.13 "File
258
207
 * Descriptor Flags".
259
 
 | [[info:libc:Descriptor%20Flags][File Descriptor Flags]] |
 
208
 * *Note File Descriptor Flags:(libc)Descriptor Flags.
260
209
 */
261
 
__attribute__((warn_unused_result))
262
210
static int set_cloexec_flag(int fd){
263
 
  int ret = (int)TEMP_FAILURE_RETRY(fcntl(fd, F_GETFD, 0));
 
211
  int ret = fcntl(fd, F_GETFD, 0);
264
212
  /* If reading the flags failed, return error indication now. */
265
213
  if(ret < 0){
266
214
    return ret;
267
215
  }
268
216
  /* Store modified flag word in the descriptor. */
269
 
  return (int)TEMP_FAILURE_RETRY(fcntl(fd, F_SETFD,
270
 
                                       ret | FD_CLOEXEC));
 
217
  return fcntl(fd, F_SETFD, ret | FD_CLOEXEC);
271
218
}
272
 
#endif  /* not O_CLOEXEC */
273
219
 
274
220
 
275
221
/* Mark processes as completed when they exit, and save their exit
276
222
   status. */
277
223
static void handle_sigchld(__attribute__((unused)) int sig){
278
 
  int old_errno = errno;
279
224
  while(true){
280
225
    plugin *proc = plugin_list;
281
226
    int status;
285
230
      break;
286
231
    }
287
232
    if(pid == -1){
288
 
      if(errno == ECHILD){
289
 
        /* No child processes */
290
 
        break;
 
233
      if (errno != ECHILD){
 
234
        perror("waitpid");
291
235
      }
292
 
      error(0, errno, "waitpid");
 
236
      /* No child processes */
 
237
      break;
293
238
    }
294
239
    
295
240
    /* A child exited, find it in process_list */
301
246
      continue;
302
247
    }
303
248
    proc->status = status;
304
 
    proc->completed = 1;
 
249
    proc->completed = true;
305
250
  }
306
 
  errno = old_errno;
307
251
}
308
252
 
309
253
/* Prints out a password to stdout */
310
 
__attribute__((nonnull, warn_unused_result))
311
254
static bool print_out_password(const char *buffer, size_t length){
312
255
  ssize_t ret;
313
256
  for(size_t written = 0; written < length; written += (size_t)ret){
321
264
}
322
265
 
323
266
/* Removes and free a plugin from the plugin list */
324
 
__attribute__((nonnull))
325
267
static void free_plugin(plugin *plugin_node){
326
268
  
327
 
  for(char **arg = (plugin_node->argv)+1; *arg != NULL; arg++){
 
269
  for(char **arg = plugin_node->argv; *arg != NULL; arg++){
328
270
    free(*arg);
329
271
  }
330
 
  free(plugin_node->name);
331
272
  free(plugin_node->argv);
332
273
  for(char **env = plugin_node->environ; *env != NULL; env++){
333
274
    free(*env);
334
275
  }
335
276
  free(plugin_node->environ);
336
277
  free(plugin_node->buffer);
337
 
  
 
278
 
338
279
  /* Removes the plugin from the singly-linked list */
339
280
  if(plugin_node == plugin_list){
340
281
    /* First one - simple */
360
301
 
361
302
int main(int argc, char *argv[]){
362
303
  char *plugindir = NULL;
363
 
  char *pluginhelperdir = NULL;
364
304
  char *argfile = NULL;
365
305
  FILE *conffp;
366
 
  struct dirent **direntries = NULL;
 
306
  size_t d_name_len;
 
307
  DIR *dir = NULL;
 
308
  struct dirent *dirst;
367
309
  struct stat st;
368
310
  fd_set rfds_all;
369
311
  int ret, maxfd = 0;
370
 
  ssize_t sret;
371
312
  uid_t uid = 65534;
372
313
  gid_t gid = 65534;
373
314
  bool debug = false;
377
318
                                      .sa_flags = SA_NOCLDSTOP };
378
319
  char **custom_argv = NULL;
379
320
  int custom_argc = 0;
380
 
  int dir_fd = -1;
381
321
  
382
322
  /* Establish a signal handler */
383
323
  sigemptyset(&sigchld_action.sa_mask);
384
324
  ret = sigaddset(&sigchld_action.sa_mask, SIGCHLD);
385
325
  if(ret == -1){
386
 
    error(0, errno, "sigaddset");
387
 
    exitstatus = EX_OSERR;
 
326
    perror("sigaddset");
 
327
    exitstatus = EXIT_FAILURE;
388
328
    goto fallback;
389
329
  }
390
330
  ret = sigaction(SIGCHLD, &sigchld_action, &old_sigchld_action);
391
331
  if(ret == -1){
392
 
    error(0, errno, "sigaction");
393
 
    exitstatus = EX_OSERR;
 
332
    perror("sigaction");
 
333
    exitstatus = EXIT_FAILURE;
394
334
    goto fallback;
395
335
  }
396
336
  
428
368
      .doc = "Group ID the plugins will run as", .group = 3 },
429
369
    { .name = "debug", .key = 132,
430
370
      .doc = "Debug mode", .group = 4 },
431
 
    { .name = "plugin-helper-dir", .key = 133,
432
 
      .arg = "DIRECTORY",
433
 
      .doc = "Specify a different plugin helper directory",
434
 
      .group = 2 },
435
 
    /*
436
 
     * These reproduce what we would get without ARGP_NO_HELP
437
 
     */
438
 
    { .name = "help", .key = '?',
439
 
      .doc = "Give this help list", .group = -1 },
440
 
    { .name = "usage", .key = -3,
441
 
      .doc = "Give a short usage message", .group = -1 },
442
 
    { .name = "version", .key = 'V',
443
 
      .doc = "Print program version", .group = -1 },
444
371
    { .name = NULL }
445
372
  };
446
373
  
447
 
  __attribute__((nonnull(3)))
448
 
  error_t parse_opt(int key, char *arg, struct argp_state *state){
449
 
    errno = 0;
450
 
    switch(key){
451
 
      char *tmp;
452
 
      intmax_t tmp_id;
 
374
  error_t parse_opt (int key, char *arg, __attribute__((unused))
 
375
                     struct argp_state *state) {
 
376
    switch (key) {
453
377
    case 'g':                   /* --global-options */
454
 
      {
455
 
        char *plugin_option;
456
 
        while((plugin_option = strsep(&arg, ",")) != NULL){
457
 
          if(not add_argument(getplugin(NULL), plugin_option)){
458
 
            break;
 
378
      if (arg != NULL){
 
379
        char *p;
 
380
        while((p = strsep(&arg, ",")) != NULL){
 
381
          if(p[0] == '\0'){
 
382
            continue;
 
383
          }
 
384
          if(not add_argument(getplugin(NULL), p)){
 
385
            perror("add_argument");
 
386
            return ARGP_ERR_UNKNOWN;
459
387
          }
460
388
        }
461
 
        errno = 0;
462
389
      }
463
390
      break;
464
391
    case 'G':                   /* --global-env */
465
 
      if(add_environment(getplugin(NULL), arg, true)){
466
 
        errno = 0;
 
392
      if(arg == NULL){
 
393
        break;
 
394
      }
 
395
      if(not add_environment(getplugin(NULL), arg, true)){
 
396
        perror("add_environment");
467
397
      }
468
398
      break;
469
399
    case 'o':                   /* --options-for */
470
 
      {
471
 
        char *option_list = strchr(arg, ':');
472
 
        if(option_list == NULL){
473
 
          argp_error(state, "No colon in \"%s\"", arg);
474
 
          errno = EINVAL;
475
 
          break;
476
 
        }
477
 
        *option_list = '\0';
478
 
        option_list++;
479
 
        if(arg[0] == '\0'){
480
 
          argp_error(state, "Empty plugin name");
481
 
          errno = EINVAL;
482
 
          break;
483
 
        }
484
 
        char *option;
485
 
        while((option = strsep(&option_list, ",")) != NULL){
486
 
          if(not add_argument(getplugin(arg), option)){
487
 
            break;
488
 
          }
489
 
        }
490
 
        errno = 0;
 
400
      if (arg != NULL){
 
401
        char *p_name = strsep(&arg, ":");
 
402
        if(p_name[0] == '\0' or arg == NULL){
 
403
          break;
 
404
        }
 
405
        char *opt = strsep(&arg, ":");
 
406
        if(opt[0] == '\0' or opt == NULL){
 
407
          break;
 
408
        }
 
409
        char *p;
 
410
        while((p = strsep(&opt, ",")) != NULL){
 
411
          if(p[0] == '\0'){
 
412
            continue;
 
413
          }
 
414
          if(not add_argument(getplugin(p_name), p)){
 
415
            perror("add_argument");
 
416
            return ARGP_ERR_UNKNOWN;
 
417
          }
 
418
        }
491
419
      }
492
420
      break;
493
421
    case 'E':                   /* --env-for */
 
422
      if(arg == NULL){
 
423
        break;
 
424
      }
494
425
      {
495
426
        char *envdef = strchr(arg, ':');
496
427
        if(envdef == NULL){
497
 
          argp_error(state, "No colon in \"%s\"", arg);
498
 
          errno = EINVAL;
499
428
          break;
500
429
        }
501
430
        *envdef = '\0';
502
 
        envdef++;
503
 
        if(arg[0] == '\0'){
504
 
          argp_error(state, "Empty plugin name");
505
 
          errno = EINVAL;
506
 
          break;
507
 
        }
508
 
        if(add_environment(getplugin(arg), envdef, true)){
509
 
          errno = 0;
 
431
        if(not add_environment(getplugin(arg), envdef+1, true)){
 
432
          perror("add_environment");
510
433
        }
511
434
      }
512
435
      break;
513
436
    case 'd':                   /* --disable */
514
 
      {
 
437
      if (arg != NULL){
515
438
        plugin *p = getplugin(arg);
516
 
        if(p != NULL){
517
 
          p->disabled = true;
518
 
          errno = 0;
 
439
        if(p == NULL){
 
440
          return ARGP_ERR_UNKNOWN;
519
441
        }
 
442
        p->disabled = true;
520
443
      }
521
444
      break;
522
445
    case 'e':                   /* --enable */
523
 
      {
 
446
      if (arg != NULL){
524
447
        plugin *p = getplugin(arg);
525
 
        if(p != NULL){
526
 
          p->disabled = false;
527
 
          errno = 0;
 
448
        if(p == NULL){
 
449
          return ARGP_ERR_UNKNOWN;
528
450
        }
 
451
        p->disabled = false;
529
452
      }
530
453
      break;
531
454
    case 128:                   /* --plugin-dir */
532
455
      free(plugindir);
533
456
      plugindir = strdup(arg);
534
 
      if(plugindir != NULL){
535
 
        errno = 0;
536
 
      }
 
457
      if(plugindir == NULL){
 
458
        perror("strdup");
 
459
      }      
537
460
      break;
538
461
    case 129:                   /* --config-file */
539
462
      /* This is already done by parse_opt_config_file() */
540
463
      break;
541
464
    case 130:                   /* --userid */
542
 
      tmp_id = strtoimax(arg, &tmp, 10);
543
 
      if(errno != 0 or tmp == arg or *tmp != '\0'
544
 
         or tmp_id != (uid_t)tmp_id){
545
 
        argp_error(state, "Bad user ID number: \"%s\", using %"
546
 
                   PRIdMAX, arg, (intmax_t)uid);
547
 
        break;
548
 
      }
549
 
      uid = (uid_t)tmp_id;
550
 
      errno = 0;
 
465
      uid = (uid_t)strtol(arg, NULL, 10);
551
466
      break;
552
467
    case 131:                   /* --groupid */
553
 
      tmp_id = strtoimax(arg, &tmp, 10);
554
 
      if(errno != 0 or tmp == arg or *tmp != '\0'
555
 
         or tmp_id != (gid_t)tmp_id){
556
 
        argp_error(state, "Bad group ID number: \"%s\", using %"
557
 
                   PRIdMAX, arg, (intmax_t)gid);
558
 
        break;
559
 
      }
560
 
      gid = (gid_t)tmp_id;
561
 
      errno = 0;
 
468
      gid = (gid_t)strtol(arg, NULL, 10);
562
469
      break;
563
470
    case 132:                   /* --debug */
564
471
      debug = true;
565
472
      break;
566
 
    case 133:                   /* --plugin-helper-dir */
567
 
      free(pluginhelperdir);
568
 
      pluginhelperdir = strdup(arg);
569
 
      if(pluginhelperdir != NULL){
570
 
        errno = 0;
571
 
      }
572
 
      break;
573
 
      /*
574
 
       * These reproduce what we would get without ARGP_NO_HELP
575
 
       */
576
 
    case '?':                   /* --help */
577
 
      state->flags &= ~(unsigned int)ARGP_NO_EXIT; /* force exit */
578
 
      argp_state_help(state, state->out_stream, ARGP_HELP_STD_HELP);
579
 
      __builtin_unreachable();
580
 
    case -3:                    /* --usage */
581
 
      state->flags &= ~(unsigned int)ARGP_NO_EXIT; /* force exit */
582
 
      argp_state_help(state, state->out_stream,
583
 
                      ARGP_HELP_USAGE | ARGP_HELP_EXIT_OK);
584
 
      __builtin_unreachable();
585
 
    case 'V':                   /* --version */
586
 
      fprintf(state->out_stream, "%s\n", argp_program_version);
587
 
      exit(EXIT_SUCCESS);
588
 
      break;
589
 
/*
590
 
 * When adding more options before this line, remember to also add a
591
 
 * "case" to the "parse_opt_config_file" function below.
592
 
 */
593
473
    case ARGP_KEY_ARG:
594
474
      /* Cryptsetup always passes an argument, which is an empty
595
475
         string if "none" was specified in /etc/crypttab.  So if
596
476
         argument was empty, we ignore it silently. */
597
 
      if(arg[0] == '\0'){
598
 
        break;
 
477
      if(arg[0] != '\0'){
 
478
        fprintf(stderr, "Ignoring unknown argument \"%s\"\n", arg);
599
479
      }
600
 
#if __GNUC__ >= 7
601
 
      __attribute__((fallthrough));
602
 
#else
603
 
          /* FALLTHROUGH */
604
 
#endif
 
480
      break;
 
481
    case ARGP_KEY_END:
 
482
      break;
605
483
    default:
606
484
      return ARGP_ERR_UNKNOWN;
607
485
    }
608
 
    return errno;               /* Set to 0 at start */
 
486
    return 0;
609
487
  }
610
488
  
611
489
  /* This option parser is the same as parse_opt() above, except it
612
490
     ignores everything but the --config-file option. */
613
 
  error_t parse_opt_config_file(int key, char *arg,
614
 
                                __attribute__((unused))
615
 
                                struct argp_state *state){
616
 
    errno = 0;
617
 
    switch(key){
 
491
  error_t parse_opt_config_file (int key, char *arg,
 
492
                                 __attribute__((unused))
 
493
                                 struct argp_state *state) {
 
494
    switch (key) {
618
495
    case 'g':                   /* --global-options */
619
496
    case 'G':                   /* --global-env */
620
497
    case 'o':                   /* --options-for */
626
503
    case 129:                   /* --config-file */
627
504
      free(argfile);
628
505
      argfile = strdup(arg);
629
 
      if(argfile != NULL){
630
 
        errno = 0;
 
506
      if(argfile == NULL){
 
507
        perror("strdup");
631
508
      }
632
 
      break;
 
509
      break;      
633
510
    case 130:                   /* --userid */
634
511
    case 131:                   /* --groupid */
635
512
    case 132:                   /* --debug */
636
 
    case 133:                   /* --plugin-helper-dir */
637
 
    case '?':                   /* --help */
638
 
    case -3:                    /* --usage */
639
 
    case 'V':                   /* --version */
640
513
    case ARGP_KEY_ARG:
 
514
    case ARGP_KEY_END:
641
515
      break;
642
516
    default:
643
517
      return ARGP_ERR_UNKNOWN;
644
518
    }
645
 
    return errno;
 
519
    return 0;
646
520
  }
647
521
  
648
522
  struct argp argp = { .options = options,
650
524
                       .args_doc = "",
651
525
                       .doc = "Mandos plugin runner -- Run plugins" };
652
526
  
653
 
  /* Parse using parse_opt_config_file() in order to get the custom
 
527
  /* Parse using the parse_opt_config_file in order to get the custom
654
528
     config file location, if any. */
655
 
  ret = argp_parse(&argp, argc, argv,
656
 
                   ARGP_IN_ORDER | ARGP_NO_EXIT | ARGP_NO_HELP,
657
 
                   NULL, NULL);
658
 
  switch(ret){
659
 
  case 0:
660
 
    break;
661
 
  case ENOMEM:
662
 
  default:
663
 
    errno = ret;
664
 
    error(0, errno, "argp_parse");
665
 
    exitstatus = EX_OSERR;
666
 
    goto fallback;
667
 
  case EINVAL:
668
 
    exitstatus = EX_USAGE;
 
529
  ret = argp_parse (&argp, argc, argv, ARGP_IN_ORDER, 0, NULL);
 
530
  if (ret == ARGP_ERR_UNKNOWN){
 
531
    fprintf(stderr, "Unknown error while parsing arguments\n");
 
532
    exitstatus = EXIT_FAILURE;
669
533
    goto fallback;
670
534
  }
671
535
  
673
537
  argp.parser = parse_opt;
674
538
  
675
539
  /* Open the configfile if available */
676
 
  if(argfile == NULL){
 
540
  if (argfile == NULL){
677
541
    conffp = fopen(AFILE, "r");
678
542
  } else {
679
543
    conffp = fopen(argfile, "r");
680
 
  }
 
544
  }  
681
545
  if(conffp != NULL){
682
546
    char *org_line = NULL;
683
547
    char *p, *arg, *new_arg, *line;
684
548
    size_t size = 0;
 
549
    ssize_t sret;
685
550
    const char whitespace_delims[] = " \r\t\f\v\n";
686
551
    const char comment_delim[] = "#";
687
 
    
 
552
 
688
553
    custom_argc = 1;
689
554
    custom_argv = malloc(sizeof(char*) * 2);
690
555
    if(custom_argv == NULL){
691
 
      error(0, errno, "malloc");
692
 
      exitstatus = EX_OSERR;
 
556
      perror("malloc");
 
557
      exitstatus = EXIT_FAILURE;
693
558
      goto fallback;
694
559
    }
695
560
    custom_argv[0] = argv[0];
696
561
    custom_argv[1] = NULL;
697
 
    
 
562
 
698
563
    /* for each line in the config file, strip whitespace and ignore
699
564
       commented text */
700
565
    while(true){
702
567
      if(sret == -1){
703
568
        break;
704
569
      }
705
 
      
 
570
 
706
571
      line = org_line;
707
572
      arg = strsep(&line, comment_delim);
708
573
      while((p = strsep(&arg, whitespace_delims)) != NULL){
711
576
        }
712
577
        new_arg = strdup(p);
713
578
        if(new_arg == NULL){
714
 
          error(0, errno, "strdup");
715
 
          exitstatus = EX_OSERR;
 
579
          perror("strdup");
 
580
          exitstatus = EXIT_FAILURE;
716
581
          free(org_line);
717
582
          goto fallback;
718
583
        }
719
584
        
720
585
        custom_argc += 1;
721
 
        {
722
 
#if defined(__GLIBC_PREREQ) and __GLIBC_PREREQ(2, 26)
723
 
          char **new_argv = reallocarray(custom_argv, (size_t)custom_argc + 1,
724
 
                                         sizeof(char *));
725
 
#else
726
 
          char **new_argv = NULL;
727
 
          if(((size_t)custom_argc + 1) > (SIZE_MAX / sizeof(char *))){
728
 
            /* overflow */
729
 
            errno = ENOMEM;
730
 
          } else {
731
 
            new_argv = realloc(custom_argv, ((size_t)custom_argc + 1)
732
 
                               * sizeof(char *));
733
 
          }
734
 
#endif
735
 
          if(new_argv == NULL){
736
 
            error(0, errno, "reallocarray");
737
 
            exitstatus = EX_OSERR;
738
 
            free(new_arg);
739
 
            free(org_line);
740
 
            goto fallback;
741
 
          } else {
742
 
            custom_argv = new_argv;
743
 
          }
 
586
        custom_argv = realloc(custom_argv, sizeof(char *)
 
587
                              * ((unsigned int) custom_argc + 1));
 
588
        if(custom_argv == NULL){
 
589
          perror("realloc");
 
590
          exitstatus = EXIT_FAILURE;
 
591
          free(org_line);
 
592
          goto fallback;
744
593
        }
745
594
        custom_argv[custom_argc-1] = new_arg;
746
 
        custom_argv[custom_argc] = NULL;
 
595
        custom_argv[custom_argc] = NULL;        
747
596
      }
748
597
    }
749
 
    do {
750
 
      ret = fclose(conffp);
751
 
    } while(ret == EOF and errno == EINTR);
752
 
    if(ret == EOF){
753
 
      error(0, errno, "fclose");
754
 
      exitstatus = EX_IOERR;
755
 
      goto fallback;
756
 
    }
757
598
    free(org_line);
758
599
  } else {
759
600
    /* Check for harmful errors and go to fallback. Other errors might
760
601
       not affect opening plugins */
761
 
    if(errno == EMFILE or errno == ENFILE or errno == ENOMEM){
762
 
      error(0, errno, "fopen");
763
 
      exitstatus = EX_OSERR;
 
602
    if (errno == EMFILE or errno == ENFILE or errno == ENOMEM){
 
603
      perror("fopen");
 
604
      exitstatus = EXIT_FAILURE;
764
605
      goto fallback;
765
606
    }
766
607
  }
767
 
  /* If there were any arguments from the configuration file, pass
768
 
     them to parser as command line arguments */
 
608
  /* If there was any arguments from configuration file,
 
609
     pass them to parser as command arguments */
769
610
  if(custom_argv != NULL){
770
 
    ret = argp_parse(&argp, custom_argc, custom_argv,
771
 
                     ARGP_IN_ORDER | ARGP_NO_EXIT | ARGP_NO_HELP,
772
 
                     NULL, NULL);
773
 
    switch(ret){
774
 
    case 0:
775
 
      break;
776
 
    case ENOMEM:
777
 
    default:
778
 
      errno = ret;
779
 
      error(0, errno, "argp_parse");
780
 
      exitstatus = EX_OSERR;
781
 
      goto fallback;
782
 
    case EINVAL:
783
 
      exitstatus = EX_CONFIG;
 
611
    ret = argp_parse (&argp, custom_argc, custom_argv, ARGP_IN_ORDER,
 
612
                      0, NULL);
 
613
    if (ret == ARGP_ERR_UNKNOWN){
 
614
      fprintf(stderr, "Unknown error while parsing arguments\n");
 
615
      exitstatus = EXIT_FAILURE;
784
616
      goto fallback;
785
617
    }
786
618
  }
787
619
  
788
620
  /* Parse actual command line arguments, to let them override the
789
621
     config file */
790
 
  ret = argp_parse(&argp, argc, argv,
791
 
                   ARGP_IN_ORDER | ARGP_NO_EXIT | ARGP_NO_HELP,
792
 
                   NULL, NULL);
793
 
  switch(ret){
794
 
  case 0:
795
 
    break;
796
 
  case ENOMEM:
797
 
  default:
798
 
    errno = ret;
799
 
    error(0, errno, "argp_parse");
800
 
    exitstatus = EX_OSERR;
801
 
    goto fallback;
802
 
  case EINVAL:
803
 
    exitstatus = EX_USAGE;
804
 
    goto fallback;
805
 
  }
806
 
  
807
 
  {
808
 
    char *pluginhelperenv;
809
 
    bool bret = true;
810
 
    ret = asprintf(&pluginhelperenv, "MANDOSPLUGINHELPERDIR=%s",
811
 
                   pluginhelperdir != NULL ? pluginhelperdir : PHDIR);
812
 
    if(ret != -1){
813
 
      bret = add_environment(getplugin(NULL), pluginhelperenv, true);
814
 
    }
815
 
    if(ret == -1 or not bret){
816
 
      error(0, errno, "Failed to set MANDOSPLUGINHELPERDIR"
817
 
            " environment variable to \"%s\" for all plugins\n",
818
 
            pluginhelperdir != NULL ? pluginhelperdir : PHDIR);
819
 
    }
820
 
    if(ret != -1){
821
 
      free(pluginhelperenv);
822
 
    }
 
622
  ret = argp_parse (&argp, argc, argv, ARGP_IN_ORDER, 0, NULL);
 
623
  if (ret == ARGP_ERR_UNKNOWN){
 
624
    fprintf(stderr, "Unknown error while parsing arguments\n");
 
625
    exitstatus = EXIT_FAILURE;
 
626
    goto fallback;
823
627
  }
824
628
  
825
629
  if(debug){
826
 
    for(plugin *p = plugin_list; p != NULL; p = p->next){
 
630
    for(plugin *p = plugin_list; p != NULL; p=p->next){
827
631
      fprintf(stderr, "Plugin: %s has %d arguments\n",
828
632
              p->name ? p->name : "Global", p->argc - 1);
829
633
      for(char **a = p->argv; *a != NULL; a++){
830
634
        fprintf(stderr, "\tArg: %s\n", *a);
831
635
      }
832
 
      fprintf(stderr, "...and %d environment variables\n", p->envc);
 
636
      fprintf(stderr, "...and %u environment variables\n", p->envc);
833
637
      for(char **a = p->environ; *a != NULL; a++){
834
638
        fprintf(stderr, "\t%s\n", *a);
835
639
      }
836
640
    }
837
641
  }
838
642
  
839
 
  if(getuid() == 0){
840
 
    /* Work around Debian bug #633582:
841
 
       <https://bugs.debian.org/633582> */
842
 
    int plugindir_fd = open(/* plugindir or */ PDIR, O_RDONLY);
843
 
    if(plugindir_fd == -1){
844
 
      if(errno != ENOENT){
845
 
        error(0, errno, "open(\"" PDIR "\")");
846
 
      }
847
 
    } else {
848
 
      ret = (int)TEMP_FAILURE_RETRY(fstat(plugindir_fd, &st));
849
 
      if(ret == -1){
850
 
        error(0, errno, "fstat");
851
 
      } else {
852
 
        if(S_ISDIR(st.st_mode) and st.st_uid == 0 and st.st_gid == 0){
853
 
          ret = fchown(plugindir_fd, uid, gid);
854
 
          if(ret == -1){
855
 
            error(0, errno, "fchown");
856
 
          }
857
 
        }
858
 
      }
859
 
      close(plugindir_fd);
860
 
    }
861
 
 
862
 
    /* Work around Debian bug #981302
863
 
       <https://bugs.debian.org/981302> */
864
 
    if(lstat("/dev/fd", &st) != 0 and errno == ENOENT){
865
 
      ret = symlink("/proc/self/fd", "/dev/fd");
866
 
      if(ret == -1){
867
 
        error(0, errno, "Failed to create /dev/fd symlink");
868
 
      }
869
 
    }
870
 
  }
871
 
  
872
 
  /* Lower permissions */
873
 
  ret = setgid(gid);
874
 
  if(ret == -1){
875
 
    error(0, errno, "setgid");
876
 
  }
 
643
  /* Strip permissions down to nobody */
877
644
  ret = setuid(uid);
878
 
  if(ret == -1){
879
 
    error(0, errno, "setuid");
880
 
  }
881
 
  
882
 
  /* Open plugin directory with close_on_exec flag */
 
645
  if (ret == -1){
 
646
    perror("setuid");
 
647
  }  
 
648
  setgid(gid);
 
649
  if (ret == -1){
 
650
    perror("setgid");
 
651
  }
 
652
  
 
653
  if (plugindir == NULL){
 
654
    dir = opendir(PDIR);
 
655
  } else {
 
656
    dir = opendir(plugindir);
 
657
  }
 
658
  
 
659
  if(dir == NULL){
 
660
    perror("Could not open plugin dir");
 
661
    exitstatus = EXIT_FAILURE;
 
662
    goto fallback;
 
663
  }
 
664
  
 
665
  /* Set the FD_CLOEXEC flag on the directory, if possible */
883
666
  {
884
 
    dir_fd = open(plugindir != NULL ? plugindir : PDIR, O_RDONLY |
885
 
#ifdef O_CLOEXEC
886
 
                  O_CLOEXEC
887
 
#else  /* not O_CLOEXEC */
888
 
                  0
889
 
#endif  /* not O_CLOEXEC */
890
 
                  );
891
 
    if(dir_fd == -1){
892
 
      error(0, errno, "Could not open plugin dir");
893
 
      exitstatus = EX_UNAVAILABLE;
894
 
      goto fallback;
895
 
    }
896
 
    
897
 
#ifndef O_CLOEXEC
898
 
  /* Set the FD_CLOEXEC flag on the directory */
899
 
    ret = set_cloexec_flag(dir_fd);
900
 
    if(ret < 0){
901
 
      error(0, errno, "set_cloexec_flag");
902
 
      exitstatus = EX_OSERR;
903
 
      goto fallback;
904
 
    }
905
 
#endif  /* O_CLOEXEC */
906
 
  }
907
 
  
908
 
  int good_name(const struct dirent * const dirent){
909
 
    const char * const patterns[] = { ".*", "#*#", "*~", "*.dpkg-new",
910
 
                                      "*.dpkg-old", "*.dpkg-bak",
911
 
                                      "*.dpkg-divert", NULL };
912
 
#ifdef __GNUC__
913
 
#pragma GCC diagnostic push
914
 
#pragma GCC diagnostic ignored "-Wcast-qual"
915
 
#endif
916
 
    for(const char **pat = (const char **)patterns;
917
 
        *pat != NULL; pat++){
918
 
#ifdef __GNUC__
919
 
#pragma GCC diagnostic pop
920
 
#endif
921
 
      if(fnmatch(*pat, dirent->d_name, FNM_FILE_NAME | FNM_PERIOD)
922
 
         != FNM_NOMATCH){
923
 
        if(debug){
924
 
            fprintf(stderr, "Ignoring plugin dir entry \"%s\""
925
 
                    " matching pattern %s\n", dirent->d_name, *pat);
926
 
        }
927
 
        return 0;
 
667
    int dir_fd = dirfd(dir);
 
668
    if(dir_fd >= 0){
 
669
      ret = set_cloexec_flag(dir_fd);
 
670
      if(ret < 0){
 
671
        perror("set_cloexec_flag");
 
672
        exitstatus = EXIT_FAILURE;
 
673
        goto fallback;
928
674
      }
929
675
    }
930
 
    return 1;
931
 
  }
932
 
  
933
 
  int numplugins = scandirat(dir_fd, ".", &direntries, good_name,
934
 
                             alphasort);
935
 
  if(numplugins == -1){
936
 
    error(0, errno, "Could not scan plugin dir");
937
 
    direntries = NULL;
938
 
    exitstatus = EX_OSERR;
939
 
    goto fallback;
940
676
  }
941
677
  
942
678
  FD_ZERO(&rfds_all);
943
679
  
944
680
  /* Read and execute any executable in the plugin directory*/
945
 
  for(int i = 0; i < numplugins; i++){
946
 
    
947
 
    int plugin_fd = openat(dir_fd, direntries[i]->d_name, O_RDONLY);
948
 
    if(plugin_fd == -1){
949
 
      error(0, errno, "Could not open plugin");
950
 
      free(direntries[i]);
951
 
      continue;
952
 
    }
953
 
    ret = (int)TEMP_FAILURE_RETRY(fstat(plugin_fd, &st));
954
 
    if(ret == -1){
955
 
      error(0, errno, "stat");
956
 
      close(plugin_fd);
957
 
      free(direntries[i]);
958
 
      continue;
959
 
    }
960
 
    
 
681
  while(true){
 
682
    dirst = readdir(dir);
 
683
    
 
684
    /* All directory entries have been processed */
 
685
    if(dirst == NULL){
 
686
      if (errno == EBADF){
 
687
        perror("readdir");
 
688
        exitstatus = EXIT_FAILURE;
 
689
        goto fallback;
 
690
      }
 
691
      break;
 
692
    }
 
693
    
 
694
    d_name_len = strlen(dirst->d_name);
 
695
    
 
696
    /* Ignore dotfiles, backup files and other junk */
 
697
    {
 
698
      bool bad_name = false;
 
699
      
 
700
      const char const *bad_prefixes[] = { ".", "#", NULL };
 
701
      
 
702
      const char const *bad_suffixes[] = { "~", "#", ".dpkg-new",
 
703
                                           ".dpkg-old",
 
704
                                           ".dpkg-bak",
 
705
                                           ".dpkg-divert", NULL };
 
706
      for(const char **pre = bad_prefixes; *pre != NULL; pre++){
 
707
        size_t pre_len = strlen(*pre);
 
708
        if((d_name_len >= pre_len)
 
709
           and strncmp((dirst->d_name), *pre, pre_len) == 0){
 
710
          if(debug){
 
711
            fprintf(stderr, "Ignoring plugin dir entry \"%s\""
 
712
                    " with bad prefix %s\n", dirst->d_name, *pre);
 
713
          }
 
714
          bad_name = true;
 
715
          break;
 
716
        }
 
717
      }
 
718
      if(bad_name){
 
719
        continue;
 
720
      }
 
721
      for(const char **suf = bad_suffixes; *suf != NULL; suf++){
 
722
        size_t suf_len = strlen(*suf);
 
723
        if((d_name_len >= suf_len)
 
724
           and (strcmp((dirst->d_name)+d_name_len-suf_len, *suf)
 
725
                == 0)){
 
726
          if(debug){
 
727
            fprintf(stderr, "Ignoring plugin dir entry \"%s\""
 
728
                    " with bad suffix %s\n", dirst->d_name, *suf);
 
729
          }
 
730
          bad_name = true;
 
731
          break;
 
732
        }
 
733
      }
 
734
      
 
735
      if(bad_name){
 
736
        continue;
 
737
      }
 
738
    }
 
739
 
 
740
    char *filename;
 
741
    if(plugindir == NULL){
 
742
      ret = asprintf(&filename, PDIR "/%s", dirst->d_name);
 
743
    } else {
 
744
      ret = asprintf(&filename, "%s/%s", plugindir, dirst->d_name);
 
745
    }
 
746
    if(ret < 0){
 
747
      perror("asprintf");
 
748
      continue;
 
749
    }
 
750
    
 
751
    ret = stat(filename, &st);
 
752
    if (ret == -1){
 
753
      perror("stat");
 
754
      free(filename);
 
755
      continue;
 
756
    }
 
757
 
961
758
    /* Ignore non-executable files */
962
 
    if(not S_ISREG(st.st_mode)
963
 
       or (TEMP_FAILURE_RETRY(faccessat(dir_fd, direntries[i]->d_name,
964
 
                                        X_OK, 0)) != 0)){
 
759
    if (not S_ISREG(st.st_mode) or (access(filename, X_OK) != 0)){
965
760
      if(debug){
966
 
        fprintf(stderr, "Ignoring plugin dir entry \"%s/%s\""
967
 
                " with bad type or mode\n",
968
 
                plugindir != NULL ? plugindir : PDIR,
969
 
                direntries[i]->d_name);
 
761
        fprintf(stderr, "Ignoring plugin dir entry \"%s\""
 
762
                " with bad type or mode\n", filename);
970
763
      }
971
 
      close(plugin_fd);
972
 
      free(direntries[i]);
 
764
      free(filename);
973
765
      continue;
974
766
    }
975
767
    
976
 
    plugin *p = getplugin(direntries[i]->d_name);
 
768
    plugin *p = getplugin(dirst->d_name);
977
769
    if(p == NULL){
978
 
      error(0, errno, "getplugin");
979
 
      close(plugin_fd);
980
 
      free(direntries[i]);
 
770
      perror("getplugin");
 
771
      free(filename);
981
772
      continue;
982
773
    }
983
774
    if(p->disabled){
984
775
      if(debug){
985
776
        fprintf(stderr, "Ignoring disabled plugin \"%s\"\n",
986
 
                direntries[i]->d_name);
 
777
                dirst->d_name);
987
778
      }
988
 
      close(plugin_fd);
989
 
      free(direntries[i]);
 
779
      free(filename);
990
780
      continue;
991
781
    }
992
782
    {
995
785
      if(g != NULL){
996
786
        for(char **a = g->argv + 1; *a != NULL; a++){
997
787
          if(not add_argument(p, *a)){
998
 
            error(0, errno, "add_argument");
 
788
            perror("add_argument");
999
789
          }
1000
790
        }
1001
791
        /* Add global environment variables */
1002
792
        for(char **e = g->environ; *e != NULL; e++){
1003
793
          if(not add_environment(p, *e, false)){
1004
 
            error(0, errno, "add_environment");
 
794
            perror("add_environment");
1005
795
          }
1006
796
        }
1007
797
      }
1008
798
    }
1009
 
    /* If this plugin has any environment variables, we need to
1010
 
       duplicate the environment from this process, too. */
 
799
    /* If this plugin has any environment variables, we will call
 
800
       using execve and need to duplicate the environment from this
 
801
       process, too. */
1011
802
    if(p->environ[0] != NULL){
1012
803
      for(char **e = environ; *e != NULL; e++){
1013
804
        if(not add_environment(p, *e, false)){
1014
 
          error(0, errno, "add_environment");
 
805
          perror("add_environment");
1015
806
        }
1016
807
      }
1017
808
    }
1018
809
    
1019
810
    int pipefd[2];
1020
 
#ifndef O_CLOEXEC
1021
 
    ret = (int)TEMP_FAILURE_RETRY(pipe(pipefd));
1022
 
#else  /* O_CLOEXEC */
1023
 
    ret = (int)TEMP_FAILURE_RETRY(pipe2(pipefd, O_CLOEXEC));
1024
 
#endif  /* O_CLOEXEC */
1025
 
    if(ret == -1){
1026
 
      error(0, errno, "pipe");
1027
 
      exitstatus = EX_OSERR;
1028
 
      free(direntries[i]);
1029
 
      goto fallback;
1030
 
    }
1031
 
    if(pipefd[0] >= FD_SETSIZE){
1032
 
      fprintf(stderr, "pipe()[0] (%d) >= FD_SETSIZE (%d)", pipefd[0],
1033
 
              FD_SETSIZE);
1034
 
      close(pipefd[0]);
1035
 
      close(pipefd[1]);
1036
 
      exitstatus = EX_OSERR;
1037
 
      free(direntries[i]);
1038
 
      goto fallback;
1039
 
    }
1040
 
#ifndef O_CLOEXEC
 
811
    ret = pipe(pipefd);
 
812
    if (ret == -1){
 
813
      perror("pipe");
 
814
      exitstatus = EXIT_FAILURE;
 
815
      goto fallback;
 
816
    }
1041
817
    /* Ask OS to automatic close the pipe on exec */
1042
818
    ret = set_cloexec_flag(pipefd[0]);
1043
819
    if(ret < 0){
1044
 
      error(0, errno, "set_cloexec_flag");
1045
 
      close(pipefd[0]);
1046
 
      close(pipefd[1]);
1047
 
      exitstatus = EX_OSERR;
1048
 
      free(direntries[i]);
 
820
      perror("set_cloexec_flag");
 
821
      exitstatus = EXIT_FAILURE;
1049
822
      goto fallback;
1050
823
    }
1051
824
    ret = set_cloexec_flag(pipefd[1]);
1052
825
    if(ret < 0){
1053
 
      error(0, errno, "set_cloexec_flag");
1054
 
      close(pipefd[0]);
1055
 
      close(pipefd[1]);
1056
 
      exitstatus = EX_OSERR;
1057
 
      free(direntries[i]);
 
826
      perror("set_cloexec_flag");
 
827
      exitstatus = EXIT_FAILURE;
1058
828
      goto fallback;
1059
829
    }
1060
 
#endif  /* not O_CLOEXEC */
1061
830
    /* Block SIGCHLD until process is safely in process list */
1062
 
    ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_BLOCK,
1063
 
                                              &sigchld_action.sa_mask,
1064
 
                                              NULL));
 
831
    ret = sigprocmask (SIG_BLOCK, &sigchld_action.sa_mask, NULL);
1065
832
    if(ret < 0){
1066
 
      error(0, errno, "sigprocmask");
1067
 
      exitstatus = EX_OSERR;
1068
 
      free(direntries[i]);
 
833
      perror("sigprocmask");
 
834
      exitstatus = EXIT_FAILURE;
1069
835
      goto fallback;
1070
836
    }
1071
837
    /* Starting a new process to be watched */
1072
 
    pid_t pid;
1073
 
    do {
1074
 
      pid = fork();
1075
 
    } while(pid == -1 and errno == EINTR);
 
838
    pid_t pid = fork();
1076
839
    if(pid == -1){
1077
 
      error(0, errno, "fork");
1078
 
      TEMP_FAILURE_RETRY(sigprocmask(SIG_UNBLOCK,
1079
 
                                     &sigchld_action.sa_mask, NULL));
1080
 
      close(pipefd[0]);
1081
 
      close(pipefd[1]);
1082
 
      exitstatus = EX_OSERR;
1083
 
      free(direntries[i]);
 
840
      perror("fork");
 
841
      exitstatus = EXIT_FAILURE;
1084
842
      goto fallback;
1085
843
    }
1086
844
    if(pid == 0){
1087
845
      /* this is the child process */
1088
846
      ret = sigaction(SIGCHLD, &old_sigchld_action, NULL);
1089
847
      if(ret < 0){
1090
 
        error(0, errno, "sigaction");
1091
 
        _exit(EX_OSERR);
 
848
        perror("sigaction");
 
849
        _exit(EXIT_FAILURE);
1092
850
      }
1093
851
      ret = sigprocmask(SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
1094
852
      if(ret < 0){
1095
 
        error(0, errno, "sigprocmask");
1096
 
        _exit(EX_OSERR);
 
853
        perror("sigprocmask");
 
854
        _exit(EXIT_FAILURE);
1097
855
      }
1098
856
      
1099
857
      ret = dup2(pipefd[1], STDOUT_FILENO); /* replace our stdout */
1100
858
      if(ret == -1){
1101
 
        error(0, errno, "dup2");
1102
 
        _exit(EX_OSERR);
 
859
        perror("dup2");
 
860
        _exit(EXIT_FAILURE);
1103
861
      }
1104
862
      
1105
 
      if(fexecve(plugin_fd, p->argv,
1106
 
                (p->environ[0] != NULL) ? p->environ : environ) < 0){
1107
 
        error(0, errno, "fexecve for %s/%s",
1108
 
              plugindir != NULL ? plugindir : PDIR,
1109
 
              direntries[i]->d_name);
1110
 
        _exit(EX_OSERR);
 
863
      if(dirfd(dir) < 0){
 
864
        /* If dir has no file descriptor, we could not set FD_CLOEXEC
 
865
           above and must now close it manually here. */
 
866
        closedir(dir);
 
867
      }
 
868
      if(p->environ[0] == NULL){
 
869
        if(execv(filename, p->argv) < 0){
 
870
          perror("execv");
 
871
          _exit(EXIT_FAILURE);
 
872
        }
 
873
      } else {
 
874
        if(execve(filename, p->argv, p->environ) < 0){
 
875
          perror("execve");
 
876
          _exit(EXIT_FAILURE);
 
877
        }
1111
878
      }
1112
879
      /* no return */
1113
880
    }
1114
881
    /* Parent process */
1115
882
    close(pipefd[1]);           /* Close unused write end of pipe */
1116
 
    close(plugin_fd);
1117
 
    plugin *new_plugin = getplugin(direntries[i]->d_name);
1118
 
    if(new_plugin == NULL){
1119
 
      error(0, errno, "getplugin");
1120
 
      ret = (int)(TEMP_FAILURE_RETRY
1121
 
                  (sigprocmask(SIG_UNBLOCK, &sigchld_action.sa_mask,
1122
 
                               NULL)));
 
883
    free(filename);
 
884
    plugin *new_plugin = getplugin(dirst->d_name);
 
885
    if (new_plugin == NULL){
 
886
      perror("getplugin");
 
887
      ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
1123
888
      if(ret < 0){
1124
 
        error(0, errno, "sigprocmask");
 
889
        perror("sigprocmask");
1125
890
      }
1126
 
      exitstatus = EX_OSERR;
1127
 
      free(direntries[i]);
 
891
      exitstatus = EXIT_FAILURE;
1128
892
      goto fallback;
1129
893
    }
1130
 
    free(direntries[i]);
1131
894
    
1132
895
    new_plugin->pid = pid;
1133
896
    new_plugin->fd = pipefd[0];
1134
 
 
1135
 
    if(debug){
1136
 
      fprintf(stderr, "Plugin %s started (PID %" PRIdMAX ")\n",
1137
 
              new_plugin->name, (intmax_t) (new_plugin->pid));
1138
 
    }
1139
 
 
 
897
    
1140
898
    /* Unblock SIGCHLD so signal handler can be run if this process
1141
899
       has already completed */
1142
 
    ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_UNBLOCK,
1143
 
                                              &sigchld_action.sa_mask,
1144
 
                                              NULL));
 
900
    ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
1145
901
    if(ret < 0){
1146
 
      error(0, errno, "sigprocmask");
1147
 
      exitstatus = EX_OSERR;
 
902
      perror("sigprocmask");
 
903
      exitstatus = EXIT_FAILURE;
1148
904
      goto fallback;
1149
905
    }
1150
906
    
1151
907
    FD_SET(new_plugin->fd, &rfds_all);
1152
908
    
1153
 
    if(maxfd < new_plugin->fd){
 
909
    if (maxfd < new_plugin->fd){
1154
910
      maxfd = new_plugin->fd;
1155
911
    }
1156
912
  }
1157
913
  
1158
 
  free(direntries);
1159
 
  direntries = NULL;
1160
 
  close(dir_fd);
1161
 
  dir_fd = -1;
1162
 
  free_plugin(getplugin(NULL));
 
914
  closedir(dir);
 
915
  dir = NULL;
1163
916
  
1164
917
  for(plugin *p = plugin_list; p != NULL; p = p->next){
1165
918
    if(p->pid != 0){
1176
929
  while(plugin_list){
1177
930
    fd_set rfds = rfds_all;
1178
931
    int select_ret = select(maxfd+1, &rfds, NULL, NULL, NULL);
1179
 
    if(select_ret == -1 and errno != EINTR){
1180
 
      error(0, errno, "select");
1181
 
      exitstatus = EX_OSERR;
 
932
    if (select_ret == -1){
 
933
      perror("select");
 
934
      exitstatus = EXIT_FAILURE;
1182
935
      goto fallback;
1183
936
    }
1184
937
    /* OK, now either a process completed, or something can be read
1185
938
       from one of them */
1186
939
    for(plugin *proc = plugin_list; proc != NULL;){
1187
940
      /* Is this process completely done? */
1188
 
      if(proc->completed and proc->eof){
 
941
      if(proc->eof and proc->completed){
1189
942
        /* Only accept the plugin output if it exited cleanly */
1190
943
        if(not WIFEXITED(proc->status)
1191
944
           or WEXITSTATUS(proc->status) != 0){
1192
945
          /* Bad exit by plugin */
1193
 
          
 
946
 
1194
947
          if(debug){
1195
948
            if(WIFEXITED(proc->status)){
1196
 
              fprintf(stderr, "Plugin %s [%" PRIdMAX "] exited with"
1197
 
                      " status %d\n", proc->name,
1198
 
                      (intmax_t) (proc->pid),
 
949
              fprintf(stderr, "Plugin %u exited with status %d\n",
 
950
                      (unsigned int) (proc->pid),
1199
951
                      WEXITSTATUS(proc->status));
1200
 
            } else if(WIFSIGNALED(proc->status)){
1201
 
              fprintf(stderr, "Plugin %s [%" PRIdMAX "] killed by"
1202
 
                      " signal %d: %s\n", proc->name,
1203
 
                      (intmax_t) (proc->pid),
1204
 
                      WTERMSIG(proc->status),
1205
 
                      strsignal(WTERMSIG(proc->status)));
 
952
            } else if(WIFSIGNALED(proc->status)) {
 
953
              fprintf(stderr, "Plugin %u killed by signal %d\n",
 
954
                      (unsigned int) (proc->pid),
 
955
                      WTERMSIG(proc->status));
 
956
            } else if(WCOREDUMP(proc->status)){
 
957
              fprintf(stderr, "Plugin %d dumped core\n",
 
958
                      (unsigned int) (proc->pid));
1206
959
            }
1207
960
          }
1208
961
          
1209
962
          /* Remove the plugin */
1210
963
          FD_CLR(proc->fd, &rfds_all);
1211
 
          
 
964
 
1212
965
          /* Block signal while modifying process_list */
1213
 
          ret = (int)TEMP_FAILURE_RETRY(sigprocmask
1214
 
                                        (SIG_BLOCK,
1215
 
                                         &sigchld_action.sa_mask,
1216
 
                                         NULL));
 
966
          ret = sigprocmask(SIG_BLOCK, &sigchld_action.sa_mask, NULL);
1217
967
          if(ret < 0){
1218
 
            error(0, errno, "sigprocmask");
1219
 
            exitstatus = EX_OSERR;
 
968
            perror("sigprocmask");
 
969
            exitstatus = EXIT_FAILURE;
1220
970
            goto fallback;
1221
971
          }
1222
972
          
1225
975
          proc = next_plugin;
1226
976
          
1227
977
          /* We are done modifying process list, so unblock signal */
1228
 
          ret = (int)(TEMP_FAILURE_RETRY
1229
 
                      (sigprocmask(SIG_UNBLOCK,
1230
 
                                   &sigchld_action.sa_mask, NULL)));
 
978
          ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask,
 
979
                             NULL);
1231
980
          if(ret < 0){
1232
 
            error(0, errno, "sigprocmask");
1233
 
            exitstatus = EX_OSERR;
 
981
            perror("sigprocmask");
 
982
            exitstatus = EXIT_FAILURE;
1234
983
            goto fallback;
1235
984
          }
1236
985
          
1246
995
        bool bret = print_out_password(proc->buffer,
1247
996
                                       proc->buffer_length);
1248
997
        if(not bret){
1249
 
          error(0, errno, "print_out_password");
1250
 
          exitstatus = EX_IOERR;
 
998
          perror("print_out_password");
 
999
          exitstatus = EXIT_FAILURE;
1251
1000
        }
1252
1001
        goto fallback;
1253
1002
      }
1260
1009
      }
1261
1010
      /* Before reading, make the process' data buffer large enough */
1262
1011
      if(proc->buffer_length + BUFFER_SIZE > proc->buffer_size){
1263
 
        char *new_buffer = realloc(proc->buffer, proc->buffer_size
1264
 
                                   + (size_t) BUFFER_SIZE);
1265
 
        if(new_buffer == NULL){
1266
 
          error(0, errno, "malloc");
1267
 
          exitstatus = EX_OSERR;
 
1012
        proc->buffer = realloc(proc->buffer, proc->buffer_size
 
1013
                               + (size_t) BUFFER_SIZE);
 
1014
        if (proc->buffer == NULL){
 
1015
          perror("malloc");
 
1016
          exitstatus = EXIT_FAILURE;
1268
1017
          goto fallback;
1269
1018
        }
1270
 
        proc->buffer = new_buffer;
1271
1019
        proc->buffer_size += BUFFER_SIZE;
1272
1020
      }
1273
1021
      /* Read from the process */
1274
 
      sret = TEMP_FAILURE_RETRY(read(proc->fd,
1275
 
                                     proc->buffer
1276
 
                                     + proc->buffer_length,
1277
 
                                     BUFFER_SIZE));
1278
 
      if(sret < 0){
 
1022
      ret = read(proc->fd, proc->buffer + proc->buffer_length,
 
1023
                 BUFFER_SIZE);
 
1024
      if(ret < 0){
1279
1025
        /* Read error from this process; ignore the error */
1280
1026
        proc = proc->next;
1281
1027
        continue;
1282
1028
      }
1283
 
      if(sret == 0){
 
1029
      if(ret == 0){
1284
1030
        /* got EOF */
1285
1031
        proc->eof = true;
1286
1032
      } else {
1287
 
        proc->buffer_length += (size_t) sret;
 
1033
        proc->buffer_length += (size_t) ret;
1288
1034
      }
1289
1035
    }
1290
1036
  }
1291
 
  
1292
 
  
 
1037
 
 
1038
 
1293
1039
 fallback:
1294
1040
  
1295
 
  if(plugin_list == NULL or (exitstatus != EXIT_SUCCESS
1296
 
                             and exitstatus != EX_OK)){
 
1041
  if(plugin_list == NULL or exitstatus != EXIT_SUCCESS){
1297
1042
    /* Fallback if all plugins failed, none are found or an error
1298
1043
       occured */
1299
1044
    bool bret;
1307
1052
    }
1308
1053
    bret = print_out_password(passwordbuffer, len);
1309
1054
    if(not bret){
1310
 
      error(0, errno, "print_out_password");
1311
 
      exitstatus = EX_IOERR;
 
1055
      perror("print_out_password");
 
1056
      exitstatus = EXIT_FAILURE;
1312
1057
    }
1313
1058
  }
1314
1059
  
1315
1060
  /* Restore old signal handler */
1316
1061
  ret = sigaction(SIGCHLD, &old_sigchld_action, NULL);
1317
1062
  if(ret == -1){
1318
 
    error(0, errno, "sigaction");
1319
 
    exitstatus = EX_OSERR;
 
1063
    perror("sigaction");
 
1064
    exitstatus = EXIT_FAILURE;
1320
1065
  }
1321
1066
  
1322
1067
  if(custom_argv != NULL){
1326
1071
    free(custom_argv);
1327
1072
  }
1328
1073
  
1329
 
  free(direntries);
1330
 
  
1331
 
  if(dir_fd != -1){
1332
 
    close(dir_fd);
 
1074
  if(dir != NULL){
 
1075
    closedir(dir);
1333
1076
  }
1334
1077
  
1335
1078
  /* Kill the processes */
1339
1082
      ret = kill(p->pid, SIGTERM);
1340
1083
      if(ret == -1 and errno != ESRCH){
1341
1084
        /* Set-uid proccesses might not get closed */
1342
 
        error(0, errno, "kill");
 
1085
        perror("kill");
1343
1086
      }
1344
1087
    }
1345
1088
  }
1346
1089
  
1347
1090
  /* Wait for any remaining child processes to terminate */
1348
 
  do {
 
1091
  do{
1349
1092
    ret = wait(NULL);
1350
1093
  } while(ret >= 0);
1351
1094
  if(errno != ECHILD){
1352
 
    error(0, errno, "wait");
 
1095
    perror("wait");
1353
1096
  }
1354
1097
  
1355
1098
  free_plugin_list();
1356
1099
  
1357
1100
  free(plugindir);
1358
 
  free(pluginhelperdir);
1359
1101
  free(argfile);
1360
1102
  
1361
1103
  return exitstatus;