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