/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* * 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.
*/
#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;
/* * 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 + SDB_BUSY_RETRY_TIME) * 30 = 30 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 /* 'ticks', varies by platforms */ #define SDB_MAX_BUSY_RETRIES 30
/* * 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 */
CK_ATTRIBUTE_TYPE *schemaAttrs; /* Attribute columns that exist in the table. */ unsignedint numSchemaAttrs;
};
typedefstruct SDBPrivateStr SDBPrivate;
/* 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).
*/ constunsignedchar SQLITE_EXPLICIT_NULL[] = { 0xa5, 0x0, 0x5a }; #define SQLITE_EXPLICIT_NULL_LEN 3
/* * determine when we've completed our tasks
*/ staticint
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;
}
#ifdefined(_WIN32) /* * NSPR functions and narrow CRT functions do not handle UTF-8 file paths that * sqlite3 expects.
*/
staticint
sdb_chmod(constchar *filename, int pmode)
{ int result;
if (!filename) { return -1;
}
wchar_t *filenameWide = _NSSUTIL_UTF8ToWide(filename); if (!filenameWide) { return -1;
}
result = _wchmod(filenameWide, pmode);
PORT_Free(filenameWide);
/* * find out where sqlite stores the temp tables. We do this by replicating * the logic from sqlite.
*/ #ifdefined(_WIN32) staticchar *
sdb_getFallbackTempDir(void)
{ /* sqlite uses sqlite3_temp_directory if it is not NULL. We don't have * access to sqlite3_temp_directory because it is not exported from * sqlite3.dll. Assume sqlite3_win32_set_directory isn't called and * sqlite3_temp_directory is NULL.
*/ char path[MAX_PATH];
DWORD rv;
size_t len;
rv = GetTempPathA(MAX_PATH, path); if (rv > MAX_PATH || rv == 0) return NULL;
len = strlen(path); if (len == 0) return NULL; /* The returned string ends with a backslash, for example, "C:\TEMP\". */ if (path[len - 1] == '\\')
path[len - 1] = '\0'; return PORT_Strdup(path);
} #elifdefined(XP_UNIX) staticchar *
sdb_getFallbackTempDir(void)
{ constchar *azDirs[] = {
NULL,
NULL, "/var/tmp", "/usr/tmp", "/tmp",
NULL /* List terminator */
}; unsignedint i; struct stat buf; constchar *zDir = NULL;
for (i = 0; i < PR_ARRAY_SIZE(azDirs); i++) {
zDir = azDirs[i]; if (zDir == NULL) continue; if (stat(zDir, &buf)) continue; if (!S_ISDIR(buf.st_mode)) continue; if (access(zDir, 07)) continue; break;
}
if (zDir == NULL) return NULL; return PORT_Strdup(zDir);
} #else #error"sdb_getFallbackTempDir not implemented" #endif
#ifndef SQLITE_FCNTL_TEMPFILENAME /* SQLITE_FCNTL_TEMPFILENAME was added in SQLite 3.7.15 */ #define SQLITE_FCNTL_TEMPFILENAME 16 #endif
/* Obtain temporary filename in sqlite's directory for temporary tables */
sqlrv = sqlite3_file_control(sqlDB, 0, SQLITE_FCNTL_TEMPFILENAME,
(void *)&tempName); if (sqlrv == SQLITE_NOTFOUND) { /* SQLITE_FCNTL_TEMPFILENAME not implemented because we are using
* an older SQLite. */ return sdb_getFallbackTempDir();
} if (sqlrv != SQLITE_OK) { return NULL;
}
/* We'll extract the temporary directory from tempName */
foundSeparator = PORT_Strrchr(tempName, PR_GetDirectorySeparator()); if (foundSeparator) { /* We shorten the temp filename string to contain only * the directory name (including the trailing separator). * We know the byte after the foundSeparator position is * safe to use, in the shortest scenario it contains the * end-of-string byte. * By keeping the separator at the found position, it will * even work if tempDir consists of the separator, only. * (In this case the toplevel directory will be used for
* access speed testing). */
++foundSeparator;
*foundSeparator = 0;
/* Now we copy the directory name for our caller */
result = PORT_Strdup(tempName);
}
sqlite3_free(tempName); return result;
}
/* * Map SQL_LITE errors to PKCS #11 errors as best we can.
*/ static CK_RV
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_NSS_CERTDB_FAILED : CKR_NSS_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.
*/ staticchar *
sdb_BuildFileName(constchar *directory, constchar *prefix, constchar *type, int version)
{ char *dbname = NULL; /* build the full dbname */
dbname = sqlite3_mprintf("%s%c%s%s%d.db", directory,
(int)(unsignedchar)PR_GetDirectorySeparator(),
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(constchar *directory)
{
PRUint32 i;
PRIntervalTime time;
PRIntervalTime delta;
PRIntervalTime duration = PR_MillisecondsToInterval(33); constchar *doesntExistName = "_dOeSnotExist_.db"; char *temp, *tempStartOfFilename;
size_t maxTempLen, maxFileNameLen, directoryLength, tmpdirLength = 0; #ifdef SDB_MEASURE_USE_TEMP_DIR /* * on some OS's and Filesystems, creating a bunch of files and deleting * them messes up the systems's caching, but if we create the files in * a temp directory which we later delete, then the cache gets cleared * up. This code uses several OS dependent calls, and it's not clear * that temp directory use won't mess up other filesystems and OS caching, * so if you need this for your OS, you can turn on the * 'SDB_MEASURE_USE_TEMP_DIR' define in coreconf
*/ constchartemplate[] = "dbTemp.XXXXXX";
tmpdirLength = sizeof(template); #endif /* no directory, just return one */ if (directory == NULL) { return 1;
}
/* our calculation assumes time is a 4 bytes == 32 bit integer */
PORT_Assert(sizeof(time) == 4);
directoryLength = strlen(directory);
maxTempLen = directoryLength + 1 /* dirname + / */
+ tmpdirLength /* tmpdirname includes / */
+ strlen(doesntExistName) /* filename base */
+ 11 /* max chars for 32 bit int plus potential sign */
+ 1; /* zero terminator */
temp = PORT_ZAlloc(maxTempLen); if (!temp) { return 1;
}
/* We'll copy directory into temp just once, then ensure it ends
* with the directory separator. */
#ifdef SDB_MEASURE_USE_TEMP_DIR /* add the template for a temporary subdir, and create it */
strcat(temp, template); if (!mkdtemp(temp)) {
PORT_Free(temp); return 1;
} /* and terminate that tmp subdir with a / */
strcat(temp, "/"); #endif
/* Remember the position after the last separator, and calculate the
* number of remaining bytes. */
tempStartOfFilename = temp + directoryLength + tmpdirLength;
maxFileNameLen = maxTempLen - directoryLength;
/* 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++) {
PRIntervalTime next;
/* We'll use the variable part first in the filename string, just in * case it's longer than assumed, so if anything gets cut off, it * will be cut off from the constant part. * This code assumes the directory name at the beginning of
* temp remains unchanged during our loop. */
PR_snprintf(tempStartOfFilename, maxFileNameLen, ".%lu%s", (PRUint32)(time + i), doesntExistName);
PR_Access(temp, PR_ACCESS_EXISTS);
next = PR_IntervalNow();
delta = next - time; if (delta >= duration) break;
}
#ifdef SDB_MEASURE_USE_TEMP_DIR /* turn temp back into our tmpdir path by removing doesntExistName, and
* remove the tmp dir */
*tempStartOfFilename = '\0';
(void)rmdir(temp); #endif
PORT_Free(temp);
/* 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.
*/ staticconstchar DROP_CACHE_CMD[] = "DROP TABLE %s"; staticconstchar CREATE_CACHE_CMD[] = "CREATE TEMPORARY TABLE %s AS SELECT * FROM %s"; staticconstchar CREATE_ISSUER_INDEX_CMD[] = "CREATE INDEX issuer ON %s (a81)"; staticconstchar CREATE_SUBJECT_INDEX_CMD[] = "CREATE INDEX subject ON %s (a101)"; staticconstchar CREATE_LABEL_INDEX_CMD[] = "CREATE INDEX label ON %s (a3)"; staticconstchar CREATE_ID_INDEX_CMD[] = "CREATE INDEX ckaid ON %s (a102)";
/* * 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, constchar **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
*/ staticint
sdb_openDB(constchar *name, sqlite3 **sqlDB, int flags)
{ int sqlerr; int openFlags;
*sqlDB = NULL;
if (flags & SDB_RDONLY) {
openFlags = SQLITE_OPEN_READONLY;
} else {
openFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE; /* sqlite 3.34 seem to incorrectly open readwrite.
* when the file is readonly. Explicitly reject that issue here */ if ((_NSSUTIL_Access(name, PR_ACCESS_EXISTS) == PR_SUCCESS) && (_NSSUTIL_Access(name, PR_ACCESS_WRITE_OK) != PR_SUCCESS)) { return SQLITE_READONLY;
}
}
/* 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. */ staticint
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;
} elseif (sdb_p->sqlXactDB == *sqlDB) {
sdb_p->sqlXactDB = newDB;
}
PR_ExitMonitor(sdb_p->dbMon);
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()
// NB: indices in sqlite3_bind_int are 1-indexed
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) {
PORT_Assert(!found); for (i = 0; i < count; i++) { unsignedint blobSize; constchar *blobData;
// NB: indices in sqlite_column_{bytes,blob} are 0-indexed
blobSize = sqlite3_column_bytes(stmt, i);
blobData = sqlite3_column_blob(stmt, i); if (blobData == NULL) { /* PKCS 11 requires that get attributes process all the * attributes in the template, marking the attributes with
* issues with -1. Mark the error but continue */ 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) { /* like CKR_ATTRIBUTE_TYPE_INVALID, continue processing */ 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));
if (!invalidExists) {
validTemplate = template;
validCount = count;
} else { /* Create a new template containing only the valid subset of
* input |template|, and query with that. */
validCount = tmplIdx;
validTemplate = malloc(sizeof(CK_ATTRIBUTE) * count); if (!validTemplate) { return CKR_HOST_MEMORY;
} /* Copy in what we already know is valid. */ for (i = 0; i < validCount; i++) {
validTemplate[i] = template[i];
}
/* tmplIdx was left at the index of the first invalid * attribute, which has been handled. We only need to
* deal with the remainder. */
tmplIdx++; for (; tmplIdx < count; tmplIdx++) { if (sdb_attributeExists(sdb, template[tmplIdx].type)) {
validTemplate[validCount++] = template[tmplIdx];
} else { template[tmplIdx].ulValueLen = -1;
}
}
}
/* If an invalid attribute was removed above, let * the caller know. Any other error from the actual
* query should propogate. */
crv = (crv2 == CKR_OK) ? crv : crv2;
}
if (invalidExists) { /* Copy out valid lengths. */
tmplIdx = 0; for (resIdx = 0; resIdx < validCount; resIdx++) { for (; tmplIdx < count; tmplIdx++) { if (template[tmplIdx].type != validTemplate[resIdx].type) { continue;
} template[tmplIdx].ulValueLen = validTemplate[resIdx].ulValueLen;
tmplIdx++; break;
}
}
free(validTemplate);
}
/* * 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;
}
id = sdb_getObjectId(sdb); if (id == CK_INVALID_HANDLE) { return CKR_DEVICE_MEMORY; /* basically we ran out of resources */
}
*object = id; return CKR_OK;
}
/* * 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;
}
/* 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, constchar *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);
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);
/* 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) { constchar *blobData; unsignedint 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;
}
staticconstchar PW_CREATE_TABLE_CMD[] = "CREATE TABLE metaData (id PRIMARY KEY UNIQUE ON CONFLICT REPLACE, item1, item2);"; staticconstchar PW_CREATE_CMD[] = "INSERT INTO metaData (id,item1,item2) VALUES($ID,$ITEM1,$ITEM2);"; staticconstchar MD_CREATE_CMD[] = "INSERT INTO metaData (id,item1) VALUES($ID,$ITEM1);";
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 */
}
/* 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();
create = (_NSSUTIL_Access(dbname, PR_ACCESS_EXISTS) != PR_SUCCESS); if ((flags == SDB_RDONLY) && create) {
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;
}
/* * SQL created the file, but it doesn't set appropriate modes for * a database. * * NO NSPR call for chmod? :(
*/ if (create && sdb_chmod(dbname, 0600) != 0) {
error = sdb_mapSQLError(type, SQLITE_CANTOPEN); 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 set to anything other than * "yes" or "no" (for instance, "auto"): NSS will measure the performance * of access to the temp database versus the access to the user's * passed-in database location. If the temp database location is * "significantly" faster we will use the cache. * * NSS_SDB_USE_CACHE environment variable is nonexistent or 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 will not need this feature, and * thus it is disabled by default.
*/
env = PR_GetEnvSecure("NSS_SDB_USE_CACHE");
/* Variables enableCache, checkFSType, measureSpeed are PR_FALSE by default, * which is the expected behavior for NSS_SDB_USE_CACHE="no".
* We don't need to check for "no" here. */ if (!env) { /* By default, with no variable set, we avoid expensive measuring for * most FS types. We start with inexpensive FS type checking, and
--> --------------------
--> maximum size reached
--> --------------------
Messung V0.5
¤ Dauer der Verarbeitung: 0.68 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.