]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/tty-ask-password-agent/tty-ask-password-agent.c
shared/utmp-wtmp: add parameter for origin tty and callback userdata
[thirdparty/systemd.git] / src / tty-ask-password-agent / tty-ask-password-agent.c
CommitLineData
ec863ba6
LP
1/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3/***
4 This file is part of systemd.
5
6 Copyright 2010 Lennart Poettering
7
8 systemd is free software; you can redistribute it and/or modify it
5430f7f2
LP
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
ec863ba6
LP
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
5430f7f2 16 Lesser General Public License for more details.
ec863ba6 17
5430f7f2 18 You should have received a copy of the GNU Lesser General Public License
ec863ba6
LP
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20***/
21
22#include <stdbool.h>
23#include <errno.h>
24#include <string.h>
25#include <sys/socket.h>
26#include <sys/un.h>
27#include <stddef.h>
0a6f50c0 28#include <poll.h>
ec863ba6
LP
29#include <sys/inotify.h>
30#include <unistd.h>
31#include <getopt.h>
b9ba604e 32#include <sys/signalfd.h>
7af53310 33#include <fcntl.h>
ec863ba6
LP
34
35#include "util.h"
49e942b2 36#include "mkdir.h"
9eb977db 37#include "path-util.h"
ec863ba6
LP
38#include "conf-parser.h"
39#include "utmp-wtmp.h"
e5ebf783 40#include "socket-util.h"
7f4e0805 41#include "ask-password-api.h"
21bc923a 42#include "strv.h"
c52f663b 43#include "build.h"
1d749d04 44#include "def.h"
0b452006 45#include "process-util.h"
288a74cc 46#include "terminal-util.h"
ec863ba6
LP
47
48static enum {
49 ACTION_LIST,
50 ACTION_QUERY,
51 ACTION_WATCH,
52 ACTION_WALL
53} arg_action = ACTION_QUERY;
54
e5ebf783 55static bool arg_plymouth = false;
0cf84693 56static bool arg_console = false;
e5ebf783 57
21bc923a
LP
58static int ask_password_plymouth(
59 const char *message,
60 usec_t until,
61 const char *flag_file,
62 bool accept_cached,
63 char ***_passphrases) {
64
1d749d04
ZJS
65 _cleanup_close_ int fd = -1, notify = -1;
66 union sockaddr_union sa = PLYMOUTH_SOCKET;
67 _cleanup_free_ char *packet = NULL;
e5ebf783
LP
68 ssize_t k;
69 int r, n;
b92bea5d 70 struct pollfd pollfd[2] = {};
e5ebf783
LP
71 char buffer[LINE_MAX];
72 size_t p = 0;
73 enum {
74 POLL_SOCKET,
75 POLL_INOTIFY
76 };
77
21bc923a
LP
78 assert(_passphrases);
79
e5ebf783 80 if (flag_file) {
1d749d04
ZJS
81 notify = inotify_init1(IN_CLOEXEC|IN_NONBLOCK);
82 if (notify < 0)
83 return -errno;
e5ebf783 84
1d749d04
ZJS
85 r = inotify_add_watch(notify, flag_file, IN_ATTRIB); /* for the link count */
86 if (r < 0)
87 return -errno;
e5ebf783
LP
88 }
89
1d749d04
ZJS
90 fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
91 if (fd < 0)
92 return -errno;
e5ebf783 93
1d749d04 94 r = connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + 1 + strlen(sa.un.sun_path+1));
4a62c710
MS
95 if (r < 0)
96 return log_error_errno(errno, "Failed to connect to Plymouth: %m");
e5ebf783 97
21bc923a
LP
98 if (accept_cached) {
99 packet = strdup("c");
100 n = 1;
7de80bfe
KZ
101 } else if (asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1),
102 message, &n) < 0)
103 packet = NULL;
21bc923a 104
1d749d04
ZJS
105 if (!packet)
106 return log_oom();
e5ebf783 107
553acb7b
ZJS
108 r = loop_write(fd, packet, n + 1, true);
109 if (r < 0)
110 return r;
e5ebf783 111
e5ebf783
LP
112 pollfd[POLL_SOCKET].fd = fd;
113 pollfd[POLL_SOCKET].events = POLLIN;
114 pollfd[POLL_INOTIFY].fd = notify;
115 pollfd[POLL_INOTIFY].events = POLLIN;
116
117 for (;;) {
118 int sleep_for = -1, j;
119
120 if (until > 0) {
121 usec_t y;
122
123 y = now(CLOCK_MONOTONIC);
124
1d749d04
ZJS
125 if (y > until)
126 return -ETIME;
e5ebf783
LP
127
128 sleep_for = (int) ((until - y) / USEC_PER_MSEC);
129 }
130
1d749d04
ZJS
131 if (flag_file && access(flag_file, F_OK) < 0)
132 return -errno;
e5ebf783 133
1d749d04
ZJS
134 j = poll(pollfd, notify > 0 ? 2 : 1, sleep_for);
135 if (j < 0) {
e5ebf783
LP
136 if (errno == EINTR)
137 continue;
138
1d749d04
ZJS
139 return -errno;
140 } else if (j == 0)
141 return -ETIME;
e5ebf783
LP
142
143 if (notify > 0 && pollfd[POLL_INOTIFY].revents != 0)
144 flush_fd(notify);
145
146 if (pollfd[POLL_SOCKET].revents == 0)
147 continue;
148
1d749d04
ZJS
149 k = read(fd, buffer + p, sizeof(buffer) - p);
150 if (k <= 0)
151 return r = k < 0 ? -errno : -EIO;
e5ebf783
LP
152
153 p += k;
154
155 if (p < 1)
156 continue;
157
158 if (buffer[0] == 5) {
21bc923a
LP
159
160 if (accept_cached) {
161 /* Hmm, first try with cached
162 * passwords failed, so let's retry
163 * with a normal password request */
164 free(packet);
165 packet = NULL;
166
1d749d04
ZJS
167 if (asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1), message, &n) < 0)
168 return -ENOMEM;
21bc923a 169
553acb7b
ZJS
170 r = loop_write(fd, packet, n+1, true);
171 if (r < 0)
172 return r;
21bc923a
LP
173
174 accept_cached = false;
175 p = 0;
176 continue;
177 }
178
e5ebf783 179 /* No password, because UI not shown */
1d749d04 180 return -ENOENT;
e5ebf783 181
21bc923a 182 } else if (buffer[0] == 2 || buffer[0] == 9) {
e5ebf783 183 uint32_t size;
21bc923a 184 char **l;
e5ebf783 185
4cf07da2 186 /* One or more answers */
e5ebf783
LP
187 if (p < 5)
188 continue;
189
190 memcpy(&size, buffer+1, sizeof(size));
bb53abeb 191 size = le32toh(size);
1d749d04
ZJS
192 if (size + 5 > sizeof(buffer))
193 return -EIO;
e5ebf783
LP
194
195 if (p-5 < size)
196 continue;
197
1d749d04
ZJS
198 l = strv_parse_nulstr(buffer + 5, size);
199 if (!l)
200 return -ENOMEM;
e5ebf783 201
21bc923a 202 *_passphrases = l;
e5ebf783 203 break;
21bc923a 204
1d749d04 205 } else
e5ebf783 206 /* Unknown packet */
1d749d04 207 return -EIO;
e5ebf783
LP
208 }
209
1d749d04 210 return 0;
e5ebf783
LP
211}
212
0ddf1d3a 213static int parse_password(const char *filename, char **wall) {
e46eab86 214 _cleanup_free_ char *socket_name = NULL, *message = NULL, *packet = NULL;
ec863ba6
LP
215 uint64_t not_after = 0;
216 unsigned pid = 0;
64845bdc 217 bool accept_cached = false, echo = false;
ec863ba6 218
f975e971
LP
219 const ConfigTableItem items[] = {
220 { "Ask", "Socket", config_parse_string, 0, &socket_name },
221 { "Ask", "NotAfter", config_parse_uint64, 0, &not_after },
222 { "Ask", "Message", config_parse_string, 0, &message },
223 { "Ask", "PID", config_parse_unsigned, 0, &pid },
224 { "Ask", "AcceptCached", config_parse_bool, 0, &accept_cached },
64845bdc 225 { "Ask", "Echo", config_parse_bool, 0, &echo },
1d749d04 226 {}
ec863ba6
LP
227 };
228
ec863ba6 229 int r;
ec863ba6
LP
230
231 assert(filename);
232
36f822c4
ZJS
233 r = config_parse(NULL, filename, NULL,
234 NULL,
235 config_item_table_lookup, items,
236 true, false, true, NULL);
237 if (r < 0)
238 return r;
ec863ba6 239
7dcda352 240 if (!socket_name) {
ec863ba6 241 log_error("Invalid password file %s", filename);
e46eab86 242 return -EBADMSG;
ec863ba6
LP
243 }
244
e46eab86
ZJS
245 if (not_after > 0 && now(CLOCK_MONOTONIC) > not_after)
246 return 0;
ec863ba6 247
e46eab86
ZJS
248 if (pid > 0 && !pid_is_alive(pid))
249 return 0;
ded80335 250
ec863ba6
LP
251 if (arg_action == ACTION_LIST)
252 printf("'%s' (PID %u)\n", message, pid);
e46eab86 253
ec863ba6 254 else if (arg_action == ACTION_WALL) {
0ddf1d3a 255 char *_wall;
ec863ba6 256
0ddf1d3a
LP
257 if (asprintf(&_wall,
258 "%s%sPassword entry required for \'%s\' (PID %u).\r\n"
9d3e691e 259 "Please enter password with the systemd-tty-ask-password-agent tool!",
0ddf1d3a
LP
260 *wall ? *wall : "",
261 *wall ? "\r\n\r\n" : "",
ec863ba6 262 message,
e46eab86
ZJS
263 pid) < 0)
264 return log_oom();
ec863ba6 265
0ddf1d3a
LP
266 free(*wall);
267 *wall = _wall;
e46eab86 268
ec863ba6 269 } else {
e46eab86 270 union sockaddr_union sa = {};
3414abee 271 size_t packet_length = 0;
e46eab86 272 _cleanup_close_ int socket_fd = -1;
ec863ba6
LP
273
274 assert(arg_action == ACTION_QUERY ||
275 arg_action == ACTION_WATCH);
276
277 if (access(socket_name, W_OK) < 0) {
ec863ba6
LP
278 if (arg_action == ACTION_QUERY)
279 log_info("Not querying '%s' (PID %u), lacking privileges.", message, pid);
280
e46eab86 281 return 0;
ec863ba6
LP
282 }
283
21bc923a 284 if (arg_plymouth) {
cb90460e 285 _cleanup_strv_free_ char **passwords = NULL;
21bc923a 286
e46eab86
ZJS
287 r = ask_password_plymouth(message, not_after, filename, accept_cached, &passwords);
288 if (r >= 0) {
21bc923a
LP
289 char **p;
290
291 packet_length = 1;
292 STRV_FOREACH(p, passwords)
293 packet_length += strlen(*p) + 1;
294
e46eab86
ZJS
295 packet = new(char, packet_length);
296 if (!packet)
21bc923a
LP
297 r = -ENOMEM;
298 else {
1d749d04 299 char *d = packet + 1;
21bc923a
LP
300
301 STRV_FOREACH(p, passwords)
302 d = stpcpy(d, *p) + 1;
1d749d04
ZJS
303
304 packet[0] = '+';
21bc923a
LP
305 }
306 }
307
308 } else {
0cf84693 309 int tty_fd = -1;
e46eab86 310 _cleanup_free_ char *password = NULL;
0cf84693 311
e46eab86 312 if (arg_console) {
3a43da28 313 tty_fd = acquire_terminal("/dev/console", false, false, false, USEC_INFINITY);
e46eab86
ZJS
314 if (tty_fd < 0)
315 return tty_fd;
316 }
0cf84693 317
64845bdc 318 r = ask_password_tty(message, not_after, echo, filename, &password);
e5ebf783 319
0cf84693 320 if (arg_console) {
03e334a1 321 safe_close(tty_fd);
0cf84693
LP
322 release_terminal();
323 }
21bc923a 324
9726f9ff 325 if (r >= 0) {
e46eab86
ZJS
326 packet_length = 1 + strlen(password) + 1;
327 packet = new(char, packet_length);
328 if (!packet)
9726f9ff
LP
329 r = -ENOMEM;
330 else {
331 packet[0] = '+';
e46eab86 332 strcpy(packet + 1, password);
9726f9ff 333 }
5ba7b871 334 }
0cf84693
LP
335 }
336
1d749d04 337 if (IN_SET(r, -ETIME, -ENOENT))
446f0046 338 /* If the query went away, that's OK */
e46eab86 339 return 0;
446f0046 340
f647962d
MS
341 if (r < 0)
342 return log_error_errno(r, "Failed to query password: %m");
ec863ba6 343
e46eab86 344 socket_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0);
4a62c710
MS
345 if (socket_fd < 0)
346 return log_error_errno(errno, "socket(): %m");
ec863ba6 347
ec863ba6
LP
348 sa.un.sun_family = AF_UNIX;
349 strncpy(sa.un.sun_path, socket_name, sizeof(sa.un.sun_path));
350
e46eab86
ZJS
351 r = sendto(socket_fd, packet, packet_length, MSG_NOSIGNAL, &sa.sa,
352 offsetof(struct sockaddr_un, sun_path) + strlen(socket_name));
353 if (r < 0) {
56f64d95 354 log_error_errno(errno, "Failed to send: %m");
e46eab86 355 return r;
ec863ba6
LP
356 }
357 }
358
e46eab86 359 return 0;
ec863ba6
LP
360}
361
0cf84693 362static int wall_tty_block(void) {
1d749d04 363 _cleanup_free_ char *p = NULL;
fc116c6a
LP
364 int fd, r;
365 dev_t devnr;
7af53310 366
4d6d6518
LP
367 r = get_ctty_devnr(0, &devnr);
368 if (r < 0)
ee0e4cca 369 return r;
7af53310 370
2b583ce6 371 if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(devnr), minor(devnr)) < 0)
7af53310
LP
372 return -ENOMEM;
373
d2e54fae 374 mkdir_parents_label(p, 0700);
7af53310
LP
375 mkfifo(p, 0600);
376
377 fd = open(p, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
7af53310
LP
378 if (fd < 0)
379 return -errno;
380
381 return fd;
382}
383
99f710dd 384static bool wall_tty_match(const char *path, void *userdata) {
1d749d04 385 int fd, r;
fc116c6a 386 struct stat st;
1d749d04 387 _cleanup_free_ char *p = NULL;
fc116c6a 388
1d749d04 389 if (!path_is_absolute(path))
63c372cb 390 path = strjoina("/dev/", path);
fc116c6a 391
1d749d04
ZJS
392 r = lstat(path, &st);
393 if (r < 0)
fc116c6a
LP
394 return true;
395
396 if (!S_ISCHR(st.st_mode))
397 return true;
7af53310
LP
398
399 /* We use named pipes to ensure that wall messages suggesting
400 * password entry are not printed over password prompts
401 * already shown. We use the fact here that opening a pipe in
402 * non-blocking mode for write-only will succeed only if
403 * there's some writer behind it. Using pipes has the
404 * advantage that the block will automatically go away if the
405 * process dies. */
406
2b583ce6 407 if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(st.st_rdev), minor(st.st_rdev)) < 0)
7af53310
LP
408 return true;
409
410 fd = open(p, O_WRONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
7af53310
LP
411 if (fd < 0)
412 return true;
413
414 /* What, we managed to open the pipe? Then this tty is filtered. */
03e334a1 415 safe_close(fd);
7af53310
LP
416 return false;
417}
418
ec863ba6 419static int show_passwords(void) {
1d749d04 420 _cleanup_closedir_ DIR *d;
ec863ba6
LP
421 struct dirent *de;
422 int r = 0;
423
1d749d04
ZJS
424 d = opendir("/run/systemd/ask-password");
425 if (!d) {
ec863ba6
LP
426 if (errno == ENOENT)
427 return 0;
428
56f64d95 429 log_error_errno(errno, "opendir(/run/systemd/ask-password): %m");
ec863ba6
LP
430 return -errno;
431 }
432
433 while ((de = readdir(d))) {
1d749d04 434 _cleanup_free_ char *p = NULL, *wall = NULL;
ec863ba6
LP
435 int q;
436
1a6f4df6
LP
437 /* We only support /dev on tmpfs, hence we can rely on
438 * d_type to be reliable */
439
ec863ba6
LP
440 if (de->d_type != DT_REG)
441 continue;
442
a34bf9db 443 if (hidden_file(de->d_name))
ec863ba6
LP
444 continue;
445
446 if (!startswith(de->d_name, "ask."))
447 continue;
448
1d749d04
ZJS
449 p = strappend("/run/systemd/ask-password/", de->d_name);
450 if (!p)
451 return log_oom();
ec863ba6 452
1d749d04
ZJS
453 q = parse_password(p, &wall);
454 if (q < 0 && r == 0)
ec863ba6
LP
455 r = q;
456
1d749d04 457 if (wall)
99f710dd 458 utmp_wall(wall, NULL, NULL, wall_tty_match, NULL);
ec863ba6
LP
459 }
460
ec863ba6
LP
461 return r;
462}
463
464static int watch_passwords(void) {
b9ba604e
LP
465 enum {
466 FD_INOTIFY,
467 FD_SIGNAL,
468 _FD_MAX
469 };
470
1d749d04 471 _cleanup_close_ int notify = -1, signal_fd = -1, tty_block_fd = -1;
b92bea5d 472 struct pollfd pollfd[_FD_MAX] = {};
b9ba604e 473 sigset_t mask;
ec863ba6
LP
474 int r;
475
0cf84693 476 tty_block_fd = wall_tty_block();
7af53310 477
d2e54fae 478 mkdir_p_label("/run/systemd/ask-password", 0755);
ec863ba6 479
1d749d04
ZJS
480 notify = inotify_init1(IN_CLOEXEC);
481 if (notify < 0)
482 return -errno;
ec863ba6 483
1d749d04
ZJS
484 if (inotify_add_watch(notify, "/run/systemd/ask-password", IN_CLOSE_WRITE|IN_MOVED_TO) < 0)
485 return -errno;
ec863ba6 486
b9ba604e
LP
487 assert_se(sigemptyset(&mask) == 0);
488 sigset_add_many(&mask, SIGINT, SIGTERM, -1);
489 assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0);
490
1d749d04
ZJS
491 signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC);
492 if (signal_fd < 0)
493 return -errno;
b9ba604e 494
b9ba604e
LP
495 pollfd[FD_INOTIFY].fd = notify;
496 pollfd[FD_INOTIFY].events = POLLIN;
497 pollfd[FD_SIGNAL].fd = signal_fd;
498 pollfd[FD_SIGNAL].events = POLLIN;
ec863ba6
LP
499
500 for (;;) {
1d749d04
ZJS
501 r = show_passwords();
502 if (r < 0)
da927ba9 503 log_error_errno(r, "Failed to show password: %m");
ec863ba6 504
b9ba604e 505 if (poll(pollfd, _FD_MAX, -1) < 0) {
ec863ba6
LP
506 if (errno == EINTR)
507 continue;
508
1d749d04 509 return -errno;
ec863ba6
LP
510 }
511
b9ba604e 512 if (pollfd[FD_INOTIFY].revents != 0)
ec863ba6 513 flush_fd(notify);
b9ba604e
LP
514
515 if (pollfd[FD_SIGNAL].revents != 0)
516 break;
ec863ba6
LP
517 }
518
1d749d04 519 return 0;
ec863ba6
LP
520}
521
601185b4 522static void help(void) {
ec863ba6
LP
523 printf("%s [OPTIONS...]\n\n"
524 "Process system password requests.\n\n"
e5ebf783 525 " -h --help Show this help\n"
c52f663b 526 " --version Show package version\n"
e5ebf783
LP
527 " --list Show pending password requests\n"
528 " --query Process pending password requests\n"
35b8ca3a
HH
529 " --watch Continuously process password requests\n"
530 " --wall Continuously forward password requests to wall\n"
0cf84693
LP
531 " --plymouth Ask question with Plymouth instead of on TTY\n"
532 " --console Ask question on /dev/console instead of current TTY\n",
ec863ba6 533 program_invocation_short_name);
ec863ba6
LP
534}
535
536static int parse_argv(int argc, char *argv[]) {
537
538 enum {
539 ARG_LIST = 0x100,
540 ARG_QUERY,
541 ARG_WATCH,
542 ARG_WALL,
0cf84693 543 ARG_PLYMOUTH,
c52f663b
LP
544 ARG_CONSOLE,
545 ARG_VERSION
ec863ba6
LP
546 };
547
548 static const struct option options[] = {
e5ebf783 549 { "help", no_argument, NULL, 'h' },
c52f663b 550 { "version", no_argument, NULL, ARG_VERSION },
e5ebf783
LP
551 { "list", no_argument, NULL, ARG_LIST },
552 { "query", no_argument, NULL, ARG_QUERY },
553 { "watch", no_argument, NULL, ARG_WATCH },
554 { "wall", no_argument, NULL, ARG_WALL },
555 { "plymouth", no_argument, NULL, ARG_PLYMOUTH },
0cf84693 556 { "console", no_argument, NULL, ARG_CONSOLE },
eb9da376 557 {}
ec863ba6
LP
558 };
559
560 int c;
561
562 assert(argc >= 0);
563 assert(argv);
564
601185b4 565 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
ec863ba6
LP
566
567 switch (c) {
568
569 case 'h':
601185b4
ZJS
570 help();
571 return 0;
ec863ba6 572
c52f663b
LP
573 case ARG_VERSION:
574 puts(PACKAGE_STRING);
c52f663b
LP
575 puts(SYSTEMD_FEATURES);
576 return 0;
577
ec863ba6
LP
578 case ARG_LIST:
579 arg_action = ACTION_LIST;
580 break;
581
582 case ARG_QUERY:
583 arg_action = ACTION_QUERY;
584 break;
585
586 case ARG_WATCH:
587 arg_action = ACTION_WATCH;
588 break;
589
590 case ARG_WALL:
591 arg_action = ACTION_WALL;
592 break;
593
e5ebf783
LP
594 case ARG_PLYMOUTH:
595 arg_plymouth = true;
596 break;
597
0cf84693
LP
598 case ARG_CONSOLE:
599 arg_console = true;
600 break;
601
ec863ba6
LP
602 case '?':
603 return -EINVAL;
604
605 default:
eb9da376 606 assert_not_reached("Unhandled option");
ec863ba6 607 }
ec863ba6
LP
608
609 if (optind != argc) {
601185b4 610 log_error("%s takes no arguments.", program_invocation_short_name);
ec863ba6
LP
611 return -EINVAL;
612 }
613
614 return 1;
615}
616
617int main(int argc, char *argv[]) {
618 int r;
619
4b261568 620 log_set_target(LOG_TARGET_AUTO);
ec863ba6
LP
621 log_parse_environment();
622 log_open();
623
4c12626c
LP
624 umask(0022);
625
1d749d04
ZJS
626 r = parse_argv(argc, argv);
627 if (r <= 0)
ec863ba6
LP
628 goto finish;
629
0cf84693
LP
630 if (arg_console) {
631 setsid();
632 release_terminal();
633 }
634
1d749d04 635 if (IN_SET(arg_action, ACTION_WATCH, ACTION_WALL))
ec863ba6
LP
636 r = watch_passwords();
637 else
638 r = show_passwords();
639
96707269 640 if (r < 0)
da927ba9 641 log_error_errno(r, "Error: %m");
96707269 642
ec863ba6
LP
643finish:
644 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
645}