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