/mandos/release

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

« back to all changes in this revision

Viewing changes to plugin-runner.c

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