/mandos/trunk

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

« back to all changes in this revision

Viewing changes to plugin-runner.c

  • Committer: Teddy Hogeborn
  • Date: 2019-08-05 21:14:05 UTC
  • Revision ID: teddy@recompile.se-20190805211405-9m6hecekaihpttz9
Override lintian warnings about upgrading from old versions

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

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

Show diffs side-by-side

added added

removed removed

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