/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() */
25
 
 
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(), strcpy(),
55
 
                                   strcat() */
56
 
#include <errno.h>              /* errno */
57
 
#include <argp.h>               /* struct argp_option, struct
58
 
                                   argp_state, struct argp,
59
 
                                   argp_parse(), ARGP_ERR_UNKNOWN,
60
 
                                   ARGP_KEY_END, ARGP_KEY_ARG, error_t */
61
 
#include <signal.h>             /* struct sigaction, sigemptyset(),
62
 
                                   sigaddset(), sigaction(),
63
 
                                   sigprocmask(), SIG_BLOCK, SIGCHLD,
64
 
                                   SIG_UNBLOCK, kill() */
65
 
#include <errno.h>              /* errno, EBADF */
66
 
 
67
 
#define BUFFER_SIZE 256
68
 
 
69
 
const char *argp_program_version = "mandos-client 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
 
  bool disabled;
91
 
  struct plugin *next;
92
 
} plugin;
93
 
 
94
 
static plugin *getplugin(char *name, plugin **plugin_list){
95
 
  for (plugin *p = *plugin_list; p != NULL; p = p->next){
96
 
    if ((p->name == name)
97
 
        or (p->name and name and (strcmp(p->name, name) == 0))){
98
 
      return p;
99
 
    }
100
 
  }
101
 
  /* Create a new plugin */
102
 
  plugin *new_plugin = malloc(sizeof(plugin));
103
 
  if (new_plugin == NULL){
104
 
    perror("malloc");
105
 
    exit(EXIT_FAILURE);
106
 
  }
107
 
  new_plugin->name = name;
108
 
  new_plugin->argv = malloc(sizeof(char *) * 2);
109
 
  if (new_plugin->argv == NULL){
110
 
    perror("malloc");
111
 
    exit(EXIT_FAILURE);
112
 
  }
113
 
  new_plugin->argv[0] = name;
114
 
  new_plugin->argv[1] = NULL;
115
 
  new_plugin->argc = 1;
116
 
  new_plugin->disabled = false;
117
 
  new_plugin->next = *plugin_list;
118
 
  /* Append the new plugin to the list */
119
 
  *plugin_list = new_plugin;
120
 
  return new_plugin;
121
 
}
122
 
 
123
 
static void addargument(plugin *p, char *arg){
124
 
  p->argv[p->argc] = arg;
125
 
  p->argv = realloc(p->argv, sizeof(char *) * (size_t)(p->argc + 2));
126
 
  if (p->argv == NULL){
127
 
    perror("malloc");
128
 
    exit(EXIT_FAILURE);
129
 
  }
130
 
  p->argc++;
131
 
  p->argv[p->argc] = NULL;
132
 
}
133
 
 
134
 
/*
135
 
 * Based on the example in the GNU LibC manual chapter 13.13 "File
136
 
 * Descriptor Flags".
137
 
 * *Note File Descriptor Flags:(libc)Descriptor Flags.
138
 
 */
139
 
static int set_cloexec_flag(int fd)
140
 
{
141
 
  int ret = fcntl(fd, F_GETFD, 0);
142
 
  /* If reading the flags failed, return error indication now. */
143
 
  if(ret < 0){
144
 
    return ret;
145
 
  }
146
 
  /* Store modified flag word in the descriptor. */
147
 
  return fcntl(fd, F_SETFD, ret | FD_CLOEXEC);
148
 
}
149
 
 
150
 
process *process_list = NULL;
151
 
 
152
 
/* Mark a process as completed when it exits, and save its exit
153
 
   status. */
154
 
void handle_sigchld(__attribute__((unused)) int sig){
155
 
  process *proc = process_list;
156
 
  int status;
157
 
  pid_t pid = wait(&status);
158
 
  if(pid == -1){
159
 
    perror("wait");
160
 
    return;
161
 
  }
162
 
  while(proc != NULL and proc->pid != pid){
163
 
    proc = proc->next;
164
 
  }
165
 
  if(proc == NULL){
166
 
    /* Process not found in process list */
167
 
    return;
168
 
  }
169
 
  proc->status = status;
170
 
  proc->completed = true;
171
 
}
172
 
 
173
 
