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