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