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