bool print_out_password(const char *buffer, size_t length){
174
 
  ssize_t ret;
175
 
  if(length>0 and buffer[length-1] == '\n'){
176
 
    length--;
177
 
  }
178
 
  for(size_t written = 0; written < length; written += (size_t)ret){
179
 
    ret = TEMP_FAILURE_RETRY(write(STDOUT_FILENO, buffer + written,
180
 
                                   length - written));
181
 
    if(ret < 0){
182
 
      return false;
183
 
    }
184
 
  }
185
 
  return true;
186
 
}
 
27
#define BUFFER_SIZE 256
187
28
 
188
29
int main(int argc, char *argv[]){
189
 
  const char *plugindir = "/conf/conf.d/mandos/plugins.d";
190
 
  size_t d_name_len;
191
 
  DIR *dir = NULL;
 
30
  char plugindir[] = "plugins.d";
 
31
  size_t d_name_len, plugindir_len = sizeof(plugindir)-1;
 
32
  DIR *dir;
192
33
  struct dirent *dirst;
193
34
  struct stat st;
194
 
  fd_set rfds_all;
 
35
  fd_set rfds_orig;
195
36
  int ret, maxfd = 0;
196
 
  uid_t uid = 65534;
197
 
  gid_t gid = 65534;
198
 
  bool debug = false;
199
 
  int exitstatus = EXIT_SUCCESS;
200
 
  struct sigaction old_sigchld_action;
201
 
  struct sigaction sigchld_action = { .sa_handler = handle_sigchld,
202
 
                                      .sa_flags = SA_NOCLDSTOP };
203
 
  char *plus_options = NULL;
204
 
  char **plus_argv = NULL;
205
 
 
206
 
  /* Establish a signal handler */
207
 
  sigemptyset(&sigchld_action.sa_mask);
208
 
  ret = sigaddset(&sigchld_action.sa_mask, SIGCHLD);
209
 
  if(ret < 0){
210
 
    perror("sigaddset");
211
 
    exit(EXIT_FAILURE);
212
 
  }
213
 
  ret = sigaction(SIGCHLD, &sigchld_action, &old_sigchld_action);
214
 
  if(ret < 0){
215
 
    perror("sigaction");
216
 
    exit(EXIT_FAILURE);
217
 
  }
218
 
  
219
 
  /* The options we understand. */
220
 
  struct argp_option options[] = {
221
 
    { .name = "global-options", .key = 'g',
222
 
      .arg = "OPTION[,OPTION[,...]]",
223
 
      .doc = "Options passed to all plugins" },
224
 
    { .name = "options-for", .key = 'o',
225
 
      .arg = "PLUGIN:OPTION[,OPTION[,...]]",
226
 
      .doc = "Options passed only to specified plugin" },
227
 
    { .name = "disable", .key = 'd',
228
 
      .arg = "PLUGIN",
229
 
      .doc = "Disable a specific plugin", .group = 1 },
230
 
    { .name = "plugin-dir", .key = 128,
231
 
      .arg = "DIRECTORY",
232
 
      .doc = "Specify a different plugin directory", .group = 2 },
233
 
    { .name = "userid", .key = 129,
234
 
      .arg = "ID", .flags = 0,
235
 
      .doc = "User ID the plugins will run as", .group = 2 },
236
 
    { .name = "groupid", .key = 130,
237
 
      .arg = "ID", .flags = 0,
238
 
      .doc = "Group ID the plugins will run as", .group = 2 },
239
 
    { .name = "debug", .key = 131,
240
 
      .doc = "Debug mode", .group = 3 },
241
 
    { .name = NULL }
242
 
  };
243
 
  
244
 
  error_t parse_opt (int key, char *arg, struct argp_state *state) {
245
 
    /* Get the INPUT argument from `argp_parse', which we know is a
246
 
       pointer to our plugin list pointer. */
247
 
    plugin **plugins = state->input;
248
 
    switch (key) {
249
 
    case 'g':
250
 
      if (arg != NULL){
251
 
        char *p;
252
 
        while((p = strsep(&arg, ",")) != NULL){
253
 
          if(strcmp(p, "") == 0){
254
 
            continue;
255
 
          }
256
 
          addargument(getplugin(NULL, plugins), p);
257
 
        }
258
 
      }
259
 
      break;
260
 
    case 'o':
261
 
      if (arg != NULL){
262
 
        char *name = strsep(&arg, ":");
263
 
        if(strcmp(name, "") == 0){
264
 
          break;
265
 
        }
266
 
        char *opt = strsep(&arg, ":");
267
 
        if(strcmp(opt, "") == 0){
268
 
          break;
269
 
        }
270
 
        if(opt != NULL){
271
 
          char *p;
272
 
          while((p = strsep(&opt, ",")) != NULL){
273
 
            if(strcmp(p, "") == 0){
274
 
              continue;
275
 
            }
276
 
            addargument(getplugin(name, plugins), p);
277
 
          }
278
 
        }
279
 
      }
280
 
      break;
281
 
    case 'd':
282
 
      if (arg != NULL){
283
 
        getplugin(arg, plugins)->disabled = true;
284
 
      }
285
 
      break;
286
 
    case 128:
287
 
      plugindir = arg;
288
 
      break;
289
 
    case 129:
290
 
      uid = (uid_t)strtol(arg, NULL, 10);
291
 
      break;
292
 
    case 130:
293
 
      gid = (gid_t)strtol(arg, NULL, 10);
294
 
      break;
295
 
    case 131:
296
 
      debug = true;
297
 
      break;
298
 
    case ARGP_KEY_ARG:
299
 
      if(plus_options != NULL or arg == NULL or arg[0] != '+'){
300
 
        argp_usage (state);
301
 
      }
302
 
      plus_options = arg;
303
 
      break;
304
 
    case ARGP_KEY_END:
305
 
      break;
306
 
    default:
307
 
      return ARGP_ERR_UNKNOWN;
308
 
    }
309
 
    return 0;
310
 
  }
311
 
  
312
 
  plugin *plugin_list = NULL;
313
 
  
314
 
  struct argp argp = { .options = options, .parser = parse_opt,
315
 
                       .args_doc = "[+PLUS_SEPARATED_OPTIONS]",
316
 
                       .doc = "Mandos plugin runner -- Run plugins" };
317
 
  
318
 
  ret = argp_parse (&argp, argc, argv, 0, 0, &plugin_list);
319
 
  if (ret == ARGP_ERR_UNKNOWN){
320
 
    fprintf(stderr, "Unknown error while parsing arguments\n");
321
 
    exitstatus = EXIT_FAILURE;
322
 
    goto end;
323
 
  }
324
 
  
325
 
  if(plus_options){
326
 
    /* This is a mangled argument in the form of
327
 
     "+--option+--other-option=parameter+--yet-another-option", etc */
328
 
    /* Make new argc and argv vars, and call argp_parse() again. */
329
 
    plus_options++;             /* skip the first '+' character */
330
 
    const char delims[] = "+";
331
 
    char *arg;
332
 
    int new_argc = 1;
333
 
    plus_argv = malloc(sizeof(char*) * 2);
334
 
    if(plus_argv == NULL){
335
 
      perror("malloc");
336
 
      exitstatus = EXIT_FAILURE;
337
 
      goto end;
338
 
    }
339
 
    plus_argv[0] = argv[0];
340
 
    plus_argv[1] = NULL;
341
 
 
342
 
    while((arg = strsep(&plus_options, delims)) != NULL){
343
 
      new_argc++;
344
 
      plus_argv = realloc(plus_argv, sizeof(char *)
345
 
                         * ((unsigned int) new_argc + 1));
346
 
      if(plus_argv == NULL){
347
 
        perror("realloc");
348
 
        exitstatus = EXIT_FAILURE;
349
 
        goto end;
350
 
      }
351
 
      plus_argv[new_argc-1] = arg;
352
 
      plus_argv[new_argc] = NULL;
353
 
    }
354
 
 
355
 
    ret = argp_parse (&argp, new_argc, plus_argv, 0, 0, &plugin_list);
356
 
    if (ret == ARGP_ERR_UNKNOWN){
357
 
      fprintf(stderr, "Unknown error while parsing arguments\n");
358
 
      exitstatus = EXIT_FAILURE;
359
 
      goto end;
360
 
    }
361
 
  }
362
 
  
363
 
  if(debug){
364
 
    for(plugin *p = plugin_list; p != NULL; p=p->next){
365
 
      fprintf(stderr, "Plugin: %s has %d arguments\n",
366
 
              p->name ? p->name : "Global", p->argc - 1);
367
 
      for(char **a = p->argv; *a != NULL; a++){
368
 
        fprintf(stderr, "\tArg: %s\n", *a);
369
 
      }
370
 
    }
371
 
  }
372
 
  
373
 
  ret = setuid(uid);
374
 
  if (ret == -1){
375
 
    perror("setuid");
376
 
  }
377
 
  
378
 
  setgid(gid);
379
 
  if (ret == -1){
380
 
    perror("setgid");
381
 
  }
 
37
  process *process_list = NULL;
382
38
  
383
39
  dir = opendir(plugindir);
 
40
 
384
41
  if(dir == NULL){
385
 
    perror("Could not open plugin dir");
386
 
    exitstatus = EXIT_FAILURE;
387
 
    goto end;
388
 
  }
389
 
  
390
 
  /* Set the FD_CLOEXEC flag on the directory, if possible */
391
 
  {
392
 
    int dir_fd = dirfd(dir);
393
 
    if(dir_fd >= 0){
394
 
      ret = set_cloexec_flag(dir_fd);
395
 
      if(ret < 0){
396
 
        perror("set_cloexec_flag");
397
 
        exitstatus = EXIT_FAILURE;
398
 
        goto end;
399
 
      }
400
 
    }
401
 
  }
402
 
  
403
 
  FD_ZERO(&rfds_all);
 
42
    fprintf(stderr, "Can not open directory\n");
 
43
    return EXIT_FAILURE;
 
44
  }
 
