/mandos/trunk

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

« back to all changes in this revision

Viewing changes to plugin-runner.c

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

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

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

Show diffs side-by-side

added added

removed removed

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