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