xref: /dragonfly/sbin/cryptdisks/cryptdisks.c (revision e65bc1c3)
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 void
284 generic_opts_to_tcplay(struct tc_api_opts *tco, struct generic_opts *go)
285 {
286 	/* Make sure keyfile array is NULL-terminated */
287 	go->keyfiles[go->nkeyfiles] = NULL;
288 
289 	tco->tc_interactive_prompt = (go->passphrase != NULL) ? 0 : 1;
290 	tco->tc_password_retries = go->ntries;
291 	tco->tc_map_name = go->map_name;
292 	tco->tc_device = go->device;
293 	tco->tc_keyfiles = go->keyfiles;
294 	tco->tc_passphrase = go->passphrase;
295 	tco->tc_prompt_timeout = go->timeout;
296 }
297 
298 static int
299 entry_parser(char **tokens, char **options, int type)
300 {
301 	struct crypt_options co;
302 	struct tc_api_opts tco;
303 	struct generic_opts go;
304 	int r, i, error, isluks;
305 
306 	if (entry_check_num_args(tokens, 2) != 0)
307 		return 1;
308 
309 	bzero(&go, sizeof(go));
310 	bzero(&co, sizeof(co));
311 	bzero(&tco, sizeof(tco));
312 
313 
314 	go.ntries = 3;
315 	go.map_name = tokens[0];
316 	go.device = tokens[1];
317 
318 	/* (Try to) parse extra options */
319 	for (i = 0; options[i] != NULL; i++)
320 		parse_crypt_options(&go, options[i]);
321 
322 	if ((tokens[2] != NULL) && (strcmp(tokens[2], "none") != 0)) {
323 		/* We got a keyfile */
324 		go.keyfiles[go.nkeyfiles++] = tokens[2];
325 	}
326 
327 	generic_opts_to_luks(&co, &go);
328 	generic_opts_to_tcplay(&tco, &go);
329 
330 	/*
331 	 * Check whether the device is a LUKS-formatted device; otherwise
332 	 * we assume its a TrueCrypt volume.
333 	 */
334 	isluks = !crypt_isLuks(&co);
335 
336 	if (!isluks) {
337 		if ((error = tc_api_init(0)) != 0) {
338 			fprintf(stderr, "crypttab: line %d: tc_api could not "
339 			    "be initialized\n", line_no);
340 			return 1;
341 		}
342 	}
343 
344 	if (type == CRYPTDISKS_STOP) {
345 		if (isluks) {
346 			/* Check if the device is active */
347 			r = crypt_query_device(&co);
348 
349 			/* If r > 0, then the device is active */
350 			if (r <= 0)
351 				return 0;
352 
353 			/* Actually close the device */
354 			crypt_remove_device(&co);
355 		} else {
356 			/* Assume tcplay volume */
357 			tc_api_unmap_volume(&tco);
358 		}
359 	} else if (type == CRYPTDISKS_START) {
360 		/* Open the device */
361 		if (isluks) {
362 			if ((error = crypt_luksOpen(&co)) != 0) {
363 				fprintf(stderr, "crypttab: line %d: device %s "
364 				    "could not be mapped / opened\n",
365 				    line_no, tco.tc_device);
366 				return 1;
367 			}
368 		} else {
369 			/* Assume tcplay volume */
370 			if ((error = tc_api_map_volume(&tco)) != 0) {
371 				fprintf(stderr, "crypttab: line %d: device %s "
372 				    "could not be mapped / opened: %s\n",
373 				    line_no, tco.tc_device,
374 				    tc_api_get_error_msg());
375 				tc_api_uninit();
376 				return 1;
377 			}
378 		}
379 	}
380 
381 	if (!isluks)
382 		tc_api_uninit();
383 
384 	return 0;
385 }
386 
387 static int
388 process_line(FILE* fd, int type)
389 {
390 	char buffer[4096];
391 	char *tokens[256];
392 	char *options[256];
393 	int c, n, i = 0;
394 	int ret = 0;
395 
396 	while (((c = fgetc(fd)) != EOF) && (c != '\n')) {
397 		buffer[i++] = (char)c;
398 		if (i == (sizeof(buffer) -1))
399 			break;
400 	}
401 	buffer[i] = '\0';
402 
403 	if (feof(fd) || ferror(fd))
404 		ret = 1;
405 
406 
407 	n = line_tokenize(buffer, &iswhitespace, '#', tokens);
408 
409 	/*
410 	 * If there are not enough arguments for any function or it is
411 	 * a line full of whitespaces, we just return here. Or if a
412 	 * quote wasn't closed.
413 	 */
414 	if ((n < 2) || (tokens[0][0] == '\0'))
415 		return ret;
416 
417 	/*
418 	 * If there are at least 4 tokens, one of them (the last) is a list
419 	 * of options.
420 	 */
421 	if (n >= 4)
422 	{
423 		i = line_tokenize(tokens[3], &iscomma, '#', options);
424 		if (i == 0)
425 			syntax_error("Invalid expression in options token");
426 			/* NOTREACHED */
427 	}
428 
429 	entry_parser(tokens, options, type);
430 
431 	return ret;
432 }
433 
434 
435 int
436 main(int argc, char *argv[])
437 {
438 	FILE *fd;
439 	int ch, start = 0, stop = 0;
440 
441 	while ((ch = getopt(argc, argv, "01")) != -1) {
442 		switch (ch) {
443 		case '1':
444 			start = 1;
445 			break;
446 		case '0':
447 			stop = 1;
448 			break;
449 		default:
450 			break;
451 		}
452 	}
453 
454 	argc -= optind;
455 	argv += optind;
456 
457 	atexit(check_and_purge_safe_mem);
458 
459 	if ((start && stop) || (!start && !stop))
460 		errx(1, "please specify exactly one of -0 and -1");
461 
462 	fd = fopen("/etc/crypttab", "r");
463 	if (fd == NULL)
464 		err(1, "fopen");
465 		/* NOTREACHED */
466 
467 	while (process_line(fd, (start) ? CRYPTDISKS_START : CRYPTDISKS_STOP) == 0)
468 		++line_no;
469 
470 	fclose(fd);
471 	return 0;
472 }
473 
474