/mandos/trunk

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

« back to all changes in this revision

Viewing changes to plugin-runner.c

  • Committer: Teddy Hogeborn
  • Date: 2008-08-29 05:53:59 UTC
  • Revision ID: teddy@fukt.bsnet.se-20080829055359-wkdasnyxtylmnxus
* mandos.xml (EXAMPLE): Replaced all occurences of command name with
                        "&COMMANDNAME;".

* plugins.d/password-prompt.c (main): Improved some documentation
                                      strings.  Do perror() of
                                      tcgetattr() fails.  Add debug
                                      output if interrupted by signal.
                                      Loop over write() instead of
                                      using fwrite() when outputting
                                      password.  Add debug output if
                                      getline() returns 0, unless it
                                      was caused by a signal.  Add
                                      exit status code to debug
                                      output.

* plugins.d/password-prompt.xml: Changed all single quotes to double
                                 quotes for consistency.  Removed
                                 <?xml-stylesheet>.
  (ENTITY TIMESTAMP): New.  Automatically updated by Emacs time-stamp
                      by using Emacs local variables.
  (/refentry/refentryinfo/title): Changed to "Mandos Manual".
  (/refentry/refentryinfo/productname): Changed to "Mandos".
  (/refentry/refentryinfo/date): New; set to "&TIMESTAMP;".
  (/refentry/refentryinfo/copyright): Split copyright holders.
  (/refentry/refnamediv/refpurpose): Improved wording.
  (SYNOPSIS): Fix to use correct markup.  Add short options.
  (DESCRIPTION, OPTIONS): Improved wording.
  (OPTIONS): Improved wording.  Use more correct markup.  Document
             short options.
  (EXIT STATUS): Add text.
  (ENVIRONMENT): Document use of "cryptsource" and "crypttarget".
  (FILES): REMOVED.
  (BUGS): Add text.
  (EXAMPLE): Added some examples.
  (SECURITY): Added text.
  (SEE ALSO): Remove reference to mandos(8).  Add reference to
              crypttab(5).

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/*  -*- coding: utf-8; mode: c; mode: orgtbl -*- */
 
1
/*  -*- coding: utf-8 -*- */
2
2
/*
3
3
 * Mandos plugin runner - Run Mandos plugins
4
4
 *
5
 
 * Copyright © 2008-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
 
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
16
13
 * WITHOUT ANY WARRANTY; without even the implied warranty of
17
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18
15
 * General Public License for more details.
19
16
 * 
20
17
 * You should have received a copy of the GNU General Public License
21
 
 * along with Mandos.  If not, see <http://www.gnu.org/licenses/>.
 
18
 * along with this program.  If not, see
 
19
 * <http://www.gnu.org/licenses/>.
22
20
 * 
23
 
 * Contact the authors at <mandos@recompile.se>.
 
21
 * Contact the authors at <mandos@fukt.bsnet.se>.
24
22
 */
25
23
 
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() */
 
24
#define _GNU_SOURCE             /* TEMP_FAILURE_RETRY(), getline(),
 
25
                                   asprintf() */
42
26
#include <stddef.h>             /* size_t, NULL */
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() */
 
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() */
63
52
#include <fcntl.h>              /* fcntl(), F_GETFD, F_SETFD,
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 */
 
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 */
89
66
 
90
67
#define BUFFER_SIZE 256
91
68
 
92
69
#define PDIR "/lib/mandos/plugins.d"
93
 
#define PHDIR "/lib/mandos/plugin-helpers"
94
70
#define AFILE "/conf/conf.d/mandos/plugin-runner.conf"
95
71
 
96
 
const char *argp_program_version = "plugin-runner " VERSION;
97
 
const char *argp_program_bug_address = "<mandos@recompile.se>";
 
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;
98
88
 
99
89
typedef struct plugin{
100
90
  char *name;                   /* can be NULL or any plugin name */
103
93
  char **environ;
104
94
  int envc;
105
95
  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;
116
96
  struct plugin *next;
117
97
} plugin;
118
98
 
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))){
 
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))){
129
103
      return p;
130
104
    }
131
105
  }
