]>
Commit | Line | Data |
---|---|---|
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); |