xref: /dragonfly/sbin/cryptdisks/cryptdisks.c (revision cfd1aba3)
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 
67 static int iswhitespace(char c)
68 {
69 	return _iswhitespace(c);
70 }
71 
72 static int iscomma(char c)
73 {
74 	return (c == ',');
75 }
76 
77 static int yesDialog(char *msg __unused)
78 {
79 	return 1;
80 }
81 
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
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
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
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
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
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
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 				tc_api_uninit();
384 			}
385 			if ((error = tc_api_task_set(tcplay_task, "map_name",
386 			    go.map_name))) {
387 				fprintf(stderr, "tc_api_task_set map_name failed\n");
388 				goto tcplay_err;
389 			}
390 			if ((error = tc_api_task_set(tcplay_task, "interactive",
391 			    (go.passphrase != NULL) ? 0 : 1))) {
392 				fprintf(stderr, "tc_api_task_set map_name failed\n");
393 				goto tcplay_err;
394 			}
395 			if ((error = tc_api_task_set(tcplay_task, "retries",
396 			    go.ntries))) {
397 				fprintf(stderr, "tc_api_task_set map_name failed\n");
398 				goto tcplay_err;
399 			}
400 			if ((error = tc_api_task_set(tcplay_task, "timeout",
401 			    go.timeout))) {
402 				fprintf(stderr, "tc_api_task_set map_name failed\n");
403 				goto tcplay_err;
404 			}
405 
406 			if (go.passphrase != NULL) {
407 				if ((error = tc_api_task_set(tcplay_task, "passphrase",
408 				    go.passphrase))) {
409 					fprintf(stderr, "tc_api_task_set map_name failed\n");
410 					goto tcplay_err;
411 				}
412 			}
413 
414 			for (i = 0; i < go.nkeyfiles; i++) {
415 				if ((error = tc_api_task_set(tcplay_task, "keyfiles",
416 				    go.keyfiles[i]))) {
417 					fprintf(stderr, "tc_api_task_set keyfile failed\n");
418 					goto tcplay_err;
419 				}
420 			}
421 			if ((error = tc_api_task_do(tcplay_task))) {
422 				fprintf(stderr, "crypttab: line %d: device %s "
423 				    "could not be mapped/opened: %s\n",
424 				    line_no, go.device,
425 				    tc_api_task_get_error(tcplay_task));
426 				goto tcplay_err;
427 			}
428 			if ((error = tc_api_task_uninit(tcplay_task))) {
429 				fprintf(stderr, "tc_api_task_uninit failed\n");
430 				goto tcplay_err;
431 			}
432 		}
433 	}
434 
435 	if (!isluks)
436 		tc_api_uninit();
437 
438 	return 0;
439 
440 tcplay_err:
441 	tc_api_uninit();
442 	return 1;
443 }
444 
445 static int
446 process_line(FILE* fd, int type)
447 {
448 	char buffer[4096];
449 	char *tokens[256];
450 	char *options[256];
451 	int c, n, i = 0;
452 	int ret = 0;
453 
454 	while (((c = fgetc(fd)) != EOF) && (c != '\n')) {
455 		buffer[i++] = (char)c;
456 		if (i == (sizeof(buffer) -1))
457 			break;
458 	}
459 	buffer[i] = '\0';
460 
461 	if (feof(fd) || ferror(fd))
462 		ret = 1;
463 
464 
465 	n = line_tokenize(buffer, &iswhitespace, '#', tokens);
466 
467 	/*
468 	 * If there are not enough arguments for any function or it is
469 	 * a line full of whitespaces, we just return here. Or if a
470 	 * quote wasn't closed.
471 	 */
472 	if ((n < 2) || (tokens[0][0] == '\0'))
473 		return ret;
474 
475 	/*
476 	 * If there are at least 4 tokens, one of them (the last) is a list
477 	 * of options.
478 	 */
479 	if (n >= 4)
480 	{
481 		i = line_tokenize(tokens[3], &iscomma, '#', options);
482 		if (i == 0)
483 			syntax_error("Invalid expression in options token");
484 			/* NOTREACHED */
485 	}
486 
487 	entry_parser(tokens, options, type);
488 
489 	return ret;
490 }
491 
492 
493 int
494 main(int argc, char *argv[])
495 {
496 	FILE *fd;
497 	int ch, start = 0, stop = 0;
498 
499 	while ((ch = getopt(argc, argv, "01")) != -1) {
500 		switch (ch) {
501 		case '1':
502 			start = 1;
503 			break;
504 		case '0':
505 			stop = 1;
506 			break;
507 		default:
508 			break;
509 		}
510 	}
511 
512 	argc -= optind;
513 	argv += optind;
514 
515 	atexit(check_and_purge_safe_mem);
516 
517 	if ((start && stop) || (!start && !stop))
518 		errx(1, "please specify exactly one of -0 and -1");
519 
520 	fd = fopen("/etc/crypttab", "r");
521 	if (fd == NULL)
522 		err(1, "fopen");
523 		/* NOTREACHED */
524 
525 	while (process_line(fd, (start) ? CRYPTDISKS_START : CRYPTDISKS_STOP) == 0)
526 		++line_no;
527 
528 	fclose(fd);
529 	return 0;
530 }
531 
532