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