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