]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
ask-password: add support for caching passwords in the kernel keyring 1484/head
authorLennart Poettering <lennart@poettering.net>
Wed, 7 Oct 2015 09:26:10 +0000 (11:26 +0200)
committerLennart Poettering <lennart@poettering.net>
Wed, 7 Oct 2015 10:26:14 +0000 (12:26 +0200)
This adds support for caching harddisk passwords in the kernel keyring
if it is available, thus supporting caching without Plymouth being
around.

This is also useful for hooking up "gdm-auto-login" with the collected
boot-time harddisk password, in order to support gnome keyring
passphrase unlocking via the HDD password, if it is the same.

Any passwords added to the kernel keyring this way have a timeout of
2.5min at which time they are purged from the kernel.

16 files changed:
configure.ac
man/systemd-ask-password.xml
src/ask-password/ask-password.c
src/basic/missing.h
src/basic/strv.c
src/basic/strv.h
src/core/execute.c
src/core/main.c
src/cryptsetup/cryptsetup.c
src/firstboot/firstboot.c
src/modules-load/modules-load.c
src/shared/ask-password-api.c
src/shared/ask-password-api.h
src/shared/path-lookup.c
src/test/test-strv.c
src/tty-ask-password-agent/tty-ask-password-agent.c

index aabb5e4fe48680889565355d43f87584316b6baf..3a59a978d3df9a7952b75c42ae29be6d81be4391 100644 (file)
@@ -298,7 +298,7 @@ AC_SUBST(CAP_LIBS)
 
 AC_CHECK_FUNCS([memfd_create])
 AC_CHECK_FUNCS([__secure_getenv secure_getenv])
