/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: 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 parsing */
258
 
    argv[1] = NULL;
259
 
  }
260
 
  *argc += 1;
261
 
  argv = realloc(argv, sizeof(char *)
262
 
                  * ((unsigned int) *argc + 1));
263
 
  if(argv == NULL){
264
 
    return NULL;
265
 
  }
266
 
  argv[*argc-1] = arg;
267
 
  argv[*argc] = NULL;
268
 
  return argv;
269
 
}
270
 
 
271
 
static void free_plugin_list(plugin *plugin_list){
272
 
  for(plugin *next; plugin_list != NULL; plugin_list = next){
273
 
    next = plugin_list->next;
274
 
    for(char **arg = plugin_list->argv; *arg != NULL; arg++){
275
 
      free(*arg);
276
 
    }
277
 
    free(plugin_list->argv);
278
 
    for(char **env = plugin_list->environ; *env != NULL; env++){
279
 
      free(*env);
280
 
    }
281
 
    free(plugin_list->environ);
282
 
    free(plugin_list);
283
 
  }
284
 
}
 
27
#define BUFFER_SIZE 256
285
28
 
286
29
int main(int argc, char *argv[]){
287
 
  const char *plugindir = "/lib/mandos/plugins.d";
288
 
  const char *argfile = ARGFILE;
289
 
  FILE *conffp;
290
 
  size_t d_name_len;
291
 
  DIR *dir = NULL;
 
30
  char plugindir[] = "plugins.d";
 
31
  size_t d_name_len, plugindir_len = sizeof(plugindir)-1;
 
32
  DIR *dir;
292
33
  struct dirent *dirst;
293
34
  struct stat st;
294
 
  fd_set rfds_all;
 
35
  fd_set rfds_orig;
295
36
  int ret, maxfd = 0;
296
 
  uid_t uid = 65534;
297
 
  gid_t gid = 65534;
298
 
  bool debug = false;
299
 
  int exitstatus = EXIT_SUCCESS;
300
 
  struct sigaction old_sigchld_action;
301
 
  struct sigaction sigchld_action = { .sa_handler = handle_sigchld,
302
 
                                      .sa_flags = SA_NOCLDSTOP };
303
 
  char **custom_argv = NULL;
304
 
  int custom_argc = 0;
305
 
  
306
 
  /* Establish a signal handler */
307
 
  sigemptyset(&sigchld_action.sa_mask);
308
 
  ret = sigaddset(&sigchld_action.sa_mask, SIGCHLD);
309
 
  if(ret == -1){
310
 
    perror("sigaddset");
311
 
    exitstatus = EXIT_FAILURE;
312
 
    goto fallback;
313
 
  }
314
 
  ret = sigaction(SIGCHLD, &sigchld_action, &old_sigchld_action);
315
 
  if(ret == -1){
316
 
    perror("sigaction");
317
 
    exitstatus = EXIT_FAILURE;
318
 
    goto fallback;
319
 
  }
320
 
  
321
 
  /* The options we understand. */
322
 
  struct argp_option options[] = {
323
 
    { .name = "global-options", .key = 'g',
324
 
      .arg = "OPTION[,OPTION[,...]]",
325
 
      .doc = "Options passed to all plugins" },
326
 
    { .name = "global-envs", .key = 'e',
327
 
      .arg = "VAR=value",
328
 
      .doc = "Environment variable passed to all plugins" },
329
 
    { .name = "options-for", .key = 'o',
330
 
      .arg = "PLUGIN:OPTION[,OPTION[,...]]",
331
 
      .doc = "Options passed only to specified plugin" },
332
 
    { .name = "envs-for", .key = 'f',
333
 
      .arg = "PLUGIN:ENV=value",
334
 
      .doc = "Environment variable passed to specified plugin" },
335
 
    { .name = "disable", .key = 'd',
336
 
      .arg = "PLUGIN",
337
 
      .doc = "Disable a specific plugin", .group = 1 },
338
 
    { .name = "plugin-dir", .key = 128,
339
 
      .arg = "DIRECTORY",
340
 
      .doc = "Specify a different plugin directory", .group = 2 },
341
 
    { .name = "userid", .key = 129,
342
 
      .arg = "ID", .flags = 0,
343
 
      .doc = "User ID the plugins will run as", .group = 2 },
344
 
    { .name = "groupid", .key = 130,
345
 
      .arg = "ID", .flags = 0,
346
 
      .doc = "Group ID the plugins will run as", .group = 2 },
347
 
    { .name = "debug", .key = 131,
348
 
      .doc = "Debug mode", .group = 3 },
349
 
    { .name = NULL }
350
 
  };
351
 
  
352
 
  error_t parse_opt (int key, char *arg, struct argp_state *state) {
353
 
    /* Get the INPUT argument from `argp_parse', which we know is a
354
 
       pointer to our plugin list pointer. */
355
 
    plugin **plugins = state->input;
356
 
    switch (key) {
357
 
    case 'g':
358
 
      if (arg != NULL){
359
 
        char *p;
360
 
        while((p = strsep(&arg, ",")) != NULL){
361
 
          if(p[0] == '\0'){
362
 
            continue;
363
 
          }
364
 
          if(not add_argument(getplugin(NULL, plugins), p)){
365
 
            perror("add_argument");
366
 
            return ARGP_ERR_UNKNOWN;
367
 
          }
368
 
        }
369
 
      }
370
 
      break;
371
 
    case 'e':
372
 
      if(arg == NULL){
373
 
        break;
374
 
      }
375
 
      {
376
 
        char *envdef = strdup(arg);
377
 
        if(envdef == NULL){
378
 
          break;
379
 
        }
380
 
        if(not add_environment(getplugin(NULL, plugins), envdef)){
381
 
          perror("add_environment");
382
 
        }
383
 
      }
384
 
      break;
385
 
    case 'o':
386
 
      if (arg != NULL){
387
 
        char *p_name = strsep(&arg, ":");
388
 
        if(p_name[0] == '\0'){
389
 
          break;
390
 
        }
391
 
        char *opt = strsep(&arg, ":");
392
 
        if(opt[0] == '\0'){
393
 
          break;
394
 
        }
395
 
        if(opt != NULL){
396
 
          char *p;
397
 
          while((p = strsep(&opt, ",")) != NULL){
398
 
            if(p[0] == '\0'){
399
 
              continue;
400
 
            }
401
 
            if(not add_argument(getplugin(p_name, plugins), p)){
402
 
              perror("add_argument");
403
 
              return ARGP_ERR_UNKNOWN;
404
 
            }
405
 
          }
406
 
        }
407
 
      }
408
 
      break;
409
 
    case 'f':
410
 
      if(arg == NULL){
411
 
        break;
412
 
      }
413
 
      {
414
 
        char *envdef = strchr(arg, ':');
415
 
        if(envdef == NULL){
416
 
          break;
417
 
        }
418
 
        char *p_name = strndup(arg, (size_t) (envdef-arg));
419
 
        if(p_name == NULL){
420
 
          break;
421
 
        }
422
 
        envdef++;
423
 
        if(not add_environment(getplugin(p_name, plugins), envdef)){
424
 
          perror("add_environment");
425
 
        }
426
 
      }
427
 
      break;
428
 
    case 'd':
429
 
      if (arg != NULL){
430
 
        plugin *p = getplugin(arg, plugins);
431
 
        if(p == NULL){
432
 
          return ARGP_ERR_UNKNOWN;
433
 
        }
434
 
        p->disabled = true;
435
 
      }
436
 
      break;
437
 
    case 128:
438
 
      plugindir = arg;
439
 
      break;
440
 
    case 129:
441
 
      uid = (uid_t)strtol(arg, NULL, 10);
442
 
      break;
443
 
    case 130:
444
 
      gid = (gid_t)strtol(arg, NULL, 10);
445
 
      break;
446
 
    case 131:
447
 
      debug = true;
448
 
      break;
449
 
    case ARGP_KEY_ARG:
450
 
      fprintf(stderr, "Ignoring unknown argument \"%s\"\n", arg);
451
 
      break;
452
 
    case ARGP_KEY_END:
453
 
      break;
454
 
    default:
455
 
      return ARGP_ERR_UNKNOWN;
456
 
    }
457
 
    return 0;
458
 
  }
459
 
  
460
 
  plugin *plugin_list = NULL;
461
 
  
462
 
  struct argp argp = { .options = options, .parser = parse_opt,
463
 
                       .args_doc = "[+PLUS_SEPARATED_OPTIONS]",
464
 
                       .doc = "Mandos plugin runner -- Run plugins" };
465
 
  
466
 
  ret = argp_parse (&argp, argc, argv, 0, 0, &plugin_list);
467
 
  if (ret == ARGP_ERR_UNKNOWN){
468
 
    fprintf(stderr, "Unknown error while parsing arguments\n");
469
 
    exitstatus = EXIT_FAILURE;
470
 
    goto fallback;
471
 
  }
472
 
 
473
 
  conffp = fopen(argfile, "r");
474
 
  if(conffp != NULL){
475
 
    char *org_line = NULL;
476
 
    char *p, *arg, *new_arg, *line;
477
 
    size_t size = 0;
478
 
    ssize_t sret;
479
 
    const char whitespace_delims[] = " \r\t\f\v\n";
480
 
    const char comment_delim[] = "#";
481
 
 
482
 
    while(true){
483
 
      sret = getline(&org_line, &size, conffp);
484
 
      if(sret == -1){
485
 
        break;
486
 
      }
487
 
 
488
 
      line = org_line;
489
 
      arg = strsep(&line, comment_delim);
490
 
      while((p = strsep(&arg, whitespace_delims)) != NULL){
491
 
        if(p[0] == '\0'){
492
 
          continue;
493
 
        }
494
 
        new_arg = strdup(p);
495
 
        custom_argv = add_to_argv(custom_argv, &custom_argc, new_arg);
496
 
        if (custom_argv == NULL){
497
 
          perror("add_to_argv");
498
 
          exitstatus = EXIT_FAILURE;
499
 
          goto fallback;
500
 
        }
501
 
      }
502
 
    }
503
 
    free(org_line);
504
 
  } else{
505
 
    /* Check for harmful errors and go to fallback. Other errors might
506
 
       not affect opening plugins */
507
 
    if (errno == EMFILE or errno == ENFILE or errno == ENOMEM){
508
 
      perror("fopen");
509
 
      exitstatus = EXIT_FAILURE;
510
 
      goto fallback;
511
 
    }
512
 
  }
513
 
 
514
 
  if(custom_argv != NULL){
515
 
    custom_argv[0] = argv[0];
516
 
    ret = argp_parse (&argp, custom_argc, custom_argv, 0, 0, &plugin_list);
517
 
    if (ret == ARGP_ERR_UNKNOWN){
518
 
      fprintf(stderr, "Unknown error while parsing arguments\n");
519
 
      exitstatus = EXIT_FAILURE;
520
 
      goto fallback;
521
 
    }
522
 
  }
523
 
  
524
 
  if(debug){
525
 
    for(plugin *p = plugin_list; p != NULL; p=p->next){
526
 
      fprintf(stderr, "Plugin: %s has %d arguments\n",
527
 
              p->name ? p->name : "Global", p->argc - 1);
528
 
      for(char **a = p->argv; *a != NULL; a++){
529
 
        fprintf(stderr, "\tArg: %s\n", *a);
530
 
      }
531
 
      fprintf(stderr, "...and %u environment variables\n", p->envc);
532
 
      for(char **a = p->environ; *a != NULL; a++){
533
 
        fprintf(stderr, "\t%s\n", *a);
534
 
      }
535
 
    }
536
 
  }
537
 
  
538
 
  ret = setuid(uid);
539
 
  if (ret == -1){
540
 
    perror("setuid");
541
 
  }
542
 
  
543
 
  setgid(gid);
544
 
  if (ret == -1){
545
 
    perror("setgid");
546
 
  }
 
37
  process *process_list = NULL;
547
38
  
548
39
  dir = opendir(plugindir);
 
40
 
549
41
  if(dir == NULL){
550
 
    perror("Could not open plugin dir");
551
 
    exitstatus = EXIT_FAILURE;
552
 
    goto fallback;
553
 
  }
554
 
  
555
 
  /* Set the FD_CLOEXEC flag on the directory, if possible */
556
 
  {
557
 
    int dir_fd = dirfd(dir);
558
 
    if(dir_fd >= 0){
559
 
      ret = set_cloexec_flag(dir_fd);
560
 
      if(ret < 0){
561
 
        perror("set_cloexec_flag");
562
 
        exitstatus = EXIT_FAILURE;
563
 
        goto fallback;
564
 
      }
565
 
    }
566
 
  }
567
 
  
568
 
  FD_ZERO(&rfds_all);
 
42
    fprintf(stderr, "Can not open directory\n");
 
43
    return EXIT_FAILURE;
 
44
  }
 
