chiark / gitweb /
split selinux label operations out of cgroup-util, socket-util
[elogind.git] / src / 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 General Public License as published by
10   the Free Software Foundation; either version 2 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   General Public License for more details.
17
18   You should have received a copy of the GNU 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 "conf-parser.h"
38 #include "utmp-wtmp.h"
39 #include "socket-util.h"
40 #include "ask-password-api.h"
41 #include "strv.h"
42
43 static enum {
44         ACTION_LIST,
45         ACTION_QUERY,
46         ACTION_WATCH,
47         ACTION_WALL
48 } arg_action = ACTION_QUERY;
49
50 static bool arg_plymouth = false;
51 static bool arg_console = false;
52
53 static int ask_password_plymouth(
54                 const char *message,
55                 usec_t until,
56                 const char *flag_file,
57                 bool accept_cached,
58                 char ***_passphrases) {
59
60         int fd = -1, notify = -1;
61         union sockaddr_union sa;
62         char *packet = NULL;
63         ssize_t k;
64         int r, n;
65         struct pollfd pollfd[2];
66         char buffer[LINE_MAX];
67         size_t p = 0;
68         enum {
69                 POLL_SOCKET,
70                 POLL_INOTIFY
71         };
72
73         assert(_passphrases);
74
75         if (flag_file) {
76                 if ((notify = inotify_init1(IN_CLOEXEC|IN_NONBLOCK)) < 0) {
77                         r = -errno;
78                         goto finish;
79                 }
80
81                 if (inotify_add_watch(notify, flag_file, IN_ATTRIB /* for the link count */) < 0) {
82                         r = -errno;
83                         goto finish;
84                 }
85         }
86
87         if ((fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0)) < 0) {
88                 r = -errno;
89                 goto finish;
90         }
91
92         zero(sa);
93         sa.sa.sa_family = AF_UNIX;
94         strncpy(sa.un.sun_path+1, "/org/freedesktop/plymouthd", sizeof(sa.un.sun_path)-1);
95         if (connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + 1 + strlen(sa.un.sun_path+1)) < 0) {
96                 log_error("Failed to connect to Plymouth: %m");
97                 r = -errno;
98                 goto finish;
99         }
100
101         if (accept_cached) {
102                 packet = strdup("c");
103                 n = 1;
104         } else
105                 asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1), message, &n);
106
107         if (!packet) {
108                 r = -ENOMEM;
109                 goto finish;
110         }
111
112         if ((k = loop_write(fd, packet, n+1, true)) != n+1) {
113                 r = k < 0 ? (int) k : -EIO;
114                 goto finish;
115         }
116
117         zero(pollfd);
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         if (notify >= 0)
238                 close_nointr_nofail(notify);
239
240         if (fd >= 0)
241                 close_nointr_nofail(fd);
242
243         free(packet);
244
245         return r;
246 }
247
248 static int parse_password(const char *filename, char **wall) {
249         char *socket_name = NULL, *message = NULL, *packet = NULL;
250         uint64_t not_after = 0;
251         unsigned pid = 0;
252         int socket_fd = -1;
253         bool accept_cached = false;
254
255         const ConfigTableItem items[] = {
256                 { "Ask", "Socket",       config_parse_string,   0, &socket_name   },
257                 { "Ask", "NotAfter",     config_parse_uint64,   0, &not_after     },
258                 { "Ask", "Message",      config_parse_string,   0, &message       },
259                 { "Ask", "PID",          config_parse_unsigned, 0, &pid           },
260                 { "Ask", "AcceptCached", config_parse_bool,     0, &accept_cached },
261                 { NULL, NULL, NULL, 0, NULL }
262         };
263
264         FILE *f;
265         int r;
266
267         assert(filename);
268
269         f = fopen(filename, "re");
270         if (!f) {
271                 if (errno == ENOENT)
272                         return 0;
273
274                 log_error("open(%s): %m", filename);
275                 return -errno;
276         }
277
278         r = config_parse(filename, f, NULL, config_item_table_lookup, (void*) items, true, NULL);
279         if (r < 0) {
280                 log_error("Failed to parse password file %s: %s", filename, strerror(-r));
281                 goto finish;
282         }
283
284         if (!socket_name) {
285                 log_error("Invalid password file %s", filename);
286                 r = -EBADMSG;
287                 goto finish;
288         }
289
290         if (not_after > 0) {
291                 if (now(CLOCK_MONOTONIC) > not_after) {
292                         r = 0;
293                         goto finish;
294                 }
295         }
296
297         if (pid > 0 &&
298             kill(pid, 0) < 0 &&
299             errno == ESRCH) {
300                 r = 0;
301                 goto finish;
302         }
303
304         if (arg_action == ACTION_LIST)
305                 printf("'%s' (PID %u)\n", message, pid);
306         else if (arg_action == ACTION_WALL) {
307                 char *_wall;
308
309                 if (asprintf(&_wall,
310                              "%s%sPassword entry required for \'%s\' (PID %u).\r\n"
311                              "Please enter password with the systemd-tty-ask-password-agent tool!",
312                              *wall ? *wall : "",
313                              *wall ? "\r\n\r\n" : "",
314                              message,
315                              pid) < 0) {
316                         log_error("Out of memory");
317                         r = -ENOMEM;
318                         goto finish;
319                 }
320
321                 free(*wall);
322                 *wall = _wall;
323         } else {
324                 union {
325                         struct sockaddr sa;
326                         struct sockaddr_un un;
327                 } sa;
328                 size_t packet_length = 0;
329
330                 assert(arg_action == ACTION_QUERY ||
331                        arg_action == ACTION_WATCH);
332
333                 if (access(socket_name, W_OK) < 0) {
334
335                         if (arg_action == ACTION_QUERY)
336                                 log_info("Not querying '%s' (PID %u), lacking privileges.", message, pid);
337
338                         r = 0;
339                         goto finish;
340                 }
341
342                 if (arg_plymouth) {
343                         char **passwords = NULL;
344
345                         if ((r = ask_password_plymouth(message, not_after, filename, accept_cached, &passwords)) >= 0) {
346                                 char **p;
347
348                                 packet_length = 1;
349                                 STRV_FOREACH(p, passwords)
350                                         packet_length += strlen(*p) + 1;
351
352                                 if (!(packet = new(char, packet_length)))
353                                         r = -ENOMEM;
354                                 else {
355                                         char *d;
356
357                                         packet[0] = '+';
358                                         d = packet+1;
359
360                                         STRV_FOREACH(p, passwords)
361                                                 d = stpcpy(d, *p) + 1;
362                                 }
363                         }
364
365                 } else {
366                         int tty_fd = -1;
367                         char *password;
368
369                         if (arg_console)
370                                 if ((tty_fd = acquire_terminal("/dev/console", false, false, false)) < 0) {
371                                         r = tty_fd;
372                                         goto finish;
373                                 }
374
375                         r = ask_password_tty(message, not_after, filename, &password);
376
377                         if (arg_console) {
378                                 close_nointr_nofail(tty_fd);
379                                 release_terminal();
380                         }
381
382                         if (r >= 0) {
383                                 packet_length = 1+strlen(password)+1;
384                                 if (!(packet = new(char, packet_length)))
385                                         r = -ENOMEM;
386                                 else {
387                                         packet[0] = '+';
388                                         strcpy(packet+1, password);
389                                 }
390
391                                 free(password);
392                         }
393                 }
394
395                 if (r == -ETIME || r == -ENOENT) {
396                         /* If the query went away, that's OK */
397                         r = 0;
398                         goto finish;
399                 }
400
401                 if (r < 0) {
402                         log_error("Failed to query password: %s", strerror(-r));
403                         goto finish;
404                 }
405
406                 if ((socket_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0)) < 0) {
407                         log_error("socket(): %m");
408                         r = -errno;
409                         goto finish;
410                 }
411
412                 zero(sa);
413                 sa.un.sun_family = AF_UNIX;
414                 strncpy(sa.un.sun_path, socket_name, sizeof(sa.un.sun_path));
415
416                 if (sendto(socket_fd, packet, packet_length, MSG_NOSIGNAL, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(socket_name)) < 0) {
417                         log_error("Failed to send: %m");
418                         r = -errno;
419                         goto finish;
420                 }
421         }
422
423 finish:
424         fclose(f);
425
426         if (socket_fd >= 0)
427                 close_nointr_nofail(socket_fd);
428
429         free(packet);
430         free(socket_name);
431         free(message);
432
433         return r;
434 }
435
436 static int wall_tty_block(void) {
437         char *p;
438         int fd, r;
439         dev_t devnr;
440
441         r = get_ctty_devnr(0, &devnr);
442         if (r < 0)
443                 return -r;
444
445         if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(devnr), minor(devnr)) < 0)
446                 return -ENOMEM;
447
448         mkdir_parents(p, 0700);
449         mkfifo(p, 0600);
450
451         fd = open(p, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
452         free(p);
453
454         if (fd < 0)
455                 return -errno;
456
457         return fd;
458 }
459
460 static bool wall_tty_match(const char *path) {
461         int fd, k;
462         char *p;
463         struct stat st;
464
465         if (path_is_absolute(path))
466                 k = lstat(path, &st);
467         else {
468                 if (asprintf(&p, "/dev/%s", path) < 0)
469                         return true;
470
471                 k = lstat(p, &st);
472                 free(p);
473         }
474
475         if (k < 0)
476                 return true;
477
478         if (!S_ISCHR(st.st_mode))
479                 return true;
480
481         /* We use named pipes to ensure that wall messages suggesting
482          * password entry are not printed over password prompts
483          * already shown. We use the fact here that opening a pipe in
484          * non-blocking mode for write-only will succeed only if
485          * there's some writer behind it. Using pipes has the
486          * advantage that the block will automatically go away if the
487          * process dies. */
488
489         if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(st.st_rdev), minor(st.st_rdev)) < 0)
490                 return true;
491
492         fd = open(p, O_WRONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
493         free(p);
494
495         if (fd < 0)
496                 return true;
497
498         /* What, we managed to open the pipe? Then this tty is filtered. */
499         close_nointr_nofail(fd);
500         return false;
501 }
502
503 static int show_passwords(void) {
504         DIR *d;
505         struct dirent *de;
506         int r = 0;
507
508         if (!(d = opendir("/run/systemd/ask-password"))) {
509                 if (errno == ENOENT)
510                         return 0;
511
512                 log_error("opendir(): %m");
513                 return -errno;
514         }
515
516         while ((de = readdir(d))) {
517                 char *p;
518                 int q;
519                 char *wall;
520
521                 /* We only support /dev on tmpfs, hence we can rely on
522                  * d_type to be reliable */
523
524                 if (de->d_type != DT_REG)
525                         continue;
526
527                 if (ignore_file(de->d_name))
528                         continue;
529
530                 if (!startswith(de->d_name, "ask."))
531                         continue;
532
533                 if (!(p = strappend("/run/systemd/ask-password/", de->d_name))) {
534                         log_error("Out of memory");
535                         r = -ENOMEM;
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("/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                "     --list     Show pending password requests\n"
641                "     --query    Process pending password requests\n"
642                "     --watch    Continuously process password requests\n"
643                "     --wall     Continuously forward password requests to wall\n"
644                "     --plymouth Ask question with Plymouth instead of on TTY\n"
645                "     --console  Ask question on /dev/console instead of current TTY\n",
646                program_invocation_short_name);
647
648         return 0;
649 }
650
651 static int parse_argv(int argc, char *argv[]) {
652
653         enum {
654                 ARG_LIST = 0x100,
655                 ARG_QUERY,
656                 ARG_WATCH,
657                 ARG_WALL,
658                 ARG_PLYMOUTH,
659                 ARG_CONSOLE
660         };
661
662         static const struct option options[] = {
663                 { "help",     no_argument, NULL, 'h'          },
664                 { "list",     no_argument, NULL, ARG_LIST     },
665                 { "query",    no_argument, NULL, ARG_QUERY    },
666                 { "watch",    no_argument, NULL, ARG_WATCH    },
667                 { "wall",     no_argument, NULL, ARG_WALL     },
668                 { "plymouth", no_argument, NULL, ARG_PLYMOUTH },
669                 { "console",  no_argument, NULL, ARG_CONSOLE  },
670                 { NULL,    0,           NULL, 0               }
671         };
672
673         int c;
674
675         assert(argc >= 0);
676         assert(argv);
677
678         while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
679
680                 switch (c) {
681
682                 case 'h':
683                         help();
684                         return 0;
685
686                 case ARG_LIST:
687                         arg_action = ACTION_LIST;
688                         break;
689
690                 case ARG_QUERY:
691                         arg_action = ACTION_QUERY;
692                         break;
693
694                 case ARG_WATCH:
695                         arg_action = ACTION_WATCH;
696                         break;
697
698                 case ARG_WALL:
699                         arg_action = ACTION_WALL;
700                         break;
701
702                 case ARG_PLYMOUTH:
703                         arg_plymouth = true;
704                         break;
705
706                 case ARG_CONSOLE:
707                         arg_console = true;
708                         break;
709
710                 case '?':
711                         return -EINVAL;
712
713                 default:
714                         log_error("Unknown option code %c", c);
715                         return -EINVAL;
716                 }
717         }
718
719         if (optind != argc) {
720                 help();
721                 return -EINVAL;
722         }
723
724         return 1;
725 }
726
727 int main(int argc, char *argv[]) {
728         int r;
729
730         log_parse_environment();
731         log_open();
732
733         umask(0022);
734
735         if ((r = parse_argv(argc, argv)) <= 0)
736                 goto finish;
737
738         if (arg_console) {
739                 setsid();
740                 release_terminal();
741         }
742
743         if (arg_action == ACTION_WATCH ||
744             arg_action == ACTION_WALL)
745                 r = watch_passwords();
746         else
747                 r = show_passwords();
748
749         if (r < 0)
750                 log_error("Error: %s", strerror(-r));
751
752 finish:
753         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
754 }