summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWerner Fink <werner@suse.de>2015-11-18 12:28:30 +0100
committerFranck Bui <fbui@suse.com>2016-05-24 11:57:27 +0200
commit6af621248f2255f9ce50b0bafdde475305dc4e57 (patch)
treed766a8a616f8126fc9fa828f5a73e8e57bcbb487
parent2099b3e9931eea8962cf7a97493abf9361cc6366 (diff)
ask-password: ask for passphrases not only on the first console of /dev/console
but also on all other consoles. This does help on e.g. mainframes where often a serial console together with other consoles are used. Even rack based servers attachted to both a serial console as well as having a virtual console do sometimes miss a connected monitor. To be able to ask on all terminal devices of /dev/console the devices are collected. If more than one device are found, then on each of the terminals a inquiring task for passphrase is forked and do not return to the caller. 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. Also let contradictory options on the command of systemd-tty-ask-password-agent fail. Spwan for each device of the system console /dev/console a own process. Replace the system call wait() with with system call waitid(). Use SIGTERM instead of SIGHUP to get unresponsive childs down. Port the collect_consoles() function forward to a pulbic and strv based function "get_kernel_consoles()" in terminal-util.c and use this in tty-ask-password-agent.c.
-rw-r--r--src/basic/terminal-util.c59
-rw-r--r--src/basic/terminal-util.h1
-rw-r--r--src/tty-ask-password-agent/tty-ask-password-agent.c229
3 files changed, 272 insertions, 17 deletions
diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c
index 9521b79daa..3189b8789d 100644
--- a/src/basic/terminal-util.c
+++ b/src/basic/terminal-util.c
@@ -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;
diff --git a/src/basic/terminal-util.h b/src/basic/terminal-util.h
index a7c96a77cb..b449370974 100644
--- a/src/basic/terminal-util.h
+++ b/src/basic/terminal-util.h
@@ -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_;
diff --git a/src/tty-ask-password-agent/tty-ask-password-agent.c b/src/tty-ask-password-agent/tty-ask-password-agent.c
index ee879c7b89..8851af449d 100644
--- a/src/tty-ask-password-agent/tty-ask-password-agent.c
+++ b/src/tty-ask-password-agent/tty-ask-password-agent.c
@@ -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 (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();
+ if (IN_SET(arg_action, ACTION_WATCH, ACTION_WALL))
+ r = watch_passwords();
+ else
+ r = show_passwords();
+ }
finish:
return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;