chiark / gitweb /
05ceff443fe1dc80529dd6452230e77de472a28d
[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;
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         if (parse_proc_cmdline(parse_proc_cmdline_item) < 0)
329                 goto cleanup;
330
331         if (!arg_enabled) {
332                 r = r2 = EXIT_SUCCESS;
333                 goto cleanup;
334         }
335
336         strv_uniq(arg_disks);
337
338         if (arg_read_crypttab) {
339                 struct stat st;
340
341                 f = fopen("/etc/crypttab", "re");
342                 if (!f) {
343                         if (errno == ENOENT)
344                                 r = EXIT_SUCCESS;
345                         else
346                                 log_error("Failed to open /etc/crypttab: %m");
347
348                         goto next;
349                 }
350
351                 if (fstat(fileno(f), &st) < 0) {
352                         log_error("Failed to stat /etc/crypttab: %m");
353                         goto next;
354                 }
355
356                 /* If we readd support for specifying passphrases
357                  * directly in crypttabe we should upgrade the warning
358                  * below, though possibly only if a passphrase is
359                  * specified directly. */
360                 if (st.st_mode & 0005)
361                         log_debug("/etc/crypttab is world-readable. This is usually not a good idea.");
362
363                 for (;;) {
364                         char line[LINE_MAX], *l;
365                         _cleanup_free_ char *name = NULL, *device = NULL, *password = NULL, *options = NULL;
366                         int k;
367
368                         if (!fgets(line, sizeof(line), f))
369                                 break;
370
371                         n++;
372
373                         l = strstrip(line);
374                         if (*l == '#' || *l == 0)
375                                 continue;
376
377                         k = sscanf(l, "%ms %ms %ms %ms", &name, &device, &password, &options);
378                         if (k < 2 || k > 4) {
379                                 log_error("Failed to parse /etc/crypttab:%u, ignoring.", n);
380                                 continue;
381                         }
382
383                         /*
384                           If options are specified on the kernel commandline, let them override
385                           the ones from crypttab.
386                         */
387                         STRV_FOREACH(i, arg_options) {
388                                 _cleanup_free_ char *proc_uuid = NULL, *proc_options = NULL;
389                                 const char *p = *i;
390
391                                 k = sscanf(p, "%m[0-9a-fA-F-]=%ms", &proc_uuid, &proc_options);
392                                 if (k == 2 && streq(proc_uuid, device + 5)) {
393                                         free(options);
394                                         options = strdup(p);
395                                         if (!options) {
396                                                 log_oom();
397                                                 goto cleanup;
398                                         }
399                                 }
400                         }
401
402                         if (arg_disks) {
403                                 /*
404                                   If luks UUIDs are specified on the kernel command line, use them as a filter
405                                   for /etc/crypttab and only generate units for those.
406                                 */
407                                 STRV_FOREACH(i, arg_disks) {
408                                         _cleanup_free_ char *proc_device = NULL, *proc_name = NULL;
409                                         const char *p = *i;
410
411                                         if (startswith(p, "luks-"))
412                                                 p += 5;
413
414                                         proc_name = strappend("luks-", p);
415                                         proc_device = strappend("UUID=", p);
416
417                                         if (!proc_name || !proc_device) {
418                                                 log_oom();
419                                                 goto cleanup;
420                                         }
421
422                                         if (streq(proc_device, device) || streq(proc_name, name)) {
423                                                 if (create_disk(name, device, password, options) < 0)
424                                                         goto cleanup;
425
426                                                 if (strv_extend(&disks_done, p) < 0) {
427                                                         log_oom();
428                                                         goto cleanup;
429                                                 }
430                                         }
431                                 }
432                         } else if (create_disk(name, device, password, options) < 0)
433                                 goto cleanup;
434
435                 }
436         }
437
438         r = EXIT_SUCCESS;
439
440 next:
441         STRV_FOREACH(i, arg_disks) {
442                 /*
443                   Generate units for those UUIDs, which were specified
444                   on the kernel command line and not yet written.
445                 */
446
447                 _cleanup_free_ char *name = NULL, *device = NULL, *options = NULL;
448                 const char *p = *i;
449
450                 if (startswith(p, "luks-"))
451                         p += 5;
452
453                 if (strv_contains(disks_done, p))
454                         continue;
455
456                 name = strappend("luks-", p);
457                 device = strappend("UUID=", p);
458
459                 if (!name || !device) {
460                         log_oom();
461                         goto cleanup;
462                 }
463
464                 if (arg_options) {
465                         /*
466                           If options are specified on the kernel commandline, use them.
467                         */
468                         char **j;
469
470                         STRV_FOREACH(j, arg_options) {
471                                 _cleanup_free_ char *proc_uuid = NULL, *proc_options = NULL;
472                                 const char *s = *j;
473                                 int k;
474
475                                 k = sscanf(s, "%m[0-9a-fA-F-]=%ms", &proc_uuid, &proc_options);
476                                 if (k == 2) {
477                                         if (streq(proc_uuid, device + 5)) {
478                                                 free(options);
479                                                 options = proc_options;
480                                                 proc_options = NULL;
481                                         }
482                                 } else if (!options) {
483                                         /*
484                                           Fall back to options without a specified UUID
485                                         */
486                                         options = strdup(s);
487                                         if (!options) {
488                                                 log_oom();
489                                                 goto cleanup;
490                                         };
491                                 }
492                         }
493                 }
494
495                 if (!options) {
496                         options = strdup("timeout=0");
497                         if (!options) {
498                                 log_oom();
499                                 goto cleanup;
500                         }
501                 }
502
503                 if (create_disk(name, device, arg_keyfile, options) < 0)
504                         goto cleanup;
505         }
506
507         r2 = EXIT_SUCCESS;
508
509 cleanup:
510         strv_free(arg_disks);
511         strv_free(arg_options);
512         free(arg_keyfile);
513
514         return r != EXIT_SUCCESS ? r : r2;
515 }