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