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