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