/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 plugins.d/splashy.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:
1
1
/*  -*- coding: utf-8 -*- */
2
2
/*
3
 
 * Splashy - Read a password from splashy and output it
 
3
 * Passprompt - Read a password from splashy and output it
4
4
 * 
5
 
 * Copyright © 2008-2017 Teddy Hogeborn
6
 
 * Copyright © 2008-2017 Björn Påhlsson
 
5
 * Copyright © 2008 Teddy Hogeborn
 
6
 * Copyright © 2008 Björn Påhlsson
7
7
 * 
8
8
 * This program is free software: you can redistribute it and/or
9
9
 * modify it under the terms of the GNU General Public License as
19
19
 * along with this program.  If not, see
20
20
 * <http://www.gnu.org/licenses/>.
21
21
 * 
22
 
 * Contact the authors at <mandos@recompile.se>.
 
22
 * Contact the authors at <https://www.fukt.bsnet.se/~belorn/> and
 
23
 * <https://www.fukt.bsnet.se/~teddy/>.
23
24
 */
24
25
 
25
 
#define _GNU_SOURCE             /* TEMP_FAILURE_RETRY(), asprintf() */
 
26
#define _GNU_SOURCE             /* asprintf() */
26
27
#include <signal.h>             /* sig_atomic_t, struct sigaction,
27
28
                                   sigemptyset(), sigaddset(), SIGINT,
28
29
                                   SIGHUP, SIGTERM, sigaction,
29
30
                                   SIG_IGN, kill(), SIGKILL */
30
31
#include <stddef.h>             /* NULL */
31
32
#include <stdlib.h>             /* getenv() */
32
 
#include <stdio.h>              /* asprintf(), vasprintf(), vprintf(),
33
 
                                   fprintf() */
34
 
#include <stdlib.h>             /* EXIT_FAILURE, free(),
 
33
#include <stdio.h>              /* asprintf(), perror() */
 
34
#include <stdlib.h>             /* EXIT_FAILURE, free(), strtoul(),
35
35
                                   EXIT_SUCCESS */
36
36
#include <sys/types.h>          /* pid_t, DIR, struct dirent,
37
37
                                   ssize_t */
38
38
#include <dirent.h>             /* opendir(), readdir(), closedir() */
39
 
#include <inttypes.h>           /* intmax_t, strtoimax() */
40
39
#include <sys/stat.h>           /* struct stat, lstat(), S_ISLNK */
41
40
#include <iso646.h>             /* not, or, and */
42
41
#include <unistd.h>             /* readlink(), fork(), execl(),
43
42
                                   sleep(), dup2() STDERR_FILENO,
44
 
                                   STDOUT_FILENO, _exit(),
45
 
                                   pause() */
46
 
#include <string.h>             /* memcmp(), strerror() */
47
 
#include <errno.h>              /* errno, EACCES, ENOTDIR, ELOOP,
48
 
                                   ENOENT, ENAMETOOLONG, EMFILE,
49
 
                                   ENFILE, ENOMEM, ENOEXEC, EINVAL,
50
 
                                   E2BIG, EFAULT, EIO, ETXTBSY,
51
 
                                   EISDIR, ELIBBAD, EPERM, EINTR,
52
 
                                   ECHILD */
53
 
#include <error.h>              /* error() */
 
43
                                   STDOUT_FILENO, _exit() */
 
44
#include <string.h>             /* memcmp() */
 
45
#include <errno.h>              /* errno */
54
46
#include <sys/wait.h>           /* waitpid(), WIFEXITED(),
55
47
                                   WEXITSTATUS() */
56
 
#include <sysexits.h>           /* EX_OSERR, EX_OSFILE,
57
 
                                   EX_UNAVAILABLE */
58
 
#include <stdarg.h>             /* va_list, va_start(), ... */
59
48
 
60
49
sig_atomic_t interrupted_by_signal = 0;
61
 
int signal_received;
62
 
 
63
 
/* Function to use when printing errors */
64
 
__attribute__((format (gnu_printf, 3, 4)))
65
 
void error_plus(int status, int errnum, const char *formatstring,
66
 
                ...){
67
 
  va_list ap;
68
 
  char *text;
69
 
  int ret;
70
 
  
71
 
  va_start(ap, formatstring);
72
 
  ret = vasprintf(&text, formatstring, ap);
73
 
  if(ret == -1){
74
 
    fprintf(stderr, "Mandos plugin %s: ",
75
 
            program_invocation_short_name);
76
 
    vfprintf(stderr, formatstring, ap);
77
 
    fprintf(stderr, ": ");
78
 
    fprintf(stderr, "%s\n", strerror(errnum));
79
 
    error(status, errno, "vasprintf while printing error");
80
 
    return;
81
 
  }
82
 
  fprintf(stderr, "Mandos plugin ");
83
 
  error(status, errnum, "%s", text);
84
 
  free(text);
85
 
}
86
 
 
87
 
 
88
 
