/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/plymouth.c

  • Committer: Teddy Hogeborn
  • Date: 2016-03-17 20:40:55 UTC
  • mto: (237.7.594 trunk)
  • mto: This revision was merged to the branch mainline in revision 341.
  • Revision ID: teddy@recompile.se-20160317204055-bhsh5xsidq7w5cxu
Client: Fix plymouth agent; broken since 1.7.2.

Fix an very old memory bug in the plymouth agent (which has been
present since its apperance in version 1.2), but which was only
recently detected at run time due to the new -fsanitize=address
compile- time flag, which has been used since version 1.7.2.  This
detection of a memory access violation causes the program to abort,
making the Plymouth graphical boot system unable to accept interactive
input of passwords when using the Mandos client.

* plugins.d/plymouth.c (exec_and_wait): Fix memory allocation bug when
  allocating new_argv.  Also tolerate a zero-length argv.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*  -*- coding: utf-8 -*- */
 
2
/*
 
3
 * Plymouth - Read a password from Plymouth and output it
 
4
 * 
 
5
 * Copyright © 2010-2016 Teddy Hogeborn
 
6
 * Copyright © 2010-2016 Björn Påhlsson
 
7
 * 
 
8
 * This program is free software: you can redistribute it and/or
 
9
 * modify it under the terms of the GNU General Public License as
 
10
 * published by the Free Software Foundation, either version 3 of the
 
11
 * License, or (at your option) any later version.
 
12
 * 
 
13
 * This program is distributed in the hope that it will be useful, but
 
14
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 
16
 * General Public License for more details.
 
17
 * 
 
18
 * You should have received a copy of the GNU General Public License
 
19
 * along with this program.  If not, see
 
20
 * <http://www.gnu.org/licenses/>.
 
21
 * 
 
22
 * Contact the authors at <mandos@recompile.se>.
 
23
 */
 
24
 
1
25
#define _GNU_SOURCE             /* asprintf(), TEMP_FAILURE_RETRY() */
2
26
#include <signal.h>             /* sig_atomic_t, struct sigaction,
3
27
                                   sigemptyset(), sigaddset(), SIGINT,
6
30
#include <stdbool.h>            /* bool, false, true */
7
31
#include <fcntl.h>              /* open(), O_RDONLY */
8
32
#include <iso646.h>             /* and, or, not*/
9
 
#include <sys/types.h>          /* size_t, ssize_t, pid_t, struct dirent,
10
 
                                   waitpid() */
 
33
#include <sys/types.h>          /* size_t, ssize_t, pid_t, struct
 
34
                                   dirent, waitpid() */
11
35
#include <sys/wait.h>           /* waitpid() */
12
36
#include <stddef.h>             /* NULL */
13
37
#include <string.h>             /* strchr(), memcmp() */
14
 
#include <stdio.h>              /* asprintf(), perror(), fopen(), fscanf() */
15
 
#include <unistd.h>             /* close(), readlink(), read(), fork()
16
 
                                   setsid(), chdir(), dup2()
 
38
#include <stdio.h>              /* asprintf(), perror(), fopen(),
 
39
                                   fscanf(), vasprintf(), fprintf(),
 
40
                                   vfprintf() */
 
41
#include <unistd.h>             /* close(), readlink(), read(),
 
42
                                   fork(), setsid(), chdir(), dup2(),
17
43
                                   STDERR_FILENO, execv(), access() */
18
44
#include <stdlib.h>             /* free(), EXIT_FAILURE, realloc(),
19
45
                                   EXIT_SUCCESS, malloc(), _exit(),
21
47
#include <dirent.h>             /* scandir(), alphasort() */
22
48
#include <inttypes.h>           /* intmax_t, strtoumax(), SCNuMAX */
23
49
#include <sys/stat.h>           /* struct stat, lstat() */
24
 
#include <sysexits.h>           /* EX_OSERR */
 
50
#include <sysexits.h>           /* EX_OSERR, EX_UNAVAILABLE */
25
51
#include <error.h>              /* error() */
26
52
#include <errno.h>              /* TEMP_FAILURE_RETRY */
27
53
#include <argz.h>               /* argz_count(), argz_extract() */
 
54
#include <stdarg.h>             /* va_list, va_start(), ... */
28
55
 
