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