-AC_CHECK_DECLS([gettid, pivot_root, name_to_handle_at, setns, getrandom, renameat2, kcmp, LO_FLAGS_PARTSCAN],
+AC_CHECK_DECLS([gettid, pivot_root, name_to_handle_at, setns, getrandom, renameat2, kcmp, keyctl, key_serial_t, LO_FLAGS_PARTSCAN],
                [], [], [[
 #include <sys/types.h>
 #include <unistd.h>
index 877c71af539702d716c35e8bc5d6786634915fec..10bb529b812911780cb8e3bbaf232d76f7092510 100644 (file)
@@ -1,4 +1,4 @@
-<?xml version='1.0'?> <!--*-nxml-*-->
+<?xml version='1.0'?> <!--*- Mode: nxml; nxml-child-indent: 2; indent-tabs-mode: nil -*-->
 <!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
   "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
 
     plugged in or at boot, entering an SSL certificate passphrase for
     web and VPN servers.</para>
 
-    <para>Existing agents are: a boot-time password agent asking the
-    user for passwords using Plymouth; a boot-time password agent
-    querying the user directly on the console; an agent requesting
-    password input via a
-    <citerefentry project='man-pages'><refentrytitle>wall</refentrytitle><manvolnum>1</manvolnum></citerefentry>
-    message; an agent suitable for running in a GNOME session; a
-    command line agent which can be started temporarily to process
-    queued password requests; a TTY agent that is temporarily spawned
-    during
-    <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>
-    invocations.</para>
+    <para>Existing agents are:
+    <itemizedlist>
+
+      <listitem><para>A boot-time password agent asking the user for
+      passwords using Plymouth</para></listitem>
+
+      <listitem><para>A boot-time password agent querying the user
+      directly on the console</para></listitem>
+
+      <listitem><para>An agent requesting password input via a
+      <citerefentry
+      project='man-pages'><refentrytitle>wall</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+      message</para></listitem>
+
+      <listitem><para>A command line agent which can be started
+      temporarily to process queued password
+      requests</para></listitem>
+
+      <listitem><para>A TTY agent that is temporarily spawned during
+      <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+      invocations</para></listitem>
+    </itemizedlist></para>
 
     <para>Additional password agents may be implemented according to
     the <ulink
         Icon Naming Specification</ulink>.</para></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><option>--id=</option></term>
+        <listitem><para>Specify an identifier for this password
+        query. This identifier is freely choosable and allows
+        recognition of queries by involved agents. It should include
+        the subsystem doing the query and the specific object the
+        query is done for. Example:
+        <literal>--id=cryptsetup:/dev/sda5</literal>.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--keyname=</option></term>
+        <listitem><para>Configure a kernel keyring key name to use as
+        cache for the password. If set, then the tool will try to push
+        any collected passwords into the kernel keyring of the root
+        user, as a key of the specified name. If combined with
+        <option>--accept-cached</option> it will also try to retrieve
+        the such cached passwords from the key in the kernel keyring
+        instead of querying the user right-away. By using this option
+        the kernel keyring may be used as effective cache to avoid
+        repeatedly asking users for passwords, if there are multiple
+        objects that may be unlocked with the same password. The
+        cached key will have a timeout of 2.5min set, after which it
+        will be purged from the kernel keyring. Note that it is
+        possible to cache multiple passwords under the same keyname,
+        in which case they will be stored as NUL-separated list of
+        passwords. Use
+        <citerefentry><refentrytitle>keyctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+        to access the cached key via the kernel keyring
+        directly. Example: <literal>--keyname=cryptsetup</literal></para></listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><option>--timeout=</option></term>
 
         <term><option>--accept-cached</option></term>
 
         <listitem><para>If passed, accept cached passwords, i.e.
-        passwords previously typed in.</para></listitem>
+        passwords previously typed in. </para></listitem>
       </varlistentry>
 
       <varlistentry>
     <para>
       <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
       <citerefentry><refentrytitle>systemctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+      <citerefentry><refentrytitle>keyctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
       <citerefentry project='die-net'><refentrytitle>plymouth</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
       <citerefentry project='man-pages'><refentrytitle>wall</refentrytitle><manvolnum>1</manvolnum></citerefentry>
     </para>
index bf3fa30f6995a7b5c50242c3f9efc26ce3bff5ad..1a69d15908534b516907dc070ad82478bbf854e3 100644 (file)
 
 static const char *arg_icon = NULL;
 static const char *arg_id = NULL;
-static const char *arg_message = NULL;
-static bool arg_echo = false;
-static bool arg_use_tty = true;
+static const char *arg_keyname = NULL;
+static char *arg_message = NULL;
 static usec_t arg_timeout = DEFAULT_TIMEOUT_USEC;
-static bool arg_accept_cached = false;
 static bool arg_multiple = false;
+static AskPasswordFlags arg_flags = ASK_PASSWORD_PUSH_CACHE;
 
 static void help(void) {
         printf("%s [OPTIONS...] MESSAGE\n\n"
                "Query the user for a system passphrase, via the TTY or an UI agent.\n\n"
-               "  -h --help          Show this help\n"
-               "     --icon=NAME     Icon name\n"
-               "     --timeout=SEC   Timeout in sec\n"
-               "     --echo          Do not mask input (useful for usernames)\n"
-               "     --no-tty        Ask question via agent even on TTY\n"
-               "     --accept-cached Accept cached passwords\n"
-               "     --multiple      List multiple passwords if available\n"
-               "     --id=ID         Query identifier (e.g. cryptsetup:/dev/sda5)\n"
+               "  -h --help           Show this help\n"
+               "     --icon=NAME      Icon name\n"
+               "     --id=ID          Query identifier (e.g. \"cryptsetup:/dev/sda5\")\n"
+               "     --keyname=NAME   Kernel key name for caching passwords (e.g. \"cryptsetup\")\n"
+               "     --timeout=SEC    Timeout in seconds\n"
+               "     --echo           Do not mask input (useful for usernames)\n"
+               "     --no-tty         Ask question via agent even on TTY\n"
+               "     --accept-cached  Accept cached passwords\n"
+               "     --multiple       List multiple passwords if available\n"
                , program_invocation_short_name);
 }
 
@@ -62,7 +62,8 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_NO_TTY,
                 ARG_ACCEPT_CACHED,
                 ARG_MULTIPLE,
-                ARG_ID
+                ARG_ID,
+                ARG_KEYNAME,
         };
 
         static const struct option options[] = {
@@ -74,6 +75,7 @@ static int parse_argv(int argc, char *argv[]) {
                 { "accept-cached", no_argument,       NULL, ARG_ACCEPT_CACHED },
                 { "multiple",      no_argument,       NULL, ARG_MULTIPLE      },
                 { "id",            required_argument, NULL, ARG_ID            },
+                { "keyname",       required_argument, NULL, ARG_KEYNAME       },
                 {}
         };
 
@@ -102,15 +104,15 @@ static int parse_argv(int argc, char *argv[]) {
                         break;
 
                 case ARG_ECHO:
-                        arg_echo = true;
+                        arg_flags |= ASK_PASSWORD_ECHO;
                         break;
 
                 case ARG_NO_TTY:
-                        arg_use_tty = false;
+                        arg_flags |= ASK_PASSWORD_NO_TTY;
                         break;
 
                 case ARG_ACCEPT_CACHED:
-                        arg_accept_cached = true;
+                        arg_flags |= ASK_PASSWORD_ACCEPT_CACHED;
                         break;
 
                 case ARG_MULTIPLE:
@@ -121,6 +123,10 @@ static int parse_argv(int argc, char *argv[]) {
                         arg_id = optarg;
                         break;
 
+                case ARG_KEYNAME:
+                        arg_keyname = optarg;
+                        break;
+
                 case '?':
                         return -EINVAL;
 
@@ -128,18 +134,20 @@ static int parse_argv(int argc, char *argv[]) {
                         assert_not_reached("Unhandled option");
                 }
 
-        if (optind != argc - 1) {
-                log_error("%s: required argument missing.", program_invocation_short_name);
-                return -EINVAL;
+        if (argc > optind) {
+                arg_message = strv_join(argv + optind, " ");
+                if (!arg_message)
+                        return log_oom();
         }
 
-        arg_message = argv[optind];
         return 1;
 }
 
 int main(int argc, char *argv[]) {
-        int r;
+        _cleanup_strv_free_ char **l = NULL;
         usec_t timeout;
+        char **p;
+        int r;
 
         log_parse_environment();
         log_open();
@@ -153,34 +161,21 @@ int main(int argc, char *argv[]) {
         else
                 timeout = 0;
 
-        if (arg_use_tty && isatty(STDIN_FILENO)) {
-                _cleanup_free_ char *password = NULL;
-
-                r = ask_password_tty(arg_message, timeout, arg_echo, NULL, &password);
-                if (r < 0) {
-                        log_error_errno(r, "Failed to ask for password on terminal: %m");
-                        goto finish;
-                }
-
-                puts(password);
-        } else {
-                _cleanup_free_ char **l = NULL;
-                char **p;
-
-                r = ask_password_agent(arg_message, arg_icon, arg_id, timeout, arg_echo, arg_accept_cached, &l);
-                if (r < 0) {
-                        log_error_errno(r, "Failed to ask for password via agent: %m");
-                        goto finish;
-                }
+        r = ask_password_auto(arg_message, arg_icon, arg_id, arg_keyname, timeout, arg_flags, &l);
+        if (r < 0) {
+                log_error_errno(r, "Failed to query password: %m");
+                goto finish;
+        }
 
-                STRV_FOREACH(p, l) {
-                        puts(*p);
+        STRV_FOREACH(p, l) {
+                puts(*p);
 
-                        if (!arg_multiple)
-                                break;
-                }
+                if (!arg_multiple)
+                        break;
         }
 
 finish:
+        free(arg_message);
+
         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
 }
index 1e3af283bb3496e5b819cf5f12f235ed550aa6ab..59e835a4664b84b61392710ac2a1308d512cc335 100644 (file)
@@ -1063,3 +1063,48 @@ static inline int kcmp(pid_t pid1, pid_t pid2, int type, unsigned long idx1, uns
 #ifndef INPUT_PROP_ACCELEROMETER
 #define INPUT_PROP_ACCELEROMETER  0x06
 #endif
+
+#if !HAVE_DECL_KEY_SERIAL_T
+typedef int32_t key_serial_t;
+#endif
+
+#if !HAVE_DECL_KEYCTL
+static inline long keyctl(int cmd, unsigned long arg2, unsigned long arg3, unsigned long arg4,unsigned long arg5) {
+#if defined(__NR_keyctl)
+        return syscall(__NR_keyctl, cmd, arg2, arg3, arg4, arg5);
+#else
+        errno = ENOSYS;
+        return -1;
+#endif
+}
+
+static inline key_serial_t add_key(const char *type, const char *description, const void *payload, size_t plen, key_serial_t ringid) {
+#if defined (__NR_add_key)
+        return syscall(__NR_add_key, type, description, payload, plen, ringid);
+#else
+        errno = ENOSYS;
+        return -1;
+#endif
+}
+
+static inline key_serial_t request_key(const char *type, const char *description, const char * callout_info, key_serial_t destringid) {
+#if defined (__NR_request_key)
+        return syscall(__NR_request_key, type, description, callout_info, destringid);
+#else
+        errno = ENOSYS;
+        return -1;
+#endif
+}
+#endif
+
+#ifndef KEYCTL_READ
+#define KEYCTL_READ 11
+#endif
+
+#ifndef KEYCTL_SET_TIMEOUT
+#define KEYCTL_SET_TIMEOUT 15
+#endif
+
+#ifndef KEY_SPEC_USER_KEYRING
+#define KEY_SPEC_USER_KEYRING -4
+#endif
index d5169467da4a27b111b7da111bd9d5929f130b1e..27cb540895dfd2c5d05194b2c4855248b5ef6e13 100644 (file)
@@ -188,17 +188,48 @@ char **strv_new(const char *x, ...) {
         return r;
 }
 
-int strv_extend_strv(char ***a, char **b) {
-        int r;
-        char **s;
+int strv_extend_strv(char ***a, char **b, bool filter_duplicates) {
+        char **s, **t;
+        size_t p, q, i = 0, j;
+
+        assert(a);
+
+        if (strv_isempty(b))
+                return 0;
+
+        p = strv_length(*a);
+        q = strv_length(b);
+
+        t = realloc(*a, sizeof(char*) * (p + q + 1));
+        if (!t)
+                return -ENOMEM;
+
+        t[p] = NULL;
+        *a = t;
 
         STRV_FOREACH(s, b) {
-                r = strv_extend(a, *s);
-                if (r < 0)
-                        return r;
+
+                if (filter_duplicates && strv_contains(t, *s))
+                        continue;
+
+                t[p+i] = strdup(*s);
+                if (!t[p+i])
+                        goto rollback;
+
+                i++;
+                t[p+i] = NULL;
         }
 
-        return 0;
+        assert(i <= q);
+
+        return (int) i;
+
+rollback:
+        for (j = 0; j < i; j++)
+                free(t[p + j]);
+
+        t[p] = NULL;
+        return -ENOMEM;
 }
 
 int strv_extend_strv_concat(char ***a, char **b, const char *suffix) {
@@ -618,6 +649,41 @@ char **strv_split_nulstr(const char *s) {
         return r;
 }
 
+int strv_make_nulstr(char **l, char **p, size_t *q) {
+        size_t n_allocated = 0, n = 0;
+        _cleanup_free_ char *m = NULL;
+        char **i;
+
+        assert(p);
+        assert(q);
+
+        STRV_FOREACH(i, l) {
+                size_t z;
+
+                z = strlen(*i);
+
+                if (!GREEDY_REALLOC(m, n_allocated, n + z + 1))
+                        return -ENOMEM;
+
+                memcpy(m + n, *i, z + 1);
+                n += z + 1;
+        }
+
+        if (!m) {
+                m = new0(char, 1);
+                if (!m)
+                        return -ENOMEM;
+                n = 0;
+        }
+
+        *p = m;
+        *q = n;
+
+        m = NULL;
+
+        return 0;
+}
+
 bool strv_overlap(char **a, char **b) {
         char **i;
 
@@ -644,8 +710,12 @@ char **strv_sort(char **l) {
 }
 
 bool strv_equal(char **a, char **b) {
-        if (!a || !b)
-                return a == b;
+
+        if (strv_isempty(a))
+                return strv_isempty(b);
+
+        if (strv_isempty(b))
+                return false;
 
         for ( ; *a || *b; ++a, ++b)
                 if (!streq_ptr(*a, *b))
index 7c1f80230a7045e870767fef0b9d0e8c2f9400d4..e49f443835797b458700dcf25b7801044c3defcd 100644 (file)
@@ -40,7 +40,7 @@ void strv_clear(char **l);
 char **strv_copy(char * const *l);
 unsigned strv_length(char * const *l) _pure_;
 
-int strv_extend_strv(char ***a, char **b);
+int strv_extend_strv(char ***a, char **b, bool filter_duplicates);
 int strv_extend_strv_concat(char ***a, char **b, const char *suffix);
 int strv_extend(char ***l, const char *value);
 int strv_extendf(char ***l, const char *format, ...) _printf_(2,0);
@@ -80,6 +80,7 @@ char *strv_join_quoted(char **l);
 
 char **strv_parse_nulstr(const char *s, size_t l);
 char **strv_split_nulstr(const char *s);
+int strv_make_nulstr(char **l, char **p, size_t *n);
 
 bool strv_overlap(char **a, char **b) _pure_;
 
index 4664af873f1e16b2fdfd2ea39cff3c2e477186e0..f5baad05f3038f167a22f047a35705b50b338b19 100644 (file)
@@ -2725,7 +2725,7 @@ int exec_command_append(ExecCommand *c, const char *path, ...) {
         if (!l)
                 return -ENOMEM;
 
-        r = strv_extend_strv(&c->argv, l);
+        r = strv_extend_strv(&c->argv, l, false);
         if (r < 0)
                 return r;
 
index 2406832694ff5593344dae91808a988a1dd438f6..2256fc5b33775e363eeb41972c5d16157c4aefe8 100644 (file)
@@ -601,7 +601,7 @@ static int config_parse_join_controllers(const char *unit,
                         for (a = arg_join_controllers; *a; a++) {
 
                                 if (strv_overlap(*a, l)) {
-                                        if (strv_extend_strv(&l, *a) < 0) {
+                                        if (strv_extend_strv(&l, *a, false) < 0) {
                                                 strv_free(l);
                                                 strv_free_free(t);
                                                 return log_oom();
index 5d5872b7f4b3b58b8fcdefd1c10f581ebfa1e9e8..cc03ad3ca8c7558c5d7a3ca49a9a3569bca87aa3 100644 (file)
@@ -313,14 +313,10 @@ static char *disk_mount_point(const char *label) {
 }
 
 static int get_password(const char *vol, const char *src, usec_t until, bool accept_cached, char ***passwords) {
-        int r = 0;
-        char **p;
-        _cleanup_free_ char *text = NULL;
-        _cleanup_free_ char *escaped_name = NULL;
-        char *id;
+        _cleanup_free_ char *description = NULL, *name_buffer = NULL, *mount_point = NULL, *maj_min = NULL, *text = NULL, *escaped_name = NULL;
         const char *name = NULL;
-        _cleanup_free_ char *description = NULL, *name_buffer = NULL,
-                *mount_point = NULL, *maj_min = NULL;
+        char **p, *id;
+        int r = 0;
 
         assert(vol);
         assert(src);
@@ -364,7 +360,7 @@ static int get_password(const char *vol, const char *src, usec_t until, bool acc
 
         id = strjoina("cryptsetup:", escaped_name);
 
-        r = ask_password_auto(text, "drive-harddisk", id, until, accept_cached, passwords);
+        r = ask_password_auto(text, "drive-harddisk", id, "cryptsetup", until, ASK_PASSWORD_PUSH_CACHE|(accept_cached ? ASK_PASSWORD_ACCEPT_CACHED : 0), passwords);
         if (r < 0)
                 return log_error_errno(r, "Failed to query password: %m");
 
@@ -378,7 +374,7 @@ static int get_password(const char *vol, const char *src, usec_t until, bool acc
 
                 id = strjoina("cryptsetup-verification:", escaped_name);
 
-                r = ask_password_auto(text, "drive-harddisk", id, until, false, &passwords2);
+                r = ask_password_auto(text, "drive-harddisk", id, "cryptsetup", until, ASK_PASSWORD_PUSH_CACHE, &passwords2);
                 if (r < 0)
                         return log_error_errno(r, "Failed to query verification password: %m");
 
index 8c0724a0e7fda8c2ac2c4cac94a777369e4264a0..1562ccf0d79af1c31d780737e511e5692d73c772 100644 (file)
@@ -466,7 +466,7 @@ static int prompt_root_password(void) {
         for (;;) {
                 _cleanup_free_ char *a = NULL, *b = NULL;
 
-                r = ask_password_tty(msg1, 0, false, NULL, &a);
+                r = ask_password_tty(msg1, NULL, 0, 0, NULL, &a);
                 if (r < 0)
                         return log_error_errno(r, "Failed to query root password: %m");
 
@@ -475,7 +475,7 @@ static int prompt_root_password(void) {
                         break;
                 }
 
-                r = ask_password_tty(msg2, 0, false, NULL, &b);
+                r = ask_password_tty(msg2, NULL, 0, 0, NULL, &b);
                 if (r < 0) {
                         clear_string(a);
                         return log_error_errno(r, "Failed to query root password: %m");
index 55bb35b9f781da22b961f18d2387230adacb4b6d..b0a3add3e7d648d665e04b4d6d08940cc804b19a 100644 (file)
@@ -50,7 +50,7 @@ static int add_modules(const char *p) {
         if (!k)
                 return log_oom();
 
-        if (strv_extend_strv(&arg_proc_cmdline_modules, k) < 0)
+        if (strv_extend_strv(&arg_proc_cmdline_modules, k, true) < 0)
                 return log_oom();
 
         return 0;
index 314f983b2060c692a9677c644ea45572174624b8..f8cf11b297434aad908977eee5395e9a6e610b93 100644 (file)
@@ -33,6 +33,7 @@
 #include <unistd.h>
 
 #include "formats-util.h"
+#include "missing.h"
 #include "mkdir.h"
 #include "random-util.h"
 #include "signal-util.h"
 #include "util.h"
 #include "ask-password-api.h"
 
+#define KEYRING_TIMEOUT_USEC ((5 * USEC_PER_MINUTE) / 2)
+
+static int lookup_key(const char *keyname, key_serial_t *ret) {
+        key_serial_t serial;
+
+        assert(keyname);
+        assert(ret);
+
+        serial = request_key("user", keyname, NULL, 0);
+        if (serial == -1)
+                return -errno;
+
+        *ret = serial;
+        return 0;
+}
+
+static int retrieve_key(key_serial_t serial, char ***ret) {
+        _cleanup_free_ char *p = NULL;
+        long m = 100, n;
+        char **l;
+
+        assert(ret);
+
+        for (;;) {
+                p = new(char, m);
+                if (!p)
+                        return -ENOMEM;
+
+                n = keyctl(KEYCTL_READ, (unsigned long) serial, (unsigned long) p, (unsigned long) m, 0);
+                if (n < 0)
+                        return -errno;
+
+                if (n < m)
+                        break;
+
+                free(p);
+                m *= 2;
+        }
+
+        l = strv_parse_nulstr(p, n);
+        if (!l)
+                return -ENOMEM;
+
+        *ret = l;
+        return 0;
+}
+
+static int add_to_keyring(const char *keyname, AskPasswordFlags flags, char **passwords) {
+        _cleanup_strv_free_ char **l = NULL;
+        _cleanup_free_ char *p = NULL;
+        key_serial_t serial;
+        size_t n;
+        int r;
+
+        assert(keyname);
+        assert(passwords);
+
+        if (!(flags & ASK_PASSWORD_PUSH_CACHE))
+                return 0;
+
+        r = lookup_key(keyname, &serial);
+        if (r >= 0) {
+                r = retrieve_key(serial, &l);
+                if (r < 0)
+                        return r;
+        } else if (r != -ENOKEY)
+                return r;
+
+        r = strv_extend_strv(&l, passwords, true);
+        if (r <= 0)
+                return r;
+
+        r = strv_make_nulstr(l, &p, &n);
+        if (r < 0)
+                return r;
+
+        /* Truncate trailing NUL */
+        assert(n > 0);
+        assert(p[n-1] == 0);
+
+        serial = add_key("user", keyname, p, n-1, KEY_SPEC_USER_KEYRING);
+        if (serial == -1)
+                return -errno;
+
+        if (keyctl(KEYCTL_SET_TIMEOUT,
+                   (unsigned long) serial,
+                   (unsigned long) DIV_ROUND_UP(KEYRING_TIMEOUT_USEC, USEC_PER_SEC), 0, 0) < 0)
+                log_debug_errno(errno, "Failed to adjust timeout: %m");
+
+        log_debug("Added key to keyring as %" PRIi32 ".", serial);
+
+        return 1;
+}
+
+static int add_to_keyring_and_log(const char *keyname, AskPasswordFlags flags, char **passwords) {
+        int r;
+
+        assert(keyname);
+        assert(passwords);
+
+        r = add_to_keyring(keyname, flags, passwords);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to add password to keyring: %m");
+
+        return 0;
+}
+
+int ask_password_keyring(const char *keyname, AskPasswordFlags flags, char ***ret) {
+
+        key_serial_t serial;
+        int r;
+
+        assert(keyname);
+        assert(ret);
+
+        if (!(flags & ASK_PASSWORD_ACCEPT_CACHED))
+                return -EUNATCH;
+
+        r = lookup_key(keyname, &serial);
+        if (r == -ENOSYS) /* when retrieving the distinction doesn't matter */
+                return -ENOKEY;
+        if (r < 0)
+                return r;
+
+        return retrieve_key(serial, ret);
+}
+
 static void backspace_chars(int ttyfd, size_t p) {
 
         if (ttyfd < 0)
@@ -56,10 +184,11 @@ static void backspace_chars(int ttyfd, size_t p) {
 
 int ask_password_tty(
                 const char *message,
+                const char *keyname,
                 usec_t until,
-                bool echo,
+                AskPasswordFlags flags,
                 const char *flag_file,
-                char **_passphrase) {
+                char **ret) {
 
         struct termios old_termios, new_termios;
         char passphrase[LINE_MAX], *x;
@@ -68,15 +197,19 @@ int ask_password_tty(
         _cleanup_close_ int ttyfd = -1, notify = -1;
         struct pollfd pollfd[2];
         bool reset_tty = false;
-        bool silent_mode = false;
         bool dirty = false;
         enum {
                 POLL_TTY,
                 POLL_INOTIFY
         };
 
-        assert(message);
-        assert(_passphrase);
+        assert(ret);
+
+        if (flags & ASK_PASSWORD_NO_TTY)
+                return -EUNATCH;
+
+        if (!message)
+                message = "Password:";
 
         if (flag_file) {
                 notify = inotify_init1(IN_CLOEXEC|IN_NONBLOCK);
@@ -147,7 +280,7 @@ int ask_password_tty(
                                 goto finish;
                         }
 
-                k = poll(pollfd, notify > 0 ? 2 : 1, sleep_for);
+                k = poll(pollfd, notify >= 0 ? 2 : 1, sleep_for);
                 if (k < 0) {
                         if (errno == EINTR)
                                 continue;
@@ -159,7 +292,7 @@ int ask_password_tty(
                         goto finish;
                 }
 
-                if (notify > 0 && pollfd[POLL_INOTIFY].revents != 0)
+                if (notify >= 0 && pollfd[POLL_INOTIFY].revents != 0)
                         flush_fd(notify);
 
                 if (pollfd[POLL_TTY].revents == 0)
@@ -180,7 +313,7 @@ int ask_password_tty(
                         break;
                 else if (c == 21) { /* C-u */
 
-                        if (!silent_mode)
+                        if (!(flags & ASK_PASSWORD_SILENT))
                                 backspace_chars(ttyfd, p);
                         p = 0;
 
@@ -188,28 +321,28 @@ int ask_password_tty(
 
                         if (p > 0) {
 
-                                if (!silent_mode)
+                                if (!(flags & ASK_PASSWORD_SILENT))
                                         backspace_chars(ttyfd, 1);
 
                                 p--;
-                        } else if (!dirty && !silent_mode) {
+                        } else if (!dirty && !(flags & ASK_PASSWORD_SILENT)) {
 
-                                silent_mode = true;
+                                flags |= ASK_PASSWORD_SILENT;
 
                                 /* There are two ways to enter silent
                                  * mode. Either by pressing backspace
-                                 * as first key (and only as first key),
-                                 * or ... */
+                                 * as first key (and only as first
+                                 * key), or ... */
                                 if (ttyfd >= 0)
                                         loop_write(ttyfd, "(no echo) ", 10, false);
 
                         } else if (ttyfd >= 0)
                                 loop_write(ttyfd, "\a", 1, false);
 
-                } else if (c == '\t' && !silent_mode) {
+                } else if (c == '\t' && !(flags & ASK_PASSWORD_SILENT)) {
 
                         backspace_chars(ttyfd, p);
-                        silent_mode = true;
+                        flags |= ASK_PASSWORD_SILENT;
 
                         /* ... or by pressing TAB at any time. */
 
@@ -223,8 +356,8 @@ int ask_password_tty(
 
                         passphrase[p++] = c;
 
-                        if (!silent_mode && ttyfd >= 0)
-                                loop_write(ttyfd, echo ? &c : "*", 1, false);
+                        if (!(flags & ASK_PASSWORD_SILENT) && ttyfd >= 0)
+                                loop_write(ttyfd, (flags & ASK_PASSWORD_ECHO) ? &c : "*", 1, false);
 
                         dirty = true;
                 }
@@ -236,7 +369,10 @@ int ask_password_tty(
                 goto finish;
         }
 
-        *_passphrase = x;
+        if (keyname)
+                (void) add_to_keyring_and_log(keyname, flags, STRV_MAKE(x));
+
+        *ret = x;
         r = 0;
 
 finish:
@@ -289,10 +425,10 @@ int ask_password_agent(
                 const char *message,
                 const char *icon,
                 const char *id,
+                const char *keyname,
                 usec_t until,
-                bool echo,
-                bool accept_cached,
-                char ***_passphrases) {
+                AskPasswordFlags flags,
+                char ***ret) {
 
         enum {
                 FD_SOCKET,
@@ -300,17 +436,20 @@ int ask_password_agent(
                 _FD_MAX
         };
 
+        _cleanup_close_ int socket_fd = -1, signal_fd = -1, fd = -1;
         char temp[] = "/run/systemd/ask-password/tmp.XXXXXX";
         char final[sizeof(temp)] = "";
-        _cleanup_fclose_ FILE *f = NULL;
         _cleanup_free_ char *socket_name = NULL;
-        _cleanup_close_ int socket_fd = -1, signal_fd = -1, fd = -1;
-        sigset_t mask, oldmask;
+        _cleanup_strv_free_ char **l = NULL;
+        _cleanup_fclose_ FILE *f = NULL;
         struct pollfd pollfd[_FD_MAX];
+        sigset_t mask, oldmask;
         int r;
 
-        assert(_passphrases);
+        assert(ret);
 
+        if (flags & ASK_PASSWORD_NO_AGENT)
+                return -EUNATCH;
 
         assert_se(sigemptyset(&mask) >= 0);
         assert_se(sigset_add_many(&mask, SIGINT, SIGTERM, -1) >= 0);
@@ -355,8 +494,8 @@ int ask_password_agent(
                 "NotAfter="USEC_FMT"\n",
                 getpid(),
                 socket_name,
-                accept_cached ? 1 : 0,
-                echo ? 1 : 0,
+                (flags & ASK_PASSWORD_ACCEPT_CACHED) ? 1 : 0,
+                (flags & ASK_PASSWORD_ECHO) ? 1 : 0,
                 until);
 
         if (message)
@@ -476,38 +615,38 @@ int ask_password_agent(
                 }
 
                 if (passphrase[0] == '+') {
-                        char **l;
-
+                        /* An empty message refers to the empty password */
                         if (n == 1)
                                 l = strv_new("", NULL);
                         else
                                 l = strv_parse_nulstr(passphrase+1, n-1);
-                                /* An empty message refers to the empty password */
-
                         if (!l) {
                                 r = -ENOMEM;
                                 goto finish;
                         }
 
                         if (strv_length(l) <= 0) {
-                                strv_free(l);
+                                l = strv_free(l);
                                 log_debug("Invalid packet");
                                 continue;
                         }
 
-                        *_passphrases = l;
+                        break;
+                }
 
-                } else if (passphrase[0] == '-') {
+                if (passphrase[0] == '-') {
                         r = -ECANCELED;
                         goto finish;
-                } else {
-                        log_debug("Invalid packet");
-                        continue;
                 }
 
-                break;
+                log_debug("Invalid packet");
         }
 
+        if (keyname)
+                (void) add_to_keyring_and_log(keyname, flags, l);
+
+        *ret = l;
+        l = NULL;
         r = 0;
 
 finish:
@@ -520,7 +659,6 @@ finish:
                 (void) unlink(final);
 
         assert_se(sigprocmask(SIG_SETMASK, &oldmask, NULL) == 0);
-
         return r;
 }
 
@@ -528,19 +666,25 @@ int ask_password_auto(
                 const char *message,
                 const char *icon,
                 const char *id,
+                const char *keyname,
                 usec_t until,
-                bool accept_cached,
-                char ***_passphrases) {
+                AskPasswordFlags flags,
+                char ***ret) {
 
         int r;
 
-        assert(message);
-        assert(_passphrases);
+        assert(ret);
 
-        if (isatty(STDIN_FILENO)) {
+        if ((flags & ASK_PASSWORD_ACCEPT_CACHED) && keyname) {
+                r = ask_password_keyring(keyname, flags, ret);
+                if (r != -ENOKEY)
+                        return r;
+        }
+
+        if (!(flags & ASK_PASSWORD_NO_TTY) && isatty(STDIN_FILENO)) {
                 char *s = NULL, **l = NULL;
 
-                r = ask_password_tty(message, until, false, NULL, &s);
+                r = ask_password_tty(message, keyname, until, flags, NULL, &s);
                 if (r < 0)
                         return r;
 
@@ -548,9 +692,12 @@ int ask_password_auto(
                 if (r < 0)
                         return -ENOMEM;
 
-                *_passphrases = l;
+                *ret = l;
                 return 0;
         }
 
-        return ask_password_agent(message, icon, id, until, false, accept_cached, _passphrases);
+        if (!(flags & ASK_PASSWORD_NO_AGENT))
+                return ask_password_agent(message, icon, id, keyname, until, flags, ret);
+
+        return -EUNATCH;
 }
index aeb045fe19486a48fa259e0376bf18bb92375dcd..913cad9f8ae6f88b10bb381eaf337a5610a7bf2d 100644 (file)
 
 #include "time-util.h"
 
-int ask_password_tty(const char *message, usec_t until, bool echo, const char *flag_file, char **_passphrase);
-int ask_password_agent(const char *message, const char *icon, const char *id, usec_t until, bool echo, bool accept_cached, char ***_passphrases);
-int ask_password_auto(const char *message, const char *icon, const char *id, usec_t until, bool accept_cached, char ***_passphrases);
+typedef enum AskPasswordFlags {
+        ASK_PASSWORD_ACCEPT_CACHED = 1,
+        ASK_PASSWORD_PUSH_CACHE = 2,
+        ASK_PASSWORD_ECHO = 4,                /* show the password literally while reading, instead of "*" */
+        ASK_PASSWORD_SILENT = 8,              /* do no show any password at all while reading */
+        ASK_PASSWORD_NO_TTY = 16,
+        ASK_PASSWORD_NO_AGENT = 32,
+} AskPasswordFlags;
+
+int ask_password_tty(const char *message, const char *keyname, usec_t until, AskPasswordFlags flags, const char *flag_file, char **ret);
+int ask_password_agent(const char *message, const char *icon, const char *id, const char *keyname, usec_t until, AskPasswordFlags flag, char ***ret);
+int ask_password_keyring(const char *keyname, AskPasswordFlags flags, char ***ret);
+int ask_password_auto(const char *message, const char *icon, const char *id, const char *keyname, usec_t until, AskPasswordFlags flag, char ***ret);
index d803bbe07e1cc68151218c4902e25ef1375edd3d..34eec959ef910191120915394ea31b91fb5c2250 100644 (file)
@@ -181,7 +181,7 @@ static char** user_dirs(
                 if (strv_extend_strv_concat(&res, config_dirs, "/systemd/user") < 0)
                         return NULL;
 
-        if (strv_extend_strv(&res, (char**) config_unit_paths) < 0)
+        if (strv_extend_strv(&res, (char**) config_unit_paths, false) < 0)
                 return NULL;
 
         if (runtime_dir)
@@ -203,7 +203,7 @@ static char** user_dirs(
                 if (strv_extend_strv_concat(&res, data_dirs, "/systemd/user") < 0)
                         return NULL;
 
-        if (strv_extend_strv(&res, (char**) data_unit_paths) < 0)
+        if (strv_extend_strv(&res, (char**) data_unit_paths, false) < 0)
                 return NULL;
 
         if (generator_late)
@@ -318,7 +318,7 @@ int lookup_paths_init(
                 if (!unit_path)
                         return -ENOMEM;
 
-                r = strv_extend_strv(&p->unit_path, unit_path);
+                r = strv_extend_strv(&p->unit_path, unit_path, false);
                 if (r < 0)
                         return r;
         }
index 64801f8e01c62e7ca76ec1ccccfe2884427d4ef6..623c9265210c1f1b4aeb0e66ce679977b19e3d2d 100644 (file)
@@ -341,11 +341,11 @@ static void test_strv_extend_strv(void) {
         _cleanup_strv_free_ char **a = NULL, **b = NULL;
 
         a = strv_new("abc", "def", "ghi", NULL);
-        b = strv_new("jkl", "mno", "pqr", NULL);
+        b = strv_new("jkl", "mno", "abc", "pqr", NULL);
         assert_se(a);
         assert_se(b);
 
-        assert_se(strv_extend_strv(&a, b) >= 0);
+        assert_se(strv_extend_strv(&a, b, true) == 3);
 
         assert_se(streq(a[0], "abc"));
         assert_se(streq(a[1], "def"));
@@ -618,6 +618,28 @@ static void test_strv_extend_n(void) {
         assert_se(v[1] == NULL);
 }
 
+static void test_strv_make_nulstr_one(char **l) {
+        _cleanup_free_ char *b = NULL, *c = NULL;
+        _cleanup_strv_free_ char **q = NULL;
+        size_t n, m;
+
+        assert_se(strv_make_nulstr(l, &b, &n) >= 0);
+        assert_se(q = strv_parse_nulstr(b, n));
+        assert_se(strv_equal(l, q));
+
+        assert_se(strv_make_nulstr(q, &c, &m) >= 0);
+        assert_se(m == n);
+        assert_se(memcmp(b, c, m) == 0);
+}
+
+static void test_strv_make_nulstr(void) {
+        test_strv_make_nulstr_one(NULL);
+        test_strv_make_nulstr_one(STRV_MAKE(NULL));
+        test_strv_make_nulstr_one(STRV_MAKE("foo"));
+        test_strv_make_nulstr_one(STRV_MAKE("foo", "bar"));
+        test_strv_make_nulstr_one(STRV_MAKE("foo", "bar", "quuux"));
+}
+
 int main(int argc, char *argv[]) {
         test_specifier_printf();
         test_strv_foreach();
@@ -678,6 +700,7 @@ int main(int argc, char *argv[]) {
         test_strv_shell_escape();
         test_strv_skip();
         test_strv_extend_n();
+        test_strv_make_nulstr();
 
         return 0;
 }
index e1e2945c06d9bbc1c9fa32b499520cf2c2f9d6b4..5dbc0a9bcce2099dfba12d5a52325003906a2bf0 100644 (file)
@@ -58,9 +58,9 @@ static bool arg_console = false;
 static int ask_password_plymouth(
                 const char *message,
                 usec_t until,
+                AskPasswordFlags flags,
                 const char *flag_file,
-                bool accept_cached,
-                char ***_passphrases) {
+                char ***ret) {
 
         _cleanup_close_ int fd = -1, notify = -1;
         union sockaddr_union sa = PLYMOUTH_SOCKET;
@@ -75,7 +75,7 @@ static int ask_password_plymouth(
                 POLL_INOTIFY
         };
 
-        assert(_passphrases);
+        assert(ret);
 
         if (flag_file) {
                 notify = inotify_init1(IN_CLOEXEC|IN_NONBLOCK);
@@ -95,12 +95,11 @@ static int ask_password_plymouth(
         if (r < 0)
                 return -errno;
 
-        if (accept_cached) {
+        if (flags & ASK_PASSWORD_ACCEPT_CACHED) {
                 packet = strdup("c");
                 n = 1;
         } else if (asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1), message, &n) < 0)
                 packet = NULL;
-
         if (!packet)
                 return -ENOMEM;
 
@@ -130,7 +129,7 @@ static int ask_password_plymouth(
                 if (flag_file && access(flag_file, F_OK) < 0)
                         return -errno;
 
-                j = poll(pollfd, notify > 0 ? 2 : 1, sleep_for);
+                j = poll(pollfd, notify >= 0 ? 2 : 1, sleep_for);
                 if (j < 0) {
                         if (errno == EINTR)
                                 continue;
@@ -139,15 +138,20 @@ static int ask_password_plymouth(
                 } else if (j == 0)
                         return -ETIME;
 
-                if (notify > 0 && pollfd[POLL_INOTIFY].revents != 0)
+                if (notify >= 0 && pollfd[POLL_INOTIFY].revents != 0)
                         flush_fd(notify);
 
                 if (pollfd[POLL_SOCKET].revents == 0)
                         continue;
 
                 k = read(fd, buffer + p, sizeof(buffer) - p);
-                if (k <= 0)
-                        return r = k < 0 ? -errno : -EIO;
+                if (k < 0) {
+                        if (errno == EINTR || errno == EAGAIN)
+                                continue;
+
+                        return -errno;
+                } else if (k == 0)
+                        return -EIO;
 
                 p += k;
 
@@ -156,7 +160,7 @@ static int ask_password_plymouth(
 
                 if (buffer[0] == 5) {
 
-                        if (accept_cached) {
+                        if (flags & ASK_PASSWORD_ACCEPT_CACHED) {
                                 /* Hmm, first try with cached
                                  * passwords failed, so let's retry
                                  * with a normal password request */
@@ -169,7 +173,7 @@ static int ask_password_plymouth(
                                 if (r < 0)
                                         return r;
 
-                                accept_cached = false;
+                                flags &= ~ASK_PASSWORD_ACCEPT_CACHED;
                                 p = 0;
                                 continue;
                         }
@@ -197,7 +201,7 @@ static int ask_password_plymouth(
                         if (!l)
                                 return -ENOMEM;
 
-                        *_passphrases = l;
+                        *ret = l;
                         break;
 
                 } else
@@ -282,7 +286,7 @@ static int parse_password(const char *filename, char **wall) {
                 if (arg_plymouth) {
                         _cleanup_strv_free_ char **passwords = NULL;
 
-                        r = ask_password_plymouth(message, not_after, filename, accept_cached, &passwords);
+                        r = ask_password_plymouth(message, not_after, accept_cached ? ASK_PASSWORD_ACCEPT_CACHED : 0, filename, &passwords);
                         if (r >= 0) {
                                 char **p;
 
@@ -313,7 +317,7 @@ static int parse_password(const char *filename, char **wall) {
                                         return log_error_errno(tty_fd, "Failed to acquire /dev/console: %m");
                         }
 
-                        r = ask_password_tty(message, not_after, echo, filename, &password);
+                        r = ask_password_tty(message, NULL, not_after, echo ? ASK_PASSWORD_ECHO : 0, filename, &password);
 
                         if (arg_console) {
                                 tty_fd = safe_close(tty_fd);
@@ -360,6 +364,8 @@ static int wall_tty_block(void) {
         int fd, r;
 
         r = get_ctty_devnr(0, &devnr);
+        if (r == -ENXIO) /* We have no controlling tty */
+                return -ENOTTY;
         if (r < 0)
                 return log_error_errno(r, "Failed to get controlling TTY: %m");