45
  
 
46
  FD_ZERO(&rfds_orig);
404
47
  
405
48
  while(true){
406
49
    dirst = readdir(dir);
407
50
    
408
51
    // All directory entries have been processed
409
52
    if(dirst == NULL){
410
 
      if (errno == EBADF){
411
 
        perror("readdir");
412
 
        exitstatus = EXIT_FAILURE;
413
 
        goto end;
414
 
      }
415
53
      break;
416
54
    }
417
55
    
418
56
    d_name_len = strlen(dirst->d_name);
419
57
    
420
 
    // Ignore dotfiles, backup files and other junk
421
 
    {
422
 
      bool bad_name = false;
423
 
      
424
 
      const char const *bad_prefixes[] = { ".", "#", NULL };
425
 
      
426
 
      const char const *bad_suffixes[] = { "~", "#", ".dpkg-new",
427
 
                                           ".dpkg-old",
428
 
                                           ".dpkg-divert", NULL };
429
 
      for(const char **pre = bad_prefixes; *pre != NULL; pre++){
430
 
        size_t pre_len = strlen(*pre);
431
 
        if((d_name_len >= pre_len)
432
 
           and strncmp((dirst->d_name), *pre, pre_len) == 0){
433
 
          if(debug){
434
 
            fprintf(stderr, "Ignoring plugin dir entry \"%s\""
435
 
                    " with bad prefix %s\n", dirst->d_name, *pre);
436
 
          }
437
 
          bad_name = true;
438
 
          break;
439
 
        }
440
 
      }
441
 
      
442
 
      if(bad_name){
443
 
        continue;
444
 
      }
445
 
      
446
 
      for(const char **suf = bad_suffixes; *suf != NULL; suf++){
447
 
        size_t suf_len = strlen(*suf);
448
 
        if((d_name_len >= suf_len)
449
 
           and (strcmp((dirst->d_name)+d_name_len-suf_len, *suf)
450
 
                == 0)){
451
 
          if(debug){
452
 
            fprintf(stderr, "Ignoring plugin dir entry \"%s\""
453
 
                    " with bad suffix %s\n", dirst->d_name, *suf);
454
 
          }
455
 
          bad_name = true;
456
 
          break;
457
 
        }
458
 
      }
459
 
      
460
 
      if(bad_name){
461
 
        continue;
462
 
      }
463
 
    }
464
 
    
465
 
    char *filename = malloc(d_name_len + strlen(plugindir) + 2);
466
 
    if (filename == NULL){
467
 
      perror("malloc");
468
 
      continue;
469
 
    }
470
 
    strcpy(filename, plugindir); /* Spurious warning */
471
 
    strcat(filename, "/");      /* Spurious warning */
472
 
    strcat(filename, dirst->d_name); /* Spurious warning */
473
 
    
474
 
    ret = stat(filename, &st);
475
 
    if (ret == -1){
476
 
      perror("stat");
477
 
      free(filename);
478
 
      continue;
479
 
    }
480
 
    
481
 
    if (not S_ISREG(st.st_mode) or (access(filename, X_OK) != 0)){
482
 
      if(debug){
483
 
        fprintf(stderr, "Ignoring plugin dir entry \"%s\""
484
 
                " with bad type or mode\n", filename);
485
 
      }
486
 
      free(filename);
487
 
      continue;
488
 
    }
489
 
    if(getplugin(dirst->d_name, &plugin_list)->disabled){
490
 
      if(debug){
491
 
        fprintf(stderr, "Ignoring disabled plugin \"%s\"\n",
492
 
                dirst->d_name);
493
 
      }
494
 
      free(filename);
495
 
      continue;
496
 
    }
497
 
    plugin *p = getplugin(dirst->d_name, &plugin_list);
498
 
    {
499
 
      /* Add global arguments to argument list for this plugin */
500
 
      plugin *g = getplugin(NULL, &plugin_list);
501
 
      for(char **a = g->argv + 1; *a != NULL; a++){
502
 
        addargument(p, *a);
503
 
      }
504
 
    }
505
 
    int pipefd[2]; 
506
 
    ret = pipe(pipefd);
507
 
    if (ret == -1){
508
 
      perror("pipe");
509
 
      exitstatus = EXIT_FAILURE;
510
 
      goto end;
511
 
    }
512
 
    ret = set_cloexec_flag(pipefd[0]);
513
 
    if(ret < 0){
514
 
      perror("set_cloexec_flag");
515
 
      exitstatus = EXIT_FAILURE;
516
 
      goto end;
517
 
    }
518
 
    ret = set_cloexec_flag(pipefd[1]);
519
 
    if(ret < 0){
520
 
      perror("set_cloexec_flag");
521
 
      exitstatus = EXIT_FAILURE;
522
 
      goto end;
523
 
    }
524
 
    /* Block SIGCHLD until process is safely in process list */
525
 
    ret = sigprocmask (SIG_BLOCK, &sigchld_action.sa_mask, NULL);
526
 
    if(ret < 0){
527
 
      perror("sigprocmask");
528
 
      exitstatus = EXIT_FAILURE;
529
 
      goto end;
530
 
    }
531
 
    // Starting a new process to be watched
532
 
    pid_t pid = fork();
533
 
    if(pid == -1){
534
 
      perror("fork");
535
 
      exitstatus = EXIT_FAILURE;
536
 
      goto end;
537
 
    }
538
 
    if(pid == 0){
539
 
      /* this is the child process */
540
 
      ret = sigaction(SIGCHLD, &old_sigchld_action, NULL);
541
 
      if(ret < 0){
542
 
        perror("sigaction");
543
 
        _exit(EXIT_FAILURE);
544
 
      }
545
 
      ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
546
 
      if(ret < 0){
547
 
        perror("sigprocmask");
548
 
        _exit(EXIT_FAILURE);
549
 
      }
 
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);
550
70
 
551
 
      ret = dup2(pipefd[1], STDOUT_FILENO); /* replace our stdout */
552
 
      if(ret == -1){
553
 
        perror("dup2");
554
 
        _exit(EXIT_FAILURE);
555
 
      }
556
 
      
557
 
      if(dirfd(dir) < 0){
558
 
        /* If dir has no file descriptor, we could not set FD_CLOEXEC
559
 
           above and must now close it manually here. */
 
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 */
560
79
        closedir(dir);
561
 
      }
562
 
      if(execv(filename, p->argv) < 0){
563
 
        perror("execv");
564
 
        _exit(EXIT_FAILURE);
565
 
      }
566
 
      /* no return */
567
 
    }
