]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/cryptsetup/cryptsetup-generator.c
cryptsetup: introduce new cryptsetup-pre.traget unit so that services can make sure...
[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:crypttab(5) man:systemd-cryptsetup-generator(8) man:systemd-cryptsetup@.service(8)\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 cryptsetup-pre.target\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_item(const char *key, const char *value) {
259 int r;
260
261 if (STR_IN_SET(key, "luks", "rd.luks") && value) {
262
263 r = parse_boolean(value);
264 if (r < 0)
265 log_warning("Failed to parse luks switch %s. Ignoring.", value);
266 else
267 arg_enabled = r;
268
269 } else if (STR_IN_SET(key, "luks.crypttab", "rd.luks.crypttab") && value) {
270
271 r = parse_boolean(value);
272 if (r < 0)
273 log_warning("Failed to parse luks crypttab switch %s. Ignoring.", value);
274 else
275 arg_read_crypttab = r;
276
277 } else if (STR_IN_SET(key, "luks.uuid", "rd.luks.uuid") && value) {
278
279 if (strv_extend(&arg_disks, value) < 0)
280 return log_oom();
281
282 } else if (STR_IN_SET(key, "luks.options", "rd.luks.options") && value) {
283
284 if (strv_extend(&arg_options, value) < 0)
285 return log_oom();
286
287 } else if (STR_IN_SET(key, "luks.key", "rd.luks.key") && value) {
288
289 free(arg_keyfile);
290 arg_keyfile = strdup(value);
291 if (!arg_keyfile)
292 return log_oom();
293
294 } else if (startswith(key, "luks.") || startswith(key, "rd.luks."))
295 log_warning("Unknown kernel switch %s. Ignoring.", key);
296
297 return 0;
298 }
299
300 int main(int argc, char *argv[]) {
301 _cleanup_strv_free_ char **disks_done = NULL;
302 _cleanup_fclose_ FILE *f = NULL;
303 unsigned n = 0;
304 int r = EXIT_FAILURE, r2 = EXIT_FAILURE;
305 char **i;
306
307 if (argc > 1 && argc != 4) {
308 log_error("This program takes three or no arguments.");
309 return EXIT_FAILURE;
310 }
311
312 if (argc > 1)
313 arg_dest = argv[1];
314
315 log_set_target(LOG_TARGET_SAFE);
316 log_parse_environment();
317 log_open();
318
319 umask(0022);
320
321 if (parse_proc_cmdline(parse_proc_cmdline_item) < 0)
322 goto cleanup;
323
324 if (!arg_enabled) {
325 r = r2 = EXIT_SUCCESS;
326 goto cleanup;
327 }
328
329 strv_uniq(arg_disks);
330
331 if (arg_read_crypttab) {
332 struct stat st;
333
334 f = fopen("/etc/crypttab", "re");
335 if (!f) {
336 if (errno == ENOENT)
337 r = EXIT_SUCCESS;
338 else
339 log_error("Failed to open /etc/crypttab: %m");
340
341 goto next;
342 }
343
344 if (fstat(fileno(f), &st) < 0) {
345 log_error("Failed to stat /etc/crypttab: %m");
346 goto next;
347 }
348
349 /* If we readd support for specifying passphrases
350 * directly in crypttabe we should upgrade the warning
351 * below, though possibly only if a passphrase is
352 * specified directly. */
353 if (st.st_mode & 0005)
354 log_debug("/etc/crypttab is world-readable. This is usually not a good idea.");
355
356 for (;;) {
357 char line[LINE_MAX], *l;
358 _cleanup_free_ char *name = NULL, *device = NULL, *password = NULL, *options = NULL;
359 int k;
360
361 if (!fgets(line, sizeof(line), f))
362 break;
363
364 n++;
365
366 l = strstrip(line);
367 if (*l == '#' || *l == 0)
368 continue;
369
370 k = sscanf(l, "%ms %ms %ms %ms", &name, &device, &password, &options);
371 if (k < 2 || k > 4) {
372 log_error("Failed to parse /etc/crypttab:%u, ignoring.", n);
373 continue;
374 }
375
376 /*
377 If options are specified on the kernel commandline, let them override
378 the ones from crypttab.
379 */
380 STRV_FOREACH(i, arg_options) {
381 _cleanup_free_ char *proc_uuid = NULL, *proc_options = NULL;
382 const char *p = *i;
383
384 k = sscanf(p, "%m[0-9a-fA-F-]=%ms", &proc_uuid, &proc_options);
385 if (k == 2 && streq(proc_uuid, device + 5)) {
386 free(options);
387 options = strdup(p);
388 if (!proc_options) {
389 log_oom();
390 goto cleanup;
391 }
392 }
393 }
394
395 if (arg_disks) {
396 /*
397 If luks UUIDs are specified on the kernel command line, use them as a filter
398 for /etc/crypttab and only generate units for those.
399 */
400 STRV_FOREACH(i, arg_disks) {
401 _cleanup_free_ char *proc_device = NULL, *proc_name = NULL;
402 const char *p = *i;
403
404 if (startswith(p, "luks-"))
405 p += 5;
406
407 proc_name = strappend("luks-", p);
408 proc_device = strappend("UUID=", p);
409
410 if (!proc_name || !proc_device) {
411 log_oom();
412 goto cleanup;
413 }
414
415 if (streq(proc_device, device) || streq(proc_name, name)) {
416 if (create_disk(name, device, password, options) < 0)
417 goto cleanup;
418
419 if (strv_extend(&disks_done, p) < 0) {
420 log_oom();
421 goto cleanup;
422 }
423 }
424 }
425 } else if (create_disk(name, device, password, options) < 0)
426 goto cleanup;
427
428 }
429 }
430
431 r = EXIT_SUCCESS;
432
433 next:
434 STRV_FOREACH(i, arg_disks) {
435 /*
436 Generate units for those UUIDs, which were specified
437 on the kernel command line and not yet written.
438 */
439
440 _cleanup_free_ char *name = NULL, *device = NULL, *options = NULL;
441 const char *p = *i;
442
443 if (startswith(p, "luks-"))
444 p += 5;
445
446 if (strv_contains(disks_done, p))
447 continue;
448
449 name = strappend("luks-", p);
450 device = strappend("UUID=", p);
451
452 if (!name || !device) {
453 log_oom();
454 goto cleanup;
455 }
456
457 if (arg_options) {
458 /*
459 If options are specified on the kernel commandline, use them.
460 */
461 char **j;
462
463 STRV_FOREACH(j, arg_options) {
464 _cleanup_free_ char *proc_uuid = NULL, *proc_options = NULL;
465 const char *s = *j;
466 int k;
467
468 k = sscanf(s, "%m[0-9a-fA-F-]=%ms", &proc_uuid, &proc_options);
469 if (k == 2) {
470 if (streq(proc_uuid, device + 5)) {
471 free(options);
472 options = proc_options;
473 proc_options = NULL;
474 }
475 } else if (!options) {
476 /*
477 Fall back to options without a specified UUID
478 */
479 options = strdup(s);
480 if (!options) {
481 log_oom();
482 goto cleanup;
483 };
484 }
485 }
486 }
487
488 if (!options) {
489 options = strdup("timeout=0");
490 if (!options) {
491 log_oom();
492 goto cleanup;
493 }
494 }
495
496 if (create_disk(name, device, arg_keyfile, options) < 0)
497 goto cleanup;
498 }
499
500 r2 = EXIT_SUCCESS;
501
502 cleanup:
503 strv_free(arg_disks);
504 strv_free(arg_options);
505 free(arg_keyfile);
506
507 return r != EXIT_SUCCESS ? r : r2;
508 }