/mandos/release

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

« back to all changes in this revision

Viewing changes to plugbasedclient.c

  • Committer: Teddy Hogeborn
  • Date: 2008-08-02 10:48:24 UTC
  • Revision ID: teddy@fukt.bsnet.se-20080802104824-fx0miwp9o4g9r31e
* plugbasedclient.c (struct process): New fields "eof", "completed",
                                      and "status".
  (handle_sigchld): New function.
  (main): Initialize "dir" to NULL to only closedir() it if necessary.
          Move "process_list" to be a global variable to be accessible
          by "handle_sigchld".  Make "handle_sigchld" handle SIGCHLD.
          Remove redundant check for NULL "dir".  Free "filename" when
          no longer used.  Block SIGCHLD around fork()/exec().
          Restore normal signals in child.  Only loop while running
          processes exist.  Print process buffer when the process is
          done and it has emitted EOF, not when it only emits EOF.
          Remove processes from list which exit non-cleanly.  In
          cleaning up, closedir() if necessary.  Bug fix: set next
          pointer correctly when freeing process list.

* plugins.d/passprompt.c (main): Do not ignore SIGQUIT.

Show diffs side-by-side

added added

removed removed

Lines of Context:
21
21
 * Contact the authors at <mandos@fukt.bsnet.se>.
22
22
 */
23
23
 
24
 
#define _GNU_SOURCE             /* TEMP_FAILURE_RETRY(), getline(),
25
 
                                   asprintf() */
26
 
#include <stddef.h>             /* size_t, NULL */
27
 
#include <stdlib.h>             /* malloc(), exit(), EXIT_FAILURE,
28
 
                                   EXIT_SUCCESS, realloc() */
29
 
#include <stdbool.h>            /* bool, true, false */
30
 
#include <stdio.h>              /* perror, popen(), fileno(),
31
 
                                   fprintf(), stderr, STDOUT_FILENO */
32
 
#include <sys/types.h>          /* DIR, opendir(), stat(), struct
33
 
                                   stat, waitpid(), WIFEXITED(),
34
 
                                   WEXITSTATUS(), wait(), pid_t,
35
 
                                   uid_t, gid_t, getuid(), getgid(),
36
 
                                   dirfd() */
37
 
#include <sys/select.h>         /* fd_set, select(), FD_ZERO(),
38
 
                                   FD_SET(), FD_ISSET(), FD_CLR */
39
 
#include <sys/wait.h>           /* wait(), waitpid(), WIFEXITED(),
40
 
                                   WEXITSTATUS() */
41
 
#include <sys/stat.h>           /* struct stat, stat(), S_ISREG() */
 
24
#define _GNU_SOURCE             /* TEMP_FAILURE_RETRY() */
 
25
 
 
26
#include <stdio.h>              /* popen(), fileno(), fprintf(),
 
27
                                   stderr, STDOUT_FILENO */
42
28
#include <iso646.h>             /* and, or, not */
 
29
#include <sys/types.h>          /* DIR, opendir(), stat(),
 
30
                                   struct stat, waitpid(),
 
31
                                   WIFEXITED(), WEXITSTATUS(),
 
32
                                   wait() */
 
33
#include <sys/wait.h>           /* wait() */
43
34
#include <dirent.h>             /* DIR, struct dirent, opendir(),
44
 
                                   readdir(), closedir(), dirfd() */
 
35
                                   readdir(), closedir() */
 
36
#include <sys/stat.h>           /* struct stat, stat(), S_ISREG() */
45
37
#include <unistd.h>             /* struct stat, stat(), S_ISREG(),
46
 
                                   fcntl(), setuid(), setgid(),
47
 
                                   F_GETFD, F_SETFD, FD_CLOEXEC,
48
 
                                   access(), pipe(), fork(), close()
49
 
                                   dup2, STDOUT_FILENO, _exit(),
50
 
                                   execv(), write(), read(),
51
 
                                   close() */
52
 
#include <fcntl.h>              /* fcntl(), F_GETFD, F_SETFD,
53
 
                                   FD_CLOEXEC */
54
 
#include <string.h>             /* strsep, strlen(), asprintf() */
 
38
                                   fcntl() */
 
39
#include <fcntl.h>              /* fcntl() */
 
40
#include <stddef.h>             /* NULL */
 
41
#include <stdlib.h>             /* EXIT_FAILURE */
 
42
#include <sys/select.h>         /* fd_set, select(), FD_ZERO(),
 
43
                                   FD_SET(), FD_ISSET() */
 
44
#include <string.h>             /* strlen(), strcpy(), strcat() */
 
45
#include <stdbool.h>            /* true */
 
46
#include <sys/wait.h>           /* waitpid(), WIFEXITED(),
 
47
                                   WEXITSTATUS() */
55
48
#include <errno.h>              /* errno */
56
 
#include <argp.h>               /* struct argp_option, struct
57
 
                                   argp_state, struct argp,
58
 
                                   argp_parse(), ARGP_ERR_UNKNOWN,
59
 
                                   ARGP_KEY_END, ARGP_KEY_ARG,
60
 
                                   error_t */
61
 
#include <signal.h>             /* struct sigaction, sigemptyset(),
62
 
                                   sigaddset(), sigaction(),
63
 
                                   sigprocmask(), SIG_BLOCK, SIGCHLD,
64
 
                                   SIG_UNBLOCK, kill() */
65
 
#include <errno.h>              /* errno, EBADF */
66
 
 
67
 
#define BUFFER_SIZE 256
68
 
 
69
 
#define PDIR "/lib/mandos/plugins.d"
70
 
#define AFILE "/conf/conf.d/mandos/plugin-runner.conf"
71
 
 
72
 
const char *argp_program_version = "plugin-runner 1.0";
73
 
const char *argp_program_bug_address = "<mandos@fukt.bsnet.se>";
74
 
 
75
 
struct plugin;
76
 
 
77
 
