/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 plugin-runner.c

  • Committer: Teddy Hogeborn
  • Date: 2008-12-10 01:26:02 UTC
  • mfrom: (237.1.2 mandos)
  • Revision ID: teddy@fukt.bsnet.se-20081210012602-vhz3h75xkj24t340
First version of a somewhat complete D-Bus server interface.  Also
change user/group name to "_mandos".

* debian/mandos.postinst: Rename old "mandos" user and group to
                          "_mandos"; create "_mandos" user and group
                          if none exist.
* debian/mandos-client.postinst: - '' -

* initramfs-tools-hook: Try "_mandos" before "mandos" as user and
                        group name.

* mandos (_datetime_to_dbus_struct): New; was previously local.
  (Client.started): Renamed to "last_started".  All users changed.
  (Client.started): New; boolean.
  (Client.dbus_object_path): New.
  (Client.check_command): Renamed to "checker_command".  All users
                          changed.
  (Client.__init__): Set and use "self.dbus_object_path".  Set
                     "self.started".
  (Client.start): Update "self.started".  Emit "self.PropertyChanged"
                  signals for both "started" and "last_started".
  (Client.stop): Update "self.started".  Emit "self.PropertyChanged"
                 signal for "started".
  (Client.checker_callback): Take additional "command" argument.  All
                             callers changed. Emit
                             "self.PropertyChanged" signal.
  (Client.bump_timeout): Emit "self.PropertyChanged" signal for
                         "last_checked_ok".
  (Client.start_checker): Emit "self.PropertyChanged" signal for
                          "checker_running".
  (Client.stop_checker): Emit "self.PropertyChanged" signal for
                         "checker_running".
  (Client.still_valid): Bug fix: use "getattr(self, started, False)"
                        instead of "self.started" in case this client
                        object is so new that the "started" attribute
                        has not been created yet.
  (Client.IntervalChanged, Client.CheckerIsRunning, Client.GetChecker,
  Client.GetCreated, Client.GetFingerprint, Client.GetHost,
  Client.GetInterval, Client.GetName, Client.GetStarted,
  Client.GetTimeout, Client.StateChanged, Client.TimeoutChanged):
  Removed; all callers changed.
  (Client.CheckerCompleted): Add "condition" and "command" arguments.
                             All callers changed.
  (Client.GetAllProperties, Client.PropertyChanged): New.
  (Client.StillValid): Renamed to "IsStillValid".
  (Client.StartChecker): Changed to its own function to avoid the
                         return value from "Client.start_checker()".
  (Client.Stop): Changed to its own function to avoid the return value
                 from "Client.stop()".
  (main): Try "_mandos" before "mandos" as user and group name.
          Removed inner function "remove_from_clients".  New inner
          class "MandosServer".

Show diffs side-by-side

added added

removed removed

Lines of Context:
2
2
/*
3
3
 * Mandos plugin runner - Run Mandos plugins
4
4
 *
5
 
 * Copyright © 2007-2008 Teddy Hogeborn & Björn Påhlsson
 
5
 * Copyright © 2008 Teddy Hogeborn
 
6
 * Copyright © 2008 Björn Påhlsson
6
7
 * 
7
8
 * This program is free software: you can redistribute it and/or
8
9
 * modify it under the terms of the GNU General Public License as
27
28
#include <stdlib.h>             /* malloc(), exit(), EXIT_FAILURE,
28
29
                                   EXIT_SUCCESS, realloc() */
29
30
#include <stdbool.h>            /* bool, true, false */
30
 
#include <stdio.h>              /* perror, popen(), fileno(),
31
 
                                   fprintf(), stderr, STDOUT_FILENO */
 
31
#include <stdio.h>              /* perror, fileno(), fprintf(),
 
32
                                   stderr, STDOUT_FILENO */
32
33
#include <sys/types.h>          /* DIR, opendir(), stat(), struct
33
34
                                   stat, waitpid(), WIFEXITED(),
34
35
                                   WEXITSTATUS(), wait(), pid_t,
46
47
                                   fcntl(), setuid(), setgid(),
47
48
                                   F_GETFD, F_SETFD, FD_CLOEXEC,
48
49
                                   access(), pipe(), fork(), close()
49
 
                                   dup2, STDOUT_FILENO, _exit(),
 
50
                                   dup2(), STDOUT_FILENO, _exit(),
50
51
                                   execv(), write(), read(),
51
52
                                   close() */
52
53
#include <fcntl.h>              /* fcntl(), F_GETFD, F_SETFD,
56
57
#include <argp.h>               /* struct argp_option, struct
57
58
                                   argp_state, struct argp,
58
59
                                   argp_parse(), ARGP_ERR_UNKNOWN,
59
 
                                   ARGP_KEY_END, ARGP_KEY_ARG, error_t */
 
60
                                   ARGP_KEY_END, ARGP_KEY_ARG,
 
61
                                   error_t */
60
62
#include <signal.h>             /* struct sigaction, sigemptyset(),
61
63
                                   sigaddset(), sigaction(),
62
64
                                   sigprocmask(), SIG_BLOCK, SIGCHLD,
64
66
#include <errno.h>              /* errno, EBADF */
65
67
 
66
68
#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";
 
69
 
 
70
#define PDIR "/lib/mandos/plugins.d"
 
71
#define AFILE "/conf/conf.d/mandos/plugin-runner.conf"
 
72
 
 
73
const char *argp_program_version = "plugin-runner " VERSION;
70
74
const char *argp_program_bug_address = "<mandos@fukt.bsnet.se>";
71
75
 
72
 
struct process;
73
 
 
74
 
