chiark / gitweb /
sd-event: rename sd_event_source_set_name() to sd_event_source_get_name()
[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                         log_error("Failed to query password: %s", strerror(-r));
343                         return r;
344                 }
345
346                 socket_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0);
347                 if (socket_fd < 0) {
348                         log_error("socket(): %m");
349                         return -errno;
350                 }
351
352                 sa.un.sun_family = AF_UNIX;
353                 strncpy(sa.un.sun_path, socket_name, sizeof(sa.un.sun_path));
354
355                 r = sendto(socket_fd, packet, packet_length, MSG_NOSIGNAL, &sa.sa,
356                            offsetof(struct sockaddr_un, sun_path) + strlen(socket_name));
357                 if (r < 0) {
358                         log_error("Failed to send: %m");
359                         return r;
360                 }
361         }
362
363         return 0;
364 }
365
366 static int wall_tty_block(void) {
367         _cleanup_free_ char *p = NULL;
368         int fd, r;
369         dev_t devnr;
370
371         r = get_ctty_devnr(0, &devnr);
372         if (r < 0)
373                 return r;
374
375         if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(devnr), minor(devnr)) < 0)
376                 return -ENOMEM;
377
378         mkdir_parents_label(p, 0700);
379         mkfifo(p, 0600);
380
381         fd = open(p, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
382         if (fd < 0)
383                 return -errno;
384
385         return fd;
386 }
387
388 static bool wall_tty_match(const char *path) {
389         int fd, r;
390         struct stat st;
391         _cleanup_free_ char *p = NULL;
392
393         if (!path_is_absolute(path))
394                 path = strappenda("/dev/", path);
395
396         r = lstat(path, &st);
397         if (r < 0)
398                 return true;
399
400         if (!S_ISCHR(st.st_mode))
401                 return true;
402
403         /* We use named pipes to ensure that wall messages suggesting
404          * password entry are not printed over password prompts
405          * already shown. We use the fact here that opening a pipe in
406          * non-blocking mode for write-only will succeed only if
407          * there's some writer behind it. Using pipes has the
408          * advantage that the block will automatically go away if the
409          * process dies. */
410
411         if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(st.st_rdev), minor(st.st_rdev)) < 0)
412                 return true;
413
414         fd = open(p, O_WRONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
415         if (fd < 0)
416                 return true;
417
418         /* What, we managed to open the pipe? Then this tty is filtered. */
419         safe_close(fd);
420         return false;
421 }
422
423 static int show_passwords(void) {
424         _cleanup_closedir_ DIR *d;
425         struct dirent *de;
426         int r = 0;
427
428         d = opendir("/run/systemd/ask-password");
429         if (!d) {
430                 if (errno == ENOENT)
431                         return 0;
432
433                 log_error("opendir(/run/systemd/ask-password): %m");
434                 return -errno;
435         }
436
437         while ((de = readdir(d))) {
438                 _cleanup_free_ char *p = NULL, *wall = NULL;
439                 int q;
440
441                 /* We only support /dev on tmpfs, hence we can rely on
442                  * d_type to be reliable */
443
444                 if (de->d_type != DT_REG)
445                         continue;
446
447                 if (ignore_file(de->d_name))
448                         continue;
449
450                 if (!startswith(de->d_name, "ask."))
451                         continue;
452
453                 p = strappend("/run/systemd/ask-password/", de->d_name);
454                 if (!p)
455                         return log_oom();
456
457                 q = parse_password(p, &wall);
458                 if (q < 0 && r == 0)
459                         r = q;
460
461                 if (wall)
462                         utmp_wall(wall, NULL, wall_tty_match);
463         }
464
465         return r;
466 }
467
468 static int watch_passwords(void) {
469         enum {
470                 FD_INOTIFY,
471                 FD_SIGNAL,
472                 _FD_MAX
473         };
474
475         _cleanup_close_ int notify = -1, signal_fd = -1, tty_block_fd = -1;
476         struct pollfd pollfd[_FD_MAX] = {};
477         sigset_t mask;
478         int r;
479
480         tty_block_fd = wall_tty_block();
481
482         mkdir_p_label("/run/systemd/ask-password", 0755);
483
484         notify = inotify_init1(IN_CLOEXEC);
485         if (notify < 0)
486                 return -errno;
487
488         if (inotify_add_watch(notify, "/run/systemd/ask-password", IN_CLOSE_WRITE|IN_MOVED_TO) < 0)
489                 return -errno;
490
491         assert_se(sigemptyset(&mask) == 0);
492         sigset_add_many(&mask, SIGINT, SIGTERM, -1);
493         assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0);
494
495         signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC);
496         if (signal_fd < 0)
497                 return -errno;
498
499         pollfd[FD_INOTIFY].fd = notify;
500         pollfd[FD_INOTIFY].events = POLLIN;
501         pollfd[FD_SIGNAL].fd = signal_fd;
502         pollfd[FD_SIGNAL].events = POLLIN;
503
504         for (;;) {
505                 r = show_passwords();
506                 if (r < 0)
507                         log_error("Failed to show password: %s", strerror(-r));
508
509                 if (poll(pollfd, _FD_MAX, -1) < 0) {
510                         if (errno == EINTR)
511                                 continue;
512
513                         return -errno;
514                 }
515
516                 if (pollfd[FD_INOTIFY].revents != 0)
517                         flush_fd(notify);
518
519                 if (pollfd[FD_SIGNAL].revents != 0)
520                         break;
521         }
522
523         return 0;
524 }
525
526 static void help(void) {
527         printf("%s [OPTIONS...]\n\n"
528                "Process system password requests.\n\n"
529                "  -h --help     Show this help\n"
530                "     --version  Show package version\n"
531                "     --list     Show pending password requests\n"
532                "     --query    Process pending password requests\n"
533                "     --watch    Continuously process password requests\n"
534                "     --wall     Continuously forward password requests to wall\n"
535                "     --plymouth Ask question with Plymouth instead of on TTY\n"
536                "     --console  Ask question on /dev/console instead of current TTY\n",
537                program_invocation_short_name);
538 }
539
540 static int parse_argv(int argc, char *argv[]) {
541
542         enum {
543                 ARG_LIST = 0x100,
544                 ARG_QUERY,
545                 ARG_WATCH,
546                 ARG_WALL,
547                 ARG_PLYMOUTH,
548                 ARG_CONSOLE,
549                 ARG_VERSION
550         };
551
552         static const struct option options[] = {
553                 { "help",     no_argument, NULL, 'h'          },
554                 { "version",  no_argument, NULL, ARG_VERSION  },
555                 { "list",     no_argument, NULL, ARG_LIST     },
556                 { "query",    no_argument, NULL, ARG_QUERY    },
557                 { "watch",    no_argument, NULL, ARG_WATCH    },
558                 { "wall",     no_argument, NULL, ARG_WALL     },
559                 { "plymouth", no_argument, NULL, ARG_PLYMOUTH },
560                 { "console",  no_argument, NULL, ARG_CONSOLE  },
561                 {}
562         };
563
564         int c;
565
566         assert(argc >= 0);
567         assert(argv);
568
569         while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
570
571                 switch (c) {
572
573                 case 'h':
574                         help();
575                         return 0;
576
577                 case ARG_VERSION:
578                         puts(PACKAGE_STRING);
579                         puts(SYSTEMD_FEATURES);
580                         return 0;
581
582                 case ARG_LIST:
583                         arg_action = ACTION_LIST;
584                         break;
585
586                 case ARG_QUERY:
587                         arg_action = ACTION_QUERY;
588                         break;
589
590                 case ARG_WATCH:
591                         arg_action = ACTION_WATCH;
592                         break;
593
594                 case ARG_WALL:
595                         arg_action = ACTION_WALL;
596                         break;
597
598                 case ARG_PLYMOUTH:
599                         arg_plymouth = true;
600                         break;
601
602                 case ARG_CONSOLE:
603                         arg_console = true;
604                         break;
605
606                 case '?':
607                         return -EINVAL;
608
609                 default:
610                         assert_not_reached("Unhandled option");
611                 }
612
613         if (optind != argc) {
614                 log_error("%s takes no arguments.", program_invocation_short_name);
615                 return -EINVAL;
616         }
617
618         return 1;
619 }
620
621 int main(int argc, char *argv[]) {
622         int r;
623
624         log_set_target(LOG_TARGET_AUTO);
625         log_parse_environment();
626         log_open();
627
628         umask(0022);
629
630         r = parse_argv(argc, argv);
631         if (r <= 0)
632                 goto finish;
633
634         if (arg_console) {
635                 setsid();
636                 release_terminal();
637         }
638
639         if (IN_SET(arg_action, ACTION_WATCH, ACTION_WALL))
640                 r = watch_passwords();
641         else
642                 r = show_passwords();
643
644         if (r < 0)
645                 log_error("Error: %s", strerror(-r));
646
647 finish:
648         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
649 }