132
106
  /* Create a new plugin */
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){
 
107
  plugin *new_plugin = malloc(sizeof(plugin));
 
108
  if (new_plugin == NULL){
138
109
    return NULL;
139
110
  }
140
111
  char *copy_name = NULL;
141
112
  if(name != NULL){
142
 
    do {
143
 
      copy_name = strdup(name);
144
 
    } while(copy_name == NULL and errno == EINTR);
 
113
    copy_name = strdup(name);
145
114
    if(copy_name == NULL){
146
 
      int e = errno;
147
 
      free(new_plugin);
148
 
      errno = e;
149
115
      return NULL;
150
116
    }
151
117
  }
152
118
  
153
 
  *new_plugin = (plugin){ .name = copy_name,
154
 
                          .argc = 1,
155
 
                          .disabled = false,
156
 
                          .next = plugin_list };
 
119
  *new_plugin = (plugin) { .name = copy_name,
 
120
                           .argc = 1,
 
121
                           .envc = 0,
 
122
                           .disabled = false,
 
123
                           .next = *plugin_list };
157
124
  
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;
 
125
  new_plugin->argv = malloc(sizeof(char *) * 2);
 
126
  if (new_plugin->argv == NULL){
163
127
    free(copy_name);
164
128
    free(new_plugin);
165
 
    errno = e;
166
129
    return NULL;
167
130
  }
168
131
  new_plugin->argv[0] = copy_name;
169
132
  new_plugin->argv[1] = NULL;
170
 
  
171
 
  do {
172
 
    new_plugin->environ = malloc(sizeof(char *));
173
 
  } while(new_plugin->environ == NULL and errno == EINTR);
 
133
 
 
134
  new_plugin->environ = malloc(sizeof(char *));
174
135
  if(new_plugin->environ == NULL){
175
 
    int e = errno;
176
136
    free(copy_name);
177
137
    free(new_plugin->argv);
178
138
    free(new_plugin);
179
 
    errno = e;
180
139
    return NULL;
181
140
  }
182
141
  new_plugin->environ[0] = NULL;
183
 
  
184
142
  /* Append the new plugin to the list */
185
 
  plugin_list = new_plugin;
 
143
  *plugin_list = new_plugin;
186
144
  return new_plugin;
187
145
}
188
146
 
189
147
/* Helper function for add_argument and add_environment */
190
 
__attribute__((nonnull, warn_unused_result))
191
148
static bool add_to_char_array(const char *new, char ***array,
192
149
                              int *len){
193
150
  /* Resize the pointed-to array to hold one more pointer */
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);
 
151
  *array = realloc(*array, sizeof(char *)
 
152
                   * (size_t) ((*len) + 2));
210
153
  /* Malloc check */
211
 
  if(new_array == NULL){
 
154
  if(*array == NULL){
212
155
    return false;
213
156
  }
214
 
  *array = new_array;
215
157
  /* Make a copy of the new string */
216
 
  char *copy;
217
 
  do {
218
 
    copy = strdup(new);
219
 
  } while(copy == NULL and errno == EINTR);
 
158
  char *copy = strdup(new);
220
159
  if(copy == NULL){
221
160
    return false;
222
161
  }
229
168
}
230
169
 
231
170
/* Add to a plugin's argument vector */
232
 
