]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/ask-password/ask-password.c
c56dfc23468c0ff043e7cba76b367c0ffee6e993
[thirdparty/systemd.git] / src / ask-password / ask-password.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <getopt.h>
4 #include <unistd.h>
5
6 #include "sd-varlink.h"
7
8 #include "alloc-util.h"
9 #include "ask-password-api.h"
10 #include "build.h"
11 #include "bus-polkit.h"
12 #include "constants.h"
13 #include "hashmap.h"
14 #include "json-util.h"
15 #include "log.h"
16 #include "main-func.h"
17 #include "parse-argument.h"
18 #include "pretty-print.h"
19 #include "string-table.h"
20 #include "string-util.h"
21 #include "strv.h"
22 #include "time-util.h"
23 #include "varlink-io.systemd.AskPassword.h"
24 #include "varlink-util.h"
25
26 static const char *arg_icon = NULL;
27 static const char *arg_id = NULL; /* identifier for 'ask-password' protocol */
28 static const char *arg_key_name = NULL; /* name in kernel keyring */
29 static const char *arg_credential_name = NULL; /* name in $CREDENTIALS_DIRECTORY directory */
30 static char *arg_message = NULL;
31 static usec_t arg_timeout = DEFAULT_TIMEOUT_USEC;
32 static bool arg_multiple = false;
33 static bool arg_no_output = false;
34 static AskPasswordFlags arg_flags = ASK_PASSWORD_PUSH_CACHE;
35 static bool arg_newline = true;
36 static bool arg_varlink = false;
37
38 STATIC_DESTRUCTOR_REGISTER(arg_message, freep);
39
40 static 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
48 printf("%1$s [OPTIONS...] MESSAGE\n\n"
49 "%3$sQuery the user for a passphrase, via the TTY or a UI agent.%4$s\n\n"
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"
54 " --credential=NAME\n"
55 " Credential name for ImportCredential=, LoadCredential= or\n"
56 " SetCredential= credentials\n"
57 " --timeout=SEC Timeout in seconds\n"
58 " --echo=yes|no|masked\n"
59 " Control whether to show password while typing (echo)\n"
60 " -e --echo Equivalent to --echo=yes\n"
61 " --emoji=yes|no|auto\n"
62 " Show a lock and key emoji\n"
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"
66 " --no-output Do not print password to standard output\n"
67 " -n Do not suffix password written to standard output with\n"
68 " newline\n"
69 " --user Ask only our own user's agents\n"
70 " --system Ask agents of the system and of all users\n"
71 "\nSee the %2$s for details.\n",
72 program_invocation_short_name,
73 link,
74 ansi_highlight(),
75 ansi_normal());
76
77 return 0;
78 }
79
80 static int parse_argv(int argc, char *argv[]) {
81
82 enum {
83 ARG_ICON = 0x100,
84 ARG_TIMEOUT,
85 ARG_EMOJI,
86 ARG_NO_TTY,
87 ARG_ACCEPT_CACHED,
88 ARG_MULTIPLE,
89 ARG_ID,
90 ARG_KEYNAME,
91 ARG_NO_OUTPUT,
92 ARG_VERSION,
93 ARG_CREDENTIAL,
94 ARG_USER,
95 ARG_SYSTEM,
96 };
97
98 static const struct option options[] = {
99 { "help", no_argument, NULL, 'h' },
100 { "version", no_argument, NULL, ARG_VERSION },
101 { "icon", required_argument, NULL, ARG_ICON },
102 { "timeout", required_argument, NULL, ARG_TIMEOUT },
103 { "echo", optional_argument, NULL, 'e' },
104 { "emoji", required_argument, NULL, ARG_EMOJI },
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 },
108 { "id", required_argument, NULL, ARG_ID },
109 { "keyname", required_argument, NULL, ARG_KEYNAME },
110 { "no-output", no_argument, NULL, ARG_NO_OUTPUT },
111 { "credential", required_argument, NULL, ARG_CREDENTIAL },
112 { "user", no_argument, NULL, ARG_USER },
113 { "system", no_argument, NULL, ARG_SYSTEM },
114 {}
115 };
116
117 const char *emoji = NULL;
118 int c, r;
119
120 assert(argc >= 0);
121 assert(argv);
122
123 /* Note the asymmetry: the long option --echo= allows an optional argument, the short option does
124 * not. */
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;
129 while ((c = getopt_long(argc, argv, "+hen", options, NULL)) >= 0)
130
131 switch (c) {
132
133 case 'h':
134 return help();
135
136 case ARG_VERSION:
137 return version();
138
139 case ARG_ICON:
140 arg_icon = optarg;
141 break;
142
143 case ARG_TIMEOUT:
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
148 break;
149
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 {
159 r = parse_boolean_argument("--echo=", optarg, NULL);
160 if (r < 0)
161 return r;
162
163 SET_FLAG(arg_flags, ASK_PASSWORD_ECHO, r);
164 SET_FLAG(arg_flags, ASK_PASSWORD_SILENT, !r);
165 }
166 break;
167
168 case ARG_EMOJI:
169 emoji = optarg;
170 break;
171
172 case ARG_NO_TTY:
173 arg_flags |= ASK_PASSWORD_NO_TTY;
174 break;
175
176 case ARG_ACCEPT_CACHED:
177 arg_flags |= ASK_PASSWORD_ACCEPT_CACHED;
178 break;
179
180 case ARG_MULTIPLE:
181 arg_multiple = true;
182 break;
183
184 case ARG_ID:
185 arg_id = optarg;
186 break;
187
188 case ARG_KEYNAME:
189 arg_key_name = optarg;
190 break;
191
192 case ARG_NO_OUTPUT:
193 arg_no_output = true;
194 break;
195
196 case ARG_CREDENTIAL:
197 arg_credential_name = optarg;
198 break;
199
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
208 case 'n':
209 arg_newline = false;
210 break;
211
212 case '?':
213 return -EINVAL;
214
215 default:
216 assert_not_reached();
217 }
218
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 {
222 r = parse_boolean_argument("--emoji=", emoji, NULL);
223 if (r < 0)
224 return r;
225
226 SET_FLAG(arg_flags, ASK_PASSWORD_HIDE_EMOJI, !r);
227 }
228
229 if (argc > optind) {
230 arg_message = strv_join(argv + optind, " ");
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:");
239 if (!arg_message)
240 return log_oom();
241 }
242
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
249 return 1;
250 }
251
252 typedef enum EchoMode {
253 ECHO_OFF,
254 ECHO_ON,
255 ECHO_MASKED,
256 _ECHO_MODE_MAX,
257 _ECHO_MODE_INVALID = -EINVAL,
258 } EchoMode;
259
260 static const char* echo_mode_table[_ECHO_MODE_MAX] = {
261 [ECHO_OFF] = "off",
262 [ECHO_ON] = "on",
263 [ECHO_MASKED] = "masked",
264 };
265
266 DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(echo_mode, EchoMode, ECHO_ON);
267
268 static JSON_DISPATCH_ENUM_DEFINE(dispatch_echo_mode, EchoMode, echo_mode_from_string);
269
270 typedef 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
282 static 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
377 static int vl_server(void) {
378 _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *varlink_server = NULL;
379 _cleanup_hashmap_free_ Hashmap *polkit_registry = NULL;
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
401 static int run(int argc, char *argv[]) {
402 _cleanup_strv_free_erase_ char **l = NULL;
403 usec_t timeout;
404 int r;
405
406 log_setup();
407
408 /* Unprivileged? Then imply ASK_PASSWORD_USER by default */
409 SET_FLAG(arg_flags, ASK_PASSWORD_USER, geteuid() != 0);
410
411 r = parse_argv(argc, argv);
412 if (r <= 0)
413 return r;
414
415 if (arg_varlink)
416 return vl_server(); /* Invocation as Varlink service */
417
418 timeout = arg_timeout > 0 ? usec_add(now(CLOCK_MONOTONIC), arg_timeout) : 0;
419
420 AskPasswordRequest req = {
421 .tty_fd = -EBADF,
422 .message = arg_message,
423 .icon = arg_icon,
424 .id = arg_id,
425 .keyring = arg_key_name,
426 .credential = arg_credential_name ?: "password",
427 .until = timeout,
428 .hup_fd = -EBADF,
429 };
430
431 r = ask_password_auto(&req, arg_flags, &l);
432 if (r < 0)
433 return log_error_errno(r, "Failed to query password: %m");
434
435 STRV_FOREACH(p, l) {
436 if (!arg_no_output) {
437 if (arg_newline)
438 puts(*p);
439 else
440 fputs(*p, stdout);
441 }
442
443 fflush(stdout);
444
445 if (!arg_multiple)
446 break;
447 }
448
449 return 0;
450 }
451
452 DEFINE_MAIN_FUNCTION(run);