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