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