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