/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 plugbasedclient.c

  • Committer: Björn Påhlsson
  • Date: 2008-07-20 02:52:20 UTC
  • Revision ID: belorn@braxen-20080720025220-r5u0388uy9iu23h6
Added following support:
Pluginbased client handler
rewritten Mandos client
       Avahi instead of udp server discovery
       openpgp encrypted key support
Passprompt stand alone application for direct console input
Added logging for Mandos server

Show diffs side-by-side

added added

removed removed

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