chiark / gitweb /
Treat a trailing backslash as an error
[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                 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                 if (n <= 0) {
479                         log_error("Message too short");
480                         continue;
481                 }
482
483                 if (msghdr.msg_controllen < CMSG_LEN(sizeof(struct ucred)) ||
484                     control.cmsghdr.cmsg_level != SOL_SOCKET ||
485                     control.cmsghdr.cmsg_type != SCM_CREDENTIALS ||
486                     control.cmsghdr.cmsg_len != CMSG_LEN(sizeof(struct ucred))) {
487                         log_warning("Received message without credentials. Ignoring.");
488                         continue;
489                 }
490
491                 ucred = (struct ucred*) CMSG_DATA(&control.cmsghdr);
492                 if (ucred->uid != 0) {
493                         log_warning("Got request from unprivileged user. Ignoring.");
494                         continue;
495                 }
496
497                 if (passphrase[0] == '+') {
498                         char **l;
499
500                         if (n == 1)
501                                 l = strv_new("", NULL);
502                         else
503                                 l = strv_parse_nulstr(passphrase+1, n-1);
504                                 /* An empty message refers to the empty password */
505
506                         if (!l) {
507                                 r = -ENOMEM;
508                                 goto finish;
509                         }
510
511                         if (strv_length(l) <= 0) {
512                                 strv_free(l);
513                                 log_error("Invalid packet");
514                                 continue;
515                         }
516
517                         *_passphrases = l;
518
519                 } else if (passphrase[0] == '-') {
520                         r = -ECANCELED;
521                         goto finish;
522                 } else {
523                         log_error("Invalid packet");
524                         continue;
525                 }
526
527                 break;
528         }
529
530         r = 0;
531
532 finish:
533         if (socket_name)
534                 unlink(socket_name);
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, const char *id,
547                       usec_t until, bool accept_cached, char ***_passphrases) {
548         assert(message);
549         assert(_passphrases);
550
551         if (isatty(STDIN_FILENO)) {
552                 int r;
553                 char *s = NULL, **l = NULL;
554
555                 r = ask_password_tty(message, until, false, NULL, &s);
556                 if (r < 0)
557                         return r;
558
559                 r = strv_consume(&l, s);
560                 if (r < 0)
561                         return r;
562
563                 *_passphrases = l;
564                 return r;
565         } else
566                 return ask_password_agent(message, icon, id, until, false, accept_cached, _passphrases);
567 }