summaryrefslogtreecommitdiff
path: root/src/tty-ask-password-agent/tty-ask-password-agent.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/tty-ask-password-agent/tty-ask-password-agent.c')
-rw-r--r--src/tty-ask-password-agent/tty-ask-password-agent.c229
1 files changed, 212 insertions, 17 deletions
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;