chiark / gitweb /
c1581ef9c8a7be55a18044eed0c2356803780631
[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 *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 }