__attribute__((nonnull(2), warn_unused_result))
233
171
static bool add_argument(plugin *p, const char *arg){
234
172
  if(p == NULL){
235
173
    return false;
238
176
}
239
177
 
240
178
/* Add to a plugin's environment */
241
 
__attribute__((nonnull(2), warn_unused_result))
242
 
static bool add_environment(plugin *p, const char *def, bool replace){
 
179
static bool add_environment(plugin *p, const char *def){
243
180
  if(p == NULL){
244
181
    return false;
245
182
  }
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
 
  }
266
183
  return add_to_char_array(def, &(p->environ), &(p->envc));
267
184
}
268
185
 
269
 
#ifndef O_CLOEXEC
 
186
 
270
187
/*
271
188
 * Based on the example in the GNU LibC manual chapter 13.13 "File
272
189
 * Descriptor Flags".
273
 
 | [[info:libc:Descriptor%20Flags][File Descriptor Flags]] |
 
190
 * *Note File Descriptor Flags:(libc)Descriptor Flags.
274
191
 */
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
static int set_cloexec_flag(int fd)
 
193
{
 
194
  int ret = fcntl(fd, F_GETFD, 0);
278
195
  /* If reading the flags failed, return error indication now. */
279
196
  if(ret < 0){
280
197
    return ret;
281
198
  }
282
199
  /* Store modified flag word in the descriptor. */
283
 
  return (int)TEMP_FAILURE_RETRY(fcntl(fd, F_SETFD,
284
 
                                       ret | FD_CLOEXEC));
 
200
  return fcntl(fd, F_SETFD, ret | FD_CLOEXEC);
285
201
}
286
 
#endif  /* not O_CLOEXEC */
287
202
 
 
203
process *process_list = NULL;
288
204
 
289
205
/* Mark processes as completed when they exit, and save their exit
290
206
   status. */
291
 
static void handle_sigchld(__attribute__((unused)) int sig){
292
 
  int old_errno = errno;
 
207
void handle_sigchld(__attribute__((unused)) int sig){
293
208
  while(true){
294
 
    plugin *proc = plugin_list;
 
209
    process *proc = process_list;
295
210
    int status;
296
211
    pid_t pid = waitpid(-1, &status, WNOHANG);
297
212
    if(pid == 0){
299
214
      break;
300
215
    }
301
216
    if(pid == -1){
302
 
      if(errno == ECHILD){
303
 
        /* No child processes */
304
 
        break;
 
217
      if (errno != ECHILD){
 
218
        perror("waitpid");
305
219
      }
306
 
      error(0, errno, "waitpid");
 
220
      /* No child processes */
 
221
      break;
307
222
    }
308
 
    
 
223
 
309
224
    /* A child exited, find it in process_list */
310
225
    while(proc != NULL and proc->pid != pid){
311
226
      proc = proc->next;
315
230
      continue;
316
231
    }
317
232
    proc->status = status;
318
 
    proc->completed = 1;
 
233
    proc->completed = true;
319
234
  }
320
 
  errno = old_errno;
321
235
}
322
236
 
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){
 
237
bool print_out_password(const char *buffer, size_t length){
326
238
  ssize_t ret;
 
239
  if(length>0 and buffer[length-1] == '\n'){
 
240
    length--;
 
241
  }
327
242
  for(size_t written = 0; written < length; written += (size_t)ret){
328
243
    ret = TEMP_FAILURE_RETRY(write(STDOUT_FILENO, buffer + written,
329
244
                                   length - written));
334
249
  return true;
335
250
}
336
251
 
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);
 
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);
372
264
  }
373
265
}
374
266
 
375
267
int main(int argc, char *argv[]){
376
268
  char *plugindir = NULL;
377
 
  char *pluginhelperdir = NULL;
378
269
  char *argfile = NULL;
379
270
  FILE *conffp;
380
 
  struct dirent **direntries = NULL;
 
271
  size_t d_name_len;
 
272
  DIR *dir = NULL;
 
273
  struct dirent *dirst;
381
274
  struct stat st;
382
275
  fd_set rfds_all;
383
276
  int ret, maxfd = 0;
384
 
  ssize_t sret;
385
277
  uid_t uid = 65534;
386
278
  gid_t gid = 65534;
387
279
  bool debug = false;
391
283
                                      .sa_flags = SA_NOCLDSTOP };
392
284
  char **custom_argv = NULL;
393
285
  int custom_argc = 0;
394
 
  int dir_fd = -1;
395
286
  
396
287
  /* Establish a signal handler */
397
288
  sigemptyset(&sigchld_action.sa_mask);
398
289
  ret = sigaddset(&sigchld_action.sa_mask, SIGCHLD);
399
290
  if(ret == -1){
400
 
    error(0, errno, "sigaddset");
401
 
    exitstatus = EX_OSERR;
 
291
    perror("sigaddset");
 
292
    exitstatus = EXIT_FAILURE;
402
293
    goto fallback;
403
294
  }
404
295
  ret = sigaction(SIGCHLD, &sigchld_action, &old_sigchld_action);
405
296
  if(ret == -1){
406
 
    error(0, errno, "sigaction");
407
 
    exitstatus = EX_OSERR;
 
297
    perror("sigaction");
 
298
    exitstatus = EXIT_FAILURE;
408
299
    goto fallback;
409
300
  }
410
301
  
413
304
    { .name = "global-options", .key = 'g',
414
305
      .arg = "OPTION[,OPTION[,...]]",
415
306
      .doc = "Options passed to all plugins" },
