/*
 * The contents of this file are subject to the Mozilla Public
 * License Version 1.1 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.mozilla.org/MPL/
 * 
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 * 
 * The Original Code is the Netscape security libraries.
 * 
 * The Initial Developer of the Original Code is Netscape
 * Communications Corporation.  Portions created by Netscape are 
 * Copyright (C) 1994-2000 Netscape Communications Corporation.  All
 * Rights Reserved.
 * 
 * Contributor(s):
 * 
 * Alternatively, the contents of this file may be used under the
 * terms of the GNU General Public License Version 2 or later (the
 * "GPL"), in which case the provisions of the GPL are applicable 
 * instead of those above.  If you wish to allow use of your 
 * version of this file only under the terms of the GPL and not to
 * allow others to use your version of this file under the MPL,
 * indicate your decision by deleting the provisions above and
 * replace them with the notice and other provisions required by
 * the GPL.  If you do not delete the provisions above, a recipient
 * may use your version of this file under either the MPL or the
 * GPL.
 */
/* 
 *  The following code handles the storage of PKCS 11 modules used by the
 * NSS. This file is written to abstract away how the modules are
 * stored so we can deside that later.
 */
#include "seccomon.h"
#include "secmod.h"
#include "nssilock.h"
#include "pkcs11.h"
#include "secmodi.h"
#include "pk11func.h"
#include "mcom_db.h"

/* create a new module */
SECMODModule *SECMOD_NewModule(void) {
    SECMODModule *newMod;
    PRArenaPool *arena;


    /* create an arena in which dllName and commonName can be
     * allocated.
     */
    arena = PORT_NewArena(512);
    if (arena == NULL) {
	return NULL;
    }

    newMod = (SECMODModule *)PORT_ArenaAlloc(arena,sizeof (SECMODModule));
    if (newMod == NULL) {
	PORT_FreeArena(arena,PR_FALSE);
	return NULL;
    }

    /*
     * initialize of the fields of the module
     */
    newMod->arena = arena;
    newMod->internal = PR_FALSE;
    newMod->loaded = PR_FALSE;
    newMod->isFIPS = PR_FALSE;
    newMod->dllName = NULL;
    newMod->commonName = NULL;
    newMod->library = NULL;
    newMod->functionList = NULL;
    newMod->slotCount = 0;
    newMod->slots = NULL;
    newMod->slotInfo = NULL;
    newMod->slotInfoCount = 0;
    newMod->refCount = 1;
    newMod->ssl[0] = 0;
    newMod->ssl[1] = 0;
#ifdef PKCS11_USE_THREADS
    newMod->refLock = (void *)PZ_NewLock(nssILockRefLock);
    if (newMod->refLock == NULL) {
	PORT_FreeArena(arena,PR_FALSE);
	return NULL;
    }
#else
    newMod->refLock = NULL;
#endif
    return newMod;
    
}

/* create a new ModuleListElement */
SECMODModuleList *SECMOD_NewModuleListElement(void) {
    SECMODModuleList *newModList;

    newModList= (SECMODModuleList *) PORT_Alloc(sizeof(SECMODModuleList));
    if (newModList) {
	newModList->next = NULL;
	newModList->module = NULL;
    }
    return newModList;
}

static unsigned long internalFlags = SECMOD_RSA_FLAG|SECMOD_DSA_FLAG|
	SECMOD_RC2_FLAG| SECMOD_RC4_FLAG|SECMOD_DES_FLAG|SECMOD_RANDOM_FLAG|
	SECMOD_SHA1_FLAG|SECMOD_MD5_FLAG|SECMOD_MD2_FLAG|SECMOD_SSL_FLAG|
	SECMOD_TLS_FLAG;

/* create a Internal  module */
SECMODModule *SECMOD_NewInternal(void) {
    SECMODModule *intern;
    static PK11PreSlotInfo internSlotInfo =
	{ 1, SECMOD_RSA_FLAG|SECMOD_DSA_FLAG|SECMOD_RC2_FLAG|
	SECMOD_RC4_FLAG|SECMOD_DES_FLAG|SECMOD_RANDOM_FLAG|
	SECMOD_SHA1_FLAG|SECMOD_MD5_FLAG|SECMOD_MD2_FLAG|
	SECMOD_SSL_FLAG|SECMOD_TLS_FLAG, -1, 30, 0 };

    intern = SECMOD_NewModule();
    if (intern == NULL) {
	return NULL;
    }

    /*
     * make this module an internal module
     */
    intern->commonName = "Netscape Internal PKCS #11 Module";
    intern->internal = PR_TRUE;
    intern->slotInfoCount = 1;
    intern->slotInfo = &internSlotInfo;

    return (intern);
}

