chiark / gitweb /
Partially revert e62d8c394474
[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, "/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         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         close_nointr_nofail(fd);
295
296         return r;
297 }
298
299 int ask_password_agent(
300                 const char *message,
301                 const char *icon,
302                 usec_t until,
303                 bool accept_cached,
304                 char ***_passphrases) {
305
306         enum {
307                 FD_SOCKET,
308                 FD_SIGNAL,
309                 _FD_MAX
310         };
311
312         char temp[] = "/run/systemd/ask-password/tmp.XXXXXX";
313         char final[sizeof(temp)] = "";
314         int fd = -1, r;
315         FILE *f = NULL;
316         char *socket_name = NULL;
317         int socket_fd = -1, signal_fd = -1;
318         sigset_t mask, oldmask;
319         struct pollfd pollfd[_FD_MAX];
320         mode_t u;
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         u = umask(0022);
331         fd = mkostemp(temp, O_CLOEXEC|O_CREAT|O_WRONLY);
332         umask(u);
333
334         if (fd < 0) {
335                 log_error("Failed to create password file: %m");
336                 r = -errno;
337                 goto finish;
338         }
339
340         fchmod(fd, 0644);
341
342         if (!(f = fdopen(fd, "w"))) {
343                 log_error("Failed to allocate FILE: %m");
344                 r = -errno;
345                 goto finish;
346         }
347
348         fd = -1;
349
350         if ((signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC)) < 0) {
351                 log_error("signalfd(): %m");
352                 r = -errno;
353                 goto finish;
354         }
355
356         if ((socket_fd = create_socket(&socket_name)) < 0) {
357                 r = socket_fd;
358                 goto finish;
359         }
360
361         fprintf(f,
362                 "[Ask]\n"
363                 "PID=%lu\n"
364                 "Socket=%s\n"
365                 "AcceptCached=%i\n"
366                 "NotAfter=%llu\n",
367                 (unsigned long) getpid(),
368                 socket_name,
369                 accept_cached ? 1 : 0,
370                 (unsigned long long) until);
371
372         if (message)
373                 fprintf(f, "Message=%s\n", message);
374
375         if (icon)
376                 fprintf(f, "Icon=%s\n", icon);
377
378         fflush(f);
379
380         if (ferror(f)) {
381                 log_error("Failed to write query file: %m");
382                 r = -errno;
383                 goto finish;
384         }
385
386         memcpy(final, temp, sizeof(temp));
387
388         final[sizeof(final)-11] = 'a';
389         final[sizeof(final)-10] = 's';
390         final[sizeof(final)-9] = 'k';
391
392         if (rename(temp, final) < 0) {
393                 log_error("Failed to rename query file: %m");
394                 r = -errno;
395                 goto finish;
396         }
397
398         zero(pollfd);
399         pollfd[FD_SOCKET].fd = socket_fd;
400         pollfd[FD_SOCKET].events = POLLIN;
401         pollfd[FD_SIGNAL].fd = signal_fd;
402         pollfd[FD_SIGNAL].events = POLLIN;
403
404         for (;;) {
405                 char passphrase[LINE_MAX+1];
406                 struct msghdr msghdr;
407                 struct iovec iovec;
408                 struct ucred *ucred;
409                 union {
410                         struct cmsghdr cmsghdr;
411                         uint8_t buf[CMSG_SPACE(sizeof(struct ucred))];
412                 } control;
413                 ssize_t n;
414                 int k;
415                 usec_t t;
416
417                 t = now(CLOCK_MONOTONIC);
418
419                 if (until > 0 && until <= t) {
420                         log_notice("Timed out");
421                         r = -ETIME;
422                         goto finish;
423                 }
424
425                 if ((k = poll(pollfd, _FD_MAX, until > 0 ? (int) ((until-t)/USEC_PER_MSEC) : -1)) < 0) {
426
427                         if (errno == EINTR)
428                                 continue;
429
430                         log_error("poll() failed: %m");
431                         r = -errno;
432                         goto finish;
433                 }
434
435                 if (k <= 0) {
436                         log_notice("Timed out");
437                         r = -ETIME;
438                         goto finish;
439                 }
440
441                 if (pollfd[FD_SIGNAL].revents & POLLIN) {
442                         r = -EINTR;
443                         goto finish;
444                 }
445
446                 if (pollfd[FD_SOCKET].revents != POLLIN) {
447                         log_error("Unexpected poll() event.");
448                         r = -EIO;
449                         goto finish;
450                 }
451
452                 zero(iovec);
453                 iovec.iov_base = passphrase;
454                 iovec.iov_len = sizeof(passphrase);
455
456                 zero(control);
457                 zero(msghdr);
458                 msghdr.msg_iov = &iovec;
459                 msghdr.msg_iovlen = 1;
460                 msghdr.msg_control = &control;
461                 msghdr.msg_controllen = sizeof(control);
462
463                 if ((n = recvmsg(socket_fd, &msghdr, 0)) < 0) {
464
465                         if (errno == EAGAIN ||
466                             errno == EINTR)
467                                 continue;
468
469                         log_error("recvmsg() failed: %m");
470                         r = -errno;
471                         goto finish;
472                 }
473
474                 if (n <= 0) {
475                         log_error("Message too short");
476                         continue;
477                 }
478
479                 if (msghdr.msg_controllen < CMSG_LEN(sizeof(struct ucred)) ||
480                     control.cmsghdr.cmsg_level != SOL_SOCKET ||
481                     control.cmsghdr.cmsg_type != SCM_CREDENTIALS ||
482                     control.cmsghdr.cmsg_len != CMSG_LEN(sizeof(struct ucred))) {
483                         log_warning("Received message without credentials. Ignoring.");
484                         continue;
485                 }
486
487                 ucred = (struct ucred*) CMSG_DATA(&control.cmsghdr);
488                 if (ucred->uid != 0) {
489                         log_warning("Got request from unprivileged user. Ignoring.");
490                         continue;
491                 }
492
493                 if (passphrase[0] == '+') {
494                         char **l;
495
496                         if (n == 1)
497                                 l = strv_new("", NULL);
498                         else
499                                 l = strv_parse_nulstr(passphrase+1, n-1);
500                                 /* An empty message refers to the empty password */
501
502                         if (!l) {
503                                 r = -ENOMEM;
504                                 goto finish;
505                         }
506
507                         if (strv_length(l) <= 0) {
508                                 strv_free(l);
509                                 log_error("Invalid packet");
510                                 continue;
511                         }
512
513                         *_passphrases = l;
514
515                 } else if (passphrase[0] == '-') {
516                         r = -ECANCELED;
517                         goto finish;
518                 } else {
519                         log_error("Invalid packet");
520                         continue;
521                 }
522
523                 break;
524         }
525
526         r = 0;
527
528 finish:
529         if (fd >= 0)
530                 close_nointr_nofail(fd);
531
532         if (socket_name) {
533                 unlink(socket_name);
534                 free(socket_name);
535         }
536
537         if (socket_fd >= 0)
538                 close_nointr_nofail(socket_fd);
539
540         if (signal_fd >= 0)
541                 close_nointr_nofail(signal_fd);
542
543         if (f)
544                 fclose(f);
545
546         unlink(temp);
547
548         if (final[0])
549                 unlink(final);
550
551         assert_se(sigprocmask(SIG_SETMASK, &oldmask, NULL) == 0);
552
553         return r;
554 }
555
556 int ask_password_auto(const char *message, const char *icon, usec_t until, bool accept_cached, char ***_passphrases) {
557         assert(message);
558         assert(_passphrases);
559
560         if (isatty(STDIN_FILENO)) {
561                 int r;
562                 char *s = NULL, **l = NULL;
563
564                 if ((r = ask_password_tty(message, until, NULL, &s)) < 0)
565                         return r;
566
567                 l = strv_new(s, NULL);
568                 free(s);
569
570                 if (!l)
571                         return -ENOMEM;
572
573                 *_passphrases = l;
574                 return r;
575
576         } else
577                 return ask_password_agent(message, icon, until, accept_cached, _passphrases);
578 }