Blame SOURCES/nss-softokn-3.79-dbtool.patch

5d145d
diff --git a/cmd/Makefile b/cmd/Makefile
5d145d
--- a/cmd/Makefile
5d145d
+++ b/cmd/Makefile
5d145d
@@ -11,22 +11,23 @@ include manifest.mn
5d145d
 include $(CORE_DEPTH)/coreconf/config.mk
5d145d
 
5d145d
 ifdef BUILD_LIBPKIX_TESTS
5d145d
 DIRS += libpkix
5d145d
 endif
5d145d
 
5d145d
 ifeq ($(NSS_BUILD_WITHOUT_SOFTOKEN),1)
5d145d
 BLTEST_SRCDIR =
5d145d
-ECPERF_SRCDIR =
5d145d
+DBTOOL_SRCDIR = 
5d145d
 FREEBL_ECTEST_SRCDIR =
5d145d
 FIPSTEST_SRCDIR =
5d145d
 SHLIBSIGN_SRCDIR =
5d145d
 else
5d145d
 BLTEST_SRCDIR = bltest
5d145d
+DBTOOL_SRCDIR = dbtool
5d145d
 ECPERF_SRCDIR = ecperf
5d145d
 FREEBL_ECTEST_SRCDIR = fbectest
5d145d
 FIPSTEST_SRCDIR = fipstest
5d145d
 SHLIBSIGN_SRCDIR = shlibsign
5d145d
 endif
5d145d
 
5d145d
 LOWHASHTEST_SRCDIR=
5d145d
 ifeq ($(FREEBL_LOWHASH),1)
