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