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