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