mirror of
https://github.com/rn10950/RetroZilla.git
synced 2024-11-11 02:10:17 +01:00
44b7f056d9
bug1001332, 56b691c003ad, bug1086145, bug1054069, bug1155922, bug991783, bug1125025, bug1162521, bug1162644, bug1132941, bug1164364, bug1166205, bug1166163, bug1166515, bug1138554, bug1167046, bug1167043, bug1169451, bug1172128, bug1170322, bug102794, bug1128184, bug557830, bug1174648, bug1180244, bug1177784, bug1173413, bug1169174, bug1084669, bug951455, bug1183395, bug1177430, bug1183827, bug1160139, bug1154106, bug1142209, bug1185033, bug1193467, bug1182667(with sha512 changes backed out, which breaks VC6 compilation), bug1158489, bug337796
2088 lines
58 KiB
C
2088 lines
58 KiB
C
/* 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.
|
|
*/
|
|
|
|
#include "sdb.h"
|
|
#include "pkcs11t.h"
|
|
#include "seccomon.h"
|
|
#include <sqlite3.h>
|
|
#include "prthread.h"
|
|
#include "prio.h"
|
|
#include <stdio.h>
|
|
#include "secport.h"
|
|
#include "prmon.h"
|
|
#include "prenv.h"
|
|
#include "prprf.h"
|
|
#include "prsystem.h" /* for PR_GetDirectorySeparator() */
|
|
#include <sys/stat.h>
|
|
#if defined(_WIN32)
|
|
#include <io.h>
|
|
#include <windows.h>
|
|
#elif defined(XP_UNIX)
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#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;
|
|
}
|
|
|
|
/*
|
|
* find out where sqlite stores the temp tables. We do this by replicating
|
|
* the logic from sqlite.
|
|
*/
|
|
#if defined(_WIN32)
|
|
static char *
|
|
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);
|
|
}
|
|
#elif defined(XP_UNIX)
|
|
static char *
|
|
sdb_getFallbackTempDir(void)
|
|
{
|
|
const char *azDirs[] = {
|
|
NULL,
|
|
NULL,
|
|
"/var/tmp",
|
|
"/usr/tmp",
|
|
"/tmp",
|
|
NULL /* List terminator */
|
|
};
|
|
unsigned int i;
|
|
struct stat buf;
|
|
const char *zDir = NULL;
|
|
|
|
azDirs[0] = sqlite3_temp_directory;
|
|
azDirs[1] = getenv("TMPDIR");
|
|
|
|
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
|
|
|
|
static char *
|
|
sdb_getTempDir(sqlite3 *sqlDB)
|
|
{
|
|
int sqlrv;
|
|
char *result = NULL;
|
|
char *tempName = NULL;
|
|
char *foundSeparator = NULL;
|
|
|
|
/* Obtain temporary filename in sqlite's directory for temporary tables */
|
|
sqlrv = SQLITE_NOTFOUND;/*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_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)
|
|
{
|
|
char *dbname = NULL;
|
|
/* build the full dbname */
|
|
dbname = sqlite3_mprintf("%s%c%s%s%d.db", directory,
|
|
(int)(unsigned char)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(const char *directory)
|
|
{
|
|
PRUint32 i;
|
|
PRIntervalTime time;
|
|
PRIntervalTime delta;
|
|
PRIntervalTime duration = PR_MillisecondsToInterval(33);
|
|
const char *doesntExistName = "_dOeSnotExist_.db";
|
|
char *temp, *tempStartOfFilename;
|
|
size_t maxTempLen, maxFileNameLen, directoryLength;
|
|
|
|
/* 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 + strlen(doesntExistName)
|
|
+ 1 /* potential additional separator char */
|
|
+ 11 /* max chars for 32 bit int plus potential sign */
|
|
+ 1; /* zero terminator */
|
|
|
|
temp = PORT_Alloc(maxTempLen);
|
|
if (!temp) {
|
|
return 1;
|
|
}
|
|
|
|
/* We'll copy directory into temp just once, then ensure it ends
|
|
* with the directory separator, then remember the position after
|
|
* the separator, and calculate the number of remaining bytes. */
|
|
|
|
strcpy(temp, directory);
|
|
if (directory[directoryLength - 1] != PR_GetDirectorySeparator()) {
|
|
temp[directoryLength++] = PR_GetDirectorySeparator();
|
|
}
|
|
tempStartOfFilename = temp + directoryLength;
|
|
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;
|
|
}
|
|
|
|
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.
|
|
*/
|
|
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;
|
|
unsigned 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_reset(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;
|
|
unsigned int i;
|
|
|
|
|
|
/* open a new db if necessary */
|
|
error = sdb_openDBLocal(sdb_p, &sqlDB, &table);
|
|
if (error != CKR_OK) {
|
|
goto loser;
|
|
}
|
|
|
|
for (i=0; i < count; i++) {
|
|
getStr = sqlite3_mprintf("a%x", template[i].type);
|
|
|
|
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);
|
|
sqlite3_free(newStr);
|
|
newStr = NULL;
|
|
if (sqlerr == SQLITE_ERROR) {
|
|
template[i].ulValueLen = -1;
|
|
error = CKR_ATTRIBUTE_TYPE_INVALID;
|
|
continue;
|
|
} else 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) {
|
|
unsigned int blobSize;
|
|
const char *blobData;
|
|
|
|
blobSize = sqlite3_column_bytes(stmt, 0);
|
|
blobData = sqlite3_column_blob(stmt, 0);
|
|
if (blobData == NULL) {
|
|
template[i].ulValueLen = -1;
|
|
error = CKR_ATTRIBUTE_TYPE_INVALID;
|
|
break;
|
|
}
|
|
/* 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;
|
|
break;
|
|
}
|
|
PORT_Memcpy(template[i].pValue, blobData, blobSize);
|
|
}
|
|
template[i].ulValueLen = blobSize;
|
|
found = 1;
|
|
}
|
|
} while (!sdb_done(sqlerr,&retry));
|
|
sqlite3_reset(stmt);
|
|
sqlite3_finalize(stmt);
|
|
stmt = NULL;
|
|
}
|
|
|
|
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 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;
|
|
unsigned 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;
|
|
unsigned 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;
|
|
|
|
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;
|
|
}
|
|
|
|
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)";
|
|
|
|
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;
|
|
PRBool create;
|
|
|
|
*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();
|
|
create = (PR_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 */
|
|
if (create) {
|
|
/* NO NSPR call for this? :( */
|
|
chmod (dbname, 0600);
|
|
}
|
|
|
|
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->version = 0;
|
|
sdb->sdb_flags = flags | SDB_HAS_META;
|
|
sdb->app_private = NULL;
|
|
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_Reset = sdb_Reset;
|
|
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);
|
|
char *key = sdb_BuildFileName(directory, keyPrefix,
|
|
"key", key_version);
|
|
CK_RV error = CKR_OK;
|
|
int inUpdate;
|
|
PRUint32 accessOps;
|
|
|
|
if (certdb)
|
|
*certdb = NULL;
|
|
if (keydb)
|
|
*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 = 1;
|
|
{
|
|
char *env;
|
|
env = PR_GetEnv("NSS_SDB_USE_CACHE");
|
|
/* If the environment variable is set to yes or no, sdb_init() will
|
|
* ignore the value of accessOps, and we can skip the measuring.*/
|
|
if (!env || ((PORT_Strcasecmp(env, "no") != 0) &&
|
|
(PORT_Strcasecmp(env, "yes") != 0))){
|
|
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;
|
|
}
|