chiark / gitweb /
journalctl: don't choke on entries with no MESSAGE= field
[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
113         pollfd[POLL_TTY].fd = ttyfd >= 0 ? ttyfd : STDIN_FILENO;
114         pollfd[POLL_TTY].events = POLLIN;
115         pollfd[POLL_INOTIFY].fd = notify;
116         pollfd[POLL_INOTIFY].events = POLLIN;
117
118         for (;;) {
119                 char c;
120                 int sleep_for = -1, k;
121                 ssize_t n;
122
123                 if (until > 0) {
124                         usec_t y;
125
126                         y = now(CLOCK_MONOTONIC);
127
128                         if (y > until) {
129                                 r = -ETIME;
130                                 goto finish;
131                         }
132
133                         sleep_for = (int) ((until - y) / USEC_PER_MSEC);
134                 }
135
136                 if (flag_file)
137                         if (access(flag_file, F_OK) < 0) {
138                                 r = -errno;
139                                 goto finish;
140                         }
141
142                 if ((k = poll(pollfd, notify > 0 ? 2 : 1, sleep_for)) < 0) {
143
144                         if (errno == EINTR)
145                                 continue;
146
147                         r = -errno;
148                         goto finish;
149                 } else if (k == 0) {
150                         r = -ETIME;
151                         goto finish;
152                 }
153
154                 if (notify > 0 && pollfd[POLL_INOTIFY].revents != 0)
155                         flush_fd(notify);
156
157                 if (pollfd[POLL_TTY].revents == 0)
158                         continue;
159
160                 if ((n = read(ttyfd >= 0 ? ttyfd : STDIN_FILENO, &c, 1)) < 0) {
161
162                         if (errno == EINTR || errno == EAGAIN)
163                                 continue;
164
165                         r = -errno;
166                         goto finish;
167
168                 } else if (n == 0)
169                         break;
170
171                 if (c == '\n')
172                         break;
173                 else if (c == 21) { /* C-u */
174
175                         if (!silent_mode)
176                                 backspace_chars(ttyfd, p);
177                         p = 0;
178
179                 } else if (c == '\b' || c == 127) {
180
181                         if (p > 0) {
182
183                                 if (!silent_mode)
184                                         backspace_chars(ttyfd, 1);
185
186                                 p--;
187                         } else if (!dirty && !silent_mode) {
188
189                                 silent_mode = true;
190
191                                 /* There are two ways to enter silent
192                                  * mode. Either by pressing backspace
193                                  * as first key (and only as first key),
194                                  * or ... */
195                                 if (ttyfd >= 0)
196                                         loop_write(ttyfd, "(no echo) ", 10, false);
197
198                         } else if (ttyfd >= 0)
199                                 loop_write(ttyfd, "\a", 1, false);
200
201                 } else if (c == '\t' && !silent_mode) {
202
203                         backspace_chars(ttyfd, p);
204                         silent_mode = true;
205
206                         /* ... or by pressing TAB at any time. */
207
208                         if (ttyfd >= 0)
209                                 loop_write(ttyfd, "(no echo) ", 10, false);
210                 } else {
211                         passphrase[p++] = c;
212
213                         if (!silent_mode && ttyfd >= 0)
214                                 loop_write(ttyfd, "*", 1, false);
215
216                         dirty = true;
217                 }
218         }
219
220         passphrase[p] = 0;
221
222         if (!(*_passphrase = strdup(passphrase))) {
223                 r = -ENOMEM;
224                 goto finish;
225         }
226
227         r = 0;
228
229 finish:
230         if (notify >= 0)
231                 close_nointr_nofail(notify);
232
233         if (ttyfd >= 0) {
234
235                 if (reset_tty) {
236                         loop_write(ttyfd, "\n", 1, false);
237                         tcsetattr(ttyfd, TCSADRAIN, &old_termios);
238                 }
239
240                 close_nointr_nofail(ttyfd);
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         int one = 1, r;
253         char *c;
254         mode_t u;
255
256         assert(name);
257
258         if ((fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0)) < 0) {
259                 log_error("socket() failed: %m");
260                 return -errno;
261         }
262
263         zero(sa);
264         sa.un.sun_family = AF_UNIX;
265         snprintf(sa.un.sun_path, sizeof(sa.un.sun_path)-1, "/run/systemd/ask-password/sck.%llu", random_ull());
266
267         u = umask(0177);
268         r = bind(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path));
269         umask(u);
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         if (!(c = strdup(sa.un.sun_path))) {
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         mode_t u;
319
320         assert(_passphrases);
321
322         assert_se(sigemptyset(&mask) == 0);
323         sigset_add_many(&mask, SIGINT, SIGTERM, -1);
324         assert_se(sigprocmask(SIG_BLOCK, &mask, &oldmask) == 0);
325
326         mkdir_p_label("/run/systemd/ask-password", 0755);
327
328         u = umask(0022);
329         fd = mkostemp(temp, O_CLOEXEC|O_CREAT|O_WRONLY);
330         umask(u);
331
332         if (fd < 0) {
333                 log_error("Failed to create password file: %m");
334                 r = -errno;
335                 goto finish;
336         }
337
338         fchmod(fd, 0644);
339
340         if (!(f = fdopen(fd, "w"))) {
341                 log_error("Failed to allocate FILE: %m");
342                 r = -errno;
343                 goto finish;
344         }
345
346         fd = -1;
347
348         if ((signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC)) < 0) {
349                 log_error("signalfd(): %m");
350                 r = -errno;
351                 goto finish;
352         }
353
354         if ((socket_fd = create_socket(&socket_name)) < 0) {
355                 r = socket_fd;
356                 goto finish;
357         }
358
359         fprintf(f,
360                 "[Ask]\n"
361                 "PID=%lu\n"
362                 "Socket=%s\n"
363                 "AcceptCached=%i\n"
364                 "NotAfter=%llu\n",
365                 (unsigned long) getpid(),
366                 socket_name,
367                 accept_cached ? 1 : 0,
368                 (unsigned long long) until);
369
370         if (message)
371                 fprintf(f, "Message=%s\n", message);
372
373         if (icon)
374                 fprintf(f, "Icon=%s\n", icon);
375
376         fflush(f);
377
378         if (ferror(f)) {
379                 log_error("Failed to write query file: %m");
380                 r = -errno;
381                 goto finish;
382         }
383
384         memcpy(final, temp, sizeof(temp));
385
386         final[sizeof(final)-11] = 'a';
387         final[sizeof(final)-10] = 's';
388         final[sizeof(final)-9] = 'k';
389
390         if (rename(temp, final) < 0) {
391                 log_error("Failed to rename query file: %m");
392                 r = -errno;
393                 goto finish;
394         }
395
396         zero(pollfd);
397         pollfd[FD_SOCKET].fd = socket_fd;
398         pollfd[FD_SOCKET].events = POLLIN;
399         pollfd[FD_SIGNAL].fd = signal_fd;
400         pollfd[FD_SIGNAL].events = POLLIN;
401
402         for (;;) {
403                 char passphrase[LINE_MAX+1];
404                 struct msghdr msghdr;
405                 struct iovec iovec;
406                 struct ucred *ucred;
407                 union {
408                         struct cmsghdr cmsghdr;
409                         uint8_t buf[CMSG_SPACE(sizeof(struct ucred))];
410                 } control;
411                 ssize_t n;
412                 int k;
413                 usec_t t;
414
415                 t = now(CLOCK_MONOTONIC);
416
417                 if (until > 0 && until <= t) {
418                         log_notice("Timed out");
419                         r = -ETIME;
420                         goto finish;
421                 }
422
423                 if ((k = poll(pollfd, _FD_MAX, until > 0 ? (int) ((until-t)/USEC_PER_MSEC) : -1)) < 0) {
424
425                         if (errno == EINTR)
426                                 continue;
427
428                         log_error("poll() failed: %m");
429                         r = -errno;
430                         goto finish;
431                 }
432
433                 if (k <= 0) {
434                         log_notice("Timed out");
435                         r = -ETIME;
436                         goto finish;
437                 }
438
439                 if (pollfd[FD_SIGNAL].revents & POLLIN) {
440                         r = -EINTR;
441                         goto finish;
442                 }
443
444                 if (pollfd[FD_SOCKET].revents != POLLIN) {
445                         log_error("Unexpected poll() event.");
446                         r = -EIO;
447                         goto finish;
448                 }
449
450                 zero(iovec);
451                 iovec.iov_base = passphrase;
452                 iovec.iov_len = sizeof(passphrase);
453
454                 zero(control);
455                 zero(msghdr);
456                 msghdr.msg_iov = &iovec;
457                 msghdr.msg_iovlen = 1;
458                 msghdr.msg_control = &control;
459                 msghdr.msg_controllen = sizeof(control);
460
461                 if ((n = recvmsg(socket_fd, &msghdr, 0)) < 0) {
462
463                         if (errno == EAGAIN ||
464                             errno == EINTR)
465                                 continue;
466
467                         log_error("recvmsg() failed: %m");
468                         r = -errno;
469                         goto finish;
470                 }
471
472                 if (n <= 0) {
473                         log_error("Message too short");
474                         continue;
475                 }
476
477                 if (msghdr.msg_controllen < CMSG_LEN(sizeof(struct ucred)) ||
478                     control.cmsghdr.cmsg_level != SOL_SOCKET ||
479                     control.cmsghdr.cmsg_type != SCM_CREDENTIALS ||
480                     control.cmsghdr.cmsg_len != CMSG_LEN(sizeof(struct ucred))) {
481                         log_warning("Received message without credentials. Ignoring.");
482                         continue;
483                 }
484
485                 ucred = (struct ucred*) CMSG_DATA(&control.cmsghdr);
486                 if (ucred->uid != 0) {
487                         log_warning("Got request from unprivileged user. Ignoring.");
488                         continue;
489                 }
490
491                 if (passphrase[0] == '+') {
492                         char **l;
493
494                         if (n == 1)
495                                 l = strv_new("", NULL);
496                         else
497                                 l = strv_parse_nulstr(passphrase+1, n-1);
498                                 /* An empty message refers to the empty password */
499
500                         if (!l) {
501                                 r = -ENOMEM;
502                                 goto finish;
503                         }
504
505                         if (strv_length(l) <= 0) {
506                                 strv_free(l);
507                                 log_error("Invalid packet");
508                                 continue;
509                         }
510
511                         *_passphrases = l;
512
513                 } else if (passphrase[0] == '-') {
514                         r = -ECANCELED;
515                         goto finish;
516                 } else {
517                         log_error("Invalid packet");
518                         continue;
519                 }
520
521                 break;
522         }
523
524         r = 0;
525
526 finish:
527         if (fd >= 0)
528                 close_nointr_nofail(fd);
529
530         if (socket_name) {
531                 unlink(socket_name);
532                 free(socket_name);
533         }
534
535         if (socket_fd >= 0)
536                 close_nointr_nofail(socket_fd);
537
538         if (signal_fd >= 0)
539                 close_nointr_nofail(signal_fd);
540
541         if (f)
542                 fclose(f);
543
544         unlink(temp);
545
546         if (final[0])
547                 unlink(final);
548
549         assert_se(sigprocmask(SIG_SETMASK, &oldmask, NULL) == 0);
550
551         return r;
552 }
553
554 int ask_password_auto(const char *message, const char *icon, usec_t until, bool accept_cached, char ***_passphrases) {
555         assert(message);
556         assert(_passphrases);
557
558         if (isatty(STDIN_FILENO)) {
559                 int r;
560                 char *s = NULL, **l = NULL;
561
562                 if ((r = ask_password_tty(message, until, NULL, &s)) < 0)
563                         return r;
564
565                 l = strv_new(s, NULL);
566                 free(s);
567
568                 if (!l)
569                         return -ENOMEM;
570
571                 *_passphrases = l;
572                 return r;
573
574         } else
575                 return ask_password_agent(message, icon, until, accept_cached, _passphrases);
576 }