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 532