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