/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-16 03:29:08 UTC
  • Revision ID: teddy@fukt.bsnet.se-20080816032908-ihw7c05r2mnyk389
Add feature to specify custom environment variables for plugins.

* plugin-runner.c (plugin): New members "environ" and "envc" to
                            contain possible custom environment.
  (getplugin): Return NULL on failure instead of doing exit(); all
               callers changed.
  (add_to_char_array): New helper function for "add_argument" and
                       "add_environment".
  (addargument): Renamed to "add_argument".  Return bool.  Call
                 "add_to_char_array" to actually do things.
  (add_environment): New; analogous to "add_argument".
  (addcustomargument): Renamed to "add_to_argv" to avoid confusion
                       with "add_argument".
  (main): New options "--global-envs" and "--envs-for" to specify
          custom environment for plugins.  Print environment for
          plugins in debug mode.  Use asprintf instead of strcpy and
          strcat.  Use execve() for plugins with custom environments.
          Free environment for plugin when freeing plugin list.

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