chiark / gitweb /
cryptsetup: introduce new cryptsetup-pre.traget unit so that services can make sure...
[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:crypttab(5) man:systemd-cryptsetup-generator(8) man:systemd-cryptsetup@.service(8)\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 cryptsetup-pre.target\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_item(const char *key, const char *value) {
259         int r;
260
261         if (STR_IN_SET(key, "luks", "rd.luks") && value) {
262
263                 r = parse_boolean(value);
264                 if (r < 0)
265                         log_warning("Failed to parse luks switch %s. Ignoring.", value);
266                 else
267                         arg_enabled = r;
268
269         } else if (STR_IN_SET(key, "luks.crypttab", "rd.luks.crypttab") && value) {
270
271                 r = parse_boolean(value);
272                 if (r < 0)
273                         log_warning("Failed to parse luks crypttab switch %s. Ignoring.", value);
274                 else
275                         arg_read_crypttab = r;
276
277         } else if (STR_IN_SET(key, "luks.uuid", "rd.luks.uuid") && value) {
278
279                 if (strv_extend(&arg_disks, value) < 0)
280                         return log_oom();
281
282         } else if (STR_IN_SET(key, "luks.options", "rd.luks.options") && value) {
283
284                 if (strv_extend(&arg_options, value) < 0)
285                         return log_oom();
286
287         } else if (STR_IN_SET(key, "luks.key", "rd.luks.key") && value) {
288
289                 free(arg_keyfile);
290                 arg_keyfile = strdup(value);
291                 if (!arg_keyfile)
292                         return log_oom();
293
294         } else if (startswith(key, "luks.") || startswith(key, "rd.luks."))
295                 log_warning("Unknown kernel switch %s. Ignoring.", key);
296
297         return 0;
298 }
299
300 int main(int argc, char *argv[]) {
301         _cleanup_strv_free_ char **disks_done = NULL;
302         _cleanup_fclose_ FILE *f = NULL;
303         unsigned n = 0;
304         int r = EXIT_FAILURE, r2 = EXIT_FAILURE;
305         char **i;
306
307         if (argc > 1 && argc != 4) {
308                 log_error("This program takes three or no arguments.");
309                 return EXIT_FAILURE;
310         }
311
312         if (argc > 1)
313                 arg_dest = argv[1];
314
315         log_set_target(LOG_TARGET_SAFE);
316         log_parse_environment();
317         log_open();
318
319         umask(0022);
320
321         if (parse_proc_cmdline(parse_proc_cmdline_item) < 0)
322                 goto cleanup;
323
324         if (!arg_enabled) {
325                 r = r2 = EXIT_SUCCESS;
326                 goto cleanup;
327         }
328
329         strv_uniq(arg_disks);
330
331         if (arg_read_crypttab) {
332                 struct stat st;
333
334                 f = fopen("/etc/crypttab", "re");
335                 if (!f) {
336                         if (errno == ENOENT)
337                                 r = EXIT_SUCCESS;
338                         else
339                                 log_error("Failed to open /etc/crypttab: %m");
340
341                         goto next;
342                 }
343
344                 if (fstat(fileno(f), &st) < 0) {
345                         log_error("Failed to stat /etc/crypttab: %m");
346                         goto next;
347                 }
348
349                 /* If we readd support for specifying passphrases
350                  * directly in crypttabe we should upgrade the warning
351                  * below, though possibly only if a passphrase is
352                  * specified directly. */
353                 if (st.st_mode & 0005)
354                         log_debug("/etc/crypttab is world-readable. This is usually not a good idea.");
355
356                 for (;;) {
357                         char line[LINE_MAX], *l;
358                         _cleanup_free_ char *name = NULL, *device = NULL, *password = NULL, *options = NULL;
359                         int k;
360
361                         if (!fgets(line, sizeof(line), f))
362                                 break;
363
364                         n++;
365
366                         l = strstrip(line);
367                         if (*l == '#' || *l == 0)
368                                 continue;
369
370                         k = sscanf(l, "%ms %ms %ms %ms", &name, &device, &password, &options);
371                         if (k < 2 || k > 4) {
372                                 log_error("Failed to parse /etc/crypttab:%u, ignoring.", n);
373                                 continue;
374                         }
375
376                         /*
377                           If options are specified on the kernel commandline, let them override
378                           the ones from crypttab.
379                         */
380                         STRV_FOREACH(i, arg_options) {
381                                 _cleanup_free_ char *proc_uuid = NULL, *proc_options = NULL;
382                                 const char *p = *i;
383
384                                 k = sscanf(p, "%m[0-9a-fA-F-]=%ms", &proc_uuid, &proc_options);
385                                 if (k == 2 && streq(proc_uuid, device + 5)) {
386                                         free(options);
387                                         options = strdup(p);
388                                         if (!proc_options) {
389                                                 log_oom();
390                                                 goto cleanup;
391                                         }
392                                 }
393                         }
394
395                         if (arg_disks) {
396                                 /*
397                                   If luks UUIDs are specified on the kernel command line, use them as a filter
398                                   for /etc/crypttab and only generate units for those.
399                                 */
400                                 STRV_FOREACH(i, arg_disks) {
401                                         _cleanup_free_ char *proc_device = NULL, *proc_name = NULL;
402                                         const char *p = *i;
403
404                                         if (startswith(p, "luks-"))
405                                                 p += 5;
406
407                                         proc_name = strappend("luks-", p);
408                                         proc_device = strappend("UUID=", p);
409
410                                         if (!proc_name || !proc_device) {
411                                                 log_oom();
412                                                 goto cleanup;
413                                         }
414
415                                         if (streq(proc_device, device) || streq(proc_name, name)) {
416                                                 if (create_disk(name, device, password, options) < 0)
417                                                         goto cleanup;
418
419                                                 if (strv_extend(&disks_done, p) < 0) {
420                                                         log_oom();
421                                                         goto cleanup;
422                                                 }
423                                         }
424                                 }
425                         } else if (create_disk(name, device, password, options) < 0)
426                                 goto cleanup;
427
428                 }
429         }
430
431         r = EXIT_SUCCESS;
432
433 next:
434         STRV_FOREACH(i, arg_disks) {
435                 /*
436                   Generate units for those UUIDs, which were specified
437                   on the kernel command line and not yet written.
438                 */
439
440                 _cleanup_free_ char *name = NULL, *device = NULL, *options = NULL;
441                 const char *p = *i;
442
443                 if (startswith(p, "luks-"))
444                         p += 5;
445
446                 if (strv_contains(disks_done, p))
447                         continue;
448
449                 name = strappend("luks-", p);
450                 device = strappend("UUID=", p);
451
452                 if (!name || !device) {
453                         log_oom();
454                         goto cleanup;
455                 }
456
457                 if (arg_options) {
458                         /*
459                           If options are specified on the kernel commandline, use them.
460                         */
461                         char **j;
462
463                         STRV_FOREACH(j, arg_options) {
464                                 _cleanup_free_ char *proc_uuid = NULL, *proc_options = NULL;
465                                 const char *s = *j;
466                                 int k;
467
468                                 k = sscanf(s, "%m[0-9a-fA-F-]=%ms", &proc_uuid, &proc_options);
469                                 if (k == 2) {
470                                         if (streq(proc_uuid, device + 5)) {
471                                                 free(options);
472                                                 options = proc_options;
473                                                 proc_options = NULL;
474                                         }
475                                 } else if (!options) {
476                                         /*
477                                           Fall back to options without a specified UUID
478                                         */
479                                         options = strdup(s);
480                                         if (!options) {
481                                                 log_oom();
482                                                 goto cleanup;
483                                         };
484                                 }
485                         }
486                 }
487
488                 if (!options) {
489                         options = strdup("timeout=0");
490                         if (!options) {
491                                 log_oom();
492                                 goto cleanup;
493                         }
494                 }
495
496                 if (create_disk(name, device, arg_keyfile, options) < 0)
497                         goto cleanup;
498         }
499
500         r2 = EXIT_SUCCESS;
501
502 cleanup:
503         strv_free(arg_disks);
504         strv_free(arg_options);
505         free(arg_keyfile);
506
507         return r != EXIT_SUCCESS ? r : r2;
508 }