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