static void termination_handler(int signum){
89
 
  if(interrupted_by_signal){
90
 
    return;
91
 
  }
 
50
 
 
51
static void termination_handler(__attribute__((unused))int signum){
92
52
  interrupted_by_signal = 1;
93
 
  signal_received = signum;
94
53
}
95
54
 
96
55
int main(__attribute__((unused))int argc,
97
56
         __attribute__((unused))char **argv){
98
57
  int ret = 0;
 
58
  
 
59
  /* Create prompt string */
99
60
  char *prompt = NULL;
100
 
  DIR *proc_dir = NULL;
101
 
  pid_t splashy_pid = 0;
102
 
  pid_t splashy_command_pid = 0;
103
 
  int exitstatus = EXIT_FAILURE;
104
 
  
105
 
  /* Create prompt string */
106
61
  {
107
62
    const char *const cryptsource = getenv("cryptsource");
108
63
    const char *const crypttarget = getenv("crypttarget");
125
80
      }
126
81
    }
127
82
    if(ret == -1){
128
 
      prompt = NULL;
129
 
      exitstatus = EX_OSERR;
130
 
      goto failure;
 
83
      return EXIT_FAILURE;
131
84
    }
132
85
  }
133
86
  
134
87
  /* Find splashy process */
 
88
  pid_t splashy_pid = 0;
