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