File tty-ask-password-agent-on-console.patch of Package systemd.1059
---
src/tty-ask-password-agent/tty-ask-password-agent.c | 318 +++++++++++++++++++-
1 file changed, 305 insertions(+), 13 deletions(-)
--- systemd-210/src/tty-ask-password-agent/tty-ask-password-agent.c
+++ systemd-210/src/tty-ask-password-agent/tty-ask-password-agent.c 2015-10-27 11:35:43.242893783 +0000
@@ -4,6 +4,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
@@ -28,8 +29,12 @@
#include <sys/poll.h>
#include <sys/inotify.h>
#include <unistd.h>
+#include <sys/prctl.h>
#include <getopt.h>
+#include <signal.h>
+#include <sys/wait.h>
#include <sys/signalfd.h>
+#include <sys/mman.h>
#include <fcntl.h>
#include "util.h"
@@ -41,6 +46,11 @@
#include "ask-password-api.h"
#include "strv.h"
#include "build.h"
+#include "fileio.h"
+#include "macro.h"
+#include "hashmap.h"
+#include "strv.h"
+#include "exit-status.h"
static enum {
ACTION_LIST,
@@ -49,8 +59,16 @@ static enum {
ACTION_WALL
} arg_action = ACTION_QUERY;
+static volatile sig_atomic_t sigchild;
+static void chld_handler(int sig)
+{
+ (void)sig;
+ sigchild++;
+}
+
static bool arg_plymouth = false;
static bool arg_console = false;
+static const char *arg_device;
static int ask_password_plymouth(
const char *message,
@@ -246,12 +264,73 @@ finish:
return r;
}
+static int get_kernel_consoles(char ***consoles) {
+ _cleanup_strv_free_ char **con = NULL;
+ _cleanup_free_ char *active = NULL;
+ char *word, *state;
+ int count = 0;
+ size_t len;
+ int ret;
+
+ assert(consoles);
+
+ ret = read_one_line_file("/sys/class/tty/console/active", &active);
+ if (ret < 0)
+ return ret;
+
+ FOREACH_WORD(word, len, active, state) {
+ _cleanup_free_ char *tty = NULL;
+ char *path;
+
+ if (len == 4 && strneq(word, "tty0", 4)) {
+
+ ret = read_one_line_file("/sys/class/tty/tty0/active", &tty);
+ if (ret < 0)
+ return ret;
+ } else {
+
+ tty = strndup(word, len);
+ if (!tty)
+ return -ENOMEM;
+ }
+
+ path = strjoin("/dev/", tty, NULL);
+ if (!path)
+ return -ENOMEM;
+
+ ret = strv_push(&con, path);
+ if (ret < 0) {
+ free(path);
+ return ret;
+ }
+
+ count++;
+ }
+
+ if (count == 0) {
+
+ log_debug("No devices found for system console");
+
+ ret = strv_extend(&con, "/dev/console");
+ if (ret < 0)
+ return ret;
+
+ count++;
+ }
+
+ *consoles = con;
+ con = NULL;
+
+ return count;
+}
+
static int parse_password(const char *filename, char **wall) {
char *socket_name = NULL, *message = NULL, *packet = NULL;
uint64_t not_after = 0;
unsigned pid = 0;
int socket_fd = -1;
bool accept_cached = false;
+ size_t packet_length = 0;
const ConfigTableItem items[] = {
{ "Ask", "Socket", config_parse_string, 0, &socket_name },
@@ -323,7 +402,6 @@ static int parse_password(const char *fi
struct sockaddr sa;
struct sockaddr_un un;
} sa = {};
- size_t packet_length = 0;
assert(arg_action == ACTION_QUERY ||
arg_action == ACTION_WATCH);
@@ -365,7 +443,7 @@ static int parse_password(const char *fi
char *password = NULL;
if (arg_console)
- if ((tty_fd = acquire_terminal("/dev/console", false, false, false, (usec_t) -1)) < 0) {
+ if ((tty_fd = acquire_terminal(arg_device ? arg_device : "/dev/console", false, false, false, (usec_t) -1)) < 0) {
r = tty_fd;
goto finish;
}
@@ -386,6 +464,7 @@ static int parse_password(const char *fi
strcpy(packet+1, password);
}
+ memset(password, 0, strlen(password));
free(password);
}
}
@@ -423,6 +502,7 @@ finish:
if (socket_fd >= 0)
close_nointr_nofail(socket_fd);
+ memset(packet, 0, packet_length);
free(packet);
free(socket_name);
free(message);
@@ -664,7 +744,7 @@ static int parse_argv(int argc, char *ar
{ "watch", no_argument, NULL, ARG_WATCH },
{ "wall", no_argument, NULL, ARG_WALL },
{ "plymouth", no_argument, NULL, ARG_PLYMOUTH },
- { "console", no_argument, NULL, ARG_CONSOLE },
+ { "console", optional_argument, NULL, ARG_CONSOLE },
{}
};
@@ -707,6 +787,8 @@ static int parse_argv(int argc, char *ar
case ARG_CONSOLE:
arg_console = true;
+ if (!isempty(optarg))
+ arg_device = optarg;
break;
case '?':
@@ -725,6 +807,205 @@ static int parse_argv(int argc, char *ar
return 1;
}
+/*
+ * 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 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_consoles(int argc, char *argv[]) {
+ _cleanup_hashmap_free_ Hashmap *pids = NULL;
+ _cleanup_strv_free_ char **consoles = NULL;
+ struct sigaction sig = {
+ .sa_handler = chld_handler,
+ .sa_flags = SA_NOCLDSTOP | SA_RESTART,
+ };
+ struct timespec timeout;
+ siginfo_t status = {};
+ sigset_t set;
+ Iterator it;
+ char *device;
+ char **tty;
+ void *ptr;
+ pid_t pid;
+ int ret, signum;
+
+ ret = get_kernel_consoles(&consoles);
+ if (ret < 0) {
+ errno = -ret;
+ log_error("Failed to determine devices of /dev/console: %m");
+ return ret;
+ }
+
+ pids = hashmap_new(NULL, NULL);
+ if (!pids)
+ return log_oom();
+
+ 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);
+
+ STRV_FOREACH(tty, consoles) {
+
+ pid = fork();
+ if (pid < 0) {
+ log_error("Failed to fork process: %m");
+ return -errno;
+ }
+
+ device = *tty;
+
+ if (pid == 0) {
+ char *conarg;
+ static const struct sigaction sa = {
+ .sa_handler = SIG_DFL,
+ .sa_flags = SA_RESTART,
+ };
+ sigset_t ss;
+ int ac;
+
+ conarg = strjoin("--console=", device, NULL);
+ if (!conarg)
+ return log_oom();
+
+ assert_se(prctl(PR_SET_PDEATHSIG, SIGHUP) >= 0);
+
+ assert_se(sigemptyset(&ss) >= 0);
+ assert_se(sigprocmask(SIG_SETMASK, &ss, NULL) >= 0);
+ assert_se(sigaction(SIGCHLD, &sa, NULL) == 0);
+
+ for (ac = 0; ac < argc; ac++) {
+ if (streq(argv[ac], "--console")) {
+ argv[ac] = conarg;
+ break;
+ }
+ }
+
+ assert(ac < argc);
+
+ execv(SYSTEMD_TTY_ASK_PASSWORD_AGENT_BINARY_PATH, argv);
+ _exit(EXIT_FAILURE);
+ }
+
+ device = strdup(*tty);
+ if (!device)
+ return log_oom();
+
+ ret = hashmap_put(pids, UINT_TO_PTR(pid), device);
+ if (ret < 0)
+ return log_oom();
+ }
+
+ for (;;) {
+
+ assert_se(!hashmap_isempty(pids));
+
+ ret = waitid(P_ALL, 0, &status, WEXITED);
+
+ if (!ret)
+ break;
+
+ if (errno == EINTR)
+ continue;
+
+ log_error("waitid() failed: %m");
+ return -errno;
+ }
+
+ /*
+ * Remove the returned process from hashmap.
+ */
+ device = hashmap_remove(pids, UINT_TO_PTR(status.si_pid));
+ assert(device);
+
+ if (!is_clean_exit(status.si_code, status.si_status, NULL)) {
+ if (status.si_code == CLD_EXITED)
+ log_error("Failed to execute child for %s: %d", device, status.si_status);
+ else
+ log_error("Failed to execute child for %s due signal %s", device, signal_to_string(status.si_status));
+ }
+
+ free(device);
+
+ if (hashmap_isempty(pids))
+ return ret;
+
+ /*
+ * Request termination of the remaining processes as those
+ * are not required anymore.
+ */
+ HASHMAP_FOREACH_KEY(device, ptr, pids, it) {
+
+ assert(device);
+ pid = PTR_TO_UINT(ptr);
+
+ if (kill(pid, SIGTERM) < 0 && errno != ESRCH)
+ log_warning("kill(%d, SIGTERM) failed: %m", pid);
+ }
+
+ /*
+ * Collect the processes which have go away.
+ */
+ assert_se(sigemptyset(&set) >= 0);
+ assert_se(sigaddset(&set, SIGCHLD) >= 0);
+ timespec_store(&timeout, 50 * USEC_PER_MSEC);
+
+ while ((ptr = hashmap_first_key(pids))) {
+
+ ret = waitid(P_ALL, 0, &status, WEXITED|WNOHANG);
+ if (ret < 0 && errno == EINTR)
+ continue;
+
+ if (!ret && status.si_pid > 0) {
+ device = hashmap_remove(pids, UINT_TO_PTR(status.si_pid));
+ assert(device);
+ free(device);
+ continue;
+ }
+
+ signum = sigtimedwait(&set, NULL, &timeout);
+ if (signum != SIGCHLD) {
+
+ if (signum < 0 && errno == EAGAIN)
+ break;
+
+ if (signum < 0) {
+ log_error("sigtimedwait() failed: %m");
+ return -errno;
+ }
+
+ if (signum >= 0)
+ log_warning("sigtimedwait() returned unexpected signal.");
+ }
+ }
+
+
+ /*
+ * Kill hanging processes.
+ */
+ while ((ptr = hashmap_first_key(pids))) {
+
+ device = hashmap_remove(pids, ptr);
+ assert(device);
+
+ pid = PTR_TO_UINT(ptr);
+
+ log_debug("Failed to terminate child %d for %s, going to kill it", pid, device);
+ free(device);
+
+ if (kill(pid, SIGKILL) < 0 && errno != ESRCH)
+ log_warning("kill(%d, SIGKILL) failed: %m", pid);
+ }
+
+ return ret;
+}
+
int main(int argc, char *argv[]) {
int r;
@@ -737,16 +1018,27 @@ int main(int argc, char *argv[]) {
if ((r = parse_argv(argc, argv)) <= 0)
goto finish;
- if (arg_console) {
- setsid();
- release_terminal();
- }
+ if (arg_console && !arg_device)
+ /*
+ * Spwan for each console device a own process
+ */
+ r = ask_on_consoles(argc, argv);
+ else {
- if (arg_action == ACTION_WATCH ||
- arg_action == ACTION_WALL)
- r = watch_passwords();
- else
- r = show_passwords();
+ if (arg_device) {
+ /*
+ * Later on a controlling terminal will be 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 (arg_action == ACTION_WATCH || arg_action == ACTION_WALL)
+ r = watch_passwords();
+ else
+ r = show_passwords();
+ }
if (r < 0)
log_error("Error: %s", strerror(-r));