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