/* * Copyright (c) 2009-2011 The DragonFly Project. All rights reserved. * * This code is derived from software contributed to The DragonFly Project * by Alex Hornung * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * 3. Neither the name of The DragonFly Project nor the names of its * contributors may be used to endorse or promote products derived * from this software without specific, prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include "safe_mem.h" #define _iswhitespace(X) ((((X) == ' ') || ((X) == '\t'))?1:0) #define CRYPTDISKS_START 1 #define CRYPTDISKS_STOP 2 struct generic_opts { char *device; char *map_name; char *passphrase; const char *keyfiles[256]; int nkeyfiles; int ntries; unsigned long long timeout; }; static void syntax_error(const char *, ...) __printflike(1, 2); static int line_no = 1; static int iswhitespace(char c) { return _iswhitespace(c); } static int iscomma(char c) { return (c == ','); } static int yesDialog(char *msg __unused) { return 1; } static void cmdLineLog(int level __unused, char *msg) { printf("%s", msg); } static struct interface_callbacks cmd_icb = { .yesDialog = yesDialog, .log = cmdLineLog, }; static void syntax_error(const char *fmt, ...) { char buf[1024]; va_list ap; va_start(ap, fmt); vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); errx(1, "crypttab: syntax error on line %d: %s\n", line_no, buf); } static int entry_check_num_args(char **tokens, int num) { int i; for (i = 0; tokens[i] != NULL; i++) ; if (i < num) { syntax_error("at least %d tokens were expected but only %d " "were found", num, i); return 1; } return 0; } static int line_tokenize(char *buffer, int (*is_sep)(char), char comment_char, char **tokens) { int c, n, i; int quote = 0; i = strlen(buffer) + 1; c = 0; /* Skip leading white-space */ while ((_iswhitespace(buffer[c])) && (c < i)) c++; /* * If this line effectively (after indentation) begins with the comment * character, we ignore the rest of the line. */ if (buffer[c] == comment_char) return 0; tokens[0] = &buffer[c]; for (n = 1; c < i; c++) { if (buffer[c] == '"') { quote = !quote; if (quote) { if ((c >= 1) && (&buffer[c] != tokens[n-1])) { #if 0 syntax_error("stray opening quote not " "at beginning of token"); /* NOTREACHED */ #endif } else { tokens[n-1] = &buffer[c+1]; } } else { if ((c < i-1) && (!is_sep(buffer[c+1]))) { #if 0 syntax_error("stray closing quote not " "at end of token"); /* NOTREACHED */ #endif } else { buffer[c] = '\0'; } } } if (quote) { continue; } if (is_sep(buffer[c])) { buffer[c++] = '\0'; while ((_iswhitespace(buffer[c])) && (c < i)) c++; tokens[n++] = &buffer[c--]; } } tokens[n] = NULL; if (quote) { tokens[0] = NULL; return 0; } return n; } static int parse_crypt_options(struct generic_opts *go, char *option) { char *parameter, *endptr; char *buf; long lval; unsigned long long ullval; int noparam = 0; FILE *fd; parameter = strchr(option, '='); noparam = (parameter == NULL); if (!noparam) { *parameter = '\0'; ++parameter; } if (strcmp(option, "tries") == 0) { if (noparam) syntax_error("The option 'tries' needs a parameter"); /* NOTREACHED */ lval = strtol(parameter, &endptr, 10); if (*endptr != '\0') syntax_error("The option 'tries' expects an integer " "parameter, not '%s'", parameter); /* NOTREACHED */ go->ntries = (int)lval; } else if (strcmp(option, "timeout") == 0) { if (noparam) syntax_error("The option 'timeout' needs a parameter"); /* NOTREACHED */ ullval = strtoull(parameter, &endptr, 10); if (*endptr != '\0') syntax_error("The option 'timeout' expects an integer " "parameter, not '%s'", parameter); /* NOTREACHED */ go->timeout = ullval; } else if (strcmp(option, "keyscript") == 0) { size_t keymem_len = 8192; if (noparam) syntax_error("The option 'keyscript' needs a parameter"); /* NOTREACHED */ /* Allocate safe key memory */ buf = alloc_safe_mem(keymem_len); if (buf == NULL) err(1, "Could not allocate safe memory"); /* NOTREACHED */ fd = popen(parameter, "r"); if (fd == NULL) syntax_error("The 'keyscript' file could not be run"); /* NOTREACHED */ if ((fread(buf, 1, keymem_len, fd)) == 0) syntax_error("The 'keyscript' program failed"); /* NOTREACHED */ pclose(fd); /* Get rid of trailing new-line */ if ((endptr = strrchr(buf, '\n')) != NULL) *endptr = '\0'; go->passphrase = buf; } else if (strcmp(option, "none") == 0) { /* Valid option, does nothing */ } else { syntax_error("Unknown option: %s", option); /* NOTREACHED */ } return 0; } static void generic_opts_to_luks(struct crypt_options *co, struct generic_opts *go) { if (go->nkeyfiles > 1) fprintf(stderr, "crypttab: Warning: LUKS only supports one " "keyfile; on line %d\n", line_no); co->icb = &cmd_icb; co->tries = go->ntries; co->name = go->map_name; co->device = go->device; co->key_file = (go->nkeyfiles == 1) ? go->keyfiles[0] : NULL; co->passphrase = go->passphrase; co->timeout = go->timeout; } static void generic_opts_to_tcplay(struct tc_api_opts *tco, struct generic_opts *go) { /* Make sure keyfile array is NULL-terminated */ go->keyfiles[go->nkeyfiles] = NULL; tco->tc_interactive_prompt = (go->passphrase != NULL) ? 0 : 1; tco->tc_password_retries = go->ntries; tco->tc_map_name = go->map_name; tco->tc_device = go->device; tco->tc_keyfiles = go->keyfiles; tco->tc_passphrase = go->passphrase; tco->tc_prompt_timeout = go->timeout; } static int entry_parser(char **tokens, char **options, int type) { struct crypt_options co; struct tc_api_opts tco; struct generic_opts go; int r, i, error, isluks; if (entry_check_num_args(tokens, 2) != 0) return 1; bzero(&go, sizeof(go)); bzero(&co, sizeof(co)); bzero(&tco, sizeof(tco)); go.ntries = 3; go.map_name = tokens[0]; go.device = tokens[1]; /* (Try to) parse extra options */ for (i = 0; options[i] != NULL; i++) parse_crypt_options(&go, options[i]); if ((tokens[2] != NULL) && (strcmp(tokens[2], "none") != 0)) { /* We got a keyfile */ go.keyfiles[go.nkeyfiles++] = tokens[2]; } generic_opts_to_luks(&co, &go); generic_opts_to_tcplay(&tco, &go); /* * Check whether the device is a LUKS-formatted device; otherwise * we assume its a TrueCrypt volume. */ isluks = !crypt_isLuks(&co); if (!isluks) { if ((error = tc_api_init(0)) != 0) { fprintf(stderr, "crypttab: line %d: tc_api could not " "be initialized\n", line_no); return 1; } } if (type == CRYPTDISKS_STOP) { if (isluks) { /* Check if the device is active */ r = crypt_query_device(&co); /* If r > 0, then the device is active */ if (r <= 0) return 0; /* Actually close the device */ crypt_remove_device(&co); } else { /* Assume tcplay volume */ tc_api_unmap_volume(&tco); } } else if (type == CRYPTDISKS_START) { /* Open the device */ if (isluks) { if ((error = crypt_luksOpen(&co)) != 0) { fprintf(stderr, "crypttab: line %d: device %s " "could not be mapped / opened\n", line_no, tco.tc_device); return 1; } } else { /* Assume tcplay volume */ if ((error = tc_api_map_volume(&tco)) != 0) { fprintf(stderr, "crypttab: line %d: device %s " "could not be mapped / opened: %s\n", line_no, tco.tc_device, tc_api_get_error_msg()); tc_api_uninit(); return 1; } } } if (!isluks) tc_api_uninit(); return 0; } static int process_line(FILE* fd, int type) { char buffer[4096]; char *tokens[256]; char *options[256]; int c, n, i = 0; int ret = 0; while (((c = fgetc(fd)) != EOF) && (c != '\n')) { buffer[i++] = (char)c; if (i == (sizeof(buffer) -1)) break; } buffer[i] = '\0'; if (feof(fd) || ferror(fd)) ret = 1; n = line_tokenize(buffer, &iswhitespace, '#', tokens); /* * If there are not enough arguments for any function or it is * a line full of whitespaces, we just return here. Or if a * quote wasn't closed. */ if ((n < 2) || (tokens[0][0] == '\0')) return ret; /* * If there are at least 4 tokens, one of them (the last) is a list * of options. */ if (n >= 4) { i = line_tokenize(tokens[3], &iscomma, '#', options); if (i == 0) syntax_error("Invalid expression in options token"); /* NOTREACHED */ } entry_parser(tokens, options, type); return ret; } int main(int argc, char *argv[]) { FILE *fd; int ch, start = 0, stop = 0; while ((ch = getopt(argc, argv, "01")) != -1) { switch (ch) { case '1': start = 1; break; case '0': stop = 1; break; default: break; } } argc -= optind; argv += optind; atexit(check_and_purge_safe_mem); if ((start && stop) || (!start && !stop)) errx(1, "please specify exactly one of -0 and -1"); fd = fopen("/etc/crypttab", "r"); if (fd == NULL) err(1, "fopen"); /* NOTREACHED */ while (process_line(fd, (start) ? CRYPTDISKS_START : CRYPTDISKS_STOP) == 0) ++line_no; fclose(fd); return 0; }