/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 plugbasedclient.c

  • Committer: Björn Påhlsson
  • Date: 2008-07-20 02:52:20 UTC
  • Revision ID: belorn@braxen-20080720025220-r5u0388uy9iu23h6
Added following support:
Pluginbased client handler
rewritten Mandos client
       Avahi instead of udp server discovery
       openpgp encrypted key support
Passprompt stand alone application for direct console input
Added logging for Mandos server

Show diffs side-by-side

added added

removed removed

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