568
 
    /* parent process */
569
 
    free(filename);
570
 
    close(pipefd[1]);           /* close unused write end of pipe */
571
 
    process *new_process = malloc(sizeof(process));
572
 
    if (new_process == NULL){
573
 
      perror("malloc");
574
 
      ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
575
 
      if(ret < 0){
576
 
        perror("sigprocmask");
577
 
      }
578
 
      exitstatus = EXIT_FAILURE;
579
 
      goto end;
580
 
    }
581
 
    
582
 
    *new_process = (struct process){ .pid = pid,
583
 
                                     .fd = pipefd[0],
584
 
                                     .next = process_list };
585
 
    // List handling
586
 
    process_list = new_process;
587
 
    /* Unblock SIGCHLD so signal handler can be run if this process
588
 
       has already completed */
589
 
    ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
590
 
    if(ret < 0){
591
 
      perror("sigprocmask");
592
 
      exitstatus = EXIT_FAILURE;
593
 
      goto end;
594
 
    }
595
 
    
596
 
    FD_SET(new_process->fd, &rfds_all);
597
 
    
598
 
    if (maxfd < new_process->fd){
599
 
      maxfd = new_process->fd;
600
 
    }
601
 
    
602
 
  }
603
 
  
604
 
  /* Free the plugin list */