416
 
    { .name = "global-env", .key = 'G',
 
307
    { .name = "global-envs", .key = 'e',
417
308
      .arg = "VAR=value",
418
309
      .doc = "Environment variable passed to all plugins" },
419
310
    { .name = "options-for", .key = 'o',
420
311
      .arg = "PLUGIN:OPTION[,OPTION[,...]]",
421
312
      .doc = "Options passed only to specified plugin" },
422
 
    { .name = "env-for", .key = 'E',
 
313
    { .name = "envs-for", .key = 'f',
423
314
      .arg = "PLUGIN:ENV=value",
424
315
      .doc = "Environment variable passed to specified plugin" },
425
316
    { .name = "disable", .key = 'd',
426
317
      .arg = "PLUGIN",
427
318
      .doc = "Disable a specific plugin", .group = 1 },
428
 
    { .name = "enable", .key = 'e',
429
 
      .arg = "PLUGIN",
430
 
      .doc = "Enable a specific plugin", .group = 1 },
431
319
    { .name = "plugin-dir", .key = 128,
432
320
      .arg = "DIRECTORY",
433
321
      .doc = "Specify a different plugin directory", .group = 2 },
442
330
      .doc = "Group ID the plugins will run as", .group = 3 },
443
331
    { .name = "debug", .key = 132,
444
332
      .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 },
458
333
    { .name = NULL }
459
334
  };
460
335
  
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 */
 
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
      }
508
397
      {
509
398
        char *envdef = strchr(arg, ':');
510
399
        if(envdef == NULL){
511
 
          argp_error(state, "No colon in \"%s\"", arg);
512
 
          errno = EINVAL;
513
 
          break;
514
 
        }
515
 
        *envdef = '\0';
 
400
          break;
 
401
        }
 
402
        char *p_name = strndup(arg, (size_t) (envdef-arg));
 
403
        if(p_name == NULL){
 
404
          break;
 
405
        }
516
406
        envdef++;
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);
 
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:
547
422
      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 */
 
423
      if(plugindir == NULL){
 
424
        perror("strdup");
 
425
      }      
 
426
      break;
 
427
    case 129:
 
428
      argfile = strdup(arg);
 
429
      if(argfile == NULL){
 
430
        perror("strdup");
 
431
      }
 
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:
578
440
      debug = true;
579
441
      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);
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 = "",
 
442
    case ARGP_KEY_ARG:
 
443
      fprintf(stderr, "Ignoring unknown argument \"%s\"\n", arg);
 
444
      break;
 
445
    case ARGP_KEY_END:
 
446
      break;
 
447
    default:
 
448
      return ARGP_ERR_UNKNOWN;
 
449
    }
 
450
    return 0;
 
451
  }
 
452
  
 
453
  plugin *plugin_list = NULL;
 
454
  
 
455
  struct argp argp = { .options = options, .parser = parse_opt,
 
456
                       .args_doc = "[+PLUS_SEPARATED_OPTIONS]",
665
457
                       .doc = "Mandos plugin runner -- Run plugins" };
666
458
  
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;
 
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;
683
463
    goto fallback;
684
464
  }
685
 
  
686
 
  /* Reset to the normal argument parser */
687
 
  argp.parser = parse_opt;
688
 
  
689
 
  /* Open the configfile if available */
690
 
  if(argfile == NULL){
 
465
 
 
466
  if (argfile == NULL){
691
467
    conffp = fopen(AFILE, "r");
692
468
  } else {
693
469
    conffp = fopen(argfile, "r");
694
470
  }
 
471
  
695
472
  if(conffp != NULL){
696
473
    char *org_line = NULL;
697
474
    char *p, *arg, *new_arg, *line;
698
475
    size_t size = 0;
 
476
    ssize_t sret;
699
477
    const char whitespace_delims[] = " \r\t\f\v\n";
700
478
    const char comment_delim[] = "#";
701
 
    
 
479
 
702
480
    custom_argc = 1;
703
481
    custom_argv = malloc(sizeof(char*) * 2);
704
482
    if(custom_argv == NULL){
705
 
      error(0, errno, "malloc");
706
 
      exitstatus = EX_OSERR;
 
483
      perror("malloc");
 
484
      exitstatus = EXIT_FAILURE;
707
485
      goto fallback;
708
486
    }
709
487
    custom_argv[0] = argv[0];
710
488
    custom_argv[1] = NULL;
711
489
    
712
 
    /* for each line in the config file, strip whitespace and ignore
713
 
       commented text */
714
490
    while(true){
715
491
      sret = getline(&org_line, &size, conffp);
716
492
      if(sret == -1){
717
493
        break;
718
494
      }
719
 
      
 
495
 
720
496
      line = org_line;
721
497
      arg = strsep(&line, comment_delim);
722
498
      while((p = strsep(&arg, whitespace_delims)) != NULL){
725
501
        }
726
502
        new_arg = strdup(p);
727
503
        if(new_arg == NULL){
728
 
          error(0, errno, "strdup");
729
 
          exitstatus = EX_OSERR;
 
504
          perror("strdup");
 
505
          exitstatus = EXIT_FAILURE;
730
506
          free(org_line);
731
507
          goto fallback;
732
508
        }
733
509
        
734
510
        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
 
          }
 
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;
759
518
        }
760
519
        custom_argv[custom_argc-1] = new_arg;
761
 
        custom_argv[custom_argc] = NULL;
 
520
        custom_argv[custom_argc] = NULL;        
762
521
      }
