chiark / gitweb /
e7cbde285ce89087d011a6b9d336325be76e4e71
[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;
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                 {}
226         };
227
228         int r;
229
230         assert(filename);
231
232         r = config_parse(NULL, filename, NULL,
233                          NULL,
234                          config_item_table_lookup, items,
235                          true, false, true, NULL);
236         if (r < 0)
237                 return r;
238
239         if (!socket_name) {
240                 log_error("Invalid password file %s", filename);
241                 return -EBADMSG;
242         }
243
244         if (not_after > 0 && now(CLOCK_MONOTONIC) > not_after)
245                 return 0;
246
247         if (pid > 0 && !pid_is_alive(pid))
248                 return 0;
249
250         if (arg_action == ACTION_LIST)
251                 printf("'%s' (PID %u)\n", message, pid);
252
253         else if (arg_action == ACTION_WALL) {
254                 char *_wall;
255
256                 if (asprintf(&_wall,
257                              "%s%sPassword entry required for \'%s\' (PID %u).\r\n"
258                              "Please enter password with the systemd-tty-ask-password-agent tool!",
259                              *wall ? *wall : "",
260                              *wall ? "\r\n\r\n" : "",
261                              message,
262                              pid) < 0)
263                         return log_oom();
264
265                 free(*wall);
266                 *wall = _wall;
267
268         } else {
269                 union sockaddr_union sa = {};
270                 size_t packet_length = 0;
271                 _cleanup_close_ int socket_fd = -1;
272
273                 assert(arg_action == ACTION_QUERY ||
274                        arg_action == ACTION_WATCH);
275
276                 if (access(socket_name, W_OK) < 0) {
277                         if (arg_action == ACTION_QUERY)
278                                 log_info("Not querying '%s' (PID %u), lacking privileges.", message, pid);
279
280                         return 0;
281                 }
282
283                 if (arg_plymouth) {
284                         _cleanup_strv_free_ char **passwords = NULL;
285
286                         r = ask_password_plymouth(message, not_after, filename, accept_cached, &passwords);
287                         if (r >= 0) {
288                                 char **p;
289
290                                 packet_length = 1;
291                                 STRV_FOREACH(p, passwords)
292                                         packet_length += strlen(*p) + 1;
293
294                                 packet = new(char, packet_length);
295                                 if (!packet)
296                                         r = -ENOMEM;
297                                 else {
298                                         char *d = packet + 1;
299
300                                         STRV_FOREACH(p, passwords)
301                                                 d = stpcpy(d, *p) + 1;
302
303                                         packet[0] = '+';
304                                 }
305                         }
306
307                 } else {
308                         int tty_fd = -1;
309                         _cleanup_free_ char *password = NULL;
310
311                         if (arg_console) {
312                                 tty_fd = acquire_terminal("/dev/console", false, false, false, USEC_INFINITY);
313                                 if (tty_fd < 0)
314                                         return tty_fd;
315                         }
316
317                         r = ask_password_tty(message, not_after, filename, &password);
318
319                         if (arg_console) {
320                                 safe_close(tty_fd);
321                                 release_terminal();
322                         }
323
324                         if (r >= 0) {
325                                 packet_length = 1 + strlen(password) + 1;
326                                 packet = new(char, packet_length);
327                                 if (!packet)
328                                         r = -ENOMEM;
329                                 else {
330                                         packet[0] = '+';
331                                         strcpy(packet + 1, password);
332                                 }
333                         }
334                 }
335
336                 if (IN_SET(r, -ETIME, -ENOENT))
337                         /* If the query went away, that's OK */
338                         return 0;
339
340                 if (r < 0) {
341                         log_error("Failed to query password: %s", strerror(-r));
342                         return r;
343                 }
344
345                 socket_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0);
346                 if (socket_fd < 0) {
347                         log_error("socket(): %m");
348                         return -errno;
349                 }
350
351                 sa.un.sun_family = AF_UNIX;
352                 strncpy(sa.un.sun_path, socket_name, sizeof(sa.un.sun_path));
353
354                 r = sendto(socket_fd, packet, packet_length, MSG_NOSIGNAL, &sa.sa,
355                            offsetof(struct sockaddr_un, sun_path) + strlen(socket_name));
356                 if (r < 0) {
357                         log_error("Failed to send: %m");
358                         return r;
359                 }
360         }
361
362         return 0;
363 }
364
365 static int wall_tty_block(void) {
366         _cleanup_free_ char *p = NULL;
367         int fd, r;
368         dev_t devnr;
369
370         r = get_ctty_devnr(0, &devnr);
371         if (r < 0)
372                 return r;
373
374         if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(devnr), minor(devnr)) < 0)
375                 return -ENOMEM;
376
377         mkdir_parents_label(p, 0700);
378         mkfifo(p, 0600);
379
380         fd = open(p, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
381         if (fd < 0)
382                 return -errno;
383
384         return fd;
385 }
386
387 static bool wall_tty_match(const char *path) {
388         int fd, r;
389         struct stat st;
390         _cleanup_free_ char *p = NULL;
391
392         if (!path_is_absolute(path))
393                 path = strappenda("/dev/", path);
394
395         r = lstat(path, &st);
396         if (r < 0)
397                 return true;
398
399         if (!S_ISCHR(st.st_mode))
400                 return true;
401
402         /* We use named pipes to ensure that wall messages suggesting
403          * password entry are not printed over password prompts
404          * already shown. We use the fact here that opening a pipe in
405          * non-blocking mode for write-only will succeed only if
406          * there's some writer behind it. Using pipes has the
407          * advantage that the block will automatically go away if the
408          * process dies. */
409
410         if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(st.st_rdev), minor(st.st_rdev)) < 0)
411                 return true;
412
413         fd = open(p, O_WRONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
414         if (fd < 0)
415                 return true;
416
417         /* What, we managed to open the pipe? Then this tty is filtered. */
418         safe_close(fd);
419         return false;
420 }
421
422 static int show_passwords(void) {
423         _cleanup_closedir_ DIR *d;
424         struct dirent *de;
425         int r = 0;
426
427         d = opendir("/run/systemd/ask-password");
428         if (!d) {
429                 if (errno == ENOENT)
430                         return 0;
431
432                 log_error("opendir(/run/systemd/ask-password): %m");
433                 return -errno;
434         }
435
436         while ((de = readdir(d))) {
437                 _cleanup_free_ char *p = NULL, *wall = NULL;
438                 int q;
439
440                 /* We only support /dev on tmpfs, hence we can rely on
441                  * d_type to be reliable */
442
443                 if (de->d_type != DT_REG)
444                         continue;
445
446                 if (ignore_file(de->d_name))
447                         continue;
448
449                 if (!startswith(de->d_name, "ask."))
450                         continue;
451
452                 p = strappend("/run/systemd/ask-password/", de->d_name);
453                 if (!p)
454                         return log_oom();
455
456                 q = parse_password(p, &wall);
457                 if (q < 0 && r == 0)
458                         r = q;
459
460                 if (wall)
461                         utmp_wall(wall, NULL, wall_tty_match);
462         }
463
464         return r;
465 }
466
467 static int watch_passwords(void) {
468         enum {
469                 FD_INOTIFY,
470                 FD_SIGNAL,
471                 _FD_MAX
472         };
473
474         _cleanup_close_ int notify = -1, signal_fd = -1, tty_block_fd = -1;
475         struct pollfd pollfd[_FD_MAX] = {};
476         sigset_t mask;
477         int r;
478
479         tty_block_fd = wall_tty_block();
480
481         mkdir_p_label("/run/systemd/ask-password", 0755);
482
483         notify = inotify_init1(IN_CLOEXEC);
484         if (notify < 0)
485                 return -errno;
486
487         if (inotify_add_watch(notify, "/run/systemd/ask-password", IN_CLOSE_WRITE|IN_MOVED_TO) < 0)
488                 return -errno;
489
490         assert_se(sigemptyset(&mask) == 0);
491         sigset_add_many(&mask, SIGINT, SIGTERM, -1);
492         assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0);
493
494         signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC);
495         if (signal_fd < 0)
496                 return -errno;
497
498         pollfd[FD_INOTIFY].fd = notify;
499         pollfd[FD_INOTIFY].events = POLLIN;
500         pollfd[FD_SIGNAL].fd = signal_fd;
501         pollfd[FD_SIGNAL].events = POLLIN;
502
503         for (;;) {
504                 r = show_passwords();
505                 if (r < 0)
506                         log_error("Failed to show password: %s", strerror(-r));
507
508                 if (poll(pollfd, _FD_MAX, -1) < 0) {
509                         if (errno == EINTR)
510                                 continue;
511
512                         return -errno;
513                 }
514
515                 if (pollfd[FD_INOTIFY].revents != 0)
516                         flush_fd(notify);
517
518                 if (pollfd[FD_SIGNAL].revents != 0)
519                         break;
520         }
521
522         return 0;
523 }
524
525 static void help(void) {
526         printf("%s [OPTIONS...]\n\n"
527                "Process system password requests.\n\n"
528                "  -h --help     Show this help\n"
529                "     --version  Show package version\n"
530                "     --list     Show pending password requests\n"
531                "     --query    Process pending password requests\n"
532                "     --watch    Continuously process password requests\n"
533                "     --wall     Continuously forward password requests to wall\n"
534                "     --plymouth Ask question with Plymouth instead of on TTY\n"
535                "     --console  Ask question on /dev/console instead of current TTY\n",
536                program_invocation_short_name);
537 }
538
539 static int parse_argv(int argc, char *argv[]) {
540
541         enum {
542                 ARG_LIST = 0x100,
543                 ARG_QUERY,
544                 ARG_WATCH,
545                 ARG_WALL,
546                 ARG_PLYMOUTH,
547                 ARG_CONSOLE,
548                 ARG_VERSION
549         };
550
551         static const struct option options[] = {
552                 { "help",     no_argument, NULL, 'h'          },
553                 { "version",  no_argument, NULL, ARG_VERSION  },
554                 { "list",     no_argument, NULL, ARG_LIST     },
555                 { "query",    no_argument, NULL, ARG_QUERY    },
556                 { "watch",    no_argument, NULL, ARG_WATCH    },
557                 { "wall",     no_argument, NULL, ARG_WALL     },
558                 { "plymouth", no_argument, NULL, ARG_PLYMOUTH },
559                 { "console",  no_argument, NULL, ARG_CONSOLE  },
560                 {}
561         };
562
563         int c;
564
565         assert(argc >= 0);
566         assert(argv);
567
568         while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
569
570                 switch (c) {
571
572                 case 'h':
573                         help();
574                         return 0;
575
576                 case ARG_VERSION:
577                         puts(PACKAGE_STRING);
578                         puts(SYSTEMD_FEATURES);
579                         return 0;
580
581                 case ARG_LIST:
582                         arg_action = ACTION_LIST;
583                         break;
584
585                 case ARG_QUERY:
586                         arg_action = ACTION_QUERY;
587                         break;
588
589                 case ARG_WATCH:
590                         arg_action = ACTION_WATCH;
591                         break;
592
593                 case ARG_WALL:
594                         arg_action = ACTION_WALL;
595                         break;
596
597                 case ARG_PLYMOUTH:
598                         arg_plymouth = true;
599                         break;
600
601                 case ARG_CONSOLE:
602                         arg_console = true;
603                         break;
604
605                 case '?':
606                         return -EINVAL;
607
608                 default:
609                         assert_not_reached("Unhandled option");
610                 }
611
612         if (optind != argc) {
613                 log_error("%s takes no arguments.", program_invocation_short_name);
614                 return -EINVAL;
615         }
616
617         return 1;
618 }
619
620 int main(int argc, char *argv[]) {
621         int r;
622
623         log_set_target(LOG_TARGET_AUTO);
624         log_parse_environment();
625         log_open();
626
627         umask(0022);
628
629         r = parse_argv(argc, argv);
630         if (r <= 0)
631                 goto finish;
632
633         if (arg_console) {
634                 setsid();
635                 release_terminal();
636         }
637
638         if (IN_SET(arg_action, ACTION_WATCH, ACTION_WALL))
639                 r = watch_passwords();
640         else
641                 r = show_passwords();
642
643         if (r < 0)
644                 log_error("Error: %s", strerror(-r));
645
646 finish:
647         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
648 }