chiark / gitweb /
ask-password: when the user types a overly long password, beep and refuse
[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                         if (p >= sizeof(passphrase)-1) {
211                                 loop_write(ttyfd, "\a", 1, false);
212                                 continue;
213                         }
214
215                         passphrase[p++] = c;
216
217                         if (!silent_mode && ttyfd >= 0)
218                                 loop_write(ttyfd, "*", 1, false);
219
220                         dirty = true;
221                 }
222         }
223
224         passphrase[p] = 0;
225
226         if (!(*_passphrase = strdup(passphrase))) {
227                 r = -ENOMEM;
228                 goto finish;
229         }
230
231         r = 0;
232
233 finish:
234         safe_close(notify);
235
236         if (ttyfd >= 0) {
237
238                 if (reset_tty) {
239                         loop_write(ttyfd, "\n", 1, false);
240                         tcsetattr(ttyfd, TCSADRAIN, &old_termios);
241                 }
242
243                 safe_close(ttyfd);
244         }
245
246         return r;
247 }
248
249 static int create_socket(char **name) {
250         int fd;
251         union {
252                 struct sockaddr sa;
253                 struct sockaddr_un un;
254         } sa = {
255                 .un.sun_family = AF_UNIX,
256         };
257         int one = 1;
258         int r = 0;
259         char *c;
260
261         assert(name);
262
263         fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
264         if (fd < 0) {
265                 log_error("socket() failed: %m");
266                 return -errno;
267         }
268
269         snprintf(sa.un.sun_path, sizeof(sa.un.sun_path)-1, "/run/systemd/ask-password/sck.%" PRIx64, random_u64());
270
271         RUN_WITH_UMASK(0177) {
272                 r = bind(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path));
273         }
274
275         if (r < 0) {
276                 r = -errno;
277                 log_error("bind() failed: %m");
278                 goto fail;
279         }
280
281         if (setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)) < 0) {
282                 r = -errno;
283                 log_error("SO_PASSCRED failed: %m");
284                 goto fail;
285         }
286
287         c = strdup(sa.un.sun_path);
288         if (!c) {
289                 r = log_oom();
290                 goto fail;
291         }
292
293         *name = c;
294         return fd;
295
296 fail:
297         safe_close(fd);
298
299         return r;
300 }
301
302 int ask_password_agent(
303                 const char *message,
304                 const char *icon,
305                 usec_t until,
306                 bool accept_cached,
307                 char ***_passphrases) {
308
309         enum {
310                 FD_SOCKET,
311                 FD_SIGNAL,
312                 _FD_MAX
313         };
314
315         char temp[] = "/run/systemd/ask-password/tmp.XXXXXX";
316         char final[sizeof(temp)] = "";
317         int fd = -1, r;
318         FILE *f = NULL;
319         char *socket_name = NULL;
320         int socket_fd = -1, signal_fd = -1;
321         sigset_t mask, oldmask;
322         struct pollfd pollfd[_FD_MAX];
323
324         assert(_passphrases);
325
326         assert_se(sigemptyset(&mask) == 0);
327         sigset_add_many(&mask, SIGINT, SIGTERM, -1);
328         assert_se(sigprocmask(SIG_BLOCK, &mask, &oldmask) == 0);
329
330         mkdir_p_label("/run/systemd/ask-password", 0755);
331
332         fd = mkostemp_safe(temp, O_WRONLY|O_CLOEXEC);
333         if (fd < 0) {
334                 log_error("Failed to create password file: %m");
335                 r = -errno;
336                 goto finish;
337         }
338
339         fchmod(fd, 0644);
340
341         if (!(f = fdopen(fd, "w"))) {
342                 log_error("Failed to allocate FILE: %m");
343                 r = -errno;
344                 goto finish;
345         }
346
347         fd = -1;
348
349         if ((signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC)) < 0) {
350                 log_error("signalfd(): %m");
351                 r = -errno;
352                 goto finish;
353         }
354
355         if ((socket_fd = create_socket(&socket_name)) < 0) {
356                 r = socket_fd;
357                 goto finish;
358         }
359
360         fprintf(f,
361                 "[Ask]\n"
362                 "PID=%lu\n"
363                 "Socket=%s\n"
364                 "AcceptCached=%i\n"
365                 "NotAfter=%llu\n",
366                 (unsigned long) getpid(),
367                 socket_name,
368                 accept_cached ? 1 : 0,
369                 (unsigned long long) until);
370
371         if (message)
372                 fprintf(f, "Message=%s\n", message);
373
374         if (icon)
375                 fprintf(f, "Icon=%s\n", icon);
376
377         fflush(f);
378
379         if (ferror(f)) {
380                 log_error("Failed to write query file: %m");
381                 r = -errno;
382                 goto finish;
383         }
384
385         memcpy(final, temp, sizeof(temp));
386
387         final[sizeof(final)-11] = 'a';
388         final[sizeof(final)-10] = 's';
389         final[sizeof(final)-9] = 'k';
390
391         if (rename(temp, final) < 0) {
392                 log_error("Failed to rename query file: %m");
393                 r = -errno;
394                 goto finish;
395         }
396
397         zero(pollfd);
398         pollfd[FD_SOCKET].fd = socket_fd;
399         pollfd[FD_SOCKET].events = POLLIN;
400         pollfd[FD_SIGNAL].fd = signal_fd;
401         pollfd[FD_SIGNAL].events = POLLIN;
402
403         for (;;) {
404                 char passphrase[LINE_MAX+1];
405                 struct msghdr msghdr;
406                 struct iovec iovec;
407                 struct ucred *ucred;
408                 union {
409                         struct cmsghdr cmsghdr;
410                         uint8_t buf[CMSG_SPACE(sizeof(struct ucred))];
411                 } control;
412                 ssize_t n;
413                 int k;
414                 usec_t t;
415
416                 t = now(CLOCK_MONOTONIC);
417
418                 if (until > 0 && until <= t) {
419                         log_notice("Timed out");
420                         r = -ETIME;
421                         goto finish;
422                 }
423
424                 if ((k = poll(pollfd, _FD_MAX, until > 0 ? (int) ((until-t)/USEC_PER_MSEC) : -1)) < 0) {
425
426                         if (errno == EINTR)
427                                 continue;
428
429                         log_error("poll() failed: %m");
430                         r = -errno;
431                         goto finish;
432                 }
433
434                 if (k <= 0) {
435                         log_notice("Timed out");
436                         r = -ETIME;
437                         goto finish;
438                 }
439
440                 if (pollfd[FD_SIGNAL].revents & POLLIN) {
441                         r = -EINTR;
442                         goto finish;
443                 }
444
445                 if (pollfd[FD_SOCKET].revents != POLLIN) {
446                         log_error("Unexpected poll() event.");
447                         r = -EIO;
448                         goto finish;
449                 }
450
451                 zero(iovec);
452                 iovec.iov_base = passphrase;
453                 iovec.iov_len = sizeof(passphrase);
454
455                 zero(control);
456                 zero(msghdr);
457                 msghdr.msg_iov = &iovec;
458                 msghdr.msg_iovlen = 1;
459                 msghdr.msg_control = &control;
460                 msghdr.msg_controllen = sizeof(control);
461
462                 if ((n = recvmsg(socket_fd, &msghdr, 0)) < 0) {
463
464                         if (errno == EAGAIN ||
465                             errno == EINTR)
466                                 continue;
467
468                         log_error("recvmsg() failed: %m");
469                         r = -errno;
470                         goto finish;
471                 }
472
473                 if (n <= 0) {
474                         log_error("Message too short");
475                         continue;
476                 }
477
478                 if (msghdr.msg_controllen < CMSG_LEN(sizeof(struct ucred)) ||
479                     control.cmsghdr.cmsg_level != SOL_SOCKET ||
480                     control.cmsghdr.cmsg_type != SCM_CREDENTIALS ||
481                     control.cmsghdr.cmsg_len != CMSG_LEN(sizeof(struct ucred))) {
482                         log_warning("Received message without credentials. Ignoring.");
483                         continue;
484                 }
485
486                 ucred = (struct ucred*) CMSG_DATA(&control.cmsghdr);
487                 if (ucred->uid != 0) {
488                         log_warning("Got request from unprivileged user. Ignoring.");
489                         continue;
490                 }
491
492                 if (passphrase[0] == '+') {
493                         char **l;
494
495                         if (n == 1)
496                                 l = strv_new("", NULL);
497                         else
498                                 l = strv_parse_nulstr(passphrase+1, n-1);
499                                 /* An empty message refers to the empty password */
500
501                         if (!l) {
502                                 r = -ENOMEM;
503                                 goto finish;
504                         }
505
506                         if (strv_length(l) <= 0) {
507                                 strv_free(l);
508                                 log_error("Invalid packet");
509                                 continue;
510                         }
511
512                         *_passphrases = l;
513
514                 } else if (passphrase[0] == '-') {
515                         r = -ECANCELED;
516                         goto finish;
517                 } else {
518                         log_error("Invalid packet");
519                         continue;
520                 }
521
522                 break;
523         }
524
525         r = 0;
526
527 finish:
528         safe_close(fd);
529
530         if (socket_name) {
531                 unlink(socket_name);
532                 free(socket_name);
533         }
534
535         safe_close(socket_fd);
536         safe_close(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 }