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