45
  
 
46
  FD_ZERO(&rfds_orig);
569
47
  
570
48
  while(true){
571
49
    dirst = readdir(dir);
572
50
    
573
51
    // All directory entries have been processed
574
52
    if(dirst == NULL){
575
 
      if (errno == EBADF){
576
 
        perror("readdir");
577
 
        exitstatus = EXIT_FAILURE;
578
 
        goto fallback;
579
 
      }
580
53
      break;
581
54
    }
582
55
    
583
56
    d_name_len = strlen(dirst->d_name);
584
57
    
585
 
    // Ignore dotfiles, backup files and other junk
586
 
    {
587
 
      bool bad_name = false;
588
 
      
589
 
      const char const *bad_prefixes[] = { ".", "#", NULL };
590
 
      
591
 
      const char const *bad_suffixes[] = { "~", "#", ".dpkg-new",
592
 
                                           ".dpkg-old",
593
 
                                           ".dpkg-divert", NULL };
594
 
      for(const char **pre = bad_prefixes; *pre != NULL; pre++){
595
 
        size_t pre_len = strlen(*pre);
596
 
        if((d_name_len >= pre_len)
597
 
           and strncmp((dirst->d_name), *pre, pre_len) == 0){
598
 
          if(debug){
599
 
            fprintf(stderr, "Ignoring plugin dir entry \"%s\""
600
 
                    " with bad prefix %s\n", dirst->d_name, *pre);
601
 
          }
602
 
          bad_name = true;
603
 
          break;
604
 
        }
605
 
      }
606
 
      
607
 
      if(bad_name){
608
 
        continue;
609
 
      }
610
 
      
611
 
      for(const char **suf = bad_suffixes; *suf != NULL; suf++){
612
 
        size_t suf_len = strlen(*suf);
613
 
        if((d_name_len >= suf_len)
614
 
           and (strcmp((dirst->d_name)+d_name_len-suf_len, *suf)
615
 
                == 0)){
616
 
          if(debug){
617
 
            fprintf(stderr, "Ignoring plugin dir entry \"%s\""
618
 
                    " with bad suffix %s\n", dirst->d_name, *suf);
619
 
          }
620
 
          bad_name = true;
621
 
          break;
622
 
        }
623
 
      }
624
 
      
625
 
      if(bad_name){
626
 
        continue;
627
 
      }
628
 
    }
629
 
 
630
 
    char *filename;
631
 
    ret = asprintf(&filename, "%s/%s", plugindir, dirst->d_name);
632
 
    if(ret < 0){
633
 
      perror("asprintf");
634
 
      continue;
635
 
    }
636
 
    
637
 
    ret = stat(filename, &st);
638
 
    if (ret == -1){
639
 
      perror("stat");
640
 
      free(filename);
641
 
      continue;
642
 
    }
643
 
    
644
 
    if (not S_ISREG(st.st_mode) or (access(filename, X_OK) != 0)){
645
 
      if(debug){
646
 
        fprintf(stderr, "Ignoring plugin dir entry \"%s\""
647
 
                " with bad type or mode\n", filename);
648
 
      }
649
 
      free(filename);
650
 
      continue;
651
 
    }
652
 
    plugin *p = getplugin(dirst->d_name, &plugin_list);
653
 
    if(p == NULL){
654
 
      perror("getplugin");
655
 
      free(filename);
656
 
      continue;
657
 
    }
658
 
    if(p->disabled){
659
 
      if(debug){
660
 
        fprintf(stderr, "Ignoring disabled plugin \"%s\"\n",
661
 
                dirst->d_name);
662
 
      }
663
 
      free(filename);
664
 
      continue;
665
 
    }
666
 
    {
667
 
      /* Add global arguments to argument list for this plugin */
668
 
      plugin *g = getplugin(NULL, &plugin_list);
669
 
      if(g != NULL){
670
 
        for(char **a = g->argv + 1; *a != NULL; a++){
671
 
          if(not add_argument(p, *a)){
672
 
            perror("add_argument");
673
 
          }
674
 
        }
675
 
        /* Add global environment variables */
676
 
        for(char **e = g->environ; *e != NULL; e++){
677
 
          if(not add_environment(p, *e)){
678
 
            perror("add_environment");
679
 
          }
680
 
        }
681
 
      }
682
 
    }
683
 
    /* If this plugin has any environment variables, we will call
684
 
       using execve and need to duplicate the environment from this
685
 
       process, too. */
686
 
    if(p->environ[0] != NULL){
687
 
      for(char **e = environ; *e != NULL; e++){
688
 
        char *copy = strdup(*e);
689
 
        if(copy == NULL){
690
 
          perror("strdup");
691
 
          continue;
692
 
        }
693
 
        if(not add_environment(p, copy)){
694
 
          perror("add_environment");
695
 
        }
696
 
      }
697
 
    }
698
 
    
699
 
    int pipefd[2];
700
 
    ret = pipe(pipefd);
701
 
    if (ret == -1){
702
 
      perror("pipe");
703
 
      exitstatus = EXIT_FAILURE;
704
 
      goto fallback;
705
 
    }
706
 
    ret = set_cloexec_flag(pipefd[0]);
707
 
    if(ret < 0){
708
 
      perror("set_cloexec_flag");
709
 
      exitstatus = EXIT_FAILURE;
710
 
      goto fallback;
711
 
    }
712
 
    ret = set_cloexec_flag(pipefd[1]);
713
 
    if(ret < 0){
714
 
      perror("set_cloexec_flag");
715
 
      exitstatus = EXIT_FAILURE;
716
 
      goto fallback;
717
 
    }
718
 
    /* Block SIGCHLD until process is safely in process list */
719
 
    ret = sigprocmask (SIG_BLOCK, &sigchld_action.sa_mask, NULL);
720
 
    if(ret < 0){
721
 
      perror("sigprocmask");
722
 
      exitstatus = EXIT_FAILURE;
723
 
      goto fallback;
724
 
    }
725
 
    // Starting a new process to be watched
726
 
    pid_t pid = fork();
727
 
    if(pid == -1){
728
 
      perror("fork");
729
 
      exitstatus = EXIT_FAILURE;
730
 
      goto fallback;
731
 
    }
732
 
    if(pid == 0){
733
 
      /* this is the child process */
734
 
      ret = sigaction(SIGCHLD, &old_sigchld_action, NULL);
735
 
      if(ret < 0){
736
 
        perror("sigaction");
737
 
        _exit(EXIT_FAILURE);
738
 
      }
739
 
      ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
740
 
      if(ret < 0){
741
 
        perror("sigprocmask");
742
 
        _exit(EXIT_FAILURE);
743
 
      }
744
 
 
745
 
      ret = dup2(pipefd[1], STDOUT_FILENO); /* replace our stdout */
746
 
      if(ret == -1){
747
 
        perror("dup2");
748
 
        _exit(EXIT_FAILURE);
749
 
      }
750
 
      
751
 
      if(dirfd(dir) < 0){
752
 
        /* If dir has no file descriptor, we could not set FD_CLOEXEC
753
 
           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 */
754
79
        closedir(dir);
755
 
      }
756
 
      if(p->environ[0] == NULL){
757
 
        if(execv(filename, p->argv) < 0){
758
 
          perror("execv");
759
 
          _exit(EXIT_FAILURE);
760
 
        }
761
 
      } else {
762
 
        if(execve(filename, p->argv, p->environ) < 0){
763
 
          perror("execve");
764
 
          _exit(EXIT_FAILURE);
765
 
        }
766
 
      }
767
 
      /* no return */
768
 
    }
769
 
    /* parent process */
770
 
    free(filename);
771
 
    close(pipefd[1]);           /* close unused write end of pipe */
772
 
    process *new_process = malloc(sizeof(process));
773
 
    if (new_process == NULL){
774
 
      perror("malloc");
775
 
      ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
776
 
      if(ret < 0){
777
 
        perror("sigprocmask");
778
 
      }
779
 
      exitstatus = EXIT_FAILURE;
780
 
      goto fallback;
781
 
    }
782
 
    
783
 
    *new_process = (struct process){ .pid = pid,
784
 
                                     .fd = pipefd[0],
785
 
                                     .next = process_list };
786
 
    // List handling
787
 
    process_list = new_process;
788
 
    /* Unblock SIGCHLD so signal handler can be run if this process
789
 
       has already completed */
790
 
    ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
791
 
    if(ret < 0){
792
 
      perror("sigprocmask");
793
 
      exitstatus = EXIT_FAILURE;
794
 
      goto fallback;
795
 
    }
796
 
    
797
 
    FD_SET(new_process->fd, &rfds_all);
798
 
    
799
 
    if (maxfd < new_process->fd){
800
 
      maxfd = new_process->fd;
801
 
    }
802
 
    
 
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
    }