typedef struct plugin{
78
 
  char *name;                   /* can be NULL or any plugin name */
79
 
  char **argv;
80
 
  int argc;
81
 
  char **environ;
82
 
  int envc;
83
 
  bool disabled;
84
 
 
85
 
  /* Variables used for running processes*/
 
49
#include <argp.h>               /* struct argp_option,
 
50
                                   struct argp_state, struct argp,
 
51
                                   argp_parse() */
 
52
 
 
53
struct process;
 
54
 
 
55
typedef struct process{
86
56
  pid_t pid;
87
57
  int fd;
88
58
  char *buffer;
89
59
  size_t buffer_size;
90
60
  size_t buffer_length;
91
61
  bool eof;
92
 
  volatile bool completed;
93
 
  volatile int status;
 
62
  bool completed;
 
63
  int status;
 
64
  struct process *next;
 
65
} process;
 
66
 
 
67
typedef struct plugin{
 
68
  char *name;                   /* can be NULL or any plugin name */
 
69
  char **argv;
 
70
  int argc;
 
71
  bool disabled;
94
72
  struct plugin *next;
95
73
} plugin;
96
74
 
97
 
static plugin *plugin_list = NULL;
98
 
 
99
 
/* Gets a existing plugin based on name,
100
 
   or if none is found, creates a new one */
101
 
static plugin *getplugin(char *name){
102
 
  /* Check for exiting plugin with that name */
103
 
  for (plugin *p = plugin_list; p != NULL; p = p->next){
 
75
plugin *getplugin(char *name, plugin **plugin_list){
 
76
  for (plugin *p = *plugin_list; p != NULL; p = p->next){
104
77
    if ((p->name == name)
105
78
        or (p->name and name and (strcmp(p->name, name) == 0))){
106
79
      return p;
109
82
  /* Create a new plugin */
110
83
  plugin *new_plugin = malloc(sizeof(plugin));
111
84
  if (new_plugin == NULL){
112
 
    return NULL;
113
 
  }
114
 
  char *copy_name = NULL;
115
 
  if(name != NULL){
116
 
    copy_name = strdup(name);
117
 
    if(copy_name == NULL){
118
 
      return NULL;
119
 
    }
120
 
  }
121
 
 
122
 
  *new_plugin = (plugin) { .name = copy_name,
123
 
                           .argc = 1,
124
 
                           .disabled = false,
125
 
                           .next = plugin_list };
126
 
  
 
85
    perror("malloc");
 
86
    exit(EXIT_FAILURE);
 
87
  }
 
88
  new_plugin->name = name;
127
89
  new_plugin->argv = malloc(sizeof(char *) * 2);
128
90
  if (new_plugin->argv == NULL){
129
 
    free(copy_name);
130
 
    free(new_plugin);
131
 
    return NULL;
 
91
    perror("malloc");
 
92
    exit(EXIT_FAILURE);
132
93
  }
133
 
  new_plugin->argv[0] = copy_name;
 
94
  new_plugin->argv[0] = name;
134
95
  new_plugin->argv[1] = NULL;
135
 
 
136
 
  new_plugin->environ = malloc(sizeof(char *));
137
 
  if(new_plugin->environ == NULL){
138
 
    free(copy_name);
139
 
    free(new_plugin->argv);
140
 
    free(new_plugin);
141
 
    return NULL;
142
 
  }
143
 
  new_plugin->environ[0] = NULL;
144
 
 
 
96
  new_plugin->argc = 1;
 
97
  new_plugin->disabled = false;
 
98
  new_plugin->next = *plugin_list;
145
99
  /* Append the new plugin to the list */
146
 
  plugin_list = new_plugin;
 
100
  *plugin_list = new_plugin;
147
101
  return new_plugin;
148
102
}
149
103
 
150
 
/* Helper function for add_argument and add_environment */
151
 
static bool add_to_char_array(const char *new, char ***array,
152
 
                              int *len){
153
 
  /* Resize the pointed-to array to hold one more pointer */
154
 
  *array = realloc(*array, sizeof(char *)
155
 
                   * (size_t) ((*len) + 2));
156
 
  /* Malloc check */
157
 
  if(*array == NULL){
158
 
    return false;
159
 
  }
160
 
  /* Make a copy of the new string */
161
 
  char *copy = strdup(new);
162
 
  if(copy == NULL){
163
 
    return false;
164
 
  }
165
 
  /* Insert the copy */
166
 
  (*array)[*len] = copy;
167
 
  (*len)++;
168
 
  /* Add a new terminating NULL pointer to the last element */
169
 
  (*array)[*len] = NULL;
170
 
  return true;
171
 
}
172
 
 
173
 
/* Add to a plugin's argument vector */
174
 
static bool add_argument(plugin *p, const char *arg){
175
 
  if(p == NULL){
176
 
    return false;
177
 
  }
178
 
  return add_to_char_array(arg, &(p->argv), &(p->argc));
179
 
}
180
 
 
181
 
/* Add to a plugin's environment */
182
 
static bool add_environment(plugin *p, const char *def){
183
 
  if(p == NULL){
184
 
    return false;
185
 
  }
186
 
  return add_to_char_array(def, &(p->environ), &(p->envc));
 
104
void addargument(plugin *p, char *arg){
 
105
  p->argv[p->argc] = arg;
 
106
  p->argv = realloc(p->argv, sizeof(char *) * (size_t)(p->argc + 2));
 
107
  if (p->argv == NULL){
 
108
    perror("malloc");
 
109
    exit(EXIT_FAILURE);
 
110
  }
 
111
  p->argc++;
 
112
  p->argv[p->argc] = NULL;
187
113
}
188
114
 
189
115
/*
191
117
 * Descriptor Flags".
192
118
 * *Note File Descriptor Flags:(libc)Descriptor Flags.
193
119
 */
194
 
static int set_cloexec_flag(int fd)
 
120
int set_cloexec_flag(int fd)
195
121
{
196
122
  int ret = fcntl(fd, F_GETFD, 0);
197
123
  /* If reading the flags failed, return error indication now. */
202
128
  return fcntl(fd, F_SETFD, ret | FD_CLOEXEC);
203
129
}
204
130
 
205
 
 
206
 
/* Mark processes as completed when they exit, and save their exit
 
131
#define BUFFER_SIZE 256
 
132
 
 
133
const char *argp_program_version = "plugbasedclient 0.9";
 
134
const char *argp_program_bug_address = "<mandos@fukt.bsnet.se>";
 
135
 
 
136
process *process_list = NULL;
 
137
 
 
138
/* Mark a process as completed when it exits, and save its exit
207
139
   status. */
208
140
void handle_sigchld(__attribute__((unused)) int sig){
209
 
  while(true){
210
 
    plugin *proc = plugin_list;
211
 
    int status;
212
 
    pid_t pid = waitpid(-1, &status, WNOHANG);
213
 
    if(pid == 0){
214
 
      /* Only still running child processes */
215
 
      break;
216
 
    }
217
 
    if(pid == -1){
218
 
      if (errno != ECHILD){
219
 
        perror("waitpid");
220
 
      }
221
 
      /* No child processes */
222
 
      break;
223
 
    }
224
 
 
225
 
    /* A child exited, find it in process_list */
226
 
    while(proc != NULL and proc->pid != pid){
227
 
      proc = proc->next;
228
 
    }
229
 
    if(proc == NULL){
230
 
      /* Process not found in process list */
231
 
      continue;
232
 
    }
233
 
    proc->status = status;
234
 
    proc->completed = true;
235
 
  }
236
 
}
237
 
 
238
 
/* Prints out a password to stdout */
239
 
bool print_out_password(const char *buffer, size_t length){
240
 
  ssize_t ret;
241
 
  if(length>0 and buffer[length-1] == '\n'){
242
 
    length--;
243
 
  }
244
 
  for(size_t written = 0; written < length; written += (size_t)ret){
245
 
    ret = TEMP_FAILURE_RETRY(write(STDOUT_FILENO, buffer + written,
246
 
                                   length - written));
247
 
    if(ret < 0){
248
 
      return false;
249
 
    }
250
 
  }
251
 
  return true;
252
 
}
253
 
 
254
 
/* Removes and free a plugin from the plugin list */
255
 
static void free_plugin(plugin *plugin_node){
256
 
  
257
 
  for(char **arg = plugin_node->argv; *arg != NULL; arg++){
258
 
    free(*arg);
259
 
  }
260
 
  free(plugin_node->argv);
261
 
  for(char **env = plugin_node->environ; *env != NULL; env++){
262
 
    free(*env);
263
 
  }
264
 
  free(plugin_node->environ);
265
 
  free(plugin_node->buffer);
266
 
 
267
 
  /* Removes the plugin from the singly-linked list */
268
 
  if(plugin_node == plugin_list){
269
 
    /* First one - simple */
270
 
    plugin_list = plugin_list->next;
271
 
  } else {
272
 
    /* Second one or later */
273
 
    for(plugin *p = plugin_list; p != NULL; p = p->next){
274
 
      if(p->next == plugin_node){
275
 
        p->next = plugin_node->next;
276
 
        break;
277
 
      }
278
 
    }
279
 
  }
280
 
  
281
 
  free(plugin_node);
282
 
}
283
 
 
284
 
static void free_plugin_list(void){
285
 
  while(plugin_list != NULL){
286
 
    free_plugin(plugin_list);
287
 
  }
 
141
  process *proc = process_list;
 
142
  int status;
 
143
  pid_t pid = wait(&status);
 
144
  while(proc != NULL and proc->pid != pid){
 
145
    proc = proc->next;    
 
146
  }
 
147
  if(proc == NULL){
 
148
    /* Process not found in process list */
 
149
    return;
 
150
  }
 
151
  proc->status = status;
 
152
  proc->completed = true;
288
153
}
289
154
 
290
155
int main(int argc, char *argv[]){
291
 
  char *plugindir = NULL;
292
 
  char *argfile = NULL;
293
 
  FILE *conffp;
 
156
  const char *plugindir = "/conf/conf.d/mandos/plugins.d";
294
157
  size_t d_name_len;
295
158
  DIR *dir = NULL;
296
159
  struct dirent *dirst;
297
160
  struct stat st;
298
161
  fd_set rfds_all;
299
162
  int ret, maxfd = 0;
300
 
  uid_t uid = 65534;
301
 
  gid_t gid = 65534;
302
163
  bool debug = false;
303
164
  int exitstatus = EXIT_SUCCESS;
304
 
  struct sigaction old_sigchld_action;
305
 
  struct sigaction sigchld_action = { .sa_handler = handle_sigchld,
306
 
                                      .sa_flags = SA_NOCLDSTOP };
307
 
  char **custom_argv = NULL;
308
 
  int custom_argc = 0;
309
165
  
310
166
  /* Establish a signal handler */
 
167
  struct sigaction old_sigchld_action,
 
168
    sigchld_action = { .sa_handler = handle_sigchld,
 
169
                       .sa_flags = SA_NOCLDSTOP };
311
170
  sigemptyset(&sigchld_action.sa_mask);
312
171
  ret = sigaddset(&sigchld_action.sa_mask, SIGCHLD);
313
 
  if(ret == -1){
 
172
  if(ret < 0){
314
173
    perror("sigaddset");
315
 
    exitstatus = EXIT_FAILURE;
316
 
    goto fallback;
 
174
    exit(EXIT_FAILURE);
317
175
  }
318
176
  ret = sigaction(SIGCHLD, &sigchld_action, &old_sigchld_action);
319
 
  if(ret == -1){
 
177
  if(ret < 0){
320
178
    perror("sigaction");
321
 
    exitstatus = EXIT_FAILURE;
322
 
    goto fallback;
 
179
    exit(EXIT_FAILURE);
323
180
  }
324
181
  
325
182
  /* The options we understand. */
327
184
    { .name = "global-options", .key = 'g',
328
185
      .arg = "OPTION[,OPTION[,...]]",
329
186
      .doc = "Options passed to all plugins" },
330
 
    { .name = "global-envs", .key = 'e',
331
 
      .arg = "VAR=value",
332
 
      .doc = "Environment variable passed to all plugins" },
333
187
    { .name = "options-for", .key = 'o',
334
188
      .arg = "PLUGIN:OPTION[,OPTION[,...]]",
335
189
      .doc = "Options passed only to specified plugin" },
336
 
    { .name = "envs-for", .key = 'f',
337
 
      .arg = "PLUGIN:ENV=value",
338
 
      .doc = "Environment variable passed to specified plugin" },
339
190
    { .name = "disable", .key = 'd',
340
191
      .arg = "PLUGIN",
341
192
      .doc = "Disable a specific plugin", .group = 1 },
342
193
    { .name = "plugin-dir", .key = 128,
343
194
      .arg = "DIRECTORY",
344
195
      .doc = "Specify a different plugin directory", .group = 2 },
345
 
    { .name = "config-file", .key = 129,
346
 
      .arg = "FILE",
347
 
      .doc = "Specify a different configuration file", .group = 2 },
348
 
    { .name = "userid", .key = 130,
349
 
      .arg = "ID", .flags = 0,
350
 
      .doc = "User ID the plugins will run as", .group = 3 },
351
 
    { .name = "groupid", .key = 131,
352
 
      .arg = "ID", .flags = 0,
353
 
      .doc = "Group ID the plugins will run as", .group = 3 },
354
 
    { .name = "debug", .key = 132,
355
 
      .doc = "Debug mode", .group = 4 },
 
196
    { .name = "debug", .key = 129,
 
197
      .doc = "Debug mode", .group = 3 },
356
198
    { .name = NULL }
357
199
  };
358
200
  
359
 
  error_t parse_opt (int key, char *arg, __attribute__((unused)) struct argp_state *state) {
 
201
  error_t parse_opt (int key, char *arg, struct argp_state *state) {
360
202
    /* Get the INPUT argument from `argp_parse', which we know is a
361
203
       pointer to our plugin list pointer. */
 
204
    plugin **plugins = state->input;
362
205
    switch (key) {
363
 
    case 'g':                   /* --global-options */
364
 
      if (arg != NULL){
365
 
        char *p;
366
 
        while((p = strsep(&arg, ",")) != NULL){
367
 
          if(p[0] == '\0'){
368
 
            continue;
369
 
          }
370
 
          if(not add_argument(getplugin(NULL), p)){
371
 
            perror("add_argument");
372
 
            return ARGP_ERR_UNKNOWN;
373
 
          }
374
 
        }
375
 
      }
376
 
      break;
377
 
    case 'e':                   /* --global-envs */
378
 
      if(arg == NULL){
379
 
        break;
380
 
      }
381
 
      {
382
 
        char *envdef = strdup(arg);
383
 
        if(envdef == NULL){
384
 
          break;
385
 
        }
386
 
        if(not add_environment(getplugin(NULL), envdef)){
387
 
          perror("add_environment");
388
 
        }
389
 
      }
390
 
      break;
391
 
    case 'o':                   /* --options-for */
392
 
      if (arg != NULL){
393
 
        char *p_name = strsep(&arg, ":");
394
 
        if(p_name[0] == '\0'){
395
 
          break;
396
 
        }
397
 
        char *opt = strsep(&arg, ":");
398
 
        if(opt[0] == '\0'){
399
 
          break;
400
 
        }
401
 
        if(opt != NULL){
402
 
          char *p;
403
 
          while((p = strsep(&opt, ",")) != NULL){
404
 
            if(p[0] == '\0'){
405
 
              continue;
406
 
            }
407
 
            if(not add_argument(getplugin(p_name), p)){
408
 
              perror("add_argument");
409
 
              return ARGP_ERR_UNKNOWN;
410
 
            }
411
 
          }
412
 
        }
413
 
      }
414
 
      break;
415
 
    case 'f':                   /* --envs-for */
416
 
      if(arg == NULL){
417
 
        break;
418
 
      }
419
 
      {
420
 
        char *envdef = strchr(arg, ':');
421
 
        if(envdef == NULL){
422
 
          break;
423
 
        }
424
 
        char *p_name = strndup(arg, (size_t) (envdef-arg));
425
 
        if(p_name == NULL){
426
 
          break;
427
 
        }
428
 
        envdef++;
429
 
        if(not add_environment(getplugin(p_name), envdef)){
430
 
          perror("add_environment");
431
 
        }
432
 
      }
433
 
      break;
434
 
    case 'd':                   /* --disable */
435
 
      if (arg != NULL){
436
 
        plugin *p = getplugin(arg);
437
 
        if(p == NULL){
438
 
          return ARGP_ERR_UNKNOWN;
439
 
        }
440
 
        p->disabled = true;
441
 
      }
442
 
      break;
443
 
    case 128:                   /* --plugin-dir */
444
 
      plugindir = strdup(arg);
445
 
      if(plugindir == NULL){
446
 
        perror("strdup");
447
 
      }      
448
 
      break;
449
 
    case 129:                   /* --config-file */
450
 
      argfile = strdup(arg);
451
 
      if(argfile == NULL){
452
 
        perror("strdup");
453
 
      }
454
 
      break;      
455
 
    case 130:                   /* --userid */
456
 
      uid = (uid_t)strtol(arg, NULL, 10);
457
 
      break;
458
 
    case 131:                   /* --groupid */
459
 
      gid = (gid_t)strtol(arg, NULL, 10);
460
 
      break;
461
 
    case 132:                   /* --debug */
 
206
    case 'g':
 
207
      if (arg != NULL){
 
208
        char *p = strtok(arg, ",");
 
209
        do{
 
210
          addargument(getplugin(NULL, plugins), p);
 
211
          p = strtok(NULL, ",");
 
212
        } while (p);
 
213
      }
 
214
      break;
 
215
    case 'o':
 
216
      if (arg != NULL){
 
217
        char *name = strtok(arg, ":");
 
218
        char *p = strtok(NULL, ":");
 
219
        if(p){
 
220
          p = strtok(p, ",");
 
221
          do{
 
222
            addargument(getplugin(name, plugins), p);
 
223
            p = strtok(NULL, ",");
 
224
          } while (p);
 
225
        }
 
226
      }
 
227
      break;
 
228
    case 'd':
 
229
      if (arg != NULL){
 
230
        getplugin(arg, plugins)->disabled = true;
 
231
      }
 
232
      break;
 
233
    case 128:
 
234
      plugindir = arg;
 
235
      break;
 
236
    case 129:
462
237
      debug = true;
463
238
      break;
464
239
    case ARGP_KEY_ARG:
465
 
      fprintf(stderr, "Ignoring unknown argument \"%s\"\n", arg);
 
240
      argp_usage (state);
466
241
      break;
467
242
    case ARGP_KEY_END:
468
243
      break;
472
247
    return 0;
473
248
  }
474
249
  
 
250
  plugin *plugin_list = NULL;
 
251
  
475
252
  struct argp argp = { .options = options, .parser = parse_opt,
476
 
                       .args_doc = "[+PLUS_SEPARATED_OPTIONS]",
 
253
                       .args_doc = "",
477
254
                       .doc = "Mandos plugin runner -- Run plugins" };
478
255
  
479
 
  ret = argp_parse (&argp, argc, argv, 0, 0, NULL);
480
 
  if (ret == ARGP_ERR_UNKNOWN){
481
 
    fprintf(stderr, "Unknown error while parsing arguments\n");
482
 
    exitstatus = EXIT_FAILURE;
483
 
    goto fallback;
484
 
  }
485
 
 
486
 
  /* Opens the configfile if aviable */
487
 
  if (argfile == NULL){
488
 
    conffp = fopen(AFILE, "r");
489
 
  } else {
490
 
    conffp = fopen(argfile, "r");
491
 
  }  
492
 
  if(conffp != NULL){
493
 
    char *org_line = NULL;
494
 
    char *p, *arg, *new_arg, *line;
495
 
    size_t size = 0;
496
 
    ssize_t sret;
497
 
    const char whitespace_delims[] = " \r\t\f\v\n";
498
 
    const char comment_delim[] = "#";
499
 
 
500
 
    custom_argc = 1;
501
 
    custom_argv = malloc(sizeof(char*) * 2);
502
 
    if(custom_argv == NULL){
503
 
      perror("malloc");
504
 
      exitstatus = EXIT_FAILURE;
505
 
      goto fallback;
506
 
    }
507
 
    custom_argv[0] = argv[0];
508
 
    custom_argv[1] = NULL;
509
 
 
510
 
    /* for each line in the config file, strip whitespace and ignore commented text */
511
 
    while(true){
512
 
      sret = getline(&org_line, &size, conffp);
513
 
      if(sret == -1){
514
 
        break;
515
 
      }
516
 
 
517
 
      line = org_line;
518
 
      arg = strsep(&line, comment_delim);
519
 
      while((p = strsep(&arg, whitespace_delims)) != NULL){
520
 
        if(p[0] == '\0'){
521
 
          continue;
522
 
        }
523
 
        new_arg = strdup(p);
524
 
        if(new_arg == NULL){
525
 
          perror("strdup");
526
 
          exitstatus = EXIT_FAILURE;
527
 
          free(org_line);
528
 
          goto fallback;
529
 
        }
530
 
        
531
 
        custom_argc += 1;
532
 
        custom_argv = realloc(custom_argv, sizeof(char *)
533
 
                              * ((unsigned int) custom_argc + 1));
534
 
        if(custom_argv == NULL){
535
 
          perror("realloc");
536
 
          exitstatus = EXIT_FAILURE;
537
 
          free(org_line);
538
 
          goto fallback;
539
 
        }
540
 
        custom_argv[custom_argc-1] = new_arg;
541
 
        custom_argv[custom_argc] = NULL;        
542
 
      }
543
 
    }
544
 
    free(org_line);
545
 
  } else{
546
 
    /* Check for harmful errors and go to fallback. Other errors might
547
 
       not affect opening plugins */
548
 
    if (errno == EMFILE or errno == ENFILE or errno == ENOMEM){
549
 
      perror("fopen");
550
 
      exitstatus = EXIT_FAILURE;
551
 
      goto fallback;
552
 
    }
553
 
  }
554
 
  /* If there was any arguments from configuration file,
555
 
     pass them to parser as command arguments */
556
 
  if(custom_argv != NULL){
557
 
    ret = argp_parse (&argp, custom_argc, custom_argv, 0, 0, NULL);
558
 
    if (ret == ARGP_ERR_UNKNOWN){
559
 
      fprintf(stderr, "Unknown error while parsing arguments\n");
560
 
      exitstatus = EXIT_FAILURE;
561
 
      goto fallback;
562
 
    }
563
 
  }
 
256
  argp_parse (&argp, argc, argv, 0, 0, &plugin_list);
564
257
  
565
258
  if(debug){
566
259
    for(plugin *p = plugin_list; p != NULL; p=p->next){
569
262
      for(char **a = p->argv; *a != NULL; a++){
570
263
        fprintf(stderr, "\tArg: %s\n", *a);
571
264
      }
572
 
      fprintf(stderr, "...and %u environment variables\n", p->envc);
573
 
      for(char **a = p->environ; *a != NULL; a++){
574
 
        fprintf(stderr, "\t%s\n", *a);
575
 
      }
576
265
    }
577
266
  }
578
 
 
579
 
  /* Strip permissions down to nobody */
580
 
  ret = setuid(uid);
581
 
  if (ret == -1){
582
 
    perror("setuid");
583
 
  }  
584
 
  setgid(gid);
585
 
  if (ret == -1){
586
 
    perror("setgid");
587
 
  }
588
 
 
589
 
  if (plugindir == NULL){
590
 
    dir = opendir(PDIR);
591
 
  } else {
592
 
    dir = opendir(plugindir);
593
 
  }
594
267
  
 
268
  dir = opendir(plugindir);
595
269
  if(dir == NULL){
596
270
    perror("Could not open plugin dir");
597
271
    exitstatus = EXIT_FAILURE;
598
 
    goto fallback;
 
272
    goto end;
599
273
  }
600
274
  
601
275
  /* Set the FD_CLOEXEC flag on the directory, if possible */
606
280
      if(ret < 0){
607
281
        perror("set_cloexec_flag");
608
282
        exitstatus = EXIT_FAILURE;
609
 
        goto fallback;
 
283
        goto end;
610
284
      }
611
285
    }
612
286
  }
613
287
  
614
288
  FD_ZERO(&rfds_all);
615
 
 
616
 
  /* Read and execute any executable in the plugin directory*/
 
289
  
617
290
  while(true){
618
291
    dirst = readdir(dir);
619
292
    
620
293
    // All directory entries have been processed
621
294
    if(dirst == NULL){
622
 
      if (errno == EBADF){
623
 
        perror("readdir");
624
 
        exitstatus = EXIT_FAILURE;
625
 
        goto fallback;
626
 
      }
627
295
      break;
628
296
    }
629
297
    
650
318
          break;
651
319
        }
652
320
      }
 
321
      
653
322
      if(bad_name){
654
323
        continue;
655
324
      }
 
325
      
656
326
      for(const char **suf = bad_suffixes; *suf != NULL; suf++){
657
327
        size_t suf_len = strlen(*suf);
658
328
        if((d_name_len >= suf_len)
671
341
        continue;
672
342
      }
673
343
    }
674
 
 
675
 
    char *filename;
676
 
    ret = asprintf(&filename, "%s/%s", plugindir, dirst->d_name);
677
 
    if(ret < 0){
678
 
      perror("asprintf");
679
 
      continue;
680
 
    }
681
 
    
682
 
    ret = stat(filename, &st);
683
 
    if (ret == -1){
684
 
      perror("stat");
685
 
      free(filename);
686
 
      continue;
687
 
    }
688
 
 
689
 
    /* Ignore non-executable files */
 
344
    
 
345
    char *filename = malloc(d_name_len + strlen(plugindir) + 2);
 
346
    if (filename == NULL){
 
347
      perror("malloc");
 
348
      exitstatus = EXIT_FAILURE;
 
349
      goto end;
 
350
    }
 
351
    strcpy(filename, plugindir); /* Spurious warning */
 
352
    strcat(filename, "/");      /* Spurious warning */
 
353
    strcat(filename, dirst->d_name); /* Spurious warning */
 
354
    
 
355
    stat(filename, &st);
 
356
    
690
357
    if (not S_ISREG(st.st_mode) or (access(filename, X_OK) != 0)){
691
358
      if(debug){
692
359
        fprintf(stderr, "Ignoring plugin dir entry \"%s\""
695
362
      free(filename);
696
363
      continue;
697
364
    }
698
 
    
699
 
    plugin *p = getplugin(dirst->d_name);
700
 
    if(p == NULL){
701
 
      perror("getplugin");
702
 
      free(filename);
703
 
      continue;
704
 
    }
705
 
    if(p->disabled){
 
365
    if(getplugin(dirst->d_name, &plugin_list)->disabled){
706
366
      if(debug){
707
367
        fprintf(stderr, "Ignoring disabled plugin \"%s\"\n",
708
368
                dirst->d_name);
710
370
      free(filename);
711
371
      continue;
712
372
    }
 
373
    plugin *p = getplugin(dirst->d_name, &plugin_list);
713
374
    {
714
375
      /* Add global arguments to argument list for this plugin */
715
 
      plugin *g = getplugin(NULL);
716
 
      if(g != NULL){
717
 
        for(char **a = g->argv + 1; *a != NULL; a++){
718
 
          if(not add_argument(p, *a)){
719
 
            perror("add_argument");
720
 
          }
721
 
        }
722
 
        /* Add global environment variables */
723
 
        for(char **e = g->environ; *e != NULL; e++){
724
 
          if(not add_environment(p, *e)){
725
 
            perror("add_environment");
726
 
          }
727
 
        }
728
 
      }
729
 
    }
730
 
    /* If this plugin has any environment variables, we will call
731
 
       using execve and need to duplicate the environment from this
732
 
       process, too. */
733
 
    if(p->environ[0] != NULL){
734
 
      for(char **e = environ; *e != NULL; e++){
735
 
        char *copy = strdup(*e);
736
 
        if(copy == NULL){
737
 
          perror("strdup");
738
 
          continue;
739
 
        }
740
 
        if(not add_environment(p, copy)){
741
 
          perror("add_environment");
742
 
        }
743
 
      }
744
 
    }
745
 
    
746
 
    int pipefd[2];
 
376
      plugin *g = getplugin(NULL, &plugin_list);
 
377
      for(char **a = g->argv + 1; *a != NULL; a++){
 
378
        addargument(p, *a);
 
379
      }
 
380
    }
 
381
    int pipefd[2]; 
747
382
    ret = pipe(pipefd);
748
383
    if (ret == -1){
749
384
      perror("pipe");
750
385
      exitstatus = EXIT_FAILURE;
751
 
      goto fallback;
 
386
      goto end;
752
387
    }
753
 
    /* Ask OS to automatic close the pipe on exec */
754
388
    ret = set_cloexec_flag(pipefd[0]);
755
389
    if(ret < 0){
756
390
      perror("set_cloexec_flag");
757
391
      exitstatus = EXIT_FAILURE;
758
 
      goto fallback;
 
392
      goto end;
759
393
    }
760
394
    ret = set_cloexec_flag(pipefd[1]);
761
395
    if(ret < 0){
762
396
      perror("set_cloexec_flag");
763
397
      exitstatus = EXIT_FAILURE;
764
 
      goto fallback;
 
398
      goto end;
765
399
    }
766
400
    /* Block SIGCHLD until process is safely in process list */
767
401
    ret = sigprocmask (SIG_BLOCK, &sigchld_action.sa_mask, NULL);
768
402
    if(ret < 0){
769
403
      perror("sigprocmask");
770
404
      exitstatus = EXIT_FAILURE;
771
 
      goto fallback;
 
405
      goto end;
772
406
    }
773
407
    // Starting a new process to be watched
774
408
    pid_t pid = fork();
775
 
    if(pid == -1){
776
 
      perror("fork");
777
 
      exitstatus = EXIT_FAILURE;
778
 
      goto fallback;
779
 
    }
780
409
    if(pid == 0){
781
410
      /* this is the child process */
782
411
      ret = sigaction(SIGCHLD, &old_sigchld_action, NULL);
789
418
        perror("sigprocmask");
790
419
        _exit(EXIT_FAILURE);
791
420
      }
792
 
 
793
 
      ret = dup2(pipefd[1], STDOUT_FILENO); /* replace our stdout */
794
 
      if(ret == -1){
795
 
        perror("dup2");
796
 
        _exit(EXIT_FAILURE);
797
 
      }
 
421
      dup2(pipefd[1], STDOUT_FILENO); /* replace our stdout */
798
422
      
799
423
      if(dirfd(dir) < 0){
800
424
        /* If dir has no file descriptor, we could not set FD_CLOEXEC
801
425
           above and must now close it manually here. */
802
426
        closedir(dir);
803
427
      }
804
 
      if(p->environ[0] == NULL){
805
 
        if(execv(filename, p->argv) < 0){
806
 
          perror("execv");
807
 
          _exit(EXIT_FAILURE);
808
 
        }
809
 
      } else {
810
 
        if(execve(filename, p->argv, p->environ) < 0){
811
 
          perror("execve");
812
 
          _exit(EXIT_FAILURE);
813
 
        }
 
428
      if(execv(filename, p->argv) < 0){
 
429
        perror("execv");
 
430
        _exit(EXIT_FAILURE);
814
431
      }
815
432
      /* no return */
816
433
    }
817
 
    /* Parent process */
818
 
    close(pipefd[1]);           /* Close unused write end of pipe */
 
434
    /* parent process */
819
435
    free(filename);
820
 
    plugin *new_plugin = getplugin(dirst->d_name);
821
 
    if (new_plugin == NULL){
822
 
      perror("getplugin");
 
436
    close(pipefd[1]);           /* close unused write end of pipe */
 
437
    process *new_process = malloc(sizeof(process));
 
438
    if (new_process == NULL){
 
439
      perror("malloc");
823
440
      ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
824
441
      if(ret < 0){
825
 
        perror("sigprocmask");
 
442
        perror("sigprocmask");
826
443
      }
827
444
      exitstatus = EXIT_FAILURE;
828
 
      goto fallback;
 
445
      goto end;
829
446
    }
830
447
    
831
 
    new_plugin->pid = pid;
832
 
    new_plugin->fd = pipefd[0];
833
 
    
 
448
    *new_process = (struct process){ .pid = pid,
 
449
                                     .fd = pipefd[0],
 
450
                                     .next = process_list };
 
451
    // List handling
 
452
    process_list = new_process;
834
453
    /* Unblock SIGCHLD so signal handler can be run if this process
835
454
       has already completed */
836
455
    ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
837
456
    if(ret < 0){
838
457
      perror("sigprocmask");
839
458
      exitstatus = EXIT_FAILURE;
840
 
      goto fallback;
841
 
    }
842
 
    
843
 
    FD_SET(new_plugin->fd, &rfds_all);
844
 
    
845
 
    if (maxfd < new_plugin->fd){
846
 
      maxfd = new_plugin->fd;
847
 
    }
848
 
    
 
459
      goto end;
 
460
    }
 
461
    
 
462
    FD_SET(new_process->fd, &rfds_all);
 
463
    
 
464
    if (maxfd < new_process->fd){
 
465
      maxfd = new_process->fd;
 
466
    }
 
467
    
 
468
  }
 
469
  
 
470
  /* Free the plugin list */
 
471
  for(plugin *next; plugin_list != NULL; plugin_list = next){
 
472
    next = plugin_list->next;
 
473
    free(plugin_list->argv);
 
474
    free(plugin_list);
849
475
  }
850
476
  
851
477
  closedir(dir);
852
478
  dir = NULL;
853
 
 
854
 
  for(plugin *p = plugin_list; p != NULL; p = p->next){
855
 
    if(p->pid != 0){
856
 
      break;
857
 
    }
858
 
    if(p->next == NULL){
859
 
      fprintf(stderr, "No plugin processes started. Incorrect plugin"
860
 
              " directory?\n");
861
 
      free_plugin_list();
862
 
    }
 
479
  
 
480
  if (process_list == NULL){
 
481
    fprintf(stderr, "No plugin processes started, exiting\n");
 
482
    exitstatus = EXIT_FAILURE;
 
483
    goto end;
863
484
  }
864
 
 
865
 
  /* Main loop while running plugins exist */
866
 
  while(plugin_list){
 
485
  while(process_list){
867
486
    fd_set rfds = rfds_all;
868
487
    int select_ret = select(maxfd+1, &rfds, NULL, NULL, NULL);
869
488
    if (select_ret == -1){
870
489
      perror("select");
871
490
      exitstatus = EXIT_FAILURE;
872
 
      goto fallback;
 
491
      goto end;
873
492
    }
874
493
    /* OK, now either a process completed, or something can be read
875
494
       from one of them */
876
 
    for(plugin *proc = plugin_list; proc != NULL; proc = proc->next){
 
495
    for(process *proc = process_list; proc ; proc = proc->next){
877
496
      /* Is this process completely done? */
878
497
      if(proc->eof and proc->completed){
879
498
        /* Only accept the plugin output if it exited cleanly */
880
499
        if(not WIFEXITED(proc->status)
881
500
           or WEXITSTATUS(proc->status) != 0){
882
501
          /* Bad exit by plugin */
883
 
 
884
502
          if(debug){
885
503
            if(WIFEXITED(proc->status)){
886
 
              fprintf(stderr, "Plugin %u exited with status %d\n",
887
 
                      (unsigned int) (proc->pid),
888
 
                      WEXITSTATUS(proc->status));
 
504
              fprintf(stderr, "Plugin %d exited with status %d\n",
 
505
                      proc->pid, WEXITSTATUS(proc->status));
889
506
            } else if(WIFSIGNALED(proc->status)) {
890
 
              fprintf(stderr, "Plugin %u killed by signal %d\n",
891
 
                      (unsigned int) (proc->pid),
892
 
                      WTERMSIG(proc->status));
 
507
              fprintf(stderr, "Plugin %d killed by signal %d\n",
 
508
                      proc->pid, WTERMSIG(proc->status));
893
509
            } else if(WCOREDUMP(proc->status)){
894
 
              fprintf(stderr, "Plugin %d dumped core\n",
895
 
                      (unsigned int) (proc->pid));
 
510
              fprintf(stderr, "Plugin %d dumped core\n", proc->pid);
896
511
            }
897
512
          }
898
 
          
899
513
          /* Remove the plugin */
900
514
          FD_CLR(proc->fd, &rfds_all);
901
 
 
902
515
          /* Block signal while modifying process_list */
903
 
          ret = sigprocmask(SIG_BLOCK, &sigchld_action.sa_mask, NULL);
 
516
          ret = sigprocmask (SIG_BLOCK, &sigchld_action.sa_mask, NULL);
904
517
          if(ret < 0){
905
518
            perror("sigprocmask");
906
519
            exitstatus = EXIT_FAILURE;
907
 
            goto fallback;
908
 
          }
909
 
          free_plugin(proc);
 
520
            goto end;
 
521
          }
 
522
          /* Delete this process entry from the list */
 
523
          if(process_list == proc){
 
524
            /* First one - simple */
 
525
            process_list = proc->next;
 
526
          } else {
 
527
            /* Second one or later */
 
528
            for(process *p = process_list; p != NULL; p = p->next){
 
529
              if(p->next == proc){
 
530
                p->next = proc->next;
 
531
                break;
 
532
              }
 
533
            }
 
534
          }
910
535
          /* We are done modifying process list, so unblock signal */
911
536
          ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask,
912
537
                             NULL);
913
538
          if(ret < 0){
914
539
            perror("sigprocmask");
915
 
            exitstatus = EXIT_FAILURE;
916
 
            goto fallback;
917
 
          }
918
 
          
919
 
          if(plugin_list == NULL){
920
 
            break;
921
 
          }
922
 
          continue;
 
540
          }
 
541
          free(proc->buffer);
 
542
          free(proc);
 
543
          /* We deleted this process from the list, so we can't go
 
544
             proc->next.  Therefore, start over from the beginning of
 
545
             the process list */
 
546
          break;
923
547
        }
924
 
        
925
548
        /* This process exited nicely, so print its buffer */
926
 
 
927
 
        bool bret = print_out_password(proc->buffer,
928
 
                                       proc->buffer_length);
929
 
        if(not bret){
930
 
          perror("print_out_password");
931
 
          exitstatus = EXIT_FAILURE;
 
549
        for(size_t written = 0; written < proc->buffer_length;
 
550
            written += (size_t)ret){
 
551
          ret = TEMP_FAILURE_RETRY(write(STDOUT_FILENO,
 
552
                                         proc->buffer + written,
 
553
                                         proc->buffer_length
 
554
                                         - written));
 
555
          if(ret < 0){
 
556
            perror("write");
 
557
            exitstatus = EXIT_FAILURE;
 
558
            goto end;
 
559
          }
932
560
        }
933
 
        goto fallback;
 
561
        goto end;
934
562
      }
935
 
      
936
563
      /* This process has not completed.  Does it have any output? */
937
564
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){
938
565
        /* This process had nothing to say at this time */
945
572
        if (proc->buffer == NULL){
946
573
          perror("malloc");
947
574
          exitstatus = EXIT_FAILURE;
948
 
          goto fallback;
 
575
          goto end;
949
576
        }
950
577
        proc->buffer_size += BUFFER_SIZE;
951
578
      }
964
591
      }
965
592
    }
966
593
  }
967
 
 
968
 
 
969
 
 fallback:
970
 
  
971
 
  if(plugin_list == NULL or exitstatus != EXIT_SUCCESS){
972
 
    /* Fallback if all plugins failed, none are found or an error
973
 
       occured */
974
 
    bool bret;
975
 
    fprintf(stderr, "Going to fallback mode using getpass(3)\n");
976
 
    char *passwordbuffer = getpass("Password: ");
977
 
    bret = print_out_password(passwordbuffer, strlen(passwordbuffer));
978
 
    if(not bret){
979
 
      perror("print_out_password");
980
 
      exitstatus = EXIT_FAILURE;
981
 
    }
 
594
  if(process_list == NULL){
 
595
    fprintf(stderr, "All plugin processes failed, exiting\n");
 
596
    exitstatus = EXIT_FAILURE;
982
597
  }
983
598
  
 
599
 end:
984
600
  /* Restore old signal handler */
985
 
  ret = sigaction(SIGCHLD, &old_sigchld_action, NULL);
986
 
  if(ret == -1){
987
 
    perror("sigaction");
988
 
    exitstatus = EXIT_FAILURE;
989
 
  }
990
 
 
991
 
  if(custom_argv != NULL){
992
 
    for(char **arg = custom_argv+1; *arg != NULL; arg++){
993
 
      free(*arg);
994
 
    }
995
 
    free(custom_argv);
 
601
  sigaction(SIGCHLD, &old_sigchld_action, NULL);
 
602
  
 
603
  /* Free the plugin list */
 
604
  for(plugin *next; plugin_list != NULL; plugin_list = next){
 
605
    next = plugin_list->next;
 
606
    free(plugin_list->argv);
 
607
    free(plugin_list);
996
608
  }
997
609
  
998
610
  if(dir != NULL){
1000
612
  }
1001
613
  
1002
614
  /* Free the process list and kill the processes */
1003
 
  for(plugin *p = plugin_list; p != NULL; p = p->next){
1004
 
    if(p->pid != 0){
1005
 
      close(p->fd);
1006
 
      ret = kill(p->pid, SIGTERM);
1007
 
      if(ret == -1 and errno != ESRCH){
1008
 
        /* Set-uid proccesses might not get closed */
1009
 
        perror("kill");
1010
 
      }
1011
 
    }
 
615
  for(process *next; process_list != NULL; process_list = next){
 
616
    next = process_list->next;
 
617
    close(process_list->fd);
 
618
    kill(process_list->pid, SIGTERM);
 
619
    free(process_list->buffer);
 
620
    free(process_list);
1012
621
  }
1013
622
  
1014
623
  /* Wait for any remaining child processes to terminate */
1018
627
  if(errno != ECHILD){
1019
628
    perror("wait");
1020
629
  }
1021
 
 
1022
 
  free_plugin_list();
1023
 
  
1024
 
  free(plugindir);
1025
 
  free(argfile);
1026
630
  
1027
631
  return exitstatus;
1028
632
}