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