/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;
105
110
  if (new_plugin == NULL){
106
111
    return NULL;
107
112
  }
108
 
  char *copy_name = strdup(name);
109
 
  if(copy_name == NULL){
110
 
    return NULL;
 
113
  char *copy_name = NULL;
 
114
  if(name != NULL){
 
115
    copy_name = strdup(name);
 
116
    if(copy_name == NULL){
 
117
      return NULL;
 
118
    }
111
119
  }
112
120
  
113
121
  *new_plugin = (plugin) { .name = copy_name,
114
122
                           .argc = 1,
115
 
                           .envc = 0,
116
123
                           .disabled = false,
117
 
                           .next = *plugin_list };
 
124
                           .next = plugin_list };
118
125
  
119
126
  new_plugin->argv = malloc(sizeof(char *) * 2);
120
127
  if (new_plugin->argv == NULL){
 
128
    free(copy_name);
121
129
    free(new_plugin);
122
130
    return NULL;
123
131
  }
124
132
  new_plugin->argv[0] = copy_name;
125
133
  new_plugin->argv[1] = NULL;
126
 
 
 
134
  
127
135
  new_plugin->environ = malloc(sizeof(char *));
128
136
  if(new_plugin->environ == NULL){
 
137
    free(copy_name);
129
138
    free(new_plugin->argv);
130
139
    free(new_plugin);
131
140
    return NULL;
132
141
  }
133
142
  new_plugin->environ[0] = NULL;
 
143
  
134
144
  /* Append the new plugin to the list */
135
 
  *plugin_list = new_plugin;
 
145
  plugin_list = new_plugin;
136
146
  return new_plugin;
137
147
}
138
148
 
168
178
}
169
179
 
170
180
/* Add to a plugin's environment */
171
 
static bool add_environment(plugin *p, const char *def){
 
181
static bool add_environment(plugin *p, const char *def, bool replace){
172
182
  if(p == NULL){
173
183
    return false;
174
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
  }
175
202
  return add_to_char_array(def, &(p->environ), &(p->envc));
176
203
}
177
204
 
178
 
 
179
205
/*
180
206
 * Based on the example in the GNU LibC manual chapter 13.13 "File
181
207
 * Descriptor Flags".
182
208
 * *Note File Descriptor Flags:(libc)Descriptor Flags.
183
209
 */
184
 
static int set_cloexec_flag(int fd)
185
 
{
 
210
static int set_cloexec_flag(int fd){
186
211
  int ret = fcntl(fd, F_GETFD, 0);
187
212
  /* If reading the flags failed, return error indication now. */
188
213
  if(ret < 0){
192
217
  return fcntl(fd, F_SETFD, ret | FD_CLOEXEC);
193
218
}
194
219
 
195
 
process *process_list = NULL;
196
220
 
197
 
/* Mark a process as completed when it exits, and save its exit
 
221
/* Mark processes as completed when they exit, and save their exit
198
222
   status. */
199
 
void handle_sigchld(__attribute__((unused)) int sig){
200
 
  process *proc = process_list;
201
 
  int status;
202
 
  pid_t pid = wait(&status);
203
 
  if(pid == -1){
204
 
    perror("wait");
205
 
    return;
206
 
  }
207
 
  while(proc != NULL and proc->pid != pid){
208
 
    proc = proc->next;
209
 
  }
210
 
  if(proc == NULL){
211
 
    /* Process not found in process list */
212
 
    return;
213
 
  }
214
 
  proc->status = status;
215
 
  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
  }
216
251
}
217
252
 
218
 
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){
219
255
  ssize_t ret;
220
 
  if(length>0 and buffer[length-1] == '\n'){
221
 
    length--;
222
 
  }
223
256
  for(size_t written = 0; written < length; written += (size_t)ret){
224
257
    ret = TEMP_FAILURE_RETRY(write(STDOUT_FILENO, buffer + written,
225
258
                                   length - written));
230
263
  return true;
231
264
}
232
265
 
233
 
char **add_to_argv(char **argv, int *argc, char *arg){
234
 
  if (argv == NULL){
235
 
    *argc = 1;
236
 
    argv = malloc(sizeof(char*) * 2);
237
 
    if(argv == NULL){
238
 
      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
      }
239
290
    }
240
 
    argv[0] = NULL;     /* Will be set to argv[0] in main before parsing */
241
 
    argv[1] = NULL;
242
 
  }
243
 
  *argc += 1;
244
 
  argv = realloc(argv, sizeof(char *)
245
 
                  * ((unsigned int) *argc + 1));
246
 
  if(argv == NULL){
247
 
    return NULL;
248
 
  }
249
 
  argv[*argc-1] = arg;
250
 
  argv[*argc] = NULL;   
251
 
  return argv;
 
291
  }
 
292
  
 
293
  free(plugin_node);
252
294
}
253
295
 
