sbin/hammer2: Fix "show" and "volhdr" output format
[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                         }
384                         if ((error = tc_api_task_set(tcplay_task, "map_name",
385                             go.map_name))) {
386                                 fprintf(stderr, "tc_api_task_set map_name failed\n");
387                                 goto tcplay_err;
388                         }
389                         if ((error = tc_api_task_set(tcplay_task, "interactive",
390                             (go.passphrase != NULL) ? 0 : 1))) {
391                                 fprintf(stderr, "tc_api_task_set map_name failed\n");
392                                 goto tcplay_err;
393                         }
394                         if ((error = tc_api_task_set(tcplay_task, "retries",
395                             go.ntries))) {
396                                 fprintf(stderr, "tc_api_task_set map_name failed\n");
397                                 goto tcplay_err;
398                         }
399                         if ((error = tc_api_task_set(tcplay_task, "timeout",
400                             go.timeout))) {
401                                 fprintf(stderr, "tc_api_task_set map_name failed\n");
402                                 goto tcplay_err;
403                         }
404
405                         if (go.passphrase != NULL) {
406                                 if ((error = tc_api_task_set(tcplay_task, "passphrase",
407                                     go.passphrase))) {
408                                         fprintf(stderr, "tc_api_task_set map_name failed\n");
409                                         goto tcplay_err;
410                                 }
411                         }
412
413                         for (i = 0; i < go.nkeyfiles; i++) {
414                                 if ((error = tc_api_task_set(tcplay_task, "keyfiles",
415                                     go.keyfiles[i]))) {
416                                         fprintf(stderr, "tc_api_task_set keyfile failed\n");
417                                         goto tcplay_err;
418                                 }
419                         }
420                         if ((error = tc_api_task_do(tcplay_task))) {
421                                 fprintf(stderr, "crypttab: line %d: device %s "
422                                     "could not be mapped/opened: %s\n",
423                                     line_no, go.device,
424                                     tc_api_task_get_error(tcplay_task));
425                                 goto tcplay_err;
426                         }
427                         if ((error = tc_api_task_uninit(tcplay_task))) {
428                                 fprintf(stderr, "tc_api_task_uninit failed\n");
429                                 goto tcplay_err;
430                         }
431                 }
432         }
433
434         if (!isluks)
435                 tc_api_uninit();
436
437         return 0;
438
439 tcplay_err:
440         tc_api_uninit();
441         return 1;
442 }
443
444 static int
445 process_line(FILE* fd, int type)
446 {
447         char buffer[4096];
448         char *tokens[256];
449         char *options[256];
450         int c, n, i = 0;
451         int ret = 0;
452
453         while (((c = fgetc(fd)) != EOF) && (c != '\n')) {
454                 buffer[i++] = (char)c;
455                 if (i == (sizeof(buffer) -1))
456                         break;
457         }
458         buffer[i] = '\0';
459
460         if (feof(fd) || ferror(fd))
461                 ret = 1;
462
463
464         n = line_tokenize(buffer, &iswhitespace, '#', tokens);
465
466         /*
467          * If there are not enough arguments for any function or it is
468          * a line full of whitespaces, we just return here. Or if a
469          * quote wasn't closed.
470          */
471         if ((n < 2) || (tokens[0][0] == '\0'))
472                 return ret;
473
474         /*
475          * If there are at least 4 tokens, one of them (the last) is a list
476          * of options.
477          */
478         if (n >= 4)
479         {
480                 i = line_tokenize(tokens[3], &iscomma, '#', options);
481                 if (i == 0)
482                         syntax_error("Invalid expression in options token");
483                         /* NOTREACHED */
484         }
485
486         entry_parser(tokens, options, type);
487
488         return ret;
489 }
490
491
492 int
493 main(int argc, char *argv[])
494 {
495         FILE *fd;
496         int ch, start = 0, stop = 0;
497
498         while ((ch = getopt(argc, argv, "01")) != -1) {
499                 switch (ch) {
500                 case '1':
501                         start = 1;
502                         break;
503                 case '0':
504                         stop = 1;
505                         break;
506                 default:
507                         break;
508                 }
509         }
510
511         argc -= optind;
512         argv += optind;
513
514         atexit(check_and_purge_safe_mem);
515
516         if ((start && stop) || (!start && !stop))
517                 errx(1, "please specify exactly one of -0 and -1");
518
519         fd = fopen("/etc/crypttab", "r");
520         if (fd == NULL)
521                 err(1, "fopen");
522                 /* NOTREACHED */
523
524         while (process_line(fd, (start) ? CRYPTDISKS_START : CRYPTDISKS_STOP) == 0)
525                 ++line_no;
526
527         fclose(fd);
528         return 0;
529 }
530