605
 
  for(plugin *next; plugin_list != NULL; plugin_list = next){
606
 
    next = plugin_list->next;
607
 
    free(plugin_list->argv);
608
 
    free(plugin_list);
 
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
    }
609
115
  }
610
116
  
611
117
  closedir(dir);
612
 
  dir = NULL;
613
 
    
614
 
  if (process_list == NULL){
615
 
    fprintf(stderr, "No plugin processes started. Incorrect plugin"
616
 
            " directory?\n");
617
 
    process_list = NULL;
618
 
  }
619
 
  while(process_list){
620
 
    fd_set rfds = rfds_all;
621
 
    int select_ret = select(maxfd+1, &rfds, NULL, NULL, NULL);
622
 
    if (select_ret == -1){
623
 
      perror("select");
624
 
      exitstatus = EXIT_FAILURE;
625
 
      goto end;
626
 
    }
627
 
    /* OK, now either a process completed, or something can be read
628
 
       from one of them */
629
 
    for(process *proc = process_list; proc ; proc = proc->next){
630
 
      /* Is this process completely done? */
631
 
      if(proc->eof and proc->completed){
632
 
        /* Only accept the plugin output if it exited cleanly */
633
 
        if(not WIFEXITED(proc->status)
634
 
           or WEXITSTATUS(proc->status) != 0){
635
 
          /* Bad exit by plugin */
636
 
          if(debug){
637
 
            if(WIFEXITED(proc->status)){
638
 
              fprintf(stderr, "Plugin %u exited with status %d\n",
639
 
                      (unsigned int) (proc->pid),
640
 
                      WEXITSTATUS(proc->status));
641
 
            } else if(WIFSIGNALED(proc->status)) {
642
 
              fprintf(stderr, "Plugin %u killed by signal %d\n",
643
 
                      (unsigned int) (proc->pid),
644
 
                      WTERMSIG(proc->status));
645
 
            } else if(WCOREDUMP(proc->status)){
646
 
              fprintf(stderr, "Plugin %d dumped core\n",
647
 
                      (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;
648
140
            }
649
 
          }
650
 
          /* Remove the plugin */
651
 
          FD_CLR(proc->fd, &rfds_all);
652
 
          /* Block signal while modifying process_list */
653
 
          ret = sigprocmask (SIG_BLOCK, &sigchld_action.sa_mask, NULL);
654
 
          if(ret < 0){
655
 
            perror("sigprocmask");
656
 
            exitstatus = EXIT_FAILURE;
657
 
            goto end;
658
 
          }
659
 
          /* Delete this process entry from the list */
660
 
          if(process_list == proc){
661
 
            /* First one - simple */
662
 
            process_list = proc->next;
663
 
          } else {
664
 
            /* Second one or later */
665
 
            for(process *p = process_list; p != NULL; p = p->next){
666
 
              if(p->next == proc){
667
 
                p->next = proc->next;
668
 
                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);
669
155
              }
670
156
            }
671
157
          }
672
 
          /* We are done modifying process list, so unblock signal */
673
 
          ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask,
674
 
                             NULL);
675
 
          if(ret < 0){
676
 
            perror("sigprocmask");
677
 
          }
678
 
          free(proc->buffer);
679
 
          free(proc);
680
 
          /* We deleted this process from the list, so we can't go
681
 
             proc->next.  Therefore, start over from the beginning of
682
 
             the process list */
683
 
          break;
684
 
        }
685
 
        /* This process exited nicely, so print its buffer */
686
 
 
687
 
        bool bret = print_out_password(proc->buffer, proc->buffer_length);
688
 
        if(not bret){
689
 
          perror("print_out_password");
690
 
          exitstatus = EXIT_FAILURE;
691
 
        }
692
 
        goto end;
693
 
      }
694
 
      /* This process has not completed.  Does it have any output? */
695
 
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){
696
 
        /* This process had nothing to say at this time */
697
 
        continue;
698
 
      }
699
 
      /* Before reading, make the process' data buffer large enough */
700
 
      if(proc->buffer_length + BUFFER_SIZE > proc->buffer_size){
701
 
        proc->buffer = realloc(proc->buffer, proc->buffer_size
702
 
                               + (size_t) BUFFER_SIZE);
703
 
        if (proc->buffer == NULL){
704
 
          perror("malloc");
705
 
          exitstatus = EXIT_FAILURE;
706
 
          goto end;
707
 
        }
708
 
        proc->buffer_size += BUFFER_SIZE;
709
 
      }
710
 
      /* Read from the process */
711
 
      ret = read(proc->fd, proc->buffer + proc->buffer_length,
712
 
                 BUFFER_SIZE);
713
 
      if(ret < 0){
714
 
        /* Read error from this process; ignore the error */
715
 
        continue;
716
 
      }
717
 
      if(ret == 0){
718
 
        /* got EOF */
719
 
        proc->eof = true;
720
 
      } else {
721
 
        proc->buffer_length += (size_t) ret;
 
158
        }
722
159
      }
