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