]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/ask-password-api.c
Merge pull request #485 from poettering/sd-bus-flush-close-unref
[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 #include "terminal-util.h"
40 #include "signal-util.h"
41 #include "ask-password-api.h"
42
43 static 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
55 int ask_password_tty(
56 const char *message,
57 usec_t until,
58 bool echo,
59 const char *flag_file,
60 char **_passphrase) {
61
62 struct termios old_termios, new_termios;
63 char passphrase[LINE_MAX], *x;
64 size_t p = 0;
65 int r;
66 _cleanup_close_ int ttyfd = -1, notify = -1;
67 struct pollfd pollfd[2];
68 bool reset_tty = false;
69 bool silent_mode = false;
70 bool dirty = false;
71 enum {
72 POLL_TTY,
73 POLL_INOTIFY
74 };
75
76 assert(message);
77 assert(_passphrase);
78
79 if (flag_file) {
80 notify = inotify_init1(IN_CLOEXEC|IN_NONBLOCK);
81 if (notify < 0) {
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
92 ttyfd = open("/dev/tty", O_RDWR|O_NOCTTY|O_CLOEXEC);
93 if (ttyfd >= 0) {
94
95 if (tcgetattr(ttyfd, &old_termios) < 0) {
96 r = -errno;
97 goto finish;
98 }
99
100 loop_write(ttyfd, ANSI_HIGHLIGHT_ON, sizeof(ANSI_HIGHLIGHT_ON)-1, false);
101 loop_write(ttyfd, message, strlen(message), false);
102 loop_write(ttyfd, " ", 1, false);
103 loop_write(ttyfd, ANSI_HIGHLIGHT_OFF, sizeof(ANSI_HIGHLIGHT_OFF)-1, false);
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);
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) {
135 r = -ETIME;
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
148 k = poll(pollfd, notify > 0 ? 2 : 1, sleep_for);
149 if (k < 0) {
150 if (errno == EINTR)
151 continue;
152
153 r = -errno;
154 goto finish;
155 } else if (k == 0) {
156 r = -ETIME;
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
166 n = read(ttyfd >= 0 ? ttyfd : STDIN_FILENO, &c, 1);
167 if (n < 0) {
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;
179 else if (c == 21) { /* C-u */
180
181 if (!silent_mode)
182 backspace_chars(ttyfd, p);
183 p = 0;
184
185 } else if (c == '\b' || c == 127) {
186
187 if (p > 0) {
188
189 if (!silent_mode)
190 backspace_chars(ttyfd, 1);
191
192 p--;
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
204 } else if (ttyfd >= 0)
205 loop_write(ttyfd, "\a", 1, false);
206
207 } else if (c == '\t' && !silent_mode) {
208
209 backspace_chars(ttyfd, p);
210 silent_mode = true;
211
212 /* ... or by pressing TAB at any time. */
213
214 if (ttyfd >= 0)
215 loop_write(ttyfd, "(no echo) ", 10, false);
216 } else {
217 if (p >= sizeof(passphrase)-1) {
218 loop_write(ttyfd, "\a", 1, false);
219 continue;
220 }
221
222 passphrase[p++] = c;
223
224 if (!silent_mode && ttyfd >= 0)
225 loop_write(ttyfd, echo ? &c : "*", 1, false);
226
227 dirty = true;
228 }
229 }
230
231 x = strndup(passphrase, p);
232 if (!x) {
233 r = -ENOMEM;
234 goto finish;
235 }
236
237 *_passphrase = x;
238 r = 0;
239
240 finish:
241 if (ttyfd >= 0 && reset_tty) {
242 loop_write(ttyfd, "\n", 1, false);
243 tcsetattr(ttyfd, TCSADRAIN, &old_termios);
244 }
245
246 return r;
247 }
248
249 static int create_socket(char **name) {
250 int fd;
251 union {
252 struct sockaddr sa;
253 struct sockaddr_un un;
254 } sa = {
255 .un.sun_family = AF_UNIX,
256 };
257 int one = 1;
258 int r = 0;
259 char *c;
260
261 assert(name);
262
263 fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
264 if (fd < 0)
265 return log_error_errno(errno, "socket() failed: %m");
266
267 snprintf(sa.un.sun_path, sizeof(sa.un.sun_path)-1, "/run/systemd/ask-password/sck.%" PRIx64, random_u64());
268
269 RUN_WITH_UMASK(0177) {
270 r = bind(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path));
271 }
272
273 if (r < 0) {
274 r = -errno;
275 log_error_errno(errno, "bind(%s) failed: %m", sa.un.sun_path);
276 goto fail;
277 }
278
279 if (setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)) < 0) {
280 r = -errno;
281 log_error_errno(errno, "SO_PASSCRED failed: %m");
282 goto fail;
283 }
284
285 c = strdup(sa.un.sun_path);
286 if (!c) {
287 r = log_oom();
288 goto fail;
289 }
290
291 *name = c;
292 return fd;
293
294 fail:
295 safe_close(fd);
296
297 return r;
298 }
299
300 int ask_password_agent(
301 const char *message,
302 const char *icon,
303 const char *id,
304 usec_t until,
305 bool echo,
306 bool accept_cached,
307 char ***_passphrases) {
308
309 enum {
310 FD_SOCKET,
311 FD_SIGNAL,
312 _FD_MAX
313 };
314
315 char temp[] = "/run/systemd/ask-password/tmp.XXXXXX";
316 char final[sizeof(temp)] = "";
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;
320 sigset_t mask, oldmask;
321 struct pollfd pollfd[_FD_MAX];
322 int r;
323
324 assert(_passphrases);
325
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);
329
330 mkdir_p_label("/run/systemd/ask-password", 0755);
331
332 fd = mkostemp_safe(temp, O_WRONLY|O_CLOEXEC);
333 if (fd < 0) {
334 log_error_errno(errno, "Failed to create password file: %m");
335 r = -errno;
336 goto finish;
337 }
338
339 fchmod(fd, 0644);
340
341 f = fdopen(fd, "w");
342 if (!f) {
343 log_error_errno(errno, "Failed to allocate FILE: %m");
344 r = -errno;
345 goto finish;
346 }
347
348 fd = -1;
349
350 signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC);
351 if (signal_fd < 0) {
352 log_error_errno(errno, "signalfd(): %m");
353 r = -errno;
354 goto finish;
355 }
356
357 socket_fd = create_socket(&socket_name);
358 if (socket_fd < 0) {
359 r = socket_fd;
360 goto finish;
361 }
362
363 fprintf(f,
364 "[Ask]\n"
365 "PID="PID_FMT"\n"
366 "Socket=%s\n"
367 "AcceptCached=%i\n"
368 "Echo=%i\n"
369 "NotAfter="USEC_FMT"\n",
370 getpid(),
371 socket_name,
372 accept_cached ? 1 : 0,
373 echo ? 1 : 0,
374 until);
375
376 if (message)
377 fprintf(f, "Message=%s\n", message);
378
379 if (icon)
380 fprintf(f, "Icon=%s\n", icon);
381
382 if (id)
383 fprintf(f, "Id=%s\n", id);
384
385 fflush(f);
386
387 if (ferror(f)) {
388 log_error_errno(errno, "Failed to write query file: %m");
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) {
400 log_error_errno(errno, "Failed to rename query file: %m");
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
426 if (until > 0 && until <= t) {
427 log_notice("Timed out");
428 r = -ETIME;
429 goto finish;
430 }
431
432 k = poll(pollfd, _FD_MAX, until > 0 ? (int) ((until-t)/USEC_PER_MSEC) : -1);
433 if (k < 0) {
434 if (errno == EINTR)
435 continue;
436
437 log_error_errno(errno, "poll() failed: %m");
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
448 if (pollfd[FD_SIGNAL].revents & POLLIN) {
449 r = -EINTR;
450 goto finish;
451 }
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;
461 iovec.iov_len = sizeof(passphrase);
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
470 n = recvmsg(socket_fd, &msghdr, 0);
471 if (n < 0) {
472 if (errno == EAGAIN ||
473 errno == EINTR)
474 continue;
475
476 log_error_errno(errno, "recvmsg() failed: %m");
477 r = -errno;
478 goto finish;
479 }
480
481 cmsg_close_all(&msghdr);
482
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] == '+') {
503 char **l;
504
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) {
512 r = -ENOMEM;
513 goto finish;
514 }
515
516 if (strv_length(l) <= 0) {
517 strv_free(l);
518 log_error("Invalid packet");
519 continue;
520 }
521
522 *_passphrases = l;
523
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
537 finish:
538 if (socket_name)
539 unlink(socket_name);
540
541 unlink(temp);
542
543 if (final[0])
544 unlink(final);
545
546 assert_se(sigprocmask(SIG_SETMASK, &oldmask, NULL) == 0);
547
548 return r;
549 }
550
551 int ask_password_auto(const char *message, const char *icon, const char *id,
552 usec_t until, bool accept_cached, char ***_passphrases) {
553 assert(message);
554 assert(_passphrases);
555
556 if (isatty(STDIN_FILENO)) {
557 int r;
558 char *s = NULL, **l = NULL;
559
560 r = ask_password_tty(message, until, false, NULL, &s);
561 if (r < 0)
562 return r;
563
564 r = strv_consume(&l, s);
565 if (r < 0)
566 return r;
567
568 *_passphrases = l;
569 return r;
570 } else
571 return ask_password_agent(message, icon, id, until, false, accept_cached, _passphrases);
572 }