/* create a FIPS Internal  module */
SECMODModule *SECMOD_GetFIPSInternal(void) {
    SECMODModule *intern;

    intern = SECMOD_NewInternal();
    if (intern == NULL) {
	return NULL;
    }

    /*
     * make this module a FIPS internal module
     */
    intern->slotInfo[0].slotID = 3; /* FIPS slot */
    intern->commonName = "Netscape Internal FIPS PKCS #11 Module";
    intern->isFIPS = PR_TRUE;

    return (intern);
}

SECMODModule *SECMOD_DupModule(SECMODModule *old) {
    SECMODModule *newMod;

    newMod = SECMOD_NewModule();
    if (newMod == NULL) {
	return NULL;
    }

    /*
     * initialize of the fields of the module
     */
    newMod->dllName = PORT_ArenaStrdup(newMod->arena,old->dllName);
    newMod->commonName = PORT_ArenaStrdup(newMod->arena,old->commonName);;

    return newMod;
    
}

/*
 * make a new reference to a module so It doesn't go away on us
 */
SECMODModule *
SECMOD_ReferenceModule(SECMODModule *module) {
    PK11_USE_THREADS(PZ_Lock((PZLock *)module->refLock);)
    PORT_Assert(module->refCount > 0);

    module->refCount++;
    PK11_USE_THREADS(PZ_Unlock((PZLock*)module->refLock);)
    return module;
}


/* destroy an existing module */
void
SECMOD_DestroyModule(SECMODModule *module) {
    PRBool willfree = PR_FALSE;
    int slotCount;
    int i;

    PK11_USE_THREADS(PZ_Lock((PZLock *)module->refLock);)
    if (module->refCount-- == 1) {
	willfree = PR_TRUE;
    }
    PORT_Assert(willfree || (module->refCount > 0));
    PK11_USE_THREADS(PZ_Unlock((PZLock *)module->refLock);)

    if (!willfree) {
	return;
    }

    /* slots can't really disappear until our module starts freeing them,
     * so this check is safe */
    slotCount = module->slotCount;
    if (slotCount == 0) {
	SECMOD_SlotDestroyModule(module,PR_FALSE);
	return;
    }

    /* now free all out slots, when they are done, they will cause the
     * module to disappear altogether */
    for (i=0 ; i < slotCount; i++) {
	if (!module->slots[i]->disabled) {
		PK11_ClearSlotList(module->slots[i]);
	}
	PK11_FreeSlot(module->slots[i]);
    }
    /* WARNING: once the last slot has been freed is it possible (even likely)
     * that module is no more... touching it now is a good way to go south */
}


/* we can only get here if we've destroyed the module, or some one has
 * erroneously freed a slot that wasn't referenced. */
void
SECMOD_SlotDestroyModule(SECMODModule *module, PRBool fromSlot) {
    PRBool willfree = PR_FALSE;
    if (fromSlot) {
        PORT_Assert(module->refCount == 0);
	PK11_USE_THREADS(PZ_Lock((PZLock *)module->refLock);)
	if (module->slotCount-- == 1) {
	    willfree = PR_TRUE;
	}
	PORT_Assert(willfree || (module->slotCount > 0));
	PK11_USE_THREADS(PZ_Unlock((PZLock *)module->refLock);)
        if (!willfree) return;
    }
    if (module->loaded) {
	SECMOD_UnloadModule(module);
    }
    PK11_USE_THREADS(PZ_DestroyLock((PZLock *)module->refLock);)
    PORT_FreeArena(module->arena,PR_FALSE);
}

/* destroy a list element
 * this destroys a single element, and returns the next element
 * on the chain. It makes it easy to implement for loops to delete
 * the chain. It also make deleting a single element easy */
SECMODModuleList *
SECMOD_DestroyModuleListElement(SECMODModuleList *element) {
    SECMODModuleList *next = element->next;

    if (element->module) {
	SECMOD_DestroyModule(element->module);
	element->module = NULL;
    }
    PORT_Free(element);
    return next;
}


/*
 * Destroy an entire module list
 */
void
SECMOD_DestroyModuleList(SECMODModuleList *list) {
    SECMODModuleList *lp;

    for ( lp = list; lp != NULL; lp = SECMOD_DestroyModuleListElement(lp)) ;
}