763
522
    }
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
 
    }
772
523
    free(org_line);
773
 
  } else {
 
524
  } else{
774
525
    /* Check for harmful errors and go to fallback. Other errors might
775
526
       not affect opening plugins */
776
 
    if(errno == EMFILE or errno == ENFILE or errno == ENOMEM){
777
 
      error(0, errno, "fopen");
778
 
      exitstatus = EX_OSERR;
 
527
    if (errno == EMFILE or errno == ENFILE or errno == ENOMEM){
 
528
      perror("fopen");
 
529
      exitstatus = EXIT_FAILURE;
779
530
      goto fallback;
780
531
    }
781
532
  }
782
 
  /* If there were any arguments from the configuration file, pass
783
 
     them to parser as command line arguments */
 
533
 
784
534
  if(custom_argv != NULL){
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);
 
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;
837
540
    }
838
541
  }
839
542
  
840
543
  if(debug){
841
 
    for(plugin *p = plugin_list; p != NULL; p = p->next){
 
544
    for(plugin *p = plugin_list; p != NULL; p=p->next){
842
545
      fprintf(stderr, "Plugin: %s has %d arguments\n",
843
546
              p->name ? p->name : "Global", p->argc - 1);
844
547
      for(char **a = p->argv; *a != NULL; a++){
845
548
        fprintf(stderr, "\tArg: %s\n", *a);
846
549
      }
847
 
      fprintf(stderr, "...and %d environment variables\n", p->envc);
 
550
      fprintf(stderr, "...and %u environment variables\n", p->envc);
848
551
      for(char **a = p->environ; *a != NULL; a++){
849
552
        fprintf(stderr, "\t%s\n", *a);
850
553
      }
851
554
    }
852
555
  }
853
556
  
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
 
  }
892
557
  ret = setuid(uid);
893
 
  if(ret == -1){
894
 
    error(0, errno, "setuid");
895
 
  }
896
 
  
897
 
  /* Open plugin directory with close_on_exec flag */
 
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 */
898
580
  {
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;
 
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;
943
588
      }
944
589
    }
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;
955
590
  }
956
591
  
957
592
  FD_ZERO(&rfds_all);
958
593
  
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)){
 
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)){
980
669
      if(debug){
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);
 
670
        fprintf(stderr, "Ignoring plugin dir entry \"%s\""
 
671
                " with bad type or mode\n", filename);
985
672
      }
986
 
      close(plugin_fd);
987
 
      free(direntries[i]);
 
673
      free(filename);
988
674
      continue;
989
675
    }
990
 
    
991
 
    plugin *p = getplugin(direntries[i]->d_name);
 
676
    plugin *p = getplugin(dirst->d_name, &plugin_list);
992
677
    if(p == NULL){
993
 
      error(0, errno, "getplugin");
994
 
      close(plugin_fd);
995
 
      free(direntries[i]);
 
678
      perror("getplugin");
 
679
      free(filename);
996
680
      continue;
997
681
    }
998
682
    if(p->disabled){
999
683
      if(debug){
1000
684
        fprintf(stderr, "Ignoring disabled plugin \"%s\"\n",
1001
 
                direntries[i]->d_name);
 
685
                dirst->d_name);
1002
686
      }
1003
 
      close(plugin_fd);
1004
 
      free(direntries[i]);
 
687
      free(filename);
1005
688
      continue;
1006
689
    }