803
115
  }
804
116
  
805
 
  free_plugin_list(plugin_list);
806
 
  plugin_list = NULL;
807
 
  
808
117
  closedir(dir);
809
 
  dir = NULL;
810
 
    
811
 
  if (process_list == NULL){
812
 
    fprintf(stderr, "No plugin processes started. Incorrect plugin"
813
 
            " directory?\n");
814
 
    process_list = NULL;
815
 
  }
816
 
  while(process_list){
817
 
    fd_set rfds = rfds_all;
818
 
    int select_ret = select(maxfd+1, &rfds, NULL, NULL, NULL);
819
 
    if (select_ret == -1){
820
 
      perror("select");
821
 
      exitstatus = EXIT_FAILURE;
822
 
      goto fallback;
823
 
    }
824
 
    /* OK, now either a process completed, or something can be read
825
 
       from one of them */
826
 
    for(process *proc = process_list; proc ; proc = proc->next){
827
 
      /* Is this process completely done? */
828
 
      if(proc->eof and proc->completed){
829
 
        /* Only accept the plugin output if it exited cleanly */
830
 
        if(not WIFEXITED(proc->status)
831
 
           or WEXITSTATUS(proc->status) != 0){
832
 
          /* Bad exit by plugin */
833
 
          if(debug){
834
 
            if(WIFEXITED(proc->status)){
835
 
              fprintf(stderr, "Plugin %u exited with status %d\n",
836
 
                      (unsigned int) (proc->pid),
837
 
                      WEXITSTATUS(proc->status));
838
 
            } else if(WIFSIGNALED(proc->status)) {
839
 
              fprintf(stderr, "Plugin %u killed by signal %d\n",
840
 
                      (unsigned int) (proc->pid),
841
 
                      WTERMSIG(proc->status));
842
 
            } else if(WCOREDUMP(proc->status)){
843
 
              fprintf(stderr, "Plugin %d dumped core\n",
844
 
                      (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;
845
140
            }
846
 
          }
847
 
          /* Remove the plugin */
848
 
          FD_CLR(proc->fd, &rfds_all);
849
 
          /* Block signal while modifying process_list */
850
 
          ret = sigprocmask(SIG_BLOCK, &sigchld_action.sa_mask, NULL);
851
 
          if(ret < 0){
852
 
            perror("sigprocmask");
853
 
            exitstatus = EXIT_FAILURE;
854
 
            goto fallback;
855
 
          }
856
 
          /* Delete this process entry from the list */
857
 
          if(process_list == proc){
858
 
            /* First one - simple */
859
 
            process_list = proc->next;
860
 
          } else {
861
 
            /* Second one or later */
862
 
            for(process *p = process_list; p != NULL; p = p->next){
863
 
              if(p->next == proc){
864
 
                p->next = proc->next;
865
 
                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);
866
155
              }
867
156
            }
868
157
          }
869
 
          /* We are done modifying process list, so unblock signal */
870
 
          ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask,
871
 
                             NULL);
872
 
          if(ret < 0){
873
 
            perror("sigprocmask");
874
 
          }
875
 
          free(proc->buffer);
876
 
          free(proc);
877
 
          /* We deleted this process from the list, so we can't go
878
 
             proc->next.  Therefore, start over from the beginning of
879
 
             the process list */
880
 
          break;
881
 
        }
882
 
        /* This process exited nicely, so print its buffer */
883
 
 
884
 
        bool bret = print_out_password(proc->buffer,
885
 
                                       proc->buffer_length);
886
 
        if(not bret){
887
 
          perror("print_out_password");
888
 
          exitstatus = EXIT_FAILURE;
889
 
        }
890
 
        goto fallback;
891
 
      }
892
 
      /* This process has not completed.  Does it have any output? */
893
 
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){
894
 
        /* This process had nothing to say at this time */
895
 
        continue;
896
 
      }
897
 
      /* Before reading, make the process' data buffer large enough */
898
 
      if(proc->buffer_length + BUFFER_SIZE > proc->buffer_size){
899
 
        proc->buffer = realloc(proc->buffer, proc->buffer_size
900
 
                               + (size_t) BUFFER_SIZE);
901
 
        if (proc->buffer == NULL){
902
 
          perror("malloc");
903
 
          exitstatus = EXIT_FAILURE;
904
 
          goto fallback;
905
 
        }
906
 
        proc->buffer_size += BUFFER_SIZE;
907
 
      }
908
 
      /* Read from the process */
909
 
      ret = read(proc->fd, proc->buffer + proc->buffer_length,
910
 
                 BUFFER_SIZE);
911
 
      if(ret < 0){
912
 
        /* Read error from this process; ignore the error */
913
 
        continue;
914
 
      }
915
 
      if(ret == 0){
916
 
        /* got EOF */
917
 
        proc->eof = true;
918
 
      } else {
919
 
        proc->buffer_length += (size_t) ret;
920
 
      }
921
 
    }