/* Construct a database key for a given module */
static SECStatus secmod_MakeKey(DBT *key, SECMODModule * module) {
    int len = 0;

    len = PORT_Strlen(module->commonName);
    key->data = module->commonName;
    key->size = len;
    return SECSuccess;
}

/* free out constructed database key */
static void secmod_FreeKey(DBT *key) {
    key->data = NULL;
    key->size = 0;
}

typedef struct secmodDataStr secmodData;
typedef struct secmodSlotDataStr secmodSlotData;
struct secmodDataStr {
    unsigned char major;
    unsigned char minor;
    unsigned char nameStart[2];
    unsigned char slotOffset[2];
    unsigned char internal;
    unsigned char fips;
    unsigned char ssl[8];
    unsigned char names[4];	/* enough space for the length fields */
};

struct secmodSlotDataStr {
    unsigned char slotID[4];
    unsigned char defaultFlags[4];
    unsigned char timeout[4];
    unsigned char askpw;
    unsigned char hasRootCerts;
    unsigned char reserved[18]; /* this makes it a round 32 bytes */
};

#define SECMOD_DB_VERSION_MAJOR 0
#define SECMOD_DB_VERSION_MINOR 4
#define SECMOD_DB_NOUI_VERSION_MAJOR 0
#define SECMOD_DB_NOUI_VERSION_MINOR 3

#define SECMOD_PUTSHORT(dest,src) \
	(dest)[1] = (unsigned char) ((src)&0xff); \
	(dest)[0] = (unsigned char) (((src) >> 8) & 0xff);
#define SECMOD_PUTLONG(dest,src) \
	(dest)[3] = (unsigned char) ((src)&0xff); \
	(dest)[2] = (unsigned char) (((src) >> 8) & 0xff); \
	(dest)[1] = (unsigned char) (((src) >> 16) & 0xff); \
	(dest)[0] = (unsigned char) (((src) >> 24) & 0xff);
#define SECMOD_GETSHORT(src) \
	((unsigned short) (((src)[0] << 8) | (src)[1]))
#define SECMOD_GETLONG(src) \
	((unsigned long) (( (unsigned long) (src)[0] << 24) | \
			( (unsigned long) (src)[1] << 16)  | \
			( (unsigned long) (src)[2] << 8) | \
			(unsigned long) (src)[3]))
/*
 * build a data base entry from a module 
 */
static SECStatus secmod_EncodeData(DBT *data, SECMODModule * module) {
    secmodData *encoded;
    secmodSlotData *slot;
    unsigned char *dataPtr;
    unsigned short len, len2 = 0,count = 0;
    unsigned short offset;
    int dataLen, i, si;

    len = PORT_Strlen(module->commonName);
    if (module->dllName) {
    	len2 = PORT_Strlen(module->dllName);
    }
    if (module->slotCount != 0) {
	for (i=0; i < module->slotCount; i++) {
	    if (module->slots[i]->defaultFlags != 0) {
		count++;
	    }
	}
    } else {
	count = module->slotInfoCount;
    }
    dataLen = sizeof(secmodData) + len + len2 + 2 +
				 count*sizeof(secmodSlotData);

    data->data = (unsigned char *)
			PORT_Alloc(dataLen);
    encoded = (secmodData *)data->data;
    dataPtr = (unsigned char *) data->data;
    data->size = dataLen;

    if (encoded == NULL) return SECFailure;

    encoded->major = SECMOD_DB_VERSION_MAJOR;
    encoded->minor = SECMOD_DB_VERSION_MINOR;
    encoded->internal = (unsigned char) (module->internal ? 1 : 0);
    encoded->fips = (unsigned char) (module->isFIPS ? 1 : 0);
    SECMOD_PUTLONG(encoded->ssl,module->ssl[0]);
    SECMOD_PUTLONG(&encoded->ssl[4],module->ssl[1]);

    offset = (unsigned long) &(((secmodData *)0)->names[0]);
    SECMOD_PUTSHORT(encoded->nameStart,offset);
    offset = offset +len + len2 + 4;
    SECMOD_PUTSHORT(encoded->slotOffset,offset);


    SECMOD_PUTSHORT(&dataPtr[offset],count);
    slot = (secmodSlotData *)(dataPtr+offset+2);

    SECMOD_PUTSHORT(encoded->names,len);
    PORT_Memcpy(&encoded->names[2],module->commonName,len);


    SECMOD_PUTSHORT(&encoded->names[len+2],len2);
    if (len2) PORT_Memcpy(&encoded->names[len+4],module->dllName,len2);

    if (module->slotCount) {
      for (i=0,si=0; i < module->slotCount; i++) {
	if (module->slots[i]->defaultFlags) {
	    SECMOD_PUTLONG(slot[si].slotID, module->slots[i]->slotID);
	    SECMOD_PUTLONG(slot[si].defaultFlags,
					     module->slots[i]->defaultFlags);
	    SECMOD_PUTLONG(slot[si].timeout,module->slots[i]->timeout);
	    slot[si].askpw = module->slots[i]->askpw;
	    slot[si].hasRootCerts = module->slots[i]->hasRootCerts;
	    PORT_Memset(slot[si].reserved, 0, sizeof(slot[si].reserved));
	    si++;
	}
      }
    } else {
	for (i=0; i < module->slotInfoCount; i++) {
	    SECMOD_PUTLONG(slot[i].slotID, module->slotInfo[i].slotID);
	    SECMOD_PUTLONG(slot[i].defaultFlags,
					module->slotInfo[i].defaultFlags);
	    SECMOD_PUTLONG(slot[i].timeout,module->slotInfo[i].timeout);
	    slot[i].askpw = module->slotInfo[i].askpw;
	    slot[i].hasRootCerts = module->slotInfo[i].hasRootCerts;
	    PORT_Memset(slot[i].reserved, 0, sizeof(slot[i].reserved));
	}
    }

    return SECSuccess;

}

