diff options
author | Rob Austein <sra@hactrn.net> | 2015-07-01 16:30:51 -0400 |
---|---|---|
committer | Rob Austein <sra@hactrn.net> | 2015-07-01 16:30:51 -0400 |
commit | c45562762aab7e874eac71792f9eebb5185ee47d (patch) | |
tree | 99c2943914bc095ee1a6ba4b83d8fc0c98fd8f61 | |
parent | 4f7d9f0bcc0de535083bdc240f1f5c1037955990 (diff) |
Add p11util program to do things like fiddling with the BPKDF2
iteration count, setting PINs, and so forth. Factor some SQL utility
code out to a separate file so we can reuse it for p11util.
-rw-r--r-- | GNUmakefile | 10 | ||||
-rw-r--r-- | p11util.c | 301 | ||||
-rw-r--r-- | pkcs11.c | 229 | ||||
-rw-r--r-- | sql_common.h | 255 |
4 files changed, 578 insertions, 217 deletions
diff --git a/GNUmakefile b/GNUmakefile index 10299f7..c7e69c3 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -48,10 +48,10 @@ else CFLAGS += -DUSE_PTHREADS=0 endif -all: libpkcs11.so +all: libpkcs11.so p11util clean: - rm -rf pkcs11.o pkcs11.so libpkcs11.so* schema.h attributes.h + rm -rf pkcs11.o pkcs11.so libpkcs11.so* p11util p11util.o schema.h attributes.h distclean: clean rm -f TAGS @@ -71,6 +71,12 @@ pkcs11.so: pkcs11.o ${LIBS} libpkcs11.so: pkcs11.so objcopy -w -G 'C_*' $< $@ +p11util.o: p11util.c sql_common.h schema.h + ${CC} ${CFLAGS} -c $< + +p11util: p11util.o ${LIBS} + ${CC} ${CFLAGS} -o $@ -g $^ + tags: TAGS TAGS: *.[ch] diff --git a/p11util.c b/p11util.c new file mode 100644 index 0000000..514422d --- /dev/null +++ b/p11util.c @@ -0,0 +1,301 @@ +/* + * Command line tool for setting up PKCS #11. Mostly this means + * things like setting PINs. + */ + +/* + * Apparently getopt_long() works everywhere we're likely to care + * about. At least, we've been getting away with it for years in + * rcynic. rcynic.c has code to wrap option and usage stuff using + * getopt_long(), proably just reuse that. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <termios.h> +#include <getopt.h> + +#include <hal.h> + +#include "sql_common.h" + +/* + * Apparently the cool kids don't use getpassword() anymore, and there + * is no fully portable replacement, so the advice is just to roll + * one's own using termios. Yuck, but gives us the opportunity to + * provide a bit of visual feedback, what the heck. + * + * This version requires /dev/tty. Use something else if you're + * reading from a pipe. + */ + +static int getpin_tty(const char *prompt, + char *pinbuf, + const size_t pinbuf_len) +{ + struct termios oflags, nflags; + int c = '\0', ok = 0, fixtty = 0; + char *p = pinbuf; + FILE *f = NULL; + + assert(prompt != NULL && pinbuf != NULL); + + if ((f = fopen("/dev/tty", "r+")) == NULL || + setvbuf(f, NULL, _IONBF, 0) == EOF) + goto fail; + + tcgetattr(fileno(f), &oflags); + nflags = oflags; + nflags.c_lflag &= ~ (ECHO | ICANON); + + if (tcsetattr(fileno(f), TCSANOW, &nflags) < 0) + goto fail; + + fixtty = 1; + fputs(prompt, f); + + while (p < pinbuf + pinbuf_len - 1 && (c = fgetc(f)) != EOF && c != '\n') { + fputc('*', f); + *p++ = (char) c; + } + + assert(p <= pinbuf + pinbuf_len - 1); + *p = '\0'; + fputc('\n', f); + ok = (c == '\n'); + + fail: + + if (!ok) + perror("getpin_tty() failed"); + + if (fixtty && tcsetattr(fileno(f), TCSANOW, &oflags) < 0) + perror("getpin_tty() couldn't restore termios settings"); + + if (f != NULL) + fclose(f); + + return ok; +} + + + +#define OPTIONS \ + OPT_FLG('s', "set-so-pin", "set Security Officer PIN") \ + OPT_FLG('u', "set-user-pin", "set \"user\" PIN") \ + OPT_ARG('i', "set-iterations", "set PBKDF2 iteration count") \ + OPT_ARG('p', "pin-from-stdin", "read PIN from stdin instead of /dev/tty") \ + OPT_END + +#define OPT_END + +static void usage (const int code, const char *jane) +{ + assert(jane != NULL); + FILE *f = code ? stderr : stdout; + + fprintf(f, "usage: %s [options]\noptions:\n", jane); + +#define OPT_FLG(_short_, _long_, _help_) fprintf(f, " -%c --%-32s%s", _short_, _long_, _help_); +#define OPT_ARG(_short_, _long_, _help_) fprintf(f, " -%c ARG --%-32s%s", _short_, _long_ " ARG", _help_); + OPTIONS; +#undef OPT_ARG +#undef OPT_FLG + + exit(code); +} + +static void parse_args (int argc, char *argv[], + int *do_set_so_pin, + int *do_set_user_pin, + int *do_set_iterations, + int *read_from_stdin, + unsigned long *iterations) +{ + char *endptr; + int c; + +#define OPT_FLG(_short_, _long_, _help_) _short_, +#define OPT_ARG(_short_, _long_, _help_) _short_, ':', + static const char short_opts[] = { OPTIONS '\0' }; +#undef OPT_ARG +#undef OPT_FLG + +#define OPT_FLG(_short_, _long_, _help_) { _long_, no_argument, NULL, _short_ }, +#define OPT_ARG(_short_, _long_, _help_) { _long_, required_argument, NULL, _short_ }, + struct option long_opts[] = { OPTIONS { NULL } }; +#undef OPT_ARG +#undef OPT_FLG + + assert(argv != 0 && + do_set_so_pin != 0 && do_set_user_pin != 0 && do_set_iterations != NULL && + read_from_stdin != NULL && iterations != NULL); + opterr = 0; + + while ((c = getopt_long(argc, argv, short_opts, long_opts, NULL)) > 0) { + switch (c) { + + case 'h': + usage(0, argv[0]); + + case 'i': + *do_set_iterations = 1; + *iterations = strtoul(optarg, &endptr, 0); + if (*optarg == '\0' || *endptr != '\0') + usage(1, argv[0]); + continue; + + case 'p': + *read_from_stdin = 1; + continue; + + case 's': + *do_set_so_pin = 1; + continue; + + case 'u': + *do_set_user_pin = 1; + continue; + + default: + usage(1, argv[0]); + } + } + + if (optind != argc) + usage(1, argv[0]); +} + + + +#define lose(_msg_) \ + do { \ + fprintf(stderr, "%s\n", _msg_); \ + goto fail; \ + } while (0) + +static int set_iterations(unsigned long iterations) +{ + static const char update_query[] = + " UPDATE global SET pbkdf2_iterations = ?"; + + sqlite3_stmt *q = NULL; + int ok = 0; + + if (!sql_check_ok(sql_prepare(&q, update_query)) || + !sql_check_ok(sqlite3_bind_int64(q, 1, iterations)) || + !sql_check_done(sqlite3_step(q))) + lose("Couldn't update database"); + + ok = 1; + + fail: + sqlite3_finalize(q); + return ok; +} + +static int set_pin(const char * const pin_type, const int read_from_stdin) +{ + static const char iterations_query[] = + " SELECT pbkdf2_iterations FROM global"; + + static const char update_format[] = + " UPDATE global SET %s_pin = ?1, %s_pin_salt = ?2"; + + /* Allow user to change these lengths? */ + uint8_t pinbuf[32], salt[16]; + + char pin[P11_MAX_PIN_LENGTH + 1], *p; + sqlite3_stmt *q = NULL; + hal_error_t err; + int ok = 0; + + if (read_from_stdin) { + if (fgets(pin, sizeof(pin), stdin) == NULL) { + perror("Couldn't read PIN"); + return 0; + } + if ((p = strchr(pin, '\n')) != NULL) + *p = '\0'; + } + + else { + char prompt[sizeof("Enter user PIN: ")]; + snprintf(prompt, sizeof(prompt), "Enter %s PIN: ", pin_type); + if (!getpin_tty(prompt, pin, sizeof(pin))) + return 0; + } + + const size_t len = strlen(pin); + + if (len < P11_MIN_PIN_LENGTH || len > P11_MAX_PIN_LENGTH) { + fprintf(stderr, "Unacceptable length %lu for %s PIN, allowd range [%lu, %lu]\n", + (unsigned long) len, pin_type, + (unsigned long) P11_MIN_PIN_LENGTH, (unsigned long) P11_MAX_PIN_LENGTH); + return 0; + } + + if (!sql_check_ok(sql_prepare(&q, iterations_query)) || + !sql_check_row(sqlite3_step(q)) || + sqlite3_column_type(q, 0) == SQLITE_NULL) + lose("Couldn't retrieve PBKDF2 iteration count from SQL"); + + if ((err = hal_get_random(salt, sizeof(salt))) != HAL_OK) { + fprintf(stderr, "Couldn't generate salt: %s\n", hal_error_string(err)); + goto fail; + } + + if ((err = hal_pbkdf2(hal_hash_sha256, (uint8_t *) pin, len, salt, sizeof(salt), + pinbuf, sizeof(pinbuf), sqlite3_column_int(q, 0))) != HAL_OK) { + fprintf(stderr, "Couldn't process new PIN: %s\n", hal_error_string(err)); + goto fail; + } + + if (!sql_check_ok(sql_finalize_and_clear(&q)) || + !sql_check_ok(sql_prepare(&q, update_format, pin_type, pin_type)) || + !sql_check_ok(sqlite3_bind_blob(q, 1, pinbuf, sizeof(pinbuf), NULL)) || + !sql_check_ok(sqlite3_bind_blob(q, 2, salt, sizeof(salt), NULL)) || + !sql_check_done(sqlite3_step(q))) + lose("Couldn't update database"); + + ok = 1; + + fail: + sqlite3_finalize(q); + memset(pin, 0, sizeof(pin)); + memset(pinbuf, 0, sizeof(pinbuf)); + memset(salt, 0, sizeof(salt)); + return ok; +} + +int main (int argc, char *argv[]) +{ + int do_set_so_pin = 0, do_set_user_pin = 0, do_set_iterations = 0, read_from_stdin = 0; + unsigned long iterations; + int ok = 0; + + parse_args(argc, argv, &do_set_so_pin, &do_set_user_pin, &do_set_iterations, &read_from_stdin, &iterations); + + if (!sql_init() || !sql_exec("BEGIN")) + lose("Couldn't initialize SQL, giving up"); + + if (do_set_iterations && !set_iterations(iterations)) + lose("Couldn't set PBKDF2 iteration count"); + + if (do_set_so_pin && !set_pin("so", read_from_stdin)) + lose("Couldn't set SO PIN"); + + if (do_set_user_pin && !set_pin("user", read_from_stdin)) + lose("Couldn't set user PIN"); + + if (!sql_exec("COMMIT")) + lose("Couldn't commit SQL transaction"); + + ok = 1; + + fail: + sql_fini(); + return !ok; +} @@ -2,12 +2,8 @@ * pkcs11.c * -------- * - * This is a partial implementation of PKCS #11 on top of Cryptlib on - * top of a HAL connecting to the Cryptech FPGA cores. - * - * This is still at a very early stage and should not (yet?) be used - * for any serious purpose. Among other things, it's not yet entirely - * clear whether this approach really is workable. + * This is a partial implementation of PKCS #11 on top of the Cryptech + * libhal library connecting to the Cryptech FPGA cores. * * Author: Rob Austein * Copyright (c) 2015, SUNET @@ -44,8 +40,6 @@ #include <stdarg.h> #include <assert.h> -#include <sqlite3.h> - #include <hal.h> /* @@ -64,7 +58,9 @@ #endif #include "pkcs11.h" + #include "attributes.h" +#include "sql_common.h" /* * This PKCS #11 implementation is hardwired with one slot, the token @@ -75,19 +71,7 @@ #define P11_ONE_AND_ONLY_SLOT 0 /* - * Placeholders for PIN length limits. Figure out real values later. - */ - -#warning Figure out PIN length limits -#define P11_MIN_PIN_LENGTH 16 -#define P11_MAX_PIN_LENGTH 4096 - -/* - * Version numbers. Placeholders for now. Cryptlib has a version - * number, but from PKCS #11's point of view, Cryptlib is part of the - * "hardware", and we're probably going to need something other than - * Cryptlib's version number for the hardware, because we have to - * represent the version number of the attached Cryptech FPGA cores. + * Version numbers. Placeholders for now. * * Software version number is just the version of this PKCS #11 * implementation. Probably. @@ -103,10 +87,6 @@ * Debugging control. */ -#ifndef DEBUG_SQL -#define DEBUG_SQL 1 -#endif - #ifndef DEBUG_HAL #define DEBUG_HAL 1 #endif @@ -116,15 +96,6 @@ #endif /* - * Default filename for SQL database lives. Can be overriden at - * runtime by setting PKCS11_DATABASE environment variable. - */ - -#ifndef SQL_DATABASE -#define SQL_DATABASE ".cryptech-pkcs11.db" -#endif - -/* * Whether to include POSIX-specific features. */ @@ -235,12 +206,6 @@ static enum { static p11_session_t *p11_sessions; /* - * SQL database. - */ - -static sqlite3 *sqldb = NULL; - -/* * Next PKCS #11 handle to allocate. We use a single handle space for * both session and object handles, and we just keep incrementing * until it wraps, to reduce the amount of time we have to spend @@ -323,32 +288,6 @@ static pid_t initialized_pid; #endif /* DEBUG_PKCS11 > 1 */ /* - * Error checking for SQLite calls. - */ - -#if DEBUG_SQL - -#define sql_whine(_expr_) \ - (fprintf(stderr, "%s:%u: %s returned %s\n", \ - __FILE__, __LINE__, #_expr_, sqlite3_errmsg(sqldb)), \ - sql_breakpoint()) - -#else /* DEBUG_SQL */ - -#define sql_whine(_expr_) \ - ((void) 0) - -#endif /* DEBUG_SQL */ - -#define sql_check(_good_, _expr_) \ - ((_expr_) == (_good_) ? 1 : (sql_whine(_expr_), 0)) - -#define sql_check_ok(_expr_) sql_check(SQLITE_OK, _expr_) -#define sql_check_row(_expr_) sql_check(SQLITE_ROW, _expr_) -#define sql_check_done(_expr_) sql_check(SQLITE_DONE, _expr_) -#define sql_whine_step() sql_whine(sqlite3_step()) - -/* * Error checking for libhal calls. */ @@ -539,153 +478,6 @@ static CK_RV posix_mutex_unlock(CK_VOID_PTR pMutex) /* - * SQL utilities. - */ - -/* - * Hook on which to hang a debugger breakpoint on SQL errors. - */ - -#if DEBUG_SQL -static void sql_breakpoint(void) -{ - fprintf(stderr, "[sql_breakpoint]\n"); -} -#endif - -/* - * Execute SQL code that doesn't require a prepared query. - */ - -static int sql_exec(const char *cmd) -{ - char *msg = NULL; - - if (sql_check_ok(sqlite3_exec(sqldb, cmd, NULL, NULL, &msg))) - return 1; - -#if DEBUG_SQL - if (msg != NULL) - fprintf(stderr, "[%s]\n", msg); -#endif - - return 0; -} - -/* - * Initialize SQL. This includes loading our schema, portions of - * which live in the temp (memory) database thus always need to be - * created on startup. - */ - -static int sql_init(void) -{ - static const char schema[] = -#include "schema.h" - ; - - assert(sqldb == NULL); - - const char * const env = getenv("PKCS11_DATABASE"); - const char * const home = getenv("HOME"); - const char * const base = SQL_DATABASE; - int ok; - - if (env != NULL) { - ok = sql_check_ok(sqlite3_open(env, &sqldb)); - } - - else if (home == NULL) { - ok = sql_check_ok(sqlite3_open(base, &sqldb)); - } - - else { - char fn[strlen(home) + strlen(base) + 2]; - snprintf(fn, sizeof(fn), "%s/%s", home, base); - ok = sql_check_ok(sqlite3_open(fn, &sqldb)); - } - - return ok && sql_exec(schema); -} - -/* - * Shut down SQL. - * - * Yes, this can return failure, although it's not clear what we're - * meant to do about that if the application is going to shut down - * regardless of what we do. I guess we could loop retrying a few - * times for errors like SQLITE_BUSY, but that's about it. - */ - -static int sql_fini(void) -{ - if (!sql_check_ok(sqlite3_close(sqldb))) - return 0; - - sqldb = NULL; - return 1; -} - -/* - * GCC attribute declaration to help catch format string errors, - * ignored by other compilers. - */ - -#ifdef __GNUC__ -static int sql_prepare(sqlite3_stmt **q, - const char *format, ...) - __attribute__ ((format (printf, 2, 3))); -#endif - -/* - * Prepare an SQLite3 query, using vsnprintf() to format the query. - * - * WARNING WARNING WARNING WARNING - * - * Do not use this formatting mechanism for anything involving - * user-supplied data. It's only intended to handle things like - * selecting between two parallel table structures or queries using - * manifest constants that are only available in C header files. - */ - -static int sql_prepare(sqlite3_stmt **q, const char *format, ...) -{ - char buffer[2048]; - va_list ap; - size_t n; - - va_start(ap, format); - n = vsnprintf(buffer, sizeof(buffer), format, ap); - va_end(ap); - - if (n >= sizeof(buffer)) - return SQLITE_TOOBIG; - - return sqlite3_prepare_v2(sqldb, buffer, -1, q, NULL); -} - -/* - * This idiom occurs frequently, bundle it so we have the option of - * doing it along with the normal conditional control flow that SQL - * queries seem to follow. - */ - -static int sql_finalize_and_clear(sqlite3_stmt **q) -{ - assert(q != NULL); - - int err = sqlite3_finalize(*q); - - if (err != SQLITE_OK) - return err; - - *q = NULL; - return SQLITE_OK; -} - - - -/* * Initialize KEK. If we had proper hardware support the KEK would be * living in special RAM where we could wipe it if anything triggered * our tamper circuitry. But we have no such at the moment, so we @@ -906,6 +698,8 @@ static int p11_attribute_get(const CK_OBJECT_HANDLE object_handle, * Wrappers to set and get CK_BBOOL and CK_ULONG values. */ +#if 0 + static int p11_attribute_set_bbool(const CK_OBJECT_HANDLE object_handle, const CK_ATTRIBUTE_TYPE type, const CK_BBOOL value) { return p11_attribute_set(object_handle, type, &value, sizeof(value)); @@ -916,6 +710,8 @@ static int p11_attribute_set_ulong(const CK_OBJECT_HANDLE object_handle, const C return p11_attribute_set(object_handle, type, &value, sizeof(value)); } +#endif + static int p11_attribute_get_bbool(const CK_OBJECT_HANDLE object_handle, const CK_ATTRIBUTE_TYPE type, CK_BBOOL *value) { CK_ULONG length; @@ -1960,7 +1756,9 @@ static CK_RV generate_keypair_rsa_pkcs(p11_session_t *session, for (i = 0; i < ulPublicKeyAttributeCount; i++) { const CK_ATTRIBUTE_TYPE type = pPublicKeyTemplate[i].type; const void * const val = pPublicKeyTemplate[i].pValue; +#if 0 const size_t len = pPublicKeyTemplate[i].ulValueLen; +#endif assert(val != NULL); @@ -1982,7 +1780,9 @@ static CK_RV generate_keypair_rsa_pkcs(p11_session_t *session, for (i = 0; i < ulPrivateKeyAttributeCount; i++) { const CK_ATTRIBUTE_TYPE type = pPrivateKeyTemplate[i].type; const void * const val = pPrivateKeyTemplate[i].pValue; +#if 0 const size_t len = pPrivateKeyTemplate[i].ulValueLen; +#endif assert (val != NULL); @@ -2453,7 +2253,6 @@ CK_RV C_GetTokenInfo(CK_SLOT_ID slotID, /* * No real idea (yet) how we get many of the following parameters. - * See cryptlib's CRYPT_DEVINFO_* attributes for some hints. * * pInfo->label is supposed to be set when the token is initialized. * Not yet sure what that means in our context, but need something @@ -2627,7 +2426,7 @@ CK_RV C_Login(CK_SESSION_HANDLE hSession, ENTER_PUBLIC_FUNCTION(C_Login); static const char pin_query_format[] = - " SELECT pbkdf2_iterations, %s_pin, %s_pin_salt from global"; + " SELECT pbkdf2_iterations, %s_pin, %s_pin_salt FROM global"; p11_session_t *session; sqlite3_stmt *q = NULL; diff --git a/sql_common.h b/sql_common.h new file mode 100644 index 0000000..8f1844b --- /dev/null +++ b/sql_common.h @@ -0,0 +1,255 @@ +/* + * sql_common.h + * ------------ + * + * Common definitions and SQL support code for Cryptech PKCS #11 engine. + * + * We could split the functions out of this into a separate .c file, + * but there's no real point in doing so, and it's simpler to keep it + * all in one file, the build process is complex enough already. + * + * Author: Rob Austein + * Copyright (c) 2015, SUNET + * + * 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. + * + * 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 OWNER 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. + */ + +#ifndef _SQL_COMMON_H_ +#define _SQL_COMMON_H_ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <stdarg.h> +#include <assert.h> + +#include <sqlite3.h> + +/* + * Placeholders for PIN length limits. Figure out real values later. + */ + +#warning Figure out PIN length limits +#define P11_MIN_PIN_LENGTH 16 +#define P11_MAX_PIN_LENGTH 4096 + +/* + * Debugging control. + */ + +#ifndef DEBUG_SQL +#define DEBUG_SQL 1 +#endif + +/* + * Default filename for SQL database lives. Can be overriden at + * runtime by setting PKCS11_DATABASE environment variable. + */ + +#ifndef SQL_DATABASE +#define SQL_DATABASE ".cryptech-pkcs11.db" +#endif + +/* + * SQL database. + */ + +static sqlite3 *sqldb = NULL; + +/* + * Error checking for SQLite calls. + */ + +#if DEBUG_SQL + +#define sql_whine(_expr_) \ + (fprintf(stderr, "%s:%u: %s returned %s\n", \ + __FILE__, __LINE__, #_expr_, sqlite3_errmsg(sqldb)), \ + sql_breakpoint()) + +#else /* DEBUG_SQL */ + +#define sql_whine(_expr_) \ + ((void) 0) + +#endif /* DEBUG_SQL */ + +#define sql_check(_good_, _expr_) \ + ((_expr_) == (_good_) ? 1 : (sql_whine(_expr_), 0)) + +#define sql_check_ok(_expr_) sql_check(SQLITE_OK, _expr_) +#define sql_check_row(_expr_) sql_check(SQLITE_ROW, _expr_) +#define sql_check_done(_expr_) sql_check(SQLITE_DONE, _expr_) +#define sql_whine_step() sql_whine(sqlite3_step()) + +/* + * Hook on which to hang a debugger breakpoint on SQL errors. + */ + +#if DEBUG_SQL +static void sql_breakpoint(void) +{ + fprintf(stderr, "[sql_breakpoint]\n"); +} +#endif + +/* + * Execute SQL code that doesn't require a prepared query. + */ + +static int sql_exec(const char *cmd) +{ + char *msg = NULL; + + if (sql_check_ok(sqlite3_exec(sqldb, cmd, NULL, NULL, &msg))) + return 1; + +#if DEBUG_SQL + if (msg != NULL) + fprintf(stderr, "[%s]\n", msg); +#endif + + return 0; +} + +/* + * Initialize SQL. This includes loading our schema, portions of + * which live in the temp (memory) database thus always need to be + * created on startup. + */ + +static int sql_init(void) +{ + static const char schema[] = +#include "schema.h" + ; + + assert(sqldb == NULL); + + const char * const env = getenv("PKCS11_DATABASE"); + const char * const home = getenv("HOME"); + const char * const base = SQL_DATABASE; + int ok; + + if (env != NULL) { + ok = sql_check_ok(sqlite3_open(env, &sqldb)); + } + + else if (home == NULL) { + ok = sql_check_ok(sqlite3_open(base, &sqldb)); + } + + else { + char fn[strlen(home) + strlen(base) + 2]; + snprintf(fn, sizeof(fn), "%s/%s", home, base); + ok = sql_check_ok(sqlite3_open(fn, &sqldb)); + } + + return ok && sql_exec(schema); +} + +/* + * Shut down SQL. + * + * Yes, this can return failure, although it's not clear what we're + * meant to do about that if the application is going to shut down + * regardless of what we do. I guess we could loop retrying a few + * times for errors like SQLITE_BUSY, but that's about it. + */ + +static int sql_fini(void) +{ + if (!sql_check_ok(sqlite3_close(sqldb))) + return 0; + + sqldb = NULL; + return 1; +} + +/* + * GCC attribute declaration to help catch format string errors, + * ignored by other compilers. + */ + +#ifdef __GNUC__ +static int sql_prepare(sqlite3_stmt **q, + const char *format, ...) + __attribute__ ((format (printf, 2, 3))); +#endif + +/* + * Prepare an SQLite3 query, using vsnprintf() to format the query. + * + * WARNING WARNING WARNING WARNING + * + * Do not use this formatting mechanism for anything involving + * user-supplied data. It's only intended to handle things like + * selecting between two parallel table structures or queries using + * manifest constants that are only available in C header files. + */ + +static int sql_prepare(sqlite3_stmt **q, const char *format, ...) +{ + char buffer[2048]; + va_list ap; + size_t n; + + va_start(ap, format); + n = vsnprintf(buffer, sizeof(buffer), format, ap); + va_end(ap); + + if (n >= sizeof(buffer)) + return SQLITE_TOOBIG; + + return sqlite3_prepare_v2(sqldb, buffer, -1, q, NULL); +} + +/* + * This idiom occurs frequently, bundle it so we have the option of + * doing it along with the normal conditional control flow that SQL + * queries seem to follow. + */ + +static int sql_finalize_and_clear(sqlite3_stmt **q) +{ + assert(q != NULL); + + int err = sqlite3_finalize(*q); + + if (err != SQLITE_OK) + return err; + + *q = NULL; + return SQLITE_OK; +} + +#endif /* _SQL_COMMON_H_ */ + +/* + * Local variables: + * indent-tabs-mode: nil + * End: + */ |