]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/tty-ask-password-agent.c
tmpfiles: kill double slashes in unix socket names, just in case
[thirdparty/systemd.git] / src / tty-ask-password-agent.c
CommitLineData
ec863ba6
LP
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>
b9ba604e 32#include <sys/signalfd.h>
7af53310 33#include <fcntl.h>
ec863ba6
LP
34
35#include "util.h"
36#include "conf-parser.h"
37#include "utmp-wtmp.h"
e5ebf783 38#include "socket-util.h"
7f4e0805 39#include "ask-password-api.h"
ec863ba6
LP
40
41static enum {
42 ACTION_LIST,
43 ACTION_QUERY,
44 ACTION_WATCH,
45 ACTION_WALL
46} arg_action = ACTION_QUERY;
47
e5ebf783 48static bool arg_plymouth = false;
0cf84693 49static bool arg_console = false;
e5ebf783
LP
50
51static int ask_password_plymouth(const char *message, usec_t until, const char *flag_file, char **_passphrase) {
52 int fd = -1, notify = -1;
53 union sockaddr_union sa;
54 char *packet = NULL;
55 ssize_t k;
56 int r, n;
57 struct pollfd pollfd[2];
58 char buffer[LINE_MAX];
59 size_t p = 0;
60 enum {
61 POLL_SOCKET,
62 POLL_INOTIFY
63 };
64
65 if (flag_file) {
66 if ((notify = inotify_init1(IN_CLOEXEC|IN_NONBLOCK)) < 0) {
67 r = -errno;
68 goto finish;
69 }
70
71 if (inotify_add_watch(notify, flag_file, IN_ATTRIB /* for the link count */) < 0) {
72 r = -errno;
73 goto finish;
74 }
75 }
76
77 if ((fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0)) < 0) {
78 r = -errno;
79 goto finish;
80 }
81
82 zero(sa);
83 sa.sa.sa_family = AF_UNIX;
96707269
LP
84 strncpy(sa.un.sun_path+1, "/org/freedesktop/plymouthd", sizeof(sa.un.sun_path)-1);
85 if (connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + 1 + strlen(sa.un.sun_path+1)) < 0) {
7f6d6135 86 log_error("FAILED TO CONNECT: %m");
e5ebf783
LP
87 r = -errno;
88 goto finish;
89 }
90
91 if (asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1), message, &n) < 0) {
92 r = -ENOMEM;
93 goto finish;
94 }
95
96 if ((k = loop_write(fd, packet, n+1, true)) != n+1) {
97 r = k < 0 ? (int) k : -EIO;
98 goto finish;
99 }
100
101 zero(pollfd);
102 pollfd[POLL_SOCKET].fd = fd;
103 pollfd[POLL_SOCKET].events = POLLIN;
104 pollfd[POLL_INOTIFY].fd = notify;
105 pollfd[POLL_INOTIFY].events = POLLIN;
106
107 for (;;) {
108 int sleep_for = -1, j;
109
110 if (until > 0) {
111 usec_t y;
112
113 y = now(CLOCK_MONOTONIC);
114
115 if (y > until) {
116 r = -ETIMEDOUT;
117 goto finish;
118 }
119
120 sleep_for = (int) ((until - y) / USEC_PER_MSEC);
121 }
122
123 if (flag_file)
124 if (access(flag_file, F_OK) < 0) {
125 r = -errno;
126 goto finish;
127 }
128
129 if ((j = poll(pollfd, notify > 0 ? 2 : 1, sleep_for)) < 0) {
130
131 if (errno == EINTR)
132 continue;
133
134 r = -errno;
135 goto finish;
136 } else if (j == 0) {
137 r = -ETIMEDOUT;
138 goto finish;
139 }
140
141 if (notify > 0 && pollfd[POLL_INOTIFY].revents != 0)
142 flush_fd(notify);
143
144 if (pollfd[POLL_SOCKET].revents == 0)
145 continue;
146
147 if ((k = read(fd, buffer + p, sizeof(buffer) - p)) <= 0) {
148 r = k < 0 ? -errno : -EIO;
149 goto finish;
150 }
151
152 p += k;
153
154 if (p < 1)
155 continue;
156
157 if (buffer[0] == 5) {
158 /* No password, because UI not shown */
159 r = -ENOENT;
160 goto finish;
161
162 } else if (buffer[0] == 2) {
163 uint32_t size;
164 char *s;
165
166 /* One answer */
167 if (p < 5)
168 continue;
169
170 memcpy(&size, buffer+1, sizeof(size));
171 if (size+5 > sizeof(buffer)) {
172 r = -EIO;
173 goto finish;
174 }
175
176 if (p-5 < size)
177 continue;
178
179 if (!(s = strndup(buffer + 5, size))) {
180 r = -ENOMEM;
181 goto finish;
182 }
183
184 *_passphrase = s;
185 break;
186 } else {
187 /* Unknown packet */
188 r = -EIO;
189 goto finish;
190 }
191 }
192
193 r = 0;
194
195finish:
196 if (notify >= 0)
197 close_nointr_nofail(notify);
198
199 if (fd >= 0)
200 close_nointr_nofail(fd);
201
202 free(packet);
203
204 return r;
205}
206
0ddf1d3a 207static int parse_password(const char *filename, char **wall) {
ec863ba6
LP
208 char *socket_name = NULL, *message = NULL, *packet = NULL;
209 uint64_t not_after = 0;
210 unsigned pid = 0;
211 int socket_fd = -1;
212
213 const ConfigItem items[] = {
214 { "Socket", config_parse_string, &socket_name, "Ask" },
215 { "NotAfter", config_parse_uint64, &not_after, "Ask" },
216 { "Message", config_parse_string, &message, "Ask" },
217 { "PID", config_parse_unsigned, &pid, "Ask" },
53d3afa8 218 { NULL, NULL, NULL, NULL }
ec863ba6
LP
219 };
220
221 FILE *f;
222 int r;
223 usec_t n;
224
225 assert(filename);
226
227 if (!(f = fopen(filename, "re"))) {
228
229 if (errno == ENOENT)
230 return 0;
231
232 log_error("open(%s): %m", filename);
233 return -errno;
234 }
235
656ce8f7 236 if ((r = config_parse(filename, f, NULL, items, true, NULL)) < 0) {
ec863ba6
LP
237 log_error("Failed to parse password file %s: %s", filename, strerror(-r));
238 goto finish;
239 }
240
241 if (!socket_name || not_after <= 0) {
242 log_error("Invalid password file %s", filename);
243 r = -EBADMSG;
244 goto finish;
245 }
246
247 n = now(CLOCK_MONOTONIC);
248 if (n > not_after) {
249 r = 0;
250 goto finish;
251 }
252
253 if (arg_action == ACTION_LIST)
254 printf("'%s' (PID %u)\n", message, pid);
255 else if (arg_action == ACTION_WALL) {
0ddf1d3a 256 char *_wall;
ec863ba6 257
0ddf1d3a
LP
258 if (asprintf(&_wall,
259 "%s%sPassword entry required for \'%s\' (PID %u).\r\n"
9d3e691e 260 "Please enter password with the systemd-tty-ask-password-agent tool!",
0ddf1d3a
LP
261 *wall ? *wall : "",
262 *wall ? "\r\n\r\n" : "",
ec863ba6
LP
263 message,
264 pid) < 0) {
265 log_error("Out of memory");
266 r = -ENOMEM;
267 goto finish;
268 }
269
0ddf1d3a
LP
270 free(*wall);
271 *wall = _wall;
ec863ba6
LP
272 } else {
273 union {
274 struct sockaddr sa;
275 struct sockaddr_un un;
276 } sa;
277 char *password;
278
279 assert(arg_action == ACTION_QUERY ||
280 arg_action == ACTION_WATCH);
281
282 if (access(socket_name, W_OK) < 0) {
283
284 if (arg_action == ACTION_QUERY)
285 log_info("Not querying '%s' (PID %u), lacking privileges.", message, pid);
286
287 r = 0;
288 goto finish;
289 }
290
e5ebf783
LP
291 if (arg_plymouth)
292 r = ask_password_plymouth(message, not_after, filename, &password);
0cf84693
LP
293 else {
294 int tty_fd = -1;
295
296 if (arg_console)
297 if ((tty_fd = acquire_terminal("/dev/console", false, false, false)) < 0) {
298 r = tty_fd;
299 goto finish;
300 }
301
e5ebf783
LP
302 r = ask_password_tty(message, not_after, filename, &password);
303
0cf84693
LP
304 if (arg_console) {
305 close_nointr_nofail(tty_fd);
306 release_terminal();
307 }
308 }
309
e5ebf783
LP
310 if (r < 0) {
311 log_error("Failed to query password: %s", strerror(-r));
ec863ba6
LP
312 goto finish;
313 }
314
315 asprintf(&packet, "+%s", password);
316 free(password);
317
318 if (!packet) {
319 log_error("Out of memory");
320 r = -ENOMEM;
321 goto finish;
322 }
323
324 if ((socket_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0)) < 0) {
325 log_error("socket(): %m");
326 r = -errno;
327 goto finish;
328 }
329
330 zero(sa);
331 sa.un.sun_family = AF_UNIX;
332 strncpy(sa.un.sun_path, socket_name, sizeof(sa.un.sun_path));
333
334 if (sendto(socket_fd, packet, strlen(packet), MSG_NOSIGNAL, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(socket_name)) < 0) {
335 log_error("Failed to send: %m");
336 r = -errno;
337 goto finish;
338 }
339 }
340
341finish:
342 fclose(f);
343
344 if (socket_fd >= 0)
345 close_nointr_nofail(socket_fd);
346
347 free(packet);
348 free(socket_name);
349 free(message);
350
351 return r;
352}
353
0cf84693 354static int wall_tty_block(void) {
7af53310
LP
355 char *p;
356 const char *t;
357 int fd;
358
359 if (!(t = ttyname(STDIN_FILENO)))
360 return -errno;
361
362 if (asprintf(&p, "/dev/.systemd/ask-password-block/%s", file_name_from_path(t)) < 0)
363 return -ENOMEM;
364
365 mkdir_parents(p, 0700);
366 mkfifo(p, 0600);
367
368 fd = open(p, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
369 free(p);
370
371 if (fd < 0)
372 return -errno;
373
374 return fd;
375}
376
0cf84693 377static bool wall_tty_match(const char *path) {
7af53310
LP
378 int fd;
379 char *p;
380
381 /* We use named pipes to ensure that wall messages suggesting
382 * password entry are not printed over password prompts
383 * already shown. We use the fact here that opening a pipe in
384 * non-blocking mode for write-only will succeed only if
385 * there's some writer behind it. Using pipes has the
386 * advantage that the block will automatically go away if the
387 * process dies. */
388
389 if (asprintf(&p, "/dev/.systemd/ask-password-block/%s", file_name_from_path(path)) < 0)
390 return true;
391
392 fd = open(p, O_WRONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY);
393 free(p);
394
395 if (fd < 0)
396 return true;
397
398 /* What, we managed to open the pipe? Then this tty is filtered. */
399 close_nointr_nofail(fd);
400 return false;
401}
402
ec863ba6
LP
403static int show_passwords(void) {
404 DIR *d;
405 struct dirent *de;
406 int r = 0;
407
408 if (!(d = opendir("/dev/.systemd/ask-password"))) {
409 if (errno == ENOENT)
410 return 0;
411
412 log_error("opendir(): %m");
413 return -errno;
414 }
415
416 while ((de = readdir(d))) {
417 char *p;
418 int q;
0ddf1d3a 419 char *wall;
ec863ba6
LP
420
421 if (de->d_type != DT_REG)
422 continue;
423
424 if (ignore_file(de->d_name))
425 continue;
426
427 if (!startswith(de->d_name, "ask."))
428 continue;
429
430 if (!(p = strappend("/dev/.systemd/ask-password/", de->d_name))) {
431 log_error("Out of memory");
432 r = -ENOMEM;
433 goto finish;
434 }
435
0ddf1d3a
LP
436 wall = NULL;
437 if ((q = parse_password(p, &wall)) < 0)
ec863ba6
LP
438 r = q;
439
440 free(p);
0ddf1d3a
LP
441
442 if (wall) {
0cf84693 443 utmp_wall(wall, wall_tty_match);
0ddf1d3a
LP
444 free(wall);
445 }
ec863ba6
LP
446 }
447
448finish:
449 if (d)
450 closedir(d);
451
452 return r;
453}
454
455static int watch_passwords(void) {
b9ba604e
LP
456 enum {
457 FD_INOTIFY,
458 FD_SIGNAL,
459 _FD_MAX
460 };
461
7af53310 462 int notify = -1, signal_fd = -1, tty_block_fd = -1;
b9ba604e
LP
463 struct pollfd pollfd[_FD_MAX];
464 sigset_t mask;
ec863ba6
LP
465 int r;
466
0cf84693 467 tty_block_fd = wall_tty_block();
7af53310 468
ec863ba6
LP
469 mkdir_p("/dev/.systemd/ask-password", 0755);
470
471 if ((notify = inotify_init1(IN_CLOEXEC)) < 0) {
472 r = -errno;
473 goto finish;
474 }
475
476 if (inotify_add_watch(notify, "/dev/.systemd/ask-password", IN_CLOSE_WRITE|IN_MOVED_TO) < 0) {
477 r = -errno;
478 goto finish;
479 }
480
b9ba604e
LP
481 assert_se(sigemptyset(&mask) == 0);
482 sigset_add_many(&mask, SIGINT, SIGTERM, -1);
483 assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0);
484
485 if ((signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC)) < 0) {
486 log_error("signalfd(): %m");
487 r = -errno;
488 goto finish;
489 }
490
ec863ba6 491 zero(pollfd);
b9ba604e
LP
492 pollfd[FD_INOTIFY].fd = notify;
493 pollfd[FD_INOTIFY].events = POLLIN;
494 pollfd[FD_SIGNAL].fd = signal_fd;
495 pollfd[FD_SIGNAL].events = POLLIN;
ec863ba6
LP
496
497 for (;;) {
498 if ((r = show_passwords()) < 0)
0cf84693 499 log_error("Failed to show password: %s", strerror(-r));
ec863ba6 500
b9ba604e 501 if (poll(pollfd, _FD_MAX, -1) < 0) {
ec863ba6
LP
502
503 if (errno == EINTR)
504 continue;
505
506 r = -errno;
507 goto finish;
508 }
509
b9ba604e 510 if (pollfd[FD_INOTIFY].revents != 0)
ec863ba6 511 flush_fd(notify);
b9ba604e
LP
512
513 if (pollfd[FD_SIGNAL].revents != 0)
514 break;
ec863ba6
LP
515 }
516
517 r = 0;
518
519finish:
520 if (notify >= 0)
521 close_nointr_nofail(notify);
522
b9ba604e
LP
523 if (signal_fd >= 0)
524 close_nointr_nofail(signal_fd);
525
7af53310
LP
526 if (tty_block_fd >= 0)
527 close_nointr_nofail(tty_block_fd);
528
ec863ba6
LP
529 return r;
530}
531
532static int help(void) {
533
534 printf("%s [OPTIONS...]\n\n"
535 "Process system password requests.\n\n"
e5ebf783
LP
536 " -h --help Show this help\n"
537 " --list Show pending password requests\n"
538 " --query Process pending password requests\n"
539 " --watch Continously process password requests\n"
540 " --wall Continously forward password requests to wall\n"
0cf84693
LP
541 " --plymouth Ask question with Plymouth instead of on TTY\n"
542 " --console Ask question on /dev/console instead of current TTY\n",
ec863ba6
LP
543 program_invocation_short_name);
544
545 return 0;
546}
547
548static int parse_argv(int argc, char *argv[]) {
549
550 enum {
551 ARG_LIST = 0x100,
552 ARG_QUERY,
553 ARG_WATCH,
554 ARG_WALL,
0cf84693
LP
555 ARG_PLYMOUTH,
556 ARG_CONSOLE
ec863ba6
LP
557 };
558
559 static const struct option options[] = {
e5ebf783
LP
560 { "help", no_argument, NULL, 'h' },
561 { "list", no_argument, NULL, ARG_LIST },
562 { "query", no_argument, NULL, ARG_QUERY },
563 { "watch", no_argument, NULL, ARG_WATCH },
564 { "wall", no_argument, NULL, ARG_WALL },
565 { "plymouth", no_argument, NULL, ARG_PLYMOUTH },
0cf84693 566 { "console", no_argument, NULL, ARG_CONSOLE },
e5ebf783 567 { NULL, 0, NULL, 0 }
ec863ba6
LP
568 };
569
570 int c;
571
572 assert(argc >= 0);
573 assert(argv);
574
575 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
576
577 switch (c) {
578
579 case 'h':
580 help();
581 return 0;
582
583 case ARG_LIST:
584 arg_action = ACTION_LIST;
585 break;
586
587 case ARG_QUERY:
588 arg_action = ACTION_QUERY;
589 break;
590
591 case ARG_WATCH:
592 arg_action = ACTION_WATCH;
593 break;
594
595 case ARG_WALL:
596 arg_action = ACTION_WALL;
597 break;
598
e5ebf783
LP
599 case ARG_PLYMOUTH:
600 arg_plymouth = true;
601 break;
602
0cf84693
LP
603 case ARG_CONSOLE:
604 arg_console = true;
605 break;
606
ec863ba6
LP
607 case '?':
608 return -EINVAL;
609
610 default:
611 log_error("Unknown option code %c", c);
612 return -EINVAL;
613 }
614 }
615
616 if (optind != argc) {
617 help();
618 return -EINVAL;
619 }
620
621 return 1;
622}
623
624int main(int argc, char *argv[]) {
625 int r;
626
627 log_parse_environment();
628 log_open();
629
630 if ((r = parse_argv(argc, argv)) <= 0)
631 goto finish;
632
0cf84693
LP
633 if (arg_console) {
634 setsid();
635 release_terminal();
636 }
637
ec863ba6
LP
638 if (arg_action == ACTION_WATCH ||
639 arg_action == ACTION_WALL)
640 r = watch_passwords();
641 else
642 r = show_passwords();
643
96707269
LP
644 if (r < 0)
645 log_error("Error: %s", strerror(-r));
646
ec863ba6
LP
647finish:
648 return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
649}