summaryrefslogtreecommitdiffstats
path: root/src/ask-password/ask-password.c
blob: 59eb7acdddb3c43ce9702581f9bece931f325086 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
/* SPDX-License-Identifier: LGPL-2.1-or-later */

#include <errno.h>
#include <getopt.h>
#include <stddef.h>
#include <unistd.h>

#include "ask-password-api.h"
#include "build.h"
#include "constants.h"
#include "log.h"
#include "macro.h"
#include "main-func.h"
#include "parse-argument.h"
#include "pretty-print.h"
#include "strv.h"
#include "terminal-util.h"

static const char *arg_icon = NULL;
static const char *arg_id = NULL;               /* identifier for 'ask-password' protocol */
static const char *arg_key_name = NULL;         /* name in kernel keyring */
static const char *arg_credential_name = NULL;  /* name in $CREDENTIALS_DIRECTORY directory */
static char *arg_message = NULL;
static usec_t arg_timeout = DEFAULT_TIMEOUT_USEC;
static bool arg_multiple = false;
static bool arg_no_output = false;
static AskPasswordFlags arg_flags = ASK_PASSWORD_PUSH_CACHE;
static bool arg_newline = true;

STATIC_DESTRUCTOR_REGISTER(arg_message, freep);

static int help(void) {
        _cleanup_free_ char *link = NULL;
        int r;

        r = terminal_urlify_man("systemd-ask-password", "1", &link);
        if (r < 0)
                return log_oom();

        printf("%1$s [OPTIONS...] MESSAGE\n\n"
               "%3$sQuery the user for a passphrase, via the TTY or a UI agent.%4$s\n\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"
               "     --credential=NAME\n"
               "                      Credential name for ImportCredential=, LoadCredential= or\n"
               "                      SetCredential= credentials\n"
               "     --timeout=SEC    Timeout in seconds\n"
               "     --echo=yes|no|masked\n"
               "                      Control whether to show password while typing (echo)\n"
               "  -e --echo           Equivalent to --echo=yes\n"
               "     --emoji=yes|no|auto\n"
               "                      Show a lock and key emoji\n"
               "     --no-tty         Ask question via agent even on TTY\n"
               "     --accept-cached  Accept cached passwords\n"
               "     --multiple       List multiple passwords if available\n"
               "     --no-output      Do not print password to standard output\n"
               "  -n                  Do not suffix password written to standard output with\n"
               "                      newline\n"
               "     --user           Ask only our own user's agents\n"
               "     --system         Ask agents of the system and of all users\n"
               "\nSee the %2$s for details.\n",
               program_invocation_short_name,
               link,
               ansi_highlight(),
               ansi_normal());

        return 0;
}

