xref: /dflybsd-src/sbin/cryptdisks/cryptdisks.c (revision 5e1ed6ba26f072c751d1fdccdb9f02a6a0231238)
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 
226 		fd = popen(parameter, "r");
227 		if ((fread(buf, 1, sizeof(buf), fd)) == 0)
228 			syntax_error("The 'keyscript' program failed");
229 			/* NOTREACHED */
230 		pclose(fd);
231 
232 		/* Get rid of trailing new-line */
233 		if ((endptr = strrchr(buf, '\n')) != NULL)
234 			*endptr = '\0';
235 
236 		co->passphrase = buf;
237 	} else if (strcmp(option, "none") == 0) {
238 		/* Valid option, does nothing */
239 	} else {
240 		syntax_error("Unknown option: %s", option);
241 		/* NOTREACHED */
242 	}
243 
244 	return 0;
245 }
246 
247 static int
248 entry_parser(char **tokens, char **options, int type)
249 {
250 	struct crypt_options co;
251 	int r, i, error;
252 
253 	if (entry_check_num_args(tokens, 2) != 0)
254 		return 1;
255 
256 	bzero(&co, sizeof(co));
257 
258 	co.icb = &cmd_icb;
259 	co.tries = 3;
260 	co.name = tokens[0];
261 	co.device = tokens[1];
262 
263 	/* (Try to) parse extra options */
264 	for (i = 0; options[i] != NULL; i++)
265 		parse_crypt_options(&co, options[i]);
266 
267 	/* Verify that the device is indeed a LUKS-formatted device */
268 	error = crypt_isLuks(&co);
269 	if (error) {
270 		printf("crypttab: line %d: device %s is not a luks device\n",
271 		    line_no, co.device);
272 		return 1;
273 	}
274 
275 	if (type == CRYPTDISKS_STOP) {
276 		/* Check if the device is active */
277 		r = crypt_query_device(&co);
278 
279 		/* If r > 0, then the device is active */
280 		if (r <= 0)
281 			return 0;
282 
283 		/* Actually close the device */
284 		crypt_remove_device(&co);
285 	} else if (type == CRYPTDISKS_START) {
286 		if ((tokens[2] != NULL) && (strcmp(tokens[2], "none") != 0)) {
287 			/* We got a keyfile */
288 			co.key_file = tokens[2];
289 		}
290 
291 		/* Open the device */
292 		crypt_luksOpen(&co);
293 	}
294 
295 	return 0;
296 }
297 
298 static int
299 process_line(FILE* fd, int type)
300 {
301 	char buffer[4096];
302 	char *tokens[256];
303 	char *options[256];
304 	int c, n, i = 0;
305 	int ret = 0;
306 
307 	while (((c = fgetc(fd)) != EOF) && (c != '\n')) {
308 		buffer[i++] = (char)c;
309 		if (i == (sizeof(buffer) -1))
310 			break;
311 	}
312 	buffer[i] = '\0';
313 
314 	if (feof(fd) || ferror(fd))
315 		ret = 1;
316 
317 
318 	n = line_tokenize(buffer, &iswhitespace, '#', tokens);
319 
320 	/*
321 	 * If there are not enough arguments for any function or it is
322 	 * a line full of whitespaces, we just return here. Or if a
323 	 * quote wasn't closed.
324 	 */
325 	if ((n < 2) || (tokens[0][0] == '\0'))
326 		return ret;
327 
328 	/*
329 	 * If there are at least 4 tokens, one of them (the last) is a list
330 	 * of options.
331 	 */
332 	if (n >= 4)
333 	{
334 		i = line_tokenize(tokens[3], &iscomma, '#', options);
335 		if (i == 0)
336 			syntax_error("Invalid expression in options token");
337 			/* NOTREACHED */
338 	}
339 
340 	entry_parser(tokens, options, type);
341 
342 	return ret;
343 }
344 
345 
346 int
347 main(int argc, char *argv[])
348 {
349 	FILE *fd;
350 	int ch, start = 0, stop = 0;
351 
352 	while ((ch = getopt(argc, argv, "01")) != -1) {
353 		switch (ch) {
354 		case '1':
355 			start = 1;
356 			break;
357 		case '0':
358 			stop = 1;
359 			break;
360 		default:
361 			break;
362 		}
363 	}
364 
365 	argc -= optind;
366 	argv += optind;
367 
368 	atexit(check_and_purge_safe_mem);
369 
370 	if ((start && stop) || (!start && !stop))
371 		errx(1, "please specify exactly one of -0 and -1");
372 
373 	fd = fopen("/etc/crypttab", "r");
374 	if (fd == NULL)
375 		err(1, "fopen");
376 		/* NOTREACHED */
377 
378 	while (process_line(fd, (start) ? CRYPTDISKS_START : CRYPTDISKS_STOP) == 0)
379 		++line_no;
380 
381 	fclose(fd);
382 	return 0;
383 }
384 
385