static void secmod_FreeData(DBT *data) {
    if (data->data) {
	PORT_Free(data->data);
    }
}


/*
 * build a module from the data base entry.
 */
static SECMODModule *secmod_DecodeData(DBT *data) {
    SECMODModule * module;
    secmodData *encoded;
    secmodSlotData *slots;
    unsigned char *names;
    unsigned short len,len1;
    unsigned long slotCount;
    unsigned short offset;
    PRBool isOldVersion  = PR_FALSE;
    int i;

    encoded = (secmodData *)data->data;
    names = (unsigned char *)data->data;
    offset = SECMOD_GETSHORT(encoded->slotOffset);
    slots = (secmodSlotData *) (names + offset + 2);
    slotCount = SECMOD_GETSHORT(names + offset);
    names += SECMOD_GETSHORT(encoded->nameStart);

    module = SECMOD_NewModule();
    if (module == NULL) return NULL;

    module->internal = (encoded->internal != 0) ? PR_TRUE: PR_FALSE;
    module->isFIPS = (encoded->fips != 0) ? PR_TRUE: PR_FALSE;
    len = SECMOD_GETSHORT(names);

    if (module->internal && (encoded->major == SECMOD_DB_NOUI_VERSION_MAJOR) &&
 	(encoded->minor <= SECMOD_DB_NOUI_VERSION_MINOR)) {
	isOldVersion = PR_TRUE;
    }

    /* decode the common name */
    module->commonName = (char*)PORT_ArenaAlloc(module->arena,len+1);
    if (module->commonName == NULL) {
	SECMOD_DestroyModule(module);
	return NULL;
    }
    PORT_Memcpy(module->commonName,&names[2],len);
    module->commonName[len] = 0;

    /* decode the DLL name */
    len1 = (names[len+2] << 8) | names[len+3];
    if (len1) {
	module->dllName = (char*)PORT_ArenaAlloc(module->arena,len1 + 1);
	if (module->dllName == NULL) {
	    SECMOD_DestroyModule(module);
	    return NULL;
	}
	PORT_Memcpy(module->dllName,&names[len+4],len1);
	module->dllName[len1] = 0;
    }

    module->slotInfoCount = slotCount;
    module->slotInfo = (PK11PreSlotInfo *) PORT_ArenaAlloc(module->arena,
				slotCount * sizeof(PK11PreSlotInfo));
    for (i=0; i < (int) slotCount; i++) {
	module->slotInfo[i].slotID = SECMOD_GETLONG(slots[i].slotID);
	module->slotInfo[i].defaultFlags = 
				SECMOD_GETLONG(slots[i].defaultFlags);
	if (isOldVersion && module->internal && 
				(module->slotInfo[i].slotID != 2)) {
		module->slotInfo[i].defaultFlags |= internalFlags;
	}
	module->slotInfo[i].timeout = SECMOD_GETLONG(slots[i].timeout);
	module->slotInfo[i].askpw = slots[i].askpw;
	module->slotInfo[i].hasRootCerts = slots[i].hasRootCerts;
	if (module->slotInfo[i].askpw == 0xff) {
	   module->slotInfo[i].askpw = -1;
	}
    }

    /* decode SSL cipher enable flags */
    module->ssl[0] = SECMOD_GETLONG(encoded->ssl);
    module->ssl[1] = SECMOD_GETLONG(&encoded->ssl[4]);

    return (module);
}

