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