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