chiark / gitweb /
cryptsetup-generator: allow specifying options in /proc/cmdline
[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_options, 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.options=")) {
311                         if (strv_extend(arg_proc_cmdline_options, word + 13) < 0)
312                                 return log_oom();
313
314                 } else if (startswith(word, "rd.luks.options=")) {
315
316                         if (in_initrd()) {
317                                 if (strv_extend(arg_proc_cmdline_options, word + 16) < 0)
318                                         return log_oom();
319                         }
320
321                 } else if (startswith(word, "luks.key=")) {
322                         if (*arg_proc_cmdline_keyfile)
323                                 free(*arg_proc_cmdline_keyfile);
324                         *arg_proc_cmdline_keyfile = strdup(word + 9);
325                         if (!*arg_proc_cmdline_keyfile)
326                                 return log_oom();
327
328                 } else if (startswith(word, "rd.luks.key=")) {
329
330                         if (in_initrd()) {
331                                 if (*arg_proc_cmdline_keyfile)
332                                         free(*arg_proc_cmdline_keyfile);
333                                 *arg_proc_cmdline_keyfile = strdup(word + 12);
334                                 if (!*arg_proc_cmdline_keyfile)
335                                         return log_oom();
336                         }
337
338                 } else if (startswith(word, "luks.") ||
339                            (in_initrd() && startswith(word, "rd.luks."))) {
340
341                         log_warning("Unknown kernel switch %s. Ignoring.", word);
342                 }
343         }
344
345         strv_uniq(*arg_proc_cmdline_disks);
346
347         return 0;
348 }
349
350 int main(int argc, char *argv[]) {
351         _cleanup_strv_free_ char **arg_proc_cmdline_disks_done = NULL;
352         _cleanup_strv_free_ char **arg_proc_cmdline_disks = NULL;
353         _cleanup_strv_free_ char **arg_proc_cmdline_options = NULL;
354         _cleanup_free_ char *arg_proc_cmdline_keyfile = NULL;
355         _cleanup_fclose_ FILE *f = NULL;
356         unsigned n = 0;
357         int r = EXIT_SUCCESS;
358         char **i;
359
360         if (argc > 1 && argc != 4) {
361                 log_error("This program takes three or no arguments.");
362                 return EXIT_FAILURE;
363         }
364
365         if (argc > 1)
366                 arg_dest = argv[1];
367
368         log_set_target(LOG_TARGET_SAFE);
369         log_parse_environment();
370         log_open();
371
372         umask(0022);
373
374         if (parse_proc_cmdline(&arg_proc_cmdline_disks, &arg_proc_cmdline_options, &arg_proc_cmdline_keyfile) < 0)
375                 return EXIT_FAILURE;
376
377         if (!arg_enabled)
378                 return EXIT_SUCCESS;
379
380         if (arg_read_crypttab) {
381                 struct stat st;
382
383                 f = fopen("/etc/crypttab", "re");
384                 if (!f) {
385                         if (errno == ENOENT)
386                                 r = EXIT_SUCCESS;
387                         else {
388                                 r = EXIT_FAILURE;
389                                 log_error("Failed to open /etc/crypttab: %m");
390                         }
391
392                         goto next;
393                 }
394
395                 if (fstat(fileno(f), &st) < 0) {
396                         log_error("Failed to stat /etc/crypttab: %m");
397                         r = EXIT_FAILURE;
398                         goto next;
399                 }
400
401                 /* If we readd support for specifying passphrases
402                  * directly in crypttabe we should upgrade the warning
403                  * below, though possibly only if a passphrase is
404                  * specified directly. */
405                 if (st.st_mode & 0005)
406                         log_debug("/etc/crypttab is world-readable. This is usually not a good idea.");
407
408                 for (;;) {
409                         char line[LINE_MAX], *l;
410                         _cleanup_free_ char *name = NULL, *device = NULL, *password = NULL, *options = NULL;
411                         int k;
412
413                         if (!fgets(line, sizeof(line), f))
414                                 break;
415
416                         n++;
417
418                         l = strstrip(line);
419                         if (*l == '#' || *l == 0)
420                                 continue;
421
422                         k = sscanf(l, "%ms %ms %ms %ms", &name, &device, &password, &options);
423                         if (k < 2 || k > 4) {
424                                 log_error("Failed to parse /etc/crypttab:%u, ignoring.", n);
425                                 r = EXIT_FAILURE;
426                                 continue;
427                         }
428
429                         if (arg_proc_cmdline_options) {
430                                 /*
431                                   If options are specified on the kernel commandline, let them override
432                                   the ones from crypttab.
433                                 */
434                                 STRV_FOREACH(i, arg_proc_cmdline_options) {
435                                         _cleanup_free_ char *proc_uuid = NULL, *proc_options = NULL;
436                                         const char *p = *i;
437
438                                         k = sscanf(p, "%m[0-9a-fA-F-]=%ms", &proc_uuid, &proc_options);
439                                         if (k == 2 && streq(proc_uuid, device + 5)) {
440                                                 if (options)
441                                                         free(options);
442                                                 options = strdup(p);
443                                                 if (!proc_options)
444                                                         return log_oom();
445                                         }
446                                 }
447                         }
448
449                         if (arg_proc_cmdline_disks) {
450                                 /*
451                                   If luks UUIDs are specified on the kernel command line, use them as a filter
452                                   for /etc/crypttab and only generate units for those.
453                                 */
454                                 STRV_FOREACH(i, arg_proc_cmdline_disks) {
455                                         _cleanup_free_ char *proc_device = NULL, *proc_name = NULL;
456                                         const char *p = *i;
457
458                                         if (startswith(p, "luks-"))
459                                                 p += 5;
460
461                                         proc_name = strappend("luks-", p);
462                                         proc_device = strappend("UUID=", p);
463
464                                         if (!proc_name || !proc_device)
465                                                 return log_oom();
466
467                                         if (streq(proc_device, device) || streq(proc_name, name)) {
468                                                 if (create_disk(name, device, password, options) < 0)
469                                                         r = EXIT_FAILURE;
470
471                                                 if (strv_extend(&arg_proc_cmdline_disks_done, p) < 0)
472                                                         return log_oom();
473                                         }
474                                 }
475                         } else {
476                                 if (create_disk(name, device, password, options) < 0)
477                                         r = EXIT_FAILURE;
478                         }
479                 }
480         }
481
482 next:
483         STRV_FOREACH(i, arg_proc_cmdline_disks) {
484                 /*
485                   Generate units for those UUIDs, which were specified
486                   on the kernel command line and not yet written.
487                 */
488
489                 _cleanup_free_ char *name = NULL, *device = NULL, *options = NULL;
490                 const char *p = *i;
491
492                 if (startswith(p, "luks-"))
493                         p += 5;
494
495                 if (strv_contains(arg_proc_cmdline_disks_done, p))
496                         continue;
497
498                 name = strappend("luks-", p);
499                 device = strappend("UUID=", p);
500
501                 if (!name || !device)
502                         return log_oom();
503
504                 if (arg_proc_cmdline_options) {
505                         /*
506                           If options are specified on the kernel commandline, use them.
507                         */
508                         char **j;
509
510                         STRV_FOREACH(j, arg_proc_cmdline_options) {
511                                 _cleanup_free_ char *proc_uuid = NULL, *proc_options = NULL;
512                                 const char *s = *j;
513                                 int k;
514
515                                 k = sscanf(s, "%m[0-9a-fA-F-]=%ms", &proc_uuid, &proc_options);
516                                 if (k == 2) {
517                                         if (streq(proc_uuid, device + 5)) {
518                                                 if (options)
519                                                         free(options);
520                                                 options = strdup(proc_options);
521                                                 if (!options)
522                                                         return log_oom();
523                                         }
524                                 } else if (!options) {
525                                         /*
526                                           Fall back to options without a specified UUID
527                                         */
528                                         options = strdup(s);
529                                         if (!options)
530                                                 return log_oom();
531                                 }
532                         }
533                 }
534
535                 if (!options) {
536                         options = strdup("timeout=0");
537                         if (!options)
538                                 return log_oom();
539                 }
540
541                 if (create_disk(name, device, arg_proc_cmdline_keyfile, options) < 0)
542                         r = EXIT_FAILURE;
543         }
544
545         return r;
546 }