]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/ask-password-api.c
treewide: use log_*_errno whenever %m is in the format string
[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 <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"
35 #include "mkdir.h"
36 #include "strv.h"
37
38 #include "ask-password-api.h"
39
40 static 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
52 int ask_password_tty(
53 const char *message,
54 usec_t until,
55 bool echo,
56 const char *flag_file,
57 char **_passphrase) {
58
59 struct termios old_termios, new_termios;
60 char passphrase[LINE_MAX], *x;
61 size_t p = 0;
62 int r;
63 _cleanup_close_ int ttyfd = -1, notify = -1;
64 struct pollfd pollfd[2];
65 bool reset_tty = false;
66 bool silent_mode = false;
67 bool dirty = false;
68 enum {
69 POLL_TTY,
70 POLL_INOTIFY
71 };
72
73 assert(message);
74 assert(_passphrase);
75
76 if (flag_file) {
77 notify = inotify_init1(IN_CLOEXEC|IN_NONBLOCK);
78 if (notify < 0) {
79 r = -errno;
80 goto finish;
81 }
82
83 if (inotify_add_watch(notify, flag_file, IN_ATTRIB /* for the link count */) < 0) {
84 r = -errno;
85 goto finish;
86 }
87 }
88
89 ttyfd = open("/dev/tty", O_RDWR|O_NOCTTY|O_CLOEXEC);
90 if (ttyfd >= 0) {
91
92 if (tcgetattr(ttyfd, &old_termios) < 0) {
93 r = -errno;
94 goto finish;
95 }
96
97 loop_write(ttyfd, ANSI_HIGHLIGHT_ON, sizeof(ANSI_HIGHLIGHT_ON)-1, false);
98 loop_write(ttyfd, message, strlen(message), false);
99 loop_write(ttyfd, " ", 1, false);
100 loop_write(ttyfd, ANSI_HIGHLIGHT_OFF, sizeof(ANSI_HIGHLIGHT_OFF)-1, false);
101
102 new_termios = old_termios;
103 new_termios.c_lflag &= ~(ICANON|ECHO);
104 new_termios.c_cc[VMIN] = 1;
105 new_termios.c_cc[VTIME] = 0;
106
107 if (tcsetattr(ttyfd, TCSADRAIN, &new_termios) < 0) {
108 r = -errno;
109 goto finish;
110 }
111
112 reset_tty = true;
113 }
114
115 zero(pollfd);
116 pollfd[POLL_TTY].fd = ttyfd >= 0 ? ttyfd : STDIN_FILENO;
117 pollfd[POLL_TTY].events = POLLIN;
118 pollfd[POLL_INOTIFY].fd = notify;
119 pollfd[POLL_INOTIFY].events = POLLIN;
120
121 for (;;) {
122 char c;
123 int sleep_for = -1, k;
124 ssize_t n;
125
126 if (until > 0) {
127 usec_t y;
128
129 y = now(CLOCK_MONOTONIC);
130
131 if (y > until) {
132 r = -ETIME;
133 goto finish;
134 }
135
136 sleep_for = (int) ((until - y) / USEC_PER_MSEC);
137 }
138
139 if (flag_file)
140 if (access(flag_file, F_OK) < 0) {
141 r = -errno;
142 goto finish;
143 }
144
145 k = poll(pollfd, notify > 0 ? 2 : 1, sleep_for);
146 if (k < 0) {
147 if (errno == EINTR)
148 continue;
149
150 r = -errno;
151 goto finish;
152 } else if (k == 0) {
153 r = -ETIME;
154 goto finish;
155 }
156
157 if (notify > 0 && pollfd[POLL_INOTIFY].revents != 0)
158 flush_fd(notify);
159
160 if (pollfd[POLL_TTY].revents == 0)
161 continue;
162
163 n = read(ttyfd >= 0 ? ttyfd : STDIN_FILENO, &c, 1);
164 if (n < 0) {
165 if (errno == EINTR || errno == EAGAIN)
166 continue;
167
168 r = -errno;
169 goto finish;
170
171 } else if (n == 0)
172 break;
173
174 if (c == '\n')
175 break;
176 else if (c == 21) { /* C-u */
177
178 if (!silent_mode)
179 backspace_chars(ttyfd, p);
180 p = 0;
181
182 } else if (c == '\b' || c == 127) {
183
184 if (p > 0) {
185
186 if (!silent_mode)
187 backspace_chars(ttyfd, 1);
188
189 p--;
190 } else if (!dirty && !silent_mode) {
191
192 silent_mode = true;
193
194 /* There are two ways to enter silent
195 * mode. Either by pressing backspace
196 * as first key (and only as first key),
197 * or ... */
198 if (ttyfd >= 0)
199 loop_write(ttyfd, "(no echo) ", 10, false);
200
201 } else if (ttyfd >= 0)
202 loop_write(ttyfd, "\a", 1, false);
203
204 } else if (c == '\t' && !silent_mode) {
205
206 backspace_chars(ttyfd, p);
207 silent_mode = true;
208
209 /* ... or by pressing TAB at any time. */
210
211 if (ttyfd >= 0)
212 loop_write(ttyfd, "(no echo) ", 10, false);
213 } else {
214 if (p >= sizeof(passphrase)-1) {
215 loop_write(ttyfd, "\a", 1, false);
216 continue;
217 }
218
219 passphrase[p++] = c;
220
221 if (!silent_mode && ttyfd >= 0)
222 loop_write(ttyfd, echo ? &c : "*", 1, false);
223
224 dirty = true;
225 }
226 }
227
228 x = strndup(passphrase, p);
229 if (!x) {
230 r = -ENOMEM;
231 goto finish;
232 }
233
234 *_passphrase = x;
235 r = 0;
236
237 finish:
238 if (ttyfd >= 0 && reset_tty) {
239 loop_write(ttyfd, "\n", 1, false);
240 tcsetattr(ttyfd, TCSADRAIN, &old_termios);
241 }
242
243 return r;
244 }
245
246 static int create_socket(char **name) {
247 int fd;
248 union {
249 struct sockaddr sa;
250 struct sockaddr_un un;
251 } sa = {
252 .un.sun_family = AF_UNIX,
253 };
254 int one = 1;
255 int r = 0;
256 char *c;
257
258 assert(name);
259
260 fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
261 if (fd < 0) {
262 log_error_errno(errno, "socket() failed: %m");
263 return -errno;
264 }
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 if (n <= 0) {
481 log_error("Message too short");
482 continue;
483 }
484
485 if (msghdr.msg_controllen < CMSG_LEN(sizeof(struct ucred)) ||
486 control.cmsghdr.cmsg_level != SOL_SOCKET ||
487 control.cmsghdr.cmsg_type != SCM_CREDENTIALS ||
488 control.cmsghdr.cmsg_len != CMSG_LEN(sizeof(struct ucred))) {
489 log_warning("Received message without credentials. Ignoring.");
490 continue;
491 }
492
493 ucred = (struct ucred*) CMSG_DATA(&control.cmsghdr);
494 if (ucred->uid != 0) {
495 log_warning("Got request from unprivileged user. Ignoring.");
496 continue;
497 }
498
499 if (passphrase[0] == '+') {
500 char **l;
501
502 if (n == 1)
503 l = strv_new("", NULL);
504 else
505 l = strv_parse_nulstr(passphrase+1, n-1);
506 /* An empty message refers to the empty password */
507
508 if (!l) {
509 r = -ENOMEM;
510 goto finish;
511 }
512
513 if (strv_length(l) <= 0) {
514 strv_free(l);
515 log_error("Invalid packet");
516 continue;
517 }
518
519 *_passphrases = l;
520
521 } else if (passphrase[0] == '-') {
522 r = -ECANCELED;
523 goto finish;
524 } else {
525 log_error("Invalid packet");
526 continue;
527 }
528
529 break;
530 }
531
532 r = 0;
533
534 finish:
535 if (socket_name)
536 unlink(socket_name);
537
538 unlink(temp);
539
540 if (final[0])
541 unlink(final);
542
543 assert_se(sigprocmask(SIG_SETMASK, &oldmask, NULL) == 0);
544
545 return r;
546 }
547
548 int ask_password_auto(const char *message, const char *icon, const char *id,
549 usec_t until, bool accept_cached, char ***_passphrases) {
550 assert(message);
551 assert(_passphrases);
552
553 if (isatty(STDIN_FILENO)) {
554 int r;
555 char *s = NULL, **l = NULL;
556
557 r = ask_password_tty(message, until, false, NULL, &s);
558 if (r < 0)
559 return r;
560
561 r = strv_consume(&l, s);
562 if (r < 0)
563 return r;
564
565 *_passphrases = l;
566 return r;
567 } else
568 return ask_password_agent(message, icon, id, until, false, accept_cached, _passphrases);
569 }