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