chiark / gitweb /
switch from udev keymaps to hwdb
[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, r;
254         char *c;
255
256         assert(name);
257
258         fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
259         if (fd < 0) {
260                 log_error("socket() failed: %m");
261                 return -errno;
262         }
263
264         snprintf(sa.un.sun_path, sizeof(sa.un.sun_path)-1, "/run/systemd/ask-password/sck.%llu", random_ull());
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("bind() failed: %m");
273                 goto fail;
274         }
275
276         if (setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)) < 0) {
277                 r = -errno;
278                 log_error("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         close_nointr_nofail(fd);
293
294         return r;
295 }
296
297 int ask_password_agent(
298                 const char *message,
299                 const char *icon,
300                 usec_t until,
301                 bool accept_cached,
302                 char ***_passphrases) {
303
304         enum {
305                 FD_SOCKET,
306                 FD_SIGNAL,
307                 _FD_MAX
308         };
309
310         char temp[] = "/run/systemd/ask-password/tmp.XXXXXX";
311         char final[sizeof(temp)] = "";
312         int fd = -1, r;
313         FILE *f = NULL;
314         char *socket_name = NULL;
315         int socket_fd = -1, signal_fd = -1;
316         sigset_t mask, oldmask;
317         struct pollfd pollfd[_FD_MAX];
318
319         assert(_passphrases);
320
321         assert_se(sigemptyset(&mask) == 0);
322         sigset_add_many(&mask, SIGINT, SIGTERM, -1);
323         assert_se(sigprocmask(SIG_BLOCK, &mask, &oldmask) == 0);
324
325         mkdir_p_label("/run/systemd/ask-password", 0755);
326
327         RUN_WITH_UMASK(0022) {
328                 fd = mkostemp(temp, O_CLOEXEC|O_CREAT|O_WRONLY);
329         }
330
331         if (fd < 0) {
332                 log_error("Failed to create password file: %m");
333                 r = -errno;
334                 goto finish;
335         }
336
337         fchmod(fd, 0644);
338
339         if (!(f = fdopen(fd, "w"))) {
340                 log_error("Failed to allocate FILE: %m");
341                 r = -errno;
342                 goto finish;
343         }
344
345         fd = -1;
346
347         if ((signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC)) < 0) {
348                 log_error("signalfd(): %m");
349                 r = -errno;
350                 goto finish;
351         }
352
353         if ((socket_fd = create_socket(&socket_name)) < 0) {
354                 r = socket_fd;
355                 goto finish;
356         }
357
358         fprintf(f,
359                 "[Ask]\n"
360                 "PID=%lu\n"
361                 "Socket=%s\n"
362                 "AcceptCached=%i\n"
363                 "NotAfter=%llu\n",
364                 (unsigned long) getpid(),
365                 socket_name,
366                 accept_cached ? 1 : 0,
367                 (unsigned long long) until);
368
369         if (message)
370                 fprintf(f, "Message=%s\n", message);
371
372         if (icon)
373                 fprintf(f, "Icon=%s\n", icon);
374
375         fflush(f);
376
377         if (ferror(f)) {
378                 log_error("Failed to write query file: %m");
379                 r = -errno;
380                 goto finish;
381         }
382
383         memcpy(final, temp, sizeof(temp));
384
385         final[sizeof(final)-11] = 'a';
386         final[sizeof(final)-10] = 's';
387         final[sizeof(final)-9] = 'k';
388
389         if (rename(temp, final) < 0) {
390                 log_error("Failed to rename query file: %m");
391                 r = -errno;
392                 goto finish;
393         }
394
395         zero(pollfd);
396         pollfd[FD_SOCKET].fd = socket_fd;
397         pollfd[FD_SOCKET].events = POLLIN;
398         pollfd[FD_SIGNAL].fd = signal_fd;
399         pollfd[FD_SIGNAL].events = POLLIN;
400
401         for (;;) {
402                 char passphrase[LINE_MAX+1];
403                 struct msghdr msghdr;
404                 struct iovec iovec;
405                 struct ucred *ucred;
406                 union {
407                         struct cmsghdr cmsghdr;
408                         uint8_t buf[CMSG_SPACE(sizeof(struct ucred))];
409                 } control;
410                 ssize_t n;
411                 int k;
412                 usec_t t;
413
414                 t = now(CLOCK_MONOTONIC);
415
416                 if (until > 0 && until <= t) {
417                         log_notice("Timed out");
418                         r = -ETIME;
419                         goto finish;
420                 }
421
422                 if ((k = poll(pollfd, _FD_MAX, until > 0 ? (int) ((until-t)/USEC_PER_MSEC) : -1)) < 0) {
423
424                         if (errno == EINTR)
425                                 continue;
426
427                         log_error("poll() failed: %m");
428                         r = -errno;
429                         goto finish;
430                 }
431
432                 if (k <= 0) {
433                         log_notice("Timed out");
434                         r = -ETIME;
435                         goto finish;
436                 }
437
438                 if (pollfd[FD_SIGNAL].revents & POLLIN) {
439                         r = -EINTR;
440                         goto finish;
441                 }
442
443                 if (pollfd[FD_SOCKET].revents != POLLIN) {
444                         log_error("Unexpected poll() event.");
445                         r = -EIO;
446                         goto finish;
447                 }
448
449                 zero(iovec);
450                 iovec.iov_base = passphrase;
451                 iovec.iov_len = sizeof(passphrase);
452
453                 zero(control);
454                 zero(msghdr);
455                 msghdr.msg_iov = &iovec;
456                 msghdr.msg_iovlen = 1;
457                 msghdr.msg_control = &control;
458                 msghdr.msg_controllen = sizeof(control);
459
460                 if ((n = recvmsg(socket_fd, &msghdr, 0)) < 0) {
461
462                         if (errno == EAGAIN ||
463                             errno == EINTR)
464                                 continue;
465
466                         log_error("recvmsg() failed: %m");
467                         r = -errno;
468                         goto finish;
469                 }
470
471                 if (n <= 0) {
472                         log_error("Message too short");
473                         continue;
474                 }
475
476                 if (msghdr.msg_controllen < CMSG_LEN(sizeof(struct ucred)) ||
477                     control.cmsghdr.cmsg_level != SOL_SOCKET ||
478                     control.cmsghdr.cmsg_type != SCM_CREDENTIALS ||
479                     control.cmsghdr.cmsg_len != CMSG_LEN(sizeof(struct ucred))) {
480                         log_warning("Received message without credentials. Ignoring.");
481                         continue;
482                 }
483
484                 ucred = (struct ucred*) CMSG_DATA(&control.cmsghdr);
485                 if (ucred->uid != 0) {
486                         log_warning("Got request from unprivileged user. Ignoring.");
487                         continue;
488                 }
489
490                 if (passphrase[0] == '+') {
491                         char **l;
492
493                         if (n == 1)
494                                 l = strv_new("", NULL);
495                         else
496                                 l = strv_parse_nulstr(passphrase+1, n-1);
497                                 /* An empty message refers to the empty password */
498
499                         if (!l) {
500                                 r = -ENOMEM;
501                                 goto finish;
502                         }
503
504                         if (strv_length(l) <= 0) {
505                                 strv_free(l);
506                                 log_error("Invalid packet");
507                                 continue;
508                         }
509
510                         *_passphrases = l;
511
512                 } else if (passphrase[0] == '-') {
513                         r = -ECANCELED;
514                         goto finish;
515                 } else {
516                         log_error("Invalid packet");
517                         continue;
518                 }
519
520                 break;
521         }
522
523         r = 0;
524
525 finish:
526         if (fd >= 0)
527                 close_nointr_nofail(fd);
528
529         if (socket_name) {
530                 unlink(socket_name);
531                 free(socket_name);
532         }
533
534         if (socket_fd >= 0)
535                 close_nointr_nofail(socket_fd);
536
537         if (signal_fd >= 0)
538                 close_nointr_nofail(signal_fd);
539
540         if (f)
541                 fclose(f);
542
543         unlink(temp);
544
545         if (final[0])
546                 unlink(final);
547
548         assert_se(sigprocmask(SIG_SETMASK, &oldmask, NULL) == 0);
549
550         return r;
551 }
552
553 int ask_password_auto(const char *message, const char *icon, usec_t until, bool accept_cached, char ***_passphrases) {
554         assert(message);
555         assert(_passphrases);
556
557         if (isatty(STDIN_FILENO)) {
558                 int r;
559                 char *s = NULL, **l = NULL;
560
561                 if ((r = ask_password_tty(message, until, NULL, &s)) < 0)
562                         return r;
563
564                 l = strv_new(s, NULL);
565                 free(s);
566
567                 if (!l)
568                         return -ENOMEM;
569
570                 *_passphrases = l;
571                 return r;
572
573         } else
574                 return ask_password_agent(message, icon, until, accept_cached, _passphrases);
575 }