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