1007
690
    {
1008
691
      /* Add global arguments to argument list for this plugin */
1009
 
      plugin *g = getplugin(NULL);
 
692
      plugin *g = getplugin(NULL, &plugin_list);
1010
693
      if(g != NULL){
1011
694
        for(char **a = g->argv + 1; *a != NULL; a++){
1012
695
          if(not add_argument(p, *a)){
1013
 
            error(0, errno, "add_argument");
 
696
            perror("add_argument");
1014
697
          }
1015
698
        }
1016
699
        /* Add global environment variables */
1017
700
        for(char **e = g->environ; *e != NULL; e++){
1018
 
          if(not add_environment(p, *e, false)){
1019
 
            error(0, errno, "add_environment");
 
701
          if(not add_environment(p, *e)){
 
702
            perror("add_environment");
1020
703
          }
1021
704
        }
1022
705
      }
1023
706
    }
1024
 
    /* If this plugin has any environment variables, we need to
1025
 
       duplicate the environment from this process, too. */
 
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. */
1026
710
    if(p->environ[0] != NULL){
1027
711
      for(char **e = environ; *e != NULL; e++){
1028
 
        if(not add_environment(p, *e, false)){
1029
 
          error(0, errno, "add_environment");
 
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");
1030
719
        }
1031
720
      }
1032
721
    }
1033
722
    
1034
723
    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 */
 
724
    ret = pipe(pipefd);
 
725
    if (ret == -1){
 
726
      perror("pipe");
 
727
      exitstatus = EXIT_FAILURE;
 
728
      goto fallback;
 
729
    }
1057
730
    ret = set_cloexec_flag(pipefd[0]);
1058
731
    if(ret < 0){
1059
 
      error(0, errno, "set_cloexec_flag");
1060
 
      close(pipefd[0]);
1061
 
      close(pipefd[1]);
1062
 
      exitstatus = EX_OSERR;
1063
 
      free(direntries[i]);
 
732
      perror("set_cloexec_flag");
 
733
      exitstatus = EXIT_FAILURE;
1064
734
      goto fallback;
1065
735
    }
1066
736
    ret = set_cloexec_flag(pipefd[1]);
1067
737
    if(ret < 0){
1068
 
      error(0, errno, "set_cloexec_flag");
1069
 
      close(pipefd[0]);
1070
 
      close(pipefd[1]);
1071
 
      exitstatus = EX_OSERR;
1072
 
      free(direntries[i]);
 
738
      perror("set_cloexec_flag");
 
739
      exitstatus = EXIT_FAILURE;
1073
740
      goto fallback;
1074
741
    }
1075
 
#endif  /* not O_CLOEXEC */
1076
742
    /* Block SIGCHLD until process is safely in process list */
1077
 
    ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_BLOCK,
1078
 
                                              &sigchld_action.sa_mask,
1079
 
                                              NULL));
 
743
    ret = sigprocmask (SIG_BLOCK, &sigchld_action.sa_mask, NULL);
1080
744
    if(ret < 0){
1081
 
      error(0, errno, "sigprocmask");
1082
 
      exitstatus = EX_OSERR;
1083
 
      free(direntries[i]);
 
745
      perror("sigprocmask");
 
746
      exitstatus = EXIT_FAILURE;
1084
747
      goto fallback;
1085
748
    }
1086
 
    /* Starting a new process to be watched */
1087
 
    pid_t pid;
1088
 
    do {
1089
 
      pid = fork();
1090
 
    } while(pid == -1 and errno == EINTR);
 
749
    // Starting a new process to be watched
 
750
    pid_t pid = fork();
1091
751
    if(pid == -1){
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]);
 
752
      perror("fork");
 
753
      exitstatus = EXIT_FAILURE;
1099
754
      goto fallback;
1100
755
    }
1101
756
    if(pid == 0){
1102
757
      /* this is the child process */
1103
758
      ret = sigaction(SIGCHLD, &old_sigchld_action, NULL);
1104
759
      if(ret < 0){
1105
 
        error(0, errno, "sigaction");
1106
 
        _exit(EX_OSERR);
 
760
        perror("sigaction");
 
761
        _exit(EXIT_FAILURE);
1107
762
      }
1108
 
      ret = sigprocmask(SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
 
763
      ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
1109
764
      if(ret < 0){
1110
 
        error(0, errno, "sigprocmask");
1111
 
        _exit(EX_OSERR);
 
765
        perror("sigprocmask");
 
766
        _exit(EXIT_FAILURE);
1112
767
      }
1113
 
      
 
768
 
1114
769
      ret = dup2(pipefd[1], STDOUT_FILENO); /* replace our stdout */
1115
770
      if(ret == -1){
1116
 
        error(0, errno, "dup2");
1117
 
        _exit(EX_OSERR);
 
771
        perror("dup2");
 
772
        _exit(EXIT_FAILURE);
1118
773
      }
1119
774
      
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);
 
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
        }
1126
790
      }
1127
791
      /* no return */
1128
792
    }
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)));
 
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);
1138
800
      if(ret < 0){
1139
 
        error(0, errno, "sigprocmask");
 
801
        perror("sigprocmask");
1140
802
      }
1141
 
      exitstatus = EX_OSERR;
1142
 
      free(direntries[i]);
 
