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