]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/cryptsetup/cryptsetup-generator.c
cryptsetup-generator: auto add deps for device as password
[thirdparty/systemd.git] / src / cryptsetup / cryptsetup-generator.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
22 #include <string.h>
23 #include <errno.h>
24 #include <unistd.h>
25
26 #include "log.h"
27 #include "util.h"
28 #include "unit-name.h"
29 #include "mkdir.h"
30 #include "strv.h"
31 #include "fileio.h"
32
33 static const char *arg_dest = "/tmp";
34 static bool arg_enabled = true;
35 static bool arg_read_crypttab = true;
36
37 static bool has_option(const char *haystack, const char *needle) {
38 const char *f = haystack;
39 size_t l;
40
41 assert(needle);
42
43 if (!haystack)
44 return false;
45
46 l = strlen(needle);
47
48 while ((f = strstr(f, needle))) {
49
50 if (f > haystack && f[-1] != ',') {
51 f++;
52 continue;
53 }
54
55 if (f[l] != 0 && f[l] != ',') {
56 f++;
57 continue;
58 }
59
60 return true;
61 }
62
63 return false;
64 }
65
66 static int create_disk(
67 const char *name,
68 const char *device,
69 const char *password,
70 const char *options) {
71
72 _cleanup_free_ char *p = NULL, *n = NULL, *d = NULL, *u = NULL, *from = NULL, *to = NULL, *e = NULL;
73 _cleanup_fclose_ FILE *f = NULL;
74 bool noauto, nofail, tmp, swap;
75
76 assert(name);
77 assert(device);
78
79 noauto = has_option(options, "noauto");
80 nofail = has_option(options, "nofail");
81 tmp = has_option(options, "tmp");
82 swap = has_option(options, "swap");
83
84 if (tmp && swap) {
85 log_error("Device '%s' cannot be both 'tmp' and 'swap'. Ignoring.", name);
86 return -EINVAL;
87 }
88
89 n = unit_name_from_path_instance("systemd-cryptsetup", name, ".service");
90 if (!n)
91 return log_oom();
92
93 p = strjoin(arg_dest, "/", n, NULL);
94 if (!p)
95 return log_oom();
96
97 u = fstab_node_to_udev_node(device);
98 if (!u)
99 return log_oom();
100
101 d = unit_name_from_path(u, ".device");
102 if (!d)
103 return log_oom();
104
105 f = fopen(p, "wxe");
106 if (!f) {
107 log_error("Failed to create unit file %s: %m", p);
108 return -errno;
109 }
110
111 fputs(
112 "# Automatically generated by systemd-cryptsetup-generator\n\n"
113 "[Unit]\n"
114 "Description=Cryptography Setup for %I\n"
115 "Documentation=man:systemd-cryptsetup@.service(8) man:crypttab(5)\n"
116 "SourcePath=/etc/crypttab\n"
117 "Conflicts=umount.target\n"
118 "DefaultDependencies=no\n"
119 "BindsTo=dev-mapper-%i.device\n"
120 "IgnoreOnIsolate=true\n"
121 "After=systemd-readahead-collect.service systemd-readahead-replay.service\n",
122 f);
123
124 if (!nofail)
125 fprintf(f,
126 "Before=cryptsetup.target\n");
127
128 if (password) {
129 if (streq(password, "/dev/urandom") ||
130 streq(password, "/dev/random") ||
131 streq(password, "/dev/hw_random"))
132 fputs("After=systemd-random-seed.service\n", f);
133
134 else if (!streq(password, "-") && !streq(password, "none")) {
135 _cleanup_free_ char *uu = fstab_node_to_udev_node(password);
136 if (uu == NULL)
137 return log_oom();
138
139 if (is_device_path(uu)) {
140 _cleanup_free_ char *dd = unit_name_from_path(uu, ".device");
141 if (dd == NULL)
142 return log_oom();
143
144 fprintf(f, "After=%1$s\nRequires=%1$s\n", dd);
145 } else
146 fprintf(f, "RequiresMountsFor=%s\n", password);
147 }
148 }
149
150 if (is_device_path(u))
151 fprintf(f,
152 "BindsTo=%s\n"
153 "After=%s\n"
154 "Before=umount.target\n",
155 d, d);
156 else
157 fprintf(f,
158 "RequiresMountsFor=%s\n",
159 u);
160
161 fprintf(f,
162 "\n[Service]\n"
163 "Type=oneshot\n"
164 "RemainAfterExit=yes\n"
165 "TimeoutSec=0\n" /* the binary handles timeouts anyway */
166 "ExecStart=" SYSTEMD_CRYPTSETUP_PATH " attach '%s' '%s' '%s' '%s'\n"
167 "ExecStop=" SYSTEMD_CRYPTSETUP_PATH " detach '%s'\n",
168 name, u, strempty(password), strempty(options),
169 name);
170
171 if (tmp)
172 fprintf(f,
173 "ExecStartPost=/sbin/mke2fs '/dev/mapper/%s'\n",
174 name);
175
176 if (swap)
177 fprintf(f,
178 "ExecStartPost=/sbin/mkswap '/dev/mapper/%s'\n",
179 name);
180
181 fflush(f);
182
183 if (ferror(f)) {
184 log_error("Failed to write file %s: %m", p);
185 return -errno;
186 }
187
188 if (asprintf(&from, "../%s", n) < 0)
189 return log_oom();
190
191 if (!noauto) {
192
193 to = strjoin(arg_dest, "/", d, ".wants/", n, NULL);
194 if (!to)
195 return log_oom();
196
197 mkdir_parents_label(to, 0755);
198 if (symlink(from, to) < 0) {
199 log_error("Failed to create symlink %s: %m", to);
200 return -errno;
201 }
202
203 free(to);
204 if (!nofail)
205 to = strjoin(arg_dest, "/cryptsetup.target.requires/", n, NULL);
206 else
207 to = strjoin(arg_dest, "/cryptsetup.target.wants/", n, NULL);
208 if (!to)
209 return log_oom();
210
211 mkdir_parents_label(to, 0755);
212 if (symlink(from, to) < 0) {
213 log_error("Failed to create symlink %s: %m", to);
214 return -errno;
215 }
216 }
217
218 e = unit_name_escape(name);
219 if (!e)
220 return log_oom();
221
222 free(to);
223 to = strjoin(arg_dest, "/dev-mapper-", e, ".device.requires/", n, NULL);
224 if (!to)
225 return log_oom();
226
227 mkdir_parents_label(to, 0755);
228 if (symlink(from, to) < 0) {
229 log_error("Failed to create symlink %s: %m", to);
230 return -errno;
231 }
232
233 if (!noauto && !nofail) {
234 int r;
235 free(p);
236 p = strjoin(arg_dest, "/dev-mapper-", e, ".device.d/50-job-timeout-sec-0.conf", NULL);
237 if (!p)
238 return log_oom();
239
240 mkdir_parents_label(p, 0755);
241
242 r = write_string_file(p,
243 "# Automatically generated by systemd-cryptsetup-generator\n\n"
244 "[Unit]\n"
245 "JobTimeoutSec=0\n"); /* the binary handles timeouts anyway */
246 if (r)
247 return r;
248 }
249
250 return 0;
251 }
252
253 static int parse_proc_cmdline(
254 char ***arg_proc_cmdline_disks,
255 char ***arg_proc_cmdline_options,
256 char **arg_proc_cmdline_keyfile) {
257
258 _cleanup_free_ char *line = NULL;
259 char *w = NULL, *state = NULL;
260 size_t l;
261 int r;
262
263 r = proc_cmdline(&line);
264 if (r < 0)
265 log_warning("Failed to read /proc/cmdline, ignoring: %s", strerror(-r));
266 if (r <= 0)
267 return 0;
268
269 FOREACH_WORD_QUOTED(w, l, line, state) {
270 _cleanup_free_ char *word = NULL;
271
272 word = strndup(w, l);
273 if (!word)
274 return log_oom();
275
276 if (startswith(word, "luks=")) {
277 r = parse_boolean(word + 5);
278 if (r < 0)
279 log_warning("Failed to parse luks switch %s. Ignoring.", word + 5);
280 else
281 arg_enabled = r;
282
283 } else if (startswith(word, "rd.luks=")) {
284
285 if (in_initrd()) {
286 r = parse_boolean(word + 8);
287 if (r < 0)
288 log_warning("Failed to parse luks switch %s. Ignoring.", word + 8);
289 else
290 arg_enabled = r;
291 }
292
293 } else if (startswith(word, "luks.crypttab=")) {
294 r = parse_boolean(word + 14);
295 if (r < 0)
296 log_warning("Failed to parse luks crypttab switch %s. Ignoring.", word + 14);
297 else
298 arg_read_crypttab = r;
299
300 } else if (startswith(word, "rd.luks.crypttab=")) {
301
302 if (in_initrd()) {
303 r = parse_boolean(word + 17);
304 if (r < 0)
305 log_warning("Failed to parse luks crypttab switch %s. Ignoring.", word + 17);
306 else
307 arg_read_crypttab = r;
308 }
309
310 } else if (startswith(word, "luks.uuid=")) {
311 if (strv_extend(arg_proc_cmdline_disks, word + 10) < 0)
312 return log_oom();
313
314 } else if (startswith(word, "rd.luks.uuid=")) {
315
316 if (in_initrd()) {
317 if (strv_extend(arg_proc_cmdline_disks, word + 13) < 0)
318 return log_oom();
319 }
320
321 } else if (startswith(word, "luks.options=")) {
322 if (strv_extend(arg_proc_cmdline_options, word + 13) < 0)
323 return log_oom();
324
325 } else if (startswith(word, "rd.luks.options=")) {
326
327 if (in_initrd()) {
328 if (strv_extend(arg_proc_cmdline_options, word + 16) < 0)
329 return log_oom();
330 }
331
332 } else if (startswith(word, "luks.key=")) {
333 if (*arg_proc_cmdline_keyfile)
334 free(*arg_proc_cmdline_keyfile);
335 *arg_proc_cmdline_keyfile = strdup(word + 9);
336 if (!*arg_proc_cmdline_keyfile)
337 return log_oom();
338
339 } else if (startswith(word, "rd.luks.key=")) {
340
341 if (in_initrd()) {
342 if (*arg_proc_cmdline_keyfile)
343 free(*arg_proc_cmdline_keyfile);
344 *arg_proc_cmdline_keyfile = strdup(word + 12);
345 if (!*arg_proc_cmdline_keyfile)
346 return log_oom();
347 }
348
349 } else if (startswith(word, "luks.") ||
350 (in_initrd() && startswith(word, "rd.luks."))) {
351
352 log_warning("Unknown kernel switch %s. Ignoring.", word);
353 }
354 }
355
356 strv_uniq(*arg_proc_cmdline_disks);
357
358 return 0;
359 }
360
361 int main(int argc, char *argv[]) {
362 _cleanup_strv_free_ char **arg_proc_cmdline_disks_done = NULL;
363 _cleanup_strv_free_ char **arg_proc_cmdline_disks = NULL;
364 _cleanup_strv_free_ char **arg_proc_cmdline_options = NULL;
365 _cleanup_free_ char *arg_proc_cmdline_keyfile = NULL;
366 _cleanup_fclose_ FILE *f = NULL;
367 unsigned n = 0;
368 int r = EXIT_SUCCESS;
369 char **i;
370
371 if (argc > 1 && argc != 4) {
372 log_error("This program takes three or no arguments.");
373 return EXIT_FAILURE;
374 }
375
376 if (argc > 1)
377 arg_dest = argv[1];
378
379 log_set_target(LOG_TARGET_SAFE);
380 log_parse_environment();
381 log_open();
382
383 umask(0022);
384
385 if (parse_proc_cmdline(&arg_proc_cmdline_disks, &arg_proc_cmdline_options, &arg_proc_cmdline_keyfile) < 0)
386 return EXIT_FAILURE;
387
388 if (!arg_enabled)
389 return EXIT_SUCCESS;
390
391 if (arg_read_crypttab) {
392 struct stat st;
393
394 f = fopen("/etc/crypttab", "re");
395 if (!f) {
396 if (errno == ENOENT)
397 r = EXIT_SUCCESS;
398 else {
399 r = EXIT_FAILURE;
400 log_error("Failed to open /etc/crypttab: %m");
401 }
402
403 goto next;
404 }
405
406 if (fstat(fileno(f), &st) < 0) {
407 log_error("Failed to stat /etc/crypttab: %m");
408 r = EXIT_FAILURE;
409 goto next;
410 }
411
412 /* If we readd support for specifying passphrases
413 * directly in crypttabe we should upgrade the warning
414 * below, though possibly only if a passphrase is
415 * specified directly. */
416 if (st.st_mode & 0005)
417 log_debug("/etc/crypttab is world-readable. This is usually not a good idea.");
418
419 for (;;) {
420 char line[LINE_MAX], *l;
421 _cleanup_free_ char *name = NULL, *device = NULL, *password = NULL, *options = NULL;
422 int k;
423
424 if (!fgets(line, sizeof(line), f))
425 break;
426
427 n++;
428
429 l = strstrip(line);
430 if (*l == '#' || *l == 0)
431 continue;
432
433 k = sscanf(l, "%ms %ms %ms %ms", &name, &device, &password, &options);
434 if (k < 2 || k > 4) {
435 log_error("Failed to parse /etc/crypttab:%u, ignoring.", n);
436 r = EXIT_FAILURE;
437 continue;
438 }
439
440 if (arg_proc_cmdline_options) {
441 /*
442 If options are specified on the kernel commandline, let them override
443 the ones from crypttab.
444 */
445 STRV_FOREACH(i, arg_proc_cmdline_options) {
446 _cleanup_free_ char *proc_uuid = NULL, *proc_options = NULL;
447 const char *p = *i;
448
449 k = sscanf(p, "%m[0-9a-fA-F-]=%ms", &proc_uuid, &proc_options);
450 if (k == 2 && streq(proc_uuid, device + 5)) {
451 if (options)
452 free(options);
453 options = strdup(p);
454 if (!proc_options)
455 return log_oom();
456 }
457 }
458 }
459
460 if (arg_proc_cmdline_disks) {
461 /*
462 If luks UUIDs are specified on the kernel command line, use them as a filter
463 for /etc/crypttab and only generate units for those.
464 */
465 STRV_FOREACH(i, arg_proc_cmdline_disks) {
466 _cleanup_free_ char *proc_device = NULL, *proc_name = NULL;
467 const char *p = *i;
468
469 if (startswith(p, "luks-"))
470 p += 5;
471
472 proc_name = strappend("luks-", p);
473 proc_device = strappend("UUID=", p);
474
475 if (!proc_name || !proc_device)
476 return log_oom();
477
478 if (streq(proc_device, device) || streq(proc_name, name)) {
479 if (create_disk(name, device, password, options) < 0)
480 r = EXIT_FAILURE;
481
482 if (strv_extend(&arg_proc_cmdline_disks_done, p) < 0)
483 return log_oom();
484 }
485 }
486 } else {
487 if (create_disk(name, device, password, options) < 0)
488 r = EXIT_FAILURE;
489 }
490 }
491 }
492
493 next:
494 STRV_FOREACH(i, arg_proc_cmdline_disks) {
495 /*
496 Generate units for those UUIDs, which were specified
497 on the kernel command line and not yet written.
498 */
499
500 _cleanup_free_ char *name = NULL, *device = NULL, *options = NULL;
501 const char *p = *i;
502
503 if (startswith(p, "luks-"))
504 p += 5;
505
506 if (strv_contains(arg_proc_cmdline_disks_done, p))
507 continue;
508
509 name = strappend("luks-", p);
510 device = strappend("UUID=", p);
511
512 if (!name || !device)
513 return log_oom();
514
515 if (arg_proc_cmdline_options) {
516 /*
517 If options are specified on the kernel commandline, use them.
518 */
519 char **j;
520
521 STRV_FOREACH(j, arg_proc_cmdline_options) {
522 _cleanup_free_ char *proc_uuid = NULL, *proc_options = NULL;
523 const char *s = *j;
524 int k;
525
526 k = sscanf(s, "%m[0-9a-fA-F-]=%ms", &proc_uuid, &proc_options);
527 if (k == 2) {
528 if (streq(proc_uuid, device + 5)) {
529 if (options)
530 free(options);
531 options = strdup(proc_options);
532 if (!options)
533 return log_oom();
534 }
535 } else if (!options) {
536 /*
537 Fall back to options without a specified UUID
538 */
539 options = strdup(s);
540 if (!options)
541 return log_oom();
542 }
543 }
544 }
545
546 if (!options) {
547 options = strdup("timeout=0");
548 if (!options)
549 return log_oom();
550 }
551
552 if (create_disk(name, device, arg_proc_cmdline_keyfile, options) < 0)
553 r = EXIT_FAILURE;
554 }
555
556 return r;
557 }