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