Merge pull request #3247 from fbuihuu/ask-passowrd-on-all-consoles

ask-password: ask for passphrases not only on the first console
This commit is contained in:
Lennart Poettering 2016-05-24 12:14:21 +02:00
commit bbed3b8efd
3 changed files with 272 additions and 17 deletions

View File

@ -50,6 +50,7 @@
#include "socket-util.h"
#include "stat-util.h"
#include "string-util.h"
#include "strv.h"
#include "terminal-util.h"
#include "time-util.h"
#include "util.h"
@ -708,6 +709,64 @@ char *resolve_dev_console(char **active) {
return tty;
}
int get_kernel_consoles(char ***consoles) {
_cleanup_strv_free_ char **con = NULL;
_cleanup_free_ char *line = NULL;
const char *active;
int r;
assert(consoles);
r = read_one_line_file("/sys/class/tty/console/active", &line);
if (r < 0)
return r;
active = line;
for (;;) {
_cleanup_free_ char *tty = NULL;
char *path;
r = extract_first_word(&active, &tty, NULL, 0);
if (r < 0)
return r;
if (r == 0)
break;
if (streq(tty, "tty0")) {
tty = mfree(tty);
r = read_one_line_file("/sys/class/tty/tty0/active", &tty);
if (r < 0)
return r;
}
path = strappend("/dev/", tty);
if (!path)
return -ENOMEM;
if (access(path, F_OK) < 0) {
log_debug_errno(errno, "Console device %s is not accessible, skipping: %m", path);
free(path);
continue;
}
r = strv_consume(&con, path);
if (r < 0)
return r;
}
if (strv_isempty(con)) {
log_debug("No devices found for system console");
r = strv_extend(&con, "/dev/console");
if (r < 0)
return r;
}
*consoles = con;
con = NULL;
return 0;
}
bool tty_is_vc_resolve(const char *tty) {
_cleanup_free_ char *active = NULL;

View File

@ -62,6 +62,7 @@ int ask_string(char **ret, const char *text, ...) _printf_(2, 3);
int vt_disallocate(const char *name);
char *resolve_dev_console(char **active);
int get_kernel_consoles(char ***consoles);
bool tty_is_vc(const char *tty);
bool tty_is_vc_resolve(const char *tty);
bool tty_is_console(const char *tty) _pure_;

View File

@ -2,6 +2,7 @@
This file is part of systemd.
Copyright 2010 Lennart Poettering
Copyright 2015 Werner Fink
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
@ -21,12 +22,15 @@
#include <fcntl.h>
#include <getopt.h>
#include <poll.h>
#include <signal.h>
#include <stdbool.h>
#include <stddef.h>
#include <string.h>
#include <sys/inotify.h>
#include <sys/prctl.h>
#include <sys/signalfd.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/un.h>
#include <unistd.h>
@ -35,8 +39,12 @@
#include "conf-parser.h"
#include "def.h"
#include "dirent-util.h"
#include "exit-status.h"
#include "fd-util.h"
#include "fileio.h"
#include "hashmap.h"
#include "io-util.h"
#include "macro.h"
#include "mkdir.h"
#include "path-util.h"
#include "process-util.h"
@ -57,6 +65,7 @@ static enum {
static bool arg_plymouth = false;
static bool arg_console = false;
static const char *arg_device = NULL;
static int ask_password_plymouth(
const char *message,
@ -354,7 +363,9 @@ static int parse_password(const char *filename, char **wall) {
int tty_fd = -1;
if (arg_console) {
tty_fd = acquire_terminal("/dev/console", false, false, false, USEC_INFINITY);
const char *con = arg_device ? arg_device : "/dev/console";
tty_fd = acquire_terminal(con, false, false, false, USEC_INFINITY);
if (tty_fd < 0)
return log_error_errno(tty_fd, "Failed to acquire /dev/console: %m");
@ -586,14 +597,14 @@ static int parse_argv(int argc, char *argv[]) {
};
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "list", no_argument, NULL, ARG_LIST },
{ "query", no_argument, NULL, ARG_QUERY },
{ "watch", no_argument, NULL, ARG_WATCH },
{ "wall", no_argument, NULL, ARG_WALL },
{ "plymouth", no_argument, NULL, ARG_PLYMOUTH },
{ "console", no_argument, NULL, ARG_CONSOLE },
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "list", no_argument, NULL, ARG_LIST },
{ "query", no_argument, NULL, ARG_QUERY },
{ "watch", no_argument, NULL, ARG_WATCH },
{ "wall", no_argument, NULL, ARG_WALL },
{ "plymouth", no_argument, NULL, ARG_PLYMOUTH },
{ "console", optional_argument, NULL, ARG_CONSOLE },
{}
};
@ -635,6 +646,15 @@ static int parse_argv(int argc, char *argv[]) {
case ARG_CONSOLE:
arg_console = true;
if (optarg) {
if (isempty(optarg)) {
log_error("Empty console device path is not allowed.");
return -EINVAL;
}
arg_device = optarg;
}
break;
case '?':
@ -649,9 +669,171 @@ static int parse_argv(int argc, char *argv[]) {
return -EINVAL;
}
if (arg_plymouth || arg_console) {
if (!IN_SET(arg_action, ACTION_QUERY, ACTION_WATCH)) {
log_error("Options --query and --watch conflict.");
return -EINVAL;
}
if (arg_plymouth && arg_console) {
log_error("Options --plymouth and --console conflict.");
return -EINVAL;
}
}
return 1;
}
/*
* To be able to ask on all terminal devices of /dev/console
* the devices are collected. If more than one device is found,
* then on each of the terminals a inquiring task is forked.
* Every task has its own session and its own controlling terminal.
* If one of the tasks does handle a password, the remaining tasks
* will be terminated.
*/
static int ask_on_this_console(const char *tty, pid_t *pid, int argc, char *argv[]) {
struct sigaction sig = {
.sa_handler = nop_signal_handler,
.sa_flags = SA_NOCLDSTOP | SA_RESTART,
};
assert_se(sigprocmask_many(SIG_UNBLOCK, NULL, SIGHUP, SIGCHLD, -1) >= 0);
assert_se(sigemptyset(&sig.sa_mask) >= 0);
assert_se(sigaction(SIGCHLD, &sig, NULL) >= 0);
sig.sa_handler = SIG_DFL;
assert_se(sigaction(SIGHUP, &sig, NULL) >= 0);
*pid = fork();
if (*pid < 0)
return log_error_errno(errno, "Failed to fork process: %m");
if (*pid == 0) {
int ac;
assert_se(prctl(PR_SET_PDEATHSIG, SIGHUP) >= 0);
reset_signal_mask();
reset_all_signal_handlers();
for (ac = 0; ac < argc; ac++) {
if (streq(argv[ac], "--console")) {
argv[ac] = strjoina("--console=", tty, NULL);
break;
}
}
assert(ac < argc);
execv(SYSTEMD_TTY_ASK_PASSWORD_AGENT_BINARY_PATH, argv);
_exit(EXIT_FAILURE);
}
return 0;
}
static void terminate_agents(Set *pids) {
struct timespec ts;
siginfo_t status = {};
sigset_t set;
Iterator i;
void *p;
int r, signum;
/*
* Request termination of the remaining processes as those
* are not required anymore.
*/
SET_FOREACH(p, pids, i)
(void) kill(PTR_TO_PID(p), SIGTERM);
/*
* Collect the processes which have go away.
*/
assert_se(sigemptyset(&set) >= 0);
assert_se(sigaddset(&set, SIGCHLD) >= 0);
timespec_store(&ts, 50 * USEC_PER_MSEC);
while (!set_isempty(pids)) {
zero(status);
r = waitid(P_ALL, 0, &status, WEXITED|WNOHANG);
if (r < 0 && errno == EINTR)
continue;
if (r == 0 && status.si_pid > 0) {
set_remove(pids, PID_TO_PTR(status.si_pid));
continue;
}
signum = sigtimedwait(&set, NULL, &ts);
if (signum < 0) {
if (errno != EAGAIN)
log_error_errno(errno, "sigtimedwait() failed: %m");
break;
}
assert(signum == SIGCHLD);
}
/*
* Kill hanging processes.
*/
SET_FOREACH(p, pids, i) {
log_warning("Failed to terminate child %d, killing it", PTR_TO_PID(p));
(void) kill(PTR_TO_PID(p), SIGKILL);
}
}
static int ask_on_consoles(int argc, char *argv[]) {
_cleanup_set_free_ Set *pids = NULL;
_cleanup_strv_free_ char **consoles = NULL;
siginfo_t status = {};
char **tty;
pid_t pid;
int r;
r = get_kernel_consoles(&consoles);
if (r < 0)
return log_error_errno(r, "Failed to determine devices of /dev/console: %m");
pids = set_new(NULL);
if (!pids)
return log_oom();
/* Start an agent on each console. */
STRV_FOREACH(tty, consoles) {
r = ask_on_this_console(*tty, &pid, argc, argv);
if (r < 0)
return r;
if (set_put(pids, PID_TO_PTR(pid)) < 0)
return log_oom();
}
/* Wait for an agent to exit. */
for (;;) {
zero(status);
if (waitid(P_ALL, 0, &status, WEXITED) < 0) {
if (errno == EINTR)
continue;
return log_error_errno(errno, "waitid() failed: %m");
}
set_remove(pids, PID_TO_PTR(status.si_pid));
break;
}
if (!is_clean_exit(status.si_code, status.si_status, NULL))
log_error("Password agent failed with: %d", status.si_status);
terminate_agents(pids);
return 0;
}
int main(int argc, char *argv[]) {
int r;
@ -665,15 +847,28 @@ int main(int argc, char *argv[]) {
if (r <= 0)
goto finish;
if (arg_console) {
(void) setsid();
(void) release_terminal();
}
if (arg_console && !arg_device)
/*
* Spawn for each console device a separate process.
*/
r = ask_on_consoles(argc, argv);
else {
if (IN_SET(arg_action, ACTION_WATCH, ACTION_WALL))
r = watch_passwords();
else
r = show_passwords();
if (arg_device) {
/*
* Later on, a controlling terminal will be acquired,
* therefore the current process has to become a session
* leader and should not have a controlling terminal already.
*/
(void) setsid();
(void) release_terminal();
}
if (IN_SET(arg_action, ACTION_WATCH, ACTION_WALL))
r = watch_passwords();
else
r = show_passwords();
}
finish:
return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;