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