/mandos/release

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

« back to all changes in this revision

Viewing changes to plugin-runner.c

  • Committer: Teddy Hogeborn
  • Date: 2015-07-20 03:03:33 UTC
  • mto: (237.7.594 trunk)
  • mto: This revision was merged to the branch mainline in revision 325.
  • Revision ID: teddy@recompile.se-20150720030333-203m2aeblypcsfte
Bug fix for GnuTLS 3: be compatible with old 2048-bit DSA keys.

The mandos-keygen program in Mandos version 1.6.0 and older generated
2048-bit DSA keys, and when GnuTLS uses these it has trouble
connecting using the Mandos default priority string.  This was
previously fixed in Mandos 1.6.2, but the bug reappeared when using
GnuTLS 3, so the default priority string has to change again; this
time also the Mandos client has to change its default, so now the
server and the client should use the same default priority string:

SECURE256:!CTYPE-X.509:+CTYPE-OPENPGP:!RSA:+SIGN-DSA-SHA256

* mandos (main/server_defaults): Changed default priority string.
* mandos-options.xml (/section/para[id="priority_compat"]): Removed.
  (/section/para[id="priority"]): Changed default priority string.
* mandos.conf ([DEFAULT]/priority): - '' -
* mandos.conf.xml (OPTIONS/priority): Refer to the id "priority"
                                      instead of "priority_compat".
* mandos.xml (OPTIONS/--priority): - '' -
* plugins.d/mandos-client.c (main): Changed default priority string.

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