/*
 * open the PKCS #11 data base.
 */
static char *pkcs11dbName = NULL;
void SECMOD_InitDB(char *dbname) {
    pkcs11dbName = PORT_Strdup(dbname);
}


static DB *secmod_OpenDB(PRBool readOnly) {
    DB *pkcs11db = NULL;
    char *dbname;

    if (pkcs11dbName == NULL) return NULL;
    dbname = pkcs11dbName;
   
    /* I'm sure we should do more checks here sometime... */
    pkcs11db = dbopen(dbname, readOnly ? O_RDONLY : O_RDWR, 0600, DB_HASH, 0);

    /* didn't exist? create it */
    if (pkcs11db == NULL) {
	 if (readOnly) return NULL;

	 pkcs11db = dbopen( dbname,
                             O_RDWR | O_CREAT | O_TRUNC, 0600, DB_HASH, 0 );
	 if (pkcs11db) (* pkcs11db->sync)(pkcs11db, 0);
    }
    return pkcs11db;
}

static void secmod_CloseDB(DB *pkcs11db) {
     (*pkcs11db->close)(pkcs11db);
}

/*
 * Read all the existing modules in
 */
SECMODModuleList *
SECMOD_ReadPermDB(void) {
    DBT key,data;
    int ret;
    DB *pkcs11db = NULL;
    SECMODModuleList *newmod = NULL,*mod = NULL;

    pkcs11db = secmod_OpenDB(PR_TRUE);
    if (pkcs11db == NULL) {
	return NULL;
    }

    /* read and parse the file or data base */
    ret = (*pkcs11db->seq)(pkcs11db, &key, &data, R_FIRST);
    if (ret)  goto done;

    do {
        /* allocate space for modules */
	newmod = SECMOD_NewModuleListElement();
	if (newmod == NULL) break;
	newmod->module = secmod_DecodeData(&data);
	if (newmod->module == NULL) {
	    SECMOD_DestroyModuleListElement(newmod);
	    break;
	}
	newmod->next = mod;
	mod = newmod;
    } while ( (*pkcs11db->seq)(pkcs11db, &key, &data, R_NEXT) == 0);

done:
    secmod_CloseDB(pkcs11db);
    return mod;
}

/*
 * Delete a module from the Data Base
 */
SECStatus
SECMOD_DeletePermDB(SECMODModule * module) {
    DBT key;
    SECStatus rv = SECFailure;
    DB *pkcs11db = NULL;
    int ret;

    /* make sure we have a db handle */
    pkcs11db = secmod_OpenDB(PR_FALSE);
    if (pkcs11db == NULL) {
	return SECFailure;
    }

    rv = secmod_MakeKey(&key,module);
    if (rv != SECSuccess) goto done;
    rv = SECFailure;
    ret = (*pkcs11db->del)(pkcs11db, &key, 0);
    secmod_FreeKey(&key);
    if (ret != 0) goto done;


    ret = (*pkcs11db->sync)(pkcs11db, 0);
    if (ret == 0) rv = SECSuccess;

done:
    secmod_CloseDB(pkcs11db);
    return rv;
}

/*
 * Add a module to the Data base 
 */
SECStatus
SECMOD_AddPermDB(SECMODModule *module) {
    DBT key,data;
    SECStatus rv = SECFailure;
    DB *pkcs11db = NULL;
    int ret;

    /* make sure we have a db handle */
    pkcs11db = secmod_OpenDB(PR_FALSE);
    if (pkcs11db == NULL) {
	return SECFailure;
    }

    rv = secmod_MakeKey(&key,module);
    if (rv != SECSuccess) goto done;
    rv = secmod_EncodeData(&data,module);
    if (rv != SECSuccess) {
	secmod_FreeKey(&key);
	goto done;
    }
    rv = SECFailure;
    ret = (*pkcs11db->put)(pkcs11db, &key, &data, 0);
    secmod_FreeKey(&key);
    secmod_FreeData(&data);
    if (ret != 0) goto done;

    ret = (*pkcs11db->sync)(pkcs11db, 0);
    if (ret == 0) rv = SECSuccess;

done:
    secmod_CloseDB(pkcs11db);
    return rv;
}
