1 /*
2 * Copyright (c) 2009-2011 The DragonFly Project. All rights reserved.
3 *
4 * This code is derived from software contributed to The DragonFly Project
5 * by Alex Hornung <ahornung@gmail.com>
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 *
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in
15 * the documentation and/or other materials provided with the
16 * distribution.
17 * 3. Neither the name of The DragonFly Project nor the names of its
18 * contributors may be used to endorse or promote products derived
19 * from this software without specific, prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26 * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
27 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
31 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32 * SUCH DAMAGE.
33 */
34
35 #include <stdio.h>
36 #include <stdarg.h>
37 #include <string.h>
38 #include <stdlib.h>
39 #include <unistd.h>
40 #include <errno.h>
41 #include <err.h>
42
43 #include <libcryptsetup.h>
44 #include <tcplay_api.h>
45
46 #include "safe_mem.h"
47
48 #define _iswhitespace(X) ((((X) == ' ') || ((X) == '\t'))?1:0)
49
50 #define CRYPTDISKS_START 1
51 #define CRYPTDISKS_STOP 2
52
53 struct generic_opts {
54 char *device;
55 char *map_name;
56 char *passphrase;
57 const char *keyfiles[256];
58 int nkeyfiles;
59 int ntries;
60 unsigned long long timeout;
61 };
62
63 static void syntax_error(const char *, ...) __printflike(1, 2);
64
65 static int line_no = 1;
66
iswhitespace(char c)67 static int iswhitespace(char c)
68 {
69 return _iswhitespace(c);
70 }
71
iscomma(char c)72 static int iscomma(char c)
73 {
74 return (c == ',');
75 }
76
yesDialog(char * msg __unused)77 static int yesDialog(char *msg __unused)
78 {
79 return 1;
80 }
81
cmdLineLog(int level __unused,char * msg)82 static void cmdLineLog(int level __unused, char *msg)
83 {
84 printf("%s", msg);
85 }
86
87 static struct interface_callbacks cmd_icb = {
88 .yesDialog = yesDialog,
89 .log = cmdLineLog,
90 };
91
92 static void
syntax_error(const char * fmt,...)93 syntax_error(const char *fmt, ...)
94 {
95 char buf[1024];
96 va_list ap;
97
98 va_start(ap, fmt);
99 vsnprintf(buf, sizeof(buf), fmt, ap);
100 va_end(ap);
101 errx(1, "crypttab: syntax error on line %d: %s\n", line_no, buf);
102 }
103
104
105 static int
entry_check_num_args(char ** tokens,int num)106 entry_check_num_args(char **tokens, int num)
107 {
108 int i;
109
110 for (i = 0; tokens[i] != NULL; i++)
111 ;
112
113 if (i < num) {
114 syntax_error("at least %d tokens were expected but only %d "
115 "were found", num, i);
116 return 1;
117 }
118 return 0;
119 }
120
121 static int
line_tokenize(char * buffer,int (* is_sep)(char),char comment_char,char ** tokens)122 line_tokenize(char *buffer, int (*is_sep)(char), char comment_char, char **tokens)
123 {
124 int c, n, i;
125 int quote = 0;
126
127 i = strlen(buffer) + 1;
128 c = 0;
129
130 /* Skip leading white-space */
131 while ((_iswhitespace(buffer[c])) && (c < i)) c++;
132
133 /*
134 * If this line effectively (after indentation) begins with the comment
135 * character, we ignore the rest of the line.
136 */
137 if (buffer[c] == comment_char)
138 return 0;
139
140 tokens[0] = &buffer[c];
141 for (n = 1; c < i; c++) {
142 if (buffer[c] == '"') {
143 quote = !quote;
144 if (quote) {
145 if ((c >= 1) && (&buffer[c] != tokens[n-1])) {
146 #if 0
147 syntax_error("stray opening quote not "
148 "at beginning of token");
149 /* NOTREACHED */
150 #endif
151 } else {
152 tokens[n-1] = &buffer[c+1];
153 }
154 } else {
155 if ((c < i-1) && (!is_sep(buffer[c+1]))) {
156 #if 0
157 syntax_error("stray closing quote not "
158 "at end of token");
159 /* NOTREACHED */
160 #endif
161 } else {
162 buffer[c] = '\0';
163 }
164 }
165 }
166
167 if (quote) {
168 continue;
169 }
170
171 if (is_sep(buffer[c])) {
172 buffer[c++] = '\0';
173 while ((_iswhitespace(buffer[c])) && (c < i)) c++;
174 tokens[n++] = &buffer[c--];
175 }
176 }
177 tokens[n] = NULL;
178
179 if (quote) {
180 tokens[0] = NULL;
181 return 0;
182 }
183
184 return n;
185 }
186
187 static int
parse_crypt_options(struct generic_opts * go,char * option)188 parse_crypt_options(struct generic_opts *go, char *option)
189 {
190 char *parameter, *endptr;
191 char *buf;
192 long lval;
193 unsigned long long ullval;
194 int noparam = 0;
195 FILE *fd;
196
197 parameter = strchr(option, '=');
198 noparam = (parameter == NULL);
199 if (!noparam)
200 {
201 *parameter = '\0';
202 ++parameter;
203 }
204
205 if (strcmp(option, "tries") == 0) {
206 if (noparam)
207 syntax_error("The option 'tries' needs a parameter");
208 /* NOTREACHED */
209
210 lval = strtol(parameter, &endptr, 10);
211 if (*endptr != '\0')
212 syntax_error("The option 'tries' expects an integer "
213 "parameter, not '%s'", parameter);
214 /* NOTREACHED */
215
216 go->ntries = (int)lval;
217 } else if (strcmp(option, "timeout") == 0) {
218 if (noparam)
219 syntax_error("The option 'timeout' needs a parameter");
220 /* NOTREACHED */
221
222 ullval = strtoull(parameter, &endptr, 10);
223 if (*endptr != '\0')
224 syntax_error("The option 'timeout' expects an integer "
225 "parameter, not '%s'", parameter);
226 /* NOTREACHED */
227
228 go->timeout = ullval;
229 } else if (strcmp(option, "keyscript") == 0) {
230 size_t keymem_len = 8192;
231
232 if (noparam)
233 syntax_error("The option 'keyscript' needs a parameter");
234 /* NOTREACHED */
235
236 /* Allocate safe key memory */
237 buf = alloc_safe_mem(keymem_len);
238 if (buf == NULL)
239 err(1, "Could not allocate safe memory");
240 /* NOTREACHED */
241
242 fd = popen(parameter, "r");
243 if (fd == NULL)
244 syntax_error("The 'keyscript' file could not be run");
245 /* NOTREACHED */
246
247 if ((fread(buf, 1, keymem_len, fd)) == 0)
248 syntax_error("The 'keyscript' program failed");
249 /* NOTREACHED */
250 pclose(fd);
251
252 /* Get rid of trailing new-line */
253 if ((endptr = strrchr(buf, '\n')) != NULL)
254 *endptr = '\0';
255
256 go->passphrase = buf;
257 } else if (strcmp(option, "none") == 0) {
258 /* Valid option, does nothing */
259 } else {
260 syntax_error("Unknown option: %s", option);
261 /* NOTREACHED */
262 }
263
264 return 0;
265 }
266
267 static void
generic_opts_to_luks(struct crypt_options * co,struct generic_opts * go)268 generic_opts_to_luks(struct crypt_options *co, struct generic_opts *go)
269 {
270 if (go->nkeyfiles > 1)
271 fprintf(stderr, "crypttab: Warning: LUKS only supports one "
272 "keyfile; on line %d\n", line_no);
273
274 co->icb = &cmd_icb;
275 co->tries = go->ntries;
276 co->name = go->map_name;
277 co->device = go->device;
278 co->key_file = (go->nkeyfiles == 1) ? go->keyfiles[0] : NULL;
279 co->passphrase = go->passphrase;
280 co->timeout = go->timeout;
281 }
282
283 static int
entry_parser(char ** tokens,char ** options,int type)284 entry_parser(char **tokens, char **options, int type)
285 {
286 struct crypt_options co;
287 tc_api_task tcplay_task;
288 struct generic_opts go;
289 int r, i, error, isluks;
290
291 if (entry_check_num_args(tokens, 2) != 0)
292 return 1;
293
294 bzero(&go, sizeof(go));
295 bzero(&co, sizeof(co));
296
297
298 go.ntries = 3;
299 go.map_name = tokens[0];
300 go.device = tokens[1];
301
302 /* (Try to) parse extra options */
303 for (i = 0; options[i] != NULL; i++)
304 parse_crypt_options(&go, options[i]);
305
306 if ((tokens[2] != NULL) && (strcmp(tokens[2], "none") != 0)) {
307 /* We got a keyfile */
308 go.keyfiles[go.nkeyfiles++] = tokens[2];
309 }
310
311 generic_opts_to_luks(&co, &go);
312
313 /*
314 * Check whether the device is a LUKS-formatted device; otherwise
315 * we assume its a TrueCrypt volume.
316 */
317 isluks = !crypt_isLuks(&co);
318
319 if (!isluks) {
320 if ((error = tc_api_init(0)) != 0) {
321 fprintf(stderr, "crypttab: line %d: tc_api could not "
322 "be initialized\n", line_no);
323 return 1;
324 }
325 }
326
327 if (type == CRYPTDISKS_STOP) {
328 if (isluks) {
329 /* Check if the device is active */
330 r = crypt_query_device(&co);
331
332 /* If r > 0, then the device is active */
333 if (r <= 0)
334 return 0;
335
336 /* Actually close the device */
337 crypt_remove_device(&co);
338 } else {
339 /* Assume tcplay volume */
340 if ((tcplay_task = tc_api_task_init("unmap")) == NULL) {
341 fprintf(stderr, "tc_api_task_init failed.\n");
342 goto tcplay_err;
343 }
344 if ((error = tc_api_task_set(tcplay_task, "dev", go.device))) {
345 fprintf(stderr, "tc_api_task_set dev failed\n");
346 goto tcplay_err;
347 }
348 if ((error = tc_api_task_set(tcplay_task, "map_name",
349 go.map_name))) {
350 fprintf(stderr, "tc_api_task_set map_name failed\n");
351 goto tcplay_err;
352 }
353 if ((error = tc_api_task_do(tcplay_task))) {
354 fprintf(stderr, "crypttab: line %d: device %s "
355 "could not be unmapped: %s\n",
356 line_no, go.device,
357 tc_api_task_get_error(tcplay_task));
358 goto tcplay_err;
359 }
360 if ((error = tc_api_task_uninit(tcplay_task))) {
361 fprintf(stderr, "tc_api_task_uninit failed\n");
362 goto tcplay_err;
363 }
364
365 }
366 } else if (type == CRYPTDISKS_START) {
367 /* Open the device */
368 if (isluks) {
369 if ((error = crypt_luksOpen(&co)) != 0) {
370 fprintf(stderr, "crypttab: line %d: device %s "
371 "could not be mapped/opened\n",
372 line_no, co.device);
373 return 1;
374 }
375 } else {
376 if ((tcplay_task = tc_api_task_init("map")) == NULL) {
377 fprintf(stderr, "tc_api_task_init failed.\n");
378 goto tcplay_err;
379 }
380 if ((error = tc_api_task_set(tcplay_task, "dev", go.device))) {
381 fprintf(stderr, "tc_api_task_set dev failed\n");
382 goto tcplay_err;
383 }
384 if ((error = tc_api_task_set(tcplay_task, "map_name",
385 go.map_name))) {
386 fprintf(stderr, "tc_api_task_set map_name failed\n");
387 goto tcplay_err;
388 }
389 if ((error = tc_api_task_set(tcplay_task, "interactive",
390 (go.passphrase != NULL) ? 0 : 1))) {
391 fprintf(stderr, "tc_api_task_set map_name failed\n");
392 goto tcplay_err;
393 }
394 if ((error = tc_api_task_set(tcplay_task, "retries",
395 go.ntries))) {
396 fprintf(stderr, "tc_api_task_set map_name failed\n");
397 goto tcplay_err;
398 }
399 if ((error = tc_api_task_set(tcplay_task, "timeout",
400 go.timeout))) {
401 fprintf(stderr, "tc_api_task_set map_name failed\n");
402 goto tcplay_err;
403 }
404
405 if (go.passphrase != NULL) {
406 if ((error = tc_api_task_set(tcplay_task, "passphrase",
407 go.passphrase))) {
408 fprintf(stderr, "tc_api_task_set map_name failed\n");
409 goto tcplay_err;
410 }
411 }
412
413 for (i = 0; i < go.nkeyfiles; i++) {
414 if ((error = tc_api_task_set(tcplay_task, "keyfiles",
415 go.keyfiles[i]))) {
416 fprintf(stderr, "tc_api_task_set keyfile failed\n");
417 goto tcplay_err;
418 }
419 }
420 if ((error = tc_api_task_do(tcplay_task))) {
421 fprintf(stderr, "crypttab: line %d: device %s "
422 "could not be mapped/opened: %s\n",
423 line_no, go.device,
424 tc_api_task_get_error(tcplay_task));
425 goto tcplay_err;
426 }
427 if ((error = tc_api_task_uninit(tcplay_task))) {
428 fprintf(stderr, "tc_api_task_uninit failed\n");
429 goto tcplay_err;
430 }
431 }
432 }
433
434 if (!isluks)
435 tc_api_uninit();
436
437 return 0;
438
439 tcplay_err:
440 tc_api_uninit();
441 return 1;
442 }
443
444 static int
process_line(FILE * fd,int type)445 process_line(FILE* fd, int type)
446 {
447 char buffer[4096];
448 char *tokens[256];
449 char *options[256];
450 int c, n, i = 0;
451 int ret = 0;
452
453 while (((c = fgetc(fd)) != EOF) && (c != '\n')) {
454 buffer[i++] = (char)c;
455 if (i == (sizeof(buffer) -1))
456 break;
457 }
458 buffer[i] = '\0';
459
460 if (feof(fd) || ferror(fd))
461 ret = 1;
462
463
464 n = line_tokenize(buffer, &iswhitespace, '#', tokens);
465
466 /*
467 * If there are not enough arguments for any function or it is
468 * a line full of whitespaces, we just return here. Or if a
469 * quote wasn't closed.
470 */
471 if ((n < 2) || (tokens[0][0] == '\0'))
472 return ret;
473
474 /*
475 * If there are at least 4 tokens, one of them (the last) is a list
476 * of options.
477 */
478 if (n >= 4)
479 {
480 i = line_tokenize(tokens[3], &iscomma, '#', options);
481 if (i == 0)
482 syntax_error("Invalid expression in options token");
483 /* NOTREACHED */
484 }
485
486 entry_parser(tokens, options, type);
487
488 return ret;
489 }
490
491
492 int
main(int argc,char * argv[])493 main(int argc, char *argv[])
494 {
495 FILE *fd;
496 int ch, start = 0, stop = 0;
497
498 while ((ch = getopt(argc, argv, "01")) != -1) {
499 switch (ch) {
500 case '1':
501 start = 1;
502 break;
503 case '0':
504 stop = 1;
505 break;
506 default:
507 break;
508 }
509 }
510
511 argc -= optind;
512 argv += optind;
513
514 atexit(check_and_purge_safe_mem);
515
516 if ((start && stop) || (!start && !stop))
517 errx(1, "please specify exactly one of -0 and -1");
518
519 fd = fopen("/etc/crypttab", "r");
520 if (fd == NULL)
521 err(1, "fopen");
522 /* NOTREACHED */
523
524 while (process_line(fd, (start) ? CRYPTDISKS_START : CRYPTDISKS_STOP) == 0)
525 ++line_no;
526
527 fclose(fd);
528 return 0;
529 }
530
531