xref: /dflybsd-src/sbin/cryptdisks/cryptdisks.c (revision 44053572393185f2479540928f8f954712d842dc)
1 /*
2  * Copyright (c) 2009 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 
45 #include "safe_mem.h"
46 
47 #define _iswhitespace(X)	((((X) == ' ') || ((X) == '\t'))?1:0)
48 
49 #define CRYPTDISKS_START	1
50 #define CRYPTDISKS_STOP		2
51 
52 static void syntax_error(const char *, ...) __printflike(1, 2);
53 
54 static int line_no = 1;
55 
56 static int iswhitespace(char c)
57 {
58 	return _iswhitespace(c);
59 }
60 
61 static int iscomma(char c)
62 {
63 	return (c == ',');
64 }
65 
66 static int yesDialog(char *msg __unused)
67 {
68 	return 1;
69 }
70 
71 static void cmdLineLog(int level __unused, char *msg)
72 {
73 	printf("%s", msg);
74 }
75 
76 static struct interface_callbacks cmd_icb = {
77 	.yesDialog = yesDialog,
78 	.log = cmdLineLog,
79 };
80 
81 static void
82 syntax_error(const char *fmt, ...)
83 {
84 	char buf[1024];
85 	va_list ap;
86 
87 	va_start(ap, fmt);
88 	vsnprintf(buf, sizeof(buf), fmt, ap);
89 	va_end(ap);
90 	errx(1, "crypttab: syntax error on line %d: %s\n", line_no, buf);
91 }
92 
93 
94 static int
95 entry_check_num_args(char **tokens, int num)
96 {
97 	int i;
98 
99 	for (i = 0; tokens[i] != NULL; i++)
100 		;
101 
102 	if (i < num) {
103 		syntax_error("at least %d tokens were expected but only %d "
104 		    "were found", num, i);
105 		return 1;
106 	}
107 	return 0;
108 }
109 
110 static int
111 line_tokenize(char *buffer, int (*is_sep)(char), char comment_char, char **tokens)
112 {
113 	int c, n, i;
114 	int quote = 0;
115 
116 	i = strlen(buffer) + 1;
117 	c = 0;
118 
119 	/* Skip leading white-space */
120 	while ((_iswhitespace(buffer[c])) && (c < i)) c++;
121 
122 	/*
123 	 * If this line effectively (after indentation) begins with the comment
124 	 * character, we ignore the rest of the line.
125 	 */
126 	if (buffer[c] == comment_char)
127 		return 0;
128 
129 	tokens[0] = &buffer[c];
130 	for (n = 1; c < i; c++) {
131 		if (buffer[c] == '"') {
132 			quote = !quote;
133 			if (quote) {
134 				if ((c >= 1) && (&buffer[c] != tokens[n-1])) {
135 #if 0
136 					syntax_error("stray opening quote not "
137 					    "at beginning of token");
138 					/* NOTREACHED */
139 #endif
140 				} else {
141 					tokens[n-1] = &buffer[c+1];
142 				}
143 			} else {
144 				if ((c < i-1) && (!is_sep(buffer[c+1]))) {
145 #if 0
146 					syntax_error("stray closing quote not "
147 					    "at end of token");
148 					/* NOTREACHED */
149 #endif
150 				} else {
151 					buffer[c] = '\0';
152 				}
153 			}
154 		}
155 
156 		if (quote) {
157 			continue;
158 		}
159 
160 		if (is_sep(buffer[c])) {
161 			buffer[c++] = '\0';
162 			while ((_iswhitespace(buffer[c])) && (c < i)) c++;
163 			tokens[n++] = &buffer[c--];
164 		}
165 	}
166 	tokens[n] = NULL;
167 
168 	if (quote) {
169 		tokens[0] = NULL;
170 		return 0;
171 	}
172 
173 	return n;
174 }
175 
176 static int
177 parse_crypt_options(struct crypt_options *co, char *option)
178 {
179 	char	*parameter, *endptr;
180 	char	*buf;
181 	long	lval;
182 	unsigned long long ullval;
183 	int	noparam = 0;
184 	FILE	*fd;
185 
186 	parameter = strchr(option, '=');
187 	noparam = (parameter == NULL);
188 	if (!noparam)
189 	{
190 		*parameter = '\0';
191 		++parameter;
192 	}
193 
194 	if (strcmp(option, "tries") == 0) {
195 		if (noparam)
196 			syntax_error("The option 'tries' needs a parameter");
197 			/* NOTREACHED */
198 
199 		lval = strtol(parameter, &endptr, 10);
200 		if (*endptr != '\0')
201 			syntax_error("The option 'tries' expects an integer "
202 			    "parameter, not '%s'", parameter);
203 			/* NOTREACHED */
204 
205 		co->tries = (int)lval;
206 	} else if (strcmp(option, "timeout") == 0) {
207 		if (noparam)
208 			syntax_error("The option 'timeout' needs a parameter");
209 			/* NOTREACHED */
210 
211 		ullval = strtoull(parameter, &endptr, 10);
212 		if (*endptr != '\0')
213 			syntax_error("The option 'timeout' expects an integer "
214 			    "parameter, not '%s'", parameter);
215 			/* NOTREACHED */
216 
217 		co->timeout = ullval;
218 	} else if (strcmp(option, "keyscript") == 0) {
219 		if (noparam)
220 			syntax_error("The option 'keyscript' needs a parameter");
221 			/* NOTREACHED */
222 
223 		/* Allocate safe key memory */
224 		buf = alloc_safe_mem(8192);
225 		if (buf == NULL)
226 			err(1, "Could not allocate safe memory");
227 			/* NOTREACHED */
228 
229 		fd = popen(parameter, "r");
230 		if (fd == NULL)
231 			syntax_error("The 'keyscript' file could not be run");
232 			/* NOTREACHED */
233 
234 		if ((fread(buf, 1, sizeof(buf), fd)) == 0)
235 			syntax_error("The 'keyscript' program failed");
236 			/* NOTREACHED */
237 		pclose(fd);
238 
239 		/* Get rid of trailing new-line */
240 		if ((endptr = strrchr(buf, '\n')) != NULL)
241 			*endptr = '\0';
242 
243 		co->passphrase = buf;
244 	} else if (strcmp(option, "none") == 0) {
245 		/* Valid option, does nothing */
246 	} else {
247 		syntax_error("Unknown option: %s", option);
248 		/* NOTREACHED */
249 	}
250 
251 	return 0;
252 }
253 
254 static int
255 entry_parser(char **tokens, char **options, int type)
256 {
257 	struct crypt_options co;
258 	int r, i, error;
259 
260 	if (entry_check_num_args(tokens, 2) != 0)
261 		return 1;
262 
263 	bzero(&co, sizeof(co));
264 
265 	co.icb = &cmd_icb;
266 	co.tries = 3;
267 	co.name = tokens[0];
268 	co.device = tokens[1];
269 
270 	/* (Try to) parse extra options */
271 	for (i = 0; options[i] != NULL; i++)
272 		parse_crypt_options(&co, options[i]);
273 
274 	/* Verify that the device is indeed a LUKS-formatted device */
275 	error = crypt_isLuks(&co);
276 	if (error) {
277 		printf("crypttab: line %d: device %s is not a luks device\n",
278 		    line_no, co.device);
279 		return 1;
280 	}
281 
282 	if (type == CRYPTDISKS_STOP) {
283 		/* Check if the device is active */
284 		r = crypt_query_device(&co);
285 
286 		/* If r > 0, then the device is active */
287 		if (r <= 0)
288 			return 0;
289 
290 		/* Actually close the device */
291 		crypt_remove_device(&co);
292 	} else if (type == CRYPTDISKS_START) {
293 		if ((tokens[2] != NULL) && (strcmp(tokens[2], "none") != 0)) {
294 			/* We got a keyfile */
295 			co.key_file = tokens[2];
296 		}
297 
298 		/* Open the device */
299 		crypt_luksOpen(&co);
300 	}
301 
302 	return 0;
303 }
304 
305 static int
306 process_line(FILE* fd, int type)
307 {
308 	char buffer[4096];
309 	char *tokens[256];
310 	char *options[256];
311 	int c, n, i = 0;
312 	int ret = 0;
313 
314 	while (((c = fgetc(fd)) != EOF) && (c != '\n')) {
315 		buffer[i++] = (char)c;
316 		if (i == (sizeof(buffer) -1))
317 			break;
318 	}
319 	buffer[i] = '\0';
320 
321 	if (feof(fd) || ferror(fd))
322 		ret = 1;
323 
324 
325 	n = line_tokenize(buffer, &iswhitespace, '#', tokens);
326 
327 	/*
328 	 * If there are not enough arguments for any function or it is
329 	 * a line full of whitespaces, we just return here. Or if a
330 	 * quote wasn't closed.
331 	 */
332 	if ((n < 2) || (tokens[0][0] == '\0'))
333 		return ret;
334 
335 	/*
336 	 * If there are at least 4 tokens, one of them (the last) is a list
337 	 * of options.
338 	 */
339 	if (n >= 4)
340 	{
341 		i = line_tokenize(tokens[3], &iscomma, '#', options);
342 		if (i == 0)
343 			syntax_error("Invalid expression in options token");
344 			/* NOTREACHED */
345 	}
346 
347 	entry_parser(tokens, options, type);
348 
349 	return ret;
350 }
351 
352 
353 int
354 main(int argc, char *argv[])
355 {
356 	FILE *fd;
357 	int ch, start = 0, stop = 0;
358 
359 	while ((ch = getopt(argc, argv, "01")) != -1) {
360 		switch (ch) {
361 		case '1':
362 			start = 1;
363 			break;
364 		case '0':
365 			stop = 1;
366 			break;
367 		default:
368 			break;
369 		}
370 	}
371 
372 	argc -= optind;
373 	argv += optind;
374 
375 	atexit(check_and_purge_safe_mem);
376 
377 	if ((start && stop) || (!start && !stop))
378 		errx(1, "please specify exactly one of -0 and -1");
379 
380 	fd = fopen("/etc/crypttab", "r");
381 	if (fd == NULL)
382 		err(1, "fopen");
383 		/* NOTREACHED */
384 
385 	while (process_line(fd, (start) ? CRYPTDISKS_START : CRYPTDISKS_STOP) == 0)
386 		++line_no;
387 
388 	fclose(fd);
389 	return 0;
390 }
391 
392