/mandos/trunk

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

« back to all changes in this revision

Viewing changes to plugin-runner.c

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

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

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

Show diffs side-by-side

added added

removed removed

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