chiark / gitweb /
cryptsetup-generator: Add support for UUID-specific key files on kernel command line
[elogind.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 }