]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
ask-password: ask for passphrases not only on the first console of /dev/console 3247/head
authorWerner Fink <werner@suse.de>
Wed, 18 Nov 2015 11:28:30 +0000 (12:28 +0100)
committerFranck Bui <fbui@suse.com>
Tue, 24 May 2016 09:57:27 +0000 (11:57 +0200)
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.

src/basic/terminal-util.c
src/basic/terminal-util.h
src/tty-ask-password-agent/tty-ask-password-agent.c

index 9521b79daa1530580980e4a5b6bdb6f06a13391f..3189b8789dcbae94d526d8403eee3187004ce0fc 100644 (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;
 
index a7c96a77cbbbeff924944631644d4eb722db568e..b4493709749f3d7425353ed98f8cb621498f6447 100644 (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_;
index ee879c7b896a6bb7bd7cc227e95e435e51edc1dc..8851af449dc90678c9bcd6be9eb6a1fc2409e8c1 100644 (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
 #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>
 
 #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;