chiark / gitweb /
shared: introduce cmsg_close_all() call
[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 <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                 return log_error_errno(errno, "socket() failed: %m");
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_errno(errno, "bind(%s) failed: %m", sa.un.sun_path);
273                 goto fail;
274         }
275
276         if (setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)) < 0) {
277                 r = -errno;
278                 log_error_errno(errno, "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                 const char *id,
301                 usec_t until,
302                 bool echo,
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         _cleanup_fclose_ FILE *f = NULL;
315         _cleanup_free_ char *socket_name = NULL;
316         _cleanup_close_ int socket_fd = -1, signal_fd = -1, fd = -1;
317         sigset_t mask, oldmask;
318         struct pollfd pollfd[_FD_MAX];
319         int r;
320
321         assert(_passphrases);
322
323         assert_se(sigemptyset(&mask) == 0);
324         sigset_add_many(&mask, SIGINT, SIGTERM, -1);
325         assert_se(sigprocmask(SIG_BLOCK, &mask, &oldmask) == 0);
326
327         mkdir_p_label("/run/systemd/ask-password", 0755);
328
329         fd = mkostemp_safe(temp, O_WRONLY|O_CLOEXEC);
330         if (fd < 0) {
331                 log_error_errno(errno, "Failed to create password file: %m");
332                 r = -errno;
333                 goto finish;
334         }
335
336         fchmod(fd, 0644);
337
338         f = fdopen(fd, "w");
339         if (!f) {
340                 log_error_errno(errno, "Failed to allocate FILE: %m");
341                 r = -errno;
342                 goto finish;
343         }
344
345         fd = -1;
346
347         signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC);
348         if (signal_fd < 0) {
349                 log_error_errno(errno, "signalfd(): %m");
350                 r = -errno;
351                 goto finish;
352         }
353
354         socket_fd = create_socket(&socket_name);
355         if (socket_fd < 0) {
356                 r = socket_fd;
357                 goto finish;
358         }
359
360         fprintf(f,
361                 "[Ask]\n"
362                 "PID="PID_FMT"\n"
363                 "Socket=%s\n"
364                 "AcceptCached=%i\n"
365                 "Echo=%i\n"
366                 "NotAfter="USEC_FMT"\n",
367                 getpid(),
368                 socket_name,
369                 accept_cached ? 1 : 0,
370                 echo ? 1 : 0,
371                 until);
372
373         if (message)
374                 fprintf(f, "Message=%s\n", message);
375
376         if (icon)
377                 fprintf(f, "Icon=%s\n", icon);
378
379         if (id)
380                 fprintf(f, "Id=%s\n", id);
381
382         fflush(f);
383
384         if (ferror(f)) {
385                 log_error_errno(errno, "Failed to write query file: %m");
386                 r = -errno;
387                 goto finish;
388         }
389
390         memcpy(final, temp, sizeof(temp));
391
392         final[sizeof(final)-11] = 'a';
393         final[sizeof(final)-10] = 's';
394         final[sizeof(final)-9] = 'k';
395
396         if (rename(temp, final) < 0) {
397                 log_error_errno(errno, "Failed to rename query file: %m");
398                 r = -errno;
399                 goto finish;
400         }
401
402         zero(pollfd);
403         pollfd[FD_SOCKET].fd = socket_fd;
404         pollfd[FD_SOCKET].events = POLLIN;
405         pollfd[FD_SIGNAL].fd = signal_fd;
406         pollfd[FD_SIGNAL].events = POLLIN;
407
408         for (;;) {
409                 char passphrase[LINE_MAX+1];
410                 struct msghdr msghdr;
411                 struct iovec iovec;
412                 struct ucred *ucred;
413                 union {
414                         struct cmsghdr cmsghdr;
415                         uint8_t buf[CMSG_SPACE(sizeof(struct ucred))];
416                 } control;
417                 ssize_t n;
418                 int k;
419                 usec_t t;
420
421                 t = now(CLOCK_MONOTONIC);
422
423                 if (until > 0 && until <= t) {
424                         log_notice("Timed out");
425                         r = -ETIME;
426                         goto finish;
427                 }
428
429                 k = poll(pollfd, _FD_MAX, until > 0 ? (int) ((until-t)/USEC_PER_MSEC) : -1);
430                 if (k < 0) {
431                         if (errno == EINTR)
432                                 continue;
433
434                         log_error_errno(errno, "poll() failed: %m");
435                         r = -errno;
436                         goto finish;
437                 }
438
439                 if (k <= 0) {
440                         log_notice("Timed out");
441                         r = -ETIME;
442                         goto finish;
443                 }
444
445                 if (pollfd[FD_SIGNAL].revents & POLLIN) {
446                         r = -EINTR;
447                         goto finish;
448                 }
449
450                 if (pollfd[FD_SOCKET].revents != POLLIN) {
451                         log_error("Unexpected poll() event.");
452                         r = -EIO;
453                         goto finish;
454                 }
455
456                 zero(iovec);
457                 iovec.iov_base = passphrase;
458                 iovec.iov_len = sizeof(passphrase);
459
460                 zero(control);
461                 zero(msghdr);
462                 msghdr.msg_iov = &iovec;
463                 msghdr.msg_iovlen = 1;
464                 msghdr.msg_control = &control;
465                 msghdr.msg_controllen = sizeof(control);
466
467                 n = recvmsg(socket_fd, &msghdr, 0);
468                 if (n < 0) {
469                         if (errno == EAGAIN ||
470                             errno == EINTR)
471                                 continue;
472
473                         log_error_errno(errno, "recvmsg() failed: %m");
474                         r = -errno;
475                         goto finish;
476                 }
477
478                 cmsg_close_all(&msghdr);
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 }