]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/ask-password/ask-password.c
pcrlock: process components outside of location window properly
[thirdparty/systemd.git] / src / ask-password / ask-password.c
CommitLineData
db9ecf05 1/* SPDX-License-Identifier: LGPL-2.1-or-later */
490aed58 2
490aed58 3#include <getopt.h>
00843602 4#include <unistd.h>
490aed58 5
066f6bfb
LP
6#include "sd-varlink.h"
7
decad482 8#include "alloc-util.h"
00843602 9#include "ask-password-api.h"
d6b4d1c7 10#include "build.h"
066f6bfb 11#include "bus-polkit.h"
28db6fbf 12#include "constants.h"
8857aa74 13#include "hashmap.h"
066f6bfb 14#include "json-util.h"
490aed58 15#include "log.h"
c7d7adf5 16#include "main-func.h"
e390c34d 17#include "parse-argument.h"
294bf0c3 18#include "pretty-print.h"
066f6bfb 19#include "string-table.h"
8857aa74 20#include "string-util.h"
21bc923a 21#include "strv.h"
8857aa74 22#include "time-util.h"
066f6bfb
LP
23#include "varlink-io.systemd.AskPassword.h"
24#include "varlink-util.h"
490aed58
LP
25
26static const char *arg_icon = NULL;
8806bb4b
LP
27static const char *arg_id = NULL; /* identifier for 'ask-password' protocol */
28static const char *arg_key_name = NULL; /* name in kernel keyring */
29static const char *arg_credential_name = NULL; /* name in $CREDENTIALS_DIRECTORY directory */
e287086b 30static char *arg_message = NULL;
7f434cf4 31static usec_t arg_timeout = DEFAULT_TIMEOUT_USEC;
21bc923a 32static bool arg_multiple = false;
a5a4e365 33static bool arg_no_output = false;
e287086b 34static AskPasswordFlags arg_flags = ASK_PASSWORD_PUSH_CACHE;
b80ef40c 35static bool arg_newline = true;
066f6bfb 36static bool arg_varlink = false;
490aed58 37
c7d7adf5
YW
38STATIC_DESTRUCTOR_REGISTER(arg_message, freep);
39
37ec0fdd
LP
40static int help(void) {
41 _cleanup_free_ char *link = NULL;
42 int r;
43
44 r = terminal_urlify_man("systemd-ask-password", "1", &link);
45 if (r < 0)
46 return log_oom();
47
8806bb4b 48 printf("%1$s [OPTIONS...] MESSAGE\n\n"
9c1fa3c2 49 "%3$sQuery the user for a passphrase, via the TTY or a UI agent.%4$s\n\n"
e287086b
LP
50 " -h --help Show this help\n"
51 " --icon=NAME Icon name\n"
52 " --id=ID Query identifier (e.g. \"cryptsetup:/dev/sda5\")\n"
53 " --keyname=NAME Kernel key name for caching passwords (e.g. \"cryptsetup\")\n"
8806bb4b 54 " --credential=NAME\n"
bbfb25f4
DDM
55 " Credential name for ImportCredential=, LoadCredential= or\n"
56 " SetCredential= credentials\n"
e287086b 57 " --timeout=SEC Timeout in seconds\n"
49365d1c
LP
58 " --echo=yes|no|masked\n"
59 " Control whether to show password while typing (echo)\n"
60 " -e --echo Equivalent to --echo=yes\n"
e390c34d
CH
61 " --emoji=yes|no|auto\n"
62 " Show a lock and key emoji\n"
e287086b
LP
63 " --no-tty Ask question via agent even on TTY\n"
64 " --accept-cached Accept cached passwords\n"
65 " --multiple List multiple passwords if available\n"
a5a4e365 66 " --no-output Do not print password to standard output\n"
b80ef40c
LP
67 " -n Do not suffix password written to standard output with\n"
68 " newline\n"
9c1fa3c2
LP
69 " --user Ask only our own user's agents\n"
70 " --system Ask agents of the system and of all users\n"
8806bb4b 71 "\nSee the %2$s for details.\n",
bc556335 72 program_invocation_short_name,
8806bb4b
LP
73 link,
74 ansi_highlight(),
75 ansi_normal());
37ec0fdd
LP
76
77 return 0;
490aed58
LP
78}
79
80static int parse_argv(int argc, char *argv[]) {
81
82 enum {
83 ARG_ICON = 0x100,
1b39d4b9 84 ARG_TIMEOUT,
e390c34d 85 ARG_EMOJI,
21bc923a
LP
86 ARG_NO_TTY,
87 ARG_ACCEPT_CACHED,
9fa1de96 88 ARG_MULTIPLE,
e287086b
LP
89 ARG_ID,
90 ARG_KEYNAME,
a5a4e365 91 ARG_NO_OUTPUT,
37ec0fdd 92 ARG_VERSION,
8806bb4b 93 ARG_CREDENTIAL,
9c1fa3c2
LP
94 ARG_USER,
95 ARG_SYSTEM,
490aed58
LP
96 };
97
98 static const struct option options[] = {
21bc923a 99 { "help", no_argument, NULL, 'h' },
37ec0fdd 100 { "version", no_argument, NULL, ARG_VERSION },
21bc923a
LP
101 { "icon", required_argument, NULL, ARG_ICON },
102 { "timeout", required_argument, NULL, ARG_TIMEOUT },
49365d1c 103 { "echo", optional_argument, NULL, 'e' },
e390c34d 104 { "emoji", required_argument, NULL, ARG_EMOJI },
21bc923a
LP
105 { "no-tty", no_argument, NULL, ARG_NO_TTY },
106 { "accept-cached", no_argument, NULL, ARG_ACCEPT_CACHED },
107 { "multiple", no_argument, NULL, ARG_MULTIPLE },
9fa1de96 108 { "id", required_argument, NULL, ARG_ID },
e287086b 109 { "keyname", required_argument, NULL, ARG_KEYNAME },
a5a4e365 110 { "no-output", no_argument, NULL, ARG_NO_OUTPUT },
8806bb4b 111 { "credential", required_argument, NULL, ARG_CREDENTIAL },
9c1fa3c2
LP
112 { "user", no_argument, NULL, ARG_USER },
113 { "system", no_argument, NULL, ARG_SYSTEM },
eb9da376 114 {}
490aed58
LP
115 };
116
e390c34d 117 const char *emoji = NULL;
49365d1c 118 int c, r;
490aed58
LP
119
120 assert(argc >= 0);
121 assert(argv);
122
49365d1c
LP
123 /* Note the asymmetry: the long option --echo= allows an optional argument, the short option does
124 * not. */
ef9c12b1
YW
125
126 /* Resetting to 0 forces the invocation of an internal initialization routine of getopt_long()
127 * that checks for GNU extensions in optstring ('-' or '+' at the beginning). */
128 optind = 0;
b80ef40c 129 while ((c = getopt_long(argc, argv, "+hen", options, NULL)) >= 0)
490aed58
LP
130
131 switch (c) {
132
133 case 'h':
37ec0fdd
LP
134 return help();
135
136 case ARG_VERSION:
137 return version();
490aed58
LP
138
139 case ARG_ICON:
140 arg_icon = optarg;
141 break;
142
143 case ARG_TIMEOUT:
49365d1c
LP
144 r = parse_sec(optarg, &arg_timeout);
145 if (r < 0)
146 return log_error_errno(r, "Failed to parse --timeout= parameter: %s", optarg);
147
490aed58
LP
148 break;
149
49365d1c
LP
150 case 'e':
151 if (!optarg) {
152 /* Short option -e is used, or no argument to long option --echo= */
153 arg_flags |= ASK_PASSWORD_ECHO;
154 arg_flags &= ~ASK_PASSWORD_SILENT;
155 } else if (isempty(optarg) || streq(optarg, "masked"))
156 /* Empty argument or explicit string "masked" for default behaviour. */
157 arg_flags &= ~(ASK_PASSWORD_ECHO|ASK_PASSWORD_SILENT);
158 else {
51214cf4 159 r = parse_boolean_argument("--echo=", optarg, NULL);
49365d1c
LP
160 if (r < 0)
161 return r;
162
51214cf4
ZJS
163 SET_FLAG(arg_flags, ASK_PASSWORD_ECHO, r);
164 SET_FLAG(arg_flags, ASK_PASSWORD_SILENT, !r);
49365d1c 165 }
64845bdc
DS
166 break;
167
e390c34d
CH
168 case ARG_EMOJI:
169 emoji = optarg;
170 break;
171
1b39d4b9 172 case ARG_NO_TTY:
e287086b 173 arg_flags |= ASK_PASSWORD_NO_TTY;
1b39d4b9
LP
174 break;
175
21bc923a 176 case ARG_ACCEPT_CACHED:
e287086b 177 arg_flags |= ASK_PASSWORD_ACCEPT_CACHED;
21bc923a
LP
178 break;
179
180 case ARG_MULTIPLE:
181 arg_multiple = true;
182 break;
183
9fa1de96
DH
184 case ARG_ID:
185 arg_id = optarg;
186 break;
187
e287086b 188 case ARG_KEYNAME:
8806bb4b 189 arg_key_name = optarg;
e287086b
LP
190 break;
191
a5a4e365
CH
192 case ARG_NO_OUTPUT:
193 arg_no_output = true;
194 break;
195
8806bb4b
LP
196 case ARG_CREDENTIAL:
197 arg_credential_name = optarg;
198 break;
199
9c1fa3c2
LP
200 case ARG_USER:
201 arg_flags |= ASK_PASSWORD_USER;
202 break;
203
204 case ARG_SYSTEM:
205 arg_flags &= ~ASK_PASSWORD_USER;
206 break;
207
b80ef40c
LP
208 case 'n':
209 arg_newline = false;
210 break;
211
490aed58
LP
212 case '?':
213 return -EINVAL;
214
215 default:
04499a70 216 assert_not_reached();
490aed58 217 }
490aed58 218
e390c34d
CH
219 if (isempty(emoji) || streq(emoji, "auto"))
220 SET_FLAG(arg_flags, ASK_PASSWORD_HIDE_EMOJI, FLAGS_SET(arg_flags, ASK_PASSWORD_ECHO));
221 else {
51214cf4 222 r = parse_boolean_argument("--emoji=", emoji, NULL);
e390c34d 223 if (r < 0)
066f6bfb 224 return r;
49365d1c 225
51214cf4 226 SET_FLAG(arg_flags, ASK_PASSWORD_HIDE_EMOJI, !r);
e390c34d
CH
227 }
228
e287086b
LP
229 if (argc > optind) {
230 arg_message = strv_join(argv + optind, " ");
a5116848
LP
231 if (!arg_message)
232 return log_oom();
233 } else if (FLAGS_SET(arg_flags, ASK_PASSWORD_ECHO)) {
234 /* By default ask_password_auto() will query with the string "Password: ", which is not right
235 * when full echo is on, since then it's unlikely a password. Let's hence default to a less
236 * confusing string in that case. */
237
238 arg_message = strdup("Input:");
e287086b
LP
239 if (!arg_message)
240 return log_oom();
490aed58
LP
241 }
242
066f6bfb
LP
243 r = sd_varlink_invocation(SD_VARLINK_ALLOW_ACCEPT);
244 if (r < 0)
245 return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m");
246 if (r > 0)
247 arg_varlink = true;
248
1b39d4b9 249 return 1;
490aed58
LP
250}
251
066f6bfb
LP
252typedef enum EchoMode {
253 ECHO_OFF,
254 ECHO_ON,
255 ECHO_MASKED,
256 _ECHO_MODE_MAX,
257 _ECHO_MODE_INVALID = -EINVAL,
258} EchoMode;
259
260static const char* echo_mode_table[_ECHO_MODE_MAX] = {
261 [ECHO_OFF] = "off",
262 [ECHO_ON] = "on",
263 [ECHO_MASKED] = "masked",
264};
265
266DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(echo_mode, EchoMode, ECHO_ON);
267
268static JSON_DISPATCH_ENUM_DEFINE(dispatch_echo_mode, EchoMode, echo_mode_from_string);
269
270typedef struct MethodAskParameters {
271 const char *message;
272 const char *keyring;
273 const char *icon;
274 const char *id;
275 uint64_t timeout_usec;
276 uint64_t until_usec;
277 int accept_cached;
278 int push_cache;
279 EchoMode echo_mode;
280} MethodAskParameters;
281
282static int vl_method_ask(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
283
284 static const sd_json_dispatch_field dispatch_table[] = {
285 { "message", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(MethodAskParameters, message), 0 },
286 { "keyname", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(MethodAskParameters, keyring), 0 },
287 { "icon", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(MethodAskParameters, icon), 0 },
288 { "id", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(MethodAskParameters, id), 0 },
289 { "timeoutUSec", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(MethodAskParameters, timeout_usec), 0 },
290 { "untilUSec", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, offsetof(MethodAskParameters, until_usec), 0 },
291 { "acceptCached", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, offsetof(MethodAskParameters, accept_cached), 0 },
292 { "pushCache", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_tristate, offsetof(MethodAskParameters, push_cache) , 0 },
293 { "echo", SD_JSON_VARIANT_STRING, dispatch_echo_mode, offsetof(MethodAskParameters, echo_mode), 0 },
294 VARLINK_DISPATCH_POLKIT_FIELD,
295 {}
296 };
297
298 Hashmap **polkit_registry = ASSERT_PTR(userdata);
299 MethodAskParameters p = {
300 .timeout_usec = DEFAULT_TIMEOUT_USEC,
301 .until_usec = UINT64_MAX,
302 .accept_cached = -1,
303 .push_cache = -1,
304 .echo_mode = _ECHO_MODE_INVALID,
305 };
306 int r;
307
308 assert(link);
309
310 r = sd_varlink_dispatch(link, parameters, dispatch_table, &p);
311 if (r != 0)
312 return r;
313
314 r = varlink_verify_polkit_async_full(
315 link,
316 /* bus= */ NULL,
317 "io.systemd.ask-password.ask",
318 /* details= */ NULL,
319 /* good_user= */ FLAGS_SET(arg_flags, ASK_PASSWORD_USER) ? getuid() : UID_INVALID,
320 /* flags= */ 0,
321 polkit_registry);
322 if (r <= 0)
323 return r;
324
325 AskPasswordRequest req = {
326 .tty_fd = -EBADF,
327 .message = p.message ?: arg_message,
328 .icon = p.icon ?: arg_icon,
329 .id = p.id ?: arg_id,
330 .keyring = p.keyring ?: arg_key_name,
331 .credential = arg_credential_name,
332 .hup_fd = sd_varlink_get_input_fd(link),
333 };
334
335 /* Specifying the absolute or relative timeout as zero means: do not ask interactively, only check
336 * cache, hence leave the field at zero in that case. Otherwise we take the minimum of both times. */
337 if (p.timeout_usec != 0 && p.until_usec != 0)
338 req.until = MIN(usec_add(now(CLOCK_MONOTONIC), p.timeout_usec), p.until_usec);
339
340 /* If the timeout is set to zero, don't ask agents, just stick to cache */
341 SET_FLAG(arg_flags, ASK_PASSWORD_NO_AGENT, req.until == 0);
342
343 if (p.accept_cached >= 0)
344 SET_FLAG(arg_flags, ASK_PASSWORD_ACCEPT_CACHED, p.accept_cached);
345
346 if (p.push_cache >= 0)
347 SET_FLAG(arg_flags, ASK_PASSWORD_PUSH_CACHE, p.push_cache);
348
349 if (p.echo_mode >= 0) {
350 SET_FLAG(arg_flags, ASK_PASSWORD_ECHO, p.echo_mode == ECHO_ON);
351 SET_FLAG(arg_flags, ASK_PASSWORD_SILENT, p.echo_mode == ECHO_OFF);
352 }
353
354 _cleanup_strv_free_erase_ char **l = NULL;
355 r = ask_password_auto(&req, arg_flags, &l);
356 if (r == -EUNATCH)
357 return sd_varlink_error(link, "io.systemd.AskPassword.NoPasswordAvailable", NULL);
358 if (r == -ETIME)
359 return sd_varlink_error(link, "io.systemd.AskPassword.TimeoutReached", NULL);
360 if (r == -ECONNRESET) { /* POLLHUP on the varlink fd we passed in via .hup_fd */
361 sd_varlink_close(link);
362 return 1;
363 }
364 if (r < 0)
365 return r;
366
367 _cleanup_(sd_json_variant_unrefp) sd_json_variant *vl = NULL;
368 r = sd_json_variant_new_array_strv(&vl, l);
369 if (r < 0)
370 return r;
371
372 sd_json_variant_sensitive(vl);
373
374 return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR("passwords", SD_JSON_BUILD_VARIANT(vl)));
375}
376
377static int vl_server(void) {
378 _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *varlink_server = NULL;
fe5a1afb 379 _cleanup_hashmap_free_ Hashmap *polkit_registry = NULL;
066f6bfb
LP
380 int r;
381
382 r = varlink_server_new(&varlink_server, SD_VARLINK_SERVER_INHERIT_USERDATA, /* userdata= */ &polkit_registry);
383 if (r < 0)
384 return log_error_errno(r, "Failed to allocate Varlink server: %m");
385
386 r = sd_varlink_server_add_interface(varlink_server, &vl_interface_io_systemd_AskPassword);
387 if (r < 0)
388 return log_error_errno(r, "Failed to add Varlink interface: %m");
389
390 r = sd_varlink_server_bind_method(varlink_server, "io.systemd.AskPassword.Ask", vl_method_ask);
391 if (r < 0)
392 return log_error_errno(r, "Failed to bind Varlink method: %m");
393
394 r = sd_varlink_server_loop_auto(varlink_server);
395 if (r < 0)
396 return log_error_errno(r, "Failed to run Varlink event loop: %m");
397
398 return 0;
399}
400
c7d7adf5 401static int run(int argc, char *argv[]) {
ab84f5b9 402 _cleanup_strv_free_erase_ char **l = NULL;
7dcda352 403 usec_t timeout;
e287086b 404 int r;
1b39d4b9 405
aa976d87 406 log_setup();
1b39d4b9 407
9c1fa3c2
LP
408 /* Unprivileged? Then imply ASK_PASSWORD_USER by default */
409 SET_FLAG(arg_flags, ASK_PASSWORD_USER, geteuid() != 0);
410
601185b4
ZJS
411 r = parse_argv(argc, argv);
412 if (r <= 0)
c7d7adf5 413 return r;
1b39d4b9 414
066f6bfb
LP
415 if (arg_varlink)
416 return vl_server(); /* Invocation as Varlink service */
417
6a4607a3 418 timeout = arg_timeout > 0 ? usec_add(now(CLOCK_MONOTONIC), arg_timeout) : 0;
7dcda352 419
d08fd4c3 420 AskPasswordRequest req = {
72068d9d 421 .tty_fd = -EBADF,
d08fd4c3
LP
422 .message = arg_message,
423 .icon = arg_icon,
424 .id = arg_id,
425 .keyring = arg_key_name,
426 .credential = arg_credential_name ?: "password",
c4a02a52 427 .until = timeout,
d66894a7 428 .hup_fd = -EBADF,
d08fd4c3
LP
429 };
430
c4a02a52 431 r = ask_password_auto(&req, arg_flags, &l);
c7d7adf5
YW
432 if (r < 0)
433 return log_error_errno(r, "Failed to query password: %m");
21bc923a 434
e287086b 435 STRV_FOREACH(p, l) {
b80ef40c
LP
436 if (!arg_no_output) {
437 if (arg_newline)
438 puts(*p);
439 else
440 fputs(*p, stdout);
441 }
442
443 fflush(stdout);
ec863ba6 444
e287086b
LP
445 if (!arg_multiple)
446 break;
7f4e0805 447 }
1b39d4b9 448
c7d7adf5 449 return 0;
1b39d4b9 450}
c7d7adf5
YW
451
452DEFINE_MAIN_FUNCTION(run);