cryptdisks - update to support options
[dragonfly.git] / sbin / cryptdisks / cryptdisks.c
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