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

  • Committer: Teddy Hogeborn
  • Date: 2019-07-29 16:35:53 UTC
  • Revision ID: teddy@recompile.se-20190729163553-1i442i2cbx64c537
Make tests and man page examples match

Make the tests test_manual_page_example[1-5] match exactly what is
written in the manual page, and add comments to manual page as
reminders to keep tests and manual page examples in sync.

* mandos-ctl (Test_commands_from_options.test_manual_page_example_1):
  Remove "--verbose" option, since the manual does not have it as the
  first example, and change assertion to match.
* mandos-ctl.xml (EXAMPLE): Add comments to all examples documenting
  which test function they correspond to.  Also remove unnecessary
  quotes from option arguments in fourth example, and clarify language
  slightly in fifth example.

Show diffs side-by-side

added added

removed removed

Lines of Context:
2
2
/*
3
3
 * Plymouth - Read a password from Plymouth and output it
4
4
 * 
5
 
 * Copyright © 2010-2011 Teddy Hogeborn
6
 
 * Copyright © 2010-2011 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
 
5
 * Copyright © 2010-2019 Teddy Hogeborn
 
6
 * Copyright © 2010-2019 Björn Påhlsson
 
7
 * 
 
8
 * This file is part of Mandos.
 
9
 * 
 
10
 * Mandos is free software: you can redistribute it and/or modify it
 
11
 * under the terms of the GNU General Public License as published by
 
12
 * the Free Software Foundation, either version 3 of the License, or
 
13
 * (at your option) any later version.
 
14
 * 
 
15
 * Mandos is distributed in the hope that it will be useful, but
14
16
 * WITHOUT ANY WARRANTY; without even the implied warranty of
15
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16
18
 * General Public License for more details.
17
19
 * 
18
20
 * 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
 * along with Mandos.  If not, see <http://www.gnu.org/licenses/>.
21
22
 * 
22
 
 * Contact the authors at <mandos@fukt.bsnet.se>.
 
23
 * Contact the authors at <mandos@recompile.se>.
23
24
 */
24
25
 
25
26
#define _GNU_SOURCE             /* asprintf(), TEMP_FAILURE_RETRY() */
36
37
#include <stddef.h>             /* NULL */
37
38
#include <string.h>             /* strchr(), memcmp() */
38
39
#include <stdio.h>              /* asprintf(), perror(), fopen(),
39
 
                                   fscanf() */
 
40
                                   fscanf(), vasprintf(), fprintf(),
 
41
                                   vfprintf() */
40
42
#include <unistd.h>             /* close(), readlink(), read(),
41
43
                                   fork(), setsid(), chdir(), dup2(),
42
44
                                   STDERR_FILENO, execv(), access() */
50
52
#include <error.h>              /* error() */
51
53
#include <errno.h>              /* TEMP_FAILURE_RETRY */
52
54
#include <argz.h>               /* argz_count(), argz_extract() */
 
55
#include <stdarg.h>             /* va_list, va_start(), ... */
 
56
#include <argp.h>
53
57
 
54
58
sig_atomic_t interrupted_by_signal = 0;
55
 
const char plymouth_pid[] = "/dev/.initramfs/plymouth.pid";
 
59
const char *argp_program_version = "plymouth " VERSION;
 
60
const char *argp_program_bug_address = "<mandos@recompile.se>";
 
61
 
 
62
/* Used by Ubuntu 11.04 (Natty Narwahl) */
 
63
const char plymouth_old_old_pid[] = "/dev/.initramfs/plymouth.pid";
 
64
/* Used by Ubuntu 11.10 (Oneiric Ocelot) */
 
65
const char plymouth_old_pid[] = "/run/initramfs/plymouth.pid";
 
66
/* Used by Debian 9 (stretch) */
 
67
const char plymouth_pid[] = "/run/plymouth/pid";
 
68
 
