Merge branch 'vendor/OPENPAM'
[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                 if (noparam)
231                         syntax_error("The option 'keyscript' needs a parameter");
232                         /* NOTREACHED */
233
234                 /* Allocate safe key memory */
235                 buf = alloc_safe_mem(8192);
236                 if (buf == NULL)
237                         err(1, "Could not allocate safe memory");
238                         /* NOTREACHED */
239
240                 fd = popen(parameter, "r");
241                 if (fd == NULL)
242                         syntax_error("The 'keyscript' file could not be run");
243                         /* NOTREACHED */
244
245                 if ((fread(buf, 1, sizeof(buf), fd)) == 0)
246                         syntax_error("The 'keyscript' program failed");
247                         /* NOTREACHED */
248                 pclose(fd);
249
250                 /* Get rid of trailing new-line */
251                 if ((endptr = strrchr(buf, '\n')) != NULL)
252                         *endptr = '\0';
253
254                 go->passphrase = buf;
255         } else if (strcmp(option, "none") == 0) {
256                 /* Valid option, does nothing */
257         } else {
258                 syntax_error("Unknown option: %s", option);
259                 /* NOTREACHED */
260         }
261
262         return 0;
263 }
264
265 static void
266 generic_opts_to_luks(struct crypt_options *co, struct generic_opts *go)
267 {
268         if (go->nkeyfiles > 1)
269                 fprintf(stderr, "crypttab: Warning: LUKS only supports one "
270                     "keyfile; on line %d\n", line_no);
271
272         co->icb = &cmd_icb;
273         co->tries = go->ntries;
274         co->name = go->map_name;
275         co->device = go->device;
276         co->key_file = (go->nkeyfiles == 1) ? go->keyfiles[0] : NULL;
277         co->passphrase = go->passphrase;
278         co->timeout = go->timeout;
279 }
280
281 static void
282 generic_opts_to_tcplay(struct tc_api_opts *tco, struct generic_opts *go)
283 {
284         /* Make sure keyfile array is NULL-terminated */
285         go->keyfiles[go->nkeyfiles] = NULL;
286
287         tco->tc_interactive_prompt = (go->passphrase != NULL) ? 0 : 1;
288         tco->tc_password_retries = go->ntries;
289         tco->tc_map_name = go->map_name;
290         tco->tc_device = go->device;
291         tco->tc_keyfiles = go->keyfiles;
292         tco->tc_passphrase = go->passphrase;
293         tco->tc_prompt_timeout = go->timeout;
294 }
295
296 static int
297 entry_parser(char **tokens, char **options, int type)
298 {
299         struct crypt_options co;
300         struct tc_api_opts tco;
301         struct generic_opts go;
302         int r, i, error, isluks;
303
304         if (entry_check_num_args(tokens, 2) != 0)
305                 return 1;
306
307         bzero(&go, sizeof(go));
308         bzero(&co, sizeof(co));
309         bzero(&tco, sizeof(tco));
310
311
312         go.ntries = 3;
313         go.map_name = tokens[0];
314         go.device = tokens[1];
315
316         /* (Try to) parse extra options */
317         for (i = 0; options[i] != NULL; i++)
318                 parse_crypt_options(&go, options[i]);
319
320         if ((tokens[2] != NULL) && (strcmp(tokens[2], "none") != 0)) {
321                 /* We got a keyfile */
322                 go.keyfiles[go.nkeyfiles++] = tokens[2];
323         }
324
325         generic_opts_to_luks(&co, &go);
326         generic_opts_to_tcplay(&tco, &go);
327
328         /*
329          * Check whether the device is a LUKS-formatted device; otherwise
330          * we assume its a TrueCrypt volume.
331          */
332         isluks = !crypt_isLuks(&co);
333
334         if (!isluks) {
335                 if ((error = tc_api_init(0)) != 0) {
336                         fprintf(stderr, "crypttab: line %d: tc_api could not "
337                             "be initialized\n", line_no);
338                         return 1;
339                 }
340         }
341
342         if (type == CRYPTDISKS_STOP) {
343                 if (isluks) {
344                         /* Check if the device is active */
345                         r = crypt_query_device(&co);
346
347                         /* If r > 0, then the device is active */
348                         if (r <= 0)
349                                 return 0;
350
351                         /* Actually close the device */
352                         crypt_remove_device(&co);
353                 } else {
354                         /* Assume tcplay volume */
355                         tc_api_unmap_volume(&tco);
356                 }
357         } else if (type == CRYPTDISKS_START) {
358                 /* Open the device */
359                 if (isluks) {
360                         if ((error = crypt_luksOpen(&co)) != 0) {
361                                 fprintf(stderr, "crypttab: line %d: device %s "
362                                     "could not be mapped / opened\n",
363                                     line_no, tco.tc_device);
364                                 return 1;
365                         }
366                 } else {
367                         /* Assume tcplay volume */
368                         if ((error = tc_api_map_volume(&tco)) != 0) {
369                                 fprintf(stderr, "crypttab: line %d: device %s "
370                                     "could not be mapped / opened: %s\n",
371                                     line_no, tco.tc_device,
372                                     tc_api_get_error_msg());
373                                 tc_api_uninit();
374                                 return 1;
375                         }
376                 }
377         }
378
379         if (!isluks)
380                 tc_api_uninit();
381
382         return 0;
383 }
384
385 static int
386 process_line(FILE* fd, int type)
387 {
388         char buffer[4096];
389         char *tokens[256];
390         char *options[256];
391         int c, n, i = 0;
392         int ret = 0;
393
394         while (((c = fgetc(fd)) != EOF) && (c != '\n')) {
395                 buffer[i++] = (char)c;
396                 if (i == (sizeof(buffer) -1))
397                         break;
398         }
399         buffer[i] = '\0';
400
401         if (feof(fd) || ferror(fd))
402                 ret = 1;
403
404
405         n = line_tokenize(buffer, &iswhitespace, '#', tokens);
406
407         /*
408          * If there are not enough arguments for any function or it is
409          * a line full of whitespaces, we just return here. Or if a
410          * quote wasn't closed.
411          */
412         if ((n < 2) || (tokens[0][0] == '\0'))
413                 return ret;
414
415         /*
416          * If there are at least 4 tokens, one of them (the last) is a list
417          * of options.
418          */
419         if (n >= 4)
420         {
421                 i = line_tokenize(tokens[3], &iscomma, '#', options);
422                 if (i == 0)
423                         syntax_error("Invalid expression in options token");
424                         /* NOTREACHED */
425         }
426
427         entry_parser(tokens, options, type);
428
429         return ret;
430 }
431
432
433 int
434 main(int argc, char *argv[])
435 {
436         FILE *fd;
437         int ch, start = 0, stop = 0;
438
439         while ((ch = getopt(argc, argv, "01")) != -1) {
440                 switch (ch) {
441                 case '1':
442                         start = 1;
443                         break;
444                 case '0':
445                         stop = 1;
446                         break;
447                 default:
448                         break;
449                 }
450         }
451
452         argc -= optind;
453         argv += optind;
454
455         atexit(check_and_purge_safe_mem);
456
457         if ((start && stop) || (!start && !stop))
458                 errx(1, "please specify exactly one of -0 and -1");
459
460         fd = fopen("/etc/crypttab", "r");
461         if (fd == NULL)
462                 err(1, "fopen");
463                 /* NOTREACHED */
464
465         while (process_line(fd, (start) ? CRYPTDISKS_START : CRYPTDISKS_STOP) == 0)
466                 ++line_no;
467
468         fclose(fd);
469         return 0;
470 }
471