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