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