29
56
sig_atomic_t interrupted_by_signal = 0;
30
 
const char plymouth_pid[] = "/dev/.initramfs/plymouth.pid";
 
57
 
 
58
/* Used by Ubuntu 11.04 (Natty Narwahl) */
 
59
const char plymouth_old_pid[] = "/dev/.initramfs/plymouth.pid";
 
60
/* Used by Ubuntu 11.10 (Oneiric Ocelot) */
 
61
const char plymouth_pid[] = "/run/initramfs/plymouth.pid";
 
62
 
31
63
const char plymouth_path[] = "/bin/plymouth";
32
64
const char plymouthd_path[] = "/sbin/plymouthd";
33
 
const char *plymouthd_default_argv[] = {"/sbin/plymouthd", "--mode=boot",
 
65
const char *plymouthd_default_argv[] = {"/sbin/plymouthd",
 
66
                                        "--mode=boot",
34
67
                                        "--attach-to-session",
35
 
                                        "--pid-file=/dev/.initramfs/plymouth.pid",
36
68
                                        NULL };
37
69
 
38
70
static void termination_handler(__attribute__((unused))int signum){
42
74
  interrupted_by_signal = 1;
43
75
}
44
76
 
 
77
/* Function to use when printing errors */
 
78
__attribute__((format (gnu_printf, 3, 4)))
 
79
void error_plus(int status, int errnum, const char *formatstring,
 
80
                ...){
 
81
  va_list ap;
 
82
  char *text;
 
83
  int ret;
 
84
  
 
85
  va_start(ap, formatstring);
 
86
  ret = vasprintf(&text, formatstring, ap);
 
87
  if(ret == -1){
 
88
    fprintf(stderr, "Mandos plugin %s: ",
 
89
            program_invocation_short_name);
 
90
    vfprintf(stderr, formatstring, ap);
 
91
    fprintf(stderr, ": ");
 
92
    fprintf(stderr, "%s\n", strerror(errnum));
 
93
    error(status, errno, "vasprintf while printing error");
 
94
    return;
 
95
  }
 
96
  fprintf(stderr, "Mandos plugin ");
 
97
  error(status, errnum, "%s", text);
 
98
  free(text);
 
99
}
 
100
 
45
101
/* Create prompt string */
46
102
char *makeprompt(void){
47
103
  int ret = 0;
48
104
  char *prompt;
49
105
  const char *const cryptsource = getenv("cryptsource");
50
106
  const char *const crypttarget = getenv("crypttarget");
51
 
  const char prompt_start[] = "Enter passphrase to unlock the disk";
 
107
  const char prompt_start[] = "Unlocking the disk";
 
108
  const char prompt_end[] = "Enter passphrase";
52
109
  
53
110
  if(cryptsource == NULL){
54
111
    if(crypttarget == NULL){
55
 
      ret = asprintf(&prompt, "%s: ", prompt_start);
 
112
      ret = asprintf(&prompt, "%s\n%s", prompt_start, prompt_end);
56
113
    } else {
57
 
      ret = asprintf(&prompt, "%s (%s): ", prompt_start,
58
 
                     crypttarget);
 
114
      ret = asprintf(&prompt, "%s (%s)\n%s", prompt_start,
 
115
                     crypttarget, prompt_end);
59
116
    }
60
117
  } else {
61
118
    if(crypttarget == NULL){
62
 
      ret = asprintf(&prompt, "%s %s: ", prompt_start, cryptsource);
 
119
      ret = asprintf(&prompt, "%s %s\n%s", prompt_start, cryptsource,
 
120
                     prompt_end);
63
121
    } else {
64
 
      ret = asprintf(&prompt, "%s %s (%s): ", prompt_start,
65
 
                     cryptsource, crypttarget);
 
122
      ret = asprintf(&prompt, "%s %s (%s)\n%s", prompt_start,
 
123
                     cryptsource, crypttarget, prompt_end);
66
124
    }
67
125
  }
68
126
  if(ret == -1){
79
137
bool become_a_daemon(void){
80
138
  int ret = setuid(geteuid());
81
139
  if(ret == -1){
82
 
    error(0, errno, "setuid");
 
140
    error_plus(0, errno, "setuid");
83
141
  }
84
142
    
85
143
  setsid();
86
144
  ret = chdir("/");
87
145
  if(ret == -1){
88
 
    error(0, errno, "chdir");
 
146
    error_plus(0, errno, "chdir");
89
147
    return false;
90
148
  }
91
149
  ret = dup2(STDERR_FILENO, STDOUT_FILENO); /* replace our stdout */
92
150
  if(ret == -1){
93
 
    error(0, errno, "dup2");
 
151
    error_plus(0, errno, "dup2");
94
152
    return false;
95
153
  }
96
154
  return true;
97
155
}
98
156
 
 
157
__attribute__((nonnull (2, 3)))
99
158
bool exec_and_wait(pid_t *pid_return, const char *path,
100
 
                   const char **argv, bool interruptable,
 
159
                   const char * const *argv, bool interruptable,
101
160
                   bool daemonize){
102
161
  int status;
103
162
  int ret;
104
163
  pid_t pid;
105
164
  pid = fork();
106
165
  if(pid == -1){
107
 
    error(0, errno, "fork");
 
166
    error_plus(0, errno, "fork");
108
167
    return false;
109
168
  }
110
169
  if(pid == 0){
114
173
        _exit(EX_OSERR);
115
174
      }
116
175
    }
117
 
 
118
 
    char **new_argv = NULL;
119
 
    char *tmp;
 
176
    
 
177
    char **new_argv = malloc(sizeof(const char *));
 
178
    if(new_argv == NULL){
 
179
      error_plus(0, errno, "malloc");
 
180
      _exit(EX_OSERR);
 
181
    }
 
182
    char **tmp;
120
183
    int i = 0;
121
 
    for (; argv[i]!=(char *)NULL; i++){
122
 
      tmp = realloc(new_argv, sizeof(const char *) * ((size_t)i + 1));
123
 
      if (tmp == NULL){
124
 
        error(0, errno, "realloc");
 
184
    for (; argv[i]!=NULL; i++){
 
185
      tmp = realloc(new_argv, sizeof(const char *) * ((size_t)i + 2));
 
186
      if(tmp == NULL){
 
187
        error_plus(0, errno, "realloc");
125
188
        free(new_argv);
126
 
        _exit(EXIT_FAILURE);
 
189
        _exit(EX_OSERR);
127
190
      }
128
 
      new_argv = (char **)tmp;
 
191
      new_argv = tmp;
129
192
      new_argv[i] = strdup(argv[i]);
130
193
    }
131
 
    new_argv[i] = (char *) NULL;
 
194
    new_argv[i] = NULL;
132
195
    
133
196
    execv(path, (char *const *)new_argv);
134
 
    error(0, errno, "execv");
 
197
    error_plus(0, errno, "execv");
135
198
    _exit(EXIT_FAILURE);
136
199
  }
137
200
  if(pid_return != NULL){
146
209
    return false;
147
210
  }
148
211
  if(ret == -1){
149
 
    error(0, errno, "waitpid");
 
212
    error_plus(0, errno, "waitpid");
150
213
    return false;
151
214
  }
152
 
  if(WIFEXITED(status) and WEXITSTATUS(status) == 0){
 
215
  if(WIFEXITED(status) and (WEXITSTATUS(status) == 0)){
153
216
    return true;
154
217
  }
155
218
  return false;
156
219
}
157
220
 
 
221
__attribute__((nonnull))
158
222
int is_plymouth(const struct dirent *proc_entry){
159
223
  int ret;
160
224
  {
161
 
    uintmax_t maxvalue;
 
225
    uintmax_t proc_id;
162
226
    char *tmp;
163
227
    errno = 0;
164
 
    maxvalue = strtoumax(proc_entry->d_name, &tmp, 10);
 
228
    proc_id = strtoumax(proc_entry->d_name, &tmp, 10);
165
229
 
166
 
    if(errno != 0 or *tmp != '\0' or maxvalue != (uintmax_t)((pid_t)maxvalue)){
 
230
    if(errno != 0 or *tmp != '\0'
 
231
       or proc_id != (uintmax_t)((pid_t)proc_id)){
167
232
      return 0;
168
233
    }
169
234
  }
170
 
  char exe_target[sizeof(plymouth_path)];
 
235
  char exe_target[sizeof(plymouthd_path)];
171
236
  char *exe_link;
172
237
  ret = asprintf(&exe_link, "/proc/%s/exe", proc_entry->d_name);
173
238
  if(ret == -1){
174
 
    error(0, errno, "asprintf");
 
239
    error_plus(0, errno, "asprintf");
175
240
    return 0;
176
241
  }
177
 
 
 
242
  
178
243
  struct stat exe_stat;
179
244
  ret = lstat(exe_link, &exe_stat);
180
245
  if(ret == -1){
181
246
    free(exe_link);
182
247
    if(errno != ENOENT){
183
 
      error(0, errno, "lstat");
 
248
      error_plus(0, errno, "lstat");
184
249
    }
185
250
    return 0;
186
251
  }
191
256
    free(exe_link);
192
257
    return 0;
193
258
  }
194
 
 
 
259
  
195
260
  ssize_t sret = readlink(exe_link, exe_target, sizeof(exe_target));
196
261
  free(exe_link);
197
 
  if((sret != (ssize_t)sizeof(plymouth_path)-1) or
198
 
      (memcmp(plymouth_path, exe_target,
199
 
              sizeof(plymouth_path)-1) != 0)){
 
262
  if((sret != (ssize_t)sizeof(plymouthd_path)-1) or
 
263
      (memcmp(plymouthd_path, exe_target,
 
264
              sizeof(plymouthd_path)-1) != 0)){
200
265
    return 0;
201
266
  }
202
267
  return 1;
204
269
 
205
270
pid_t get_pid(void){
206
271
  int ret;
 
272
  uintmax_t proc_id = 0;
207
273
  FILE *pidfile = fopen(plymouth_pid, "r");
208
 
  uintmax_t maxvalue = 0;
 
274
  /* Try the new pid file location */
209
275
  if(pidfile != NULL){
210
 
    ret = fscanf(pidfile, "%" SCNuMAX, &maxvalue);
 
276
    ret = fscanf(pidfile, "%" SCNuMAX, &proc_id);
211
277
    if(ret != 1){
212
 
      maxvalue = 0;
 
278
      proc_id = 0;
213
279
    }
214
280
    fclose(pidfile);
215
281
  }
216
 
  if(maxvalue == 0){
217
 
    struct dirent **direntries;
 
282
  /* Try the old pid file location */
 
283
  if(proc_id == 0){
 
284
    pidfile = fopen(plymouth_pid, "r");
 
285
    if(pidfile != NULL){
 
286
      ret = fscanf(pidfile, "%" SCNuMAX, &proc_id);
 
287
      if(ret != 1){
 
288
        proc_id = 0;
 
289
      }
 
290
      fclose(pidfile);
 
291
    }
 
292
  }
 
293
  /* Look for a plymouth process */
 
294
  if(proc_id == 0){
 
295
    struct dirent **direntries = NULL;
218
296
    ret = scandir("/proc", &direntries, is_plymouth, alphasort);
219
 
    sscanf(direntries[0]->d_name, "%" SCNuMAX, &maxvalue);
 
297
    if(ret == -1){
 
298
      error_plus(0, errno, "scandir");
 
299
    }
 
300
    if(ret > 0){
 
301
      ret = sscanf(direntries[0]->d_name, "%" SCNuMAX, &proc_id);
 
302
      if(ret < 0){
 
303
        error_plus(0, errno, "sscanf");
 
304
      }
 
305
    }
 
306
    /* scandir might preallocate for this variable (man page unclear).
 
307
       even if ret == 0, therefore we need to free it. */
 
308
    free(direntries);
220
309
  }
221
310
  pid_t pid;
222
 
  pid = (pid_t)maxvalue;
223
 
  if((uintmax_t)pid == maxvalue){
 
311
  pid = (pid_t)proc_id;
 
312
  if((uintmax_t)pid == proc_id){
224
313
    return pid;
225
314
  }
226
315
  
227
316
  return 0;
228
317
}
229
318
 
230
 
const char **getargv(pid_t pid){
 
319
const char * const * getargv(pid_t pid){
231
320
  int cl_fd;
232
321
  char *cmdline_filename;
233
322
  ssize_t sret;
236
325
  ret = asprintf(&cmdline_filename, "/proc/%" PRIuMAX "/cmdline",
237
326
                 (uintmax_t)pid);
238
327
  if(ret == -1){
239
 
    error(0, errno, "asprintf");
 
328
    error_plus(0, errno, "asprintf");
240
329
    return NULL;
241
330
  }
242
331
  
244
333
  cl_fd = open(cmdline_filename, O_RDONLY);
245
334
  free(cmdline_filename);
246
335
  if(cl_fd == -1){
247
 
    error(0, errno, "open");
 
336
    error_plus(0, errno, "open");
248
337
    return NULL;
249
338
  }
250
339
  
258
347
    if(cmdline_len + blocksize > cmdline_allocated){
259
348
      tmp = realloc(cmdline, cmdline_allocated + blocksize);
260
349
      if(tmp == NULL){
261
 
        error(0, errno, "realloc");
 
350
        error_plus(0, errno, "realloc");
262
351
        free(cmdline);
263
352
        close(cl_fd);
264
353
        return NULL;
271
360
    sret = read(cl_fd, cmdline + cmdline_len,
272
361
                cmdline_allocated - cmdline_len);
273
362
    if(sret == -1){
274
 
      error(0, errno, "read");
 
363
      error_plus(0, errno, "read");
275
364
      free(cmdline);
276
365
      close(cl_fd);
277
366
      return NULL;
280
369
  } while(sret != 0);
281
370
  ret = close(cl_fd);
282
371
  if(ret == -1){
283
 
    error(0, errno, "close");
 
372
    error_plus(0, errno, "close");
284
373
    free(cmdline);
285
374
    return NULL;
286
375
  }
289
378
  char **argv = malloc((argz_count(cmdline, cmdline_len) + 1)
290
379
                       * sizeof(char *)); /* Get number of args */
291
380
  if(argv == NULL){
292
 
    error(0, errno, "argv = malloc()");
 
381
    error_plus(0, errno, "argv = malloc()");
293
382
    free(cmdline);
294
383
    return NULL;
295
384
  }
296
385
  argz_extract(cmdline, cmdline_len, argv); /* Create argv */
297
 
  return (const char **)argv;
 
386
  return (const char * const *)argv;
298
387
}
299
388
 
300
389
int main(__attribute__((unused))int argc,
308
397
  /* test -x /bin/plymouth */
309
398
  ret = access(plymouth_path, X_OK);
310
399
  if(ret == -1){
311
 
    exit(EXIT_FAILURE);
 
400
    /* Plymouth is probably not installed.  Don't print an error
 
401
       message, just exit. */
 
402
    exit(EX_UNAVAILABLE);
312
403
  }
313
 
 
 
404
  
314
405
  { /* Add signal handlers */
315
406
    struct sigaction old_action,
316
407
      new_action = { .sa_handler = termination_handler,
317
408
                     .sa_flags = 0 };
318
409
    sigemptyset(&new_action.sa_mask);
319
 
    for(int *sig = (int[]){ SIGINT, SIGHUP, SIGTERM, 0 }; *sig != 0; sig++){
 
410
    for(int *sig = (int[]){ SIGINT, SIGHUP, SIGTERM, 0 };
 
411
        *sig != 0; sig++){
320
412
      ret = sigaddset(&new_action.sa_mask, *sig);
321
413
      if(ret == -1){
322
 
        error(0, errno, "sigaddset");
323
 
        exit(EX_OSERR);
 
414
        error_plus(EX_OSERR, errno, "sigaddset");
324
415
      }
325
416
      ret = sigaction(*sig, NULL, &old_action);
326
417
      if(ret == -1){
327
 
        error(0, errno, "sigaction");
328
 
        exit(EX_OSERR);
 
418
        error_plus(EX_OSERR, errno, "sigaction");
329
419
      }
330
420
      if(old_action.sa_handler != SIG_IGN){
331
421
        ret = sigaction(*sig, &new_action, NULL);
332
422
        if(ret == -1){
333
 
          error(0, errno, "sigaction");
334
 
          exit(EX_OSERR);
 
423
          error_plus(EX_OSERR, errno, "sigaction");
335
424
        }
336
425
      }
337
426
    }
338
427
  }
339
 
    
 
428
  
340
429
  /* plymouth --ping */
341
430
  bret = exec_and_wait(&plymouth_command_pid, plymouth_path,
342
 
                       (const char *[]){ (const char *)plymouth_path, (const char *)"--ping", (const char *)NULL},
 
431
                       (const char *[])
 
432
                       { plymouth_path, "--ping", NULL },
343
433
                       true, false);
344
434
  if(not bret){
345
435
    if(interrupted_by_signal){
346
436
      kill_and_wait(plymouth_command_pid);
 
437
      exit(EXIT_FAILURE);
347
438
    }
348
 
    exit(EXIT_FAILURE);
 
439
    /* Plymouth is probably not running.  Don't print an error
 
440
       message, just exit. */
 
441
    exit(EX_UNAVAILABLE);
349
442
  }
350
443
  
351
444
  prompt = makeprompt();
352
445
  ret = asprintf(&prompt_arg, "--prompt=%s", prompt);
353
446
  free(prompt);
354
447
  if(ret == -1){
355
 
    error(0, errno, "asprintf");
356
 
    exit(EXIT_FAILURE);
 
448
    error_plus(EX_OSERR, errno, "asprintf");
357
449
  }
358
450
  
359
451
  /* plymouth ask-for-password --prompt="$prompt" */
360
 
  bret = exec_and_wait(&plymouth_command_pid, plymouth_path,
361
 
                       (const char *[]){plymouth_path, "ask-for-password", prompt_arg, NULL},
 
452
  bret = exec_and_wait(&plymouth_command_pid,
 
453
                       plymouth_path, (const char *[])
 
454
                       { plymouth_path, "ask-for-password",
 
455
                           prompt_arg, NULL },
362
456
                       true, false);
363
457
  free(prompt_arg);
364
 
  if(not bret){
365
 
    if(interrupted_by_signal){
366
 
      kill_and_wait(plymouth_command_pid);
367
 
    } else {
368
 
      exit(EXIT_FAILURE);
369
 
    }
370
 
  }
371
 
  
372
458
  if(bret){
373
459
    exit(EXIT_SUCCESS);
374
460
  }
 
461
  if(not interrupted_by_signal){
 
462
    /* exec_and_wait failed for some other reason */
 
463
    exit(EXIT_FAILURE);
 
464
  }
 
465
  kill_and_wait(plymouth_command_pid);
375
466
  
376
 
  const char **plymouthd_argv = NULL;
 
467
  const char * const *plymouthd_argv;
377
468
  pid_t pid = get_pid();
378
469
  if(pid == 0){
379
 
    error(0, 0, "plymouthd pid not found");
 
470
    error_plus(0, 0, "plymouthd pid not found");
 
471
    plymouthd_argv = plymouthd_default_argv;
380
472
  } else {
381
473
    plymouthd_argv = getargv(pid);
382
474
  }
383
 
  if(plymouthd_argv == NULL){
384
 
    plymouthd_argv = plymouthd_default_argv;
385
 
  }
386
475
  
387
 
  bret = exec_and_wait(NULL, plymouth_path,
388
 
                       (const char *[]){plymouth_path, "quit", NULL}, false, false);
389
 
  if(not bret){
390
 
    exit(EXIT_FAILURE);
391
 
  }
392
 
  bret = exec_and_wait(NULL, plymouthd_path, plymouthd_argv, false, true);
393
 
  if(not bret){
394
 
    exit(EXIT_FAILURE);
395
 
  }
396
 
  exec_and_wait(NULL, plymouth_path,
397
 
                (const char *[]){ plymouth_path, "show-splash", NULL }, false, false);
 
476
  bret = exec_and_wait(NULL, plymouth_path, (const char *[])
 
477
                       { plymouth_path, "quit", NULL },
 
478
                       false, false);
 
479
  if(not bret){
 
480
    exit(EXIT_FAILURE);
 
481
  }
 
482
  bret = exec_and_wait(NULL, plymouthd_path, plymouthd_argv,
 
483
                       false, true);
 
484
  if(not bret){
 
485
    exit(EXIT_FAILURE);
 
486
  }
 
487
  exec_and_wait(NULL, plymouth_path, (const char *[])
 
488
                { plymouth_path, "show-splash", NULL },
 
489
                false, false);
398
490
  exit(EXIT_FAILURE);
399
491
}