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