typedef struct process{
75
 
  pid_t pid;
76
 
  int fd;
77
 
  char *buffer;
78
 
  size_t buffer_size;
79
 
  size_t buffer_length;
80
 
  bool eof;
81
 
  bool completed;
82
 
  int status;
83
 
  struct process *next;
84
 
} process;
85
 
 
86
76
typedef struct plugin{
87
77
  char *name;                   /* can be NULL or any plugin name */
88
78
  char **argv;
90
80
  char **environ;
91
81
  int envc;
92
82
  bool disabled;
 
83
 
 
84
  /* Variables used for running processes*/
 
85
  pid_t pid;
 
86
  int fd;
 
87
  char *buffer;
 
88
  size_t buffer_size;
 
89
  size_t buffer_length;
 
90
  bool eof;
 
91
  volatile bool completed;
 
92
  volatile int status;
93
93
  struct plugin *next;
94
94
} plugin;
95
95
 
96
 
static plugin *getplugin(char *name, plugin **plugin_list){
97
 
  for (plugin *p = *plugin_list; p != NULL; p = p->next){
 
96
static plugin *plugin_list = NULL;
 
97
 
 
98
/* Gets an existing plugin based on name,
 
99
   or if none is found, creates a new one */
 
100
static plugin *getplugin(char *name){
 
101
  /* Check for exiting plugin with that name */
 
102
  for (plugin *p = plugin_list; p != NULL; p = p->next){
98
103
    if ((p->name == name)
99
104
        or (p->name and name and (strcmp(p->name, name) == 0))){
100
105
      return p;
108
113
  char *copy_name = NULL;
109
114
  if(name != NULL){
110
115
    copy_name = strdup(name);
111
 
  }
112
 
  if(copy_name == NULL){
113
 
    return NULL;
 
116
    if(copy_name == NULL){
 
117
      return NULL;
 
118
    }
114
119
  }
115
120
  
116
121
  *new_plugin = (plugin) { .name = copy_name,
117
122
                           .argc = 1,
118
 
                           .envc = 0,
119
123
                           .disabled = false,
120
 
                           .next = *plugin_list };
 
124
                           .next = plugin_list };
121
125
  
122
126
  new_plugin->argv = malloc(sizeof(char *) * 2);
123
127
  if (new_plugin->argv == NULL){
127
131
  }
128
132
  new_plugin->argv[0] = copy_name;
129
133
  new_plugin->argv[1] = NULL;
130
 
 
 
134
  
131
135
  new_plugin->environ = malloc(sizeof(char *));
132
136
  if(new_plugin->environ == NULL){
133
137
    free(copy_name);
136
140
    return NULL;
137
141
  }
138
142
  new_plugin->environ[0] = NULL;
 
143
  
139
144
  /* Append the new plugin to the list */
140
 
  *plugin_list = new_plugin;
 
145
  plugin_list = new_plugin;
141
146
  return new_plugin;
142
147
}
143
148
 
173
178
}
174
179
 
175
180
/* Add to a plugin's environment */
176
 
static bool add_environment(plugin *p, const char *def){
 
181
static bool add_environment(plugin *p, const char *def, bool replace){
177
182
  if(p == NULL){
178
183
    return false;
179
184
  }
 
185
  /* namelen = length of name of environment variable */
 
186
  size_t namelen = (size_t)(strchrnul(def, '=') - def);
 
187
  /* Search for this environment variable */
 
188
  for(char **e = p->environ; *e != NULL; e++){
 
189
    if(strncmp(*e, def, namelen + 1) == 0){
 
190
      /* It already exists */
 
191
      if(replace){
 
192
        char *new = realloc(*e, strlen(def) + 1);
 
193
        if(new == NULL){
 
194
          return false;
 
195
        }
 
196
        *e = new;
 
197
        strcpy(*e, def);
 
198
      }
 
199
      return true;
 
200
    }
 
201
  }
180
202
  return add_to_char_array(def, &(p->environ), &(p->envc));
181
203
}
182
204
 
183
 
 
184
205
/*
185
206
 * Based on the example in the GNU LibC manual chapter 13.13 "File
186
207
 * Descriptor Flags".
187
208
 * *Note File Descriptor Flags:(libc)Descriptor Flags.
188
209
 */
189
 
static int set_cloexec_flag(int fd)
190
 
{
 
210
static int set_cloexec_flag(int fd){
191
211
  int ret = fcntl(fd, F_GETFD, 0);
192
212
  /* If reading the flags failed, return error indication now. */
193
213
  if(ret < 0){
197
217
  return fcntl(fd, F_SETFD, ret | FD_CLOEXEC);
198
218
}
199
219
 
200
 
process *process_list = NULL;
201
220
 
202
 
/* Mark a process as completed when it exits, and save its exit
 
221
/* Mark processes as completed when they exit, and save their exit
203
222
   status. */
204
 
void handle_sigchld(__attribute__((unused)) int sig){
205
 
  process *proc = process_list;
206
 
  int status;
207
 
  pid_t pid = wait(&status);
208
 
  if(pid == -1){
209
 
    perror("wait");
210
 
    return;
211
 
  }
212
 
  while(proc != NULL and proc->pid != pid){
213
 
    proc = proc->next;
214
 
  }
215
 
  if(proc == NULL){
216
 
    /* Process not found in process list */
217
 
    return;
218
 
  }
219
 
  proc->status = status;
220
 
  proc->completed = true;
 
223
static void handle_sigchld(__attribute__((unused)) int sig){
 
224
  while(true){
 
225
    plugin *proc = plugin_list;
 
226
    int status;
 
227
    pid_t pid = waitpid(-1, &status, WNOHANG);
 
228
    if(pid == 0){
 
229
      /* Only still running child processes */
 
230
      break;
 
231
    }
 
232
    if(pid == -1){
 
233
      if (errno != ECHILD){
 
234
        perror("waitpid");
 
235
      }
 
236
      /* No child processes */
 
237
      break;
 
238
    }
 
239
    
 
240
    /* A child exited, find it in process_list */
 
241
    while(proc != NULL and proc->pid != pid){
 
242
      proc = proc->next;
 
243
    }
 
244
    if(proc == NULL){
 
245
      /* Process not found in process list */
 
246
      continue;
 
247
    }
 
248
    proc->status = status;
 
249
    proc->completed = true;
 
250
  }
221
251
}
222
252
 
223
 
bool print_out_password(const char *buffer, size_t length){
 
253
/* Prints out a password to stdout */
 
254
static bool print_out_password(const char *buffer, size_t length){
224
255
  ssize_t ret;
225
 
  if(length>0 and buffer[length-1] == '\n'){
226
 
    length--;
227
 
  }
228
256
  for(size_t written = 0; written < length; written += (size_t)ret){
229
257
    ret = TEMP_FAILURE_RETRY(write(STDOUT_FILENO, buffer + written,
230
258
                                   length - written));
235
263
  return true;
236
264
}
237
265
 
238
 
char **add_to_argv(char **argv, int *argc, char *arg){
239
 
  if (argv == NULL){
240
 
    *argc = 1;
241
 
    argv = malloc(sizeof(char*) * 2);
242
 
    if(argv == NULL){
243
 
      return NULL;
 
266
/* Removes and free a plugin from the plugin list */
 
267
static void free_plugin(plugin *plugin_node){
 
268
  
 
269
  for(char **arg = plugin_node->argv; *arg != NULL; arg++){
 
270
    free(*arg);
 
271
  }
 
272
  free(plugin_node->argv);
 
273
  for(char **env = plugin_node->environ; *env != NULL; env++){
 
274
    free(*env);
 
275
  }
 
276
  free(plugin_node->environ);
 
277
  free(plugin_node->buffer);
 
278
 
 
279
  /* Removes the plugin from the singly-linked list */
 
280
  if(plugin_node == plugin_list){
 
281
    /* First one - simple */
 
282
    plugin_list = plugin_list->next;
 
283
  } else {
 
284
    /* Second one or later */
 
285
    for(plugin *p = plugin_list; p != NULL; p = p->next){
 
286
      if(p->next == plugin_node){
 
287
        p->next = plugin_node->next;
 
288
        break;
 
289
      }
244
290
    }
245
 
    argv[0] = NULL;     /* Will be set to argv[0] in main before parsing */
246
 
    argv[1] = NULL;
247
 
  }
248
 
  *argc += 1;
249
 
  argv = realloc(argv, sizeof(char *)
250
 
                  * ((unsigned int) *argc + 1));
251
 
  if(argv == NULL){
252
 
    return NULL;
253
 
  }
254
 
  argv[*argc-1] = arg;
255
 
  argv[*argc] = NULL;   
256
 
  return argv;
 
291
  }
 
292
  
 
293
  free(plugin_node);
257
294
}
258
295
 
259
 
static void free_plugin_list(plugin *plugin_list){
260
 
  for(plugin *next = plugin_list; plugin_list != NULL; plugin_list = next){
261
 
    next = plugin_list->next;
262
 
    free(plugin_list->name);
263
 
    for(char **arg = plugin_list->argv; *arg != NULL; arg++){
264
 
      free(*arg);
265
 
    }    
266
 
    free(plugin_list->argv);
267
 
    for(char **env = plugin_list->environ; *env != NULL; env++){
268
 
      free(*env);
269
 
    }
270
 
    free(plugin_list->environ);
271
 
    free(plugin_list);
272
 
  }  
 
296
static void free_plugin_list(void){
 
297
  while(plugin_list != NULL){
 
298
    free_plugin(plugin_list);
 
299
  }
273
300
}
274
301
 
275
302
int main(int argc, char *argv[]){
276
 
  const char *plugindir = "/lib/mandos/plugins.d";
277
 
  const char *argfile = ARGFILE;
 
303
  char *plugindir = NULL;
 
304
  char *argfile = NULL;
278
305
  FILE *conffp;
279
306
  size_t d_name_len;
280
307
  DIR *dir = NULL;
303
330
  ret = sigaction(SIGCHLD, &sigchld_action, &old_sigchld_action);
304
331
  if(ret == -1){
305
332
    perror("sigaction");
306
 
    exitstatus = EXIT_FAILURE;    
 
333
    exitstatus = EXIT_FAILURE;
307
334
    goto fallback;
308
335
  }
309
336
  
312
339
    { .name = "global-options", .key = 'g',
313
340
      .arg = "OPTION[,OPTION[,...]]",
314
341
      .doc = "Options passed to all plugins" },
315
 
    { .name = "global-envs", .key = 'e',
 
342
    { .name = "global-env", .key = 'G',
316
343
      .arg = "VAR=value",
317
344
      .doc = "Environment variable passed to all plugins" },
318
345
    { .name = "options-for", .key = 'o',
319
346
      .arg = "PLUGIN:OPTION[,OPTION[,...]]",
320
347
      .doc = "Options passed only to specified plugin" },
321
 
    { .name = "envs-for", .key = 'f',
 
348
    { .name = "env-for", .key = 'E',
322
349
      .arg = "PLUGIN:ENV=value",
323
350
      .doc = "Environment variable passed to specified plugin" },
324
351
    { .name = "disable", .key = 'd',
325
352
      .arg = "PLUGIN",
326
353
      .doc = "Disable a specific plugin", .group = 1 },
 
354
    { .name = "enable", .key = 'e',
 
355
      .arg = "PLUGIN",
 
356
      .doc = "Enable a specific plugin", .group = 1 },
327
357
    { .name = "plugin-dir", .key = 128,
328
358
      .arg = "DIRECTORY",
329
359
      .doc = "Specify a different plugin directory", .group = 2 },
330
 
    { .name = "userid", .key = 129,
331
 
      .arg = "ID", .flags = 0,
332
 
      .doc = "User ID the plugins will run as", .group = 2 },
333
 
    { .name = "groupid", .key = 130,
334
 
      .arg = "ID", .flags = 0,
335
 
      .doc = "Group ID the plugins will run as", .group = 2 },
336
 
    { .name = "debug", .key = 131,
337
 
      .doc = "Debug mode", .group = 3 },
 
360
    { .name = "config-file", .key = 129,
 
361
      .arg = "FILE",
 
362
      .doc = "Specify a different configuration file", .group = 2 },
 
363
    { .name = "userid", .key = 130,
 
364
      .arg = "ID", .flags = 0,
 
365
      .doc = "User ID the plugins will run as", .group = 3 },
 
366
    { .name = "groupid", .key = 131,
 
367
      .arg = "ID", .flags = 0,
 
368
      .doc = "Group ID the plugins will run as", .group = 3 },
 
369
    { .name = "debug", .key = 132,
 
370
      .doc = "Debug mode", .group = 4 },
338
371
    { .name = NULL }
339
372
  };
340
373
  
341
 
  error_t parse_opt (int key, char *arg, struct argp_state *state) {
342
 
    /* Get the INPUT argument from `argp_parse', which we know is a
343
 
       pointer to our plugin list pointer. */
344
 
    plugin **plugins = state->input;
 
374
  error_t parse_opt (int key, char *arg, __attribute__((unused))
 
375
                     struct argp_state *state) {
345
376
    switch (key) {
346
 
    case 'g':
 
377
    case 'g':                   /* --global-options */
347
378
      if (arg != NULL){
348
379
        char *p;
349
380
        while((p = strsep(&arg, ",")) != NULL){
350
381
          if(p[0] == '\0'){
351
382
            continue;
352
383
          }
353
 
          if(not add_argument(getplugin(NULL, plugins), p)){
 
384
          if(not add_argument(getplugin(NULL), p)){
354
385
            perror("add_argument");
355
386
            return ARGP_ERR_UNKNOWN;
356
387
          }
357
388
        }
358
389
      }
359
390
      break;
360
 
    case 'e':
 
391
    case 'G':                   /* --global-env */
361
392
      if(arg == NULL){
362
393
        break;
363
394
      }
364
 
      {
365
 
        char *envdef = strdup(arg);
366
 
        if(envdef == NULL){
367
 
          break;
368
 
        }
369
 
        if(not add_environment(getplugin(NULL, plugins), envdef)){
370
 
          perror("add_environment");
371
 
        }
 
395
      if(not add_environment(getplugin(NULL), arg, true)){
 
396
        perror("add_environment");
372
397
      }
373
398
      break;
374
 
    case 'o':
 
399
    case 'o':                   /* --options-for */
375
400
      if (arg != NULL){
376
401
        char *p_name = strsep(&arg, ":");
377
 
        if(p_name[0] == '\0'){
 
402
        if(p_name[0] == '\0' or arg == NULL){
378
403
          break;
379
404
        }
380
405
        char *opt = strsep(&arg, ":");
381
 
        if(opt[0] == '\0'){
 
406
        if(opt[0] == '\0' or opt == NULL){
382
407
          break;
383
408
        }
384
 
        if(opt != NULL){
385
 
          char *p;
386
 
          while((p = strsep(&opt, ",")) != NULL){
387
 
            if(p[0] == '\0'){
388
 
              continue;
389
 
            }
390
 
            if(not add_argument(getplugin(p_name, plugins), p)){
391
 
              perror("add_argument");
392
 
              return ARGP_ERR_UNKNOWN;
393
 
            }
 
409
        char *p;
 
410
        while((p = strsep(&opt, ",")) != NULL){
 
411
          if(p[0] == '\0'){
 
412
            continue;
 
413
          }
 
414
          if(not add_argument(getplugin(p_name), p)){
 
415
            perror("add_argument");
 
416
            return ARGP_ERR_UNKNOWN;
394
417
          }
395
418
        }
396
419
      }
397
420
      break;
398
 
    case 'f':
 
421
    case 'E':                   /* --env-for */
399
422
      if(arg == NULL){
400
423
        break;
401
424
      }
404
427
        if(envdef == NULL){
405
428
          break;
406
429
        }
407
 
        char *p_name = strndup(arg, (size_t) (envdef-arg));
408
 
        if(p_name == NULL){
409
 
          break;
410
 
        }
411
 
        envdef++;
412
 
        if(not add_environment(getplugin(p_name, plugins), envdef)){
 
430
        *envdef = '\0';
 
431
        if(not add_environment(getplugin(arg), envdef+1, true)){
413
432
          perror("add_environment");
414
433
        }
415
434
      }
416
435
      break;
417
 
    case 'd':
 
436
    case 'd':                   /* --disable */
418
437
      if (arg != NULL){
419
 
        plugin *p = getplugin(arg, plugins);
 
438
        plugin *p = getplugin(arg);
420
439
        if(p == NULL){
421
440
          return ARGP_ERR_UNKNOWN;
422
441
        }
423
442
        p->disabled = true;
424
443
      }
425
444
      break;
426
 
    case 128:
427
 
      plugindir = arg;
428
 
      break;
429
 
    case 129:
 
445
    case 'e':                   /* --enable */
 
446
      if (arg != NULL){
 
447
        plugin *p = getplugin(arg);
 
448
        if(p == NULL){
 
449
          return ARGP_ERR_UNKNOWN;
 
450
        }
 
451
        p->disabled = false;
 
452
      }
 
453
      break;
 
454
    case 128:                   /* --plugin-dir */
 
455
      free(plugindir);
 
456
      plugindir = strdup(arg);
 
457
      if(plugindir == NULL){
 
458
        perror("strdup");
 
459
      }      
 
460
      break;
 
461
    case 129:                   /* --config-file */
 
462
      /* This is already done by parse_opt_config_file() */
 
463
      break;
 
464
    case 130:                   /* --userid */
430
465
      uid = (uid_t)strtol(arg, NULL, 10);
431
466
      break;
432
 
    case 130:
 
467
    case 131:                   /* --groupid */
433
468
      gid = (gid_t)strtol(arg, NULL, 10);
434
469
      break;
435
 
    case 131:
 
470
    case 132:                   /* --debug */
436
471
      debug = true;
437
472
      break;
438
473
    case ARGP_KEY_ARG:
439
 
      fprintf(stderr, "Ignoring unknown argument \"%s\"\n", arg);
440
 
      break;
441
 
    case ARGP_KEY_END:
442
 
      break;
443
 
    default:
444
 
      return ARGP_ERR_UNKNOWN;
445
 
    }
446
 
    return 0;
447
 
  }
448
 
  
449
 
  plugin *plugin_list = NULL;
450
 
  
451
 
  struct argp argp = { .options = options, .parser = parse_opt,
452
 
                       .args_doc = "[+PLUS_SEPARATED_OPTIONS]",
 
474
      /* Cryptsetup always passes an argument, which is an empty
 
475
         string if "none" was specified in /etc/crypttab.  So if
 
476
         argument was empty, we ignore it silently. */
 
477
      if(arg[0] != '\0'){
 
478
        fprintf(stderr, "Ignoring unknown argument \"%s\"\n", arg);
 
479
      }
 
480
      break;
 
481
    case ARGP_KEY_END:
 
482
      break;
 
483
    default:
 
484
      return ARGP_ERR_UNKNOWN;
 
485
    }
 
486
    return 0;
 
487
  }
 
488
  
 
489
  /* This option parser is the same as parse_opt() above, except it
 
490
     ignores everything but the --config-file option. */
 
491
  error_t parse_opt_config_file (int key, char *arg,
 
492
                                 __attribute__((unused))
 
493
                                 struct argp_state *state) {
 
494
    switch (key) {
 
495
    case 'g':                   /* --global-options */
 
496
    case 'G':                   /* --global-env */
 
497
    case 'o':                   /* --options-for */
 
498
    case 'E':                   /* --env-for */
 
499
    case 'd':                   /* --disable */
 
500
    case 'e':                   /* --enable */
 
501
    case 128:                   /* --plugin-dir */
 
502
      break;
 
503
    case 129:                   /* --config-file */
 
504
      free(argfile);
 
505
      argfile = strdup(arg);
 
506
      if(argfile == NULL){
 
507
        perror("strdup");
 
508
      }
 
509
      break;      
 
510
    case 130:                   /* --userid */
 
511
    case 131:                   /* --groupid */
 
512
    case 132:                   /* --debug */
 
513
    case ARGP_KEY_ARG:
 
514
    case ARGP_KEY_END:
 
515
      break;
 
516
    default:
 
517
      return ARGP_ERR_UNKNOWN;
 
518
    }
 
519
    return 0;
 
520
  }
 
521
  
 
522
  struct argp argp = { .options = options,
 
523
                       .parser = parse_opt_config_file,
 
524
                       .args_doc = "",
453
525
                       .doc = "Mandos plugin runner -- Run plugins" };
454
526
  
455
 
  ret = argp_parse (&argp, argc, argv, 0, 0, &plugin_list);
 
527
  /* Parse using the parse_opt_config_file in order to get the custom
 
528
     config file location, if any. */
 
529
  ret = argp_parse (&argp, argc, argv, ARGP_IN_ORDER, 0, NULL);
456
530
  if (ret == ARGP_ERR_UNKNOWN){
457
531
    fprintf(stderr, "Unknown error while parsing arguments\n");
458
532
    exitstatus = EXIT_FAILURE;
459
533
    goto fallback;
460
534
  }
461
 
 
462
 
  conffp = fopen(argfile, "r");
 
535
  
 
536
  /* Reset to the normal argument parser */
 
537
  argp.parser = parse_opt;
 
538
  
 
539
  /* Open the configfile if available */
 
540
  if (argfile == NULL){
 
541
    conffp = fopen(AFILE, "r");
 
542
  } else {
 
543
    conffp = fopen(argfile, "r");
 
544
  }  
463
545
  if(conffp != NULL){
464
546
    char *org_line = NULL;
465
547
    char *p, *arg, *new_arg, *line;
468
550
    const char whitespace_delims[] = " \r\t\f\v\n";
469
551
    const char comment_delim[] = "#";
470
552
 
 
553
    custom_argc = 1;
 
554
    custom_argv = malloc(sizeof(char*) * 2);
 
555
    if(custom_argv == NULL){
 
556
      perror("malloc");
 
557
      exitstatus = EXIT_FAILURE;
 
558
      goto fallback;
 
559
    }
 
560
    custom_argv[0] = argv[0];
 
561
    custom_argv[1] = NULL;
 
562
 
 
563
    /* for each line in the config file, strip whitespace and ignore
 
564
       commented text */
471
565
    while(true){
472
566
      sret = getline(&org_line, &size, conffp);
473
567
      if(sret == -1){
481
575
          continue;
482
576
        }
483
577
        new_arg = strdup(p);
484
 
        custom_argv = add_to_argv(custom_argv, &custom_argc, new_arg);
485
 
        if (custom_argv == NULL){
486
 
          perror("add_to_argv");
487
 
          exitstatus = EXIT_FAILURE;
488
 
          goto fallback;
489
 
        }
 
578
        if(new_arg == NULL){
 
579
          perror("strdup");
 
580
          exitstatus = EXIT_FAILURE;
 
581
          free(org_line);
 
582
          goto fallback;
 
583
        }
 
584
        
 
585
        custom_argc += 1;
 
586
        custom_argv = realloc(custom_argv, sizeof(char *)
 
587
                              * ((unsigned int) custom_argc + 1));
 
588
        if(custom_argv == NULL){
 
589
          perror("realloc");
 
590
          exitstatus = EXIT_FAILURE;
 
591
          free(org_line);
 
592
          goto fallback;
 
593
        }
 
594
        custom_argv[custom_argc-1] = new_arg;
 
595
        custom_argv[custom_argc] = NULL;        
490
596
      }
491
597
    }
492
598
    free(org_line);
493
 
  } else{
 
599
  } else {
494
600
    /* Check for harmful errors and go to fallback. Other errors might
495
601
       not affect opening plugins */
496
602
    if (errno == EMFILE or errno == ENFILE or errno == ENOMEM){
499
605
      goto fallback;
500
606
    }
501
607
  }
502
 
 
 
608
  /* If there was any arguments from configuration file,
 
609
     pass them to parser as command arguments */
503
610
  if(custom_argv != NULL){
504
 
    custom_argv[0] = argv[0];
505
 
    ret = argp_parse (&argp, custom_argc, custom_argv, 0, 0, &plugin_list);
 
611
    ret = argp_parse (&argp, custom_argc, custom_argv, ARGP_IN_ORDER,
 
612
                      0, NULL);
506
613
    if (ret == ARGP_ERR_UNKNOWN){
507
614
      fprintf(stderr, "Unknown error while parsing arguments\n");
508
615
      exitstatus = EXIT_FAILURE;
510
617
    }
511
618
  }
512
619
  
 
620
  /* Parse actual command line arguments, to let them override the
 
621
     config file */
 
622
  ret = argp_parse (&argp, argc, argv, ARGP_IN_ORDER, 0, NULL);
 
623
  if (ret == ARGP_ERR_UNKNOWN){
 
624
    fprintf(stderr, "Unknown error while parsing arguments\n");
 
625
    exitstatus = EXIT_FAILURE;
 
626
    goto fallback;
 
627
  }
 
628
  
513
629
  if(debug){
514
630
    for(plugin *p = plugin_list; p != NULL; p=p->next){
515
631
      fprintf(stderr, "Plugin: %s has %d arguments\n",
524
640
    }
525
641
  }
526
642
  
 
643
  /* Strip permissions down to nobody */
527
644
  ret = setuid(uid);
528
645
  if (ret == -1){
529
646
    perror("setuid");
530
 
  }
531
 
  
 
647
  }  
532
648
  setgid(gid);
533
649
  if (ret == -1){
534
650
    perror("setgid");
535
651
  }
536
652
  
537
 
  dir = opendir(plugindir);
 
653
  if (plugindir == NULL){
 
654
    dir = opendir(PDIR);
 
655
  } else {
 
656
    dir = opendir(plugindir);
 
657
  }
 
658
  
538
659
  if(dir == NULL){
539
660
    perror("Could not open plugin dir");
540
661
    exitstatus = EXIT_FAILURE;
556
677
  
557
678
  FD_ZERO(&rfds_all);
558
679
  
 
680
  /* Read and execute any executable in the plugin directory*/
559
681
  while(true){
560
682
    dirst = readdir(dir);
561
683
    
562
 
    // All directory entries have been processed
 
684
    /* All directory entries have been processed */
563
685
    if(dirst == NULL){
564
686
      if (errno == EBADF){
565
687
        perror("readdir");
571
693
    
572
694
    d_name_len = strlen(dirst->d_name);
573
695
    
574
 
    // Ignore dotfiles, backup files and other junk
 
696
    /* Ignore dotfiles, backup files and other junk */
575
697
    {
576
698
      bool bad_name = false;
577
699
      
579
701
      
580
702
      const char const *bad_suffixes[] = { "~", "#", ".dpkg-new",
581
703
                                           ".dpkg-old",
 
704
                                           ".dpkg-bak",
582
705
                                           ".dpkg-divert", NULL };
583
706
      for(const char **pre = bad_prefixes; *pre != NULL; pre++){
584
707
        size_t pre_len = strlen(*pre);
592
715
          break;
593
716
        }
594
717
      }
595
 
      
596
718
      if(bad_name){
597
719
        continue;
598
720
      }
599
 
      
600
721
      for(const char **suf = bad_suffixes; *suf != NULL; suf++){
601
722
        size_t suf_len = strlen(*suf);
602
723
        if((d_name_len >= suf_len)
617
738
    }
618
739
 
619
740
    char *filename;
620
 
    ret = asprintf(&filename, "%s/%s", plugindir, dirst->d_name);
 
741
    if(plugindir == NULL){
 
742
      ret = asprintf(&filename, PDIR "/%s", dirst->d_name);
 
743
    } else {
 
744
      ret = asprintf(&filename, "%s/%s", plugindir, dirst->d_name);
 
745
    }
621
746
    if(ret < 0){
622
747
      perror("asprintf");
623
748
      continue;
629
754
      free(filename);
630
755
      continue;
631
756
    }
632
 
    
 
757
 
 
758
    /* Ignore non-executable files */
633
759
    if (not S_ISREG(st.st_mode) or (access(filename, X_OK) != 0)){
634
760
      if(debug){
635
761
        fprintf(stderr, "Ignoring plugin dir entry \"%s\""
638
764
      free(filename);
639
765
      continue;
640
766
    }
641
 
    plugin *p = getplugin(dirst->d_name, &plugin_list);
 
767
    
 
768
    plugin *p = getplugin(dirst->d_name);
642
769
    if(p == NULL){
643
770
      perror("getplugin");
644
771
      free(filename);
654
781
    }
655
782
    {
656
783
      /* Add global arguments to argument list for this plugin */
657
 
      plugin *g = getplugin(NULL, &plugin_list);
 
784
      plugin *g = getplugin(NULL);
658
785
      if(g != NULL){
659
786
        for(char **a = g->argv + 1; *a != NULL; a++){
660
787
          if(not add_argument(p, *a)){
663
790
        }
664
791
        /* Add global environment variables */
665
792
        for(char **e = g->environ; *e != NULL; e++){
666
 
          if(not add_environment(p, *e)){
 
793
          if(not add_environment(p, *e, false)){
667
794
            perror("add_environment");
668
795
          }
669
796
        }
674
801
       process, too. */
675
802
    if(p->environ[0] != NULL){
676
803
      for(char **e = environ; *e != NULL; e++){
677
 
        char *copy = strdup(*e);
678
 
        if(copy == NULL){
679
 
          perror("strdup");
680
 
          continue;
681
 
        }
682
 
        if(not add_environment(p, copy)){
 
804
        if(not add_environment(p, *e, false)){
683
805
          perror("add_environment");
684
806
        }
685
807
      }
686
808
    }
687
809
    
688
 
    int pipefd[2]; 
 
810
    int pipefd[2];
689
811
    ret = pipe(pipefd);
690
812
    if (ret == -1){
691
813
      perror("pipe");
692
814
      exitstatus = EXIT_FAILURE;
693
815
      goto fallback;
694
816
    }
 
817
    /* Ask OS to automatic close the pipe on exec */
695
818
    ret = set_cloexec_flag(pipefd[0]);
696
819
    if(ret < 0){
697
820
      perror("set_cloexec_flag");
711
834
      exitstatus = EXIT_FAILURE;
712
835
      goto fallback;
713
836
    }
714
 
    // Starting a new process to be watched
 
837
    /* Starting a new process to be watched */
715
838
    pid_t pid = fork();
716
839
    if(pid == -1){
717
840
      perror("fork");
725
848
        perror("sigaction");
726
849
        _exit(EXIT_FAILURE);
727
850
      }
728
 
      ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
 
851
      ret = sigprocmask(SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
729
852
      if(ret < 0){
730
853
        perror("sigprocmask");
731
854
        _exit(EXIT_FAILURE);
732
855
      }
733
 
 
 
856
      
734
857
      ret = dup2(pipefd[1], STDOUT_FILENO); /* replace our stdout */
735
858
      if(ret == -1){
736
859
        perror("dup2");
755
878
      }
756
879
      /* no return */
757
880
    }
758
 
    /* parent process */
 
881
    /* Parent process */
 
882
    close(pipefd[1]);           /* Close unused write end of pipe */
759
883
    free(filename);
760
 
    close(pipefd[1]);           /* close unused write end of pipe */
761
 
    process *new_process = malloc(sizeof(process));
762
 
    if (new_process == NULL){
763
 
      perror("malloc");
 
884
    plugin *new_plugin = getplugin(dirst->d_name);
 
885
    if (new_plugin == NULL){
 
886
      perror("getplugin");
764
887
      ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
765
888
      if(ret < 0){
766
 
        perror("sigprocmask");
 
889
        perror("sigprocmask");
767
890
      }
768
891
      exitstatus = EXIT_FAILURE;
769
892
      goto fallback;
770
893
    }
771
894
    
772
 
    *new_process = (struct process){ .pid = pid,
773
 
                                     .fd = pipefd[0],
774
 
                                     .next = process_list };
775
 
    // List handling
776
 
    process_list = new_process;
 
895
    new_plugin->pid = pid;
 
896
    new_plugin->fd = pipefd[0];
 
897
    
777
898
    /* Unblock SIGCHLD so signal handler can be run if this process
778
899
       has already completed */
779
900
    ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
783
904
      goto fallback;
784
905
    }
785
906
    
786
 
    FD_SET(new_process->fd, &rfds_all);
 
907
    FD_SET(new_plugin->fd, &rfds_all);
787
908
    
788
 
    if (maxfd < new_process->fd){
789
 
      maxfd = new_process->fd;
 
909
    if (maxfd < new_plugin->fd){
 
910
      maxfd = new_plugin->fd;
790
911
    }
791
 
    
792
912
  }
793
913
  
794
 
  free_plugin_list(plugin_list);
795
 
  
796
914
  closedir(dir);
797
915
  dir = NULL;
798
 
    
799
 
  if (process_list == NULL){
800
 
    fprintf(stderr, "No plugin processes started. Incorrect plugin"
801
 
            " directory?\n");
802
 
    process_list = NULL;
 
916
  
 
917
  for(plugin *p = plugin_list; p != NULL; p = p->next){
 
918
    if(p->pid != 0){
 
919
      break;
 
920
    }
 
921
    if(p->next == NULL){
 
922
      fprintf(stderr, "No plugin processes started. Incorrect plugin"
 
923
              " directory?\n");
 
924
      free_plugin_list();
 
925
    }
803
926
  }
804
 
  while(process_list){
 
927
  
 
928
  /* Main loop while running plugins exist */
 
929
  while(plugin_list){
805
930
    fd_set rfds = rfds_all;
806
931
    int select_ret = select(maxfd+1, &rfds, NULL, NULL, NULL);
807
932
    if (select_ret == -1){
811
936
    }
812
937
    /* OK, now either a process completed, or something can be read
813
938
       from one of them */
814
 
    for(process *proc = process_list; proc ; proc = proc->next){
 
939
    for(plugin *proc = plugin_list; proc != NULL;){
815
940
      /* Is this process completely done? */
816
941
      if(proc->eof and proc->completed){
817
942
        /* Only accept the plugin output if it exited cleanly */
818
943
        if(not WIFEXITED(proc->status)
819
944
           or WEXITSTATUS(proc->status) != 0){
820
945
          /* Bad exit by plugin */
 
946
 
821
947
          if(debug){
822
948
            if(WIFEXITED(proc->status)){
823
949
              fprintf(stderr, "Plugin %u exited with status %d\n",
832
958
                      (unsigned int) (proc->pid));
833
959
            }
834
960
          }
 
961
          
835
962
          /* Remove the plugin */
836
963
          FD_CLR(proc->fd, &rfds_all);
 
964
 
837
965
          /* Block signal while modifying process_list */
838
 
          ret = sigprocmask (SIG_BLOCK, &sigchld_action.sa_mask, NULL);
 
966
          ret = sigprocmask(SIG_BLOCK, &sigchld_action.sa_mask, NULL);
839
967
          if(ret < 0){
840
968
            perror("sigprocmask");
841
969
            exitstatus = EXIT_FAILURE;
842
970
            goto fallback;
843
971
          }
844
 
          /* Delete this process entry from the list */
845
 
          if(process_list == proc){
846
 
            /* First one - simple */
847
 
            process_list = proc->next;
848
 
          } else {
849
 
            /* Second one or later */
850
 
            for(process *p = process_list; p != NULL; p = p->next){
851
 
              if(p->next == proc){
852
 
                p->next = proc->next;
853
 
                break;
854
 
              }
855
 
            }
856
 
          }
 
972
          
 
973
          plugin *next_plugin = proc->next;
 
974
          free_plugin(proc);
 
975
          proc = next_plugin;
 
976
          
857
977
          /* We are done modifying process list, so unblock signal */
858
978
          ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask,
859
979
                             NULL);
860
980
          if(ret < 0){
861
981
            perror("sigprocmask");
862
 
          }
863
 
          free(proc->buffer);
864
 
          free(proc);
865
 
          /* We deleted this process from the list, so we can't go
866
 
             proc->next.  Therefore, start over from the beginning of
867
 
             the process list */
868
 
          break;
 
982
            exitstatus = EXIT_FAILURE;
 
983
            goto fallback;
 
984
          }
 
985
          
 
986
          if(plugin_list == NULL){
 
987
            break;
 
988
          }
 
989
          
 
990
          continue;
869
991
        }
 
992
        
870
993
        /* This process exited nicely, so print its buffer */
871
 
 
872
 
        bool bret = print_out_password(proc->buffer, proc->buffer_length);
 
994
        
 
995
        bool bret = print_out_password(proc->buffer,
 
996
                                       proc->buffer_length);
873
997
        if(not bret){
874
998
          perror("print_out_password");
875
999
          exitstatus = EXIT_FAILURE;
876
1000
        }
877
1001
        goto fallback;
878
1002
      }
 
1003
      
879
1004
      /* This process has not completed.  Does it have any output? */
880
1005
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){
881
1006
        /* This process had nothing to say at this time */
 
1007
        proc = proc->next;
882
1008
        continue;
883
1009
      }
884
1010
      /* Before reading, make the process' data buffer large enough */
897
1023
                 BUFFER_SIZE);
898
1024
      if(ret < 0){
899
1025
        /* Read error from this process; ignore the error */
 
1026
        proc = proc->next;
900
1027
        continue;
901
1028
      }
902
1029
      if(ret == 0){
911
1038
 
912
1039
 fallback:
913
1040
  
914
 
  if(process_list == NULL or exitstatus != EXIT_SUCCESS){
915
 
    /* Fallback if all plugins failed, none are found or an error occured */
 
1041
  if(plugin_list == NULL or exitstatus != EXIT_SUCCESS){
 
1042
    /* Fallback if all plugins failed, none are found or an error
 
1043
       occured */
916
1044
    bool bret;
917
1045
    fprintf(stderr, "Going to fallback mode using getpass(3)\n");
918
1046
    char *passwordbuffer = getpass("Password: ");
919
 
    bret = print_out_password(passwordbuffer, strlen(passwordbuffer));
 
1047
    size_t len = strlen(passwordbuffer);
 
1048
    /* Strip trailing newline */
 
1049
    if(len > 0 and passwordbuffer[len-1] == '\n'){
 
1050
      passwordbuffer[len-1] = '\0'; /* not strictly necessary */
 
1051
      len--;
 
1052
    }
 
1053
    bret = print_out_password(passwordbuffer, len);
920
1054
    if(not bret){
921
1055
      perror("print_out_password");
922
1056
      exitstatus = EXIT_FAILURE;
929
1063
    perror("sigaction");
930
1064
    exitstatus = EXIT_FAILURE;
931
1065
  }
932
 
 
 
1066
  
933
1067
  if(custom_argv != NULL){
934
 
    for(char **arg = custom_argv; *arg != NULL; arg++){
 
1068
    for(char **arg = custom_argv+1; *arg != NULL; arg++){
935
1069
      free(*arg);
936
1070
    }
937
1071
    free(custom_argv);
938
1072
  }
939
 
  free_plugin_list(plugin_list);
940
1073
  
941
1074
  if(dir != NULL){
942
1075
    closedir(dir);
943
1076
  }
944
1077
  
945
 
  /* Free the process list and kill the processes */
946
 
  for(process *next; process_list != NULL; process_list = next){
947
 
    next = process_list->next;
948
 
    close(process_list->fd);
949
 
    ret = kill(process_list->pid, SIGTERM);
950
 
    if(ret == -1 and errno != ESRCH){
951
 
      /* set-uid proccesses migth not get closed */
952
 
      perror("kill");
 
1078
  /* Kill the processes */
 
1079
  for(plugin *p = plugin_list; p != NULL; p = p->next){
 
1080
    if(p->pid != 0){
 
1081
      close(p->fd);
 
1082
      ret = kill(p->pid, SIGTERM);
 
1083
      if(ret == -1 and errno != ESRCH){
 
1084
        /* Set-uid proccesses might not get closed */
 
1085
        perror("kill");
 
1086
      }
953
1087
    }
954
 
    free(process_list->buffer);
955
 
    free(process_list);
956
1088
  }
957
1089
  
958
1090
  /* Wait for any remaining child processes to terminate */
963
1095
    perror("wait");
964
1096
  }
965
1097
  
 
1098
  free_plugin_list();
 
1099
  
 
1100
  free(plugindir);
 
1101
  free(argfile);
 
1102
  
966
1103
  return exitstatus;
967
1104
}