803
      exitstatus = EXIT_FAILURE;
1143
804
      goto fallback;
1144
805
    }
1145
 
    free(direntries[i]);
1146
806
    
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
 
 
 
807
    *new_process = (struct process){ .pid = pid,
 
808
                                     .fd = pipefd[0],
 
809
                                     .next = process_list };
 
810
    // List handling
 
811
    process_list = new_process;
1155
812
    /* Unblock SIGCHLD so signal handler can be run if this process
1156
813
       has already completed */
1157
 
    ret = (int)TEMP_FAILURE_RETRY(sigprocmask(SIG_UNBLOCK,
1158
 
                                              &sigchld_action.sa_mask,
1159
 
                                              NULL));
 
814
    ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
1160
815
    if(ret < 0){
1161
 
      error(0, errno, "sigprocmask");
1162
 
      exitstatus = EX_OSERR;
 
816
      perror("sigprocmask");
 
817
      exitstatus = EXIT_FAILURE;
1163
818
      goto fallback;
1164
819
    }
1165
820
    
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){
 
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){
1192
841
    fd_set rfds = rfds_all;
1193
842
    int select_ret = select(maxfd+1, &rfds, NULL, NULL, NULL);
1194
 
    if(select_ret == -1 and errno != EINTR){
1195
 
      error(0, errno, "select");
1196
 
      exitstatus = EX_OSERR;
 
843
    if (select_ret == -1){
 
844
      perror("select");
 
845
      exitstatus = EXIT_FAILURE;
1197
846
      goto fallback;
1198
847
    }
1199
848
    /* OK, now either a process completed, or something can be read
1200
849
       from one of them */
1201
 
    for(plugin *proc = plugin_list; proc != NULL;){
 
850
    for(process *proc = process_list; proc ; proc = proc->next){
1202
851
      /* Is this process completely done? */
1203
 
      if(proc->completed and proc->eof){
 
852
      if(proc->eof and proc->completed){
1204
853
        /* Only accept the plugin output if it exited cleanly */
1205
854
        if(not WIFEXITED(proc->status)
1206
855
           or WEXITSTATUS(proc->status) != 0){
1207
856
          /* Bad exit by plugin */
1208
 
          
1209
857
          if(debug){
1210
858
            if(WIFEXITED(proc->status)){
1211
 
              fprintf(stderr, "Plugin %s [%" PRIdMAX "] exited with"
1212
 
                      " status %d\n", proc->name,
1213
 
                      (intmax_t) (proc->pid),
 
859
              fprintf(stderr, "Plugin %u exited with status %d\n",
 
860
                      (unsigned int) (proc->pid),
1214
861
                      WEXITSTATUS(proc->status));
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)));
 
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));
1221
869
            }
1222
870
          }
1223
 
          
1224
871
          /* Remove the plugin */
1225
872
          FD_CLR(proc->fd, &rfds_all);
1226
 
          
1227
873
          /* Block signal while modifying process_list */
1228
 
          ret = (int)TEMP_FAILURE_RETRY(sigprocmask
1229
 
                                        (SIG_BLOCK,
1230
 
                                         &sigchld_action.sa_mask,
1231
 
                                         NULL));
 
874
          ret = sigprocmask(SIG_BLOCK, &sigchld_action.sa_mask, NULL);
1232
875
          if(ret < 0){
1233
 
            error(0, errno, "sigprocmask");
1234
 
            exitstatus = EX_OSERR;
 
876
            perror("sigprocmask");
 
877
            exitstatus = EXIT_FAILURE;
1235
878
            goto fallback;
1236
879
          }
1237
 
          
1238
 
          plugin *next_plugin = proc->next;
1239
 
          free_plugin(proc);
1240
 
          proc = next_plugin;
1241
 
          
 
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
          }
1242
893
          /* We are done modifying process list, so unblock signal */
1243
 
          ret = (int)(TEMP_FAILURE_RETRY
1244
 
                      (sigprocmask(SIG_UNBLOCK,
1245
 
                                   &sigchld_action.sa_mask, NULL)));
 
894
          ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask,
 
895
                             NULL);
1246
896
          if(ret < 0){
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;
 
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;
1257
905
        }
1258
 
        
1259
906
        /* This process exited nicely, so print its buffer */
1260
 
        
 
907
 
1261
908
        bool bret = print_out_password(proc->buffer,
1262
909
                                       proc->buffer_length);
1263
910
        if(not bret){
1264
 
          error(0, errno, "print_out_password");
1265
 
          exitstatus = EX_IOERR;
 
911
          perror("print_out_password");
 
912
          exitstatus = EXIT_FAILURE;
1266
913
        }
1267
914
        goto fallback;
1268
915
      }
