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