922
 
  }
923
 
 
924
 
 
925
 
 fallback:
926
 
  
927
 
  if(process_list == NULL or exitstatus != EXIT_SUCCESS){
928
 
    /* Fallback if all plugins failed, none are found or an error
929
 
       occured */
930
 
    bool bret;
931
 
    fprintf(stderr, "Going to fallback mode using getpass(3)\n");
932
 
    char *passwordbuffer = getpass("Password: ");
933
 
    bret = print_out_password(passwordbuffer, strlen(passwordbuffer));
934
 
    if(not bret){
935
 
      perror("print_out_password");
936
 
      exitstatus = EXIT_FAILURE;
937
 
    }
938
 
  }
939
 
  
940
 
  /* Restore old signal handler */
941
 
  ret = sigaction(SIGCHLD, &old_sigchld_action, NULL);
942
 
  if(ret == -1){
943
 
    perror("sigaction");
944
 
    exitstatus = EXIT_FAILURE;
945
 
  }
946
 
 
947
 
  if(custom_argv != NULL){
948
 
    for(char **arg = custom_argv; *arg != NULL; arg++){
949
 
      free(*arg);
950
 
    }
951
 
    free(custom_argv);
952
 
  }
953
 
  free_plugin_list(plugin_list);
954
 
  
955
 
  if(dir != NULL){
956
 
    closedir(dir);
957
 
  }
958
 
  
959
 
  /* Free the process list and kill the processes */
960
 
  for(process *next; process_list != NULL; process_list = next){
961
 
    next = process_list->next;
962
 
    close(process_list->fd);
963
 
    ret = kill(process_list->pid, SIGTERM);
964
 
    if(ret == -1 and errno != ESRCH){
965
 
      /* set-uid proccesses migth not get closed */
966
 
      perror("kill");
967
 
    }
968
 
    free(process_list->buffer);
969
 
    free(process_list);
970
 
  }
971
 
  
972
 
  /* Wait for any remaining child processes to terminate */
973
 
  do{
974
 
    ret = wait(NULL);
975
 
  } while(ret >= 0);
976
 
  if(errno != ECHILD){
977
 
    perror("wait");
978
 
  }
979
 
  
980
 
  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;
981
182
}