chiark / gitweb /
socket: fix IPv6 availability detection
[elogind.git] / src / tty-ask-password-agent.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 General Public License as published by
10   the Free Software Foundation; either version 2 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   General Public License for more details.
17
18   You should have received a copy of the GNU General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <stdbool.h>
23 #include <errno.h>
24 #include <string.h>
25 #include <sys/socket.h>
26 #include <sys/un.h>
27 #include <stddef.h>
28 #include <sys/poll.h>
29 #include <sys/inotify.h>
30 #include <unistd.h>
31 #include <getopt.h>
32 #include <sys/signalfd.h>
33
34 #include "util.h"
35 #include "conf-parser.h"
36 #include "utmp-wtmp.h"
37 #include "socket-util.h"
38
39 static enum {
40         ACTION_LIST,
41         ACTION_QUERY,
42         ACTION_WATCH,
43         ACTION_WALL
44 } arg_action = ACTION_QUERY;
45
46 static bool arg_plymouth = false;
47
48 static int ask_password_plymouth(const char *message, usec_t until, const char *flag_file, char **_passphrase) {
49         int fd = -1, notify = -1;
50         union sockaddr_union sa;
51         char *packet = NULL;
52         ssize_t k;
53         int r, n;
54         struct pollfd pollfd[2];
55         char buffer[LINE_MAX];
56         size_t p = 0;
57         enum {
58                 POLL_SOCKET,
59                 POLL_INOTIFY
60         };
61
62         if (flag_file) {
63                 if ((notify = inotify_init1(IN_CLOEXEC|IN_NONBLOCK)) < 0) {
64                         r = -errno;
65                         goto finish;
66                 }
67
68                 if (inotify_add_watch(notify, flag_file, IN_ATTRIB /* for the link count */) < 0) {
69                         r = -errno;
70                         goto finish;
71                 }
72         }
73
74         if ((fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0)) < 0) {
75                 r = -errno;
76                 goto finish;
77         }
78
79         zero(sa);
80         sa.sa.sa_family = AF_UNIX;
81         strncpy(sa.un.sun_path+1, "/ply-boot-protocol", sizeof(sa.un.sun_path)-1);
82
83         if (connect(fd, &sa.sa, sizeof(sa.un)) < 0) {
84                 r = -errno;
85                 goto finish;
86         }
87
88         if (asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1), message, &n) < 0) {
89                 r = -ENOMEM;
90                 goto finish;
91         }
92
93         if ((k = loop_write(fd, packet, n+1, true)) != n+1) {
94                 r = k < 0 ? (int) k : -EIO;
95                 goto finish;
96         }
97
98         zero(pollfd);
99         pollfd[POLL_SOCKET].fd = fd;
100         pollfd[POLL_SOCKET].events = POLLIN;
101         pollfd[POLL_INOTIFY].fd = notify;
102         pollfd[POLL_INOTIFY].events = POLLIN;
103
104         for (;;) {
105                 int sleep_for = -1, j;
106
107                 if (until > 0) {
108                         usec_t y;
109
110                         y = now(CLOCK_MONOTONIC);
111
112                         if (y > until) {
113                                 r = -ETIMEDOUT;
114                                 goto finish;
115                         }
116
117                         sleep_for = (int) ((until - y) / USEC_PER_MSEC);
118                 }
119
120                 if (flag_file)
121                         if (access(flag_file, F_OK) < 0) {
122                                 r = -errno;
123                                 goto finish;
124                         }
125
126                 if ((j = poll(pollfd, notify > 0 ? 2 : 1, sleep_for)) < 0) {
127
128                         if (errno == EINTR)
129                                 continue;
130
131                         r = -errno;
132                         goto finish;
133                 } else if (j == 0) {
134                         r = -ETIMEDOUT;
135                         goto finish;
136                 }
137
138                 if (notify > 0 && pollfd[POLL_INOTIFY].revents != 0)
139                         flush_fd(notify);
140
141                 if (pollfd[POLL_SOCKET].revents == 0)
142                         continue;
143
144                 if ((k = read(fd, buffer + p, sizeof(buffer) - p)) <= 0) {
145                         r = k < 0 ? -errno : -EIO;
146                         goto finish;
147                 }
148
149                 p += k;
150
151                 if (p < 1)
152                         continue;
153
154                 if (buffer[0] == 5) {
155                         /* No password, because UI not shown */
156                         r = -ENOENT;
157                         goto finish;
158
159                 } else if (buffer[0] == 2) {
160                         uint32_t size;
161                         char *s;
162
163                         /* One answer */
164                         if (p < 5)
165                                 continue;
166
167                         memcpy(&size, buffer+1, sizeof(size));
168                         if (size+5 > sizeof(buffer)) {
169                                 r = -EIO;
170                                 goto finish;
171                         }
172
173                         if (p-5 < size)
174                                 continue;
175
176                         if (!(s = strndup(buffer + 5, size))) {
177                                 r = -ENOMEM;
178                                 goto finish;
179                         }
180
181                         *_passphrase = s;
182                         break;
183                 } else {
184                         /* Unknown packet */
185                         r = -EIO;
186                         goto finish;
187                 }
188         }
189
190         r = 0;
191
192 finish:
193         if (notify >= 0)
194                 close_nointr_nofail(notify);
195
196         if (fd >= 0)
197                 close_nointr_nofail(fd);
198
199         free(packet);
200
201         return r;
202 }
203
204 static int parse_password(const char *filename, char **wall) {
205         char *socket_name = NULL, *message = NULL, *packet = NULL;
206         uint64_t not_after = 0;
207         unsigned pid = 0;
208         int socket_fd = -1;
209
210         const ConfigItem items[] = {
211                 { "Socket",   config_parse_string,   &socket_name, "Ask" },
212                 { "NotAfter", config_parse_uint64,   &not_after,   "Ask" },
213                 { "Message",  config_parse_string,   &message,     "Ask" },
214                 { "PID",      config_parse_unsigned, &pid,         "Ask" },
215         };
216
217         FILE *f;
218         int r;
219         usec_t n;
220
221         assert(filename);
222
223         if (!(f = fopen(filename, "re"))) {
224
225                 if (errno == ENOENT)
226                         return 0;
227
228                 log_error("open(%s): %m", filename);
229                 return -errno;
230         }
231
232         if ((r = config_parse(filename, f, NULL, items, false, NULL)) < 0) {
233                 log_error("Failed to parse password file %s: %s", filename, strerror(-r));
234                 goto finish;
235         }
236
237         if (!socket_name || not_after <= 0) {
238                 log_error("Invalid password file %s", filename);
239                 r = -EBADMSG;
240                 goto finish;
241         }
242
243         n = now(CLOCK_MONOTONIC);
244         if (n > not_after) {
245                 r = 0;
246                 goto finish;
247         }
248
249         if (arg_action == ACTION_LIST)
250                 printf("'%s' (PID %u)\n", message, pid);
251         else if (arg_action == ACTION_WALL) {
252                 char *_wall;
253
254                 if (asprintf(&_wall,
255                              "%s%sPassword entry required for \'%s\' (PID %u).\r\n"
256                              "Please enter password with the systemd-tty-password-agent tool!",
257                              *wall ? *wall : "",
258                              *wall ? "\r\n\r\n" : "",
259                              message,
260                              pid) < 0) {
261                         log_error("Out of memory");
262                         r = -ENOMEM;
263                         goto finish;
264                 }
265
266                 free(*wall);
267                 *wall = _wall;
268         } else {
269                 union {
270                         struct sockaddr sa;
271                         struct sockaddr_un un;
272                 } sa;
273                 char *password;
274
275                 assert(arg_action == ACTION_QUERY ||
276                        arg_action == ACTION_WATCH);
277
278                 if (access(socket_name, W_OK) < 0) {
279
280                         if (arg_action == ACTION_QUERY)
281                                 log_info("Not querying '%s' (PID %u), lacking privileges.", message, pid);
282
283                         r = 0;
284                         goto finish;
285                 }
286
287                 if (arg_plymouth)
288                         r = ask_password_plymouth(message, not_after, filename, &password);
289                 else
290                         r = ask_password_tty(message, not_after, filename, &password);
291
292                 if (r < 0) {
293                         log_error("Failed to query password: %s", strerror(-r));
294                         goto finish;
295                 }
296
297                 asprintf(&packet, "+%s", password);
298                 free(password);
299
300                 if (!packet) {
301                         log_error("Out of memory");
302                         r = -ENOMEM;
303                         goto finish;
304                 }
305
306                 if ((socket_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0)) < 0) {
307                         log_error("socket(): %m");
308                         r = -errno;
309                         goto finish;
310                 }
311
312                 zero(sa);
313                 sa.un.sun_family = AF_UNIX;
314                 strncpy(sa.un.sun_path, socket_name, sizeof(sa.un.sun_path));
315
316                 if (sendto(socket_fd, packet, strlen(packet), MSG_NOSIGNAL, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(socket_name)) < 0) {
317                         log_error("Failed to send: %m");
318                         r = -errno;
319                         goto finish;
320                 }
321         }
322
323 finish:
324         fclose(f);
325
326         if (socket_fd >= 0)
327                 close_nointr_nofail(socket_fd);
328
329         free(packet);
330         free(socket_name);
331         free(message);
332
333         return r;
334 }
335
336 static int show_passwords(void) {
337         DIR *d;
338         struct dirent *de;
339         int r = 0;
340
341         if (!(d = opendir("/dev/.systemd/ask-password"))) {
342                 if (errno == ENOENT)
343                         return 0;
344
345                 log_error("opendir(): %m");
346                 return -errno;
347         }
348
349         while ((de = readdir(d))) {
350                 char *p;
351                 int q;
352                 char *wall;
353
354                 if (de->d_type != DT_REG)
355                         continue;
356
357                 if (ignore_file(de->d_name))
358                         continue;
359
360                 if (!startswith(de->d_name, "ask."))
361                         continue;
362
363                 if (!(p = strappend("/dev/.systemd/ask-password/", de->d_name))) {
364                         log_error("Out of memory");
365                         r = -ENOMEM;
366                         goto finish;
367                 }
368
369                 wall = NULL;
370                 if ((q = parse_password(p, &wall)) < 0)
371                         r = q;
372
373                 free(p);
374
375                 if (wall) {
376                         utmp_wall(wall);
377                         free(wall);
378                 }
379         }
380
381 finish:
382         if (d)
383                 closedir(d);
384
385         return r;
386 }
387
388 static int watch_passwords(void) {
389         enum {
390                 FD_INOTIFY,
391                 FD_SIGNAL,
392                 _FD_MAX
393         };
394
395         int notify = -1, signal_fd = -1;
396         struct pollfd pollfd[_FD_MAX];
397         sigset_t mask;
398         int r;
399
400         mkdir_p("/dev/.systemd/ask-password", 0755);
401
402         if ((notify = inotify_init1(IN_CLOEXEC)) < 0) {
403                 r = -errno;
404                 goto finish;
405         }
406
407         if (inotify_add_watch(notify, "/dev/.systemd/ask-password", IN_CLOSE_WRITE|IN_MOVED_TO) < 0) {
408                 r = -errno;
409                 goto finish;
410         }
411
412         assert_se(sigemptyset(&mask) == 0);
413         sigset_add_many(&mask, SIGINT, SIGTERM, -1);
414         assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0);
415
416         if ((signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC)) < 0) {
417                 log_error("signalfd(): %m");
418                 r = -errno;
419                 goto finish;
420         }
421
422         zero(pollfd);
423         pollfd[FD_INOTIFY].fd = notify;
424         pollfd[FD_INOTIFY].events = POLLIN;
425         pollfd[FD_SIGNAL].fd = signal_fd;
426         pollfd[FD_SIGNAL].events = POLLIN;
427
428         for (;;) {
429                 if ((r = show_passwords()) < 0)
430                         break;
431
432                 if (poll(pollfd, _FD_MAX, -1) < 0) {
433
434                         if (errno == EINTR)
435                                 continue;
436
437                         r = -errno;
438                         goto finish;
439                 }
440
441                 if (pollfd[FD_INOTIFY].revents != 0)
442                         flush_fd(notify);
443
444                 if (pollfd[FD_SIGNAL].revents != 0)
445                         break;
446         }
447
448         r = 0;
449
450 finish:
451         if (notify >= 0)
452                 close_nointr_nofail(notify);
453
454         if (signal_fd >= 0)
455                 close_nointr_nofail(signal_fd);
456
457         return r;
458 }
459
460 static int help(void) {
461
462         printf("%s [OPTIONS...]\n\n"
463                "Process system password requests.\n\n"
464                "  -h --help     Show this help\n"
465                "     --list     Show pending password requests\n"
466                "     --query    Process pending password requests\n"
467                "     --watch    Continously process password requests\n"
468                "     --wall     Continously forward password requests to wall\n"
469                "     --plymouth Ask question with Plymouth instead of on TTY\n",
470                program_invocation_short_name);
471
472         return 0;
473 }
474
475 static int parse_argv(int argc, char *argv[]) {
476
477         enum {
478                 ARG_LIST = 0x100,
479                 ARG_QUERY,
480                 ARG_WATCH,
481                 ARG_WALL,
482                 ARG_PLYMOUTH
483         };
484
485         static const struct option options[] = {
486                 { "help",     no_argument, NULL, 'h'          },
487                 { "list",     no_argument, NULL, ARG_LIST     },
488                 { "query",    no_argument, NULL, ARG_QUERY    },
489                 { "watch",    no_argument, NULL, ARG_WATCH    },
490                 { "wall",     no_argument, NULL, ARG_WALL     },
491                 { "plymouth", no_argument, NULL, ARG_PLYMOUTH },
492                 { NULL,    0,           NULL, 0               }
493         };
494
495         int c;
496
497         assert(argc >= 0);
498         assert(argv);
499
500         while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
501
502                 switch (c) {
503
504                 case 'h':
505                         help();
506                         return 0;
507
508                 case ARG_LIST:
509                         arg_action = ACTION_LIST;
510                         break;
511
512                 case ARG_QUERY:
513                         arg_action = ACTION_QUERY;
514                         break;
515
516                 case ARG_WATCH:
517                         arg_action = ACTION_WATCH;
518                         break;
519
520                 case ARG_WALL:
521                         arg_action = ACTION_WALL;
522                         break;
523
524                 case ARG_PLYMOUTH:
525                         arg_plymouth = true;
526                         break;
527
528                 case '?':
529                         return -EINVAL;
530
531                 default:
532                         log_error("Unknown option code %c", c);
533                         return -EINVAL;
534                 }
535         }
536
537         if (optind != argc) {
538                 help();
539                 return -EINVAL;
540         }
541
542         return 1;
543 }
544
545 int main(int argc, char *argv[]) {
546         int r;
547
548         log_parse_environment();
549         log_open();
550
551         if ((r = parse_argv(argc, argv)) <= 0)
552                 goto finish;
553
554         if (arg_action == ACTION_WATCH ||
555             arg_action == ACTION_WALL)
556                 r = watch_passwords();
557         else
558                 r = show_passwords();
559
560 finish:
561         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
562 }