135
89
  {
136
90
    const char splashy_name[] = "/sbin/splashy";
137
 
    proc_dir = opendir("/proc");
 
91
    DIR *proc_dir = opendir("/proc");
138
92
    if(proc_dir == NULL){
139
 
      int e = errno;
140
 
      error_plus(0, errno, "opendir");
141
 
      switch(e){
142
 
      case EACCES:
143
 
      case ENOTDIR:
144
 
      case ELOOP:
145
 
      case ENOENT:
146
 
      default:
147
 
        exitstatus = EX_OSFILE;
148
 
        break;
149
 
      case ENAMETOOLONG:
150
 
      case EMFILE:
151
 
      case ENFILE:
152
 
      case ENOMEM:
153
 
        exitstatus = EX_OSERR;
154
 
        break;
155
 
      }
156
 
      goto failure;
 
93
      free(prompt);
 
94
      perror("opendir");
 
95
      return EXIT_FAILURE;
157
96
    }
158
97
    for(struct dirent *proc_ent = readdir(proc_dir);
159
98
        proc_ent != NULL;
160
99
        proc_ent = readdir(proc_dir)){
161
 
      pid_t pid;
162
 
      {
163
 
        intmax_t tmpmax;
164
 
        char *tmp;
165
 
        errno = 0;
166
 
        tmpmax = strtoimax(proc_ent->d_name, &tmp, 10);
167
 
        if(errno != 0 or tmp == proc_ent->d_name or *tmp != '\0'
168
 
           or tmpmax != (pid_t)tmpmax){
169
 
          /* Not a process */
170
 
          continue;
171
 
        }
172
 
        pid = (pid_t)tmpmax;
 
100
      pid_t pid = (pid_t) strtoul(proc_ent->d_name, NULL, 10);
 
101
      if(pid == 0){
 
102
        /* Not a process */
 
103
        continue;
173
104
      }
174
105
      /* Find the executable name by doing readlink() on the
175
106
         /proc/<pid>/exe link */
179
110
        char *exe_link;
180
111
        ret = asprintf(&exe_link, "/proc/%s/exe", proc_ent->d_name);
181
112
        if(ret == -1){
182
 
          error_plus(0, errno, "asprintf");
183
 
          exitstatus = EX_OSERR;
184
 
          goto failure;
 
113
          perror("asprintf");
 
114
          free(prompt);
 
115
          closedir(proc_dir);
 
116
          return EXIT_FAILURE;
185
117
        }
186
118
        
187
119
        /* Check that it refers to a symlink owned by root:root */
188
120
        struct stat exe_stat;
189
121
        ret = lstat(exe_link, &exe_stat);
190
122
        if(ret == -1){
191
 
          if(errno == ENOENT){
192
 
            free(exe_link);
193
 
            continue;
194
 
          }
195
 
          int e = errno;
196
 
          error_plus(0, errno, "lstat");
 
123
          perror("lstat");
197
124
          free(exe_link);
198
 
          switch(e){
199
 
          case EACCES:
200
 
          case ENOTDIR:
201
 
          case ELOOP:
202
 
          default:
203
 
            exitstatus = EX_OSFILE;
204
 
            break;
205
 
          case ENAMETOOLONG:
206
 
            exitstatus = EX_OSERR;
207
 
            break;
208
 
          }
209
 
          goto failure;
 
125
          free(prompt);
 
126
          closedir(proc_dir);
 
127
          return EXIT_FAILURE;
210
128
        }
211
129
        if(not S_ISLNK(exe_stat.st_mode)
212
130
           or exe_stat.st_uid != 0
226
144
      }
227
145
    }
228
146
    closedir(proc_dir);
229
 
    proc_dir = NULL;
230
147
  }
231
148
  if(splashy_pid == 0){
232
 
    exitstatus = EX_UNAVAILABLE;
233
 
    goto failure;
 
149
    free(prompt);
 
150
    return EXIT_FAILURE;
234
151
  }
235
152
  
236
153
  /* Set up the signal handler */
239
156
      new_action = { .sa_handler = termination_handler,
240
157
                     .sa_flags = 0 };
241
158
    sigemptyset(&new_action.sa_mask);
242
 
    ret = sigaddset(&new_action.sa_mask, SIGINT);
243
 
    if(ret == -1){
244
 
      error_plus(0, errno, "sigaddset");
245
 
      exitstatus = EX_OSERR;
246
 
      goto failure;
247
 
    }
248
 
    ret = sigaddset(&new_action.sa_mask, SIGHUP);
249
 
    if(ret == -1){
250
 
      error_plus(0, errno, "sigaddset");
251
 
      exitstatus = EX_OSERR;
252
 
      goto failure;
253
 
    }
254
 
    ret = sigaddset(&new_action.sa_mask, SIGTERM);
255
 
    if(ret == -1){
256
 
      error_plus(0, errno, "sigaddset");
257
 
      exitstatus = EX_OSERR;
258
 
      goto failure;
259
 
    }
 
159
    sigaddset(&new_action.sa_mask, SIGINT);
 
160
    sigaddset(&new_action.sa_mask, SIGHUP);
 
161
    sigaddset(&new_action.sa_mask, SIGTERM);
260
162
    ret = sigaction(SIGINT, NULL, &old_action);
261
163
    if(ret == -1){
262
 
      error_plus(0, errno, "sigaction");
263
 
      exitstatus = EX_OSERR;
264
 
      goto failure;
 
164
      perror("sigaction");
 
165
      free(prompt);
 
166
      return EXIT_FAILURE;
265
167
    }
266
168
    if(old_action.sa_handler != SIG_IGN){
267
169
      ret = sigaction(SIGINT, &new_action, NULL);
268
170
      if(ret == -1){
269
 
        error_plus(0, errno, "sigaction");
270
 
        exitstatus = EX_OSERR;
271
 
        goto failure;
 
171
        perror("sigaction");
 
172
        free(prompt);
 
173
        return EXIT_FAILURE;
272
174
      }
273
175
    }
274
176
    ret = sigaction(SIGHUP, NULL, &old_action);
275
177
    if(ret == -1){
276
 
      error_plus(0, errno, "sigaction");
277
 
      exitstatus = EX_OSERR;
278
 
      goto failure;
 
178
      perror("sigaction");
 
179
      free(prompt);
 
180
      return EXIT_FAILURE;
279
181
    }
280
182
    if(old_action.sa_handler != SIG_IGN){
281
183
      ret = sigaction(SIGHUP, &new_action, NULL);
282
184
      if(ret == -1){
283
 
        error_plus(0, errno, "sigaction");
284
 
        exitstatus = EX_OSERR;
285
 
        goto failure;
 
185
        perror("sigaction");
 
186
        free(prompt);
 
187
        return EXIT_FAILURE;
286
188
      }
287
189
    }
288
190
    ret = sigaction(SIGTERM, NULL, &old_action);
289
191
    if(ret == -1){
290
 
      error_plus(0, errno, "sigaction");
291
 
      exitstatus = EX_OSERR;
292
 
      goto failure;
 
192
      perror("sigaction");
 
193
      free(prompt);
 
194
      return EXIT_FAILURE;
293
195
    }
294
196
    if(old_action.sa_handler != SIG_IGN){
295
197
      ret = sigaction(SIGTERM, &new_action, NULL);
296
198
      if(ret == -1){
297
 
        error_plus(0, errno, "sigaction");
298
 
        exitstatus = EX_OSERR;
299
 
        goto failure;
 
199
        perror("sigaction");
 
200
        free(prompt);
 
201
        return EXIT_FAILURE;
300
202
      }
301
203
    }
302
204
  }