723
160
    }
724
161
  }
725
 
 
726
 
 
 
162
  
727
163
 end:
728
 
  
729
 
  if(process_list == NULL or exitstatus != EXIT_SUCCESS){
730
 
    /* Fallback if all plugins failed, none are found or an error occured */
731
 
    bool bret;
732
 
    fprintf(stderr, "Going to fallback mode using getpass(3)\n");
733
 
    char *passwordbuffer = getpass("Password: ");
734
 
    bret = print_out_password(passwordbuffer, strlen(passwordbuffer));
735
 
    if(not bret){
736
 
      perror("print_out_password");
737
 
      exitstatus = EXIT_FAILURE;
738
 
      goto end;
739
 
    }
740
 
  }
741
 
  
742
 
  /* Restore old signal handler */
743
 
  sigaction(SIGCHLD, &old_sigchld_action, NULL);
744
 
  
745
 
  free(plus_argv);
746
 
  
747
 
  /* Free the plugin list */
748
 
  for(plugin *next; plugin_list != NULL; plugin_list = next){
749
 
    next = plugin_list->next;
750
 
    free(plugin_list->argv);
751
 
    free(plugin_list);
752
 
  }
753
 
  
754
 
  if(dir != NULL){
755
 
    closedir(dir);
756
 
  }
757
 
  
758
 
  /* Free the process list and kill the processes */
759
 
  for(process *next; process_list != NULL; process_list = next){
760
 
    next = process_list->next;
761
 
    close(process_list->fd);
762
 
    ret = kill(process_list->pid, SIGTERM);
763
 
    if(ret == -1 and errno != ESRCH){
764
 
      /* set-uid proccesses migth not get closed */
765
 
      perror("kill");
766
 
    }
767
 
    free(process_list->buffer);
768
 
    free(process_list);
769
 
  }
770
 
  
771
 
  /* Wait for any remaining child processes to terminate */
772
 
  do{
773
 
    ret = wait(NULL);
774
 
  } while(ret >= 0);
775
 
  if(errno != ECHILD){
776
 
    perror("wait");
777
 
  }
778
 
  
779
 
  return exitstatus;
 
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;
780
182
}