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