/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: 2019-03-18 22:29:25 UTC
  • mto: This revision was merged to the branch mainline in revision 382.
  • Revision ID: teddy@recompile.se-20190318222925-jvhek84dgcfgj6g3
mandos-ctl: Refactor tests

* mandos-ctl: Where the clients names "foo" and "barbar" do not refer
              to the actual mock clients in the TestCommand class,
              change all occurrences of these names to "client1" and
              "client2" (or just "client" when only one is used) .
              Also change all test doubles to use correct terminology;
              some things called mocks are actually stubs or spies,
              and rename all true mocks to have "mock" in their names.
              Also eliminate duplicate values in tests; derive values
              from previously defined values whenever possible.

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