254
 
static void free_plugin_list(plugin *plugin_list){
255
 
  for(plugin *next = plugin_list; plugin_list != NULL; plugin_list = next){
256
 
    next = plugin_list->next;
257
 
    free(plugin_list->name);
258
 
    for(char **arg = plugin_list->argv; *arg != NULL; arg++){
259
 
      free(*arg);
260
 
    }    
261
 
    free(plugin_list->argv);
262
 
    for(char **env = plugin_list->environ; *env != NULL; env++){
263
 
      free(*env);
264
 
    }
265
 
    free(plugin_list->environ);
266
 
    free(plugin_list);
267
 
  }  
 
296
static void free_plugin_list(void){
 
297
  while(plugin_list != NULL){
 
298
    free_plugin(plugin_list);
 
299
  }
268
300
}
269
301
 
270
302
int main(int argc, char *argv[]){
271
 
  const char *plugindir = "/lib/mandos/plugins.d";
272
 
  const char *argfile = ARGFILE;
 
303
  char *plugindir = NULL;
 
304
  char *argfile = NULL;
273
305
  FILE *conffp;
274
306
  size_t d_name_len;
275
307
  DIR *dir = NULL;
298
330
  ret = sigaction(SIGCHLD, &sigchld_action, &old_sigchld_action);
299
331
  if(ret == -1){
300
332
    perror("sigaction");
301
 
    exitstatus = EXIT_FAILURE;    
 
333
    exitstatus = EXIT_FAILURE;
302
334
    goto fallback;
303
335
  }
304
336
  
307
339
    { .name = "global-options", .key = 'g',
308
340
      .arg = "OPTION[,OPTION[,...]]",
309
341
      .doc = "Options passed to all plugins" },
310
 
    { .name = "global-envs", .key = 'e',
 
342
    { .name = "global-env", .key = 'G',
311
343
      .arg = "VAR=value",
312
344
      .doc = "Environment variable passed to all plugins" },
313
345
    { .name = "options-for", .key = 'o',
314
346
      .arg = "PLUGIN:OPTION[,OPTION[,...]]",
315
347
      .doc = "Options passed only to specified plugin" },
316
 
    { .name = "envs-for", .key = 'f',
 
348
    { .name = "env-for", .key = 'E',
317
349
      .arg = "PLUGIN:ENV=value",
318
350
      .doc = "Environment variable passed to specified plugin" },
319
351
    { .name = "disable", .key = 'd',
320
352
      .arg = "PLUGIN",
321
353
      .doc = "Disable a specific plugin", .group = 1 },
 
354
    { .name = "enable", .key = 'e',
 
355
      .arg = "PLUGIN",
 
356
      .doc = "Enable a specific plugin", .group = 1 },
322
357
    { .name = "plugin-dir", .key = 128,
323
358
      .arg = "DIRECTORY",
324
359
      .doc = "Specify a different plugin directory", .group = 2 },
325
 
    { .name = "userid", .key = 129,
326
 
      .arg = "ID", .flags = 0,
327
 
      .doc = "User ID the plugins will run as", .group = 2 },
328
 
    { .name = "groupid", .key = 130,
329
 
      .arg = "ID", .flags = 0,
330
 
      .doc = "Group ID the plugins will run as", .group = 2 },
331
 
    { .name = "debug", .key = 131,
332
 
      .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 },
333
371
    { .name = NULL }
334
372
  };
335
373
  
336
 
  error_t parse_opt (int key, char *arg, struct argp_state *state) {
337
 
    /* Get the INPUT argument from `argp_parse', which we know is a
338
 
       pointer to our plugin list pointer. */
339
 
    plugin **plugins = state->input;
 
374
  error_t parse_opt (int key, char *arg, __attribute__((unused))
 
375
                     struct argp_state *state) {
340
376
    switch (key) {
341
 
    case 'g':
 
377
    case 'g':                   /* --global-options */
342
378
      if (arg != NULL){
343
379
        char *p;
344
380
        while((p = strsep(&arg, ",")) != NULL){
345
381
          if(p[0] == '\0'){
346
382
            continue;
347
383
          }
348
 
          if(not add_argument(getplugin(NULL, plugins), p)){
 
384
          if(not add_argument(getplugin(NULL), p)){
349
385
            perror("add_argument");
350
386
            return ARGP_ERR_UNKNOWN;
351
387
          }
352
388
        }
353
389
      }
354
390
      break;
355
 
    case 'e':
 
391
    case 'G':                   /* --global-env */
356
392
      if(arg == NULL){
357
393
        break;
358
394
      }
359
 
      {
360
 
        char *envdef = strdup(arg);
361
 
        if(envdef == NULL){
362
 
          break;
363
 
        }
364
 
        if(not add_environment(getplugin(NULL, plugins), envdef)){
365
 
          perror("add_environment");
366
 
        }
 
395
      if(not add_environment(getplugin(NULL), arg, true)){
 
396
        perror("add_environment");
367
397
      }
368
398
      break;
369
 
    case 'o':
 
399
    case 'o':                   /* --options-for */
370
400
      if (arg != NULL){
371
401
        char *p_name = strsep(&arg, ":");
372
 
        if(p_name[0] == '\0'){
 
402
        if(p_name[0] == '\0' or arg == NULL){
373
403
          break;
374
404
        }
375
405
        char *opt = strsep(&arg, ":");
376
 
        if(opt[0] == '\0'){
 
406
        if(opt[0] == '\0' or opt == NULL){
377
407
          break;
378
408
        }
379
 
        if(opt != NULL){
380
 
          char *p;
381
 
          while((p = strsep(&opt, ",")) != NULL){
382
 
            if(p[0] == '\0'){
383
 
              continue;
384
 
            }
385
 
            if(not add_argument(getplugin(p_name, plugins), p)){
386
 
              perror("add_argument");
387
 
              return ARGP_ERR_UNKNOWN;
388
 
            }
 
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;
389
417
          }
390
418
        }
391
419
      }
392
420
      break;
393
 
    case 'f':
 
421
    case 'E':                   /* --env-for */
394
422
      if(arg == NULL){
395
423
        break;
396
424
      }
399
427
        if(envdef == NULL){
400
428
          break;
401
429
        }
402
 
        char *p_name = strndup(arg, (size_t) (envdef-arg));
403
 
        if(p_name == NULL){
404
 
          break;
405
 
        }
406
 
        envdef++;
407
 
        if(not add_environment(getplugin(p_name, plugins), envdef)){
 
430
        *envdef = '\0';
 
431
        if(not add_environment(getplugin(arg), envdef+1, true)){
408
432
          perror("add_environment");
409
433
        }
410
434
      }
411
435
      break;
412
 
    case 'd':
 
436
    case 'd':                   /* --disable */
413
437
      if (arg != NULL){
414
 
        plugin *p = getplugin(arg, plugins);
 
438
        plugin *p = getplugin(arg);
415
439
        if(p == NULL){
416
440
          return ARGP_ERR_UNKNOWN;
417
441
        }
418
442
        p->disabled = true;
419
443
      }
420
444
      break;
421
 
    case 128:
422
 
      plugindir = arg;
423
 
      break;
424
 
    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 */
425
465
      uid = (uid_t)strtol(arg, NULL, 10);
426
466
      break;
427
 
    case 130:
 
467
    case 131:                   /* --groupid */
428
468
      gid = (gid_t)strtol(arg, NULL, 10);
429
469
      break;
430
 
    case 131:
 
470
    case 132:                   /* --debug */
431
471
      debug = true;
432
472
      break;
433
473
    case ARGP_KEY_ARG:
434
 
      fprintf(stderr, "Ignoring unknown argument \"%s\"\n", arg);
435
 
      break;
436
 
    case ARGP_KEY_END:
437
 
      break;
438
 
    default:
439
 
      return ARGP_ERR_UNKNOWN;
440
 
    }
441
 
    return 0;
442
 
  }
443
 
  
444
 
  plugin *plugin_list = NULL;
445
 
  
446
 
  struct argp argp = { .options = options, .parser = parse_opt,
447
 
                       .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 = "",
448
525
                       .doc = "Mandos plugin runner -- Run plugins" };
449
526
  
450
 
  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);
451
530
  if (ret == ARGP_ERR_UNKNOWN){
452
531
    fprintf(stderr, "Unknown error while parsing arguments\n");
453
532
    exitstatus = EXIT_FAILURE;
454
533
    goto fallback;
455
534
  }
456
 
 
457
 
  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
  }  
458
545
  if(conffp != NULL){
459
546
    char *org_line = NULL;
460
547
    char *p, *arg, *new_arg, *line;
463
550
    const char whitespace_delims[] = " \r\t\f\v\n";
464
551
    const char comment_delim[] = "#";
465
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 */
466
565
    while(true){
467
566
      sret = getline(&org_line, &size, conffp);
468
567
      if(sret == -1){
476
575
          continue;
477
576
        }
478
577
        new_arg = strdup(p);
479
 
        custom_argv = add_to_argv(custom_argv, &custom_argc, new_arg);
480
 
        if (custom_argv == NULL){
481
 
          perror("add_to_argv");
482
 
          exitstatus = EXIT_FAILURE;
483
 
          goto fallback;
484
 
        }
 
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;        
485
596
      }
486
597
    }
487
598
    free(org_line);
488
 
  } else{
 
599
  } else {
489
600
    /* Check for harmful errors and go to fallback. Other errors might
490
601
       not affect opening plugins */
491
602
    if (errno == EMFILE or errno == ENFILE or errno == ENOMEM){
494
605
      goto fallback;
495
606
    }
496
607
  }
497
 
 
 
608
  /* If there was any arguments from configuration file,
 
609
     pass them to parser as command arguments */
498
610
  if(custom_argv != NULL){
499
 
    custom_argv[0] = argv[0];
500
 
    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);
501
613
    if (ret == ARGP_ERR_UNKNOWN){
502
614
      fprintf(stderr, "Unknown error while parsing arguments\n");
503
615
      exitstatus = EXIT_FAILURE;
505
617
    }
506
618
  }
507
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
  
508
629
  if(debug){
509
630
    for(plugin *p = plugin_list; p != NULL; p=p->next){
510
631
      fprintf(stderr, "Plugin: %s has %d arguments\n",
519
640
    }
520
641
  }
521
642
  
 
643
  /* Strip permissions down to nobody */
522
644
  ret = setuid(uid);
523
645
  if (ret == -1){
524
646
    perror("setuid");
525
 
  }
526
 
  
 
647
  }  
527
648
  setgid(gid);
528
649
  if (ret == -1){
529
650
    perror("setgid");
530
651
  }
531
652
  
532
 
  dir = opendir(plugindir);
 
653
  if (plugindir == NULL){
 
654
    dir = opendir(PDIR);
 
655
  } else {
 
656
    dir = opendir(plugindir);
 
657
  }
 
658
  
533
659
  if(dir == NULL){
534
660
    perror("Could not open plugin dir");
535
661
    exitstatus = EXIT_FAILURE;
551
677
  
552
678
  FD_ZERO(&rfds_all);
553
679
  
 
680
  /* Read and execute any executable in the plugin directory*/
554
681
  while(true){
555
682
    dirst = readdir(dir);
556
683
    
557
 
    // All directory entries have been processed
 
684
    /* All directory entries have been processed */
558
685
    if(dirst == NULL){
559
686
      if (errno == EBADF){
560
687
        perror("readdir");
566
693
    
567
694
    d_name_len = strlen(dirst->d_name);
568
695
    
569
 
    // Ignore dotfiles, backup files and other junk
 
696
    /* Ignore dotfiles, backup files and other junk */
570
697
    {
571
698
      bool bad_name = false;
572
699
      
574
701
      
575
702
      const char const *bad_suffixes[] = { "~", "#", ".dpkg-new",
576
703
                                           ".dpkg-old",
 
704
                                           ".dpkg-bak",
577
705
                                           ".dpkg-divert", NULL };
578
706
      for(const char **pre = bad_prefixes; *pre != NULL; pre++){
579
707
        size_t pre_len = strlen(*pre);
587
715
          break;
588
716
        }
589
717
      }
590
 
      
591
718
      if(bad_name){
592
719
        continue;
593
720
      }
594
 
      
595
721
      for(const char **suf = bad_suffixes; *suf != NULL; suf++){
596
722
        size_t suf_len = strlen(*suf);
597
723
        if((d_name_len >= suf_len)
612
738
    }
613
739
 
614
740
    char *filename;
615
 
    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
    }
616
746
    if(ret < 0){
617
747
      perror("asprintf");
618
748
      continue;
624
754
      free(filename);
625
755
      continue;
626
756
    }
627
 
    
 
757
 
 
758
    /* Ignore non-executable files */
628
759
    if (not S_ISREG(st.st_mode) or (access(filename, X_OK) != 0)){
629
760
      if(debug){
630
761
        fprintf(stderr, "Ignoring plugin dir entry \"%s\""
633
764
      free(filename);
634
765
      continue;
635
766
    }
636
 
    plugin *p = getplugin(dirst->d_name, &plugin_list);
 
767
    
 
768
    plugin *p = getplugin(dirst->d_name);
637
769
    if(p == NULL){
638
770
      perror("getplugin");
639
771
      free(filename);
649
781
    }
650
782
    {
651
783
      /* Add global arguments to argument list for this plugin */
652
 
      plugin *g = getplugin(NULL, &plugin_list);
 
784
      plugin *g = getplugin(NULL);
653
785
      if(g != NULL){
654
786
        for(char **a = g->argv + 1; *a != NULL; a++){
655
787
          if(not add_argument(p, *a)){
658
790
        }
659
791
        /* Add global environment variables */
660
792
        for(char **e = g->environ; *e != NULL; e++){
661
 
          if(not add_environment(p, *e)){
 
793
          if(not add_environment(p, *e, false)){
662
794
            perror("add_environment");
663
795
          }
664
796
        }
669
801
       process, too. */
670
802
    if(p->environ[0] != NULL){
671
803
      for(char **e = environ; *e != NULL; e++){
672
 
        char *copy = strdup(*e);
673
 
        if(copy == NULL){
674
 
          perror("strdup");
675
 
          continue;
676
 
        }
677
 
        if(not add_environment(p, copy)){
 
804
        if(not add_environment(p, *e, false)){
678
805
          perror("add_environment");
679
806
        }
680
807
      }
681
808
    }
682
809
    
683
 
    int pipefd[2]; 
 
810
    int pipefd[2];
684
811
    ret = pipe(pipefd);
685
812
    if (ret == -1){
686
813
      perror("pipe");
687
814
      exitstatus = EXIT_FAILURE;
688
815
      goto fallback;
689
816
    }
 
817
    /* Ask OS to automatic close the pipe on exec */
690
818
    ret = set_cloexec_flag(pipefd[0]);
691
819
    if(ret < 0){
692
820
      perror("set_cloexec_flag");
706
834
      exitstatus = EXIT_FAILURE;
707
835
      goto fallback;
708
836
    }
709
 
    // Starting a new process to be watched
 
837
    /* Starting a new process to be watched */
710
838
    pid_t pid = fork();
711
839
    if(pid == -1){
712
840
      perror("fork");
720
848
        perror("sigaction");
721
849
        _exit(EXIT_FAILURE);
722
850
      }
723
 
      ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
 
851
      ret = sigprocmask(SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
724
852
      if(ret < 0){
725
853
        perror("sigprocmask");
726
854
        _exit(EXIT_FAILURE);
727
855
      }
728
 
 
 
856
      
729
857
      ret = dup2(pipefd[1], STDOUT_FILENO); /* replace our stdout */
730
858
      if(ret == -1){
731
859
        perror("dup2");
750
878
      }
751
879
      /* no return */
752
880
    }
753
 
    /* parent process */
 
881
    /* Parent process */
 
882
    close(pipefd[1]);           /* Close unused write end of pipe */
754
883
    free(filename);
755
 
    close(pipefd[1]);           /* close unused write end of pipe */
756
 
    process *new_process = malloc(sizeof(process));
757
 
    if (new_process == NULL){
758
 
      perror("malloc");
 
884
    plugin *new_plugin = getplugin(dirst->d_name);
 
885
    if (new_plugin == NULL){
 
886
      perror("getplugin");
759
887
      ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
760
888
      if(ret < 0){
761
 
        perror("sigprocmask");
 
889
        perror("sigprocmask");
762
890
      }
763
891
      exitstatus = EXIT_FAILURE;
764
892
      goto fallback;
765
893
    }
766
894
    
767
 
    *new_process = (struct process){ .pid = pid,
768
 
                                     .fd = pipefd[0],
769
 
                                     .next = process_list };
770
 
    // List handling
771
 
    process_list = new_process;
 
895
    new_plugin->pid = pid;
 
896
    new_plugin->fd = pipefd[0];
 
897
    
772
898
    /* Unblock SIGCHLD so signal handler can be run if this process
773
899
       has already completed */
774
900
    ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask, NULL);
778
904
      goto fallback;
779
905
    }
780
906
    
781
 
    FD_SET(new_process->fd, &rfds_all);
 
907
    FD_SET(new_plugin->fd, &rfds_all);
782
908
    
783
 
    if (maxfd < new_process->fd){
784
 
      maxfd = new_process->fd;
 
909
    if (maxfd < new_plugin->fd){
 
910
      maxfd = new_plugin->fd;
785
911
    }
786
 
    
787
912
  }
788
913
  
789
 
  free_plugin_list(plugin_list);
790
 
  
791
914
  closedir(dir);
792
915
  dir = NULL;
793
 
    
794
 
  if (process_list == NULL){
795
 
    fprintf(stderr, "No plugin processes started. Incorrect plugin"
796
 
            " directory?\n");
797
 
    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
    }
798
926
  }
799
 
  while(process_list){
 
927
  
 
928
  /* Main loop while running plugins exist */
 
929
  while(plugin_list){
800
930
    fd_set rfds = rfds_all;
801
931
    int select_ret = select(maxfd+1, &rfds, NULL, NULL, NULL);
802
932
    if (select_ret == -1){
806
936
    }
807
937
    /* OK, now either a process completed, or something can be read
808
938
       from one of them */
809
 
    for(process *proc = process_list; proc ; proc = proc->next){
 
939
    for(plugin *proc = plugin_list; proc != NULL;){
810
940
      /* Is this process completely done? */
811
941
      if(proc->eof and proc->completed){
812
942
        /* Only accept the plugin output if it exited cleanly */
813
943
        if(not WIFEXITED(proc->status)
814
944
           or WEXITSTATUS(proc->status) != 0){
815
945
          /* Bad exit by plugin */
 
946
 
816
947
          if(debug){
817
948
            if(WIFEXITED(proc->status)){
818
949
              fprintf(stderr, "Plugin %u exited with status %d\n",
827
958
                      (unsigned int) (proc->pid));
828
959
            }
829
960
          }
 
961
          
830
962
          /* Remove the plugin */
831
963
          FD_CLR(proc->fd, &rfds_all);
 
964
 
832
965
          /* Block signal while modifying process_list */
833
 
          ret = sigprocmask (SIG_BLOCK, &sigchld_action.sa_mask, NULL);
 
966
          ret = sigprocmask(SIG_BLOCK, &sigchld_action.sa_mask, NULL);
834
967
          if(ret < 0){
835
968
            perror("sigprocmask");
836
969
            exitstatus = EXIT_FAILURE;
837
970
            goto fallback;
838
971
          }
839
 
          /* Delete this process entry from the list */
840
 
          if(process_list == proc){
841
 
            /* First one - simple */
842
 
            process_list = proc->next;
843
 
          } else {
844
 
            /* Second one or later */
845
 
            for(process *p = process_list; p != NULL; p = p->next){
846
 
              if(p->next == proc){
847
 
                p->next = proc->next;
848
 
                break;
849
 
              }
850
 
            }
851
 
          }
 
972
          
 
973
          plugin *next_plugin = proc->next;
 
974
          free_plugin(proc);
 
975
          proc = next_plugin;
 
976
          
852
977
          /* We are done modifying process list, so unblock signal */
853
978
          ret = sigprocmask (SIG_UNBLOCK, &sigchld_action.sa_mask,
854
979
                             NULL);
855
980
          if(ret < 0){
856
981
            perror("sigprocmask");
857
 
          }
858
 
          free(proc->buffer);
859
 
          free(proc);
860
 
          /* We deleted this process from the list, so we can't go
861
 
             proc->next.  Therefore, start over from the beginning of
862
 
             the process list */
863
 
          break;
 
982
            exitstatus = EXIT_FAILURE;
 
983
            goto fallback;
 
984
          }
 
985
          
 
986
          if(plugin_list == NULL){
 
987
            break;
 
988
          }
 
989
          
 
990
          continue;
864
991
        }
 
992
        
865
993
        /* This process exited nicely, so print its buffer */
866
 
 
867
 
        bool bret = print_out_password(proc->buffer, proc->buffer_length);
 
994
        
 
995
        bool bret = print_out_password(proc->buffer,
 
996
                                       proc->buffer_length);
868
997
        if(not bret){
869
998
          perror("print_out_password");
870
999
          exitstatus = EXIT_FAILURE;
871
1000
        }
872
1001
        goto fallback;
873
1002
      }
 
1003
      
874
1004
      /* This process has not completed.  Does it have any output? */
875
1005
      if(proc->eof or not FD_ISSET(proc->fd, &rfds)){
876
1006
        /* This process had nothing to say at this time */
 
1007
        proc = proc->next;
877
1008
        continue;
878
1009
      }
879
1010
      /* Before reading, make the process' data buffer large enough */
892
1023
                 BUFFER_SIZE);
893
1024
      if(ret < 0){
894
1025
        /* Read error from this process; ignore the error */
 
1026
        proc = proc->next;
895
1027
        continue;
896
1028
      }
897
1029
      if(ret == 0){
906
1038
 
907
1039
 fallback:
908
1040
  
909
 
  if(process_list == NULL or exitstatus != EXIT_SUCCESS){
910
 
    /* 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 */
911
1044
    bool bret;
912
1045
    fprintf(stderr, "Going to fallback mode using getpass(3)\n");
913
1046
    char *passwordbuffer = getpass("Password: ");
914
 
    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);
915
1054
    if(not bret){
916
1055
      perror("print_out_password");
917
1056
      exitstatus = EXIT_FAILURE;
924
1063
    perror("sigaction");
925
1064
    exitstatus = EXIT_FAILURE;
926
1065
  }
927
 
 
 
1066
  
928
1067
  if(custom_argv != NULL){
929
 
    for(char **arg = custom_argv; *arg != NULL; arg++){
 
1068
    for(char **arg = custom_argv+1; *arg != NULL; arg++){
930
1069
      free(*arg);
931
1070
    }
932
1071
    free(custom_argv);
933
1072
  }
934
 
  free_plugin_list(plugin_list);
935
1073
  
936
1074
  if(dir != NULL){
937
1075
    closedir(dir);
938
1076
  }
939
1077
  
940
 
  /* Free the process list and kill the processes */
941
 
  for(process *next; process_list != NULL; process_list = next){
942
 
    next = process_list->next;
943
 
    close(process_list->fd);
944
 
    ret = kill(process_list->pid, SIGTERM);
945
 
    if(ret == -1 and errno != ESRCH){
946
 
      /* set-uid proccesses migth not get closed */
947
 
      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
      }
948
1087
    }
949
 
    free(process_list->buffer);
950
 
    free(process_list);
951
1088
  }
952
1089
  
953
1090
  /* Wait for any remaining child processes to terminate */
958
1095
    perror("wait");
959
1096
  }
960
1097
  
 
1098
  free_plugin_list();
 
1099
  
 
1100
  free(plugindir);
 
1101
  free(argfile);
 
1102
  
961
1103
  return exitstatus;
962
1104
}