56
69
const char plymouth_path[] = "/bin/plymouth";
57
70
const char plymouthd_path[] = "/sbin/plymouthd";
58
71
const char *plymouthd_default_argv[] = {"/sbin/plymouthd",
59
72
                                        "--mode=boot",
60
73
                                        "--attach-to-session",
61
 
                                        "--pid-file="
62
 
                                        "/dev/.initramfs/"
63
 
                                        "plymouth.pid",
64
74
                                        NULL };
 
75
bool debug = false;
65
76
 
66
77
static void termination_handler(__attribute__((unused))int signum){
67
78
  if(interrupted_by_signal){
70
81
  interrupted_by_signal = 1;
71
82
}
72
83
 
 
84
__attribute__((format (gnu_printf, 2, 3), nonnull))
 
85
int fprintf_plus(FILE *stream, const char *format, ...){
 
86
  va_list ap;
 
87
  va_start (ap, format);
 
88
  fprintf(stream, "Mandos plugin %s: ", program_invocation_short_name);
 
89
  return vfprintf(stream, format, ap);
 
90
}
 
91
 
 
92
/* Function to use when printing errors */
 
93
__attribute__((format (gnu_printf, 3, 4)))
 
94
void error_plus(int status, int errnum, const char *formatstring,
 
95
                ...){
 
96
  va_list ap;
 
97
  char *text;
 
98
  int ret;
 
99
  
 
100
  va_start(ap, formatstring);
 
101
  ret = vasprintf(&text, formatstring, ap);
 
102
  if(ret == -1){
 
103
    fprintf(stderr, "Mandos plugin %s: ",
 
104
            program_invocation_short_name);
 
105
    vfprintf(stderr, formatstring, ap);
 
106
    fprintf(stderr, ": ");
 
107
    fprintf(stderr, "%s\n", strerror(errnum));
 
108
    error(status, errno, "vasprintf while printing error");
 
109
    return;
 
110
  }
 
111
  fprintf(stderr, "Mandos plugin ");
 
112
  error(status, errnum, "%s", text);
 
113
  free(text);
 
114
}
 
115
 
73
116
/* Create prompt string */
74
117
char *makeprompt(void){
75
118
  int ret = 0;
109
152
bool become_a_daemon(void){
110
153
  int ret = setuid(geteuid());
111
154
  if(ret == -1){
112
 
    error(0, errno, "setuid");
 
155
    error_plus(0, errno, "setuid");
113
156
  }
114
157
    
115
158
  setsid();
116
159
  ret = chdir("/");
117
160
  if(ret == -1){
118
 
    error(0, errno, "chdir");
 
161
    error_plus(0, errno, "chdir");
119
162
    return false;
120
163
  }
121
164
  ret = dup2(STDERR_FILENO, STDOUT_FILENO); /* replace our stdout */
122
165
  if(ret == -1){
123
 
    error(0, errno, "dup2");
 
166
    error_plus(0, errno, "dup2");
124
167
    return false;
125
168
  }
126
169
  return true;
127
170
}
128
171
 
 
172
__attribute__((nonnull (2, 3)))
129
173
bool exec_and_wait(pid_t *pid_return, const char *path,
130
 
                   const char **argv, bool interruptable,
 
174
                   const char * const * const argv, bool interruptable,
131
175
                   bool daemonize){
132
176
  int status;
133
177
  int ret;
134
178
  pid_t pid;
 
179
  if(debug){
 
180
    for(const char * const *arg = argv; *arg != NULL; arg++){
 
181
      fprintf_plus(stderr, "exec_and_wait arg: %s\n", *arg);
 
182
    }
 
183
    fprintf_plus(stderr, "exec_and_wait end of args\n");
 
184
  }
 
185
 
135
186
  pid = fork();
136
187
  if(pid == -1){
137
 
    error(0, errno, "fork");
 
188
    error_plus(0, errno, "fork");
138
189
    return false;
139
190
  }
140
191
  if(pid == 0){
145
196
      }
146
197
    }
147
198
    
148
 
    char **new_argv = NULL;
 
199
    char **new_argv = malloc(sizeof(const char *));
 
200
    if(new_argv == NULL){
 
201
      error_plus(0, errno, "malloc");
 
202
      _exit(EX_OSERR);
 
203
    }
149
204
    char **tmp;
150
205
    int i = 0;
151
 
    for (; argv[i]!=NULL; i++){
152
 
      tmp = realloc(new_argv, sizeof(const char *) * ((size_t)i + 1));
153
 
      if (tmp == NULL){
154
 
        error(0, errno, "realloc");
 
206
    for (; argv[i] != NULL; i++){
 
207
      tmp = realloc(new_argv, sizeof(const char *) * ((size_t)i + 2));
 
208
      if(tmp == NULL){
 
209
        error_plus(0, errno, "realloc");
155
210
        free(new_argv);
156
211
        _exit(EX_OSERR);
157
212
      }
161
216
    new_argv[i] = NULL;
162
217
    
163
218
    execv(path, (char *const *)new_argv);
164
 
    error(0, errno, "execv");
 
219
    error_plus(0, errno, "execv");
165
220
    _exit(EXIT_FAILURE);
166
221
  }
167
222
  if(pid_return != NULL){
173
228
          and ((not interrupted_by_signal)
174
229
               or (not interruptable)));
175
230
  if(interrupted_by_signal and interruptable){
 
231
    if(debug){
 
232
      fprintf_plus(stderr, "Interrupted by signal\n");
 
233
    }
176
234
    return false;
177
235
  }
178
236
  if(ret == -1){
179
 
    error(0, errno, "waitpid");
 
237
    error_plus(0, errno, "waitpid");
180
238
    return false;
181
239
  }
 
240
  if(debug){
 
241
    if(WIFEXITED(status)){
 
242
      fprintf_plus(stderr, "exec_and_wait exited: %d\n",
 
243
                   WEXITSTATUS(status));
 
244
    } else if(WIFSIGNALED(status)) {
 
245
      fprintf_plus(stderr, "exec_and_wait signaled: %d\n",
 
246
                   WTERMSIG(status));
 
247
    }
 
248
  }
182
249
  if(WIFEXITED(status) and (WEXITSTATUS(status) == 0)){
183
250
    return true;
184
251
  }
185
252
  return false;
186
253
}
187
254
 
 
255
__attribute__((nonnull))
188
256
int is_plymouth(const struct dirent *proc_entry){
189
257
  int ret;
190
258
  {
191
 
    uintmax_t maxvalue;
 
259
    uintmax_t proc_id;
192
260
    char *tmp;
193
261
    errno = 0;
194
 
    maxvalue = strtoumax(proc_entry->d_name, &tmp, 10);
 
262
    proc_id = strtoumax(proc_entry->d_name, &tmp, 10);
195
263
 
196
264
    if(errno != 0 or *tmp != '\0'
197
 
       or maxvalue != (uintmax_t)((pid_t)maxvalue)){
 
265
       or proc_id != (uintmax_t)((pid_t)proc_id)){
198
266
      return 0;
199
267
    }
200
268
  }
202
270
  char *exe_link;
203
271
  ret = asprintf(&exe_link, "/proc/%s/exe", proc_entry->d_name);
204
272
  if(ret == -1){
205
 
    error(0, errno, "asprintf");
 
273
    error_plus(0, errno, "asprintf");
206
274
    return 0;
207
275
  }
208
276
  
211
279
  if(ret == -1){
212
280
    free(exe_link);
213
281
    if(errno != ENOENT){
214
 
      error(0, errno, "lstat");
 
282
      error_plus(0, errno, "lstat");
215
283
    }
216
284
    return 0;
217
285
  }
235
303
 
236
304
pid_t get_pid(void){
237
305
  int ret;
 
306
  uintmax_t proc_id = 0;
238
307
  FILE *pidfile = fopen(plymouth_pid, "r");
239
 
  uintmax_t maxvalue = 0;
 
308
  /* Try the new pid file location */
240
309
  if(pidfile != NULL){
241
 
    ret = fscanf(pidfile, "%" SCNuMAX, &maxvalue);
 
310
    ret = fscanf(pidfile, "%" SCNuMAX, &proc_id);
242
311
    if(ret != 1){
243
 
      maxvalue = 0;
 
312
      proc_id = 0;
244
313
    }
245
314
    fclose(pidfile);
246
315
  }
247
 
  if(maxvalue == 0){
248
 
    struct dirent **direntries;
 
316
  /* Try the old pid file location */
 
317
  if(proc_id == 0){
 
318
    pidfile = fopen(plymouth_old_pid, "r");
 
319
    if(pidfile != NULL){
 
320
      ret = fscanf(pidfile, "%" SCNuMAX, &proc_id);
 
321
      if(ret != 1){
 
322
        proc_id = 0;
 
323
      }
 
324
      fclose(pidfile);
 
325
    }
 
326
  }
 
327
  /* Try the old old pid file location */
 
328
  if(proc_id == 0){
 
329
    pidfile = fopen(plymouth_old_old_pid, "r");
 
330
    if(pidfile != NULL){
 
331
      ret = fscanf(pidfile, "%" SCNuMAX, &proc_id);
 
332
      if(ret != 1){
 
333
        proc_id = 0;
 
334
      }
 
335
      fclose(pidfile);
 
336
    }
 
337
  }
 
338
  /* Look for a plymouth process */
 
339
  if(proc_id == 0){
 
340
    struct dirent **direntries = NULL;
249
341
    ret = scandir("/proc", &direntries, is_plymouth, alphasort);
250
 
    if (ret == -1){
251
 
      error(0, errno, "scandir");
 
342
    if(ret == -1){
 
343
      error_plus(0, errno, "scandir");
252
344
    }
253
 
    if (ret > 0){
254
 
      ret = sscanf(direntries[0]->d_name, "%" SCNuMAX, &maxvalue);
255
 
      if (ret < 0){
256
 
        error(0, errno, "sscanf");
 
345
    if(ret > 0){
 
346
      for(int i = ret-1; i >= 0; i--){
 
347
        if(proc_id == 0){
 
348
          ret = sscanf(direntries[i]->d_name, "%" SCNuMAX, &proc_id);
 
349
          if(ret < 0){
 
350
            error_plus(0, errno, "sscanf");
 
351
          }
 
352
        }
 
353
        free(direntries[i]);
257
354
      }
258
355
    }
 
356
    /* scandir might preallocate for this variable (man page unclear).
 
357
       even if ret == 0, therefore we need to free it. */
 
358
    free(direntries);
259
359
  }
260
360
  pid_t pid;
261
 
  pid = (pid_t)maxvalue;
262
 
  if((uintmax_t)pid == maxvalue){
 
361
  pid = (pid_t)proc_id;
 
362
  if((uintmax_t)pid == proc_id){
263
363
    return pid;
264
364
  }
265
365
  
266
366
  return 0;
267
367
}
268
368
 
269
 
const char **getargv(pid_t pid){
 
369
char **getargv(pid_t pid){
270
370
  int cl_fd;
271
371
  char *cmdline_filename;
272
372
  ssize_t sret;
275
375
  ret = asprintf(&cmdline_filename, "/proc/%" PRIuMAX "/cmdline",
276
376
                 (uintmax_t)pid);
277
377
  if(ret == -1){
278
 
    error(0, errno, "asprintf");
 
378
    error_plus(0, errno, "asprintf");
279
379
    return NULL;
280
380
  }
281
381
  
283
383
  cl_fd = open(cmdline_filename, O_RDONLY);
284
384
  free(cmdline_filename);
285
385
  if(cl_fd == -1){
286
 
    error(0, errno, "open");
 
386
    error_plus(0, errno, "open");
287
387
    return NULL;
288
388
  }
289
389
  
297
397
    if(cmdline_len + blocksize > cmdline_allocated){
298
398
      tmp = realloc(cmdline, cmdline_allocated + blocksize);
299
399
      if(tmp == NULL){
300
 
        error(0, errno, "realloc");
 
400
        error_plus(0, errno, "realloc");
301
401
        free(cmdline);
302
402
        close(cl_fd);
303
403
        return NULL;
310
410
    sret = read(cl_fd, cmdline + cmdline_len,
311
411
                cmdline_allocated - cmdline_len);
312
412
    if(sret == -1){
313
 
      error(0, errno, "read");
 
413
      error_plus(0, errno, "read");
314
414
      free(cmdline);
315
415
      close(cl_fd);
316
416
      return NULL;
319
419
  } while(sret != 0);
320
420
  ret = close(cl_fd);
321
421
  if(ret == -1){
322
 
    error(0, errno, "close");
 
422
    error_plus(0, errno, "close");
323
423
    free(cmdline);
324
424
    return NULL;
325
425
  }
328
428
  char **argv = malloc((argz_count(cmdline, cmdline_len) + 1)
329
429
                       * sizeof(char *)); /* Get number of args */
330
430
  if(argv == NULL){
331
 
    error(0, errno, "argv = malloc()");
 
431
    error_plus(0, errno, "argv = malloc()");
332
432
    free(cmdline);
333
433
    return NULL;
334
434
  }
335
435
  argz_extract(cmdline, cmdline_len, argv); /* Create argv */
336
 
  return (const char **)argv;
 
436
  return argv;
337
437
}
338
438
 
339
439
int main(__attribute__((unused))int argc,
340
440
         __attribute__((unused))char **argv){
341
 
  char *prompt;
 
441
  char *prompt = NULL;
342
442
  char *prompt_arg;
343
443
  pid_t plymouth_command_pid;
344
444
  int ret;
345
445
  bool bret;
346
446
 
 
447
  {
 
448
    struct argp_option options[] = {
 
449
      { .name = "prompt", .key = 128, .arg = "PROMPT",
 
450
        .doc = "The prompt to show" },
 
451
      { .name = "debug", .key = 129,
 
452
        .doc = "Debug mode" },
 
453
      { .name = NULL }
 
454
    };
 
455
    
 
456
    __attribute__((nonnull(3)))
 
457
    error_t parse_opt (int key, char *arg, __attribute__((unused))
 
458
                       struct argp_state *state){
 
459
      errno = 0;
 
460
      switch (key){
 
461
      case 128:                 /* --prompt */
 
462
        prompt = arg;
 
463
        if(debug){
 
464
          fprintf_plus(stderr, "Custom prompt \"%s\"\n", prompt);
 
465
        }
 
466
        break;
 
467
      case 129:                 /* --debug */
 
468
        debug = true;
 
469
        break;
 
470
      default:
 
471
        return ARGP_ERR_UNKNOWN;
 
472
      }
 
473
      return errno;
 
474
    }
 
475
    
 
476
    struct argp argp = { .options = options, .parser = parse_opt,
 
477
                         .args_doc = "",
 
478
                         .doc = "Mandos plymouth -- Read and"
 
479
                         " output a password" };
 
480
    ret = argp_parse(&argp, argc, argv, ARGP_IN_ORDER, NULL, NULL);
 
481
    switch(ret){
 
482
    case 0:
 
483
      break;
 
484
    case ENOMEM:
 
485
    default:
 
486
      errno = ret;
 
487
      error_plus(0, errno, "argp_parse");
 
488
      return EX_OSERR;
 
489
    case EINVAL:
 
490
      error_plus(0, errno, "argp_parse");
 
491
      return EX_USAGE;
 
492
    }
 
493
  }
 
494
  
347
495
  /* test -x /bin/plymouth */
348
496
  ret = access(plymouth_path, X_OK);
349
497
  if(ret == -1){
350
498
    /* Plymouth is probably not installed.  Don't print an error
351
499
       message, just exit. */
 
500
    if(debug){
 
501
      fprintf_plus(stderr, "Plymouth (%s) not found\n",
 
502
                   plymouth_path);
 
503
    }
352
504
    exit(EX_UNAVAILABLE);
353
505
  }
354
506
  
361
513
        *sig != 0; sig++){
362
514
      ret = sigaddset(&new_action.sa_mask, *sig);
363
515
      if(ret == -1){
364
 
        error(EX_OSERR, errno, "sigaddset");
 
516
        error_plus(EX_OSERR, errno, "sigaddset");
365
517
      }
366
518
      ret = sigaction(*sig, NULL, &old_action);
367
519
      if(ret == -1){
368
 
        error(EX_OSERR, errno, "sigaction");
 
520
        error_plus(EX_OSERR, errno, "sigaction");
369
521
      }
370
522
      if(old_action.sa_handler != SIG_IGN){
371
523
        ret = sigaction(*sig, &new_action, NULL);
372
524
        if(ret == -1){
373
 
          error(EX_OSERR, errno, "sigaction");
 
525
          error_plus(EX_OSERR, errno, "sigaction");
374
526
        }
375
527
      }
376
528
    }
388
540
    }
389
541
    /* Plymouth is probably not running.  Don't print an error
390
542
       message, just exit. */
 
543
    if(debug){
 
544
      fprintf_plus(stderr, "Plymouth not running\n");
 
545
    }
391
546
    exit(EX_UNAVAILABLE);
392
547
  }
393
548
  
394
 
  prompt = makeprompt();
395
 
  ret = asprintf(&prompt_arg, "--prompt=%s", prompt);
396
 
  free(prompt);
 
549
  if(prompt != NULL){
 
550
    ret = asprintf(&prompt_arg, "--prompt=%s", prompt);
 
551
  } else {
 
552
    char *made_prompt = makeprompt();
 
553
    ret = asprintf(&prompt_arg, "--prompt=%s", made_prompt);
 
554
    free(made_prompt);
 
555
  }
397
556
  if(ret == -1){
398
 
    error(EX_OSERR, errno, "asprintf");
 
557
    error_plus(EX_OSERR, errno, "asprintf");
399
558
  }
400
559
  
401
560
  /* plymouth ask-for-password --prompt="$prompt" */
 
561
  if(debug){
 
562
    fprintf_plus(stderr, "Prompting for password via Plymouth\n");
 
563
  }
402
564
  bret = exec_and_wait(&plymouth_command_pid,
403
565
                       plymouth_path, (const char *[])
404
566
                       { plymouth_path, "ask-for-password",
414
576
  }
415
577
  kill_and_wait(plymouth_command_pid);
416
578
  
417
 
  const char **plymouthd_argv;
 
579
  char **plymouthd_argv = NULL;
418
580
  pid_t pid = get_pid();
419
581
  if(pid == 0){
420
 
    error(0, 0, "plymouthd pid not found");
421
 
    plymouthd_argv = plymouthd_default_argv;
 
582
    error_plus(0, 0, "plymouthd pid not found");
422
583
  } else {
423
584
    plymouthd_argv = getargv(pid);
424
585
  }
427
588
                       { plymouth_path, "quit", NULL },
428
589
                       false, false);
429
590
  if(not bret){
 
591
    if(plymouthd_argv != NULL){
 
592
      free(*plymouthd_argv);
 
593
      free(plymouthd_argv);
 
594
    }
430
595
    exit(EXIT_FAILURE);
431
596
  }
432
 
  bret = exec_and_wait(NULL, plymouthd_path, plymouthd_argv,
 
597
  bret = exec_and_wait(NULL, plymouthd_path,
 
598
                       (plymouthd_argv != NULL)
 
599
                       ? (const char * const *)plymouthd_argv
 
600
                       : plymouthd_default_argv,
433
601
                       false, true);
 
602
  if(plymouthd_argv != NULL){
 
603
    free(*plymouthd_argv);
 
604
    free(plymouthd_argv);
 
605
  }
434
606
  if(not bret){
435
607
    exit(EXIT_FAILURE);
436
608
  }