]>
Commit | Line | Data |
---|---|---|
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 | |
26 | static const char *arg_icon = NULL; | |
8806bb4b LP |
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 */ | |
e287086b | 30 | static char *arg_message = NULL; |
7f434cf4 | 31 | static usec_t arg_timeout = DEFAULT_TIMEOUT_USEC; |
21bc923a | 32 | static bool arg_multiple = false; |
a5a4e365 | 33 | static bool arg_no_output = false; |
e287086b | 34 | static AskPasswordFlags arg_flags = ASK_PASSWORD_PUSH_CACHE; |
b80ef40c | 35 | static bool arg_newline = true; |
066f6bfb | 36 | static bool arg_varlink = false; |
490aed58 | 37 | |
c7d7adf5 YW |
38 | STATIC_DESTRUCTOR_REGISTER(arg_message, freep); |
39 | ||
37ec0fdd LP |
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 | ||
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 | ||
80 | static 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 |
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; | |
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 | 401 | static 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 | |
452 | DEFINE_MAIN_FUNCTION(run); |