]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/shared/ask-password-api.c
Merge pull request #208 from poettering/btrfs-rec-snapshot
[thirdparty/systemd.git] / src / shared / ask-password-api.c
CommitLineData
7f4e0805
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
5430f7f2
LP
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
7f4e0805
LP
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
5430f7f2 16 Lesser General Public License for more details.
7f4e0805 17
5430f7f2 18 You should have received a copy of the GNU Lesser General Public License
7f4e0805
LP
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20***/
d1676230 21#include <stdbool.h>
7f4e0805
LP
22#include <termios.h>
23#include <unistd.h>
0a6f50c0 24#include <poll.h>
7f4e0805
LP
25#include <sys/inotify.h>
26#include <errno.h>
27#include <fcntl.h>
28#include <sys/socket.h>
29#include <string.h>
30#include <sys/un.h>
31#include <stddef.h>
32#include <sys/signalfd.h>
33
34#include "util.h"
6482f626 35#include "formats-util.h"
49e942b2 36#include "mkdir.h"
21bc923a 37#include "strv.h"
3df3e884 38#include "random-util.h"
288a74cc 39#include "terminal-util.h"
24882e06 40#include "signal-util.h"
7f4e0805
LP
41#include "ask-password-api.h"
42
58fc840b
LP
43static void backspace_chars(int ttyfd, size_t p) {
44
45 if (ttyfd < 0)
46 return;
47
48 while (p > 0) {
49 p--;
50
51 loop_write(ttyfd, "\b \b", 3, false);
52 }
53}
54
7f4e0805
LP
55int ask_password_tty(
56 const char *message,
57 usec_t until,
64845bdc 58 bool echo,
7f4e0805
LP
59 const char *flag_file,
60 char **_passphrase) {
61
62 struct termios old_termios, new_termios;
981e4cd3 63 char passphrase[LINE_MAX], *x;
7f4e0805 64 size_t p = 0;
981e4cd3
LP
65 int r;
66 _cleanup_close_ int ttyfd = -1, notify = -1;
7f4e0805
LP
67 struct pollfd pollfd[2];
68 bool reset_tty = false;
d1676230 69 bool silent_mode = false;
441dfe09 70 bool dirty = false;
7f4e0805
LP
71 enum {
72 POLL_TTY,
73 POLL_INOTIFY
74 };
75
76 assert(message);
77 assert(_passphrase);
78
79 if (flag_file) {
981e4cd3
LP
80 notify = inotify_init1(IN_CLOEXEC|IN_NONBLOCK);
81 if (notify < 0) {
7f4e0805
LP
82 r = -errno;
83 goto finish;
84 }
85
86 if (inotify_add_watch(notify, flag_file, IN_ATTRIB /* for the link count */) < 0) {
87 r = -errno;
88 goto finish;
89 }
90 }
91
981e4cd3
LP
92 ttyfd = open("/dev/tty", O_RDWR|O_NOCTTY|O_CLOEXEC);
93 if (ttyfd >= 0) {
7f4e0805
LP
94
95 if (tcgetattr(ttyfd, &old_termios) < 0) {
96 r = -errno;
97 goto finish;
98 }
99
c1072ea0 100 loop_write(ttyfd, ANSI_HIGHLIGHT_ON, sizeof(ANSI_HIGHLIGHT_ON)-1, false);
7f4e0805 101 loop_write(ttyfd, message, strlen(message), false);
bd1db33f 102 loop_write(ttyfd, " ", 1, false);
c1072ea0 103 loop_write(ttyfd, ANSI_HIGHLIGHT_OFF, sizeof(ANSI_HIGHLIGHT_OFF)-1, false);
7f4e0805
LP
104
105 new_termios = old_termios;
106 new_termios.c_lflag &= ~(ICANON|ECHO);
107 new_termios.c_cc[VMIN] = 1;
108 new_termios.c_cc[VTIME] = 0;
109
110 if (tcsetattr(ttyfd, TCSADRAIN, &new_termios) < 0) {
111 r = -errno;
112 goto finish;
113 }
114
115 reset_tty = true;
116 }
117
118 zero(pollfd);
7f4e0805
LP
119 pollfd[POLL_TTY].fd = ttyfd >= 0 ? ttyfd : STDIN_FILENO;
120 pollfd[POLL_TTY].events = POLLIN;
121 pollfd[POLL_INOTIFY].fd = notify;
122 pollfd[POLL_INOTIFY].events = POLLIN;
123
124 for (;;) {
125 char c;
126 int sleep_for = -1, k;
127 ssize_t n;
128
129 if (until > 0) {
130 usec_t y;
131
132 y = now(CLOCK_MONOTONIC);
133
134 if (y > until) {
7ded2e28 135 r = -ETIME;
7f4e0805
LP
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
981e4cd3
LP
148 k = poll(pollfd, notify > 0 ? 2 : 1, sleep_for);
149 if (k < 0) {
7f4e0805
LP
150 if (errno == EINTR)
151 continue;
152
153 r = -errno;
154 goto finish;
155 } else if (k == 0) {
ccc80078 156 r = -ETIME;
7f4e0805
LP
157 goto finish;
158 }
159
160 if (notify > 0 && pollfd[POLL_INOTIFY].revents != 0)
161 flush_fd(notify);
162
163 if (pollfd[POLL_TTY].revents == 0)
164 continue;
165
981e4cd3
LP
166 n = read(ttyfd >= 0 ? ttyfd : STDIN_FILENO, &c, 1);
167 if (n < 0) {
7f4e0805
LP
168 if (errno == EINTR || errno == EAGAIN)
169 continue;
170
171 r = -errno;
172 goto finish;
173
174 } else if (n == 0)
175 break;
176
177 if (c == '\n')
178 break;
58fc840b 179 else if (c == 21) { /* C-u */
7f4e0805 180
58fc840b
LP
181 if (!silent_mode)
182 backspace_chars(ttyfd, p);
183 p = 0;
7f4e0805
LP
184
185 } else if (c == '\b' || c == 127) {
58fc840b
LP
186
187 if (p > 0) {
188
189 if (!silent_mode)
190 backspace_chars(ttyfd, 1);
191
7f4e0805 192 p--;
441dfe09
LP
193 } else if (!dirty && !silent_mode) {
194
195 silent_mode = true;
196
197 /* There are two ways to enter silent
198 * mode. Either by pressing backspace
199 * as first key (and only as first key),
200 * or ... */
201 if (ttyfd >= 0)
202 loop_write(ttyfd, "(no echo) ", 10, false);
203
58fc840b
LP
204 } else if (ttyfd >= 0)
205 loop_write(ttyfd, "\a", 1, false);
7f4e0805 206
58fc840b
LP
207 } else if (c == '\t' && !silent_mode) {
208
209 backspace_chars(ttyfd, p);
210 silent_mode = true;
211
441dfe09
LP
212 /* ... or by pressing TAB at any time. */
213
58fc840b
LP
214 if (ttyfd >= 0)
215 loop_write(ttyfd, "(no echo) ", 10, false);
7f4e0805 216 } else {
036eeac5
LP
217 if (p >= sizeof(passphrase)-1) {
218 loop_write(ttyfd, "\a", 1, false);
219 continue;
220 }
221
7f4e0805
LP
222 passphrase[p++] = c;
223
d1676230 224 if (!silent_mode && ttyfd >= 0)
64845bdc 225 loop_write(ttyfd, echo ? &c : "*", 1, false);
441dfe09
LP
226
227 dirty = true;
7f4e0805
LP
228 }
229 }
230
981e4cd3
LP
231 x = strndup(passphrase, p);
232 if (!x) {
7f4e0805
LP
233 r = -ENOMEM;
234 goto finish;
235 }
236
981e4cd3 237 *_passphrase = x;
7f4e0805
LP
238 r = 0;
239
240finish:
981e4cd3
LP
241 if (ttyfd >= 0 && reset_tty) {
242 loop_write(ttyfd, "\n", 1, false);
243 tcsetattr(ttyfd, TCSADRAIN, &old_termios);
7f4e0805
LP
244 }
245
246 return r;
247}
248
249static int create_socket(char **name) {
250 int fd;
251 union {
252 struct sockaddr sa;
253 struct sockaddr_un un;
b92bea5d
ZJS
254 } sa = {
255 .un.sun_family = AF_UNIX,
256 };
df28bc08
KS
257 int one = 1;
258 int r = 0;
7f4e0805
LP
259 char *c;
260
261 assert(name);
262
e62d8c39 263 fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
4a62c710
MS
264 if (fd < 0)
265 return log_error_errno(errno, "socket() failed: %m");
7f4e0805 266
9bf3b535 267 snprintf(sa.un.sun_path, sizeof(sa.un.sun_path)-1, "/run/systemd/ask-password/sck.%" PRIx64, random_u64());
7f4e0805 268
5c0d398d
LP
269 RUN_WITH_UMASK(0177) {
270 r = bind(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path));
271 }
1d6702e8
LP
272
273 if (r < 0) {
7f4e0805 274 r = -errno;
56f64d95 275 log_error_errno(errno, "bind(%s) failed: %m", sa.un.sun_path);
7f4e0805
LP
276 goto fail;
277 }
278
279 if (setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)) < 0) {
280 r = -errno;
56f64d95 281 log_error_errno(errno, "SO_PASSCRED failed: %m");
7f4e0805
LP
282 goto fail;
283 }
284
e62d8c39
ZJS
285 c = strdup(sa.un.sun_path);
286 if (!c) {
0d0f0c50 287 r = log_oom();
7f4e0805
LP
288 goto fail;
289 }
290
291 *name = c;
292 return fd;
293
294fail:
03e334a1 295 safe_close(fd);
7f4e0805
LP
296
297 return r;
298}
299
7f4e0805
LP
300int ask_password_agent(
301 const char *message,
302 const char *icon,
9fa1de96 303 const char *id,
7f4e0805 304 usec_t until,
64845bdc 305 bool echo,
21bc923a
LP
306 bool accept_cached,
307 char ***_passphrases) {
7f4e0805
LP
308
309 enum {
310 FD_SOCKET,
311 FD_SIGNAL,
312 _FD_MAX
313 };
314
2b583ce6 315 char temp[] = "/run/systemd/ask-password/tmp.XXXXXX";
7f4e0805 316 char final[sizeof(temp)] = "";
981e4cd3
LP
317 _cleanup_fclose_ FILE *f = NULL;
318 _cleanup_free_ char *socket_name = NULL;
319 _cleanup_close_ int socket_fd = -1, signal_fd = -1, fd = -1;
f9b72cd8 320 sigset_t mask, oldmask;
7f4e0805 321 struct pollfd pollfd[_FD_MAX];
981e4cd3 322 int r;
7f4e0805 323
21bc923a
LP
324 assert(_passphrases);
325
f9b72cd8
LP
326 assert_se(sigemptyset(&mask) == 0);
327 sigset_add_many(&mask, SIGINT, SIGTERM, -1);
328 assert_se(sigprocmask(SIG_BLOCK, &mask, &oldmask) == 0);
329
d2e54fae 330 mkdir_p_label("/run/systemd/ask-password", 0755);
7f4e0805 331
2d5bdf5b 332 fd = mkostemp_safe(temp, O_WRONLY|O_CLOEXEC);
1d6702e8 333 if (fd < 0) {
56f64d95 334 log_error_errno(errno, "Failed to create password file: %m");
7f4e0805
LP
335 r = -errno;
336 goto finish;
337 }
338
339 fchmod(fd, 0644);
340
981e4cd3
LP
341 f = fdopen(fd, "w");
342 if (!f) {
56f64d95 343 log_error_errno(errno, "Failed to allocate FILE: %m");
7f4e0805
LP
344 r = -errno;
345 goto finish;
346 }
347
348 fd = -1;
349
981e4cd3
LP
350 signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC);
351 if (signal_fd < 0) {
56f64d95 352 log_error_errno(errno, "signalfd(): %m");
7f4e0805
LP
353 r = -errno;
354 goto finish;
355 }
356
981e4cd3
LP
357 socket_fd = create_socket(&socket_name);
358 if (socket_fd < 0) {
7f4e0805
LP
359 r = socket_fd;
360 goto finish;
361 }
362
363 fprintf(f,
364 "[Ask]\n"
de0671ee 365 "PID="PID_FMT"\n"
7f4e0805 366 "Socket=%s\n"
21bc923a 367 "AcceptCached=%i\n"
64845bdc 368 "Echo=%i\n"
de0671ee
ZJS
369 "NotAfter="USEC_FMT"\n",
370 getpid(),
7f4e0805 371 socket_name,
21bc923a 372 accept_cached ? 1 : 0,
64845bdc 373 echo ? 1 : 0,
de0671ee 374 until);
7f4e0805
LP
375
376 if (message)
377 fprintf(f, "Message=%s\n", message);
378
379 if (icon)
380 fprintf(f, "Icon=%s\n", icon);
381
9fa1de96
DH
382 if (id)
383 fprintf(f, "Id=%s\n", id);
384
7f4e0805
LP
385 fflush(f);
386
387 if (ferror(f)) {
56f64d95 388 log_error_errno(errno, "Failed to write query file: %m");
7f4e0805
LP
389 r = -errno;
390 goto finish;
391 }
392
393 memcpy(final, temp, sizeof(temp));
394
395 final[sizeof(final)-11] = 'a';
396 final[sizeof(final)-10] = 's';
397 final[sizeof(final)-9] = 'k';
398
399 if (rename(temp, final) < 0) {
56f64d95 400 log_error_errno(errno, "Failed to rename query file: %m");
7f4e0805
LP
401 r = -errno;
402 goto finish;
403 }
404
405 zero(pollfd);
406 pollfd[FD_SOCKET].fd = socket_fd;
407 pollfd[FD_SOCKET].events = POLLIN;
408 pollfd[FD_SIGNAL].fd = signal_fd;
409 pollfd[FD_SIGNAL].events = POLLIN;
410
411 for (;;) {
412 char passphrase[LINE_MAX+1];
413 struct msghdr msghdr;
414 struct iovec iovec;
415 struct ucred *ucred;
416 union {
417 struct cmsghdr cmsghdr;
418 uint8_t buf[CMSG_SPACE(sizeof(struct ucred))];
419 } control;
420 ssize_t n;
421 int k;
422 usec_t t;
423
424 t = now(CLOCK_MONOTONIC);
425
7dcda352 426 if (until > 0 && until <= t) {
7f4e0805
LP
427 log_notice("Timed out");
428 r = -ETIME;
429 goto finish;
430 }
431
981e4cd3
LP
432 k = poll(pollfd, _FD_MAX, until > 0 ? (int) ((until-t)/USEC_PER_MSEC) : -1);
433 if (k < 0) {
7f4e0805
LP
434 if (errno == EINTR)
435 continue;
436
56f64d95 437 log_error_errno(errno, "poll() failed: %m");
7f4e0805
LP
438 r = -errno;
439 goto finish;
440 }
441
442 if (k <= 0) {
443 log_notice("Timed out");
444 r = -ETIME;
445 goto finish;
446 }
447
21bc923a
LP
448 if (pollfd[FD_SIGNAL].revents & POLLIN) {
449 r = -EINTR;
450 goto finish;
451 }
7f4e0805
LP
452
453 if (pollfd[FD_SOCKET].revents != POLLIN) {
454 log_error("Unexpected poll() event.");
455 r = -EIO;
456 goto finish;
457 }
458
459 zero(iovec);
460 iovec.iov_base = passphrase;
21bc923a 461 iovec.iov_len = sizeof(passphrase);
7f4e0805
LP
462
463 zero(control);
464 zero(msghdr);
465 msghdr.msg_iov = &iovec;
466 msghdr.msg_iovlen = 1;
467 msghdr.msg_control = &control;
468 msghdr.msg_controllen = sizeof(control);
469
981e4cd3
LP
470 n = recvmsg(socket_fd, &msghdr, 0);
471 if (n < 0) {
7f4e0805
LP
472 if (errno == EAGAIN ||
473 errno == EINTR)
474 continue;
475
56f64d95 476 log_error_errno(errno, "recvmsg() failed: %m");
7f4e0805
LP
477 r = -errno;
478 goto finish;
479 }
480
1c8da044
LP
481 cmsg_close_all(&msghdr);
482
7f4e0805
LP
483 if (n <= 0) {
484 log_error("Message too short");
485 continue;
486 }
487
488 if (msghdr.msg_controllen < CMSG_LEN(sizeof(struct ucred)) ||
489 control.cmsghdr.cmsg_level != SOL_SOCKET ||
490 control.cmsghdr.cmsg_type != SCM_CREDENTIALS ||
491 control.cmsghdr.cmsg_len != CMSG_LEN(sizeof(struct ucred))) {
492 log_warning("Received message without credentials. Ignoring.");
493 continue;
494 }
495
496 ucred = (struct ucred*) CMSG_DATA(&control.cmsghdr);
497 if (ucred->uid != 0) {
498 log_warning("Got request from unprivileged user. Ignoring.");
499 continue;
500 }
501
502 if (passphrase[0] == '+') {
21bc923a 503 char **l;
7f4e0805 504
8254a475
LP
505 if (n == 1)
506 l = strv_new("", NULL);
507 else
508 l = strv_parse_nulstr(passphrase+1, n-1);
509 /* An empty message refers to the empty password */
510
511 if (!l) {
7f4e0805
LP
512 r = -ENOMEM;
513 goto finish;
514 }
515
21bc923a
LP
516 if (strv_length(l) <= 0) {
517 strv_free(l);
518 log_error("Invalid packet");
519 continue;
520 }
521
522 *_passphrases = l;
523
7f4e0805
LP
524 } else if (passphrase[0] == '-') {
525 r = -ECANCELED;
526 goto finish;
527 } else {
528 log_error("Invalid packet");
529 continue;
530 }
531
532 break;
533 }
534
535 r = 0;
536
537finish:
981e4cd3 538 if (socket_name)
7f4e0805 539 unlink(socket_name);
7f4e0805
LP
540
541 unlink(temp);
542
543 if (final[0])
544 unlink(final);
545
f9b72cd8
LP
546 assert_se(sigprocmask(SIG_SETMASK, &oldmask, NULL) == 0);
547
7f4e0805
LP
548 return r;
549}
260ab287 550
9fa1de96
DH
551int ask_password_auto(const char *message, const char *icon, const char *id,
552 usec_t until, bool accept_cached, char ***_passphrases) {
260ab287 553 assert(message);
21bc923a
LP
554 assert(_passphrases);
555
556 if (isatty(STDIN_FILENO)) {
557 int r;
558 char *s = NULL, **l = NULL;
559
64845bdc 560 r = ask_password_tty(message, until, false, NULL, &s);
981e4cd3 561 if (r < 0)
21bc923a
LP
562 return r;
563
981e4cd3
LP
564 r = strv_consume(&l, s);
565 if (r < 0)
566 return r;
21bc923a
LP
567
568 *_passphrases = l;
569 return r;
21bc923a 570 } else
64845bdc 571 return ask_password_agent(message, icon, id, until, false, accept_cached, _passphrases);
260ab287 572}