/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: 2015-01-25 00:02:51 UTC
  • Revision ID: teddy@recompile.se-20150125000251-j2bw50gfq9smqyxe
mandos.xml (SEE ALSO): Update links.

Update link to GnuPG home page, change reference from TLS 1.1 to TLS
1.2, and change to latest RFC for using OpenPGP keys with TLS (and use
its correct title).

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