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