cryptdisks(8): Fix a wrong sizeof.
[dragonfly.git] / sbin / cryptdisks / cryptdisks.c
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