static int parse_argv(int argc, char *argv[]) {

        enum {
                ARG_ICON = 0x100,
                ARG_TIMEOUT,
                ARG_EMOJI,
                ARG_NO_TTY,
                ARG_ACCEPT_CACHED,
                ARG_MULTIPLE,
                ARG_ID,
                ARG_KEYNAME,
                ARG_NO_OUTPUT,
                ARG_VERSION,
                ARG_CREDENTIAL,
                ARG_USER,
                ARG_SYSTEM,
        };

        static const struct option options[] = {
                { "help",          no_argument,       NULL, 'h'               },
                { "version",       no_argument,       NULL, ARG_VERSION       },
                { "icon",          required_argument, NULL, ARG_ICON          },
                { "timeout",       required_argument, NULL, ARG_TIMEOUT       },
                { "echo",          optional_argument, NULL, 'e'               },
                { "emoji",         required_argument, NULL, ARG_EMOJI         },
                { "no-tty",        no_argument,       NULL, ARG_NO_TTY        },
                { "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       },
                { "no-output",     no_argument,       NULL, ARG_NO_OUTPUT     },
                { "credential",    required_argument, NULL, ARG_CREDENTIAL    },
                { "user",          no_argument,       NULL, ARG_USER          },
                { "system",        no_argument,       NULL, ARG_SYSTEM        },
                {}
        };

        const char *emoji = NULL;
        int c, r;

        assert(argc >= 0);
        assert(argv);

        /* Note the asymmetry: the long option --echo= allows an optional argument, the short option does
         * not. */

        /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long()
         * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */
        optind = 0;
        while ((c = getopt_long(argc, argv, "+hen", options, NULL)) >= 0)

                switch (c) {

                case 'h':
                        return help();

                case ARG_VERSION:
                        return version();

                case ARG_ICON:
                        arg_icon = optarg;
                        break;

                case ARG_TIMEOUT:
                        r = parse_sec(optarg, &arg_timeout);
                        if (r < 0)
                                return log_error_errno(r, "Failed to parse --timeout= parameter: %s", optarg);

                        break;

                case 'e':
                        if (!optarg) {
                                /* Short option -e is used, or no argument to long option --echo= */
                                arg_flags |= ASK_PASSWORD_ECHO;
                                arg_flags &= ~ASK_PASSWORD_SILENT;
                        } else if (isempty(optarg) || streq(optarg, "masked"))
                                /* Empty argument or explicit string "masked" for default behaviour. */
                                arg_flags &= ~(ASK_PASSWORD_ECHO|ASK_PASSWORD_SILENT);
                        else {
                                r = parse_boolean_argument("--echo=", optarg, NULL);
                                if (r < 0)
                                        return r;

                                SET_FLAG(arg_flags, ASK_PASSWORD_ECHO, r);
                                SET_FLAG(arg_flags, ASK_PASSWORD_SILENT, !r);
                        }
                        break;

                case ARG_EMOJI:
                        emoji = optarg;
                        break;

                case ARG_NO_TTY:
                        arg_flags |= ASK_PASSWORD_NO_TTY;
                        break;

                case ARG_ACCEPT_CACHED:
                        arg_flags |= ASK_PASSWORD_ACCEPT_CACHED;
                        break;

                case ARG_MULTIPLE:
                        arg_multiple = true;
                        break;

                case ARG_ID:
                        arg_id = optarg;
                        break;

                case ARG_KEYNAME:
                        arg_key_name = optarg;
                        break;

                case ARG_NO_OUTPUT:
                        arg_no_output = true;
                        break;

                case ARG_CREDENTIAL:
                        arg_credential_name = optarg;
                        break;

                case ARG_USER:
                        arg_flags |= ASK_PASSWORD_USER;
                        break;

                case ARG_SYSTEM:
                        arg_flags &= ~ASK_PASSWORD_USER;
                        break;

                case 'n':
                        arg_newline = false;
                        break;

                case '?':
                        return -EINVAL;

                default:
                        assert_not_reached();
                }

        if (isempty(emoji) || streq(emoji, "auto"))
                SET_FLAG(arg_flags, ASK_PASSWORD_HIDE_EMOJI, FLAGS_SET(arg_flags, ASK_PASSWORD_ECHO));
        else {
                r = parse_boolean_argument("--emoji=", emoji, NULL);
                if (r < 0)
                         return r;

                SET_FLAG(arg_flags, ASK_PASSWORD_HIDE_EMOJI, !r);
        }

        if (argc > optind) {
                arg_message = strv_join(argv + optind, " ");
                if (!arg_message)
                        return log_oom();
        } else if (FLAGS_SET(arg_flags, ASK_PASSWORD_ECHO)) {
                /* By default ask_password_auto() will query with the string "Password: ", which is not right
                 * when full echo is on, since then it's unlikely a password. Let's hence default to a less
                 * confusing string in that case. */

                arg_message = strdup("Input:");
                if (!arg_message)
                        return log_oom();
        }

        return 1;
}

static int run(int argc, char *argv[]) {
        _cleanup_strv_free_erase_ char **l = NULL;
        usec_t timeout;
        int r;

        log_setup();

        /* Unprivileged? Then imply ASK_PASSWORD_USER by default */
        SET_FLAG(arg_flags, ASK_PASSWORD_USER, geteuid() != 0);

        r = parse_argv(argc, argv);
        if (r <= 0)
                return r;

        timeout = arg_timeout > 0 ? usec_add(now(CLOCK_MONOTONIC), arg_timeout) : 0;

        AskPasswordRequest req = {
                .message = arg_message,
                .icon = arg_icon,
                .id = arg_id,
                .keyring = arg_key_name,
                .credential = arg_credential_name ?: "password",
        };

        r = ask_password_auto(&req, timeout, arg_flags, &l);
        if (r < 0)
                return log_error_errno(r, "Failed to query password: %m");

        STRV_FOREACH(p, l) {
                if (!arg_no_output) {
                        if (arg_newline)
                                puts(*p);
                        else
                                fputs(*p, stdout);
                }

                fflush(stdout);

                if (!arg_multiple)
                        break;
        }

        return 0;
}

DEFINE_MAIN_FUNCTION(run);