1269
 
      
1270
916
      /* This process has not completed.  Does it have any output? */
1271
917
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){
1272
918
        /* This process had nothing to say at this time */
1273
 
        proc = proc->next;
1274
919
        continue;
1275
920
      }
1276
921
      /* Before reading, make the process' data buffer large enough */
1277
922
      if(proc->buffer_length + BUFFER_SIZE > proc->buffer_size){
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;
 
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;
1283
928
          goto fallback;
1284
929
        }
1285
 
        proc->buffer = new_buffer;
1286
930
        proc->buffer_size += BUFFER_SIZE;
1287
931
      }
1288
932
      /* Read from the process */
1289
 
      sret = TEMP_FAILURE_RETRY(read(proc->fd,
1290
 
                                     proc->buffer
1291
 
                                     + proc->buffer_length,
1292
 
                                     BUFFER_SIZE));
1293
 
      if(sret < 0){
 
933
      ret = read(proc->fd, proc->buffer + proc->buffer_length,
 
934
                 BUFFER_SIZE);
 
935
      if(ret < 0){
1294
936
        /* Read error from this process; ignore the error */
1295
 
        proc = proc->next;
1296
937
        continue;
1297
938
      }
1298
 
      if(sret == 0){
 
939
      if(ret == 0){
1299
940
        /* got EOF */
1300
941
        proc->eof = true;
1301
942
      } else {
1302
 
        proc->buffer_length += (size_t) sret;
 
943
        proc->buffer_length += (size_t) ret;
1303
944
      }
1304
945
    }
1305
946
  }
1306
 
  
1307
 
  
 
947
 
 
948
 
1308
949
 fallback:
1309
950
  
1310
 
  if(plugin_list == NULL or (exitstatus != EXIT_SUCCESS
1311
 
                             and exitstatus != EX_OK)){
 
951
  if(process_list == NULL or exitstatus != EXIT_SUCCESS){
1312
952
    /* Fallback if all plugins failed, none are found or an error
1313
953
       occured */
1314
954
    bool bret;
1315
955
    fprintf(stderr, "Going to fallback mode using getpass(3)\n");
1316
956
    char *passwordbuffer = getpass("Password: ");
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);
 
957
    bret = print_out_password(passwordbuffer, strlen(passwordbuffer));
1324
958
    if(not bret){
1325
 
      error(0, errno, "print_out_password");
1326
 
      exitstatus = EX_IOERR;
 
959
      perror("print_out_password");
 
960
      exitstatus = EXIT_FAILURE;
1327
961
    }
1328
962
  }
1329
963
  
1330
964
  /* Restore old signal handler */
1331
965
  ret = sigaction(SIGCHLD, &old_sigchld_action, NULL);
1332
966
  if(ret == -1){
1333
 
    error(0, errno, "sigaction");
1334
 
    exitstatus = EX_OSERR;
 
967
    perror("sigaction");
 
968
    exitstatus = EXIT_FAILURE;
1335
969
  }
1336
 
  
 
970
 
1337
971
  if(custom_argv != NULL){
1338
972
    for(char **arg = custom_argv+1; *arg != NULL; arg++){
1339
973
      free(*arg);
1340
974
    }
1341
975
    free(custom_argv);
1342
976
  }
1343
 
  
1344
 
  free(direntries);
1345
 
  
1346
 
  if(dir_fd != -1){
1347
 
    close(dir_fd);
 
977
  free_plugin_list(plugin_list);
 
978
  
 
979
  if(dir != NULL){
 
980
    closedir(dir);
1348
981
  }
1349
982
  
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
 
      }
 
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");
1359
991
    }
 
992
    free(process_list->buffer);
 
993
    free(process_list);
1360
994
  }
1361
995
  
1362
996
  /* Wait for any remaining child processes to terminate */
1363
 
  do {
 
997
  do{
1364
998
    ret = wait(NULL);
1365
999
  } while(ret >= 0);
1366
1000
  if(errno != ECHILD){
1367
 
    error(0, errno, "wait");
 
1001
    perror("wait");
1368
1002
  }
1369
 
  
1370
 
  free_plugin_list();
1371
 
  
 
1003
 
1372
1004
  free(plugindir);
1373
 
  free(pluginhelperdir);
1374
1005
  free(argfile);
1375
1006
  
1376
1007
  return exitstatus;