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