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

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