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