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