]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
ec863ba6 | 2 | /*** |
96b2fb93 | 3 | Copyright © 2015 Werner Fink |
ec863ba6 LP |
4 | ***/ |
5 | ||
ec863ba6 | 6 | #include <errno.h> |
3f6fd1ba LP |
7 | #include <fcntl.h> |
8 | #include <getopt.h> | |
3f6fd1ba LP |
9 | #include <stdbool.h> |
10 | #include <stddef.h> | |
6af62124 | 11 | #include <sys/prctl.h> |
3f6fd1ba | 12 | #include <sys/signalfd.h> |
ca78ad1d ZJS |
13 | #include <sys/stat.h> |
14 | #include <sys/types.h> | |
ec863ba6 | 15 | #include <sys/un.h> |
ca78ad1d | 16 | #include <sys/wait.h> |
ec863ba6 | 17 | #include <unistd.h> |
ec863ba6 | 18 | |
b5efdb8a | 19 | #include "alloc-util.h" |
3f6fd1ba LP |
20 | #include "ask-password-api.h" |
21 | #include "conf-parser.h" | |
22 | #include "def.h" | |
a0956174 | 23 | #include "dirent-util.h" |
6af62124 | 24 | #include "exit-status.h" |
3ffd4af2 | 25 | #include "fd-util.h" |
6af62124 WF |
26 | #include "fileio.h" |
27 | #include "hashmap.h" | |
9e5fd717 | 28 | #include "inotify-util.h" |
c004493c | 29 | #include "io-util.h" |
6af62124 | 30 | #include "macro.h" |
5e332028 | 31 | #include "main-func.h" |
0a970718 | 32 | #include "memory-util.h" |
35cd0ba5 | 33 | #include "mkdir-label.h" |
9eb977db | 34 | #include "path-util.h" |
294bf0c3 | 35 | #include "pretty-print.h" |
3f6fd1ba | 36 | #include "process-util.h" |
23d5dd16 | 37 | #include "set.h" |
3f6fd1ba | 38 | #include "signal-util.h" |
e5ebf783 | 39 | #include "socket-util.h" |
07630cea | 40 | #include "string-util.h" |
21bc923a | 41 | #include "strv.h" |
288a74cc | 42 | #include "terminal-util.h" |
3f6fd1ba | 43 | #include "utmp-wtmp.h" |
ec863ba6 LP |
44 | |
45 | static enum { | |
46 | ACTION_LIST, | |
47 | ACTION_QUERY, | |
48 | ACTION_WATCH, | |
4c4a018c | 49 | ACTION_WALL, |
ec863ba6 LP |
50 | } arg_action = ACTION_QUERY; |
51 | ||
e5ebf783 | 52 | static bool arg_plymouth = false; |
0cf84693 | 53 | static bool arg_console = false; |
6af62124 | 54 | static const char *arg_device = NULL; |
e5ebf783 | 55 | |
bbada6d7 | 56 | static int send_passwords(const char *socket_name, char **passwords) { |
e693a932 | 57 | _cleanup_(erase_and_freep) char *packet = NULL; |
bbada6d7 | 58 | _cleanup_close_ int socket_fd = -1; |
f36a9d59 ZJS |
59 | union sockaddr_union sa; |
60 | socklen_t sa_len; | |
bbada6d7 | 61 | size_t packet_length = 1; |
de010b0b | 62 | char *d; |
5439206b | 63 | ssize_t n; |
f36a9d59 | 64 | int r; |
bbada6d7 JAS |
65 | |
66 | assert(socket_name); | |
67 | ||
f36a9d59 ZJS |
68 | r = sockaddr_un_set_path(&sa.un, socket_name); |
69 | if (r < 0) | |
70 | return r; | |
71 | sa_len = r; | |
15a3e96f | 72 | |
bbada6d7 JAS |
73 | STRV_FOREACH(p, passwords) |
74 | packet_length += strlen(*p) + 1; | |
75 | ||
76 | packet = new(char, packet_length); | |
77 | if (!packet) | |
78 | return -ENOMEM; | |
79 | ||
80 | packet[0] = '+'; | |
81 | ||
82 | d = packet + 1; | |
83 | STRV_FOREACH(p, passwords) | |
84 | d = stpcpy(d, *p) + 1; | |
85 | ||
86 | socket_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0); | |
e693a932 ZJS |
87 | if (socket_fd < 0) |
88 | return log_debug_errno(errno, "socket(): %m"); | |
bbada6d7 | 89 | |
f36a9d59 | 90 | n = sendto(socket_fd, packet, packet_length, MSG_NOSIGNAL, &sa.sa, sa_len); |
e693a932 ZJS |
91 | if (n < 0) |
92 | return log_debug_errno(errno, "sendto(): %m"); | |
5439206b | 93 | |
e693a932 | 94 | return (int) n; |
bbada6d7 JAS |
95 | } |
96 | ||
85c221eb FB |
97 | static bool wall_tty_match(const char *path, void *userdata) { |
98 | _cleanup_free_ char *p = NULL; | |
99 | _cleanup_close_ int fd = -1; | |
100 | struct stat st; | |
101 | ||
102 | if (!path_is_absolute(path)) | |
103 | path = strjoina("/dev/", path); | |
104 | ||
105 | if (lstat(path, &st) < 0) { | |
106 | log_debug_errno(errno, "Failed to stat %s: %m", path); | |
107 | return true; | |
108 | } | |
109 | ||
110 | if (!S_ISCHR(st.st_mode)) { | |
111 | log_debug("%s is not a character device.", path); | |
112 | return true; | |
113 | } | |
114 | ||
115 | /* We use named pipes to ensure that wall messages suggesting | |
116 | * password entry are not printed over password prompts | |
117 | * already shown. We use the fact here that opening a pipe in | |
118 | * non-blocking mode for write-only will succeed only if | |
119 | * there's some writer behind it. Using pipes has the | |
120 | * advantage that the block will automatically go away if the | |
121 | * process dies. */ | |
122 | ||
123 | if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(st.st_rdev), minor(st.st_rdev)) < 0) { | |
124 | log_oom(); | |
125 | return true; | |
126 | } | |
127 | ||
128 | fd = open(p, O_WRONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); | |
129 | if (fd < 0) { | |
130 | log_debug_errno(errno, "Failed to open the wall pipe: %m"); | |
131 | return 1; | |
132 | } | |
133 | ||
134 | /* What, we managed to open the pipe? Then this tty is filtered. */ | |
135 | return 0; | |
136 | } | |
137 | ||
87c4444a FB |
138 | static int agent_ask_password_tty( |
139 | const char *message, | |
140 | usec_t until, | |
141 | AskPasswordFlags flags, | |
142 | const char *flag_file, | |
143 | char ***ret) { | |
144 | ||
a4fd6cd3 | 145 | int tty_fd = -1, r; |
44a989e0 | 146 | const char *con = arg_device ?: "/dev/console"; |
87c4444a FB |
147 | |
148 | if (arg_console) { | |
87c4444a FB |
149 | tty_fd = acquire_terminal(con, ACQUIRE_TERMINAL_WAIT, USEC_INFINITY); |
150 | if (tty_fd < 0) | |
151 | return log_error_errno(tty_fd, "Failed to acquire %s: %m", con); | |
152 | ||
153 | r = reset_terminal_fd(tty_fd, true); | |
154 | if (r < 0) | |
155 | log_warning_errno(r, "Failed to reset terminal, ignoring: %m"); | |
156 | ||
44a989e0 | 157 | log_info("Starting password query on %s.", con); |
87c4444a FB |
158 | } |
159 | ||
160 | r = ask_password_tty(tty_fd, message, NULL, until, flags, flag_file, ret); | |
161 | ||
162 | if (arg_console) { | |
163 | tty_fd = safe_close(tty_fd); | |
164 | release_terminal(); | |
44a989e0 ZJS |
165 | |
166 | if (r >= 0) | |
167 | log_info("Password query on %s finished successfully.", con); | |
87c4444a FB |
168 | } |
169 | ||
a4fd6cd3 | 170 | return r; |
87c4444a FB |
171 | } |
172 | ||
5461cdeb | 173 | static int process_one_password_file(const char *filename) { |
bbada6d7 | 174 | _cleanup_free_ char *socket_name = NULL, *message = NULL; |
1fa94a31 | 175 | bool accept_cached = false, echo = false, silent = false; |
ec863ba6 | 176 | uint64_t not_after = 0; |
65a0ede2 | 177 | pid_t pid = 0; |
ec863ba6 | 178 | |
f975e971 | 179 | const ConfigTableItem items[] = { |
3f87eaa5 YW |
180 | { "Ask", "Socket", config_parse_string, CONFIG_PARSE_STRING_SAFE, &socket_name }, |
181 | { "Ask", "NotAfter", config_parse_uint64, 0, ¬_after }, | |
182 | { "Ask", "Message", config_parse_string, 0, &message }, | |
183 | { "Ask", "PID", config_parse_pid, 0, &pid }, | |
184 | { "Ask", "AcceptCached", config_parse_bool, 0, &accept_cached }, | |
185 | { "Ask", "Echo", config_parse_bool, 0, &echo }, | |
186 | { "Ask", "Silent", config_parse_bool, 0, &silent }, | |
1d749d04 | 187 | {} |
ec863ba6 LP |
188 | }; |
189 | ||
ec863ba6 | 190 | int r; |
ec863ba6 LP |
191 | |
192 | assert(filename); | |
193 | ||
36f822c4 ZJS |
194 | r = config_parse(NULL, filename, NULL, |
195 | NULL, | |
196 | config_item_table_lookup, items, | |
4f9ff96a LP |
197 | CONFIG_PARSE_RELAXED|CONFIG_PARSE_WARN, |
198 | NULL, | |
199 | NULL); | |
36f822c4 ZJS |
200 | if (r < 0) |
201 | return r; | |
ec863ba6 | 202 | |
baaa35ad ZJS |
203 | if (!socket_name) |
204 | return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), | |
205 | "Invalid password file %s", filename); | |
ec863ba6 | 206 | |
e46eab86 ZJS |
207 | if (not_after > 0 && now(CLOCK_MONOTONIC) > not_after) |
208 | return 0; | |
ec863ba6 | 209 | |
e46eab86 ZJS |
210 | if (pid > 0 && !pid_is_alive(pid)) |
211 | return 0; | |
ded80335 | 212 | |
87c4444a FB |
213 | switch (arg_action) { |
214 | case ACTION_LIST: | |
65a0ede2 | 215 | printf("'%s' (PID " PID_FMT ")\n", strna(message), pid); |
87c4444a | 216 | return 0; |
e46eab86 | 217 | |
87c4444a FB |
218 | case ACTION_WALL: { |
219 | _cleanup_free_ char *wall = NULL; | |
ec863ba6 | 220 | |
87c4444a | 221 | if (asprintf(&wall, |
65a0ede2 | 222 | "Password entry required for \'%s\' (PID " PID_FMT ").\r\n" |
87c4444a | 223 | "Please enter password with the systemd-tty-ask-password-agent tool.", |
66bff73b | 224 | strna(message), |
87c4444a FB |
225 | pid) < 0) |
226 | return log_oom(); | |
e46eab86 | 227 | |
87c4444a FB |
228 | (void) utmp_wall(wall, NULL, NULL, wall_tty_match, NULL); |
229 | return 0; | |
230 | } | |
231 | case ACTION_QUERY: | |
232 | case ACTION_WATCH: { | |
bbada6d7 | 233 | _cleanup_strv_free_erase_ char **passwords = NULL; |
87c4444a | 234 | AskPasswordFlags flags = 0; |
ec863ba6 LP |
235 | |
236 | if (access(socket_name, W_OK) < 0) { | |
ec863ba6 | 237 | if (arg_action == ACTION_QUERY) |
65a0ede2 | 238 | log_info("Not querying '%s' (PID " PID_FMT "), lacking privileges.", strna(message), pid); |
ec863ba6 | 239 | |
e46eab86 | 240 | return 0; |
ec863ba6 LP |
241 | } |
242 | ||
87c4444a FB |
243 | SET_FLAG(flags, ASK_PASSWORD_ACCEPT_CACHED, accept_cached); |
244 | SET_FLAG(flags, ASK_PASSWORD_CONSOLE_COLOR, arg_console); | |
245 | SET_FLAG(flags, ASK_PASSWORD_ECHO, echo); | |
1fa94a31 | 246 | SET_FLAG(flags, ASK_PASSWORD_SILENT, silent); |
3d18b167 | 247 | |
87c4444a FB |
248 | if (arg_plymouth) |
249 | r = ask_password_plymouth(message, not_after, flags, filename, &passwords); | |
250 | else | |
251 | r = agent_ask_password_tty(message, not_after, flags, filename, &passwords); | |
87c4444a FB |
252 | if (r < 0) { |
253 | /* If the query went away, that's OK */ | |
254 | if (IN_SET(r, -ETIME, -ENOENT)) | |
255 | return 0; | |
e5ebf783 | 256 | |
bbada6d7 | 257 | return log_error_errno(r, "Failed to query password: %m"); |
87c4444a | 258 | } |
ec863ba6 | 259 | |
d325f244 FB |
260 | if (strv_isempty(passwords)) |
261 | return -ECANCELED; | |
262 | ||
bbada6d7 | 263 | r = send_passwords(socket_name, passwords); |
00843602 | 264 | if (r < 0) |
bbada6d7 | 265 | return log_error_errno(r, "Failed to send: %m"); |
87c4444a | 266 | break; |
66bff73b | 267 | }} |
ec863ba6 | 268 | |
e46eab86 | 269 | return 0; |
ec863ba6 LP |
270 | } |
271 | ||
0cf84693 | 272 | static int wall_tty_block(void) { |
1d749d04 | 273 | _cleanup_free_ char *p = NULL; |
fc116c6a | 274 | dev_t devnr; |
00843602 | 275 | int fd, r; |
7af53310 | 276 | |
4d6d6518 | 277 | r = get_ctty_devnr(0, &devnr); |
e287086b LP |
278 | if (r == -ENXIO) /* We have no controlling tty */ |
279 | return -ENOTTY; | |
4d6d6518 | 280 | if (r < 0) |
00843602 | 281 | return log_error_errno(r, "Failed to get controlling TTY: %m"); |
7af53310 | 282 | |
2b583ce6 | 283 | if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(devnr), minor(devnr)) < 0) |
00843602 | 284 | return log_oom(); |
7af53310 | 285 | |
e3e2cf07 LP |
286 | (void) mkdir_parents_label(p, 0700); |
287 | (void) mkfifo(p, 0600); | |
7af53310 LP |
288 | |
289 | fd = open(p, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); | |
7af53310 | 290 | if (fd < 0) |
2ee4e222 | 291 | return log_debug_errno(errno, "Failed to open %s: %m", p); |
7af53310 LP |
292 | |
293 | return fd; | |
294 | } | |
295 | ||
5461cdeb | 296 | static int process_password_files(void) { |
c2b2df60 | 297 | _cleanup_closedir_ DIR *d = NULL; |
ec863ba6 LP |
298 | int r = 0; |
299 | ||
1d749d04 ZJS |
300 | d = opendir("/run/systemd/ask-password"); |
301 | if (!d) { | |
ec863ba6 LP |
302 | if (errno == ENOENT) |
303 | return 0; | |
304 | ||
ad71eee5 | 305 | return log_error_errno(errno, "Failed to open /run/systemd/ask-password: %m"); |
ec863ba6 LP |
306 | } |
307 | ||
1503bcb1 | 308 | FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read directory: %m")) { |
85c221eb | 309 | _cleanup_free_ char *p = NULL; |
ec863ba6 LP |
310 | int q; |
311 | ||
85c221eb | 312 | /* We only support /run on tmpfs, hence we can rely on |
1a6f4df6 LP |
313 | * d_type to be reliable */ |
314 | ||
ec863ba6 LP |
315 | if (de->d_type != DT_REG) |
316 | continue; | |
317 | ||
ec863ba6 LP |
318 | if (!startswith(de->d_name, "ask.")) |
319 | continue; | |
320 | ||
b910cc72 | 321 | p = path_join("/run/systemd/ask-password", de->d_name); |
1d749d04 ZJS |
322 | if (!p) |
323 | return log_oom(); | |
ec863ba6 | 324 | |
5461cdeb | 325 | q = process_one_password_file(p); |
1d749d04 | 326 | if (q < 0 && r == 0) |
ec863ba6 | 327 | r = q; |
ec863ba6 LP |
328 | } |
329 | ||
ec863ba6 LP |
330 | return r; |
331 | } | |
332 | ||
13227570 | 333 | static int process_and_watch_password_files(bool watch) { |
b9ba604e | 334 | enum { |
b9ba604e | 335 | FD_SIGNAL, |
13227570 | 336 | FD_INOTIFY, |
b9ba604e LP |
337 | _FD_MAX |
338 | }; | |
339 | ||
d7ac0952 FS |
340 | _unused_ _cleanup_close_ int tty_block_fd = -1; |
341 | _cleanup_close_ int notify = -1, signal_fd = -1; | |
7d895205 | 342 | struct pollfd pollfd[_FD_MAX]; |
b9ba604e | 343 | sigset_t mask; |
ec863ba6 LP |
344 | int r; |
345 | ||
0cf84693 | 346 | tty_block_fd = wall_tty_block(); |
7af53310 | 347 | |
00843602 | 348 | (void) mkdir_p_label("/run/systemd/ask-password", 0755); |
ec863ba6 | 349 | |
72c0a2c2 | 350 | assert_se(sigemptyset(&mask) >= 0); |
998c6da8 | 351 | assert_se(sigset_add_many(&mask, SIGTERM, -1) >= 0); |
72c0a2c2 | 352 | assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) >= 0); |
b9ba604e | 353 | |
13227570 FB |
354 | if (watch) { |
355 | signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC); | |
356 | if (signal_fd < 0) | |
357 | return log_error_errno(errno, "Failed to allocate signal file descriptor: %m"); | |
b9ba604e | 358 | |
7d895205 | 359 | pollfd[FD_SIGNAL] = (struct pollfd) { .fd = signal_fd, .events = POLLIN }; |
13227570 FB |
360 | |
361 | notify = inotify_init1(IN_CLOEXEC); | |
362 | if (notify < 0) | |
363 | return log_error_errno(errno, "Failed to allocate directory watch: %m"); | |
364 | ||
365 | r = inotify_add_watch_and_warn(notify, "/run/systemd/ask-password", IN_CLOSE_WRITE|IN_MOVED_TO); | |
366 | if (r < 0) | |
367 | return r; | |
368 | ||
7d895205 | 369 | pollfd[FD_INOTIFY] = (struct pollfd) { .fd = notify, .events = POLLIN }; |
13227570 | 370 | } |
ec863ba6 LP |
371 | |
372 | for (;;) { | |
d9e2af0a | 373 | usec_t timeout = USEC_INFINITY; |
d325f244 | 374 | |
5461cdeb | 375 | r = process_password_files(); |
d325f244 FB |
376 | if (r < 0) { |
377 | if (r == -ECANCELED) | |
378 | /* Disable poll() timeout since at least one password has | |
379 | * been skipped and therefore one file remains and is | |
380 | * unlikely to trigger any events. */ | |
381 | timeout = 0; | |
382 | else | |
825721eb FB |
383 | /* FIXME: we should do something here since otherwise the service |
384 | * requesting the password won't notice the error and will wait | |
385 | * indefinitely. */ | |
d325f244 FB |
386 | log_error_errno(r, "Failed to process password: %m"); |
387 | } | |
ec863ba6 | 388 | |
13227570 FB |
389 | if (!watch) |
390 | break; | |
391 | ||
d9e2af0a YW |
392 | r = ppoll_usec(pollfd, _FD_MAX, timeout); |
393 | if (r == -EINTR) | |
394 | continue; | |
395 | if (r < 0) | |
396 | return r; | |
dad28bff | 397 | |
b9ba604e | 398 | if (pollfd[FD_INOTIFY].revents != 0) |
00843602 | 399 | (void) flush_fd(notify); |
b9ba604e LP |
400 | |
401 | if (pollfd[FD_SIGNAL].revents != 0) | |
402 | break; | |
ec863ba6 LP |
403 | } |
404 | ||
1d749d04 | 405 | return 0; |
ec863ba6 LP |
406 | } |
407 | ||
37ec0fdd LP |
408 | static int help(void) { |
409 | _cleanup_free_ char *link = NULL; | |
410 | int r; | |
411 | ||
412 | r = terminal_urlify_man("systemd-tty-ask-password-agent", "1", &link); | |
413 | if (r < 0) | |
414 | return log_oom(); | |
415 | ||
ec863ba6 | 416 | printf("%s [OPTIONS...]\n\n" |
0727077b | 417 | "%sProcess system password requests.%s\n\n" |
af88c399 ZJS |
418 | " -h --help Show this help\n" |
419 | " --version Show package version\n" | |
420 | " --list Show pending password requests\n" | |
421 | " --query Process pending password requests\n" | |
422 | " --watch Continuously process password requests\n" | |
423 | " --wall Continuously forward password requests to wall\n" | |
424 | " --plymouth Ask question with Plymouth instead of on TTY\n" | |
425 | " --console[=DEVICE] Ask question on /dev/console (or DEVICE if specified)\n" | |
426 | " instead of the current TTY\n" | |
bc556335 DDM |
427 | "\nSee the %s for details.\n", |
428 | program_invocation_short_name, | |
0727077b ZJS |
429 | ansi_highlight(), |
430 | ansi_normal(), | |
bc556335 | 431 | link); |
37ec0fdd LP |
432 | |
433 | return 0; | |
ec863ba6 LP |
434 | } |
435 | ||
436 | static int parse_argv(int argc, char *argv[]) { | |
437 | ||
438 | enum { | |
439 | ARG_LIST = 0x100, | |
440 | ARG_QUERY, | |
441 | ARG_WATCH, | |
442 | ARG_WALL, | |
0cf84693 | 443 | ARG_PLYMOUTH, |
c52f663b LP |
444 | ARG_CONSOLE, |
445 | ARG_VERSION | |
ec863ba6 LP |
446 | }; |
447 | ||
448 | static const struct option options[] = { | |
6af62124 WF |
449 | { "help", no_argument, NULL, 'h' }, |
450 | { "version", no_argument, NULL, ARG_VERSION }, | |
451 | { "list", no_argument, NULL, ARG_LIST }, | |
452 | { "query", no_argument, NULL, ARG_QUERY }, | |
453 | { "watch", no_argument, NULL, ARG_WATCH }, | |
454 | { "wall", no_argument, NULL, ARG_WALL }, | |
455 | { "plymouth", no_argument, NULL, ARG_PLYMOUTH }, | |
456 | { "console", optional_argument, NULL, ARG_CONSOLE }, | |
eb9da376 | 457 | {} |
ec863ba6 LP |
458 | }; |
459 | ||
460 | int c; | |
461 | ||
462 | assert(argc >= 0); | |
463 | assert(argv); | |
464 | ||
601185b4 | 465 | while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) |
ec863ba6 LP |
466 | |
467 | switch (c) { | |
468 | ||
469 | case 'h': | |
37ec0fdd | 470 | return help(); |
ec863ba6 | 471 | |
c52f663b | 472 | case ARG_VERSION: |
3f6fd1ba | 473 | return version(); |
c52f663b | 474 | |
ec863ba6 LP |
475 | case ARG_LIST: |
476 | arg_action = ACTION_LIST; | |
477 | break; | |
478 | ||
479 | case ARG_QUERY: | |
480 | arg_action = ACTION_QUERY; | |
481 | break; | |
482 | ||
483 | case ARG_WATCH: | |
484 | arg_action = ACTION_WATCH; | |
485 | break; | |
486 | ||
487 | case ARG_WALL: | |
488 | arg_action = ACTION_WALL; | |
489 | break; | |
490 | ||
e5ebf783 LP |
491 | case ARG_PLYMOUTH: |
492 | arg_plymouth = true; | |
493 | break; | |
494 | ||
0cf84693 LP |
495 | case ARG_CONSOLE: |
496 | arg_console = true; | |
6af62124 WF |
497 | if (optarg) { |
498 | ||
baaa35ad ZJS |
499 | if (isempty(optarg)) |
500 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), | |
501 | "Empty console device path is not allowed."); | |
6af62124 WF |
502 | |
503 | arg_device = optarg; | |
504 | } | |
0cf84693 LP |
505 | break; |
506 | ||
ec863ba6 LP |
507 | case '?': |
508 | return -EINVAL; | |
509 | ||
510 | default: | |
04499a70 | 511 | assert_not_reached(); |
ec863ba6 | 512 | } |
ec863ba6 | 513 | |
baaa35ad ZJS |
514 | if (optind != argc) |
515 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), | |
516 | "%s takes no arguments.", program_invocation_short_name); | |
ec863ba6 | 517 | |
6af62124 WF |
518 | if (arg_plymouth || arg_console) { |
519 | ||
baaa35ad ZJS |
520 | if (!IN_SET(arg_action, ACTION_QUERY, ACTION_WATCH)) |
521 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), | |
522 | "Options --query and --watch conflict."); | |
6af62124 | 523 | |
baaa35ad ZJS |
524 | if (arg_plymouth && arg_console) |
525 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), | |
526 | "Options --plymouth and --console conflict."); | |
6af62124 WF |
527 | } |
528 | ||
ec863ba6 LP |
529 | return 1; |
530 | } | |
531 | ||
6af62124 | 532 | /* |
189b0377 | 533 | * To be able to ask on all terminal devices of /dev/console the devices are collected. If more than one |
387f6955 | 534 | * device is found, then on each of the terminals an inquiring task is forked. Every task has its own session |
189b0377 LP |
535 | * and its own controlling terminal. If one of the tasks does handle a password, the remaining tasks will be |
536 | * terminated. | |
6af62124 | 537 | */ |
6e79d2b5 | 538 | static int ask_on_this_console(const char *tty, pid_t *ret_pid, char **arguments) { |
d8502964 | 539 | static const struct sigaction sigchld = { |
6af62124 WF |
540 | .sa_handler = nop_signal_handler, |
541 | .sa_flags = SA_NOCLDSTOP | SA_RESTART, | |
542 | }; | |
d8502964 LP |
543 | static const struct sigaction sighup = { |
544 | .sa_handler = SIG_DFL, | |
545 | .sa_flags = SA_RESTART, | |
546 | }; | |
4c253ed1 | 547 | int r; |
6af62124 | 548 | |
d8502964 LP |
549 | assert_se(sigaction(SIGCHLD, &sigchld, NULL) >= 0); |
550 | assert_se(sigaction(SIGHUP, &sighup, NULL) >= 0); | |
6af62124 WF |
551 | assert_se(sigprocmask_many(SIG_UNBLOCK, NULL, SIGHUP, SIGCHLD, -1) >= 0); |
552 | ||
4bec7f09 | 553 | r = safe_fork("(sd-passwd)", FORK_RESET_SIGNALS|FORK_LOG, ret_pid); |
4c253ed1 | 554 | if (r < 0) |
b6e1fff1 | 555 | return r; |
4c253ed1 | 556 | if (r == 0) { |
6af62124 WF |
557 | assert_se(prctl(PR_SET_PDEATHSIG, SIGHUP) >= 0); |
558 | ||
ed179fd7 LP |
559 | STRV_FOREACH(i, arguments) { |
560 | char *k; | |
561 | ||
562 | if (!streq(*i, "--console")) | |
563 | continue; | |
564 | ||
565 | k = strjoin("--console=", tty); | |
566 | if (!k) { | |
567 | log_oom(); | |
568 | _exit(EXIT_FAILURE); | |
6af62124 | 569 | } |
6af62124 | 570 | |
ed179fd7 LP |
571 | free_and_replace(*i, k); |
572 | } | |
6af62124 | 573 | |
65e5d693 | 574 | execv(SYSTEMD_TTY_ASK_PASSWORD_AGENT_BINARY_PATH, arguments); |
6af62124 WF |
575 | _exit(EXIT_FAILURE); |
576 | } | |
4c253ed1 | 577 | |
6af62124 WF |
578 | return 0; |
579 | } | |
580 | ||
581 | static void terminate_agents(Set *pids) { | |
6af62124 | 582 | sigset_t set; |
6af62124 WF |
583 | void *p; |
584 | int r, signum; | |
585 | ||
586 | /* | |
587 | * Request termination of the remaining processes as those | |
588 | * are not required anymore. | |
589 | */ | |
90e74a66 | 590 | SET_FOREACH(p, pids) |
6af62124 WF |
591 | (void) kill(PTR_TO_PID(p), SIGTERM); |
592 | ||
593 | /* | |
594 | * Collect the processes which have go away. | |
595 | */ | |
596 | assert_se(sigemptyset(&set) >= 0); | |
597 | assert_se(sigaddset(&set, SIGCHLD) >= 0); | |
6af62124 WF |
598 | |
599 | while (!set_isempty(pids)) { | |
52bb308c | 600 | siginfo_t status = {}; |
6af62124 | 601 | |
6af62124 WF |
602 | r = waitid(P_ALL, 0, &status, WEXITED|WNOHANG); |
603 | if (r < 0 && errno == EINTR) | |
604 | continue; | |
605 | ||
606 | if (r == 0 && status.si_pid > 0) { | |
607 | set_remove(pids, PID_TO_PTR(status.si_pid)); | |
608 | continue; | |
609 | } | |
610 | ||
52bb308c | 611 | signum = sigtimedwait(&set, NULL, TIMESPEC_STORE(50 * USEC_PER_MSEC)); |
6af62124 WF |
612 | if (signum < 0) { |
613 | if (errno != EAGAIN) | |
614 | log_error_errno(errno, "sigtimedwait() failed: %m"); | |
615 | break; | |
616 | } | |
617 | assert(signum == SIGCHLD); | |
618 | } | |
619 | ||
620 | /* | |
621 | * Kill hanging processes. | |
622 | */ | |
90e74a66 | 623 | SET_FOREACH(p, pids) { |
6af62124 WF |
624 | log_warning("Failed to terminate child %d, killing it", PTR_TO_PID(p)); |
625 | (void) kill(PTR_TO_PID(p), SIGKILL); | |
626 | } | |
627 | } | |
628 | ||
ed179fd7 | 629 | static int ask_on_consoles(char *argv[]) { |
6af62124 | 630 | _cleanup_set_free_ Set *pids = NULL; |
6e79d2b5 | 631 | _cleanup_strv_free_ char **consoles = NULL, **arguments = NULL; |
6af62124 | 632 | siginfo_t status = {}; |
6af62124 WF |
633 | pid_t pid; |
634 | int r; | |
635 | ||
636 | r = get_kernel_consoles(&consoles); | |
637 | if (r < 0) | |
638 | return log_error_errno(r, "Failed to determine devices of /dev/console: %m"); | |
639 | ||
640 | pids = set_new(NULL); | |
641 | if (!pids) | |
642 | return log_oom(); | |
643 | ||
6e79d2b5 YW |
644 | arguments = strv_copy(argv); |
645 | if (!arguments) | |
646 | return log_oom(); | |
647 | ||
6af62124 WF |
648 | /* Start an agent on each console. */ |
649 | STRV_FOREACH(tty, consoles) { | |
6e79d2b5 | 650 | r = ask_on_this_console(*tty, &pid, arguments); |
6af62124 WF |
651 | if (r < 0) |
652 | return r; | |
653 | ||
654 | if (set_put(pids, PID_TO_PTR(pid)) < 0) | |
655 | return log_oom(); | |
656 | } | |
657 | ||
658 | /* Wait for an agent to exit. */ | |
659 | for (;;) { | |
660 | zero(status); | |
661 | ||
662 | if (waitid(P_ALL, 0, &status, WEXITED) < 0) { | |
663 | if (errno == EINTR) | |
664 | continue; | |
665 | ||
666 | return log_error_errno(errno, "waitid() failed: %m"); | |
667 | } | |
668 | ||
669 | set_remove(pids, PID_TO_PTR(status.si_pid)); | |
670 | break; | |
671 | } | |
672 | ||
1f0958f6 | 673 | if (!is_clean_exit(status.si_code, status.si_status, EXIT_CLEAN_DAEMON, NULL)) |
6af62124 WF |
674 | log_error("Password agent failed with: %d", status.si_status); |
675 | ||
676 | terminate_agents(pids); | |
677 | return 0; | |
678 | } | |
679 | ||
0420d20d | 680 | static int run(int argc, char *argv[]) { |
ec863ba6 LP |
681 | int r; |
682 | ||
d2acb93d | 683 | log_setup(); |
ec863ba6 | 684 | |
4c12626c LP |
685 | umask(0022); |
686 | ||
1d749d04 ZJS |
687 | r = parse_argv(argc, argv); |
688 | if (r <= 0) | |
0420d20d | 689 | return r; |
ec863ba6 | 690 | |
6af62124 WF |
691 | if (arg_console && !arg_device) |
692 | /* | |
0420d20d | 693 | * Spawn a separate process for each console device. |
6af62124 | 694 | */ |
ed179fd7 | 695 | return ask_on_consoles(argv); |
0cf84693 | 696 | |
0420d20d ZJS |
697 | if (arg_device) { |
698 | /* | |
699 | * Later on, a controlling terminal will be acquired, | |
700 | * therefore the current process has to become a session | |
701 | * leader and should not have a controlling terminal already. | |
702 | */ | |
703 | (void) setsid(); | |
704 | (void) release_terminal(); | |
6af62124 | 705 | } |
96707269 | 706 | |
4c4a018c | 707 | return process_and_watch_password_files(!IN_SET(arg_action, ACTION_QUERY, ACTION_LIST)); |
ec863ba6 | 708 | } |
0420d20d ZJS |
709 | |
710 | DEFINE_MAIN_FUNCTION(run); |