303
205
  
304
 
  if(interrupted_by_signal){
305
 
    goto failure;
306
 
  }
307
 
  
308
206
  /* Fork off the splashy command to prompt for password */
309
 
  splashy_command_pid = fork();
310
 
  if(splashy_command_pid != 0 and interrupted_by_signal){
311
 
    goto failure;
312
 
  }
313
 
  if(splashy_command_pid == -1){
314
 
    error_plus(0, errno, "fork");
315
 
    exitstatus = EX_OSERR;
316
 
    goto failure;
317
 
  }
318
 
  /* Child */
319
 
  if(splashy_command_pid == 0){
320
 
    if(not interrupted_by_signal){
 
207
  pid_t splashy_command_pid = 0;
 
208
  if(not interrupted_by_signal){
 
209
    splashy_command_pid = fork();
 
210
    if(splashy_command_pid == -1){
 
211
      if(not interrupted_by_signal){
 
212
        perror("fork");
 
213
      }
 
214
      return EXIT_FAILURE;
 
215
    }
 
216
    /* Child */
 
217
    if(splashy_command_pid == 0){
321
218
      const char splashy_command[] = "/sbin/splashy_update";
322
 
      execl(splashy_command, splashy_command, prompt, (char *)NULL);
323
 
      int e = errno;
324
 
      error_plus(0, errno, "execl");
325
 
      switch(e){
326
 
      case EACCES:
327
 
      case ENOENT:
328
 
      case ENOEXEC:
329
 
      case EINVAL:
330
 
        _exit(EX_UNAVAILABLE);
331
 
      case ENAMETOOLONG:
332
 
      case E2BIG:
333
 
      case ENOMEM:
334
 
      case EFAULT:
335
 
      case EIO:
336
 
      case EMFILE:
337
 
      case ENFILE:
338
 
      case ETXTBSY:
339
 
      default:
340
 
        _exit(EX_OSERR);
341
 
      case ENOTDIR:
342
 
      case ELOOP:
343
 
      case EISDIR:
344
 
#ifdef ELIBBAD
345
 
      case ELIBBAD:             /* Linux only */
346
 
#endif
347
 
      case EPERM:
348
 
        _exit(EX_OSFILE);
 
219
      ret = execl(splashy_command, splashy_command, prompt,
 
220
                  (char *)NULL);
 
221
      if(not interrupted_by_signal){
 
222
        perror("execl");
349
223
      }
 
224
      free(prompt);
 
225
      _exit(EXIT_FAILURE);
350
226
    }
351
 
    free(prompt);
352
 
    _exit(EXIT_FAILURE);
353
227
  }
354
228
  
355
229
  /* Parent */
356
230
  free(prompt);
357
 
  prompt = NULL;
358
 
  
359
 
  if(interrupted_by_signal){
360
 
    goto failure;
361
 
  }
362
231
  
363
232
  /* Wait for command to complete */
364
 
  {
 
233
  if(not interrupted_by_signal and splashy_command_pid != 0){
365
234
    int status;
366
 
    do {
367
 
      ret = waitpid(splashy_command_pid, &status, 0);
368
 
    } while(ret == -1 and errno == EINTR
369
 
            and not interrupted_by_signal);
370
 
    if(interrupted_by_signal){
371
 
      goto failure;
372
 
    }
 
235
    ret = waitpid(splashy_command_pid, &status, 0);
373
236
    if(ret == -1){
374
 
      error_plus(0, errno, "waitpid");
 
237
      if(errno != EINTR){
 
238
        perror("waitpid");
 
239
      }
375
240
      if(errno == ECHILD){
376
241
        splashy_command_pid = 0;
377
242
      }
378
243
    } else {
379
244
      /* The child process has exited */
380
245
      splashy_command_pid = 0;
381
 
      if(WIFEXITED(status) and WEXITSTATUS(status) == 0){
 
246
      if(not interrupted_by_signal and WIFEXITED(status)
 
247
         and WEXITSTATUS(status)==0){
382
248
        return EXIT_SUCCESS;
383
249
      }
384
250
    }
385
251
  }
386
 
  
387
 
 failure:
388
 
  
389
 
  free(prompt);
390
 
  
391
 
  if(proc_dir != NULL){
392
 
    TEMP_FAILURE_RETRY(closedir(proc_dir));
393
 
  }
394
 
  
395
 
  if(splashy_command_pid != 0){
396
 
    TEMP_FAILURE_RETRY(kill(splashy_command_pid, SIGTERM));
397
 
    
398
 
    TEMP_FAILURE_RETRY(kill(splashy_pid, SIGTERM));
399
 
    sleep(2);
400
 
    while(TEMP_FAILURE_RETRY(kill(splashy_pid, 0)) == 0){
401
 
      TEMP_FAILURE_RETRY(kill(splashy_pid, SIGKILL));
402
 
      sleep(1);
403
 
    }
404
 
    pid_t new_splashy_pid = (pid_t)TEMP_FAILURE_RETRY(fork());
405
 
    if(new_splashy_pid == 0){
406
 
      /* Child; will become new splashy process */
407
 
      
408
 
      /* Make the effective user ID (root) the only user ID instead of
409
 
         the real user ID (_mandos) */
410
 
      ret = setuid(geteuid());
411
 
      if(ret == -1){
412
 
        error_plus(0, errno, "setuid");
413
 
      }
414
 
      
415
 
      setsid();
416
 
      ret = chdir("/");
417
 
      if(ret == -1){
418
 
        error_plus(0, errno, "chdir");
419
 
      }
420
 
/*       if(fork() != 0){ */
421
 
/*      _exit(EXIT_SUCCESS); */
422
 
/*       } */
423
 
      ret = dup2(STDERR_FILENO, STDOUT_FILENO); /* replace stdout */
424
 
      if(ret == -1){
425
 
        error_plus(0, errno, "dup2");
426
 
        _exit(EX_OSERR);
427
 
      }
428
 
      
429
 
      execl("/sbin/splashy", "/sbin/splashy", "boot", (char *)NULL);
430
 
      {
431
 
        int e = errno;
432
 
        error_plus(0, errno, "execl");
433
 
        switch(e){
434
 
        case EACCES:
435
 
        case ENOENT:
436
 
        case ENOEXEC:
437
 
        default:
438
 
          _exit(EX_UNAVAILABLE);
439
 
        case ENAMETOOLONG:
440
 
        case E2BIG:
441
 
        case ENOMEM:
442
 
          _exit(EX_OSERR);
443
 
        case ENOTDIR:
444
 
        case ELOOP:
445
 
          _exit(EX_OSFILE);
446
 
        }
447
 
      }
448
 
    }
449
 
  }
450
 
  
451
 
  if(interrupted_by_signal){
452
 
    struct sigaction signal_action;
453
 
    sigemptyset(&signal_action.sa_mask);
454
 
    signal_action.sa_handler = SIG_DFL;
455
 
    ret = (int)TEMP_FAILURE_RETRY(sigaction(signal_received,
456
 
                                            &signal_action, NULL));
457
 
    if(ret == -1){
458
 
      error_plus(0, errno, "sigaction");
459
 
    }
460
 
    do {
461
 
      ret = raise(signal_received);
462
 
    } while(ret != 0 and errno == EINTR);
463
 
    if(ret != 0){
464
 
      error_plus(0, errno, "raise");
465
 
      abort();
466
 
    }
467
 
    TEMP_FAILURE_RETRY(pause());
468
 
  }
469
 
  
470
 
  return exitstatus;
 
252
  kill(splashy_pid, SIGTERM);
 
253
  if(interrupted_by_signal and splashy_command_pid != 0){
 
254
    kill(splashy_command_pid, SIGTERM);
 
255
  }
 
256
  sleep(2);
 
257
  while(kill(splashy_pid, 0) == 0){
 
258
    kill(splashy_pid, SIGKILL);
 
259
    sleep(1);
 
260
  }
 
261
  pid_t new_splashy_pid = fork();
 
262
  if(new_splashy_pid == 0){
 
263
    /* Child; will become new splashy process */
 
264
    
 
265
    /* Make the effective user ID (root) the only user ID instead of
 
266
       the real user ID (mandos) */
 
267
    ret = setuid(geteuid());
 
268
    if(ret == -1){
 
269
      perror("setuid");
 
270
    }
 
271
    
 
272
    setsid();
 
273
    ret = chdir("/");
 
274
/*     if(fork() != 0){ */
 
275
/*       _exit(EXIT_SUCCESS); */
 
276
/*     } */
 
277
    ret = dup2(STDERR_FILENO, STDOUT_FILENO); /* replace our stdout */
 
278
    if(ret == -1){
 
279
      perror("dup2");
 
280
      _exit(EXIT_FAILURE);
 
281
    }
 
282
    
 
283
    execl("/sbin/splashy", "/sbin/splashy", "boot", (char *)NULL);
 
284
    if(not interrupted_by_signal){
 
285
      perror("execl");
 
286
    }
 
287
    _exit(EXIT_FAILURE);
 
288
  }
 
289
  
 
290
  return EXIT_FAILURE;
471
291
}