/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: 2024-09-09 01:36:41 UTC
  • mto: This revision was merged to the branch mainline in revision 410.
  • Revision ID: teddy@recompile.se-20240909013641-6zu6kx2f7meu134k
Make all required directories when installing

When installing into a normal system, one can assume that target
directories, such as /usr/bin, already exists.  But when installing
into a subdirectory for the purpose of creating a package, one cannot
assume that all directories already exist.  Therefore, when
installing, we must not check if any directories exist, and must
instead always create any directories we want to install into.

* Makefile (confdir/mandos.conf, confdir/clients.conf, install-html):
  Use the "-D" option to "install" instead of creating the directory
  separately.
  (install-server): Move creation of $(CONFDIR) down to before it is
  needed.  Don't check if the $(TMPFILES) or $(SYSUSERS) directories
  exist; instead create them by using the "-D" option to "install".
  Create the $(PREFIX)/sbin directory.  Always use
  "--target-directory" if possible; i.e. if the file name is the same.
  Create the $(DBUSPOLICYDIR) and $(DESTDIR)/etc/init.d directories by
  using the "-D" option to "install".  Don't check if the $(SYSTEMD)
  directory exists; instead create it by using the "-D" option to
  "install".  Create the $(DESTDIR)/etc/default and $(MANDIR)/man8
  directories by using the "-D" option to "install".  Create the
  $(MANDIR)/man5 directories explicitly.
  (install-client-nokey): Remove unnecessary creation of the
  $(CONFDIR) directory.  Don't check if the $(SYSUSERS) directory
  exists; instead create it by using the "-D" option to "install".
  Move the "--directory" argument to be the first argument, for
  clarity.  Create the $(PREFIX)/sbin directory.  Use the "-D"
  argument to "install" when installing
  $(INITRAMFSTOOLS)/hooks/mandos,
  $(INITRAMFSTOOLS)/conf.d/mandos-conf,
  $(INITRAMFSTOOLS)/conf-hooks.d/zz-mandos,
  $(INITRAMFSTOOLS)/scripts/init-premount/mandos,
  $(INITRAMFSTOOLS)/scripts/local-premount/mandos,
  $(DRACUTMODULE)/ask-password-mandos.path, and
  $(DRACUTMODULE)/dracut-module/ask-password-mandos.service.  Create
  the $(MANDIR)/man8 directory.

Reported-By: Erich Eckner <erich@eckner.net>
Thanks: Erich Eckner <erich@eckner.net> for analysis

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