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