/* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Red Hat, Inc. * * The Initial Developer of the Original Code is * Red Hat, Inc. * Portions created by the Initial Developer are Copyright (C) 2005 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Robert Relyea (rrelyea@redhat.com) * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ /* * This file implements PKCS 11 on top of our existing security modules * * For more information about PKCS 11 See PKCS 11 Token Inteface Standard. * This implementation has two slots: * slot 1 is our generic crypto support. It does not require login. * It supports Public Key ops, and all they bulk ciphers and hashes. * It can also support Private Key ops for imported Private keys. It does * not have any token storage. * slot 2 is our private key support. It requires a login before use. It * can store Private Keys and Certs as token objects. Currently only private * keys and their associated Certificates are saved on the token. * * In this implementation, session objects are only visible to the session * that created or generated them. */ #include "sdb.h" #include "pkcs11t.h" #include "seccomon.h" #include #include "prthread.h" #include "prio.h" #include "stdio.h" #include "secport.h" #include "prmon.h" #include "prenv.h" #include "prsystem.h" /* for PR_GetDirectorySeparator() */ #ifdef SQLITE_UNSAFE_THREADS #include "prlock.h" /* * SQLite can be compiled to be thread safe or not. * turn on SQLITE_UNSAFE_THREADS if the OS does not support * a thread safe version of sqlite. */ static PRLock *sqlite_lock = NULL; #define LOCK_SQLITE() PR_Lock(sqlite_lock); #define UNLOCK_SQLITE() PR_Unlock(sqlite_lock); #else #define LOCK_SQLITE() #define UNLOCK_SQLITE() #endif typedef enum { SDB_CERT = 1, SDB_KEY = 2 } sdbDataType; /* * defines controlling how long we wait to acquire locks. * * SDB_SQLITE_BUSY_TIMEOUT specifies how long (in milliseconds) * sqlite will wait on lock. If that timeout expires, sqlite will * return SQLITE_BUSY. * SDB_BUSY_RETRY_TIME specifies how many seconds the sdb_ code waits * after receiving a busy before retrying. * SDB_MAX_BUSY_RETRIES specifies how many times the sdb_ will retry on * a busy condition. * * SDB_SQLITE_BUSY_TIMEOUT affects all opertions, both manual * (prepare/step/reset/finalize) and automatic (sqlite3_exec()). * SDB_BUSY_RETRY_TIME and SDB_MAX_BUSY_RETRIES only affect manual operations * * total wait time for automatic operations: * 1 second (SDB_SQLITE_BUSY_TIMEOUT/1000). * total wait time for manual operations: * (1 second + 5 seconds) * 10 = 60 seconds. * (SDB_SQLITE_BUSY_TIMEOUT/1000 + SDB_BUSY_RETRY_TIME)*SDB_MAX_BUSY_RETRIES */ #define SDB_SQLITE_BUSY_TIMEOUT 1000 /* milliseconds */ #define SDB_BUSY_RETRY_TIME 5 /* seconds */ #define SDB_MAX_BUSY_RETRIES 10 /* * Note on use of sqlReadDB: Only one thread at a time may have an actual * operation going on given sqlite3 * database. An operation is defined as * the time from a sqlite3_prepare() until the sqlite3_finalize(). * Multiple sqlite3 * databases can be open and have simultaneous operations * going. We use the sqlXactDB for all write operations. This database * is only opened when we first create a transaction and closed when the * transaction is complete. sqlReadDB is open when we first opened the database * and is used for all read operation. It's use is protected by a monitor. This * is because an operation can span the use of FindObjectsInit() through the * call to FindObjectsFinal(). In the intermediate time it is possible to call * other operations like NSC_GetAttributeValue */ struct SDBPrivateStr { char *sqlDBName; /* invariant, path to this database */ sqlite3 *sqlXactDB; /* access protected by dbMon, use protected * by the transaction. Current transaction db*/ PRThread *sqlXactThread; /* protected by dbMon, * current transaction thread */ sqlite3 *sqlReadDB; /* use protected by dbMon, value invariant */ PRIntervalTime lastUpdateTime; /* last time the cache was updated */ PRIntervalTime updateInterval; /* how long the cache can go before it * must be updated again */ sdbDataType type; /* invariant, database type */ char *table; /* invariant, SQL table which contains the db */ char *cacheTable; /* invariant, SQL table cache of db */ PRMonitor *dbMon; /* invariant, monitor to protect * sqlXact* fields, and use of the sqlReadDB */ }; typedef struct SDBPrivateStr SDBPrivate; /* * known attributes */ static const CK_ATTRIBUTE_TYPE known_attributes[] = { CKA_CLASS, CKA_TOKEN, CKA_PRIVATE, CKA_LABEL, CKA_APPLICATION, CKA_VALUE, CKA_OBJECT_ID, CKA_CERTIFICATE_TYPE, CKA_ISSUER, CKA_SERIAL_NUMBER, CKA_AC_ISSUER, CKA_OWNER, CKA_ATTR_TYPES, CKA_TRUSTED, CKA_CERTIFICATE_CATEGORY, CKA_JAVA_MIDP_SECURITY_DOMAIN, CKA_URL, CKA_HASH_OF_SUBJECT_PUBLIC_KEY, CKA_HASH_OF_ISSUER_PUBLIC_KEY, CKA_CHECK_VALUE, CKA_KEY_TYPE, CKA_SUBJECT, CKA_ID, CKA_SENSITIVE, CKA_ENCRYPT, CKA_DECRYPT, CKA_WRAP, CKA_UNWRAP, CKA_SIGN, CKA_SIGN_RECOVER, CKA_VERIFY, CKA_VERIFY_RECOVER, CKA_DERIVE, CKA_START_DATE, CKA_END_DATE, CKA_MODULUS, CKA_MODULUS_BITS, CKA_PUBLIC_EXPONENT, CKA_PRIVATE_EXPONENT, CKA_PRIME_1, CKA_PRIME_2, CKA_EXPONENT_1, CKA_EXPONENT_2, CKA_COEFFICIENT, CKA_PRIME, CKA_SUBPRIME, CKA_BASE, CKA_PRIME_BITS, CKA_SUB_PRIME_BITS, CKA_VALUE_BITS, CKA_VALUE_LEN, CKA_EXTRACTABLE, CKA_LOCAL, CKA_NEVER_EXTRACTABLE, CKA_ALWAYS_SENSITIVE, CKA_KEY_GEN_MECHANISM, CKA_MODIFIABLE, CKA_EC_PARAMS, CKA_EC_POINT, CKA_SECONDARY_AUTH, CKA_AUTH_PIN_FLAGS, CKA_ALWAYS_AUTHENTICATE, CKA_WRAP_WITH_TRUSTED, CKA_WRAP_TEMPLATE, CKA_UNWRAP_TEMPLATE, CKA_HW_FEATURE_TYPE, CKA_RESET_ON_INIT, CKA_HAS_RESET, CKA_PIXEL_X, CKA_PIXEL_Y, CKA_RESOLUTION, CKA_CHAR_ROWS, CKA_CHAR_COLUMNS, CKA_COLOR, CKA_BITS_PER_PIXEL, CKA_CHAR_SETS, CKA_ENCODING_METHODS, CKA_MIME_TYPES, CKA_MECHANISM_TYPE, CKA_REQUIRED_CMS_ATTRIBUTES, CKA_DEFAULT_CMS_ATTRIBUTES, CKA_SUPPORTED_CMS_ATTRIBUTES, CKA_NETSCAPE_URL, CKA_NETSCAPE_EMAIL, CKA_NETSCAPE_SMIME_INFO, CKA_NETSCAPE_SMIME_TIMESTAMP, CKA_NETSCAPE_PKCS8_SALT, CKA_NETSCAPE_PASSWORD_CHECK, CKA_NETSCAPE_EXPIRES, CKA_NETSCAPE_KRL, CKA_NETSCAPE_PQG_COUNTER, CKA_NETSCAPE_PQG_SEED, CKA_NETSCAPE_PQG_H, CKA_NETSCAPE_PQG_SEED_BITS, CKA_NETSCAPE_MODULE_SPEC, CKA_TRUST_DIGITAL_SIGNATURE, CKA_TRUST_NON_REPUDIATION, CKA_TRUST_KEY_ENCIPHERMENT, CKA_TRUST_DATA_ENCIPHERMENT, CKA_TRUST_KEY_AGREEMENT, CKA_TRUST_KEY_CERT_SIGN, CKA_TRUST_CRL_SIGN, CKA_TRUST_SERVER_AUTH, CKA_TRUST_CLIENT_AUTH, CKA_TRUST_CODE_SIGNING, CKA_TRUST_EMAIL_PROTECTION, CKA_TRUST_IPSEC_END_SYSTEM, CKA_TRUST_IPSEC_TUNNEL, CKA_TRUST_IPSEC_USER, CKA_TRUST_TIME_STAMPING, CKA_TRUST_STEP_UP_APPROVED, CKA_CERT_SHA1_HASH, CKA_CERT_MD5_HASH, CKA_NETSCAPE_DB, CKA_NETSCAPE_TRUST, CKA_NSS_OVERRIDE_EXTENSIONS }; static int known_attributes_size= sizeof(known_attributes)/ sizeof(known_attributes[0]); /* Magic for an explicit NULL. NOTE: ideally this should be * out of band data. Since it's not completely out of band, pick * a value that has no meaning to any existing PKCS #11 attributes. * This value is 1) not a valid string (imbedded '\0'). 2) not a U_LONG * or a normal key (too short). 3) not a bool (too long). 4) not an RSA * public exponent (too many bits). */ const unsigned char SQLITE_EXPLICIT_NULL[] = { 0xa5, 0x0, 0x5a }; #define SQLITE_EXPLICIT_NULL_LEN 3 /* * determine when we've completed our tasks */ static int sdb_done(int err, int *count) { /* allow as many rows as the database wants to give */ if (err == SQLITE_ROW) { *count = 0; return 0; } if (err != SQLITE_BUSY) { return 1; } /* err == SQLITE_BUSY, Dont' retry forever in this case */ if (++(*count) >= SDB_MAX_BUSY_RETRIES) { return 1; } return 0; } /* * * strdup limited to 'n' bytes. (Note: len of file is assumed to be >= len) * * We don't have a PORT_ version of this function, * I suspect it's only normally available in glib, */ static char * sdb_strndup(const char *file, int len) { char *result = PORT_Alloc(len+1); if (result == NULL) { return result; } PORT_Memcpy(result, file, len); result[len] = 0; return result; } /* * call back from sqlite3_exec("Pragma database_list"). Looks for the * temp directory, then return the file the temp directory is stored * at. */ static int sdb_getTempDirCallback(void *arg, int columnCount, char **cval, char **cname) { int i; int found = 0; char *file = NULL; char *end, *dir; char dirsep; /* we've already found the temp directory, don't look at any more records*/ if (*(char **)arg) { return SQLITE_OK; } /* look at the columns to see if this record is the temp database, * and does it say where it is stored */ for (i=0; i < columnCount; i++) { if (PORT_Strcmp(cname[i],"name") == 0) { if (PORT_Strcmp(cval[i], "temp") == 0) { found++; continue; } } if (PORT_Strcmp(cname[i],"file") == 0) { if (cval[i] && (*cval[i] != 0)) { file = cval[i]; } } } /* if we couldn't find it, ask for the next record */ if (!found || !file) { return SQLITE_OK; } /* drop of the database file name and just return the directory */ dirsep = PR_GetDirectorySeparator(); end = PORT_Strrchr(file, dirsep); if (!end) { return SQLITE_OK; } dir = sdb_strndup(file, end-file); *(char **)arg = dir; return SQLITE_OK; } /* * find out where sqlite stores the temp tables. We do this by creating * a temp table, then looking for the database name that sqlite3 creates. */ static char * sdb_getTempDir(sqlite3 *sqlDB) { char *tempDir = NULL; int sqlerr; /* create a temporary table */ sqlerr = sqlite3_exec(sqlDB, "CREATE TEMPORARY TABLE myTemp (id)", NULL, 0, NULL); if (sqlerr != SQLITE_OK) { return NULL; } /* look for through the database list for the temp directory */ sqlerr = sqlite3_exec(sqlDB, "PRAGMA database_list", sdb_getTempDirCallback, &tempDir, NULL); /* drop the temp table we created */ sqlite3_exec(sqlDB, "DROP TABLE myTemp", NULL, 0, NULL); if (sqlerr != SQLITE_OK) { return NULL; } return tempDir; } /* * Map SQL_LITE errors to PKCS #11 errors as best we can. */ static int sdb_mapSQLError(sdbDataType type, int sqlerr) { switch (sqlerr) { /* good matches */ case SQLITE_OK: case SQLITE_DONE: return CKR_OK; case SQLITE_NOMEM: return CKR_HOST_MEMORY; case SQLITE_READONLY: return CKR_TOKEN_WRITE_PROTECTED; /* close matches */ case SQLITE_AUTH: case SQLITE_PERM: /*return CKR_USER_NOT_LOGGED_IN; */ case SQLITE_CANTOPEN: case SQLITE_NOTFOUND: /* NSS distiguishes between failure to open the cert and the key db */ return type == SDB_CERT ? CKR_NETSCAPE_CERTDB_FAILED : CKR_NETSCAPE_KEYDB_FAILED; case SQLITE_IOERR: return CKR_DEVICE_ERROR; default: break; } return CKR_GENERAL_ERROR; } /* * build up database name from a directory, prefix, name, version and flags. */ static char *sdb_BuildFileName(const char * directory, const char *prefix, const char *type, int version, int flags) { char *dbname = NULL; /* build the full dbname */ dbname = sqlite3_mprintf("%s/%s%s%d.db",directory, prefix, type, version); return dbname; } /* * find out how expensive the access system call is for non-existant files * in the given directory. Return the number of operations done in 33 ms. */ static PRUint32 sdb_measureAccess(const char *directory) { PRUint32 i; PRIntervalTime time; PRIntervalTime delta; PRIntervalTime duration = PR_MillisecondsToInterval(33); /* no directory, just return one */ if (directory == NULL) { return 1; } /* measure number of Access operations that can be done in 33 milliseconds * (1/30'th of a second), or 10000 operations, which ever comes first. */ time = PR_IntervalNow(); for (i=0; i < 10000u; i++) { char *temp; PRIntervalTime next; temp = sdb_BuildFileName(directory,"","._dOeSnotExist_", time+i, 0); PR_Access(temp,PR_ACCESS_EXISTS); sqlite3_free(temp); next = PR_IntervalNow(); delta = next - time; if (delta >= duration) break; } /* always return 1 or greater */ return i ? i : 1u; } /* * some file sytems are very slow to run sqlite3 on, particularly if the * access count is pretty high. On these filesystems is faster to create * a temporary database on the local filesystem and access that. This * code uses a temporary table to create that cache. Temp tables are * automatically cleared when the database handle it was created on * Is freed. */ static const char DROP_CACHE_CMD[] = "DROP TABLE %s"; static const char CREATE_CACHE_CMD[] = "CREATE TEMPORARY TABLE %s AS SELECT * FROM %s"; static const char CREATE_ISSUER_INDEX_CMD[] = "CREATE INDEX issuer ON %s (a81)"; static const char CREATE_SUBJECT_INDEX_CMD[] = "CREATE INDEX subject ON %s (a101)"; static const char CREATE_LABEL_INDEX_CMD[] = "CREATE INDEX label ON %s (a3)"; static const char CREATE_ID_INDEX_CMD[] = "CREATE INDEX ckaid ON %s (a102)"; static CK_RV sdb_buildCache(sqlite3 *sqlDB, sdbDataType type, const char *cacheTable, const char *table) { char *newStr; int sqlerr = SQLITE_OK; newStr = sqlite3_mprintf(CREATE_CACHE_CMD, cacheTable, table); if (newStr == NULL) { return CKR_HOST_MEMORY; } sqlerr = sqlite3_exec(sqlDB, newStr, NULL, 0, NULL); sqlite3_free(newStr); if (sqlerr != SQLITE_OK) { return sdb_mapSQLError(type, sqlerr); } /* failure to create the indexes is not an issue */ newStr = sqlite3_mprintf(CREATE_ISSUER_INDEX_CMD, cacheTable); if (newStr == NULL) { return CKR_OK; } sqlerr = sqlite3_exec(sqlDB, newStr, NULL, 0, NULL); sqlite3_free(newStr); newStr = sqlite3_mprintf(CREATE_SUBJECT_INDEX_CMD, cacheTable); if (newStr == NULL) { return CKR_OK; } sqlerr = sqlite3_exec(sqlDB, newStr, NULL, 0, NULL); sqlite3_free(newStr); newStr = sqlite3_mprintf(CREATE_LABEL_INDEX_CMD, cacheTable); if (newStr == NULL) { return CKR_OK; } sqlerr = sqlite3_exec(sqlDB, newStr, NULL, 0, NULL); sqlite3_free(newStr); newStr = sqlite3_mprintf(CREATE_ID_INDEX_CMD, cacheTable); if (newStr == NULL) { return CKR_OK; } sqlerr = sqlite3_exec(sqlDB, newStr, NULL, 0, NULL); sqlite3_free(newStr); return CKR_OK; } /* * update the cache and the data records describing it. * The cache is updated by dropping the temp database and recreating it. */ static CK_RV sdb_updateCache(SDBPrivate *sdb_p) { int sqlerr = SQLITE_OK; CK_RV error = CKR_OK; char *newStr; /* drop the old table */ newStr = sqlite3_mprintf(DROP_CACHE_CMD, sdb_p->cacheTable); if (newStr == NULL) { return CKR_HOST_MEMORY; } sqlerr = sqlite3_exec(sdb_p->sqlReadDB, newStr, NULL, 0, NULL); sqlite3_free(newStr); if ((sqlerr != SQLITE_OK) && (sqlerr != SQLITE_ERROR )) { /* something went wrong with the drop, don't try to refresh... * NOTE: SQLITE_ERROR is returned if the table doesn't exist. In * that case, we just continue on and try to reload it */ return sdb_mapSQLError(sdb_p->type, sqlerr); } /* set up the new table */ error = sdb_buildCache(sdb_p->sqlReadDB,sdb_p->type, sdb_p->cacheTable,sdb_p->table ); if (error == CKR_OK) { /* we have a new cache! */ sdb_p->lastUpdateTime = PR_IntervalNow(); } return error; } /* * The sharing of sqlite3 handles across threads is tricky. Older versions * couldn't at all, but newer ones can under strict conditions. Basically * no 2 threads can use the same handle while another thread has an open * stmt running. Once the sqlite3_stmt is finalized, another thread can then * use the database handle. * * We use monitors to protect against trying to use a database before * it's sqlite3_stmt is finalized. This is preferable to the opening and * closing the database each operation because there is significant overhead * in the open and close. Also continually opening and closing the database * defeats the cache code as the cache table is lost on close (thus * requiring us to have to reinitialize the cache every operation). * * An execption to the shared handle is transations. All writes happen * through a transaction. When we are in a transaction, we must use the * same database pointer for that entire transation. In this case we save * the transaction database and use it for all accesses on the transaction * thread. Other threads use the common database. * * There can only be once active transaction on the database at a time. * * sdb_openDBLocal() provides us with a valid database handle for whatever * state we are in (reading or in a transaction), and acquires any locks * appropriate to that state. It also decides when it's time to refresh * the cache before we start an operation. Any database handle returned * just eventually be closed with sdb_closeDBLocal(). * * The table returned either points to the database's physical table, or * to the cached shadow. Tranactions always return the physical table * and read operations return either the physical table or the cache * depending on whether or not the cache exists. */ static CK_RV sdb_openDBLocal(SDBPrivate *sdb_p, sqlite3 **sqlDB, const char **table) { *sqlDB = NULL; PR_EnterMonitor(sdb_p->dbMon); if (table) { *table = sdb_p->table; } /* We're in a transaction, use the transaction DB */ if ((sdb_p->sqlXactDB) && (sdb_p->sqlXactThread == PR_GetCurrentThread())) { *sqlDB =sdb_p->sqlXactDB; /* only one thread can get here, safe to unlock */ PR_ExitMonitor(sdb_p->dbMon); return CKR_OK; } /* * if we are just reading from the table, we may have the table * cached in a temporary table (especially if it's on a shared FS). * In that case we want to see updates to the table, the the granularity * is on order of human scale, not computer scale. */ if (table && sdb_p->cacheTable) { PRIntervalTime now = PR_IntervalNow(); if ((now - sdb_p->lastUpdateTime) > sdb_p->updateInterval) { sdb_updateCache(sdb_p); } *table = sdb_p->cacheTable; } *sqlDB = sdb_p->sqlReadDB; /* leave holding the lock. only one thread can actually use a given * database connection at once */ return CKR_OK; } /* closing the local database currenly means unlocking the monitor */ static CK_RV sdb_closeDBLocal(SDBPrivate *sdb_p, sqlite3 *sqlDB) { if (sdb_p->sqlXactDB != sqlDB) { /* if we weren't in a transaction, we got a lock */ PR_ExitMonitor(sdb_p->dbMon); } return CKR_OK; } /* * wrapper to sqlite3_open which also sets the busy_timeout */ static int sdb_openDB(const char *name, sqlite3 **sqlDB, int flags) { int sqlerr; /* * in sqlite3 3.5.0, there is a new open call that allows us * to specify read only. Most new OS's are still on 3.3.x (including * NSS's internal version and the version shipped with Firefox). */ *sqlDB = NULL; sqlerr = sqlite3_open(name, sqlDB); if (sqlerr != SQLITE_OK) { return sqlerr; } sqlerr = sqlite3_busy_timeout(*sqlDB, SDB_SQLITE_BUSY_TIMEOUT); if (sqlerr != SQLITE_OK) { sqlite3_close(*sqlDB); *sqlDB = NULL; return sqlerr; } return SQLITE_OK; } /* Sigh, if we created a new table since we opened the database, * the database handle will not see the new table, we need to close this * database and reopen it. Caller must be in a transaction or holding * the dbMon. sqlDB is changed on success. */ static int sdb_reopenDBLocal(SDBPrivate *sdb_p, sqlite3 **sqlDB) { sqlite3 *newDB; int sqlerr; /* open a new database */ sqlerr = sdb_openDB(sdb_p->sqlDBName, &newDB, SDB_RDONLY); if (sqlerr != SQLITE_OK) { return sqlerr; } /* if we are in a transaction, we may not be holding the monitor. * grab it before we update the transaction database. This is * safe since are using monitors. */ PR_EnterMonitor(sdb_p->dbMon); /* update our view of the database */ if (sdb_p->sqlReadDB == *sqlDB) { sdb_p->sqlReadDB = newDB; } else if (sdb_p->sqlXactDB == *sqlDB) { sdb_p->sqlXactDB = newDB; } PR_ExitMonitor(sdb_p->dbMon); /* close the old one */ sqlite3_close(*sqlDB); *sqlDB = newDB; return SQLITE_OK; } struct SDBFindStr { sqlite3 *sqlDB; sqlite3_stmt *findstmt; }; static const char FIND_OBJECTS_CMD[] = "SELECT ALL * FROM %s WHERE %s;"; static const char FIND_OBJECTS_ALL_CMD[] = "SELECT ALL * FROM %s;"; CK_RV sdb_FindObjectsInit(SDB *sdb, const CK_ATTRIBUTE *template, CK_ULONG count, SDBFind **find) { SDBPrivate *sdb_p = sdb->private; sqlite3 *sqlDB = NULL; const char *table; char *newStr, *findStr = NULL; sqlite3_stmt *findstmt = NULL; char *join=""; int sqlerr = SQLITE_OK; CK_RV error = CKR_OK; int i; LOCK_SQLITE() *find = NULL; error = sdb_openDBLocal(sdb_p, &sqlDB, &table); if (error != CKR_OK) { goto loser; } findStr = sqlite3_mprintf(""); for (i=0; findStr && i < count; i++) { newStr = sqlite3_mprintf("%s%sa%x=$DATA%d", findStr, join, template[i].type, i); join=" AND "; sqlite3_free(findStr); findStr = newStr; } if (findStr == NULL) { error = CKR_HOST_MEMORY; goto loser; } if (count == 0) { newStr = sqlite3_mprintf(FIND_OBJECTS_ALL_CMD, table); } else { newStr = sqlite3_mprintf(FIND_OBJECTS_CMD, table, findStr); } sqlite3_free(findStr); if (newStr == NULL) { error = CKR_HOST_MEMORY; goto loser; } sqlerr = sqlite3_prepare_v2(sqlDB, newStr, -1, &findstmt, NULL); sqlite3_free(newStr); for (i=0; sqlerr == SQLITE_OK && i < count; i++) { const void *blobData = template[i].pValue; unsigned int blobSize = template[i].ulValueLen; if (blobSize == 0) { blobSize = SQLITE_EXPLICIT_NULL_LEN; blobData = SQLITE_EXPLICIT_NULL; } sqlerr = sqlite3_bind_blob(findstmt, i+1, blobData, blobSize, SQLITE_TRANSIENT); } if (sqlerr == SQLITE_OK) { *find = PORT_New(SDBFind); if (*find == NULL) { error = CKR_HOST_MEMORY; goto loser; } (*find)->findstmt = findstmt; (*find)->sqlDB = sqlDB; UNLOCK_SQLITE() return CKR_OK; } error = sdb_mapSQLError(sdb_p->type, sqlerr); loser: if (findstmt) { sqlite3_finalize(findstmt); } if (sqlDB) { sdb_closeDBLocal(sdb_p, sqlDB) ; } UNLOCK_SQLITE() return error; } CK_RV sdb_FindObjects(SDB *sdb, SDBFind *sdbFind, CK_OBJECT_HANDLE *object, CK_ULONG arraySize, CK_ULONG *count) { SDBPrivate *sdb_p = sdb->private; sqlite3_stmt *stmt = sdbFind->findstmt; int sqlerr = SQLITE_OK; int retry = 0; *count = 0; if (arraySize == 0) { return CKR_OK; } LOCK_SQLITE() do { sqlerr = sqlite3_step(stmt); if (sqlerr == SQLITE_BUSY) { PR_Sleep(SDB_BUSY_RETRY_TIME); } if (sqlerr == SQLITE_ROW) { /* only care about the id */ *object++= sqlite3_column_int(stmt, 0); arraySize--; (*count)++; } } while (!sdb_done(sqlerr,&retry) && (arraySize > 0)); /* we only have some of the objects, there is probably more, * set the sqlerr to an OK value so we return CKR_OK */ if (sqlerr == SQLITE_ROW && arraySize == 0) { sqlerr = SQLITE_DONE; } UNLOCK_SQLITE() return sdb_mapSQLError(sdb_p->type, sqlerr); } CK_RV sdb_FindObjectsFinal(SDB *sdb, SDBFind *sdbFind) { SDBPrivate *sdb_p = sdb->private; sqlite3_stmt *stmt = sdbFind->findstmt; sqlite3 *sqlDB = sdbFind->sqlDB; int sqlerr = SQLITE_OK; LOCK_SQLITE() if (stmt) { sqlite3_reset(stmt); sqlerr = sqlite3_finalize(stmt); } if (sqlDB) { sdb_closeDBLocal(sdb_p, sqlDB) ; } PORT_Free(sdbFind); UNLOCK_SQLITE() return sdb_mapSQLError(sdb_p->type, sqlerr); } static const char GET_ATTRIBUTE_CMD[] = "SELECT ALL %s FROM %s WHERE id=$ID;"; CK_RV sdb_GetAttributeValueNoLock(SDB *sdb, CK_OBJECT_HANDLE object_id, CK_ATTRIBUTE *template, CK_ULONG count) { SDBPrivate *sdb_p = sdb->private; sqlite3 *sqlDB = NULL; sqlite3_stmt *stmt = NULL; char *getStr = NULL; char *newStr = NULL; const char *table = NULL; int sqlerr = SQLITE_OK; CK_RV error = CKR_OK; int found = 0; int retry = 0; int i; /* open a new db if necessary */ error = sdb_openDBLocal(sdb_p, &sqlDB, &table); if (error != CKR_OK) { goto loser; } getStr = sqlite3_mprintf(""); for (i=0; getStr && i < count; i++) { if (i==0) { newStr = sqlite3_mprintf("a%x", template[i].type); } else { newStr = sqlite3_mprintf("%s, a%x", getStr, template[i].type); } sqlite3_free(getStr); getStr = newStr; } if (getStr == NULL) { error = CKR_HOST_MEMORY; goto loser; } newStr = sqlite3_mprintf(GET_ATTRIBUTE_CMD, getStr, table); sqlite3_free(getStr); getStr = NULL; if (newStr == NULL) { error = CKR_HOST_MEMORY; goto loser; } sqlerr = sqlite3_prepare_v2(sqlDB, newStr, -1, &stmt, NULL); if (sqlerr != SQLITE_OK) { goto loser; } sqlerr = sqlite3_bind_int(stmt, 1, object_id); if (sqlerr != SQLITE_OK) { goto loser; } do { sqlerr = sqlite3_step(stmt); if (sqlerr == SQLITE_BUSY) { PR_Sleep(SDB_BUSY_RETRY_TIME); } if (sqlerr == SQLITE_ROW) { for (i=0; i < count; i++) { int column = i; int blobSize; const char *blobData; blobSize = sqlite3_column_bytes(stmt, column); blobData = sqlite3_column_blob(stmt, column); if (blobData == NULL) { template[i].ulValueLen = -1; error = CKR_ATTRIBUTE_TYPE_INVALID; continue; } /* If the blob equals our explicit NULL value, then the * attribute is a NULL. */ if ((blobSize == SQLITE_EXPLICIT_NULL_LEN) && (PORT_Memcmp(blobData, SQLITE_EXPLICIT_NULL, SQLITE_EXPLICIT_NULL_LEN) == 0)) { blobSize = 0; } if (template[i].pValue) { if (template[i].ulValueLen < blobSize) { template[i].ulValueLen = -1; error = CKR_BUFFER_TOO_SMALL; continue; } PORT_Memcpy(template[i].pValue, blobData, blobSize); } template[i].ulValueLen = blobSize; } found = 1; } } while (!sdb_done(sqlerr,&retry)); loser: /* fix up the error if necessary */ if (error == CKR_OK) { error = sdb_mapSQLError(sdb_p->type, sqlerr); if (!found && error == CKR_OK) { error = CKR_OBJECT_HANDLE_INVALID; } } if (newStr) { sqlite3_free(newStr); } if (stmt) { sqlite3_reset(stmt); sqlite3_finalize(stmt); } /* if we had to open a new database, free it now */ if (sqlDB) { sdb_closeDBLocal(sdb_p, sqlDB) ; } return error; } CK_RV sdb_GetAttributeValue(SDB *sdb, CK_OBJECT_HANDLE object_id, CK_ATTRIBUTE *template, CK_ULONG count) { CK_RV crv; if (count == 0) { return CKR_OK; } LOCK_SQLITE() crv = sdb_GetAttributeValueNoLock(sdb, object_id, template, count); UNLOCK_SQLITE() return crv; } static const char SET_ATTRIBUTE_CMD[] = "UPDATE %s SET %s WHERE id=$ID;"; CK_RV sdb_SetAttributeValue(SDB *sdb, CK_OBJECT_HANDLE object_id, const CK_ATTRIBUTE *template, CK_ULONG count) { SDBPrivate *sdb_p = sdb->private; sqlite3 *sqlDB = NULL; sqlite3_stmt *stmt = NULL; char *setStr = NULL; char *newStr = NULL; int sqlerr = SQLITE_OK; int retry = 0; CK_RV error = CKR_OK; int i; if ((sdb->sdb_flags & SDB_RDONLY) != 0) { return CKR_TOKEN_WRITE_PROTECTED; } if (count == 0) { return CKR_OK; } LOCK_SQLITE() setStr = sqlite3_mprintf(""); for (i=0; setStr && i < count; i++) { if (i==0) { sqlite3_free(setStr); setStr = sqlite3_mprintf("a%x=$VALUE%d", template[i].type, i); continue; } newStr = sqlite3_mprintf("%s,a%x=$VALUE%d", setStr, template[i].type, i); sqlite3_free(setStr); setStr = newStr; } newStr = NULL; if (setStr == NULL) { return CKR_HOST_MEMORY; } newStr = sqlite3_mprintf(SET_ATTRIBUTE_CMD, sdb_p->table, setStr); sqlite3_free(setStr); if (newStr == NULL) { UNLOCK_SQLITE() return CKR_HOST_MEMORY; } error = sdb_openDBLocal(sdb_p, &sqlDB, NULL); if (error != CKR_OK) { goto loser; } sqlerr = sqlite3_prepare_v2(sqlDB, newStr, -1, &stmt, NULL); if (sqlerr != SQLITE_OK) goto loser; for (i=0; i < count; i++) { if (template[i].ulValueLen != 0) { sqlerr = sqlite3_bind_blob(stmt, i+1, template[i].pValue, template[i].ulValueLen, SQLITE_STATIC); } else { sqlerr = sqlite3_bind_blob(stmt, i+2, SQLITE_EXPLICIT_NULL, SQLITE_EXPLICIT_NULL_LEN, SQLITE_STATIC); } if (sqlerr != SQLITE_OK) goto loser; } sqlerr = sqlite3_bind_int(stmt, i+1, object_id); if (sqlerr != SQLITE_OK) goto loser; do { sqlerr = sqlite3_step(stmt); if (sqlerr == SQLITE_BUSY) { PR_Sleep(SDB_BUSY_RETRY_TIME); } } while (!sdb_done(sqlerr,&retry)); loser: if (newStr) { sqlite3_free(newStr); } if (error == CKR_OK) { error = sdb_mapSQLError(sdb_p->type, sqlerr); } if (stmt) { sqlite3_reset(stmt); sqlite3_finalize(stmt); } if (sqlDB) { sdb_closeDBLocal(sdb_p, sqlDB) ; } UNLOCK_SQLITE() return error; } /* * check to see if a candidate object handle already exists. */ static PRBool sdb_objectExists(SDB *sdb, CK_OBJECT_HANDLE candidate) { CK_RV crv; CK_ATTRIBUTE template = { CKA_LABEL, NULL, 0 }; crv = sdb_GetAttributeValueNoLock(sdb,candidate,&template, 1); if (crv == CKR_OBJECT_HANDLE_INVALID) { return PR_FALSE; } return PR_TRUE; } /* * if we're here, we are in a transaction, so it's safe * to examine the current state of the database */ static CK_OBJECT_HANDLE sdb_getObjectId(SDB *sdb) { CK_OBJECT_HANDLE candidate; static CK_OBJECT_HANDLE next_obj = CK_INVALID_HANDLE; int count; /* * get an initial object handle to use */ if (next_obj == CK_INVALID_HANDLE) { PRTime time; time = PR_Now(); next_obj = (CK_OBJECT_HANDLE)(time & 0x3fffffffL); } candidate = next_obj++; /* detect that we've looped through all the handles... */ for (count = 0; count < 0x40000000; count++, candidate = next_obj++) { /* mask off excess bits */ candidate &= 0x3fffffff; /* if we hit zero, go to the next entry */ if (candidate == CK_INVALID_HANDLE) { continue; } /* make sure we aren't already using */ if (!sdb_objectExists(sdb, candidate)) { /* this one is free */ return candidate; } } /* no handle is free, fail */ return CK_INVALID_HANDLE; } static const char CREATE_CMD[] = "INSERT INTO %s (id%s) VALUES($ID%s);"; CK_RV sdb_CreateObject(SDB *sdb, CK_OBJECT_HANDLE *object_id, const CK_ATTRIBUTE *template, CK_ULONG count) { SDBPrivate *sdb_p = sdb->private; sqlite3 *sqlDB = NULL; sqlite3_stmt *stmt = NULL; char *columnStr = NULL; char *valueStr = NULL; char *newStr = NULL; int sqlerr = SQLITE_OK; CK_RV error = CKR_OK; CK_OBJECT_HANDLE this_object = CK_INVALID_HANDLE; int retry = 0; int i; if ((sdb->sdb_flags & SDB_RDONLY) != 0) { return CKR_TOKEN_WRITE_PROTECTED; } LOCK_SQLITE() if ((*object_id != CK_INVALID_HANDLE) && !sdb_objectExists(sdb, *object_id)) { this_object = *object_id; } else { this_object = sdb_getObjectId(sdb); } if (this_object == CK_INVALID_HANDLE) { UNLOCK_SQLITE(); return CKR_HOST_MEMORY; } columnStr = sqlite3_mprintf(""); valueStr = sqlite3_mprintf(""); *object_id = this_object; for (i=0; columnStr && valueStr && i < count; i++) { newStr = sqlite3_mprintf("%s,a%x", columnStr, template[i].type); sqlite3_free(columnStr); columnStr = newStr; newStr = sqlite3_mprintf("%s,$VALUE%d", valueStr, i); sqlite3_free(valueStr); valueStr = newStr; } newStr = NULL; if ((columnStr == NULL) || (valueStr == NULL)) { if (columnStr) { sqlite3_free(columnStr); } if (valueStr) { sqlite3_free(valueStr); } UNLOCK_SQLITE() return CKR_HOST_MEMORY; } newStr = sqlite3_mprintf(CREATE_CMD, sdb_p->table, columnStr, valueStr); sqlite3_free(columnStr); sqlite3_free(valueStr); error = sdb_openDBLocal(sdb_p, &sqlDB, NULL); if (error != CKR_OK) { goto loser; } sqlerr = sqlite3_prepare_v2(sqlDB, newStr, -1, &stmt, NULL); if (sqlerr != SQLITE_OK) goto loser; sqlerr = sqlite3_bind_int(stmt, 1, *object_id); if (sqlerr != SQLITE_OK) goto loser; for (i=0; i < count; i++) { if (template[i].ulValueLen) { sqlerr = sqlite3_bind_blob(stmt, i+2, template[i].pValue, template[i].ulValueLen, SQLITE_STATIC); } else { sqlerr = sqlite3_bind_blob(stmt, i+2, SQLITE_EXPLICIT_NULL, SQLITE_EXPLICIT_NULL_LEN, SQLITE_STATIC); } if (sqlerr != SQLITE_OK) goto loser; } do { sqlerr = sqlite3_step(stmt); if (sqlerr == SQLITE_BUSY) { PR_Sleep(SDB_BUSY_RETRY_TIME); } } while (!sdb_done(sqlerr,&retry)); loser: if (newStr) { sqlite3_free(newStr); } if (error == CKR_OK) { error = sdb_mapSQLError(sdb_p->type, sqlerr); } if (stmt) { sqlite3_reset(stmt); sqlite3_finalize(stmt); } if (sqlDB) { sdb_closeDBLocal(sdb_p, sqlDB) ; } UNLOCK_SQLITE() return error; } static const char DESTROY_CMD[] = "DELETE FROM %s WHERE (id=$ID);"; CK_RV sdb_DestroyObject(SDB *sdb, CK_OBJECT_HANDLE object_id) { SDBPrivate *sdb_p = sdb->private; sqlite3 *sqlDB = NULL; sqlite3_stmt *stmt = NULL; char *newStr = NULL; int sqlerr = SQLITE_OK; CK_RV error = CKR_OK; int retry = 0; if ((sdb->sdb_flags & SDB_RDONLY) != 0) { return CKR_TOKEN_WRITE_PROTECTED; } LOCK_SQLITE() error = sdb_openDBLocal(sdb_p, &sqlDB, NULL); if (error != CKR_OK) { goto loser; } newStr = sqlite3_mprintf(DESTROY_CMD, sdb_p->table); if (newStr == NULL) { error = CKR_HOST_MEMORY; goto loser; } sqlerr =sqlite3_prepare_v2(sqlDB, newStr, -1, &stmt, NULL); sqlite3_free(newStr); if (sqlerr != SQLITE_OK) goto loser; sqlerr =sqlite3_bind_int(stmt, 1, object_id); if (sqlerr != SQLITE_OK) goto loser; do { sqlerr = sqlite3_step(stmt); if (sqlerr == SQLITE_BUSY) { PR_Sleep(SDB_BUSY_RETRY_TIME); } } while (!sdb_done(sqlerr,&retry)); loser: if (error == CKR_OK) { error = sdb_mapSQLError(sdb_p->type, sqlerr); } if (stmt) { sqlite3_reset(stmt); sqlite3_finalize(stmt); } if (sqlDB) { sdb_closeDBLocal(sdb_p, sqlDB) ; } UNLOCK_SQLITE() return error; } static const char BEGIN_CMD[] = "BEGIN IMMEDIATE TRANSACTION;"; /* * start a transaction. * * We need to open a new database, then store that new database into * the private data structure. We open the database first, then use locks * to protect storing the data to prevent deadlocks. */ CK_RV sdb_Begin(SDB *sdb) { SDBPrivate *sdb_p = sdb->private; sqlite3 *sqlDB = NULL; sqlite3_stmt *stmt = NULL; int sqlerr = SQLITE_OK; CK_RV error = CKR_OK; int retry = 0; if ((sdb->sdb_flags & SDB_RDONLY) != 0) { return CKR_TOKEN_WRITE_PROTECTED; } LOCK_SQLITE() /* get a new version that we will use for the entire transaction */ sqlerr = sdb_openDB(sdb_p->sqlDBName, &sqlDB, SDB_RDWR); if (sqlerr != SQLITE_OK) { goto loser; } sqlerr =sqlite3_prepare_v2(sqlDB, BEGIN_CMD, -1, &stmt, NULL); do { sqlerr = sqlite3_step(stmt); if (sqlerr == SQLITE_BUSY) { PR_Sleep(SDB_BUSY_RETRY_TIME); } } while (!sdb_done(sqlerr,&retry)); if (stmt) { sqlite3_reset(stmt); sqlite3_finalize(stmt); } loser: error = sdb_mapSQLError(sdb_p->type, sqlerr); /* we are starting a new transaction, * and if we succeeded, then save this database for the rest of * our transaction */ if (error == CKR_OK) { /* we hold a 'BEGIN TRANSACTION' and a sdb_p->lock. At this point * sdb_p->sqlXactDB MUST be null */ PR_EnterMonitor(sdb_p->dbMon); PORT_Assert(sdb_p->sqlXactDB == NULL); sdb_p->sqlXactDB = sqlDB; sdb_p->sqlXactThread = PR_GetCurrentThread(); PR_ExitMonitor(sdb_p->dbMon); } else { /* we failed to start our transaction, * free any databases we opened. */ if (sqlDB) { sqlite3_close(sqlDB); } } UNLOCK_SQLITE() return error; } /* * Complete a transaction. Basically undo everything we did in begin. * There are 2 flavors Abort and Commit. Basically the only differerence between * these 2 are what the database will show. (no change in to former, change in * the latter). */ static CK_RV sdb_complete(SDB *sdb, const char *cmd) { SDBPrivate *sdb_p = sdb->private; sqlite3 *sqlDB = NULL; sqlite3_stmt *stmt = NULL; int sqlerr = SQLITE_OK; CK_RV error = CKR_OK; int retry = 0; if ((sdb->sdb_flags & SDB_RDONLY) != 0) { return CKR_TOKEN_WRITE_PROTECTED; } /* We must have a transation database, or we shouldn't have arrived here */ PR_EnterMonitor(sdb_p->dbMon); PORT_Assert(sdb_p->sqlXactDB); if (sdb_p->sqlXactDB == NULL) { PR_ExitMonitor(sdb_p->dbMon); return CKR_GENERAL_ERROR; /* shouldn't happen */ } PORT_Assert( sdb_p->sqlXactThread == PR_GetCurrentThread()); if ( sdb_p->sqlXactThread != PR_GetCurrentThread()) { PR_ExitMonitor(sdb_p->dbMon); return CKR_GENERAL_ERROR; /* shouldn't happen */ } sqlDB = sdb_p->sqlXactDB; sdb_p->sqlXactDB = NULL; /* no one else can get to this DB, * safe to unlock */ sdb_p->sqlXactThread = NULL; PR_ExitMonitor(sdb_p->dbMon); sqlerr =sqlite3_prepare_v2(sqlDB, cmd, -1, &stmt, NULL); do { sqlerr = sqlite3_step(stmt); if (sqlerr == SQLITE_BUSY) { PR_Sleep(SDB_BUSY_RETRY_TIME); } } while (!sdb_done(sqlerr,&retry)); /* Pending BEGIN TRANSACTIONS Can move forward at this point. */ if (stmt) { sqlite3_reset(stmt); sqlite3_finalize(stmt); } /* we we have a cached DB image, update it as well */ if (sdb_p->cacheTable) { PR_EnterMonitor(sdb_p->dbMon); sdb_updateCache(sdb_p); PR_ExitMonitor(sdb_p->dbMon); } error = sdb_mapSQLError(sdb_p->type, sqlerr); /* We just finished a transaction. * Free the database, and remove it from the list */ sqlite3_close(sqlDB); return error; } static const char COMMIT_CMD[] = "COMMIT TRANSACTION;"; CK_RV sdb_Commit(SDB *sdb) { CK_RV crv; LOCK_SQLITE() crv = sdb_complete(sdb,COMMIT_CMD); UNLOCK_SQLITE() return crv; } static const char ROLLBACK_CMD[] = "ROLLBACK TRANSACTION;"; CK_RV sdb_Abort(SDB *sdb) { CK_RV crv; LOCK_SQLITE() crv = sdb_complete(sdb,ROLLBACK_CMD); UNLOCK_SQLITE() return crv; } static int tableExists(sqlite3 *sqlDB, const char *tableName); static const char GET_PW_CMD[] = "SELECT ALL * FROM metaData WHERE id=$ID;"; CK_RV sdb_GetMetaData(SDB *sdb, const char *id, SECItem *item1, SECItem *item2) { SDBPrivate *sdb_p = sdb->private; sqlite3 *sqlDB = sdb_p->sqlXactDB; sqlite3_stmt *stmt = NULL; int sqlerr = SQLITE_OK; CK_RV error = CKR_OK; int found = 0; int retry = 0; LOCK_SQLITE() error = sdb_openDBLocal(sdb_p, &sqlDB, NULL); if (error != CKR_OK) { goto loser; } /* handle 'test' versions of the sqlite db */ sqlerr = sqlite3_prepare_v2(sqlDB, GET_PW_CMD, -1, &stmt, NULL); /* Sigh, if we created a new table since we opened the database, * the database handle will not see the new table, we need to close this * database and reopen it. This is safe because we are holding the lock * still. */ if (sqlerr == SQLITE_SCHEMA) { sqlerr = sdb_reopenDBLocal(sdb_p, &sqlDB); if (sqlerr != SQLITE_OK) { goto loser; } sqlerr = sqlite3_prepare_v2(sqlDB, GET_PW_CMD, -1, &stmt, NULL); } if (sqlerr != SQLITE_OK) goto loser; sqlerr = sqlite3_bind_text(stmt, 1, id, PORT_Strlen(id), SQLITE_STATIC); do { sqlerr = sqlite3_step(stmt); if (sqlerr == SQLITE_BUSY) { PR_Sleep(SDB_BUSY_RETRY_TIME); } if (sqlerr == SQLITE_ROW) { const char *blobData; unsigned int len = item1->len; item1->len = sqlite3_column_bytes(stmt, 1); if (item1->len > len) { error = CKR_BUFFER_TOO_SMALL; continue; } blobData = sqlite3_column_blob(stmt, 1); PORT_Memcpy(item1->data,blobData, item1->len); if (item2) { len = item2->len; item2->len = sqlite3_column_bytes(stmt, 2); if (item2->len > len) { error = CKR_BUFFER_TOO_SMALL; continue; } blobData = sqlite3_column_blob(stmt, 2); PORT_Memcpy(item2->data,blobData, item2->len); } found = 1; } } while (!sdb_done(sqlerr,&retry)); loser: /* fix up the error if necessary */ if (error == CKR_OK) { error = sdb_mapSQLError(sdb_p->type, sqlerr); if (!found && error == CKR_OK) { error = CKR_OBJECT_HANDLE_INVALID; } } if (stmt) { sqlite3_reset(stmt); sqlite3_finalize(stmt); } if (sqlDB) { sdb_closeDBLocal(sdb_p, sqlDB) ; } UNLOCK_SQLITE() return error; } static const char PW_CREATE_TABLE_CMD[] = "CREATE TABLE metaData (id PRIMARY KEY UNIQUE ON CONFLICT REPLACE, item1, item2);"; static const char PW_CREATE_CMD[] = "INSERT INTO metaData (id,item1,item2) VALUES($ID,$ITEM1,$ITEM2);"; static const char MD_CREATE_CMD[] = "INSERT INTO metaData (id,item1) VALUES($ID,$ITEM1);"; CK_RV sdb_PutMetaData(SDB *sdb, const char *id, const SECItem *item1, const SECItem *item2) { SDBPrivate *sdb_p = sdb->private; sqlite3 *sqlDB = sdb_p->sqlXactDB; sqlite3_stmt *stmt = NULL; int sqlerr = SQLITE_OK; CK_RV error = CKR_OK; int retry = 0; const char *cmd = PW_CREATE_CMD; LOCK_SQLITE() error = sdb_openDBLocal(sdb_p, &sqlDB, NULL); if (error != CKR_OK) { goto loser; } if (!tableExists(sqlDB, "metaData")) { sqlerr = sqlite3_exec(sqlDB, PW_CREATE_TABLE_CMD, NULL, 0, NULL); if (sqlerr != SQLITE_OK) goto loser; } if (item2 == NULL) { cmd = MD_CREATE_CMD; } sqlerr = sqlite3_prepare_v2(sqlDB, cmd, -1, &stmt, NULL); if (sqlerr != SQLITE_OK) goto loser; sqlerr = sqlite3_bind_text(stmt, 1, id, PORT_Strlen(id), SQLITE_STATIC); if (sqlerr != SQLITE_OK) goto loser; sqlerr = sqlite3_bind_blob(stmt, 2, item1->data, item1->len, SQLITE_STATIC); if (sqlerr != SQLITE_OK) goto loser; if (item2) { sqlerr = sqlite3_bind_blob(stmt, 3, item2->data, item2->len, SQLITE_STATIC); if (sqlerr != SQLITE_OK) goto loser; } do { sqlerr = sqlite3_step(stmt); if (sqlerr == SQLITE_BUSY) { PR_Sleep(SDB_BUSY_RETRY_TIME); } } while (!sdb_done(sqlerr,&retry)); loser: /* fix up the error if necessary */ if (error == CKR_OK) { error = sdb_mapSQLError(sdb_p->type, sqlerr); } if (stmt) { sqlite3_reset(stmt); sqlite3_finalize(stmt); } if (sqlDB) { sdb_closeDBLocal(sdb_p, sqlDB) ; } UNLOCK_SQLITE() return error; } static const char RESET_CMD[] = "DROP TABLE IF EXISTS %s;"; CK_RV sdb_Reset(SDB *sdb) { SDBPrivate *sdb_p = sdb->private; sqlite3 *sqlDB = NULL; char *newStr; int sqlerr = SQLITE_OK; CK_RV error = CKR_OK; /* only Key databases can be reset */ if (sdb_p->type != SDB_KEY) { return CKR_OBJECT_HANDLE_INVALID; } LOCK_SQLITE() error = sdb_openDBLocal(sdb_p, &sqlDB, NULL); if (error != CKR_OK) { goto loser; } /* delete the key table */ newStr = sqlite3_mprintf(RESET_CMD, sdb_p->table); if (newStr == NULL) { error = CKR_HOST_MEMORY; goto loser; } sqlerr = sqlite3_exec(sqlDB, newStr, NULL, 0, NULL); sqlite3_free(newStr); if (sqlerr != SQLITE_OK) goto loser; /* delete the password entry table */ sqlerr = sqlite3_exec(sqlDB, "DROP TABLE IF EXISTS metaData;", NULL, 0, NULL); loser: /* fix up the error if necessary */ if (error == CKR_OK) { error = sdb_mapSQLError(sdb_p->type, sqlerr); } if (sqlDB) { sdb_closeDBLocal(sdb_p, sqlDB) ; } UNLOCK_SQLITE() return error; } CK_RV sdb_Close(SDB *sdb) { SDBPrivate *sdb_p = sdb->private; int sqlerr = SQLITE_OK; sdbDataType type = sdb_p->type; sqlerr = sqlite3_close(sdb_p->sqlReadDB); PORT_Free(sdb_p->sqlDBName); if (sdb_p->cacheTable) { sqlite3_free(sdb_p->cacheTable); } if (sdb_p->dbMon) { PR_DestroyMonitor(sdb_p->dbMon); } free(sdb_p); free(sdb); return sdb_mapSQLError(type, sqlerr); } /* * functions to support open */ static const char CHECK_TABLE_CMD[] = "SELECT ALL * FROM %s LIMIT 0;"; /* return 1 if sqlDB contains table 'tableName */ static int tableExists(sqlite3 *sqlDB, const char *tableName) { char * cmd = sqlite3_mprintf(CHECK_TABLE_CMD, tableName); int sqlerr = SQLITE_OK; if (cmd == NULL) { return 0; } sqlerr = sqlite3_exec(sqlDB, cmd, NULL, 0, 0); sqlite3_free(cmd); return (sqlerr == SQLITE_OK) ? 1 : 0; } void sdb_SetForkState(PRBool forked) { /* XXXright now this is a no-op. The global fork state in the softokn3 * shared library is already taken care of at the PKCS#11 level. * If and when we add fork state to the sqlite shared library and extern * interface, we will need to set it and reset it from here */ } /* * initialize a single database */ static const char INIT_CMD[] = "CREATE TABLE %s (id PRIMARY KEY UNIQUE ON CONFLICT ABORT%s)"; static const char ALTER_CMD[] = "ALTER TABLE %s ADD COLUMN a%x"; CK_RV sdb_init(char *dbname, char *table, sdbDataType type, int *inUpdate, int *newInit, int flags, PRUint32 accessOps, SDB **pSdb) { int i; char *initStr = NULL; char *newStr; int inTransaction = 0; SDB *sdb = NULL; SDBPrivate *sdb_p = NULL; sqlite3 *sqlDB = NULL; int sqlerr = SQLITE_OK; CK_RV error = CKR_OK; char *cacheTable = NULL; PRIntervalTime now = 0; char *env; PRBool enableCache = PR_FALSE; *pSdb = NULL; *inUpdate = 0; /* sqlite3 doesn't have a flag to specify that we want to * open the database read only. If the db doesn't exist, * sqlite3 will always create it. */ LOCK_SQLITE(); if ((flags == SDB_RDONLY) && PR_Access(dbname, PR_ACCESS_EXISTS)) { error = sdb_mapSQLError(type, SQLITE_CANTOPEN); goto loser; } sqlerr = sdb_openDB(dbname, &sqlDB, flags); if (sqlerr != SQLITE_OK) { error = sdb_mapSQLError(type, sqlerr); goto loser; } if (flags != SDB_RDONLY) { sqlerr = sqlite3_exec(sqlDB, BEGIN_CMD, NULL, 0, NULL); if (sqlerr != SQLITE_OK) { error = sdb_mapSQLError(type, sqlerr); goto loser; } inTransaction = 1; } if (!tableExists(sqlDB,table)) { *newInit = 1; if (flags != SDB_CREATE) { error = sdb_mapSQLError(type, SQLITE_CANTOPEN); goto loser; } initStr = sqlite3_mprintf(""); for (i=0; initStr && i < known_attributes_size; i++) { newStr = sqlite3_mprintf("%s, a%x",initStr, known_attributes[i]); sqlite3_free(initStr); initStr = newStr; } if (initStr == NULL) { error = CKR_HOST_MEMORY; goto loser; } newStr = sqlite3_mprintf(INIT_CMD, table, initStr); sqlite3_free(initStr); if (newStr == NULL) { error = CKR_HOST_MEMORY; goto loser; } sqlerr = sqlite3_exec(sqlDB, newStr, NULL, 0, NULL); sqlite3_free(newStr); if (sqlerr != SQLITE_OK) { error = sdb_mapSQLError(type, sqlerr); goto loser; } newStr = sqlite3_mprintf(CREATE_ISSUER_INDEX_CMD, table); if (newStr == NULL) { error = CKR_HOST_MEMORY; goto loser; } sqlerr = sqlite3_exec(sqlDB, newStr, NULL, 0, NULL); sqlite3_free(newStr); if (sqlerr != SQLITE_OK) { error = sdb_mapSQLError(type, sqlerr); goto loser; } newStr = sqlite3_mprintf(CREATE_SUBJECT_INDEX_CMD, table); if (newStr == NULL) { error = CKR_HOST_MEMORY; goto loser; } sqlerr = sqlite3_exec(sqlDB, newStr, NULL, 0, NULL); sqlite3_free(newStr); if (sqlerr != SQLITE_OK) { error = sdb_mapSQLError(type, sqlerr); goto loser; } newStr = sqlite3_mprintf(CREATE_LABEL_INDEX_CMD, table); if (newStr == NULL) { error = CKR_HOST_MEMORY; goto loser; } sqlerr = sqlite3_exec(sqlDB, newStr, NULL, 0, NULL); sqlite3_free(newStr); if (sqlerr != SQLITE_OK) { error = sdb_mapSQLError(type, sqlerr); goto loser; } newStr = sqlite3_mprintf(CREATE_ID_INDEX_CMD, table); if (newStr == NULL) { error = CKR_HOST_MEMORY; goto loser; } sqlerr = sqlite3_exec(sqlDB, newStr, NULL, 0, NULL); sqlite3_free(newStr); if (sqlerr != SQLITE_OK) { error = sdb_mapSQLError(type, sqlerr); goto loser; } } /* * detect the case where we have created the database, but have * not yet updated it. * * We only check the Key database because only the key database has * a metaData table. The metaData table is created when a password * is set, or in the case of update, when a password is supplied. * If no key database exists, then the update would have happened immediately * on noticing that the cert database didn't exist (see newInit set above). */ if (type == SDB_KEY && !tableExists(sqlDB, "metaData")) { *newInit = 1; } /* access to network filesystems are significantly slower than local ones * for database operations. In those cases we need to create a cached copy * of the database in a temporary location on the local disk. SQLITE * already provides a way to create a temporary table and initialize it, * so we use it for the cache (see sdb_buildCache for how it's done).*/ /* * we decide whether or not to use the cache based on the following input. * * NSS_SDB_USE_CACHE environment variable is non-existant or set to * anything other than "no" or "yes" ("auto", for instance). * This is the normal case. NSS will measure the performance of access * to the temp database versus the access to the users passed in * database location. If the temp database location is "significantly" * faster we will use the cache. * * NSS_SDB_USE_CACHE environment variable is set to "no": cache will not * be used. * * NSS_SDB_USE_CACHE environment variable is set to "yes": cache will * always be used. * * It is expected that most applications would use the "auto" selection, * the environment variable is primarily to simplify testing, and to * correct potential corner cases where */ env = PR_GetEnv("NSS_SDB_USE_CACHE"); if (env && PORT_Strcasecmp(env,"no") == 0) { enableCache = PR_FALSE; } else if (env && PORT_Strcasecmp(env,"yes") == 0) { enableCache = PR_TRUE; } else { char *tempDir = NULL; PRUint32 tempOps = 0; /* * Use PR_Access to determine how expensive it * is to check for the existance of a local file compared to the same * check in the temp directory. If the temp directory is faster, cache * the database there. */ tempDir = sdb_getTempDir(sqlDB); if (tempDir) { tempOps = sdb_measureAccess(tempDir); PORT_Free(tempDir); /* There is a cost to continually copying the database. * Account for that cost with the arbitrary factor of 10 */ enableCache = (PRBool)(tempOps > accessOps * 10); } } if (enableCache) { /* try to set the temp store to memory.*/ sqlite3_exec(sqlDB, "PRAGMA temp_store=MEMORY", NULL, 0, NULL); /* Failure to set the temp store to memory is not fatal, * ignore the error */ cacheTable = sqlite3_mprintf("%sCache",table); if (cacheTable == NULL) { error = CKR_HOST_MEMORY; goto loser; } /* build the cache table */ error = sdb_buildCache(sqlDB, type, cacheTable, table); if (error != CKR_OK) { goto loser; } /* initialize the last cache build time */ now = PR_IntervalNow(); } sdb = (SDB *) malloc(sizeof(SDB)); sdb_p = (SDBPrivate *) malloc(sizeof(SDBPrivate)); /* invariant fields */ sdb_p->sqlDBName = PORT_Strdup(dbname); sdb_p->type = type; sdb_p->table = table; sdb_p->cacheTable = cacheTable; sdb_p->lastUpdateTime = now; /* set the cache delay time. This is how long we will wait before we * decide the existing cache is stale. Currently set to 10 sec */ sdb_p->updateInterval = PR_SecondsToInterval(10); sdb_p->dbMon = PR_NewMonitor(); /* these fields are protected by the lock */ sdb_p->sqlXactDB = NULL; sdb_p->sqlXactThread = NULL; sdb->private = sdb_p; sdb->sdb_type = SDB_SQL; sdb->sdb_flags = flags | SDB_HAS_META; sdb->sdb_FindObjectsInit = sdb_FindObjectsInit; sdb->sdb_FindObjects = sdb_FindObjects; sdb->sdb_FindObjectsFinal = sdb_FindObjectsFinal; sdb->sdb_GetAttributeValue = sdb_GetAttributeValue; sdb->sdb_SetAttributeValue = sdb_SetAttributeValue; sdb->sdb_CreateObject = sdb_CreateObject; sdb->sdb_DestroyObject = sdb_DestroyObject; sdb->sdb_GetMetaData = sdb_GetMetaData; sdb->sdb_PutMetaData = sdb_PutMetaData; sdb->sdb_Begin = sdb_Begin; sdb->sdb_Commit = sdb_Commit; sdb->sdb_Abort = sdb_Abort; sdb->sdb_Close = sdb_Close; sdb->sdb_SetForkState = sdb_SetForkState; if (inTransaction) { sqlerr = sqlite3_exec(sqlDB, COMMIT_CMD, NULL, 0, NULL); if (sqlerr != SQLITE_OK) { error = sdb_mapSQLError(sdb_p->type, sqlerr); goto loser; } inTransaction = 0; } sdb_p->sqlReadDB = sqlDB; *pSdb = sdb; UNLOCK_SQLITE(); return CKR_OK; loser: /* lots of stuff to do */ if (inTransaction) { sqlite3_exec(sqlDB, ROLLBACK_CMD, NULL, 0, NULL); } if (sdb) { free(sdb); } if (sdb_p) { free(sdb_p); } if (sqlDB) { sqlite3_close(sqlDB); } UNLOCK_SQLITE(); return error; } /* sdbopen */ CK_RV s_open(const char *directory, const char *certPrefix, const char *keyPrefix, int cert_version, int key_version, int flags, SDB **certdb, SDB **keydb, int *newInit) { char *cert = sdb_BuildFileName(directory, certPrefix, "cert", cert_version, flags); char *key = sdb_BuildFileName(directory, keyPrefix, "key", key_version, flags); CK_RV error = CKR_OK; int inUpdate; PRUint32 accessOps; *certdb = NULL; *keydb = NULL; *newInit = 0; #ifdef SQLITE_UNSAFE_THREADS if (sqlite_lock == NULL) { sqlite_lock = PR_NewLock(); if (sqlite_lock == NULL) { error = CKR_HOST_MEMORY; goto loser; } } #endif /* how long does it take to test for a non-existant file in our working * directory? Allows us to test if we may be on a network file system */ accessOps = sdb_measureAccess(directory); /* * open the cert data base */ if (certdb) { /* initialize Certificate database */ error = sdb_init(cert, "nssPublic", SDB_CERT, &inUpdate, newInit, flags, accessOps, certdb); if (error != CKR_OK) { goto loser; } } /* * open the key data base: * NOTE:if we want to implement a single database, we open * the same database file as the certificate here. * * cert an key db's have different tables, so they will not * conflict. */ if (keydb) { /* initialize the Key database */ error = sdb_init(key, "nssPrivate", SDB_KEY, &inUpdate, newInit, flags, accessOps, keydb); if (error != CKR_OK) { goto loser; } } loser: if (cert) { sqlite3_free(cert); } if (key) { sqlite3_free(key); } if (error != CKR_OK) { /* currently redundant, but could be necessary if more code is added * just before loser */ if (keydb && *keydb) { sdb_Close(*keydb); } if (certdb && *certdb) { sdb_Close(*certdb); } } return error; } CK_RV s_shutdown() { #ifdef SQLITE_UNSAFE_THREADS if (sqlite_lock) { PR_DestroyLock(sqlite_lock); sqlite_lock = NULL; } #endif return CKR_OK; }