ipfw2 - Rename FreeBSD ipfw port to ipfw3
[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 int
284 entry_parser(char **tokens, char **options, int type)
285 {
286         struct crypt_options co;
287         tc_api_task tcplay_task;
288         struct generic_opts go;
289         int r, i, error, isluks;
290
291         if (entry_check_num_args(tokens, 2) != 0)
292                 return 1;
293
294         bzero(&go, sizeof(go));
295         bzero(&co, sizeof(co));
296
297
298         go.ntries = 3;
299         go.map_name = tokens[0];
300         go.device = tokens[1];
301
302         /* (Try to) parse extra options */
303         for (i = 0; options[i] != NULL; i++)
304                 parse_crypt_options(&go, options[i]);
305
306         if ((tokens[2] != NULL) && (strcmp(tokens[2], "none") != 0)) {
307                 /* We got a keyfile */
308                 go.keyfiles[go.nkeyfiles++] = tokens[2];
309         }
310
311         generic_opts_to_luks(&co, &go);
312
313         /*
314          * Check whether the device is a LUKS-formatted device; otherwise
315          * we assume its a TrueCrypt volume.
316          */
317         isluks = !crypt_isLuks(&co);
318
319         if (!isluks) {
320                 if ((error = tc_api_init(0)) != 0) {
321                         fprintf(stderr, "crypttab: line %d: tc_api could not "
322                             "be initialized\n", line_no);
323                         return 1;
324                 }
325         }
326
327         if (type == CRYPTDISKS_STOP) {
328                 if (isluks) {
329                         /* Check if the device is active */
330                         r = crypt_query_device(&co);
331
332                         /* If r > 0, then the device is active */
333                         if (r <= 0)
334                                 return 0;
335
336                         /* Actually close the device */
337                         crypt_remove_device(&co);
338                 } else {
339                         /* Assume tcplay volume */
340                         if ((tcplay_task = tc_api_task_init("unmap")) == NULL) {
341                                 fprintf(stderr, "tc_api_task_init failed.\n");
342                                 goto tcplay_err;
343                         }
344                         if ((error = tc_api_task_set(tcplay_task, "dev", go.device))) {
345                                 fprintf(stderr, "tc_api_task_set dev failed\n");
346                                 goto tcplay_err;
347                         }
348                         if ((error = tc_api_task_set(tcplay_task, "map_name",
349                             go.map_name))) {
350                                 fprintf(stderr, "tc_api_task_set map_name failed\n");
351                                 goto tcplay_err;
352                         }
353                         if ((error = tc_api_task_do(tcplay_task))) {
354                                 fprintf(stderr, "crypttab: line %d: device %s "
355                                     "could not be unmapped: %s\n",
356                                     line_no, go.device,
357                                     tc_api_task_get_error(tcplay_task));
358                                 goto tcplay_err;
359                         }
360                         if ((error = tc_api_task_uninit(tcplay_task))) {
361                                 fprintf(stderr, "tc_api_task_uninit failed\n");
362                                 goto tcplay_err;
363                         }
364
365                 }
366         } else if (type == CRYPTDISKS_START) {
367                 /* Open the device */
368                 if (isluks) {
369                         if ((error = crypt_luksOpen(&co)) != 0) {
370                                 fprintf(stderr, "crypttab: line %d: device %s "
371                                     "could not be mapped/opened\n",
372                                     line_no, co.device);
373                                 return 1;
374                         }
375                 } else {
376                         if ((tcplay_task = tc_api_task_init("map")) == NULL) {
377                                 fprintf(stderr, "tc_api_task_init failed.\n");
378                                 goto tcplay_err;
379                         }
380                         if ((error = tc_api_task_set(tcplay_task, "dev", go.device))) {
381                                 fprintf(stderr, "tc_api_task_set dev failed\n");
382                                 goto tcplay_err;
383                                 tc_api_uninit();
384                         }
385                         if ((error = tc_api_task_set(tcplay_task, "map_name",
386                             go.map_name))) {
387                                 fprintf(stderr, "tc_api_task_set map_name failed\n");
388                                 goto tcplay_err;
389                         }
390                         if ((error = tc_api_task_set(tcplay_task, "interactive",
391                             (go.passphrase != NULL) ? 0 : 1))) {
392                                 fprintf(stderr, "tc_api_task_set map_name failed\n");
393                                 goto tcplay_err;
394                         }
395                         if ((error = tc_api_task_set(tcplay_task, "retries",
396                             go.ntries))) {
397                                 fprintf(stderr, "tc_api_task_set map_name failed\n");
398                                 goto tcplay_err;
399                         }
400                         if ((error = tc_api_task_set(tcplay_task, "timeout",
401                             go.timeout))) {
402                                 fprintf(stderr, "tc_api_task_set map_name failed\n");
403                                 goto tcplay_err;
404                         }
405
406                         if (go.passphrase != NULL) {
407                                 if ((error = tc_api_task_set(tcplay_task, "passphrase",
408                                     go.passphrase))) {
409                                         fprintf(stderr, "tc_api_task_set map_name failed\n");
410                                         goto tcplay_err;
411                                 }
412                         }
413
414                         for (i = 0; i < go.nkeyfiles; i++) {
415                                 if ((error = tc_api_task_set(tcplay_task, "keyfiles",
416                                     go.keyfiles[i]))) {
417                                         fprintf(stderr, "tc_api_task_set keyfile failed\n");
418                                         goto tcplay_err;
419                                 }
420                         }
421                         if ((error = tc_api_task_do(tcplay_task))) {
422                                 fprintf(stderr, "crypttab: line %d: device %s "
423                                     "could not be mapped/opened: %s\n",
424                                     line_no, go.device,
425                                     tc_api_task_get_error(tcplay_task));
426                                 goto tcplay_err;
427                         }
428                         if ((error = tc_api_task_uninit(tcplay_task))) {
429                                 fprintf(stderr, "tc_api_task_uninit failed\n");
430                                 goto tcplay_err;
431                         }
432                 }
433         }
434
435         if (!isluks)
436                 tc_api_uninit();
437
438         return 0;
439
440 tcplay_err:
441         tc_api_uninit();
442         return 1;
443 }
444
445 static int
446 process_line(FILE* fd, int type)
447 {
448         char buffer[4096];
449         char *tokens[256];
450         char *options[256];
451         int c, n, i = 0;
452         int ret = 0;
453
454         while (((c = fgetc(fd)) != EOF) && (c != '\n')) {
455                 buffer[i++] = (char)c;
456                 if (i == (sizeof(buffer) -1))
457                         break;
458         }
459         buffer[i] = '\0';
460
461         if (feof(fd) || ferror(fd))
462                 ret = 1;
463
464
465         n = line_tokenize(buffer, &iswhitespace, '#', tokens);
466
467         /*
468          * If there are not enough arguments for any function or it is
469          * a line full of whitespaces, we just return here. Or if a
470          * quote wasn't closed.
471          */
472         if ((n < 2) || (tokens[0][0] == '\0'))
473                 return ret;
474
475         /*
476          * If there are at least 4 tokens, one of them (the last) is a list
477          * of options.
478          */
479         if (n >= 4)
480         {
481                 i = line_tokenize(tokens[3], &iscomma, '#', options);
482                 if (i == 0)
483                         syntax_error("Invalid expression in options token");
484                         /* NOTREACHED */
485         }
486
487         entry_parser(tokens, options, type);
488
489         return ret;
490 }
491
492
493 int
494 main(int argc, char *argv[])
495 {
496         FILE *fd;
497         int ch, start = 0, stop = 0;
498
499         while ((ch = getopt(argc, argv, "01")) != -1) {
500                 switch (ch) {
501                 case '1':
502                         start = 1;
503                         break;
504                 case '0':
505                         stop = 1;
506                         break;
507                 default:
508                         break;
509                 }
510         }
511
512         argc -= optind;
513         argv += optind;
514
515         atexit(check_and_purge_safe_mem);
516
517         if ((start && stop) || (!start && !stop))
518                 errx(1, "please specify exactly one of -0 and -1");
519
520         fd = fopen("/etc/crypttab", "r");
521         if (fd == NULL)
522                 err(1, "fopen");
523                 /* NOTREACHED */
524
525         while (process_line(fd, (start) ? CRYPTDISKS_START : CRYPTDISKS_STOP) == 0)
526                 ++line_no;
527
528         fclose(fd);
529         return 0;
530 }
531