5d145d
diff --git a/cmd/dbtool/Makefile b/cmd/dbtool/Makefile
5d145d
new file mode 100644
5d145d
--- /dev/null
5d145d
+++ b/cmd/dbtool/Makefile
5d145d
@@ -0,0 +1,46 @@
5d145d
+#! gmake
5d145d
+# 
5d145d
+# This Source Code Form is subject to the terms of the Mozilla Public
5d145d
+# License, v. 2.0. If a copy of the MPL was not distributed with this
5d145d
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
5d145d
+
5d145d
+#######################################################################
5d145d
+# (1) Include initial platform-independent assignments (MANDATORY).   #
5d145d
+#######################################################################
5d145d
+
5d145d
+include manifest.mn
5d145d
+
5d145d
+#######################################################################
5d145d
+# (2) Include "global" configuration information. (OPTIONAL)          #
5d145d
+#######################################################################
5d145d
+
5d145d
+include $(CORE_DEPTH)/coreconf/config.mk
5d145d
+
5d145d
+#######################################################################
5d145d
+# (3) Include "component" configuration information. (OPTIONAL)       #
5d145d
+#######################################################################
5d145d
+
5d145d
+#######################################################################
5d145d
+# (4) Include "local" platform-dependent assignments (OPTIONAL).      #
5d145d
+#######################################################################
5d145d
+
5d145d
+include ../platlibs.mk
5d145d
+
5d145d
+#######################################################################
5d145d
+# (5) Execute "global" rules. (OPTIONAL)                              #
5d145d
+#######################################################################
5d145d
+
5d145d
+include $(CORE_DEPTH)/coreconf/rules.mk
5d145d
+
5d145d
+#######################################################################
5d145d
+# (6) Execute "component" rules. (OPTIONAL)                           #
5d145d
+#######################################################################
5d145d
+
5d145d
+#include ../platlibs.mk
5d145d
+
5d145d
+#######################################################################
5d145d
+# (7) Execute "local" rules. (OPTIONAL).                              #
5d145d
+#######################################################################
5d145d
+
5d145d
+include ../platrules.mk
5d145d
+
5d145d
diff --git a/cmd/dbtool/dbtool.c b/cmd/dbtool/dbtool.c
5d145d
new file mode 100644
5d145d
--- /dev/null
5d145d
+++ b/cmd/dbtool/dbtool.c
5d145d
@@ -0,0 +1,839 @@
5d145d
+/* This Source Code Form is subject to the terms of the Mozilla Public
5d145d
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
5d145d
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5d145d
+
5d145d
+/*
5d145d
+** dbtool.c
5d145d
+**
5d145d
+** tool to dump the underlying encoding of a database. This tool duplicates
5d145d
+**  some private functions in softoken. It uses libsec and libutil, but no
5d145d
+**  other portions of NSS. It currently only works on sqlite databases. For
5d145d
+**  an even more primitive dump, use sqlite3 on the individual files.
5d145d
+**
5d145d
+**   TODO: dump the meta data for the databases.
5d145d
+**         optionally dump more PKCS5 information (KDF/salt/iterations)
5d145d
+**         take a password and decode encrypted attributes/verify signed
5d145d
+**             attributes.
5d145d
+*/
5d145d
+#include <stdio.h>
5d145d
+#include <string.h>
5d145d
+
5d145d
+#if defined(WIN32)
5d145d
+#include "fcntl.h"
5d145d
+#include "io.h"
5d145d
+#endif
5d145d
+
5d145d
+/*#include "secutil.h" */
5d145d
+/*#include "pk11pub.h" */
5d145d
+
5d145d
+#if defined(XP_UNIX)
5d145d
+#include <unistd.h>
5d145d
+#endif
5d145d
+
5d145d
+#include "nspr.h"
5d145d
+#include "prtypes.h"
5d145d
+/*#include "certdb.h" */
5d145d
+#include "nss.h"
5d145d
+#include "secasn1.h"
5d145d
+#include "secder.h"
5d145d
+/*#include "../modutil/modutil.h" */
5d145d
+#include "pk11table.h"
5d145d
+#include "sftkdbt.h"
5d145d
+#include "sdb.h"
5d145d
+#include "secoid.h"
5d145d
+
5d145d
+#include "plgetopt.h"
5d145d
+
5d145d
+static char *progName;
5d145d
+
5d145d
+char *dbDir = NULL;
5d145d
+
5d145d
+static void
5d145d
+Usage()
5d145d
+{
5d145d
+    printf("Usage:  %s [-c certprefix] [-k keyprefix] "
5d145d
+           "[-V certversion] [-v keyversion]\n"
5d145d
+           "           [-d dbdir]\n",
5d145d
+           progName);
5d145d
+    printf("%-20s Directory with cert database (default is .)\n",
5d145d
+           "-d certdir");
5d145d
+    printf("%-20s prefix for the cert database (default is \"\")\n",
5d145d
+           "-c certprefix");
5d145d
+    printf("%-20s prefix for the key database (default is \"\")\n",
5d145d
+           "-k keyprefix");
5d145d
+    printf("%-20s version of the cert database (default is 9)\n",
5d145d
+           "-V certversion");
5d145d
+    printf("%-20s version of the key database (default is 4)\n",
5d145d
+           "-v keyversion");
5d145d
+    exit(1);
5d145d
+}
5d145d
+#define SFTK_KEYDB_TYPE 0x40000000
5d145d
+#define SFTK_TOKEN_TYPE 0x80000000
5d145d
+
5d145d
+/*
5d145d
+ * known attributes
5d145d
+ */
5d145d
+static const CK_ATTRIBUTE_TYPE known_attributes[] = {
5d145d
+    CKA_CLASS, CKA_TOKEN, CKA_PRIVATE, CKA_LABEL, CKA_APPLICATION,
5d145d
+    CKA_VALUE, CKA_OBJECT_ID, CKA_CERTIFICATE_TYPE, CKA_ISSUER,
5d145d
+    CKA_SERIAL_NUMBER, CKA_AC_ISSUER, CKA_OWNER, CKA_ATTR_TYPES, CKA_TRUSTED,
5d145d
+    CKA_CERTIFICATE_CATEGORY, CKA_JAVA_MIDP_SECURITY_DOMAIN, CKA_URL,
5d145d
+    CKA_HASH_OF_SUBJECT_PUBLIC_KEY, CKA_HASH_OF_ISSUER_PUBLIC_KEY,
5d145d
+    CKA_CHECK_VALUE, CKA_KEY_TYPE, CKA_SUBJECT, CKA_ID, CKA_SENSITIVE,
5d145d
+    CKA_ENCRYPT, CKA_DECRYPT, CKA_WRAP, CKA_UNWRAP, CKA_SIGN, CKA_SIGN_RECOVER,
5d145d
+    CKA_VERIFY, CKA_VERIFY_RECOVER, CKA_DERIVE, CKA_START_DATE, CKA_END_DATE,
5d145d
+    CKA_MODULUS, CKA_MODULUS_BITS, CKA_PUBLIC_EXPONENT, CKA_PRIVATE_EXPONENT,
5d145d
+    CKA_PRIME_1, CKA_PRIME_2, CKA_EXPONENT_1, CKA_EXPONENT_2, CKA_COEFFICIENT,
5d145d
+    CKA_PRIME, CKA_SUBPRIME, CKA_BASE, CKA_PRIME_BITS,
5d145d
+    CKA_SUB_PRIME_BITS, CKA_VALUE_BITS, CKA_VALUE_LEN, CKA_EXTRACTABLE,
5d145d
+    CKA_LOCAL, CKA_NEVER_EXTRACTABLE, CKA_ALWAYS_SENSITIVE,
5d145d
+    CKA_KEY_GEN_MECHANISM, CKA_MODIFIABLE, CKA_EC_PARAMS,
5d145d
+    CKA_EC_POINT, CKA_SECONDARY_AUTH, CKA_AUTH_PIN_FLAGS,
5d145d
+    CKA_ALWAYS_AUTHENTICATE, CKA_WRAP_WITH_TRUSTED, CKA_WRAP_TEMPLATE,
5d145d
+    CKA_UNWRAP_TEMPLATE, CKA_HW_FEATURE_TYPE, CKA_RESET_ON_INIT,
5d145d
+    CKA_HAS_RESET, CKA_PIXEL_X, CKA_PIXEL_Y, CKA_RESOLUTION, CKA_CHAR_ROWS,
5d145d
+    CKA_CHAR_COLUMNS, CKA_COLOR, CKA_BITS_PER_PIXEL, CKA_CHAR_SETS,
5d145d
+    CKA_ENCODING_METHODS, CKA_MIME_TYPES, CKA_MECHANISM_TYPE,
5d145d
+    CKA_REQUIRED_CMS_ATTRIBUTES, CKA_DEFAULT_CMS_ATTRIBUTES,
5d145d
+    CKA_SUPPORTED_CMS_ATTRIBUTES, CKA_NSS_URL, CKA_NSS_EMAIL,
5d145d
+    CKA_NSS_SMIME_INFO, CKA_NSS_SMIME_TIMESTAMP,
5d145d
+    CKA_NSS_PKCS8_SALT, CKA_NSS_PASSWORD_CHECK, CKA_NSS_EXPIRES,
5d145d
+    CKA_NSS_KRL, CKA_NSS_PQG_COUNTER, CKA_NSS_PQG_SEED,
5d145d
+    CKA_NSS_PQG_H, CKA_NSS_PQG_SEED_BITS, CKA_NSS_MODULE_SPEC,
5d145d
+    CKA_TRUST_DIGITAL_SIGNATURE, CKA_TRUST_NON_REPUDIATION,
5d145d
+    CKA_TRUST_KEY_ENCIPHERMENT, CKA_TRUST_DATA_ENCIPHERMENT,
5d145d
+    CKA_TRUST_KEY_AGREEMENT, CKA_TRUST_KEY_CERT_SIGN, CKA_TRUST_CRL_SIGN,
5d145d
+    CKA_TRUST_SERVER_AUTH, CKA_TRUST_CLIENT_AUTH, CKA_TRUST_CODE_SIGNING,
5d145d
+    CKA_TRUST_EMAIL_PROTECTION, CKA_TRUST_IPSEC_END_SYSTEM,
5d145d
+    CKA_TRUST_IPSEC_TUNNEL, CKA_TRUST_IPSEC_USER, CKA_TRUST_TIME_STAMPING,
5d145d
+    CKA_TRUST_STEP_UP_APPROVED, CKA_CERT_SHA1_HASH, CKA_CERT_MD5_HASH,
5d145d
+    CKA_NSS_DB, CKA_NSS_TRUST, CKA_NSS_OVERRIDE_EXTENSIONS,
5d145d
+    CKA_PUBLIC_KEY_INFO
5d145d
+};
5d145d
+
5d145d
+static unsigned int known_attributes_size = sizeof(known_attributes) /
5d145d
+                                            sizeof(known_attributes[0]);
5d145d
+
5d145d
+PRBool
5d145d
+isULONGAttribute(CK_ATTRIBUTE_TYPE type)
5d145d
+{
5d145d
+    switch (type) {
5d145d
+        case CKA_CERTIFICATE_CATEGORY:
5d145d
+        case CKA_CERTIFICATE_TYPE:
5d145d
+        case CKA_CLASS:
5d145d
+        case CKA_JAVA_MIDP_SECURITY_DOMAIN:
5d145d
+        case CKA_KEY_GEN_MECHANISM:
5d145d
+        case CKA_KEY_TYPE:
5d145d
+        case CKA_MECHANISM_TYPE:
5d145d
+        case CKA_MODULUS_BITS:
5d145d
+        case CKA_PRIME_BITS:
5d145d
+        case CKA_SUBPRIME_BITS:
5d145d
+        case CKA_VALUE_BITS:
5d145d
+        case CKA_VALUE_LEN:
5d145d
+
5d145d
+        case CKA_TRUST_DIGITAL_SIGNATURE:
5d145d
+        case CKA_TRUST_NON_REPUDIATION:
5d145d
+        case CKA_TRUST_KEY_ENCIPHERMENT:
5d145d
+        case CKA_TRUST_DATA_ENCIPHERMENT:
5d145d
+        case CKA_TRUST_KEY_AGREEMENT:
5d145d
+        case CKA_TRUST_KEY_CERT_SIGN:
5d145d
+        case CKA_TRUST_CRL_SIGN:
5d145d
+
5d145d
+        case CKA_TRUST_SERVER_AUTH:
5d145d
+        case CKA_TRUST_CLIENT_AUTH:
5d145d
+        case CKA_TRUST_CODE_SIGNING:
5d145d
+        case CKA_TRUST_EMAIL_PROTECTION:
5d145d
+        case CKA_TRUST_IPSEC_END_SYSTEM:
5d145d
+        case CKA_TRUST_IPSEC_TUNNEL:
5d145d
+        case CKA_TRUST_IPSEC_USER:
5d145d
+        case CKA_TRUST_TIME_STAMPING:
5d145d
+        case CKA_TRUST_STEP_UP_APPROVED:
5d145d
+            return PR_TRUE;
5d145d
+        default:
5d145d
+            break;
5d145d
+    }
5d145d
+    return PR_FALSE;
5d145d
+}
5d145d
+
5d145d
+/* are the attributes private? */
5d145d
+static PRBool
5d145d
+isPrivateAttribute(CK_ATTRIBUTE_TYPE type)
5d145d
+{
5d145d
+    switch (type) {
5d145d
+        case CKA_VALUE:
5d145d
+        case CKA_PRIVATE_EXPONENT:
5d145d
+        case CKA_PRIME_1:
5d145d
+        case CKA_PRIME_2:
5d145d
+        case CKA_EXPONENT_1:
5d145d
+        case CKA_EXPONENT_2:
5d145d
+        case CKA_COEFFICIENT:
5d145d
+            return PR_TRUE;
5d145d
+        default:
5d145d
+            break;
5d145d
+    }
5d145d
+    return PR_FALSE;
5d145d
+}
5d145d
+
5d145d
+/* These attributes must be authenticated with an hmac. */
5d145d
+static PRBool
5d145d
+isAuthenticatedAttribute(CK_ATTRIBUTE_TYPE type)
5d145d
+{
5d145d
+    switch (type) {
5d145d
+        case CKA_MODULUS:
5d145d
+        case CKA_PUBLIC_EXPONENT:
5d145d
+        case CKA_CERT_SHA1_HASH:
5d145d
+        case CKA_CERT_MD5_HASH:
5d145d
+        case CKA_TRUST_SERVER_AUTH:
5d145d
+        case CKA_TRUST_CLIENT_AUTH:
5d145d
+        case CKA_TRUST_EMAIL_PROTECTION:
5d145d
+        case CKA_TRUST_CODE_SIGNING:
5d145d
+        case CKA_TRUST_STEP_UP_APPROVED:
5d145d
+        case CKA_NSS_OVERRIDE_EXTENSIONS:
5d145d
+            return PR_TRUE;
5d145d
+        default:
5d145d
+            break;
5d145d
+    }
5d145d
+    return PR_FALSE;
5d145d
+}
5d145d
+
5d145d
+/*
5d145d
+ * convert a database ulong back to a native ULONG. (reverse of the above
5d145d
+ * function.
5d145d
+ */
5d145d
+static CK_ULONG
5d145d
+sdbULong2ULong(unsigned char *data)
5d145d
+{
5d145d
+    int i;
5d145d
+    CK_ULONG value = 0;
5d145d
+
5d145d
+    for (i = 0; i < SDB_ULONG_SIZE; i++) {
5d145d
+        value |= (((CK_ULONG)data[i]) << (SDB_ULONG_SIZE - 1 - i)
5d145d
+                 * PR_BITS_PER_BYTE);
5d145d
+    }
5d145d
+    return value;
5d145d
+}
5d145d
+
5d145d
+/* PBE defines and functions */
5d145d
+
5d145d
+typedef struct EncryptedDataInfoStr {
5d145d
+    SECAlgorithmID algorithm;
5d145d
+    SECItem encryptedData;
5d145d
+} EncryptedDataInfo;
5d145d
+
5d145d
+static const SEC_ASN1Template encryptedDataInfoTemplate[] = {
5d145d
+    { SEC_ASN1_SEQUENCE,
5d145d
+      0, NULL, sizeof(EncryptedDataInfo) },
5d145d
+    { SEC_ASN1_INLINE | SEC_ASN1_XTRN,
5d145d
+      offsetof(EncryptedDataInfo, algorithm),
5d145d
+      SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) },
5d145d
+    { SEC_ASN1_OCTET_STRING,
5d145d
+      offsetof(EncryptedDataInfo, encryptedData) },
5d145d
+    { 0 }
5d145d
+};
5d145d
+
5d145d
+typedef struct PBEParameterStr {
5d145d
+    SECAlgorithmID prfAlg;
5d145d
+    SECItem salt;
5d145d
+    SECItem iteration;
5d145d
+    SECItem keyLength;
5d145d
+} PBEParameter;
5d145d
+
5d145d
+static const SEC_ASN1Template pkcs5V1PBEParameterTemplate[] =
5d145d
+    {
5d145d
+      { SEC_ASN1_SEQUENCE,
5d145d
+        0, NULL, sizeof(PBEParameter) },
5d145d
+      { SEC_ASN1_OCTET_STRING,
5d145d
+        offsetof(PBEParameter, salt) },
5d145d
+      { SEC_ASN1_INTEGER,
5d145d
+        offsetof(PBEParameter, iteration) },
5d145d
+      { 0 }
5d145d
+    };
5d145d
+
5d145d
+static const SEC_ASN1Template pkcs12V2PBEParameterTemplate[] =
5d145d
+    {
5d145d
+      { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(PBEParameter) },
5d145d
+      { SEC_ASN1_OCTET_STRING, offsetof(PBEParameter, salt) },
5d145d
+      { SEC_ASN1_INTEGER, offsetof(PBEParameter, iteration) },
5d145d
+      { 0 }
5d145d
+    };
5d145d
+
5d145d
+
5d145d
+static const SEC_ASN1Template pkcs5V2PBEParameterTemplate[] =
5d145d
+    {
5d145d
+      { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(PBEParameter) },
5d145d
+      /* this is really a choice, but since we don't understand any other
5d145d
+       * choice, just inline it. */
5d145d
+      { SEC_ASN1_OCTET_STRING, offsetof(PBEParameter, salt) },
5d145d
+      { SEC_ASN1_INTEGER, offsetof(PBEParameter, iteration) },
5d145d
+      { SEC_ASN1_INTEGER, offsetof(PBEParameter, keyLength) },
5d145d
+      { SEC_ASN1_INLINE | SEC_ASN1_XTRN,
5d145d
+        offsetof(PBEParameter, prfAlg),
5d145d
+        SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) },
5d145d
+      { 0 }
5d145d
+    };
5d145d
+
5d145d
+typedef struct Pkcs5v2PBEParameterStr {
5d145d
+    SECAlgorithmID keyParams; /* parameters of the key generation */
5d145d
+    SECAlgorithmID algParams; /* parameters for the encryption or mac op */
5d145d
+} Pkcs5v2PBEParameter;
5d145d
+
5d145d
+static const SEC_ASN1Template pkcs5v2PBES2ParameterTemplate[] = {
5d145d
+      { SEC_ASN1_SEQUENCE, 0, NULL, sizeof(Pkcs5v2PBEParameter) },
5d145d
+      { SEC_ASN1_INLINE | SEC_ASN1_XTRN,
5d145d
+        offsetof(Pkcs5v2PBEParameter, keyParams),
5d145d
+        SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) },
5d145d
+      { SEC_ASN1_INLINE | SEC_ASN1_XTRN,
5d145d
+        offsetof(Pkcs5v2PBEParameter, algParams),
5d145d
+        SEC_ASN1_SUB(SECOID_AlgorithmIDTemplate) },
5d145d
+      { 0 }
5d145d
+};
5d145d
+
5d145d
+static inline PRBool
5d145d
+isPKCS12PBE(SECOidTag alg) {
5d145d
+    switch (alg) {
5d145d
+    case SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_2KEY_TRIPLE_DES_CBC:
5d145d
+    case SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_3KEY_TRIPLE_DES_CBC:
5d145d
+    case SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_128_BIT_RC2_CBC:
5d145d
+    case SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_40_BIT_RC2_CBC:
5d145d
+    case SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_128_BIT_RC4:
5d145d
+    case SEC_OID_PKCS12_V2_PBE_WITH_SHA1_AND_40_BIT_RC4:
5d145d
+        return PR_TRUE;
5d145d
+    default:
5d145d
+        break;
5d145d
+    }
5d145d
+    return PR_FALSE;
5d145d
+}
5d145d
+
5d145d
+
5d145d
+/* helper functions */
5d145d
+
5d145d
+/* output an NSS specific attribute or name that wasn't found in our
5d145d
+ * pkcs #11 table */
5d145d
+const char *
5d145d
+makeNSSVendorName(CK_ATTRIBUTE_TYPE attribute, const char *nameType)
5d145d
+{
5d145d
+    static char nss_name[256];
5d145d
+    const char *name = NULL;
5d145d
+    if ((attribute >= CKA_NSS) && (attribute <= 0xffffffff)) {
5d145d
+        sprintf(nss_name,"%s+%d", nameType, (int)(attribute-CKA_NSS));
5d145d
+        name = nss_name;
5d145d
+    }
5d145d
+    return name;
5d145d
+}
5d145d
+
5d145d
+/*  turn and attribute into a name */
5d145d
+const char *
5d145d
+AttributeName(CK_ATTRIBUTE_TYPE attribute)
5d145d
+{
5d145d
+    const char *name = getNameFromAttribute(attribute);
5d145d
+    if (!name) {
5d145d
+        name = makeNSSVendorName(attribute, "CKA_NSS");
5d145d
+    }
5d145d
+
5d145d
+    return name ? name : "UNKNOWN_ATTRIBUTE_TYPE";
5d145d
+}
5d145d
+
5d145d
+/*  turn and error code into a name */
5d145d
+const char *
5d145d
+ErrorName(CK_RV crv)
5d145d
+{
5d145d
+    const char *error = getName(crv, ConstResult);
5d145d
+    if (!error) {
5d145d
+        error = makeNSSVendorName(crv, "CKR_NSS");
5d145d
+    }
5d145d
+    return error ? error : "UNKNOWN_ERROR";
5d145d
+}
5d145d
+
5d145d
+/* turn an oud tag into a string */
5d145d
+const char *
5d145d
+oid2string(SECOidTag alg)
5d145d
+{
5d145d
+    const char *oidstring = SECOID_FindOIDTagDescription(alg);
5d145d
+    const char *def="Invalid oid tag"; /* future build a dotted oid string value here */
5d145d
+    return oidstring ? oidstring : def;
5d145d
+}
5d145d
+
5d145d
+/* dump an arbitary data blob. Dump it has hex with ascii on the side */
5d145d
+#define ASCCHAR(val) ((val) >= ' ' && (val) <= 0x7e ? (val) : '.')
5d145d
+#define LINE_LENGTH 16
5d145d
+void
5d145d
+dumpValue(const unsigned char *v, int len)
5d145d
+{
5d145d
+    int i, next = 0;
5d145d
+    char string[LINE_LENGTH+1];
5d145d
+    char space[LINE_LENGTH*2+1];
5d145d
+    char *nl = "";
5d145d
+    char *sp = "";
5d145d
+    PORT_Memset(string, 0, sizeof(string));
5d145d
+
5d145d
+    for (i=0; i < len; i++) {
5d145d
+        if ((i % LINE_LENGTH) == 0) {
5d145d
+            printf("%s%s%s        ", sp, string, nl);
5d145d
+            PORT_Memset(string, 0, sizeof(string));
5d145d
+            next = 0;
5d145d
+            nl = "\n";
5d145d
+            sp = " ";
5d145d
+        }
5d145d
+        printf("%02x", v[i]);
5d145d
+        string[next++] = ASCCHAR(v[i]);
5d145d
+    }
5d145d
+    PORT_Memset(space, 0, sizeof(space));
5d145d
+    i = LINE_LENGTH - (len % LINE_LENGTH);
5d145d
+    if (i != LINE_LENGTH) {
5d145d
+        int j;
5d145d
+        for (j=0 ; j < i; j++) {
5d145d
+            space[j*2] = ' ';
5d145d
+            space[j*2+1] = ' ';
5d145d
+        }
5d145d
+     }
5d145d
+    printf("%s%s%s%s", space, sp, string, nl);
5d145d
+}
5d145d
+
5d145d
+/* dump a PKCS5/12 PBE blob */
5d145d
+void
5d145d
+dumpPKCS(unsigned char *val, CK_ULONG len, PRBool *hasSig)
5d145d
+{
5d145d
+    EncryptedDataInfo edi;
5d145d
+    SECStatus rv;
5d145d
+    SECItem data;
5d145d
+    PLArenaPool *arena;
5d145d
+    SECOidTag alg, prfAlg;
5d145d
+    PBEParameter pbeParam;
5d145d
+    unsigned char zero = 0;
5d145d
+    const SEC_ASN1Template *template = pkcs5V1PBEParameterTemplate;
5d145d
+    int iter, keyLen, i;
5d145d
+
5d145d
+    if (hasSig) { *hasSig = PR_FALSE; }
5d145d
+
5d145d
+
5d145d
+    data.data = val;
5d145d
+    data.len = len;
5d145d
+    arena =  PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
5d145d
+    if (arena == NULL) {
5d145d
+        printf("Couldn't allocate arena\n");
5d145d
+        return;
5d145d
+    }
5d145d
+
5d145d
+    /* initialize default values */
5d145d
+    PORT_Memset(&pbeParam, 0, sizeof(pbeParam));
5d145d
+    pbeParam.keyLength.data = &zer;;
5d145d
+    pbeParam.keyLength.len = sizeof(zero);
5d145d
+    SECOID_SetAlgorithmID(arena, &pbeParam.prfAlg, SEC_OID_SHA1, NULL);
5d145d
+
5d145d
+    /* first crack the encrypted data from the PBE algorithm ID */
5d145d
+    rv = SEC_QuickDERDecodeItem(arena, &edi, encryptedDataInfoTemplate, &data);
5d145d
+    if (rv != SECSuccess) {
5d145d
+        printf("Encrypted Data, failed to decode\n");
5d145d
+        dumpValue(val,len);
5d145d
+        PORT_FreeArena(arena, PR_FALSE);
5d145d
+        return;
5d145d
+    }
5d145d
+    /* now use the pbe secalg to dump info on the pbe */
5d145d
+    alg = SECOID_GetAlgorithmTag(&edi.algorithm);
5d145d
+    if ((alg == SEC_OID_PKCS5_PBES2) || (alg == SEC_OID_PKCS5_PBMAC1)){
5d145d
+        Pkcs5v2PBEParameter param;
5d145d
+        SECOidTag palg;
5d145d
+        const char *typeName = (alg == SEC_OID_PKCS5_PBES2) ?
5d145d
+                               "Encrypted Data PBES2" :
5d145d
+                               "Mac Data PBMAC1";
5d145d
+
5d145d
+        rv = SEC_QuickDERDecodeItem(arena, &param,
5d145d
+                                    pkcs5v2PBES2ParameterTemplate,
5d145d
+                                    &edi.algorithm.parameters);
5d145d
+        if (rv != SECSuccess) {
5d145d
+            printf("%s, failed to decode\n", typeName);
5d145d
+            dumpValue(val,len);
5d145d
+            PORT_FreeArena(arena, PR_FALSE);
5d145d
+            return;
5d145d
+        }
5d145d
+        palg = SECOID_GetAlgorithmTag(&param.algParams);
5d145d
+        printf("%s alg=%s ", typeName, oid2string(palg));
5d145d
+        if (hasSig && palg == SEC_OID_AES_256_CBC) {
5d145d
+            *hasSig = PR_TRUE;
5d145d
+        }
5d145d
+        template = pkcs5V2PBEParameterTemplate;
5d145d
+        edi.algorithm.parameters = param.keyParams.parameters;
5d145d
+    } else {
5d145d
+        printf("Encrypted Data alg=%s ", oid2string(alg));
5d145d
+        if (alg == SEC_OID_PKCS5_PBKDF2) {
5d145d
+            template = pkcs5V2PBEParameterTemplate;
5d145d
+        } else if (isPKCS12PBE(alg)) {
5d145d
+            template = pkcs12V2PBEParameterTemplate;
5d145d
+        } else {
5d145d
+            template = pkcs5V1PBEParameterTemplate;
5d145d
+        }
5d145d
+    }
5d145d
+    rv = SEC_QuickDERDecodeItem(arena, &pbeParam,
5d145d
+                                template,
5d145d
+                                &edi.algorithm.parameters);
5d145d
+    if (rv != SECSuccess) {
5d145d
+        printf("( failed to decode params)\n");
5d145d
+        PORT_FreeArena(arena, PR_FALSE);
5d145d
+        return;
5d145d
+    }
5d145d
+    /* dump the pbe parmeters */
5d145d
+    iter = DER_GetInteger(&pbeParam.iteration);
5d145d
+    keyLen = DER_GetInteger(&pbeParam.keyLength);
5d145d
+    prfAlg = SECOID_GetAlgorithmTag(&pbeParam.prfAlg);
5d145d
+    printf("(prf=%s iter=%d keyLen=%d salt=0x", 
5d145d
+           oid2string(prfAlg), iter, keyLen);
5d145d
+    for(i=0;i < pbeParam.salt.len; i++) printf("%02x",pbeParam.salt.data[i]);
5d145d
+    printf(")\n");
5d145d
+    /* finally dump the raw encrypted data */
5d145d
+    dumpValue(edi.encryptedData.data, edi.encryptedData.len);
5d145d
+    PORT_FreeArena(arena, PR_FALSE);
5d145d
+}
5d145d
+
5d145d
+/* dump a long attribute, convert to an unsigned long. PKCS #11 Longs are
5d145d
+ * limited to 32 bits by the spec, even if the CK_ULONG is longer */
5d145d
+void
5d145d
+dumpLongAttribute(CK_ATTRIBUTE_TYPE type, CK_ULONG value)
5d145d
+{
5d145d
+    const char *nameType = "CK_NSS";
5d145d
+    ConstType constType = ConstNone;
5d145d
+    const char *valueName = NULL;
5d145d
+
5d145d
+    switch (type) {
5d145d
+    case CKA_CLASS:
5d145d
+        nameType = "CKO_NSS";
5d145d
+        constType = ConstObject;
5d145d
+        break;
5d145d
+    case CKA_CERTIFICATE_TYPE:
5d145d
+        nameType = "CKC_NSS";
5d145d
+        constType = ConstCertType;
5d145d
+        break;
5d145d
+    case CKA_KEY_TYPE:
5d145d
+        nameType = "CKK_NSS";
5d145d
+        constType = ConstKeyType;
5d145d
+        break;
5d145d
+    case CKA_MECHANISM_TYPE:
5d145d
+        nameType = "CKM_NSS";
5d145d
+        constType = ConstMechanism;
5d145d
+        break;
5d145d
+    case CKA_TRUST_SERVER_AUTH:
5d145d
+    case CKA_TRUST_CLIENT_AUTH:
5d145d
+    case CKA_TRUST_CODE_SIGNING:
5d145d
+    case CKA_TRUST_EMAIL_PROTECTION:
5d145d
+    case CKA_TRUST_IPSEC_END_SYSTEM:
5d145d
+    case CKA_TRUST_IPSEC_TUNNEL:
5d145d
+    case CKA_TRUST_IPSEC_USER:
5d145d
+    case CKA_TRUST_TIME_STAMPING:
5d145d
+        nameType = "CKT_NSS";
5d145d
+        constType = ConstTrust;
5d145d
+        break;
5d145d
+    default:
5d145d
+        break;
5d145d
+    }
5d145d
+    /* if value has a symbolic name, use it */
5d145d
+    if (constType != ConstNone) {
5d145d
+        valueName = getName(value, constType);
5d145d
+    }
5d145d
+    if (!valueName) {
5d145d
+        valueName = makeNSSVendorName(value, nameType);
5d145d
+    }
5d145d
+    if (!valueName) {
5d145d
+        printf("%d (0x%08x)\n", (int) value, (int)value);
5d145d
+    } else {
5d145d
+        printf("%s (0x%08x)\n", valueName, (int)value);
5d145d
+    }
5d145d
+}
5d145d
+
5d145d
+/* dump a signature for an object */
5d145d
+static const char META_SIG_TEMPLATE[] = "sig_%s_%08x_%08x";
5d145d
+void
5d145d
+dumpSignature(CK_ATTRIBUTE_TYPE attribute, SDB *keydb, PRBool isKey,
5d145d
+               CK_OBJECT_HANDLE objectID, PRBool force)
5d145d
+{
5d145d
+    char id[30];
5d145d
+    CK_RV crv;
5d145d
+    SECItem signText;
5d145d
+    unsigned char signData[SDB_MAX_META_DATA_LEN];
5d145d
+
5d145d
+    if (!force && !isAuthenticatedAttribute(attribute)) {
5d145d
+        return;
5d145d
+    }
5d145d
+    sprintf(id, META_SIG_TEMPLATE,
5d145d
+            isKey ? "key" : "cert",
5d145d
+            (unsigned int)objectID, (unsigned int)attribute);
5d145d
+    printf("        Signature %s:",id);
5d145d
+    signText.data = signData;
5d145d
+    signText.len = sizeof(signData);
5d145d
+
5d145d
+
5d145d
+    crv = (*keydb->sdb_GetMetaData)(keydb, id, &signText, NULL);
5d145d
+    if ((crv != CKR_OK) && isKey) {
5d145d
+        sprintf(id, META_SIG_TEMPLATE,
5d145d
+                isKey ? "key" : "cert", (unsigned int)
5d145d
+                (objectID | SFTK_KEYDB_TYPE | SFTK_TOKEN_TYPE),
5d145d
+                (unsigned int)attribute);
5d145d
+        crv = (*keydb->sdb_GetMetaData)(keydb, id, &signText, NULL);
5d145d
+    }
5d145d
+    if (crv != CKR_OK) {
5d145d
+        printf(" FAILED %s with %s (0x%08x)\n", id, ErrorName(crv), (int) crv);
5d145d
+        return;
5d145d
+    }
5d145d
+    dumpPKCS(signText.data, signText.len, NULL);
5d145d
+    return;
5d145d
+}
5d145d
+
5d145d
+/* dump an attribute. use the helper functions above */
5d145d
+void
5d145d
+dumpAttribute(CK_ATTRIBUTE *template, SDB *keydb, PRBool isKey,
5d145d
+              CK_OBJECT_HANDLE id)
5d145d
+{
5d145d
+    CK_ATTRIBUTE_TYPE attribute = template->type;
5d145d
+    printf("      %s(0x%08x): ", AttributeName(attribute), (int)attribute);
5d145d
+    if (template->pValue == NULL) {
5d145d
+        printf("NULL (%d)\n", (int)template->ulValueLen);
5d145d
+        return;
5d145d
+    }
5d145d
+    if (template->ulValueLen == SDB_ULONG_SIZE
5d145d
+        && isULONGAttribute(attribute)) {
5d145d
+        CK_ULONG value=sdbULong2ULong(template->pValue);
5d145d
+        dumpLongAttribute(attribute, value);
5d145d
+        return;
5d145d
+    }
5d145d
+    if (template->ulValueLen == 1) {
5d145d
+        unsigned char val = *(unsigned char *)template->pValue;
5d145d
+        switch (val) {
5d145d
+        case 0:
5d145d
+           printf("CK_FALSE\n");
5d145d
+           break;
5d145d
+        case 1:
5d145d
+           printf("CK_TRUE\n");
5d145d
+           break;
5d145d
+        default:
5d145d
+           printf("%d 0x%02x %c\n", val, val, ASCCHAR(val));
5d145d
+           break;
5d145d
+        }
5d145d
+        return;
5d145d
+    }
5d145d
+    if (isKey && isPrivateAttribute(attribute)) {
5d145d
+        PRBool hasSig = PR_FALSE;
5d145d
+        dumpPKCS(template->pValue, template->ulValueLen, &hasSig);
5d145d
+        if (hasSig) {
5d145d
+            dumpSignature(attribute, keydb, isKey, id, PR_TRUE);
5d145d
+        }
5d145d
+        return;
5d145d
+    }
5d145d
+    if (template->ulValueLen == 0) { printf("empty"); }
5d145d
+    printf("\n");
5d145d
+    dumpValue(template->pValue, template->ulValueLen);
5d145d
+}
5d145d
+
5d145d
+/* dump all the attributes in an object */
5d145d
+void
5d145d
+dumpObject(CK_OBJECT_HANDLE id, SDB *db, SDB *keydb, PRBool isKey)
5d145d
+{
5d145d
+    CK_RV crv;
5d145d
+    int i;
5d145d
+    CK_ATTRIBUTE template;
5d145d
+    char buffer[2048];
5d145d
+    char * alloc = NULL;
5d145d
+
5d145d
+    printf("  Object 0x%08x:\n", (int)id);
5d145d
+    for (i = 0; i < known_attributes_size; i++) {
5d145d
+        CK_ATTRIBUTE_TYPE attribute = known_attributes[i];
5d145d
+        template.type = attribute;
5d145d
+        template.pValue = NULL;
5d145d
+        template.ulValueLen = 0;
5d145d
+        crv = (*db->sdb_GetAttributeValue)(db, id, &template, 1);
5d145d
+
5d145d
+        if (crv != CKR_OK) {
5d145d
+            if (crv != CKR_ATTRIBUTE_TYPE_INVALID) {
5d145d
+                PR_fprintf(PR_STDERR, "    "
5d145d
+                           "Get Attribute %s (0x%08x):FAILED\"%s\"(0x%08x)\n",
5d145d
+                            AttributeName(attribute), (int)attribute,
5d145d
+                            ErrorName(crv), (int)crv);
5d145d
+            }
5d145d
+            continue;
5d145d
+        }
5d145d
+
5d145d
+        if (template.ulValueLen < sizeof(buffer)) {
5d145d
+            template.pValue = buffer;
5d145d
+        } else {
5d145d
+            alloc = PORT_Alloc(template.ulValueLen);
5d145d
+            template.pValue = alloc;
5d145d
+        }
5d145d
+        if (template.pValue == NULL) {
5d145d
+            PR_fprintf(PR_STDERR, "    "
5d145d
+                       "Could allocate %d bytes for  Attribute %s (0x%08x)\n",
5d145d
+                        (int) template.ulValueLen,
5d145d
+                        AttributeName(attribute), (int)attribute);
5d145d
+             continue;
5d145d
+        }
5d145d
+        crv = (*db->sdb_GetAttributeValue)(db, id, &template, 1);
5d145d
+
5d145d
+        if (crv != CKR_OK) {
5d145d
+            if (crv != CKR_ATTRIBUTE_TYPE_INVALID) {
5d145d
+                PR_fprintf(PR_STDERR, "    "
5d145d
+                           "Get Attribute %s (0x%08x):FAILED\"%s\"(0x%08x)\n",
5d145d
+                            AttributeName(attribute), (int)attribute,
5d145d
+                            ErrorName(crv), (int)crv);
5d145d
+            }
5d145d
+            if (alloc) {
5d145d
+                PORT_Free(alloc);
5d145d
+                alloc = NULL;
5d145d
+            }
5d145d
+            continue;
5d145d
+        }
5d145d
+
5d145d
+        dumpAttribute(&template, keydb, isKey, id);
5d145d
+        dumpSignature(template.type, keydb, isKey, id, PR_FALSE);
5d145d
+        if (alloc) {
5d145d
+            PORT_Free(alloc);
5d145d
+            alloc = NULL;
5d145d
+        }
5d145d
+    }
5d145d
+}
5d145d
+
5d145d
+/* dump all the objects in a database */
5d145d
+void
5d145d
+dumpDB(SDB *db, const char *name, SDB *keydb, PRBool isKey)
5d145d
+{
5d145d
+    SDBFind *findHandle= NULL;
5d145d
+    CK_BBOOL isTrue = 1;
5d145d
+    CK_ATTRIBUTE allObjectTemplate = {CKA_TOKEN, NULL, 1 };
5d145d
+    CK_ULONG allObjectTemplateCount = 1;
5d145d
+    PRBool recordFound = PR_FALSE;
5d145d
+    CK_RV crv = CKR_OK;
5d145d
+    CK_ULONG objectCount = 0;
5d145d
+    printf("%s:\n",name);
5d145d
+
5d145d
+    allObjectTemplate.pValue = &isTrue;
5d145d
+    crv = (*db->sdb_FindObjectsInit)(db, &allObjectTemplate,
5d145d
+                                         allObjectTemplateCount, &findHandle);
5d145d
+    do {
5d145d
+       CK_OBJECT_HANDLE id;
5d145d
+       recordFound = PR_FALSE;
5d145d
+       crv =(*db->sdb_FindObjects)(db, findHandle, &id, 1, &objectCount);
5d145d
+       if ((crv == CKR_OK) && (objectCount == 1)) {
5d145d
+           recordFound = PR_TRUE;
5d145d
+           dumpObject(id, db, keydb, isKey);
5d145d
+       }
5d145d
+     } while (recordFound);
5d145d
+     if (crv != CKR_OK) {
5d145d
+         PR_fprintf(PR_STDERR,
5d145d
+                    "Last record return PKCS #11 error = %s (0x%08x)\n",
5d145d
+                    ErrorName(crv), (int)crv);
5d145d
+     }
5d145d
+     (*db->sdb_FindObjectsFinal)(db,findHandle);
5d145d
+}
5d145d
+
5d145d
+
5d145d
+static char *
5d145d
+secu_ConfigDirectory(const char *base)
5d145d
+{
5d145d
+    static PRBool initted = PR_FALSE;
5d145d
+    const char *dir = ".netscape";
5d145d
+    char *home;
5d145d
+    static char buf[1000];
5d145d
+
5d145d
+    if (initted)
5d145d
+        return buf;
5d145d
+
5d145d
+    if (base == NULL || *base == 0) {
5d145d
+        home = PR_GetEnvSecure("HOME");
5d145d
+        if (!home)
5d145d
+            home = "";
5d145d
+
5d145d
+        if (*home && home[strlen(home) - 1] == '/')
5d145d
+            sprintf(buf, "%.900s%s", home, dir);
5d145d
+        else
5d145d
+            sprintf(buf, "%.900s/%s", home, dir);
5d145d
+    } else {
5d145d
+        sprintf(buf, "%.900s", base);
5d145d
+        if (buf[strlen(buf) - 1] == '/')
5d145d
+            buf[strlen(buf) - 1] = 0;
5d145d
+    }
5d145d
+
5d145d
+    initted = PR_TRUE;
5d145d
+    return buf;
5d145d
+}
5d145d
+
5d145d
+int
5d145d
+main(int argc, char **argv)
5d145d
+{
5d145d
+    PLOptState *optstate;
5d145d
+    PLOptStatus optstatus;
5d145d
+    char *certPrefix="", *keyPrefix="";
5d145d
+    int cert_version = 9;
5d145d
+    int key_version = 4;
5d145d
+    SDB *certdb = NULL;
5d145d
+    SDB *keydb = NULL;
5d145d
+    PRBool isNew = PR_FALSE;
5d145d
+
5d145d
+    CK_RV crv;
5d145d
+
5d145d
+    progName = strrchr(argv[0], '/');
5d145d
+    if (!progName)
5d145d
+        progName = strrchr(argv[0], '\\');
5d145d
+    progName = progName ? progName + 1 : argv[0];
5d145d
+
5d145d
+    optstate = PL_CreateOptState(argc, argv, "d:c:k:v:V:h");
5d145d
+
5d145d
+    while ((optstatus = PL_GetNextOpt(optstate)) == PL_OPT_OK) {
5d145d
+        switch (optstate->option) {
5d145d
+            case 'h':
5d145d
+            default:
5d145d
+                Usage();
5d145d
+                break;
5d145d
+
5d145d
+            case 'd':
5d145d
+                dbDir = PORT_Strdup(optstate->value);
5d145d
+                break;
5d145d
+
5d145d
+            case 'c':
5d145d
+                certPrefix = PORT_Strdup(optstate->value);
5d145d
+                break;
5d145d
+
5d145d
+            case 'k':
5d145d
+                keyPrefix = PORT_Strdup(optstate->value);
5d145d
+                break;
5d145d
+
5d145d
+            case 'v':
5d145d
+                key_version = atoi(optstate->value);
5d145d
+                break;
5d145d
+
5d145d
+            case 'V':
5d145d
+                cert_version = atoi(optstate->value);
5d145d
+                break;
5d145d
+       
5d145d
+        }
5d145d
+    }
5d145d
+    PL_DestroyOptState(optstate);
5d145d
+    if (optstatus == PL_OPT_BAD)
5d145d
+        Usage();
5d145d
+
5d145d
+    if (dbDir) {
5d145d
+        char *tmp = dbDir;
5d145d
+        dbDir = secu_ConfigDirectory(tmp);
5d145d
+        PORT_Free(tmp);
5d145d
+    } else {
5d145d
+        dbDir = secu_ConfigDirectory(NULL);
5d145d
+    }
5d145d
+    PR_fprintf(PR_STDERR, "dbdir selected is %s\n\n", dbDir);
5d145d
+
5d145d
+    if (dbDir[0] == '\0') {
5d145d
+        PR_fprintf(PR_STDERR, 
5d145d
+                   "ERROR: Directory \"%s\" does not exist.\n", dbDir);
5d145d
+        return 1;
5d145d
+    }
5d145d
+
5d145d
+    PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
5d145d
+    SECOID_Init();
5d145d
+
5d145d
+    crv = s_open(dbDir, certPrefix, keyPrefix, cert_version, key_version,
5d145d
+                 SDB_RDONLY, &certdb, &keydb, &isNew);
5d145d
+    if (crv != CKR_OK) {
5d145d
+        PR_fprintf(PR_STDERR,
5d145d
+                   "Couldn't open databased in %s, error=%s (0x%08x)\n",
5d145d
+                    dbDir, ErrorName(crv), (int)crv);
5d145d
+        return 1;
5d145d
+    }
5d145d
+
5d145d
+    /* now dump the objects in the cert database */
5d145d
+    dumpDB(certdb, "CertDB", keydb, PR_FALSE);
5d145d
+    dumpDB(keydb, "KeyDB", keydb, PR_TRUE);
5d145d
+    return 0;
5d145d
+}
5d145d
diff --git a/cmd/dbtool/dbtool.gyp b/cmd/dbtool/dbtool.gyp
5d145d
new file mode 100644
5d145d
--- /dev/null
5d145d
+++ b/cmd/dbtool/dbtool.gyp
5d145d
@@ -0,0 +1,25 @@
5d145d
+# This Source Code Form is subject to the terms of the Mozilla Public
5d145d
+# License, v. 2.0. If a copy of the MPL was not distributed with this
5d145d
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
5d145d
+{
5d145d
+  'includes': [
5d145d
+    '../../coreconf/config.gypi',
5d145d
+    '../../cmd/platlibs.gypi'
5d145d
+  ],
5d145d
+  'targets': [
5d145d
+    {
5d145d
+      'target_name': 'dbtest',
5d145d
+      'type': 'executable',
5d145d
+      'sources': [
5d145d
+        'dbtest.c'
5d145d
+      ],
5d145d
+      'dependencies': [
5d145d
+        '<(DEPTH)/exports.gyp:dbm_exports',
5d145d
+        '<(DEPTH)/exports.gyp:nss_exports'
5d145d
+      ]
5d145d
+    }
5d145d
+  ],
5d145d
+  'variables': {
5d145d
+    'module': 'nss'
5d145d
+  }
5d145d
+}
5d145d
\ No newline at end of file
5d145d
diff --git a/cmd/dbtool/manifest.mn b/cmd/dbtool/manifest.mn
5d145d
new file mode 100644
5d145d
--- /dev/null
5d145d
+++ b/cmd/dbtool/manifest.mn
5d145d
@@ -0,0 +1,18 @@
5d145d
+# 
5d145d
+# This Source Code Form is subject to the terms of the Mozilla Public
5d145d
+# License, v. 2.0. If a copy of the MPL was not distributed with this
5d145d
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
5d145d
+
5d145d
+CORE_DEPTH = ../..
5d145d
+
5d145d
+# MODULE public and private header  directories are implicitly REQUIRED.
5d145d
+MODULE = nss
5d145d
+
5d145d
+USE_STATIC_LIBS = 1
5d145d
+
5d145d
+# DIRS = 
5d145d
+
5d145d
+CSRCS	= dbtool.c  sdb.c 
5d145d
+
5d145d
+PROGRAM	= dbtool
5d145d
+
5d145d
diff --git a/cmd/dbtool/sdb.c b/cmd/dbtool/sdb.c
5d145d
new file mode 100644
5d145d
--- /dev/null
5d145d
+++ b/cmd/dbtool/sdb.c
5d145d
@@ -0,0 +1,2469 @@
5d145d
+/* This Source Code Form is subject to the terms of the Mozilla Public
5d145d
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
5d145d
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5d145d
+/*
5d145d
+ * This file implements PKCS 11 on top of our existing security modules
5d145d
+ *
5d145d
+ * For more information about PKCS 11 See PKCS 11 Token Inteface Standard.
5d145d
+ *   This implementation has two slots:
5d145d
+ *      slot 1 is our generic crypto support. It does not require login.
5d145d
+ *   It supports Public Key ops, and all they bulk ciphers and hashes.
5d145d
+ *   It can also support Private Key ops for imported Private keys. It does
5d145d
+ *   not have any token storage.
5d145d
+ *      slot 2 is our private key support. It requires a login before use. It
5d145d
+ *   can store Private Keys and Certs as token objects. Currently only private
5d145d
+ *   keys and their associated Certificates are saved on the token.
5d145d
+ *
5d145d
+ *   In this implementation, session objects are only visible to the session
5d145d
+ *   that created or generated them.
5d145d
+ */
5d145d
+
5d145d
+#include "sdb.h"
5d145d
+#include "pkcs11t.h"
5d145d
+#include "seccomon.h"
5d145d
+#include <sqlite3.h>
5d145d
+#include "prthread.h"
5d145d
+#include "prio.h"
5d145d
+#include <stdio.h>
5d145d
+#include "secport.h"
5d145d
+#include "prmon.h"
5d145d
+#include "prenv.h"
5d145d
+#include "prprf.h"
5d145d
+#include "prsystem.h" /* for PR_GetDirectorySeparator() */
5d145d
+#include <sys/stat.h>
5d145d
+#if defined(_WIN32)
5d145d
+#include <io.h>
5d145d
+#include <windows.h>
5d145d
+#elif defined(XP_UNIX)
5d145d
+#include <unistd.h>
5d145d
+#endif
5d145d
+#if defined(LINUX) && !defined(ANDROID)
5d145d
+#include <linux/magic.h>
5d145d
+#include <sys/vfs.h>
5d145d
+#endif
5d145d
+#include "utilpars.h"
5d145d
+
5d145d
+#ifdef SQLITE_UNSAFE_THREADS
5d145d
+#include "prlock.h"
5d145d
+/*
5d145d
+ * SQLite can be compiled to be thread safe or not.
5d145d
+ * turn on SQLITE_UNSAFE_THREADS if the OS does not support
5d145d
+ * a thread safe version of sqlite.
5d145d
+ */
5d145d
+static PRLock *sqlite_lock = NULL;
5d145d
+
5d145d
+#define LOCK_SQLITE() PR_Lock(sqlite_lock);
5d145d
+#define UNLOCK_SQLITE() PR_Unlock(sqlite_lock);
5d145d
+#else
5d145d
+#define LOCK_SQLITE()
5d145d
+#define UNLOCK_SQLITE()
5d145d
+#endif
5d145d
+
5d145d
+typedef enum {
5d145d
+    SDB_CERT = 1,
5d145d
+    SDB_KEY = 2
5d145d
+} sdbDataType;
5d145d
+
5d145d
+/*
5d145d
+ * defines controlling how long we wait to acquire locks.
5d145d
+ *
5d145d
+ * SDB_SQLITE_BUSY_TIMEOUT specifies how long (in milliseconds)
5d145d
+ *  sqlite will wait on lock. If that timeout expires, sqlite will
5d145d
+ *  return SQLITE_BUSY.
5d145d
+ * SDB_BUSY_RETRY_TIME specifies how many seconds the sdb_ code waits
5d145d
+ *  after receiving a busy before retrying.
5d145d
+ * SDB_MAX_BUSY_RETRIES specifies how many times the sdb_ will retry on
5d145d
+ *  a busy condition.
5d145d
+ *
5d145d
+ * SDB_SQLITE_BUSY_TIMEOUT affects all opertions, both manual
5d145d
+ *   (prepare/step/reset/finalize) and automatic (sqlite3_exec()).
5d145d
+ * SDB_BUSY_RETRY_TIME and SDB_MAX_BUSY_RETRIES only affect manual operations
5d145d
+ *
5d145d
+ * total wait time for automatic operations:
5d145d
+ *   1 second (SDB_SQLITE_BUSY_TIMEOUT/1000).
5d145d
+ * total wait time for manual operations:
5d145d
+ *   (1 second + SDB_BUSY_RETRY_TIME) * 30 = 30 seconds.
5d145d
+ * (SDB_SQLITE_BUSY_TIMEOUT/1000 + SDB_BUSY_RETRY_TIME)*SDB_MAX_BUSY_RETRIES
5d145d
+ */
5d145d
+#define SDB_SQLITE_BUSY_TIMEOUT 1000 /* milliseconds */
5d145d
+#define SDB_BUSY_RETRY_TIME 5        /* 'ticks', varies by platforms */
5d145d
+#define SDB_MAX_BUSY_RETRIES 30
5d145d
+
5d145d
+/*
5d145d
+ * known attributes
5d145d
+ */
5d145d
+static const CK_ATTRIBUTE_TYPE known_attributes[] = {
5d145d
+    CKA_CLASS, CKA_TOKEN, CKA_PRIVATE, CKA_LABEL, CKA_APPLICATION,
5d145d
+    CKA_VALUE, CKA_OBJECT_ID, CKA_CERTIFICATE_TYPE, CKA_ISSUER,
5d145d
+    CKA_SERIAL_NUMBER, CKA_AC_ISSUER, CKA_OWNER, CKA_ATTR_TYPES, CKA_TRUSTED,
5d145d
+    CKA_CERTIFICATE_CATEGORY, CKA_JAVA_MIDP_SECURITY_DOMAIN, CKA_URL,
5d145d
+    CKA_HASH_OF_SUBJECT_PUBLIC_KEY, CKA_HASH_OF_ISSUER_PUBLIC_KEY,
5d145d
+    CKA_CHECK_VALUE, CKA_KEY_TYPE, CKA_SUBJECT, CKA_ID, CKA_SENSITIVE,
5d145d
+    CKA_ENCRYPT, CKA_DECRYPT, CKA_WRAP, CKA_UNWRAP, CKA_SIGN, CKA_SIGN_RECOVER,
5d145d
+    CKA_VERIFY, CKA_VERIFY_RECOVER, CKA_DERIVE, CKA_START_DATE, CKA_END_DATE,
5d145d
+    CKA_MODULUS, CKA_MODULUS_BITS, CKA_PUBLIC_EXPONENT, CKA_PRIVATE_EXPONENT,
5d145d
+    CKA_PRIME_1, CKA_PRIME_2, CKA_EXPONENT_1, CKA_EXPONENT_2, CKA_COEFFICIENT,
5d145d
+    CKA_PUBLIC_KEY_INFO, CKA_PRIME, CKA_SUBPRIME, CKA_BASE, CKA_PRIME_BITS,
5d145d
+    CKA_SUB_PRIME_BITS, CKA_VALUE_BITS, CKA_VALUE_LEN, CKA_EXTRACTABLE,
5d145d
+    CKA_LOCAL, CKA_NEVER_EXTRACTABLE, CKA_ALWAYS_SENSITIVE,
5d145d
+    CKA_KEY_GEN_MECHANISM, CKA_MODIFIABLE, CKA_EC_PARAMS,
5d145d
+    CKA_EC_POINT, CKA_SECONDARY_AUTH, CKA_AUTH_PIN_FLAGS,
5d145d
+    CKA_ALWAYS_AUTHENTICATE, CKA_WRAP_WITH_TRUSTED, CKA_HW_FEATURE_TYPE,
5d145d
+    CKA_RESET_ON_INIT, CKA_HAS_RESET, CKA_PIXEL_X, CKA_PIXEL_Y,
5d145d
+    CKA_RESOLUTION, CKA_CHAR_ROWS, CKA_CHAR_COLUMNS, CKA_COLOR,
5d145d
+    CKA_BITS_PER_PIXEL, CKA_CHAR_SETS, CKA_ENCODING_METHODS, CKA_MIME_TYPES,
5d145d
+    CKA_MECHANISM_TYPE, CKA_REQUIRED_CMS_ATTRIBUTES,
5d145d
+    CKA_DEFAULT_CMS_ATTRIBUTES, CKA_SUPPORTED_CMS_ATTRIBUTES,
5d145d
+    CKA_WRAP_TEMPLATE, CKA_UNWRAP_TEMPLATE, CKA_NSS_TRUST, CKA_NSS_URL,
5d145d
+    CKA_NSS_EMAIL, CKA_NSS_SMIME_INFO, CKA_NSS_SMIME_TIMESTAMP,
5d145d
+    CKA_NSS_PKCS8_SALT, CKA_NSS_PASSWORD_CHECK, CKA_NSS_EXPIRES,
5d145d
+    CKA_NSS_KRL, CKA_NSS_PQG_COUNTER, CKA_NSS_PQG_SEED,
5d145d
+    CKA_NSS_PQG_H, CKA_NSS_PQG_SEED_BITS, CKA_NSS_MODULE_SPEC,
5d145d
+    CKA_NSS_OVERRIDE_EXTENSIONS, CKA_NSS_SERVER_DISTRUST_AFTER,
5d145d
+    CKA_NSS_EMAIL_DISTRUST_AFTER, CKA_TRUST_DIGITAL_SIGNATURE,
5d145d
+    CKA_TRUST_NON_REPUDIATION, CKA_TRUST_KEY_ENCIPHERMENT,
5d145d
+    CKA_TRUST_DATA_ENCIPHERMENT, CKA_TRUST_KEY_AGREEMENT,
5d145d
+    CKA_TRUST_KEY_CERT_SIGN, CKA_TRUST_CRL_SIGN, CKA_TRUST_SERVER_AUTH,
5d145d
+    CKA_TRUST_CLIENT_AUTH, CKA_TRUST_CODE_SIGNING, CKA_TRUST_EMAIL_PROTECTION,
5d145d
+    CKA_TRUST_IPSEC_END_SYSTEM, CKA_TRUST_IPSEC_TUNNEL, CKA_TRUST_IPSEC_USER,
5d145d
+    CKA_TRUST_TIME_STAMPING, CKA_TRUST_STEP_UP_APPROVED, CKA_CERT_SHA1_HASH,
5d145d
+    CKA_CERT_MD5_HASH, CKA_NSS_DB
5d145d
+};
5d145d
+
5d145d
+static const int known_attributes_size = PR_ARRAY_SIZE(known_attributes);
5d145d
+
5d145d
+/*
5d145d
+ * Note on use of sqlReadDB: Only one thread at a time may have an actual
5d145d
+ * operation going on given sqlite3 * database. An operation is defined as
5d145d
+ * the time from a sqlite3_prepare() until the sqlite3_finalize().
5d145d
+ * Multiple sqlite3 * databases can be open and have simultaneous operations
5d145d
+ * going. We use the sqlXactDB for all write operations. This database
5d145d
+ * is only opened when we first create a transaction and closed when the
5d145d
+ * transaction is complete. sqlReadDB is open when we first opened the database
5d145d
+ * and is used for all read operation. It's use is protected by a monitor. This
5d145d
+ * is because an operation can span the use of FindObjectsInit() through the
5d145d
+ * call to FindObjectsFinal(). In the intermediate time it is possible to call
5d145d
+ * other operations like NSC_GetAttributeValue */
5d145d
+
5d145d
+struct SDBPrivateStr {
5d145d
+    char *sqlDBName;                /* invariant, path to this database */
5d145d
+    sqlite3 *sqlXactDB;             /* access protected by dbMon, use protected
5d145d
+                                    * by the transaction. Current transaction db*/
5d145d
+    PRThread *sqlXactThread;        /* protected by dbMon,
5d145d
+                                    * current transaction thread */
5d145d
+    sqlite3 *sqlReadDB;             /* use protected by dbMon, value invariant */
5d145d
+    PRIntervalTime lastUpdateTime;  /* last time the cache was updated */
5d145d
+    PRIntervalTime updateInterval;  /* how long the cache can go before it
5d145d
+                                    * must be updated again */
5d145d
+    sdbDataType type;               /* invariant, database type */
5d145d
+    char *table;                    /* invariant, SQL table which contains the db */
5d145d
+    char *cacheTable;               /* invariant, SQL table cache of db */
5d145d
+    PRMonitor *dbMon;               /* invariant, monitor to protect
5d145d
+                                    * sqlXact* fields, and use of the sqlReadDB */
5d145d
+    CK_ATTRIBUTE_TYPE *schemaAttrs; /* Attribute columns that exist in the table. */
5d145d
+    unsigned int numSchemaAttrs;
5d145d
+};
5d145d
+
5d145d
+typedef struct SDBPrivateStr SDBPrivate;
5d145d
+
5d145d
+/* Magic for an explicit NULL. NOTE: ideally this should be
5d145d
+ * out of band data. Since it's not completely out of band, pick
5d145d
+ * a value that has no meaning to any existing PKCS #11 attributes.
5d145d
+ * This value is 1) not a valid string (imbedded '\0'). 2) not a U_LONG
5d145d
+ * or a normal key (too short). 3) not a bool (too long). 4) not an RSA
5d145d
+ * public exponent (too many bits).
5d145d
+ */
5d145d
+const unsigned char SQLITE_EXPLICIT_NULL[] = { 0xa5, 0x0, 0x5a };
5d145d
+#define SQLITE_EXPLICIT_NULL_LEN 3
5d145d
+
5d145d
+/*
5d145d
+ * determine when we've completed our tasks
5d145d
+ */
5d145d
+static int
5d145d
+sdb_done(int err, int *count)
5d145d
+{
5d145d
+    /* allow as many rows as the database wants to give */
5d145d
+    if (err == SQLITE_ROW) {
5d145d
+        *count = 0;
5d145d
+        return 0;
5d145d
+    }
5d145d
+    if (err != SQLITE_BUSY) {
5d145d
+        return 1;
5d145d
+    }
5d145d
+    /* err == SQLITE_BUSY, Dont' retry forever in this case */
5d145d
+    if (++(*count) >= SDB_MAX_BUSY_RETRIES) {
5d145d
+        return 1;
5d145d
+    }
5d145d
+    return 0;
5d145d
+}
5d145d
+
5d145d
+#if defined(_WIN32)
5d145d
+/*
5d145d
+ * NSPR functions and narrow CRT functions do not handle UTF-8 file paths that
5d145d
+ * sqlite3 expects.
5d145d
+ */
5d145d
+
5d145d
+static int
5d145d
+sdb_chmod(const char *filename, int pmode)
5d145d
+{
5d145d
+    int result;
5d145d
+
5d145d
+    if (!filename) {
5d145d
+        return -1;
5d145d
+    }
5d145d
+
5d145d
+    wchar_t *filenameWide = _NSSUTIL_UTF8ToWide(filename);
5d145d
+    if (!filenameWide) {
5d145d
+        return -1;
5d145d
+    }
5d145d
+    result = _wchmod(filenameWide, pmode);
5d145d
+    PORT_Free(filenameWide);
5d145d
+
5d145d
+    return result;
5d145d
+}
5d145d
+#else
5d145d
+#define sdb_chmod(filename, pmode) chmod((filename), (pmode))
5d145d
+#endif
5d145d
+
5d145d
+/*
5d145d
+ * find out where sqlite stores the temp tables. We do this by replicating
5d145d
+ * the logic from sqlite.
5d145d
+ */
5d145d
+#if defined(_WIN32)
5d145d
+static char *
5d145d
+sdb_getFallbackTempDir(void)
5d145d
+{
5d145d
+    /* sqlite uses sqlite3_temp_directory if it is not NULL. We don't have
5d145d
+     * access to sqlite3_temp_directory because it is not exported from
5d145d
+     * sqlite3.dll. Assume sqlite3_win32_set_directory isn't called and
5d145d
+     * sqlite3_temp_directory is NULL.
5d145d
+     */
5d145d
+    char path[MAX_PATH];
5d145d
+    DWORD rv;
5d145d
+    size_t len;
5d145d
+
5d145d
+    rv = GetTempPathA(MAX_PATH, path);
5d145d
+    if (rv > MAX_PATH || rv == 0)
5d145d
+        return NULL;
5d145d
+    len = strlen(path);
5d145d
+    if (len == 0)
5d145d
+        return NULL;
5d145d
+    /* The returned string ends with a backslash, for example, "C:\TEMP\". */
5d145d
+    if (path[len - 1] == '\\')
5d145d
+        path[len - 1] = '\0';
5d145d
+    return PORT_Strdup(path);
5d145d
+}
5d145d
+#elif defined(XP_UNIX)
5d145d
+static char *
5d145d
+sdb_getFallbackTempDir(void)
5d145d
+{
5d145d
+    const char *azDirs[] = {
5d145d
+        NULL,
5d145d
+        NULL,
5d145d
+        "/var/tmp",
5d145d
+        "/usr/tmp",
5d145d
+        "/tmp",
5d145d
+        NULL /* List terminator */
5d145d
+    };
5d145d
+    unsigned int i;
5d145d
+    struct stat buf;
5d145d
+    const char *zDir = NULL;
5d145d
+
5d145d
+    azDirs[0] = sqlite3_temp_directory;
5d145d
+    azDirs[1] = PR_GetEnvSecure("TMPDIR");
5d145d
+
5d145d
+    for (i = 0; i < PR_ARRAY_SIZE(azDirs); i++) {
5d145d
+        zDir = azDirs[i];
5d145d
+        if (zDir == NULL)
5d145d
+            continue;
5d145d
+        if (stat(zDir, &buf))
5d145d
+            continue;
5d145d
+        if (!S_ISDIR(buf.st_mode))
5d145d
+            continue;
5d145d
+        if (access(zDir, 07))
5d145d
+            continue;
5d145d
+        break;
5d145d
+    }
5d145d
+
5d145d
+    if (zDir == NULL)
5d145d
+        return NULL;
5d145d
+    return PORT_Strdup(zDir);
5d145d
+}
5d145d
+#else
5d145d
+#error "sdb_getFallbackTempDir not implemented"
5d145d
+#endif
5d145d
+
5d145d
+#ifndef SQLITE_FCNTL_TEMPFILENAME
5d145d
+/* SQLITE_FCNTL_TEMPFILENAME was added in SQLite 3.7.15 */
5d145d
+#define SQLITE_FCNTL_TEMPFILENAME 16
5d145d
+#endif
5d145d
+
5d145d
+static char *
5d145d
+sdb_getTempDir(sqlite3 *sqlDB)
5d145d
+{
5d145d
+    int sqlrv;
5d145d
+    char *result = NULL;
5d145d
+    char *tempName = NULL;
5d145d
+    char *foundSeparator = NULL;
5d145d
+
5d145d
+    /* Obtain temporary filename in sqlite's directory for temporary tables */
5d145d
+    sqlrv = sqlite3_file_control(sqlDB, 0, SQLITE_FCNTL_TEMPFILENAME,
5d145d
+                                 (void *)&tempName);
5d145d
+    if (sqlrv == SQLITE_NOTFOUND) {
5d145d
+        /* SQLITE_FCNTL_TEMPFILENAME not implemented because we are using
5d145d
+         * an older SQLite. */
5d145d
+        return sdb_getFallbackTempDir();
5d145d
+    }
5d145d
+    if (sqlrv != SQLITE_OK) {
5d145d
+        return NULL;
5d145d
+    }
5d145d
+
5d145d
+    /* We'll extract the temporary directory from tempName */
5d145d
+    foundSeparator = PORT_Strrchr(tempName, PR_GetDirectorySeparator());
5d145d
+    if (foundSeparator) {
5d145d
+        /* We shorten the temp filename string to contain only
5d145d
+         * the directory name (including the trailing separator).
5d145d
+         * We know the byte after the foundSeparator position is
5d145d
+         * safe to use, in the shortest scenario it contains the
5d145d
+         * end-of-string byte.
5d145d
+         * By keeping the separator at the found position, it will
5d145d
+         * even work if tempDir consists of the separator, only.
5d145d
+         * (In this case the toplevel directory will be used for
5d145d
+         * access speed testing). */
5d145d
+        ++foundSeparator;
5d145d
+        *foundSeparator = 0;
5d145d
+
5d145d
+        /* Now we copy the directory name for our caller */
5d145d
+        result = PORT_Strdup(tempName);
5d145d
+    }
5d145d
+
5d145d
+    sqlite3_free(tempName);
5d145d
+    return result;
5d145d
+}
5d145d
+
5d145d
+/*
5d145d
+ * Map SQL_LITE errors to PKCS #11 errors as best we can.
5d145d
+ */
5d145d
+static CK_RV
5d145d
+sdb_mapSQLError(sdbDataType type, int sqlerr)
5d145d
+{
5d145d
+    switch (sqlerr) {
5d145d
+        /* good matches */
5d145d
+        case SQLITE_OK:
5d145d
+        case SQLITE_DONE:
5d145d
+            return CKR_OK;
5d145d
+        case SQLITE_NOMEM:
5d145d
+            return CKR_HOST_MEMORY;
5d145d
+        case SQLITE_READONLY:
5d145d
+            return CKR_TOKEN_WRITE_PROTECTED;
5d145d
+        /* close matches */
5d145d
+        case SQLITE_AUTH:
5d145d
+        case SQLITE_PERM:
5d145d
+        /*return CKR_USER_NOT_LOGGED_IN; */
5d145d
+        case SQLITE_CANTOPEN:
5d145d
+        case SQLITE_NOTFOUND:
5d145d
+            /* NSS distiguishes between failure to open the cert and the key db */
5d145d
+            return type == SDB_CERT ? CKR_NSS_CERTDB_FAILED : CKR_NSS_KEYDB_FAILED;
5d145d
+        case SQLITE_IOERR:
5d145d
+            return CKR_DEVICE_ERROR;
5d145d
+        default:
5d145d
+            break;
5d145d
+    }
5d145d
+    return CKR_GENERAL_ERROR;
5d145d
+}
5d145d
+
5d145d
+/*
5d145d
+ * build up database name from a directory, prefix, name, version and flags.
5d145d
+ */
5d145d
+static char *
5d145d
+sdb_BuildFileName(const char *directory,
5d145d
+                  const char *prefix, const char *type,
5d145d
+                  int version)
5d145d
+{
5d145d
+    char *dbname = NULL;
5d145d
+    /* build the full dbname */
5d145d
+    dbname = sqlite3_mprintf("%s%c%s%s%d.db", directory,
5d145d
+                             (int)(unsigned char)PR_GetDirectorySeparator(),
5d145d
+                             prefix, type, version);
5d145d
+    return dbname;
5d145d
+}
5d145d
+
5d145d
+/*
5d145d
+ * find out how expensive the access system call is for non-existant files
5d145d
+ * in the given directory.  Return the number of operations done in 33 ms.
5d145d
+ */
5d145d
+static PRUint32
5d145d
+sdb_measureAccess(const char *directory)
5d145d
+{
5d145d
+    PRUint32 i;
5d145d
+    PRIntervalTime time;
5d145d
+    PRIntervalTime delta;
5d145d
+    PRIntervalTime duration = PR_MillisecondsToInterval(33);
5d145d
+    const char *doesntExistName = "_dOeSnotExist_.db";
5d145d
+    char *temp, *tempStartOfFilename;
5d145d
+    size_t maxTempLen, maxFileNameLen, directoryLength, tmpdirLength = 0;
5d145d
+#ifdef SDB_MEASURE_USE_TEMP_DIR
5d145d
+    /*
5d145d
+     * on some OS's and Filesystems, creating a bunch of files and deleting
5d145d
+     * them messes up the systems's caching, but if we create the files in
5d145d
+     * a temp directory which we later delete, then the cache gets cleared
5d145d
+     * up. This code uses several OS dependent calls, and it's not clear
5d145d
+     * that temp directory use won't mess up other filesystems and OS caching,
5d145d
+     * so if you need this for your OS, you can turn on the
5d145d
+     * 'SDB_MEASURE_USE_TEMP_DIR' define in coreconf
5d145d
+     */
5d145d
+    const char template[] = "dbTemp.XXXXXX";
5d145d
+    tmpdirLength = sizeof(template);
5d145d
+#endif
5d145d
+    /* no directory, just return one */
5d145d
+    if (directory == NULL) {
5d145d
+        return 1;
5d145d
+    }
5d145d
+
5d145d
+    /* our calculation assumes time is a 4 bytes == 32 bit integer */
5d145d
+    PORT_Assert(sizeof(time) == 4);
5d145d
+
5d145d
+    directoryLength = strlen(directory);
5d145d
+
5d145d
+    maxTempLen = directoryLength + 1       /* dirname + / */
5d145d
+                 + tmpdirLength            /* tmpdirname includes / */
5d145d
+                 + strlen(doesntExistName) /* filename base */
5d145d
+                 + 11                      /* max chars for 32 bit int plus potential sign */
5d145d
+                 + 1;                      /* zero terminator */
5d145d
+
5d145d
+    temp = PORT_ZAlloc(maxTempLen);
5d145d
+    if (!temp) {
5d145d
+        return 1;
5d145d
+    }
5d145d
+
5d145d
+    /* We'll copy directory into temp just once, then ensure it ends
5d145d
+     * with the directory separator. */
5d145d
+
5d145d
+    strcpy(temp, directory);
5d145d
+    if (directory[directoryLength - 1] != PR_GetDirectorySeparator()) {
5d145d
+        temp[directoryLength++] = PR_GetDirectorySeparator();
5d145d
+    }
5d145d
+
5d145d
+#ifdef SDB_MEASURE_USE_TEMP_DIR
5d145d
+    /* add the template for a temporary subdir, and create it */
5d145d
+    strcat(temp, template);
5d145d
+    if (!mkdtemp(temp)) {
5d145d
+        PORT_Free(temp);
5d145d
+        return 1;
5d145d
+    }
5d145d
+    /* and terminate that tmp subdir with a / */
5d145d
+    strcat(temp, "/");
5d145d
+#endif
5d145d
+
5d145d
+    /* Remember the position after the last separator, and calculate the
5d145d
+     * number of remaining bytes. */
5d145d
+    tempStartOfFilename = temp + directoryLength + tmpdirLength;
5d145d
+    maxFileNameLen = maxTempLen - directoryLength;
5d145d
+
5d145d
+    /* measure number of Access operations that can be done in 33 milliseconds
5d145d
+     * (1/30'th of a second), or 10000 operations, which ever comes first.
5d145d
+     */
5d145d
+    time = PR_IntervalNow();
5d145d
+    for (i = 0; i < 10000u; i++) {
5d145d
+        PRIntervalTime next;
5d145d
+
5d145d
+        /* We'll use the variable part first in the filename string, just in
5d145d
+         * case it's longer than assumed, so if anything gets cut off, it
5d145d
+         * will be cut off from the constant part.
5d145d
+         * This code assumes the directory name at the beginning of
5d145d
+         * temp remains unchanged during our loop. */
5d145d
+        PR_snprintf(tempStartOfFilename, maxFileNameLen,
5d145d
+                    ".%lu%s", (PRUint32)(time + i), doesntExistName);
5d145d
+        PR_Access(temp, PR_ACCESS_EXISTS);
5d145d
+        next = PR_IntervalNow();
5d145d
+        delta = next - time;
5d145d
+        if (delta >= duration)
5d145d
+            break;
5d145d
+    }
5d145d
+
5d145d
+#ifdef SDB_MEASURE_USE_TEMP_DIR
5d145d
+    /* turn temp back into our tmpdir path by removing doesntExistName, and
5d145d
+     * remove the tmp dir */
5d145d
+    *tempStartOfFilename = '\0';
5d145d
+    (void)rmdir(temp);
5d145d
+#endif
5d145d
+    PORT_Free(temp);
5d145d
+
5d145d
+    /* always return 1 or greater */
5d145d
+    return i ? i : 1u;
5d145d
+}
5d145d
+
5d145d
+/*
5d145d
+ * some file sytems are very slow to run sqlite3 on, particularly if the
5d145d
+ * access count is pretty high. On these filesystems is faster to create
5d145d
+ * a temporary database on the local filesystem and access that. This
5d145d
+ * code uses a temporary table to create that cache. Temp tables are
5d145d
+ * automatically cleared when the database handle it was created on
5d145d
+ * Is freed.
5d145d
+ */
5d145d
+static const char DROP_CACHE_CMD[] = "DROP TABLE %s";
5d145d
+static const char CREATE_CACHE_CMD[] =
5d145d
+    "CREATE TEMPORARY TABLE %s AS SELECT * FROM %s";
5d145d
+static const char CREATE_ISSUER_INDEX_CMD[] =
5d145d
+    "CREATE INDEX issuer ON %s (a81)";
5d145d
+static const char CREATE_SUBJECT_INDEX_CMD[] =
5d145d
+    "CREATE INDEX subject ON %s (a101)";
5d145d
+static const char CREATE_LABEL_INDEX_CMD[] = "CREATE INDEX label ON %s (a3)";
5d145d
+static const char CREATE_ID_INDEX_CMD[] = "CREATE INDEX ckaid ON %s (a102)";
5d145d
+
5d145d
+static CK_RV
5d145d
+sdb_buildCache(sqlite3 *sqlDB, sdbDataType type,
5d145d
+               const char *cacheTable, const char *table)
5d145d
+{
5d145d
+    char *newStr;
5d145d
+    int sqlerr = SQLITE_OK;
5d145d
+
5d145d
+    newStr = sqlite3_mprintf(CREATE_CACHE_CMD, cacheTable, table);
5d145d
+    if (newStr == NULL) {
5d145d
+        return CKR_HOST_MEMORY;
5d145d
+    }
5d145d
+    sqlerr = sqlite3_exec(sqlDB, newStr, NULL, 0, NULL);
5d145d
+    sqlite3_free(newStr);
5d145d
+    if (sqlerr != SQLITE_OK) {
5d145d
+        return sdb_mapSQLError(type, sqlerr);
5d145d
+    }
5d145d
+    /* failure to create the indexes is not an issue */
5d145d
+    newStr = sqlite3_mprintf(CREATE_ISSUER_INDEX_CMD, cacheTable);
5d145d
+    if (newStr == NULL) {
5d145d
+        return CKR_OK;
5d145d
+    }
5d145d
+    sqlerr = sqlite3_exec(sqlDB, newStr, NULL, 0, NULL);
5d145d
+    sqlite3_free(newStr);
5d145d
+    newStr = sqlite3_mprintf(CREATE_SUBJECT_INDEX_CMD, cacheTable);
5d145d
+    if (newStr == NULL) {
5d145d
+        return CKR_OK;
5d145d
+    }
5d145d
+    sqlerr = sqlite3_exec(sqlDB, newStr, NULL, 0, NULL);
5d145d
+    sqlite3_free(newStr);
5d145d
+    newStr = sqlite3_mprintf(CREATE_LABEL_INDEX_CMD, cacheTable);
5d145d
+    if (newStr == NULL) {
5d145d
+        return CKR_OK;
5d145d
+    }
5d145d
+    sqlerr = sqlite3_exec(sqlDB, newStr, NULL, 0, NULL);
5d145d
+    sqlite3_free(newStr);
5d145d
+    newStr = sqlite3_mprintf(CREATE_ID_INDEX_CMD, cacheTable);
5d145d
+    if (newStr == NULL) {
5d145d
+        return CKR_OK;
5d145d
+    }
5d145d
+    sqlerr = sqlite3_exec(sqlDB, newStr, NULL, 0, NULL);
5d145d
+    sqlite3_free(newStr);
5d145d
+    return CKR_OK;
5d145d
+}
5d145d
+
5d145d
+/*
5d145d
+ * update the cache and the data records describing it.
5d145d
+ *  The cache is updated by dropping the temp database and recreating it.
5d145d
+ */
5d145d
+static CK_RV
5d145d
+sdb_updateCache(SDBPrivate *sdb_p)
5d145d
+{
5d145d
+    int sqlerr = SQLITE_OK;
5d145d
+    CK_RV error = CKR_OK;
5d145d
+    char *newStr;
5d145d
+
5d145d
+    /* drop the old table */
5d145d
+    newStr = sqlite3_mprintf(DROP_CACHE_CMD, sdb_p->cacheTable);
5d145d
+    if (newStr == NULL) {
5d145d
+        return CKR_HOST_MEMORY;
5d145d
+    }
5d145d
+    sqlerr = sqlite3_exec(sdb_p->sqlReadDB, newStr, NULL, 0, NULL);
5d145d
+    sqlite3_free(newStr);
5d145d
+    if ((sqlerr != SQLITE_OK) && (sqlerr != SQLITE_ERROR)) {
5d145d
+        /* something went wrong with the drop, don't try to refresh...
5d145d
+         * NOTE: SQLITE_ERROR is returned if the table doesn't exist. In
5d145d
+         * that case, we just continue on and try to reload it */
5d145d
+        return sdb_mapSQLError(sdb_p->type, sqlerr);
5d145d
+    }
5d145d
+
5d145d
+    /* set up the new table */
5d145d
+    error = sdb_buildCache(sdb_p->sqlReadDB, sdb_p->type,
5d145d
+                           sdb_p->cacheTable, sdb_p->table);
5d145d
+    if (error == CKR_OK) {
5d145d
+        /* we have a new cache! */
5d145d
+        sdb_p->lastUpdateTime = PR_IntervalNow();
5d145d
+    }
5d145d
+    return error;
5d145d
+}
5d145d
+
5d145d
+/*
5d145d
+ *  The sharing of sqlite3 handles across threads is tricky. Older versions
5d145d
+ *  couldn't at all, but newer ones can under strict conditions. Basically
5d145d
+ *  no 2 threads can use the same handle while another thread has an open
5d145d
+ *  stmt running. Once the sqlite3_stmt is finalized, another thread can then
5d145d
+ *  use the database handle.
5d145d
+ *
5d145d
+ *  We use monitors to protect against trying to use a database before
5d145d
+ *  it's sqlite3_stmt is finalized. This is preferable to the opening and
5d145d
+ *  closing the database each operation because there is significant overhead
5d145d
+ *  in the open and close. Also continually opening and closing the database
5d145d
+ *  defeats the cache code as the cache table is lost on close (thus
5d145d
+ *  requiring us to have to reinitialize the cache every operation).
5d145d
+ *
5d145d
+ *  An execption to the shared handle is transations. All writes happen
5d145d
+ *  through a transaction. When we are in  a transaction, we must use the
5d145d
+ *  same database pointer for that entire transation. In this case we save
5d145d
+ *  the transaction database and use it for all accesses on the transaction
5d145d
+ *  thread. Other threads use the common database.
5d145d
+ *
5d145d
+ *  There can only be once active transaction on the database at a time.
5d145d
+ *
5d145d
+ *  sdb_openDBLocal() provides us with a valid database handle for whatever
5d145d
+ *  state we are in (reading or in a transaction), and acquires any locks
5d145d
+ *  appropriate to that state. It also decides when it's time to refresh
5d145d
+ *  the cache before we start an operation. Any database handle returned
5d145d
+ *  just eventually be closed with sdb_closeDBLocal().
5d145d
+ *
5d145d
+ *  The table returned either points to the database's physical table, or
5d145d
+ *  to the cached shadow. Tranactions always return the physical table
5d145d
+ *  and read operations return either the physical table or the cache
5d145d
+ *  depending on whether or not the cache exists.
5d145d
+ */
5d145d
+static CK_RV
5d145d
+sdb_openDBLocal(SDBPrivate *sdb_p, sqlite3 **sqlDB, const char **table)
5d145d
+{
5d145d
+    *sqlDB = NULL;
5d145d
+
5d145d
+    PR_EnterMonitor(sdb_p->dbMon);
5d145d
+
5d145d
+    if (table) {
5d145d
+        *table = sdb_p->table;
5d145d
+    }
5d145d
+
5d145d
+    /* We're in a transaction, use the transaction DB */
5d145d
+    if ((sdb_p->sqlXactDB) && (sdb_p->sqlXactThread == PR_GetCurrentThread())) {
5d145d
+        *sqlDB = sdb_p->sqlXactDB;
5d145d
+        /* only one thread can get here, safe to unlock */
5d145d
+        PR_ExitMonitor(sdb_p->dbMon);
5d145d
+        return CKR_OK;
5d145d
+    }
5d145d
+
5d145d
+    /*
5d145d
+     * if we are just reading from the table, we may have the table
5d145d
+     * cached in a temporary table (especially if it's on a shared FS).
5d145d
+     * In that case we want to see updates to the table, the the granularity
5d145d
+     * is on order of human scale, not computer scale.
5d145d
+     */
5d145d
+    if (table && sdb_p->cacheTable) {
5d145d
+        PRIntervalTime now = PR_IntervalNow();
5d145d
+        if ((now - sdb_p->lastUpdateTime) > sdb_p->updateInterval) {
5d145d
+            sdb_updateCache(sdb_p);
5d145d
+        }
5d145d
+        *table = sdb_p->cacheTable;
5d145d
+    }
5d145d
+
5d145d
+    *sqlDB = sdb_p->sqlReadDB;
5d145d
+
5d145d
+    /* leave holding the lock. only one thread can actually use a given
5d145d
+     * database connection at once */
5d145d
+
5d145d
+    return CKR_OK;
5d145d
+}
5d145d
+
5d145d
+/* closing the local database currenly means unlocking the monitor */
5d145d
+static CK_RV
5d145d
+sdb_closeDBLocal(SDBPrivate *sdb_p, sqlite3 *sqlDB)
5d145d
+{
5d145d
+    if (sdb_p->sqlXactDB != sqlDB) {
5d145d
+        /* if we weren't in a transaction, we got a lock */
5d145d
+        PR_ExitMonitor(sdb_p->dbMon);
5d145d
+    }
5d145d
+    return CKR_OK;
5d145d
+}
5d145d
+
5d145d
+/*
5d145d
+ * wrapper to sqlite3_open which also sets the busy_timeout
5d145d
+ */
5d145d
+static int
5d145d
+sdb_openDB(const char *name, sqlite3 **sqlDB, int flags)
5d145d
+{
5d145d
+    int sqlerr;
5d145d
+    int openFlags;
5d145d
+
5d145d
+    *sqlDB = NULL;
5d145d
+
5d145d
+    if (flags & SDB_RDONLY) {
5d145d
+        openFlags = SQLITE_OPEN_READONLY;
5d145d
+    } else {
5d145d
+        openFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
5d145d
+        /* sqlite 3.34 seem to incorrectly open readwrite.
5d145d
+        * when the file is readonly. Explicitly reject that issue here */
5d145d
+        if ((_NSSUTIL_Access(name, PR_ACCESS_EXISTS) == PR_SUCCESS) && (_NSSUTIL_Access(name, PR_ACCESS_WRITE_OK) != PR_SUCCESS)) {
5d145d
+            return SQLITE_READONLY;
5d145d
+        }
5d145d
+    }
5d145d
+
5d145d
+    /* Requires SQLite 3.5.0 or newer. */
5d145d
+    sqlerr = sqlite3_open_v2(name, sqlDB, openFlags, NULL);
5d145d
+    if (sqlerr != SQLITE_OK) {
5d145d
+        return sqlerr;
5d145d
+    }
5d145d
+
5d145d
+    sqlerr = sqlite3_busy_timeout(*sqlDB, SDB_SQLITE_BUSY_TIMEOUT);
5d145d
+    if (sqlerr != SQLITE_OK) {
5d145d
+        sqlite3_close(*sqlDB);
5d145d
+        *sqlDB = NULL;
5d145d
+        return sqlerr;
5d145d
+    }
5d145d
+    return SQLITE_OK;
5d145d
+}
5d145d
+
5d145d
+/* Sigh, if we created a new table since we opened the database,
5d145d
+ * the database handle will not see the new table, we need to close this
5d145d
+ * database and reopen it. Caller must be in a transaction or holding
5d145d
+ * the dbMon. sqlDB is changed on success. */
5d145d
+static int
5d145d
+sdb_reopenDBLocal(SDBPrivate *sdb_p, sqlite3 **sqlDB)
5d145d
+{
5d145d
+    sqlite3 *newDB;
5d145d
+    int sqlerr;
5d145d
+
5d145d
+    /* open a new database */
5d145d
+    sqlerr = sdb_openDB(sdb_p->sqlDBName, &newDB, SDB_RDONLY);
5d145d
+    if (sqlerr != SQLITE_OK) {
5d145d
+        return sqlerr;
5d145d
+    }
5d145d
+
5d145d
+    /* if we are in a transaction, we may not be holding the monitor.
5d145d
+     * grab it before we update the transaction database. This is
5d145d
+     * safe since are using monitors. */
5d145d
+    PR_EnterMonitor(sdb_p->dbMon);
5d145d
+    /* update our view of the database */
5d145d
+    if (sdb_p->sqlReadDB == *sqlDB) {
5d145d
+        sdb_p->sqlReadDB = newDB;
5d145d
+    } else if (sdb_p->sqlXactDB == *sqlDB) {
5d145d
+        sdb_p->sqlXactDB = newDB;
5d145d
+    }
5d145d
+    PR_ExitMonitor(sdb_p->dbMon);
5d145d
+
5d145d
+    /* close the old one */
5d145d
+    sqlite3_close(*sqlDB);
5d145d
+
5d145d
+    *sqlDB = newDB;
5d145d
+    return SQLITE_OK;
5d145d
+}
5d145d
+
5d145d
+struct SDBFindStr {
5d145d
+    sqlite3 *sqlDB;
5d145d
+    sqlite3_stmt *findstmt;
5d145d
+};
5d145d
+
5d145d
+static const char FIND_OBJECTS_CMD[] = "SELECT ALL id FROM %s WHERE %s;";
5d145d
+static const char FIND_OBJECTS_ALL_CMD[] = "SELECT ALL id FROM %s;";
5d145d
+CK_RV
5d145d
+sdb_FindObjectsInit(SDB *sdb, const CK_ATTRIBUTE *template, CK_ULONG count,
5d145d
+                    SDBFind **find)
5d145d
+{
5d145d
+    SDBPrivate *sdb_p = sdb->private;
5d145d
+    sqlite3 *sqlDB = NULL;
5d145d
+    const char *table;
5d145d
+    char *newStr, *findStr = NULL;
5d145d
+    sqlite3_stmt *findstmt = NULL;
5d145d
+    char *join = "";
5d145d
+    int sqlerr = SQLITE_OK;
5d145d
+    CK_RV error = CKR_OK;
5d145d
+    unsigned int i;
5d145d
+
5d145d
+    LOCK_SQLITE()
5d145d
+    *find = NULL;
5d145d
+    error = sdb_openDBLocal(sdb_p, &sqlDB, &table);
5d145d
+    if (error != CKR_OK) {
5d145d
+        goto loser;
5d145d
+    }
5d145d
+
5d145d
+    findStr = sqlite3_mprintf("");
5d145d
+    for (i = 0; findStr && i < count; i++) {
5d145d
+        newStr = sqlite3_mprintf("%s%sa%x=$DATA%d", findStr, join,
5d145d
+                                 template[i].type, i);
5d145d
+        join = " AND ";
5d145d
+        sqlite3_free(findStr);
5d145d
+        findStr = newStr;
5d145d
+    }
5d145d
+
5d145d
+    if (findStr == NULL) {
5d145d
+        error = CKR_HOST_MEMORY;
5d145d
+        goto loser;
5d145d
+    }
5d145d
+
5d145d
+    if (count == 0) {
5d145d
+        newStr = sqlite3_mprintf(FIND_OBJECTS_ALL_CMD, table);
5d145d
+    } else {
5d145d
+        newStr = sqlite3_mprintf(FIND_OBJECTS_CMD, table, findStr);
5d145d
+    }
5d145d
+    sqlite3_free(findStr);
5d145d
+    if (newStr == NULL) {
5d145d
+        error = CKR_HOST_MEMORY;
5d145d
+        goto loser;
5d145d
+    }
5d145d
+    sqlerr = sqlite3_prepare_v2(sqlDB, newStr, -1, &findstmt, NULL);
5d145d
+    sqlite3_free(newStr);
5d145d
+    for (i = 0; sqlerr == SQLITE_OK && i < count; i++) {
5d145d
+        const void *blobData = template[i].pValue;
5d145d
+        unsigned int blobSize = template[i].ulValueLen;
5d145d
+        if (blobSize == 0) {
5d145d
+            blobSize = SQLITE_EXPLICIT_NULL_LEN;
5d145d
+            blobData = SQLITE_EXPLICIT_NULL;
5d145d
+        }
5d145d
+        sqlerr = sqlite3_bind_blob(findstmt, i + 1, blobData, blobSize,
5d145d
+                                   SQLITE_TRANSIENT);
5d145d
+    }
5d145d
+    if (sqlerr == SQLITE_OK) {
5d145d
+        *find = PORT_New(SDBFind);
5d145d
+        if (*find == NULL) {
5d145d
+            error = CKR_HOST_MEMORY;
5d145d
+            goto loser;
5d145d
+        }
5d145d
+        (*find)->findstmt = findstmt;
5d145d
+        (*find)->sqlDB = sqlDB;
5d145d
+        UNLOCK_SQLITE()
5d145d
+        return CKR_OK;
5d145d
+    }
5d145d
+    error = sdb_mapSQLError(sdb_p->type, sqlerr);
5d145d
+
5d145d
+loser:
5d145d
+    if (findstmt) {
5d145d
+        sqlite3_reset(findstmt);
5d145d
+        sqlite3_finalize(findstmt);
5d145d
+    }
5d145d
+    if (sqlDB) {
5d145d
+        sdb_closeDBLocal(sdb_p, sqlDB);
5d145d
+    }
5d145d
+    UNLOCK_SQLITE()
5d145d
+    return error;
5d145d
+}
5d145d
+
5d145d
+CK_RV
5d145d
+sdb_FindObjects(SDB *sdb, SDBFind *sdbFind, CK_OBJECT_HANDLE *object,
5d145d
+                CK_ULONG arraySize, CK_ULONG *count)
5d145d
+{
5d145d
+    SDBPrivate *sdb_p = sdb->private;
5d145d
+    sqlite3_stmt *stmt = sdbFind->findstmt;
5d145d
+    int sqlerr = SQLITE_OK;
5d145d
+    int retry = 0;
5d145d
+
5d145d
+    *count = 0;
5d145d
+
5d145d
+    if (arraySize == 0) {
5d145d
+        return CKR_OK;
5d145d
+    }
5d145d
+    LOCK_SQLITE()
5d145d
+
5d145d
+    do {
5d145d
+        sqlerr = sqlite3_step(stmt);
5d145d
+        if (sqlerr == SQLITE_BUSY) {
5d145d
+            PR_Sleep(SDB_BUSY_RETRY_TIME);
5d145d
+        }
5d145d
+        if (sqlerr == SQLITE_ROW) {
5d145d
+            /* only care about the id */
5d145d
+            *object++ = sqlite3_column_int(stmt, 0);
5d145d
+            arraySize--;
5d145d
+            (*count)++;
5d145d
+        }
5d145d
+    } while (!sdb_done(sqlerr, &retry) && (arraySize > 0));
5d145d
+
5d145d
+    /* we only have some of the objects, there is probably more,
5d145d
+     * set the sqlerr to an OK value so we return CKR_OK */
5d145d
+    if (sqlerr == SQLITE_ROW && arraySize == 0) {
5d145d
+        sqlerr = SQLITE_DONE;
5d145d
+    }
5d145d
+    UNLOCK_SQLITE()
5d145d
+
5d145d
+    return sdb_mapSQLError(sdb_p->type, sqlerr);
5d145d
+}
5d145d
+
5d145d
+CK_RV
5d145d
+sdb_FindObjectsFinal(SDB *sdb, SDBFind *sdbFind)
5d145d
+{
5d145d
+    SDBPrivate *sdb_p = sdb->private;
5d145d
+    sqlite3_stmt *stmt = sdbFind->findstmt;
5d145d
+    sqlite3 *sqlDB = sdbFind->sqlDB;
5d145d
+    int sqlerr = SQLITE_OK;
5d145d
+
5d145d
+    LOCK_SQLITE()
5d145d
+    if (stmt) {
5d145d
+        sqlite3_reset(stmt);
5d145d
+        sqlerr = sqlite3_finalize(stmt);
5d145d
+    }
5d145d
+    if (sqlDB) {
5d145d
+        sdb_closeDBLocal(sdb_p, sqlDB);
5d145d
+    }
5d145d
+    PORT_Free(sdbFind);
5d145d
+
5d145d
+    UNLOCK_SQLITE()
5d145d
+    return sdb_mapSQLError(sdb_p->type, sqlerr);
5d145d
+}
5d145d
+
5d145d
+static CK_RV
5d145d
+sdb_GetValidAttributeValueNoLock(SDB *sdb, CK_OBJECT_HANDLE object_id,
5d145d
+                                 CK_ATTRIBUTE *template, CK_ULONG count)
5d145d
+{
5d145d
+    SDBPrivate *sdb_p = sdb->private;
5d145d
+    sqlite3 *sqlDB = NULL;
5d145d
+    sqlite3_stmt *stmt = NULL;
5d145d
+    const char *table = NULL;
5d145d
+    int sqlerr = SQLITE_OK;
5d145d
+    CK_RV error = CKR_OK;
5d145d
+    int found = 0;
5d145d
+    int retry = 0;
5d145d
+    unsigned int i;
5d145d
+
5d145d
+    if (count == 0) {
5d145d
+        error = CKR_OBJECT_HANDLE_INVALID;
5d145d
+        goto loser;
5d145d
+    }
5d145d
+
5d145d
+    /* open a new db if necessary */
5d145d
+    error = sdb_openDBLocal(sdb_p, &sqlDB, &table);
5d145d
+    if (error != CKR_OK) {
5d145d
+        goto loser;
5d145d
+    }
5d145d
+
5d145d
+    char *columns = NULL;
5d145d
+    for (i = 0; i < count; i++) {
5d145d
+        char *newColumns;
5d145d
+        if (columns) {
5d145d
+            newColumns = sqlite3_mprintf("%s, a%x", columns, template[i].type);
5d145d
+            sqlite3_free(columns);
5d145d
+            columns = NULL;
5d145d
+        } else {
5d145d
+            newColumns = sqlite3_mprintf("a%x", template[i].type);
5d145d
+        }
5d145d
+        if (!newColumns) {
5d145d
+            error = CKR_HOST_MEMORY;
5d145d
+            goto loser;
5d145d
+        }
5d145d
+        columns = newColumns;
5d145d
+    }
5d145d
+
5d145d
+    PORT_Assert(columns);
5d145d
+
5d145d
+    char *statement = sqlite3_mprintf("SELECT DISTINCT %s FROM %s where id=$ID LIMIT 1;",
5d145d
+                                      columns, table);
5d145d
+    sqlite3_free(columns);
5d145d
+    columns = NULL;
5d145d
+    if (!statement) {
5d145d
+        error = CKR_HOST_MEMORY;
5d145d
+        goto loser;
5d145d
+    }
5d145d
+
5d145d
+    sqlerr = sqlite3_prepare_v2(sqlDB, statement, -1, &stmt, NULL);
5d145d
+    sqlite3_free(statement);
5d145d
+    statement = NULL;
5d145d
+    if (sqlerr != SQLITE_OK) {
5d145d
+        goto loser;
5d145d
+    }
5d145d
+
5d145d
+    // NB: indices in sqlite3_bind_int are 1-indexed
5d145d
+    sqlerr = sqlite3_bind_int(stmt, 1, object_id);
5d145d
+    if (sqlerr != SQLITE_OK) {
5d145d
+        goto loser;
5d145d
+    }
5d145d
+
5d145d
+    do {
5d145d
+        sqlerr = sqlite3_step(stmt);
5d145d
+        if (sqlerr == SQLITE_BUSY) {
5d145d
+            PR_Sleep(SDB_BUSY_RETRY_TIME);
5d145d
+        }
5d145d
+        if (sqlerr == SQLITE_ROW) {
5d145d
+            PORT_Assert(!found);
5d145d
+            for (i = 0; i < count; i++) {
5d145d
+                unsigned int blobSize;
5d145d
+                const char *blobData;
5d145d
+
5d145d
+                // NB: indices in sqlite_column_{bytes,blob} are 0-indexed
5d145d
+                blobSize = sqlite3_column_bytes(stmt, i);
5d145d
+                blobData = sqlite3_column_blob(stmt, i);
5d145d
+                if (blobData == NULL) {
5d145d
+                    /* PKCS 11 requires that get attributes process all the
5d145d
+                     * attributes in the template, marking the attributes with
5d145d
+                     * issues with -1. Mark the error but continue */
5d145d
+                    template[i].ulValueLen = -1;
5d145d
+                    error = CKR_ATTRIBUTE_TYPE_INVALID;
5d145d
+                    continue;
5d145d
+                }
5d145d
+                /* If the blob equals our explicit NULL value, then the
5d145d
+                 * attribute is a NULL. */
5d145d
+                if ((blobSize == SQLITE_EXPLICIT_NULL_LEN) &&
5d145d
+                    (PORT_Memcmp(blobData, SQLITE_EXPLICIT_NULL,
5d145d
+                                 SQLITE_EXPLICIT_NULL_LEN) == 0)) {
5d145d
+                    blobSize = 0;
5d145d
+                }
5d145d
+                if (template[i].pValue) {
5d145d
+                    if (template[i].ulValueLen < blobSize) {
5d145d
+                        /* like CKR_ATTRIBUTE_TYPE_INVALID, continue processing */
5d145d
+                        template[i].ulValueLen = -1;
5d145d
+                        error = CKR_BUFFER_TOO_SMALL;
5d145d
+                        continue;
5d145d
+                    }
5d145d
+                    PORT_Memcpy(template[i].pValue, blobData, blobSize);
5d145d
+                }
5d145d
+                template[i].ulValueLen = blobSize;
5d145d
+            }
5d145d
+            found = 1;
5d145d
+        }
5d145d
+    } while (!sdb_done(sqlerr, &retry));
5d145d
+
5d145d
+    sqlite3_reset(stmt);
5d145d
+    sqlite3_finalize(stmt);
5d145d
+    stmt = NULL;
5d145d
+
5d145d
+loser:
5d145d
+    /* fix up the error if necessary */
5d145d
+    if (error == CKR_OK) {
5d145d
+        error = sdb_mapSQLError(sdb_p->type, sqlerr);
5d145d
+        if (!found && error == CKR_OK) {
5d145d
+            error = CKR_OBJECT_HANDLE_INVALID;
5d145d
+        }
5d145d
+    }
5d145d
+
5d145d
+    if (stmt) {
5d145d
+        sqlite3_reset(stmt);
5d145d
+        sqlite3_finalize(stmt);
5d145d
+    }
5d145d
+
5d145d
+    /* if we had to open a new database, free it now */
5d145d
+    if (sqlDB) {
5d145d
+        sdb_closeDBLocal(sdb_p, sqlDB);
5d145d
+    }
5d145d
+    return error;
5d145d
+}
5d145d
+
5d145d
+/* NOTE: requires sdb_p->schemaAttrs to be sorted asc. */
5d145d
+inline static PRBool
5d145d
+sdb_attributeExists(SDB *sdb, CK_ATTRIBUTE_TYPE attr)
5d145d
+{
5d145d
+    SDBPrivate *sdb_p = sdb->private;
5d145d
+    int first = 0;
5d145d
+    int last = (int)sdb_p->numSchemaAttrs - 1;
5d145d
+    while (last >= first) {
5d145d
+        int mid = first + (last - first) / 2;
5d145d
+        if (sdb_p->schemaAttrs[mid] == attr) {
5d145d
+            return PR_TRUE;
5d145d
+        }
5d145d
+        if (attr > sdb_p->schemaAttrs[mid]) {
5d145d
+            first = mid + 1;
5d145d
+        } else {
5d145d
+            last = mid - 1;
5d145d
+        }
5d145d
+    }
5d145d
+
5d145d
+    return PR_FALSE;
5d145d
+}
5d145d
+
5d145d
+CK_RV
5d145d
+sdb_GetAttributeValue(SDB *sdb, CK_OBJECT_HANDLE object_id,
5d145d
+                      CK_ATTRIBUTE *template, CK_ULONG count)
5d145d
+{
5d145d
+    CK_RV crv = CKR_OK;
5d145d
+    unsigned int tmplIdx;
5d145d
+    unsigned int resIdx = 0;
5d145d
+    unsigned int validCount = 0;
5d145d
+    unsigned int i;
5d145d
+
5d145d
+    if (count == 0) {
5d145d
+        return crv;
5d145d
+    }
5d145d
+
5d145d
+    CK_ATTRIBUTE *validTemplate;
5d145d
+    PRBool invalidExists = PR_FALSE;
5d145d
+    for (tmplIdx = 0; tmplIdx < count; tmplIdx++) {
5d145d
+        if (!sdb_attributeExists(sdb, template[tmplIdx].type)) {
5d145d
+            template[tmplIdx].ulValueLen = -1;
5d145d
+            crv = CKR_ATTRIBUTE_TYPE_INVALID;
5d145d
+            invalidExists = PR_TRUE;
5d145d
+            break;
5d145d
+        }
5d145d
+    }
5d145d
+
5d145d
+    if (!invalidExists) {
5d145d
+        validTemplate = template;
5d145d
+        validCount = count;
5d145d
+    } else {
5d145d
+        /* Create a new template containing only the valid subset of
5d145d
+         * input |template|, and query with that. */
5d145d
+        validCount = tmplIdx;
5d145d
+        validTemplate = malloc(sizeof(CK_ATTRIBUTE) * count);
5d145d
+        if (!validTemplate) {
5d145d
+            return CKR_HOST_MEMORY;
5d145d
+        }
5d145d
+        /* Copy in what we already know is valid. */
5d145d
+        for (i = 0; i < validCount; i++) {
5d145d
+            validTemplate[i] = template[i];
5d145d
+        }
5d145d
+
5d145d
+        /* tmplIdx was left at the index of the first invalid
5d145d
+         * attribute, which has been handled. We only need to
5d145d
+         * deal with the remainder. */
5d145d
+        tmplIdx++;
5d145d
+        for (; tmplIdx < count; tmplIdx++) {
5d145d
+            if (sdb_attributeExists(sdb, template[tmplIdx].type)) {
5d145d
+                validTemplate[validCount++] = template[tmplIdx];
5d145d
+            } else {
5d145d
+                template[tmplIdx].ulValueLen = -1;
5d145d
+            }
5d145d
+        }
5d145d
+    }
5d145d
+
5d145d
+    if (validCount) {
5d145d
+        LOCK_SQLITE()
5d145d
+        CK_RV crv2 = sdb_GetValidAttributeValueNoLock(sdb, object_id, validTemplate, validCount);
5d145d
+        UNLOCK_SQLITE()
5d145d
+
5d145d
+        /* If an invalid attribute was removed above, let
5d145d
+         * the caller know. Any other error from the actual
5d145d
+         * query should propogate. */
5d145d
+        crv = (crv2 == CKR_OK) ? crv : crv2;
5d145d
+    }
5d145d
+
5d145d
+    if (invalidExists) {
5d145d
+        /* Copy out valid lengths. */
5d145d
+        tmplIdx = 0;
5d145d
+        for (resIdx = 0; resIdx < validCount; resIdx++) {
5d145d
+            for (; tmplIdx < count; tmplIdx++) {
5d145d
+                if (template[tmplIdx].type != validTemplate[resIdx].type) {
5d145d
+                    continue;
5d145d
+                }
5d145d
+                template[tmplIdx].ulValueLen = validTemplate[resIdx].ulValueLen;
5d145d
+                tmplIdx++;
5d145d
+                break;
5d145d
+            }
5d145d
+        }
5d145d
+        free(validTemplate);
5d145d
+    }
5d145d
+
5d145d
+    return crv;
5d145d
+}
5d145d
+
5d145d
+static const char SET_ATTRIBUTE_CMD[] = "UPDATE %s SET %s WHERE id=$ID;";
5d145d
+CK_RV
5d145d
+sdb_SetAttributeValue(SDB *sdb, CK_OBJECT_HANDLE object_id,
5d145d
+                      const CK_ATTRIBUTE *template, CK_ULONG count)
5d145d
+{
5d145d
+    SDBPrivate *sdb_p = sdb->private;
5d145d
+    sqlite3 *sqlDB = NULL;
5d145d
+    sqlite3_stmt *stmt = NULL;
5d145d
+    char *setStr = NULL;
5d145d
+    char *newStr = NULL;
5d145d
+    int sqlerr = SQLITE_OK;
5d145d
+    int retry = 0;
5d145d
+    CK_RV error = CKR_OK;
5d145d
+    unsigned int i;
5d145d
+
5d145d
+    if ((sdb->sdb_flags & SDB_RDONLY) != 0) {
5d145d
+        return CKR_TOKEN_WRITE_PROTECTED;
5d145d
+    }
5d145d
+
5d145d
+    if (count == 0) {
5d145d
+        return CKR_OK;
5d145d
+    }
5d145d
+
5d145d
+    LOCK_SQLITE()
5d145d
+    setStr = sqlite3_mprintf("");
5d145d
+    for (i = 0; setStr && i < count; i++) {
5d145d
+        if (i == 0) {
5d145d
+            sqlite3_free(setStr);
5d145d
+            setStr = sqlite3_mprintf("a%x=$VALUE%d",
5d145d
+                                     template[i].type, i);
5d145d
+            continue;
5d145d
+        }
5d145d
+        newStr = sqlite3_mprintf("%s,a%x=$VALUE%d", setStr,
5d145d
+                                 template[i].type, i);
5d145d
+        sqlite3_free(setStr);
5d145d
+        setStr = newStr;
5d145d
+    }
5d145d
+    newStr = NULL;
5d145d
+
5d145d
+    if (setStr == NULL) {
5d145d
+        return CKR_HOST_MEMORY;
5d145d
+    }
5d145d
+    newStr = sqlite3_mprintf(SET_ATTRIBUTE_CMD, sdb_p->table, setStr);
5d145d
+    sqlite3_free(setStr);
5d145d
+    if (newStr == NULL) {
5d145d
+        UNLOCK_SQLITE()
5d145d
+        return CKR_HOST_MEMORY;
5d145d
+    }
5d145d
+    error = sdb_openDBLocal(sdb_p, &sqlDB, NULL);
5d145d
+    if (error != CKR_OK) {
5d145d
+        goto loser;
5d145d
+    }
5d145d
+    sqlerr = sqlite3_prepare_v2(sqlDB, newStr, -1, &stmt, NULL);
5d145d
+    if (sqlerr != SQLITE_OK)
5d145d
+        goto loser;
5d145d
+    for (i = 0; i < count; i++) {
5d145d
+        if (template[i].ulValueLen != 0) {
5d145d
+            sqlerr = sqlite3_bind_blob(stmt, i + 1, template[i].pValue,
5d145d
+                                       template[i].ulValueLen, SQLITE_STATIC);
5d145d
+        } else {
5d145d
+            sqlerr = sqlite3_bind_blob(stmt, i + 1, SQLITE_EXPLICIT_NULL,
5d145d
+                                       SQLITE_EXPLICIT_NULL_LEN, SQLITE_STATIC);
5d145d
+        }
5d145d
+        if (sqlerr != SQLITE_OK)
5d145d
+            goto loser;
5d145d
+    }
5d145d
+    sqlerr = sqlite3_bind_int(stmt, i + 1, object_id);
5d145d
+    if (sqlerr != SQLITE_OK)
5d145d
+        goto loser;
5d145d
+
5d145d
+    do {
5d145d
+        sqlerr = sqlite3_step(stmt);
5d145d
+        if (sqlerr == SQLITE_BUSY) {
5d145d
+            PR_Sleep(SDB_BUSY_RETRY_TIME);
5d145d
+        }
5d145d
+    } while (!sdb_done(sqlerr, &retry));
5d145d
+
5d145d
+loser:
5d145d
+    if (newStr) {
5d145d
+        sqlite3_free(newStr);
5d145d
+    }
5d145d
+    if (error == CKR_OK) {
5d145d
+        error = sdb_mapSQLError(sdb_p->type, sqlerr);
5d145d
+    }
5d145d
+
5d145d
+    if (stmt) {
5d145d
+        sqlite3_reset(stmt);
5d145d
+        sqlite3_finalize(stmt);
5d145d
+    }
5d145d
+
5d145d
+    if (sqlDB) {
5d145d
+        sdb_closeDBLocal(sdb_p, sqlDB);
5d145d
+    }
5d145d
+
5d145d
+    UNLOCK_SQLITE()
5d145d
+    return error;
5d145d
+}
5d145d
+
5d145d
+/*
5d145d
+ * check to see if a candidate object handle already exists.
5d145d
+ */
5d145d
+static PRBool
5d145d
+sdb_objectExists(SDB *sdb, CK_OBJECT_HANDLE candidate)
5d145d
+{
5d145d
+    CK_RV crv;
5d145d
+    CK_ATTRIBUTE template = { CKA_LABEL, NULL, 0 };
5d145d
+
5d145d
+    crv = sdb_GetValidAttributeValueNoLock(sdb, candidate, &template, 1);
5d145d
+    if (crv == CKR_OBJECT_HANDLE_INVALID) {
5d145d
+        return PR_FALSE;
5d145d
+    }
5d145d
+    return PR_TRUE;
5d145d
+}
5d145d
+
5d145d
+/*
5d145d
+ * if we're here, we are in a transaction, so it's safe
5d145d
+ * to examine the current state of the database
5d145d
+ */
5d145d
+static CK_OBJECT_HANDLE
5d145d
+sdb_getObjectId(SDB *sdb)
5d145d
+{
5d145d
+    CK_OBJECT_HANDLE candidate;
5d145d
+    static CK_OBJECT_HANDLE next_obj = CK_INVALID_HANDLE;
5d145d
+    int count;
5d145d
+    /*
5d145d
+     * get an initial object handle to use
5d145d
+     */
5d145d
+    if (next_obj == CK_INVALID_HANDLE) {
5d145d
+        PRTime time;
5d145d
+        time = PR_Now();
5d145d
+
5d145d
+        next_obj = (CK_OBJECT_HANDLE)(time & 0x3fffffffL);
5d145d
+    }
5d145d
+    candidate = next_obj++;
5d145d
+    /* detect that we've looped through all the handles... */
5d145d
+    for (count = 0; count < 0x40000000; count++, candidate = next_obj++) {
5d145d
+        /* mask off excess bits */
5d145d
+        candidate &= 0x3fffffff;
5d145d
+        /* if we hit zero, go to the next entry */
5d145d
+        if (candidate == CK_INVALID_HANDLE) {
5d145d
+            continue;
5d145d
+        }
5d145d
+        /* make sure we aren't already using */
5d145d
+        if (!sdb_objectExists(sdb, candidate)) {
5d145d
+            /* this one is free */
5d145d
+            return candidate;
5d145d
+        }
5d145d
+    }
5d145d
+
5d145d
+    /* no handle is free, fail */
5d145d
+    return CK_INVALID_HANDLE;
5d145d
+}
5d145d
+
5d145d
+CK_RV
5d145d
+sdb_GetNewObjectID(SDB *sdb, CK_OBJECT_HANDLE *object)
5d145d
+{
5d145d
+    CK_OBJECT_HANDLE id;
5d145d
+
5d145d
+    id = sdb_getObjectId(sdb);
5d145d
+    if (id == CK_INVALID_HANDLE) {
5d145d
+        return CKR_DEVICE_MEMORY; /* basically we ran out of resources */
5d145d
+    }
5d145d
+    *object = id;
5d145d
+    return CKR_OK;
5d145d
+}
5d145d
+
5d145d
+static const char CREATE_CMD[] = "INSERT INTO %s (id%s) VALUES($ID%s);";
5d145d
+CK_RV
5d145d
+sdb_CreateObject(SDB *sdb, CK_OBJECT_HANDLE *object_id,
5d145d
+                 const CK_ATTRIBUTE *template, CK_ULONG count)
5d145d
+{
5d145d
+    SDBPrivate *sdb_p = sdb->private;
5d145d
+    sqlite3 *sqlDB = NULL;
5d145d
+    sqlite3_stmt *stmt = NULL;
5d145d
+    char *columnStr = NULL;
5d145d
+    char *valueStr = NULL;
5d145d
+    char *newStr = NULL;
5d145d
+    int sqlerr = SQLITE_OK;
5d145d
+    CK_RV error = CKR_OK;
5d145d
+    CK_OBJECT_HANDLE this_object = CK_INVALID_HANDLE;
5d145d
+    int retry = 0;
5d145d
+    unsigned int i;
5d145d
+
5d145d
+    if ((sdb->sdb_flags & SDB_RDONLY) != 0) {
5d145d
+        return CKR_TOKEN_WRITE_PROTECTED;
5d145d
+    }
5d145d
+
5d145d
+    LOCK_SQLITE()
5d145d
+    if ((*object_id != CK_INVALID_HANDLE) &&
5d145d
+        !sdb_objectExists(sdb, *object_id)) {
5d145d
+        this_object = *object_id;
5d145d
+    } else {
5d145d
+        this_object = sdb_getObjectId(sdb);
5d145d
+    }
5d145d
+    if (this_object == CK_INVALID_HANDLE) {
5d145d
+        UNLOCK_SQLITE();
5d145d
+        return CKR_HOST_MEMORY;
5d145d
+    }
5d145d
+    columnStr = sqlite3_mprintf("");
5d145d
+    valueStr = sqlite3_mprintf("");
5d145d
+    *object_id = this_object;
5d145d
+    for (i = 0; columnStr && valueStr && i < count; i++) {
5d145d
+        newStr = sqlite3_mprintf("%s,a%x", columnStr, template[i].type);
5d145d
+        sqlite3_free(columnStr);
5d145d
+        columnStr = newStr;
5d145d
+        newStr = sqlite3_mprintf("%s,$VALUE%d", valueStr, i);
5d145d
+        sqlite3_free(valueStr);
5d145d
+        valueStr = newStr;
5d145d
+    }
5d145d
+    newStr = NULL;
5d145d
+    if ((columnStr == NULL) || (valueStr == NULL)) {
5d145d
+        if (columnStr) {
5d145d
+            sqlite3_free(columnStr);
5d145d
+        }
5d145d
+        if (valueStr) {
5d145d
+            sqlite3_free(valueStr);
5d145d
+        }
5d145d
+        UNLOCK_SQLITE()
5d145d
+        return CKR_HOST_MEMORY;
5d145d
+    }
5d145d
+    newStr = sqlite3_mprintf(CREATE_CMD, sdb_p->table, columnStr, valueStr);
5d145d
+    sqlite3_free(columnStr);
5d145d
+    sqlite3_free(valueStr);
5d145d
+    error = sdb_openDBLocal(sdb_p, &sqlDB, NULL);
5d145d
+    if (error != CKR_OK) {
5d145d
+        goto loser;
5d145d
+    }
5d145d
+    sqlerr = sqlite3_prepare_v2(sqlDB, newStr, -1, &stmt, NULL);
5d145d
+    if (sqlerr != SQLITE_OK)
5d145d
+        goto loser;
5d145d
+    sqlerr = sqlite3_bind_int(stmt, 1, *object_id);
5d145d
+    if (sqlerr != SQLITE_OK)
5d145d
+        goto loser;
5d145d
+    for (i = 0; i < count; i++) {
5d145d
+        if (template[i].ulValueLen) {
5d145d
+            sqlerr = sqlite3_bind_blob(stmt, i + 2, template[i].pValue,
5d145d
+                                       template[i].ulValueLen, SQLITE_STATIC);
5d145d
+        } else {
5d145d
+            sqlerr = sqlite3_bind_blob(stmt, i + 2, SQLITE_EXPLICIT_NULL,
5d145d
+                                       SQLITE_EXPLICIT_NULL_LEN, SQLITE_STATIC);
5d145d
+        }
5d145d
+        if (sqlerr != SQLITE_OK)
5d145d
+            goto loser;
5d145d
+    }
5d145d
+
5d145d
+    do {
5d145d
+        sqlerr = sqlite3_step(stmt);
5d145d
+        if (sqlerr == SQLITE_BUSY) {
5d145d
+            PR_Sleep(SDB_BUSY_RETRY_TIME);
5d145d
+        }
5d145d
+    } while (!sdb_done(sqlerr, &retry));
5d145d
+
5d145d
+loser:
5d145d
+    if (newStr) {
5d145d
+        sqlite3_free(newStr);
5d145d
+    }
5d145d
+    if (error == CKR_OK) {
5d145d
+        error = sdb_mapSQLError(sdb_p->type, sqlerr);
5d145d
+    }
5d145d
+
5d145d
+    if (stmt) {
5d145d
+        sqlite3_reset(stmt);
5d145d
+        sqlite3_finalize(stmt);
5d145d
+    }
5d145d
+
5d145d
+    if (sqlDB) {
5d145d
+        sdb_closeDBLocal(sdb_p, sqlDB);
5d145d
+    }
5d145d
+    UNLOCK_SQLITE()
5d145d
+
5d145d
+    return error;
5d145d
+}
5d145d
+
5d145d
+/*
5d145d
+ *  Generic destroy that can destroy metadata or objects
5d145d
+ */
5d145d
+static const char DESTROY_CMD[] = "DELETE FROM %s WHERE (id=$ID);";
5d145d
+CK_RV
5d145d
+sdb_destroyAnyObject(SDB *sdb, const char *table,
5d145d
+                     CK_OBJECT_HANDLE object_id, const char *string_id)
5d145d
+{
5d145d
+    SDBPrivate *sdb_p = sdb->private;
5d145d
+    sqlite3 *sqlDB = NULL;
5d145d
+    sqlite3_stmt *stmt = NULL;
5d145d
+    char *newStr = NULL;
5d145d
+    int sqlerr = SQLITE_OK;
5d145d
+    CK_RV error = CKR_OK;
5d145d
+    int retry = 0;
5d145d
+
5d145d
+    if ((sdb->sdb_flags & SDB_RDONLY) != 0) {
5d145d
+        return CKR_TOKEN_WRITE_PROTECTED;
5d145d
+    }
5d145d
+
5d145d
+    LOCK_SQLITE()
5d145d
+    error = sdb_openDBLocal(sdb_p, &sqlDB, NULL);
5d145d
+    if (error != CKR_OK) {
5d145d
+        goto loser;
5d145d
+    }
5d145d
+    newStr = sqlite3_mprintf(DESTROY_CMD, table);
5d145d
+    if (newStr == NULL) {
5d145d
+        error = CKR_HOST_MEMORY;
5d145d
+        goto loser;
5d145d
+    }
5d145d
+    sqlerr = sqlite3_prepare_v2(sqlDB, newStr, -1, &stmt, NULL);
5d145d
+    sqlite3_free(newStr);
5d145d
+    if (sqlerr != SQLITE_OK)
5d145d
+        goto loser;
5d145d
+    if (string_id == NULL) {
5d145d
+        sqlerr = sqlite3_bind_int(stmt, 1, object_id);
5d145d
+    } else {
5d145d
+        sqlerr = sqlite3_bind_text(stmt, 1, string_id,
5d145d
+                                   PORT_Strlen(string_id), SQLITE_STATIC);
5d145d
+    }
5d145d
+    if (sqlerr != SQLITE_OK)
5d145d
+        goto loser;
5d145d
+
5d145d
+    do {
5d145d
+        sqlerr = sqlite3_step(stmt);
5d145d
+        if (sqlerr == SQLITE_BUSY) {
5d145d
+            PR_Sleep(SDB_BUSY_RETRY_TIME);
5d145d
+        }
5d145d
+    } while (!sdb_done(sqlerr, &retry));
5d145d
+
5d145d
+loser:
5d145d
+    if (error == CKR_OK) {
5d145d
+        error = sdb_mapSQLError(sdb_p->type, sqlerr);
5d145d
+    }
5d145d
+
5d145d
+    if (stmt) {
5d145d
+        sqlite3_reset(stmt);
5d145d
+        sqlite3_finalize(stmt);
5d145d
+    }
5d145d
+
5d145d
+    if (sqlDB) {
5d145d
+        sdb_closeDBLocal(sdb_p, sqlDB);
5d145d
+    }
5d145d
+
5d145d
+    UNLOCK_SQLITE()
5d145d
+    return error;
5d145d
+}
5d145d
+
5d145d
+CK_RV
5d145d
+sdb_DestroyObject(SDB *sdb, CK_OBJECT_HANDLE object_id)
5d145d
+{
5d145d
+    SDBPrivate *sdb_p = sdb->private;
5d145d
+    return sdb_destroyAnyObject(sdb, sdb_p->table, object_id, NULL);
5d145d
+}
5d145d
+
5d145d
+CK_RV
5d145d
+sdb_DestroyMetaData(SDB *sdb, const char *id)
5d145d
+{
5d145d
+    return sdb_destroyAnyObject(sdb, "metaData", 0, id);
5d145d
+}
5d145d
+
5d145d
+static const char BEGIN_CMD[] = "BEGIN IMMEDIATE TRANSACTION;";
5d145d
+
5d145d
+/*
5d145d
+ * start a transaction.
5d145d
+ *
5d145d
+ * We need to open a new database, then store that new database into
5d145d
+ * the private data structure. We open the database first, then use locks
5d145d
+ * to protect storing the data to prevent deadlocks.
5d145d
+ */
5d145d
+CK_RV
5d145d
+sdb_Begin(SDB *sdb)
5d145d
+{
5d145d
+    SDBPrivate *sdb_p = sdb->private;
5d145d
+    sqlite3 *sqlDB = NULL;
5d145d
+    sqlite3_stmt *stmt = NULL;
5d145d
+    int sqlerr = SQLITE_OK;
5d145d
+    CK_RV error = CKR_OK;
5d145d
+    int retry = 0;
5d145d
+
5d145d
+    if ((sdb->sdb_flags & SDB_RDONLY) != 0) {
5d145d
+        return CKR_TOKEN_WRITE_PROTECTED;
5d145d
+    }
5d145d
+
5d145d
+    LOCK_SQLITE()
5d145d
+
5d145d
+    /* get a new version that we will use for the entire transaction */
5d145d
+    sqlerr = sdb_openDB(sdb_p->sqlDBName, &sqlDB, SDB_RDWR);
5d145d
+    if (sqlerr != SQLITE_OK) {
5d145d
+        goto loser;
5d145d
+    }
5d145d
+
5d145d
+    sqlerr = sqlite3_prepare_v2(sqlDB, BEGIN_CMD, -1, &stmt, NULL);
5d145d
+
5d145d
+    do {
5d145d
+        sqlerr = sqlite3_step(stmt);
5d145d
+        if (sqlerr == SQLITE_BUSY) {
5d145d
+            PR_Sleep(SDB_BUSY_RETRY_TIME);
5d145d
+        }
5d145d
+        /* don't retry BEGIN transaction*/
5d145d
+        retry = 0;
5d145d
+    } while (!sdb_done(sqlerr, &retry));
5d145d
+
5d145d
+    if (stmt) {
5d145d
+        sqlite3_reset(stmt);
5d145d
+        sqlite3_finalize(stmt);
5d145d
+    }
5d145d
+
5d145d
+loser:
5d145d
+    error = sdb_mapSQLError(sdb_p->type, sqlerr);
5d145d
+
5d145d
+    /* we are starting a new transaction,
5d145d
+     * and if we succeeded, then save this database for the rest of
5d145d
+     * our transaction */
5d145d
+    if (error == CKR_OK) {
5d145d
+        /* we hold a 'BEGIN TRANSACTION' and a sdb_p->lock. At this point
5d145d
+         * sdb_p->sqlXactDB MUST be null */
5d145d
+        PR_EnterMonitor(sdb_p->dbMon);
5d145d
+        PORT_Assert(sdb_p->sqlXactDB == NULL);
5d145d
+        sdb_p->sqlXactDB = sqlDB;
5d145d
+        sdb_p->sqlXactThread = PR_GetCurrentThread();
5d145d
+        PR_ExitMonitor(sdb_p->dbMon);
5d145d
+    } else {
5d145d
+        /* we failed to start our transaction,
5d145d
+         * free any databases we opened. */
5d145d
+        if (sqlDB) {
5d145d
+            sqlite3_close(sqlDB);
5d145d
+        }
5d145d
+    }
5d145d
+
5d145d
+    UNLOCK_SQLITE()
5d145d
+    return error;
5d145d
+}
5d145d
+
5d145d
+/*
5d145d
+ * Complete a transaction. Basically undo everything we did in begin.
5d145d
+ * There are 2 flavors Abort and Commit. Basically the only differerence between
5d145d
+ * these 2 are what the database will show. (no change in to former, change in
5d145d
+ * the latter).
5d145d
+ */
5d145d
+static CK_RV
5d145d
+sdb_complete(SDB *sdb, const char *cmd)
5d145d
+{
5d145d
+    SDBPrivate *sdb_p = sdb->private;
5d145d
+    sqlite3 *sqlDB = NULL;
5d145d
+    sqlite3_stmt *stmt = NULL;
5d145d
+    int sqlerr = SQLITE_OK;
5d145d
+    CK_RV error = CKR_OK;
5d145d
+    int retry = 0;
5d145d
+
5d145d
+    if ((sdb->sdb_flags & SDB_RDONLY) != 0) {
5d145d
+        return CKR_TOKEN_WRITE_PROTECTED;
5d145d
+    }
5d145d
+
5d145d
+    /* We must have a transation database, or we shouldn't have arrived here */
5d145d
+    PR_EnterMonitor(sdb_p->dbMon);
5d145d
+    PORT_Assert(sdb_p->sqlXactDB);
5d145d
+    if (sdb_p->sqlXactDB == NULL) {
5d145d
+        PR_ExitMonitor(sdb_p->dbMon);
5d145d
+        return CKR_GENERAL_ERROR; /* shouldn't happen */
5d145d
+    }
5d145d
+    PORT_Assert(sdb_p->sqlXactThread == PR_GetCurrentThread());
5d145d
+    if (sdb_p->sqlXactThread != PR_GetCurrentThread()) {
5d145d
+        PR_ExitMonitor(sdb_p->dbMon);
5d145d
+        return CKR_GENERAL_ERROR; /* shouldn't happen */
5d145d
+    }
5d145d
+    sqlDB = sdb_p->sqlXactDB;
5d145d
+    sdb_p->sqlXactDB = NULL; /* no one else can get to this DB,
5d145d
+                              * safe to unlock */
5d145d
+    sdb_p->sqlXactThread = NULL;
5d145d
+    PR_ExitMonitor(sdb_p->dbMon);
5d145d
+
5d145d
+    sqlerr = sqlite3_prepare_v2(sqlDB, cmd, -1, &stmt, NULL);
5d145d
+
5d145d
+    do {
5d145d
+        sqlerr = sqlite3_step(stmt);
5d145d
+        if (sqlerr == SQLITE_BUSY) {
5d145d
+            PR_Sleep(SDB_BUSY_RETRY_TIME);
5d145d
+        }
5d145d
+    } while (!sdb_done(sqlerr, &retry));
5d145d
+
5d145d
+    /* Pending BEGIN TRANSACTIONS Can move forward at this point. */
5d145d
+
5d145d
+    if (stmt) {
5d145d
+        sqlite3_reset(stmt);
5d145d
+        sqlite3_finalize(stmt);
5d145d
+    }
5d145d
+
5d145d
+    /* we we have a cached DB image, update it as well */
5d145d
+    if (sdb_p->cacheTable) {
5d145d
+        PR_EnterMonitor(sdb_p->dbMon);
5d145d
+        sdb_updateCache(sdb_p);
5d145d
+        PR_ExitMonitor(sdb_p->dbMon);
5d145d
+    }
5d145d
+
5d145d
+    error = sdb_mapSQLError(sdb_p->type, sqlerr);
5d145d
+
5d145d
+    /* We just finished a transaction.
5d145d
+     * Free the database, and remove it from the list */
5d145d
+    sqlite3_close(sqlDB);
5d145d
+
5d145d
+    return error;
5d145d
+}
5d145d
+
5d145d
+static const char COMMIT_CMD[] = "COMMIT TRANSACTION;";
5d145d
+CK_RV
5d145d
+sdb_Commit(SDB *sdb)
5d145d
+{
5d145d
+    CK_RV crv;
5d145d
+    LOCK_SQLITE()
5d145d
+    crv = sdb_complete(sdb, COMMIT_CMD);
5d145d
+    UNLOCK_SQLITE()
5d145d
+    return crv;
5d145d
+}
5d145d
+
5d145d
+static const char ROLLBACK_CMD[] = "ROLLBACK TRANSACTION;";
5d145d
+CK_RV
5d145d
+sdb_Abort(SDB *sdb)
5d145d
+{
5d145d
+    CK_RV crv;
5d145d
+    LOCK_SQLITE()
5d145d
+    crv = sdb_complete(sdb, ROLLBACK_CMD);
5d145d
+    UNLOCK_SQLITE()
5d145d
+    return crv;
5d145d
+}
5d145d
+
5d145d
+static int tableExists(sqlite3 *sqlDB, const char *tableName);
5d145d
+
5d145d
+static const char GET_PW_CMD[] = "SELECT ALL * FROM metaData WHERE id=$ID;";
5d145d
+CK_RV
5d145d
+sdb_GetMetaData(SDB *sdb, const char *id, SECItem *item1, SECItem *item2)
5d145d
+{
5d145d
+    SDBPrivate *sdb_p = sdb->private;
5d145d
+    sqlite3 *sqlDB = sdb_p->sqlXactDB;
5d145d
+    sqlite3_stmt *stmt = NULL;
5d145d
+    int sqlerr = SQLITE_OK;
5d145d
+    CK_RV error = CKR_OK;
5d145d
+    int found = 0;
5d145d
+    int retry = 0;
5d145d
+
5d145d
+    LOCK_SQLITE()
5d145d
+    error = sdb_openDBLocal(sdb_p, &sqlDB, NULL);
5d145d
+    if (error != CKR_OK) {
5d145d
+        goto loser;
5d145d
+    }
5d145d
+
5d145d
+    /* handle 'test' versions of the sqlite db */
5d145d
+    sqlerr = sqlite3_prepare_v2(sqlDB, GET_PW_CMD, -1, &stmt, NULL);
5d145d
+    /* Sigh, if we created a new table since we opened the database,
5d145d
+     * the database handle will not see the new table, we need to close this
5d145d
+     * database and reopen it. This is safe because we are holding the lock
5d145d
+     * still. */
5d145d
+    if (sqlerr == SQLITE_SCHEMA) {
5d145d
+        sqlerr = sdb_reopenDBLocal(sdb_p, &sqlDB);
5d145d
+        if (sqlerr != SQLITE_OK) {
5d145d
+            goto loser;
5d145d
+        }
5d145d
+        sqlerr = sqlite3_prepare_v2(sqlDB, GET_PW_CMD, -1, &stmt, NULL);
5d145d
+    }
5d145d
+    if (sqlerr != SQLITE_OK)
5d145d
+        goto loser;
5d145d
+    sqlerr = sqlite3_bind_text(stmt, 1, id, PORT_Strlen(id), SQLITE_STATIC);
5d145d
+    do {
5d145d
+        sqlerr = sqlite3_step(stmt);
5d145d
+        if (sqlerr == SQLITE_BUSY) {
5d145d
+            PR_Sleep(SDB_BUSY_RETRY_TIME);
5d145d
+        }
5d145d
+        if (sqlerr == SQLITE_ROW) {
5d145d
+            const char *blobData;
5d145d
+            unsigned int len = item1->len;
5d145d
+            item1->len = sqlite3_column_bytes(stmt, 1);
5d145d
+            if (item1->len > len) {
5d145d
+                error = CKR_BUFFER_TOO_SMALL;
5d145d
+                continue;
5d145d
+            }
5d145d
+            blobData = sqlite3_column_blob(stmt, 1);
5d145d
+            PORT_Memcpy(item1->data, blobData, item1->len);
5d145d
+            if (item2) {
5d145d
+                len = item2->len;
5d145d
+                item2->len = sqlite3_column_bytes(stmt, 2);
5d145d
+                if (item2->len > len) {
5d145d
+                    error = CKR_BUFFER_TOO_SMALL;
5d145d
+                    continue;
5d145d
+                }
5d145d
+                blobData = sqlite3_column_blob(stmt, 2);
5d145d
+                PORT_Memcpy(item2->data, blobData, item2->len);
5d145d
+            }
5d145d
+            found = 1;
5d145d
+        }
5d145d
+    } while (!sdb_done(sqlerr, &retry));
5d145d
+
5d145d
+loser:
5d145d
+    /* fix up the error if necessary */
5d145d
+    if (error == CKR_OK) {
5d145d
+        error = sdb_mapSQLError(sdb_p->type, sqlerr);
5d145d
+        if (!found && error == CKR_OK) {
5d145d
+            error = CKR_OBJECT_HANDLE_INVALID;
5d145d
+        }
5d145d
+    }
5d145d
+
5d145d
+    if (stmt) {
5d145d
+        sqlite3_reset(stmt);
5d145d
+        sqlite3_finalize(stmt);
5d145d
+    }
5d145d
+
5d145d
+    if (sqlDB) {
5d145d
+        sdb_closeDBLocal(sdb_p, sqlDB);
5d145d
+    }
5d145d
+    UNLOCK_SQLITE()
5d145d
+
5d145d
+    return error;
5d145d
+}
5d145d
+
5d145d
+static const char PW_CREATE_TABLE_CMD[] =
5d145d
+    "CREATE TABLE metaData (id PRIMARY KEY UNIQUE ON CONFLICT REPLACE, item1, item2);";
5d145d
+static const char PW_CREATE_CMD[] =
5d145d
+    "INSERT INTO metaData (id,item1,item2) VALUES($ID,$ITEM1,$ITEM2);";
5d145d
+static const char MD_CREATE_CMD[] =
5d145d
+    "INSERT INTO metaData (id,item1) VALUES($ID,$ITEM1);";
5d145d
+
5d145d
+CK_RV
5d145d
+sdb_PutMetaData(SDB *sdb, const char *id, const SECItem *item1,
5d145d
+                const SECItem *item2)
5d145d
+{
5d145d
+    SDBPrivate *sdb_p = sdb->private;
5d145d
+    sqlite3 *sqlDB = sdb_p->sqlXactDB;
5d145d
+    sqlite3_stmt *stmt = NULL;
5d145d
+    int sqlerr = SQLITE_OK;
5d145d
+    CK_RV error = CKR_OK;
5d145d
+    int retry = 0;
5d145d
+    const char *cmd = PW_CREATE_CMD;
5d145d
+
5d145d
+    if ((sdb->sdb_flags & SDB_RDONLY) != 0) {
5d145d
+        return CKR_TOKEN_WRITE_PROTECTED;
5d145d
+    }
5d145d
+
5d145d
+    LOCK_SQLITE()
5d145d
+    error = sdb_openDBLocal(sdb_p, &sqlDB, NULL);
5d145d
+    if (error != CKR_OK) {
5d145d
+        goto loser;
5d145d
+    }
5d145d
+
5d145d
+    if (!tableExists(sqlDB, "metaData")) {
5d145d
+        sqlerr = sqlite3_exec(sqlDB, PW_CREATE_TABLE_CMD, NULL, 0, NULL);
5d145d
+        if (sqlerr != SQLITE_OK)
5d145d
+            goto loser;
5d145d
+    }
5d145d
+    if (item2 == NULL) {
5d145d
+        cmd = MD_CREATE_CMD;
5d145d
+    }
5d145d
+    sqlerr = sqlite3_prepare_v2(sqlDB, cmd, -1, &stmt, NULL);
5d145d
+    if (sqlerr != SQLITE_OK)
5d145d
+        goto loser;
5d145d
+    sqlerr = sqlite3_bind_text(stmt, 1, id, PORT_Strlen(id), SQLITE_STATIC);
5d145d
+    if (sqlerr != SQLITE_OK)
5d145d
+        goto loser;
5d145d
+    sqlerr = sqlite3_bind_blob(stmt, 2, item1->data, item1->len, SQLITE_STATIC);
5d145d
+    if (sqlerr != SQLITE_OK)
5d145d
+        goto loser;
5d145d
+    if (item2) {
5d145d
+        sqlerr = sqlite3_bind_blob(stmt, 3, item2->data,
5d145d
+                                   item2->len, SQLITE_STATIC);
5d145d
+        if (sqlerr != SQLITE_OK)
5d145d
+            goto loser;
5d145d
+    }
5d145d
+
5d145d
+    do {
5d145d
+        sqlerr = sqlite3_step(stmt);
5d145d
+        if (sqlerr == SQLITE_BUSY) {
5d145d
+            PR_Sleep(SDB_BUSY_RETRY_TIME);
5d145d
+        }
5d145d
+    } while (!sdb_done(sqlerr, &retry));
5d145d
+
5d145d
+loser:
5d145d
+    /* fix up the error if necessary */
5d145d
+    if (error == CKR_OK) {
5d145d
+        error = sdb_mapSQLError(sdb_p->type, sqlerr);
5d145d
+    }
5d145d
+
5d145d
+    if (stmt) {
5d145d
+        sqlite3_reset(stmt);
5d145d
+        sqlite3_finalize(stmt);
5d145d
+    }
5d145d
+
5d145d
+    if (sqlDB) {
5d145d
+        sdb_closeDBLocal(sdb_p, sqlDB);
5d145d
+    }
5d145d
+    UNLOCK_SQLITE()
5d145d
+
5d145d
+    return error;
5d145d
+}
5d145d
+
5d145d
+static const char RESET_CMD[] = "DELETE FROM %s;";
5d145d
+CK_RV
5d145d
+sdb_Reset(SDB *sdb)
5d145d
+{
5d145d
+    SDBPrivate *sdb_p = sdb->private;
5d145d
+    sqlite3 *sqlDB = NULL;
5d145d
+    char *newStr;
5d145d
+    int sqlerr = SQLITE_OK;
5d145d
+    CK_RV error = CKR_OK;
5d145d
+
5d145d
+    /* only Key databases can be reset */
5d145d
+    if (sdb_p->type != SDB_KEY) {
5d145d
+        return CKR_OBJECT_HANDLE_INVALID;
5d145d
+    }
5d145d
+
5d145d
+    LOCK_SQLITE()
5d145d
+    error = sdb_openDBLocal(sdb_p, &sqlDB, NULL);
5d145d
+    if (error != CKR_OK) {
5d145d
+        goto loser;
5d145d
+    }
5d145d
+
5d145d
+    if (tableExists(sqlDB, sdb_p->table)) {
5d145d
+        /* delete the contents of the key table */
5d145d
+        newStr = sqlite3_mprintf(RESET_CMD, sdb_p->table);
5d145d
+        if (newStr == NULL) {
5d145d
+            error = CKR_HOST_MEMORY;
5d145d
+            goto loser;
5d145d
+        }
5d145d
+        sqlerr = sqlite3_exec(sqlDB, newStr, NULL, 0, NULL);
5d145d
+        sqlite3_free(newStr);
5d145d
+
5d145d
+        if (sqlerr != SQLITE_OK)
5d145d
+            goto loser;
5d145d
+    }
5d145d
+
5d145d
+    /* delete the password entry table */
5d145d
+    sqlerr = sqlite3_exec(sqlDB, "DROP TABLE IF EXISTS metaData;",
5d145d
+                          NULL, 0, NULL);
5d145d
+
5d145d
+loser:
5d145d
+    /* fix up the error if necessary */
5d145d
+    if (error == CKR_OK) {
5d145d
+        error = sdb_mapSQLError(sdb_p->type, sqlerr);
5d145d
+    }
5d145d
+
5d145d
+    if (sqlDB) {
5d145d
+        sdb_closeDBLocal(sdb_p, sqlDB);
5d145d
+    }
5d145d
+
5d145d
+    UNLOCK_SQLITE()
5d145d
+    return error;
5d145d
+}
5d145d
+
5d145d
+CK_RV
5d145d
+sdb_Close(SDB *sdb)
5d145d
+{
5d145d
+    SDBPrivate *sdb_p = sdb->private;
5d145d
+    int sqlerr = SQLITE_OK;
5d145d
+    sdbDataType type = sdb_p->type;
5d145d
+
5d145d
+    sqlerr = sqlite3_close(sdb_p->sqlReadDB);
5d145d
+    PORT_Free(sdb_p->sqlDBName);
5d145d
+    if (sdb_p->cacheTable) {
5d145d
+        sqlite3_free(sdb_p->cacheTable);
5d145d
+    }
5d145d
+    if (sdb_p->dbMon) {
5d145d
+        PR_DestroyMonitor(sdb_p->dbMon);
5d145d
+    }
5d145d
+    free(sdb_p->schemaAttrs);
5d145d
+    free(sdb_p);
5d145d
+    free(sdb);
5d145d
+    return sdb_mapSQLError(type, sqlerr);
5d145d
+}
5d145d
+
5d145d
+/*
5d145d
+ * functions to support open
5d145d
+ */
5d145d
+
5d145d
+static const char CHECK_TABLE_CMD[] = "SELECT ALL * FROM %s LIMIT 0;";
5d145d
+
5d145d
+/* return 1 if sqlDB contains table 'tableName */
5d145d
+static int
5d145d
+tableExists(sqlite3 *sqlDB, const char *tableName)
5d145d
+{
5d145d
+    char *cmd = sqlite3_mprintf(CHECK_TABLE_CMD, tableName);
5d145d
+    int sqlerr = SQLITE_OK;
5d145d
+
5d145d
+    if (cmd == NULL) {
5d145d
+        return 0;
5d145d
+    }
5d145d
+
5d145d
+    sqlerr = sqlite3_exec(sqlDB, cmd, NULL, 0, 0);
5d145d
+    sqlite3_free(cmd);
5d145d
+
5d145d
+    return (sqlerr == SQLITE_OK) ? 1 : 0;
5d145d
+}
5d145d
+
5d145d
+void
5d145d
+sdb_SetForkState(PRBool forked)
5d145d
+{
5d145d
+    /* XXXright now this is a no-op. The global fork state in the softokn3
5d145d
+     * shared library is already taken care of at the PKCS#11 level.
5d145d
+     * If and when we add fork state to the sqlite shared library and extern
5d145d
+     * interface, we will need to set it and reset it from here */
5d145d
+}
5d145d
+
5d145d
+static int
5d145d
+sdb_attributeComparator(const void *a, const void *b)
5d145d
+{
5d145d
+    if (*(CK_ATTRIBUTE_TYPE *)a < *(CK_ATTRIBUTE_TYPE *)b) {
5d145d
+        return -1;
5d145d
+    }
5d145d
+    if (*(CK_ATTRIBUTE_TYPE *)a > *(CK_ATTRIBUTE_TYPE *)b) {
5d145d
+        return 1;
5d145d
+    }
5d145d
+    return 0;
5d145d
+}
5d145d
+
5d145d
+/*
5d145d
+ * initialize a single database
5d145d
+ */
5d145d
+static const char INIT_CMD[] =
5d145d
+    "CREATE TABLE %s (id PRIMARY KEY UNIQUE ON CONFLICT ABORT%s)";
5d145d
+
5d145d
+CK_RV
5d145d
+sdb_init(char *dbname, char *table, sdbDataType type, int *inUpdate,
5d145d
+         int *newInit, int inFlags, PRUint32 accessOps, SDB **pSdb)
5d145d
+{
5d145d
+    int i;
5d145d
+    char *initStr = NULL;
5d145d
+    char *newStr;
5d145d
+    char *queryStr = NULL;
5d145d
+    int inTransaction = 0;
5d145d
+    SDB *sdb = NULL;
5d145d
+    SDBPrivate *sdb_p = NULL;
5d145d
+    sqlite3 *sqlDB = NULL;
5d145d
+    int sqlerr = SQLITE_OK;
5d145d
+    CK_RV error = CKR_OK;
5d145d
+    char *cacheTable = NULL;
5d145d
+    PRIntervalTime now = 0;
5d145d
+    char *env;
5d145d
+    PRBool enableCache = PR_FALSE;
5d145d
+    PRBool checkFSType = PR_FALSE;
5d145d
+    PRBool measureSpeed = PR_FALSE;
5d145d
+    PRBool create;
5d145d
+    int flags = inFlags & 0x7;
5d145d
+
5d145d
+    *pSdb = NULL;
5d145d
+    *inUpdate = 0;
5d145d
+
5d145d
+    /* sqlite3 doesn't have a flag to specify that we want to
5d145d
+     * open the database read only. If the db doesn't exist,
5d145d
+     * sqlite3 will always create it.
5d145d
+     */
5d145d
+    LOCK_SQLITE();
5d145d
+    create = (_NSSUTIL_Access(dbname, PR_ACCESS_EXISTS) != PR_SUCCESS);
5d145d
+    if ((flags == SDB_RDONLY) && create) {
5d145d
+        error = sdb_mapSQLError(type, SQLITE_CANTOPEN);
5d145d
+        goto loser;
5d145d
+    }
5d145d
+    sqlerr = sdb_openDB(dbname, &sqlDB, flags);
5d145d
+    if (sqlerr != SQLITE_OK) {
5d145d
+        error = sdb_mapSQLError(type, sqlerr);
5d145d
+        goto loser;
5d145d
+    }
5d145d
+
5d145d
+    /*
5d145d
+     * SQL created the file, but it doesn't set appropriate modes for
5d145d
+     * a database.
5d145d
+     *
5d145d
+     * NO NSPR call for chmod? :(
5d145d
+     */
5d145d
+    if (create && sdb_chmod(dbname, 0600) != 0) {
5d145d
+        error = sdb_mapSQLError(type, SQLITE_CANTOPEN);
5d145d
+        goto loser;
5d145d
+    }
5d145d
+
5d145d
+    if (flags != SDB_RDONLY) {
5d145d
+        sqlerr = sqlite3_exec(sqlDB, BEGIN_CMD, NULL, 0, NULL);
5d145d
+        if (sqlerr != SQLITE_OK) {
5d145d
+            error = sdb_mapSQLError(type, sqlerr);
5d145d
+            goto loser;
5d145d
+        }
5d145d
+        inTransaction = 1;
5d145d
+    }
5d145d
+    if (!tableExists(sqlDB, table)) {
5d145d
+        *newInit = 1;
5d145d
+        if (flags != SDB_CREATE) {
5d145d
+            error = sdb_mapSQLError(type, SQLITE_CANTOPEN);
5d145d
+            goto loser;
5d145d
+        }
5d145d
+        initStr = sqlite3_mprintf("");
5d145d
+        for (i = 0; initStr && i < known_attributes_size; i++) {
5d145d
+            newStr = sqlite3_mprintf("%s, a%x", initStr, known_attributes[i]);
5d145d
+            sqlite3_free(initStr);
5d145d
+            initStr = newStr;
5d145d
+        }
5d145d
+        if (initStr == NULL) {
5d145d
+            error = CKR_HOST_MEMORY;
5d145d
+            goto loser;
5d145d
+        }
5d145d
+
5d145d
+        newStr = sqlite3_mprintf(INIT_CMD, table, initStr);
5d145d
+        sqlite3_free(initStr);
5d145d
+        if (newStr == NULL) {
5d145d
+            error = CKR_HOST_MEMORY;
5d145d
+            goto loser;
5d145d
+        }
5d145d
+        sqlerr = sqlite3_exec(sqlDB, newStr, NULL, 0, NULL);
5d145d
+        sqlite3_free(newStr);
5d145d
+        if (sqlerr != SQLITE_OK) {
5d145d
+            error = sdb_mapSQLError(type, sqlerr);
5d145d
+            goto loser;
5d145d
+        }
5d145d
+
5d145d
+        newStr = sqlite3_mprintf(CREATE_ISSUER_INDEX_CMD, table);
5d145d
+        if (newStr == NULL) {
5d145d
+            error = CKR_HOST_MEMORY;
5d145d
+            goto loser;
5d145d
+        }
5d145d
+        sqlerr = sqlite3_exec(sqlDB, newStr, NULL, 0, NULL);
5d145d
+        sqlite3_free(newStr);
5d145d
+        if (sqlerr != SQLITE_OK) {
5d145d
+            error = sdb_mapSQLError(type, sqlerr);
5d145d
+            goto loser;
5d145d
+        }
5d145d
+
5d145d
+        newStr = sqlite3_mprintf(CREATE_SUBJECT_INDEX_CMD, table);
5d145d
+        if (newStr == NULL) {
5d145d
+            error = CKR_HOST_MEMORY;
5d145d
+            goto loser;
5d145d
+        }
5d145d
+        sqlerr = sqlite3_exec(sqlDB, newStr, NULL, 0, NULL);
5d145d
+        sqlite3_free(newStr);
5d145d
+        if (sqlerr != SQLITE_OK) {
5d145d
+            error = sdb_mapSQLError(type, sqlerr);
5d145d
+            goto loser;
5d145d
+        }
5d145d
+
5d145d
+        newStr = sqlite3_mprintf(CREATE_LABEL_INDEX_CMD, table);
5d145d
+        if (newStr == NULL) {
5d145d
+            error = CKR_HOST_MEMORY;
5d145d
+            goto loser;
5d145d
+        }
5d145d
+        sqlerr = sqlite3_exec(sqlDB, newStr, NULL, 0, NULL);
5d145d
+        sqlite3_free(newStr);
5d145d
+        if (sqlerr != SQLITE_OK) {
5d145d
+            error = sdb_mapSQLError(type, sqlerr);
5d145d
+            goto loser;
5d145d
+        }
5d145d
+
5d145d
+        newStr = sqlite3_mprintf(CREATE_ID_INDEX_CMD, table);
5d145d
+        if (newStr == NULL) {
5d145d
+            error = CKR_HOST_MEMORY;
5d145d
+            goto loser;
5d145d
+        }
5d145d
+        sqlerr = sqlite3_exec(sqlDB, newStr, NULL, 0, NULL);
5d145d
+        sqlite3_free(newStr);
5d145d
+        if (sqlerr != SQLITE_OK) {
5d145d
+            error = sdb_mapSQLError(type, sqlerr);
5d145d
+            goto loser;
5d145d
+        }
5d145d
+    }
5d145d
+    /*
5d145d
+     * detect the case where we have created the database, but have
5d145d
+     * not yet updated it.
5d145d
+     *
5d145d
+     * We only check the Key database because only the key database has
5d145d
+     * a metaData table. The metaData table is created when a password
5d145d
+     * is set, or in the case of update, when a password is supplied.
5d145d
+     * If no key database exists, then the update would have happened immediately
5d145d
+     * on noticing that the cert database didn't exist (see newInit set above).
5d145d
+     */
5d145d
+    if (type == SDB_KEY && !tableExists(sqlDB, "metaData")) {
5d145d
+        *newInit = 1;
5d145d
+    }
5d145d
+
5d145d
+    /* access to network filesystems are significantly slower than local ones
5d145d
+     * for database operations. In those cases we need to create a cached copy
5d145d
+     * of the database in a temporary location on the local disk. SQLITE
5d145d
+     * already provides a way to create a temporary table and initialize it,
5d145d
+     * so we use it for the cache (see sdb_buildCache for how it's done).*/
5d145d
+
5d145d
+    /*
5d145d
+     * we decide whether or not to use the cache based on the following input.
5d145d
+     *
5d145d
+     * NSS_SDB_USE_CACHE environment variable is set to anything other than
5d145d
+     *   "yes" or "no" (for instance, "auto"): NSS will measure the performance
5d145d
+     *   of access to the temp database versus the access to the user's
5d145d
+     *   passed-in database location. If the temp database location is
5d145d
+     *   "significantly" faster we will use the cache.
5d145d
+     *
5d145d
+     * NSS_SDB_USE_CACHE environment variable is nonexistent or set to "no":
5d145d
+     *   cache will not be used.
5d145d
+     *
5d145d
+     * NSS_SDB_USE_CACHE environment variable is set to "yes": cache will
5d145d
+     *   always be used.
5d145d
+     *
5d145d
+     * It is expected that most applications will not need this feature, and
5d145d
+     * thus it is disabled by default.
5d145d
+     */
5d145d
+
5d145d
+    env = PR_GetEnvSecure("NSS_SDB_USE_CACHE");
5d145d
+
5d145d
+    /* Variables enableCache, checkFSType, measureSpeed are PR_FALSE by default,
5d145d
+     * which is the expected behavior for NSS_SDB_USE_CACHE="no".
5d145d
+     * We don't need to check for "no" here. */
5d145d
+    if (!env) {
5d145d
+        /* By default, with no variable set, we avoid expensive measuring for
5d145d
+         * most FS types. We start with inexpensive FS type checking, and
5d145d
+         * might perform measuring for some types. */
5d145d
+        checkFSType = PR_TRUE;
5d145d
+    } else if (PORT_Strcasecmp(env, "yes") == 0) {
5d145d
+        enableCache = PR_TRUE;
5d145d
+    } else if (PORT_Strcasecmp(env, "no") != 0) { /* not "no" => "auto" */
5d145d
+        measureSpeed = PR_TRUE;
5d145d
+    }
5d145d
+
5d145d
+    if (checkFSType) {
5d145d
+#if defined(LINUX) && !defined(ANDROID)
5d145d
+        struct statfs statfs_s;
5d145d
+        if (statfs(dbname, &statfs_s) == 0) {
5d145d
+            switch (statfs_s.f_type) {
5d145d
+                case SMB_SUPER_MAGIC:
5d145d
+                case 0xff534d42: /* CIFS_MAGIC_NUMBER */
5d145d
+                case NFS_SUPER_MAGIC:
5d145d
+                    /* We assume these are slow. */
5d145d
+                    enableCache = PR_TRUE;
5d145d
+                    break;
5d145d
+                case CODA_SUPER_MAGIC:
5d145d
+                case 0x65735546: /* FUSE_SUPER_MAGIC */
5d145d
+                case NCP_SUPER_MAGIC:
5d145d
+                    /* It's uncertain if this FS is fast or slow.
5d145d
+                     * It seems reasonable to perform slow measuring for users
5d145d
+                     * with questionable FS speed. */
5d145d
+                    measureSpeed = PR_TRUE;
5d145d
+                    break;
5d145d
+                case AFS_SUPER_MAGIC: /* Already implements caching. */
5d145d
+                default:
5d145d
+                    break;
5d145d
+            }
5d145d
+        }
5d145d
+#endif
5d145d
+    }
5d145d
+
5d145d
+    if (measureSpeed) {
5d145d
+        char *tempDir = NULL;
5d145d
+        PRUint32 tempOps = 0;
5d145d
+        /*
5d145d
+         *  Use PR_Access to determine how expensive it
5d145d
+         * is to check for the existance of a local file compared to the same
5d145d
+         * check in the temp directory. If the temp directory is faster, cache
5d145d
+         * the database there. */
5d145d
+        tempDir = sdb_getTempDir(sqlDB);
5d145d
+        if (tempDir) {
5d145d
+            tempOps = sdb_measureAccess(tempDir);
5d145d
+            PORT_Free(tempDir);
5d145d
+
5d145d
+            /* There is a cost to continually copying the database.
5d145d
+             * Account for that cost  with the arbitrary factor of 10 */
5d145d
+            enableCache = (PRBool)(tempOps > accessOps * 10);
5d145d
+        }
5d145d
+    }
5d145d
+
5d145d
+    if (enableCache) {
5d145d
+        /* try to set the temp store to memory.*/
5d145d
+        sqlite3_exec(sqlDB, "PRAGMA temp_store=MEMORY", NULL, 0, NULL);
5d145d
+        /* Failure to set the temp store to memory is not fatal,
5d145d
+         * ignore the error */
5d145d
+
5d145d
+        cacheTable = sqlite3_mprintf("%sCache", table);
5d145d
+        if (cacheTable == NULL) {
5d145d
+            error = CKR_HOST_MEMORY;
5d145d
+            goto loser;
5d145d
+        }
5d145d
+        /* build the cache table */
5d145d
+        error = sdb_buildCache(sqlDB, type, cacheTable, table);
5d145d
+        if (error != CKR_OK) {
5d145d
+            goto loser;
5d145d
+        }
5d145d
+        /* initialize the last cache build time */
5d145d
+        now = PR_IntervalNow();
5d145d
+    }
5d145d
+
5d145d
+    sdb = (SDB *)malloc(sizeof(SDB));
5d145d
+    if (!sdb) {
5d145d
+        error = CKR_HOST_MEMORY;
5d145d
+        goto loser;
5d145d
+    }
5d145d
+    sdb_p = (SDBPrivate *)malloc(sizeof(SDBPrivate));
5d145d
+    if (!sdb_p) {
5d145d
+        error = CKR_HOST_MEMORY;
5d145d
+        goto loser;
5d145d
+    }
5d145d
+
5d145d
+    /* Cache the attributes that are held in the table, so we can later check
5d145d
+     * that queried attributes actually exist. We don't assume the schema
5d145d
+     * to be exactly |known_attributes|, as it may change over time. */
5d145d
+    sdb_p->schemaAttrs = NULL;
5d145d
+    if (!PORT_Strcmp("nssPublic", table) ||
5d145d
+        !PORT_Strcmp("nssPrivate", table)) {
5d145d
+        sqlite3_stmt *stmt = NULL;
5d145d
+        int retry = 0;
5d145d
+        unsigned int backedAttrs = 0;
5d145d
+
5d145d
+        /* Can't bind parameters to a PRAGMA. */
5d145d
+        queryStr = sqlite3_mprintf("PRAGMA table_info(%s);", table);
5d145d
+        if (queryStr == NULL) {
5d145d
+            error = CKR_HOST_MEMORY;
5d145d
+            goto loser;
5d145d
+        }
5d145d
+        sqlerr = sqlite3_prepare_v2(sqlDB, queryStr, -1, &stmt, NULL);
5d145d
+        sqlite3_free(queryStr);
5d145d
+        queryStr = NULL;
5d145d
+        if (sqlerr != SQLITE_OK) {
5d145d
+            goto loser;
5d145d
+        }
5d145d
+        unsigned int schemaAttrsCapacity = known_attributes_size;
5d145d
+        sdb_p->schemaAttrs = malloc(schemaAttrsCapacity * sizeof(CK_ATTRIBUTE_TYPE));
5d145d
+        if (!sdb_p->schemaAttrs) {
5d145d
+            error = CKR_HOST_MEMORY;
5d145d
+            goto loser;
5d145d
+        }
5d145d
+        do {
5d145d
+            sqlerr = sqlite3_step(stmt);
5d145d
+            if (sqlerr == SQLITE_BUSY) {
5d145d
+                PR_Sleep(SDB_BUSY_RETRY_TIME);
5d145d
+            }
5d145d
+            if (sqlerr == SQLITE_ROW) {
5d145d
+                if (backedAttrs == schemaAttrsCapacity) {
5d145d
+                    schemaAttrsCapacity += known_attributes_size;
5d145d
+                    sdb_p->schemaAttrs = realloc(sdb_p->schemaAttrs,
5d145d
+                                                 schemaAttrsCapacity * sizeof(CK_ATTRIBUTE_TYPE));
5d145d
+                    if (!sdb_p->schemaAttrs) {
5d145d
+                        error = CKR_HOST_MEMORY;
5d145d
+                        goto loser;
5d145d
+                    }
5d145d
+                }
5d145d
+                /* Record the ULONG attribute value. */
5d145d
+                char *val = (char *)sqlite3_column_text(stmt, 1);
5d145d
+                if (val && val[0] == 'a') {
5d145d
+                    CK_ATTRIBUTE_TYPE attr = strtoul(&val[1], NULL, 16);
5d145d
+                    sdb_p->schemaAttrs[backedAttrs++] = attr;
5d145d
+                }
5d145d
+            }
5d145d
+        } while (!sdb_done(sqlerr, &retry));
5d145d
+
5d145d
+        if (sqlerr != SQLITE_DONE) {
5d145d
+            goto loser;
5d145d
+        }
5d145d
+        sqlerr = sqlite3_reset(stmt);
5d145d
+        if (sqlerr != SQLITE_OK) {
5d145d
+            goto loser;
5d145d
+        }
5d145d
+        sqlerr = sqlite3_finalize(stmt);
5d145d
+        if (sqlerr != SQLITE_OK) {
5d145d
+            goto loser;
5d145d
+        }
5d145d
+
5d145d
+        sdb_p->numSchemaAttrs = backedAttrs;
5d145d
+
5d145d
+        /* Sort these once so we can shortcut invalid attribute searches. */
5d145d
+        qsort(sdb_p->schemaAttrs, sdb_p->numSchemaAttrs,
5d145d
+              sizeof(CK_ATTRIBUTE_TYPE), sdb_attributeComparator);
5d145d
+    }
5d145d
+
5d145d
+    /* invariant fields */
5d145d
+    sdb_p->sqlDBName = PORT_Strdup(dbname);
5d145d
+    sdb_p->type = type;
5d145d
+    sdb_p->table = table;
5d145d
+    sdb_p->cacheTable = cacheTable;
5d145d
+    sdb_p->lastUpdateTime = now;
5d145d
+    /* set the cache delay time. This is how long we will wait before we
5d145d
+     * decide the existing cache is stale. Currently set to 10 sec */
5d145d
+    sdb_p->updateInterval = PR_SecondsToInterval(10);
5d145d
+    sdb_p->dbMon = PR_NewMonitor();
5d145d
+    /* these fields are protected by the lock */
5d145d
+    sdb_p->sqlXactDB = NULL;
5d145d
+    sdb_p->sqlXactThread = NULL;
5d145d
+    sdb->private = sdb_p;
5d145d
+    sdb->version = 1;
5d145d
+    sdb->sdb_flags = inFlags | SDB_HAS_META;
5d145d
+    sdb->app_private = NULL;
5d145d
+    sdb->sdb_FindObjectsInit = sdb_FindObjectsInit;
5d145d
+    sdb->sdb_FindObjects = sdb_FindObjects;
5d145d
+    sdb->sdb_FindObjectsFinal = sdb_FindObjectsFinal;
5d145d
+    sdb->sdb_GetAttributeValue = sdb_GetAttributeValue;
5d145d
+    sdb->sdb_SetAttributeValue = sdb_SetAttributeValue;
5d145d
+    sdb->sdb_CreateObject = sdb_CreateObject;
5d145d
+    sdb->sdb_DestroyObject = sdb_DestroyObject;
5d145d
+    sdb->sdb_GetMetaData = sdb_GetMetaData;
5d145d
+    sdb->sdb_PutMetaData = sdb_PutMetaData;
5d145d
+    sdb->sdb_DestroyMetaData = sdb_DestroyMetaData;
5d145d
+    sdb->sdb_Begin = sdb_Begin;
5d145d
+    sdb->sdb_Commit = sdb_Commit;
5d145d
+    sdb->sdb_Abort = sdb_Abort;
5d145d
+    sdb->sdb_Reset = sdb_Reset;
5d145d
+    sdb->sdb_Close = sdb_Close;
5d145d
+    sdb->sdb_SetForkState = sdb_SetForkState;
5d145d
+    sdb->sdb_GetNewObjectID = sdb_GetNewObjectID;
5d145d
+
5d145d
+    if (inTransaction) {
5d145d
+        sqlerr = sqlite3_exec(sqlDB, COMMIT_CMD, NULL, 0, NULL);
5d145d
+        if (sqlerr != SQLITE_OK) {
5d145d
+            error = sdb_mapSQLError(sdb_p->type, sqlerr);
5d145d
+            goto loser;
5d145d
+        }
5d145d
+        inTransaction = 0;
5d145d
+    }
5d145d
+
5d145d
+    sdb_p->sqlReadDB = sqlDB;
5d145d
+
5d145d
+    *pSdb = sdb;
5d145d
+    UNLOCK_SQLITE();
5d145d
+    return CKR_OK;
5d145d
+
5d145d
+loser:
5d145d
+    /* lots of stuff to do */
5d145d
+    if (inTransaction) {
5d145d
+        sqlite3_exec(sqlDB, ROLLBACK_CMD, NULL, 0, NULL);
5d145d
+    }
5d145d
+    if (sdb) {
5d145d
+        free(sdb);
5d145d
+    }
5d145d
+    if (sdb_p) {
5d145d
+        if (sdb_p->schemaAttrs) {
5d145d
+            free(sdb_p->schemaAttrs);
5d145d
+        }
5d145d
+        free(sdb_p);
5d145d
+    }
5d145d
+    if (sqlDB) {
5d145d
+        sqlite3_close(sqlDB);
5d145d
+    }
5d145d
+    UNLOCK_SQLITE();
5d145d
+    return error;
5d145d
+}
5d145d
+
5d145d
+/* sdbopen */
5d145d
+CK_RV
5d145d
+s_open(const char *directory, const char *certPrefix, const char *keyPrefix,
5d145d
+       int cert_version, int key_version, int flags,
5d145d
+       SDB **certdb, SDB **keydb, int *newInit)
5d145d
+{
5d145d
+    char *cert = sdb_BuildFileName(directory, certPrefix,
5d145d
+                                   "cert", cert_version);
5d145d
+    char *key = sdb_BuildFileName(directory, keyPrefix,
5d145d
+                                  "key", key_version);
5d145d
+    CK_RV error = CKR_OK;
5d145d
+    int inUpdate;
5d145d
+    PRUint32 accessOps;
5d145d
+
5d145d
+    if (certdb)
5d145d
+        *certdb = NULL;
5d145d
+    if (keydb)
5d145d
+        *keydb = NULL;
5d145d
+    *newInit = 0;
5d145d
+
5d145d
+#ifdef SQLITE_UNSAFE_THREADS
5d145d
+    if (sqlite_lock == NULL) {
5d145d
+        sqlite_lock = PR_NewLock();
5d145d
+        if (sqlite_lock == NULL) {
5d145d
+            error = CKR_HOST_MEMORY;
5d145d
+            goto loser;
5d145d
+        }
5d145d
+    }
5d145d
+#endif
5d145d
+
5d145d
+    /* how long does it take to test for a non-existant file in our working
5d145d
+     * directory? Allows us to test if we may be on a network file system */
5d145d
+    accessOps = 1;
5d145d
+    {
5d145d
+        char *env;
5d145d
+        env = PR_GetEnvSecure("NSS_SDB_USE_CACHE");
5d145d
+        /* If the environment variable is undefined or set to yes or no,
5d145d
+         * sdb_init() will ignore the value of accessOps, and we can skip the
5d145d
+         * measuring.*/
5d145d
+        if (env && PORT_Strcasecmp(env, "no") != 0 &&
5d145d
+            PORT_Strcasecmp(env, "yes") != 0) {
5d145d
+            accessOps = sdb_measureAccess(directory);
5d145d
+        }
5d145d
+    }
5d145d
+
5d145d
+    /*
5d145d
+     * open the cert data base
5d145d
+     */
5d145d
+    if (certdb) {
5d145d
+        /* initialize Certificate database */
5d145d
+        error = sdb_init(cert, "nssPublic", SDB_CERT, &inUpdate,
5d145d
+                         newInit, flags, accessOps, certdb);
5d145d
+        if (error != CKR_OK) {
5d145d
+            goto loser;
5d145d
+        }
5d145d
+    }
5d145d
+
5d145d
+    /*
5d145d
+     * open the key data base:
5d145d
+     *  NOTE:if we want to implement a single database, we open
5d145d
+     *  the same database file as the certificate here.
5d145d
+     *
5d145d
+     *  cert an key db's have different tables, so they will not
5d145d
+     *  conflict.
5d145d
+     */
5d145d
+    if (keydb) {
5d145d
+        /* initialize the Key database */
5d145d
+        error = sdb_init(key, "nssPrivate", SDB_KEY, &inUpdate,
5d145d
+                         newInit, flags, accessOps, keydb);
5d145d
+        if (error != CKR_OK) {
5d145d
+            goto loser;
5d145d
+        }
5d145d
+    }
5d145d
+
5d145d
+loser:
5d145d
+    if (cert) {
5d145d
+        sqlite3_free(cert);
5d145d
+    }
5d145d
+    if (key) {
5d145d
+        sqlite3_free(key);
5d145d
+    }
5d145d
+
5d145d
+    if (error != CKR_OK) {
5d145d
+        /* currently redundant, but could be necessary if more code is added
5d145d
+         * just before loser */
5d145d
+        if (keydb && *keydb) {
5d145d
+            sdb_Close(*keydb);
5d145d
+        }
5d145d
+        if (certdb && *certdb) {
5d145d
+            sdb_Close(*certdb);
5d145d
+        }
5d145d
+    }
5d145d
+
5d145d
+    return error;
5d145d
+}
5d145d
+
5d145d
+CK_RV
5d145d
+s_shutdown()
5d145d
+{
5d145d
+#ifdef SQLITE_UNSAFE_THREADS
5d145d
+    if (sqlite_lock) {
5d145d
+        PR_DestroyLock(sqlite_lock);
5d145d
+        sqlite_lock = NULL;
5d145d
+    }
5d145d
+#endif
5d145d
+    return CKR_OK;
5d145d
+}
5d145d
diff --git a/cmd/manifest.mn b/cmd/manifest.mn
5d145d
--- a/cmd/manifest.mn
5d145d
+++ b/cmd/manifest.mn
5d145d
@@ -17,16 +17,17 @@ REQUIRES = nss nspr libdbm
5d145d
 LIB_SRCDIRS = \
5d145d
  lib \
5d145d
  $(NULL)
5d145d
 endif
5d145d
 
5d145d
 ifndef NSS_BUILD_UTIL_ONLY
5d145d
 SOFTOKEN_SRCDIRS = \
5d145d
  $(BLTEST_SRCDIR) \
5d145d
+ $(DBTOOL_SRCDIR) \
5d145d
  $(ECPERF_SRCDIR) \
5d145d
  $(FREEBL_ECTEST_SRCDIR) \
5d145d
  $(FIPSTEST_SRCDIR)  \
5d145d
  $(LOWHASHTEST_SRCDIR)  \
5d145d
  $(SHLIBSIGN_SRCDIR) \
5d145d
  $(NULL)
5d145d
 endif
5d145d