/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-11-17 18:43:11 UTC
  • mto: This revision was merged to the branch mainline in revision 412.
  • Revision ID: teddy@recompile.se-20241117184311-ox25kvngy62h209g
Debian package: Avoid suggesting a C compiler unnecessarily

The list of suggested packages, meant to enable the "mandos" program
to find the correct value of SO_BINDTODEVICE by using a C compiler,
are not necessary when Python 3.3 or later is used, since it has the
SO_BINDTODEVICE constant defined in the "socket" module.  Also, Python
2.6 or older has the same constant in the old "IN" module.  Therefore,
we should suggest these Python versions as alternatives to a C
compiler, so that a C compiler is not installed unnecessarily.

debian/control (Package: mandos/Suggests): Add "python3 (>= 3.3)" and
"python (<= 2.6)" as alternatives to "libc6-dev | libc-dev" and
"c-compiler".

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