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