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