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