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