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