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));
openSUSE Build Service is sponsored by