]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/shared/ask-password-api.c
tree-wide: port everything over to fflush_and_check()
[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
72c0a2c2
LP
326 assert_se(sigemptyset(&mask) >= 0);
327 assert_se(sigset_add_many(&mask, SIGINT, SIGTERM, -1) >= 0);
328 assert_se(sigprocmask(SIG_BLOCK, &mask, &oldmask) >= 0);
f9b72cd8 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
dacd6cee
LP
385 r = fflush_and_check(f);
386 if (r < 0) {
387 log_error_errno(r, "Failed to write query file: %m");
7f4e0805
LP
388 goto finish;
389 }
390
391 memcpy(final, temp, sizeof(temp));
392
393 final[sizeof(final)-11] = 'a';
394 final[sizeof(final)-10] = 's';
395 final[sizeof(final)-9] = 'k';
396
397 if (rename(temp, final) < 0) {
56f64d95 398 log_error_errno(errno, "Failed to rename query file: %m");
7f4e0805
LP
399 r = -errno;
400 goto finish;
401 }
402
403 zero(pollfd);
404 pollfd[FD_SOCKET].fd = socket_fd;
405 pollfd[FD_SOCKET].events = POLLIN;
406 pollfd[FD_SIGNAL].fd = signal_fd;
407 pollfd[FD_SIGNAL].events = POLLIN;
408
409 for (;;) {
410 char passphrase[LINE_MAX+1];
411 struct msghdr msghdr;
412 struct iovec iovec;
413 struct ucred *ucred;
414 union {
415 struct cmsghdr cmsghdr;
416 uint8_t buf[CMSG_SPACE(sizeof(struct ucred))];
417 } control;
418 ssize_t n;
419 int k;
420 usec_t t;
421
422 t = now(CLOCK_MONOTONIC);
423
7dcda352 424 if (until > 0 && until <= t) {
7f4e0805
LP
425 log_notice("Timed out");
426 r = -ETIME;
427 goto finish;
428 }
429
981e4cd3
LP
430 k = poll(pollfd, _FD_MAX, until > 0 ? (int) ((until-t)/USEC_PER_MSEC) : -1);
431 if (k < 0) {
7f4e0805
LP
432 if (errno == EINTR)
433 continue;
434
56f64d95 435 log_error_errno(errno, "poll() failed: %m");
7f4e0805
LP
436 r = -errno;
437 goto finish;
438 }
439
440 if (k <= 0) {
441 log_notice("Timed out");
442 r = -ETIME;
443 goto finish;
444 }
445
21bc923a
LP
446 if (pollfd[FD_SIGNAL].revents & POLLIN) {
447 r = -EINTR;
448 goto finish;
449 }
7f4e0805
LP
450
451 if (pollfd[FD_SOCKET].revents != POLLIN) {
452 log_error("Unexpected poll() event.");
453 r = -EIO;
454 goto finish;
455 }
456
457 zero(iovec);
458 iovec.iov_base = passphrase;
21bc923a 459 iovec.iov_len = sizeof(passphrase);
7f4e0805
LP
460
461 zero(control);
462 zero(msghdr);
463 msghdr.msg_iov = &iovec;
464 msghdr.msg_iovlen = 1;
465 msghdr.msg_control = &control;
466 msghdr.msg_controllen = sizeof(control);
467
981e4cd3
LP
468 n = recvmsg(socket_fd, &msghdr, 0);
469 if (n < 0) {
7f4e0805
LP
470 if (errno == EAGAIN ||
471 errno == EINTR)
472 continue;
473
56f64d95 474 log_error_errno(errno, "recvmsg() failed: %m");
7f4e0805
LP
475 r = -errno;
476 goto finish;
477 }
478
1c8da044
LP
479 cmsg_close_all(&msghdr);
480
7f4e0805
LP
481 if (n <= 0) {
482 log_error("Message too short");
483 continue;
484 }
485
486 if (msghdr.msg_controllen < CMSG_LEN(sizeof(struct ucred)) ||
487 control.cmsghdr.cmsg_level != SOL_SOCKET ||
488 control.cmsghdr.cmsg_type != SCM_CREDENTIALS ||
489 control.cmsghdr.cmsg_len != CMSG_LEN(sizeof(struct ucred))) {
490 log_warning("Received message without credentials. Ignoring.");
491 continue;
492 }
493
494 ucred = (struct ucred*) CMSG_DATA(&control.cmsghdr);
495 if (ucred->uid != 0) {
496 log_warning("Got request from unprivileged user. Ignoring.");
497 continue;
498 }
499
500 if (passphrase[0] == '+') {
21bc923a 501 char **l;
7f4e0805 502
8254a475
LP
503 if (n == 1)
504 l = strv_new("", NULL);
505 else
506 l = strv_parse_nulstr(passphrase+1, n-1);
507 /* An empty message refers to the empty password */
508
509 if (!l) {
7f4e0805
LP
510 r = -ENOMEM;
511 goto finish;
512 }
513
21bc923a
LP
514 if (strv_length(l) <= 0) {
515 strv_free(l);
516 log_error("Invalid packet");
517 continue;
518 }
519
520 *_passphrases = l;
521
7f4e0805
LP
522 } else if (passphrase[0] == '-') {
523 r = -ECANCELED;
524 goto finish;
525 } else {
526 log_error("Invalid packet");
527 continue;
528 }
529
530 break;
531 }
532
533 r = 0;
534
535finish:
981e4cd3 536 if (socket_name)
7f4e0805 537 unlink(socket_name);
7f4e0805
LP
538
539 unlink(temp);
540
541 if (final[0])
542 unlink(final);
543
f9b72cd8
LP
544 assert_se(sigprocmask(SIG_SETMASK, &oldmask, NULL) == 0);
545
7f4e0805
LP
546 return r;
547}
260ab287 548
9fa1de96
DH
549int ask_password_auto(const char *message, const char *icon, const char *id,
550 usec_t until, bool accept_cached, char ***_passphrases) {
260ab287 551 assert(message);
21bc923a
LP
552 assert(_passphrases);
553
554 if (isatty(STDIN_FILENO)) {
555 int r;
556 char *s = NULL, **l = NULL;
557
64845bdc 558 r = ask_password_tty(message, until, false, NULL, &s);
981e4cd3 559 if (r < 0)
21bc923a
LP
560 return r;
561
981e4cd3
LP
562 r = strv_consume(&l, s);
563 if (r < 0)
564 return r;
21bc923a
LP
565
566 *_passphrases = l;
567 return r;
21bc923a 568 } else
64845bdc 569 return ask_password_agent(message, icon, id, until, false, accept_cached, _passphrases);
260ab287 570}