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