chiark / gitweb /
test: always use assert_se in tests
[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
37 static char **arg_disks;
38 static char **arg_options;
39 static char *arg_keyfile;
40
41
42 static bool has_option(const char *haystack, const char *needle) {
43         const char *f = haystack;
44         size_t l;
45
46         assert(needle);
47
48         if (!haystack)
49                 return false;
50
51         l = strlen(needle);
52
53         while ((f = strstr(f, needle))) {
54
55                 if (f > haystack && f[-1] != ',') {
56                         f++;
57                         continue;
58                 }
59
60                 if (f[l] != 0 && f[l] != ',') {
61                         f++;
62                         continue;
63                 }
64
65                 return true;
66         }
67
68         return false;
69 }
70
71 static int create_disk(
72                 const char *name,
73                 const char *device,
74                 const char *password,
75                 const char *options) {
76
77         _cleanup_free_ char *p = NULL, *n = NULL, *d = NULL, *u = NULL, *from = NULL, *to = NULL, *e = NULL;
78         _cleanup_fclose_ FILE *f = NULL;
79         bool noauto, nofail, tmp, swap;
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         n = unit_name_from_path_instance("systemd-cryptsetup", name, ".service");
95         if (!n)
96                 return log_oom();
97
98         p = strjoin(arg_dest, "/", n, NULL);
99         if (!p)
100                 return log_oom();
101
102         u = fstab_node_to_udev_node(device);
103         if (!u)
104                 return log_oom();
105
106         d = unit_name_from_path(u, ".device");
107         if (!d)
108                 return log_oom();
109
110         f = fopen(p, "wxe");
111         if (!f) {
112                 log_error("Failed to create unit file %s: %m", p);
113                 return -errno;
114         }
115
116         fputs(
117                 "# Automatically generated by systemd-cryptsetup-generator\n\n"
118                 "[Unit]\n"
119                 "Description=Cryptography Setup for %I\n"
120                 "Documentation=man:systemd-cryptsetup@.service(8) man:crypttab(5)\n"
121                 "SourcePath=/etc/crypttab\n"
122                 "Conflicts=umount.target\n"
123                 "DefaultDependencies=no\n"
124                 "BindsTo=dev-mapper-%i.device\n"
125                 "IgnoreOnIsolate=true\n"
126                 "After=systemd-readahead-collect.service systemd-readahead-replay.service\n",
127                 f);
128
129         if (!nofail)
130                 fprintf(f,
131                         "Before=cryptsetup.target\n");
132
133         if (password) {
134                 if (streq(password, "/dev/urandom") ||
135                     streq(password, "/dev/random") ||
136                     streq(password, "/dev/hw_random"))
137                         fputs("After=systemd-random-seed.service\n", f);
138
139                 else if (!streq(password, "-") && !streq(password, "none")) {
140                         _cleanup_free_ char *uu = fstab_node_to_udev_node(password);
141                         if (uu == NULL)
142                                 return log_oom();
143
144                         if (is_device_path(uu)) {
145                                 _cleanup_free_ char *dd = unit_name_from_path(uu, ".device");
146                                 if (dd == NULL)
147                                         return log_oom();
148
149                                 fprintf(f, "After=%1$s\nRequires=%1$s\n", dd);
150                         } else
151                                 fprintf(f, "RequiresMountsFor=%s\n", password);
152                 }
153         }
154
155         if (is_device_path(u))
156                 fprintf(f,
157                         "BindsTo=%s\n"
158                         "After=%s\n"
159                         "Before=umount.target\n",
160                         d, d);
161         else
162                 fprintf(f,
163                         "RequiresMountsFor=%s\n",
164                         u);
165
166         fprintf(f,
167                 "\n[Service]\n"
168                 "Type=oneshot\n"
169                 "RemainAfterExit=yes\n"
170                 "TimeoutSec=0\n" /* the binary handles timeouts anyway */
171                 "ExecStart=" SYSTEMD_CRYPTSETUP_PATH " attach '%s' '%s' '%s' '%s'\n"
172                 "ExecStop=" SYSTEMD_CRYPTSETUP_PATH " detach '%s'\n",
173                 name, u, strempty(password), strempty(options),
174                 name);
175
176         if (tmp)
177                 fprintf(f,
178                         "ExecStartPost=/sbin/mke2fs '/dev/mapper/%s'\n",
179                         name);
180
181         if (swap)
182                 fprintf(f,
183                         "ExecStartPost=/sbin/mkswap '/dev/mapper/%s'\n",
184                         name);
185
186         fflush(f);
187
188         if (ferror(f)) {
189                 log_error("Failed to write file %s: %m", p);
190                 return -errno;
191         }
192
193         if (asprintf(&from, "../%s", n) < 0)
194                 return log_oom();
195
196         if (!noauto) {
197
198                 to = strjoin(arg_dest, "/", d, ".wants/", n, NULL);
199                 if (!to)
200                         return log_oom();
201
202                 mkdir_parents_label(to, 0755);
203                 if (symlink(from, to) < 0) {
204                         log_error("Failed to create symlink %s: %m", to);
205                         return -errno;
206                 }
207
208                 free(to);
209                 if (!nofail)
210                         to = strjoin(arg_dest, "/cryptsetup.target.requires/", n, NULL);
211                 else
212                         to = strjoin(arg_dest, "/cryptsetup.target.wants/", n, NULL);
213                 if (!to)
214                         return log_oom();
215
216                 mkdir_parents_label(to, 0755);
217                 if (symlink(from, to) < 0) {
218                         log_error("Failed to create symlink %s: %m", to);
219                         return -errno;
220                 }
221         }
222
223         e = unit_name_escape(name);
224         if (!e)
225                 return log_oom();
226
227         free(to);
228         to = strjoin(arg_dest, "/dev-mapper-", e, ".device.requires/", n, NULL);
229         if (!to)
230                 return log_oom();
231
232         mkdir_parents_label(to, 0755);
233         if (symlink(from, to) < 0) {
234                 log_error("Failed to create symlink %s: %m", to);
235                 return -errno;
236         }
237
238         if (!noauto && !nofail) {
239                 int r;
240                 free(p);
241                 p = strjoin(arg_dest, "/dev-mapper-", e, ".device.d/50-job-timeout-sec-0.conf", NULL);
242                 if (!p)
243                         return log_oom();
244
245                 mkdir_parents_label(p, 0755);
246
247                 r = write_string_file(p,
248                                 "# Automatically generated by systemd-cryptsetup-generator\n\n"
249                                 "[Unit]\n"
250                                 "JobTimeoutSec=0\n"); /* the binary handles timeouts anyway */
251                 if (r)
252                         return r;
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                                                 if (options)
513                                                         free(options);
514                                                 options = strdup(proc_options);
515                                                 if (!options) {
516                                                         log_oom();
517                                                         goto cleanup;
518                                                 }
519                                         }
520                                 } else if (!options) {
521                                         /*
522                                           Fall back to options without a specified UUID
523                                         */
524                                         options = strdup(s);
525                                         if (!options) {
526                                                 log_oom();
527                                                 goto cleanup;
528                                         };
529                                 }
530                         }
531                 }
532
533                 if (!options) {
534                         options = strdup("timeout=0");
535                         if (!options) {
536                                 log_oom();
537                                 goto cleanup;
538                         }
539                 }
540
541                 if (create_disk(name, device, arg_keyfile, options) < 0)
542                         goto cleanup;
543         }
544
545         r2 = EXIT_SUCCESS;
546
547 cleanup:
548         strv_free(arg_disks);
549         strv_free(arg_options);
550         free(arg_keyfile);
551
552         return r != EXIT_SUCCESS ? r : r2;
553 }