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