chiark / gitweb /
remove unused includes
[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
24 #include "dropin.h"
25 #include "generator.h"
26 #include "hashmap.h"
27 #include "log.h"
28 #include "mkdir.h"
29 #include "path-util.h"
30 #include "fstab-util.h"
31 #include "strv.h"
32 #include "unit-name.h"
33 #include "util.h"
34
35 typedef struct crypto_device {
36         char *uuid;
37         char *keyfile;
38         char *name;
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 int create_disk(
52                 const char *name,
53                 const char *device,
54                 const char *password,
55                 const char *options) {
56
57         _cleanup_free_ char *p = NULL, *n = NULL, *d = NULL, *u = NULL, *to = NULL, *e = NULL,
58                 *filtered = NULL;
59         _cleanup_fclose_ FILE *f = NULL;
60         bool noauto, nofail, tmp, swap;
61         char *from;
62         int r;
63
64         assert(name);
65         assert(device);
66
67         noauto = fstab_test_yes_no_option(options, "noauto\0" "auto\0");
68         nofail = fstab_test_yes_no_option(options, "nofail\0" "fail\0");
69         tmp = fstab_test_option(options, "tmp\0");
70         swap = fstab_test_option(options, "swap\0");
71
72         if (tmp && swap) {
73                 log_error("Device '%s' cannot be both 'tmp' and 'swap'. Ignoring.", name);
74                 return -EINVAL;
75         }
76
77         e = unit_name_escape(name);
78         if (!e)
79                 return log_oom();
80
81         n = unit_name_build("systemd-cryptsetup", e, ".service");
82         if (!n)
83                 return log_oom();
84
85         p = strjoin(arg_dest, "/", n, NULL);
86         if (!p)
87                 return log_oom();
88
89         u = fstab_node_to_udev_node(device);
90         if (!u)
91                 return log_oom();
92
93         d = unit_name_from_path(u, ".device");
94         if (!d)
95                 return log_oom();
96
97         f = fopen(p, "wxe");
98         if (!f)
99                 return log_error_errno(errno, "Failed to create unit file %s: %m", p);
100
101         fputs(
102                 "# Automatically generated by systemd-cryptsetup-generator\n\n"
103                 "[Unit]\n"
104                 "Description=Cryptography Setup for %I\n"
105                 "Documentation=man:crypttab(5) man:systemd-cryptsetup-generator(8) man:systemd-cryptsetup@.service(8)\n"
106                 "SourcePath=/etc/crypttab\n"
107                 "DefaultDependencies=no\n"
108                 "Conflicts=umount.target\n"
109                 "BindsTo=dev-mapper-%i.device\n"
110                 "IgnoreOnIsolate=true\n"
111                 "After=cryptsetup-pre.target\n",
112                 f);
113
114         if (!nofail)
115                 fprintf(f,
116                         "Before=cryptsetup.target\n");
117
118         if (password) {
119                 if (STR_IN_SET(password, "/dev/urandom", "/dev/random", "/dev/hw_random"))
120                         fputs("After=systemd-random-seed.service\n", f);
121                 else if (!streq(password, "-") && !streq(password, "none")) {
122                         _cleanup_free_ char *uu;
123
124                         uu = fstab_node_to_udev_node(password);
125                         if (!uu)
126                                 return log_oom();
127
128                         if (!path_equal(uu, "/dev/null")) {
129
130                                 if (is_device_path(uu)) {
131                                         _cleanup_free_ char *dd;
132
133                                         dd = unit_name_from_path(uu, ".device");
134                                         if (!dd)
135                                                 return log_oom();
136
137                                         fprintf(f, "After=%1$s\nRequires=%1$s\n", dd);
138                                 } else
139                                         fprintf(f, "RequiresMountsFor=%s\n", password);
140                         }
141                 }
142         }
143
144         if (is_device_path(u))
145                 fprintf(f,
146                         "BindsTo=%s\n"
147                         "After=%s\n"
148                         "Before=umount.target\n",
149                         d, d);
150         else
151                 fprintf(f,
152                         "RequiresMountsFor=%s\n",
153                         u);
154
155         r = generator_write_timeouts(arg_dest, device, name, options, &filtered);
156         if (r < 0)
157                 return r;
158
159         fprintf(f,
160                 "\n[Service]\n"
161                 "Type=oneshot\n"
162                 "RemainAfterExit=yes\n"
163                 "TimeoutSec=0\n" /* the binary handles timeouts anyway */
164                 "ExecStart=" SYSTEMD_CRYPTSETUP_PATH " attach '%s' '%s' '%s' '%s'\n"
165                 "ExecStop=" SYSTEMD_CRYPTSETUP_PATH " detach '%s'\n",
166                 name, u, strempty(password), strempty(filtered),
167                 name);
168
169         if (tmp)
170                 fprintf(f,
171                         "ExecStartPost=/sbin/mke2fs '/dev/mapper/%s'\n",
172                         name);
173
174         if (swap)
175                 fprintf(f,
176                         "ExecStartPost=/sbin/mkswap '/dev/mapper/%s'\n",
177                         name);
178
179         fflush(f);
180         if (ferror(f))
181                 return log_error_errno(errno, "Failed to write file %s: %m", p);
182
183         from = strjoina("../", n);
184
185         if (!noauto) {
186
187                 to = strjoin(arg_dest, "/", d, ".wants/", n, NULL);
188                 if (!to)
189                         return log_oom();
190
191                 mkdir_parents_label(to, 0755);
192                 if (symlink(from, to) < 0)
193                         return log_error_errno(errno, "Failed to create symlink %s: %m", to);
194
195                 free(to);
196                 if (!nofail)
197                         to = strjoin(arg_dest, "/cryptsetup.target.requires/", n, NULL);
198                 else
199                         to = strjoin(arg_dest, "/cryptsetup.target.wants/", n, NULL);
200                 if (!to)
201                         return log_oom();
202
203                 mkdir_parents_label(to, 0755);
204                 if (symlink(from, to) < 0)
205                         return log_error_errno(errno, "Failed to create symlink %s: %m", to);
206         }
207
208         free(to);
209         to = strjoin(arg_dest, "/dev-mapper-", e, ".device.requires/", n, NULL);
210         if (!to)
211                 return log_oom();
212
213         mkdir_parents_label(to, 0755);
214         if (symlink(from, to) < 0)
215                 return log_error_errno(errno, "Failed to create symlink %s: %m", to);
216
217         if (!noauto && !nofail) {
218                 _cleanup_free_ char *dmname;
219                 dmname = strjoin("dev-mapper-", e, ".device", NULL);
220                 if (!dmname)
221                         return log_oom();
222
223                 r = write_drop_in(arg_dest, dmname, 90, "device-timeout",
224                                   "# Automatically generated by systemd-cryptsetup-generator \n\n"
225                                   "[Unit]\nJobTimeoutSec=0");
226                 if (r < 0)
227                         return log_error_errno(r, "Failed to write device drop-in: %m");
228         }
229
230         return 0;
231 }
232
233 static void free_arg_disks(void) {
234         crypto_device *d;
235
236         while ((d = hashmap_steal_first(arg_disks))) {
237                 free(d->uuid);
238                 free(d->keyfile);
239                 free(d->name);
240                 free(d->options);
241                 free(d);
242         }
243
244         hashmap_free(arg_disks);
245 }
246
247 static crypto_device *get_crypto_device(const char *uuid) {
248         int r;
249         crypto_device *d;
250
251         assert(uuid);
252
253         d = hashmap_get(arg_disks, uuid);
254         if (!d) {
255                 d = new0(struct crypto_device, 1);
256                 if (!d)
257                         return NULL;
258
259                 d->create = false;
260                 d->keyfile = d->options = d->name = NULL;
261
262                 d->uuid = strdup(uuid);
263                 if (!d->uuid) {
264                         free(d);
265                         return NULL;
266                 }
267
268                 r = hashmap_put(arg_disks, d->uuid, d);
269                 if (r < 0) {
270                         free(d->uuid);
271                         free(d);
272                         return NULL;
273                 }
274         }
275
276         return d;
277 }
278
279 static int parse_proc_cmdline_item(const char *key, const char *value) {
280         int r;
281         crypto_device *d;
282         _cleanup_free_ char *uuid = NULL, *uuid_value = NULL;
283
284         if (STR_IN_SET(key, "luks", "rd.luks") && value) {
285
286                 r = parse_boolean(value);
287                 if (r < 0)
288                         log_warning("Failed to parse luks switch %s. Ignoring.", value);
289                 else
290                         arg_enabled = r;
291
292         } else if (STR_IN_SET(key, "luks.crypttab", "rd.luks.crypttab") && value) {
293
294                 r = parse_boolean(value);
295                 if (r < 0)
296                         log_warning("Failed to parse luks crypttab switch %s. Ignoring.", value);
297                 else
298                         arg_read_crypttab = r;
299
300         } else if (STR_IN_SET(key, "luks.uuid", "rd.luks.uuid") && value) {
301
302                 d = get_crypto_device(startswith(value, "luks-") ? value+5 : value);
303                 if (!d)
304                         return log_oom();
305
306                 d->create = arg_whitelist = true;
307
308         } else if (STR_IN_SET(key, "luks.options", "rd.luks.options") && value) {
309
310                 r = sscanf(value, "%m[0-9a-fA-F-]=%ms", &uuid, &uuid_value);
311                 if (r == 2) {
312                         d = get_crypto_device(uuid);
313                         if (!d)
314                                 return log_oom();
315
316                         free(d->options);
317                         d->options = uuid_value;
318                         uuid_value = NULL;
319                 } else if (free_and_strdup(&arg_default_options, value) < 0)
320                         return log_oom();
321
322         } else if (STR_IN_SET(key, "luks.key", "rd.luks.key") && value) {
323
324                 r = sscanf(value, "%m[0-9a-fA-F-]=%ms", &uuid, &uuid_value);
325                 if (r == 2) {
326                         d = get_crypto_device(uuid);
327                         if (!d)
328                                 return log_oom();
329
330                         free(d->keyfile);
331                         d->keyfile = uuid_value;
332                         uuid_value = NULL;
333                 } else if (free_and_strdup(&arg_default_keyfile, value))
334                         return log_oom();
335
336         } else if (STR_IN_SET(key, "luks.name", "rd.luks.name") && value) {
337
338                 r = sscanf(value, "%m[0-9a-fA-F-]=%ms", &uuid, &uuid_value);
339                 if (r == 2) {
340                         d = get_crypto_device(uuid);
341                         if (!d)
342                                 return log_oom();
343
344                         d->create = arg_whitelist = true;
345
346                         free(d->name);
347                         d->name = uuid_value;
348                         uuid_value = NULL;
349                 } else
350                         log_warning("Failed to parse luks name switch %s. Ignoring.", value);
351
352         }
353
354         return 0;
355 }
356
357 static int add_crypttab_devices(void) {
358         struct stat st;
359         unsigned crypttab_line = 0;
360         _cleanup_fclose_ FILE *f = NULL;
361
362         if (!arg_read_crypttab)
363                 return 0;
364
365         f = fopen("/etc/crypttab", "re");
366         if (!f) {
367                 if (errno != ENOENT)
368                         log_error_errno(errno, "Failed to open /etc/crypttab: %m");
369                 return 0;
370         }
371
372         if (fstat(fileno(f), &st) < 0) {
373                 log_error_errno(errno, "Failed to stat /etc/crypttab: %m");
374                 return 0;
375         }
376
377         /* If we readd support for specifying passphrases
378          * directly in crypttab we should upgrade the warning
379          * below, though possibly only if a passphrase is
380          * specified directly. */
381         if (st.st_mode & 0005)
382                 log_debug("/etc/crypttab is world-readable. This is usually not a good idea.");
383
384         for (;;) {
385                 int r, k;
386                 char line[LINE_MAX], *l, *uuid;
387                 crypto_device *d = NULL;
388                 _cleanup_free_ char *name = NULL, *device = NULL, *keyfile = NULL, *options = NULL;
389
390                 if (!fgets(line, sizeof(line), f))
391                         break;
392
393                 crypttab_line++;
394
395                 l = strstrip(line);
396                 if (*l == '#' || *l == 0)
397                         continue;
398
399                 k = sscanf(l, "%ms %ms %ms %ms", &name, &device, &keyfile, &options);
400                 if (k < 2 || k > 4) {
401                         log_error("Failed to parse /etc/crypttab:%u, ignoring.", crypttab_line);
402                         continue;
403                 }
404
405                 uuid = startswith(device, "UUID=");
406                 if (!uuid)
407                         uuid = path_startswith(device, "/dev/disk/by-uuid/");
408                 if (!uuid)
409                         uuid = startswith(name, "luks-");
410                 if (uuid)
411                         d = hashmap_get(arg_disks, uuid);
412
413                 if (arg_whitelist && !d) {
414                         log_info("Not creating device '%s' because it was not specified on the kernel command line.", name);
415                         continue;
416                 }
417
418                 r = create_disk(name, device, keyfile, (d && d->options) ? d->options : options);
419                 if (r < 0)
420                         return r;
421
422                 if (d)
423                         d->create = false;
424         }
425
426         return 0;
427 }
428
429 static int add_proc_cmdline_devices(void) {
430         int r;
431         Iterator i;
432         crypto_device *d;
433
434         HASHMAP_FOREACH(d, arg_disks, i) {
435                 const char *options;
436                 _cleanup_free_ char *device = NULL;
437
438                 if (!d->create)
439                         continue;
440
441                 if (!d->name) {
442                         d->name = strappend("luks-", d->uuid);
443                         if (!d->name)
444                                 return log_oom();
445                 }
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(d->name, device, d->keyfile ?: 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 }