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