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