/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* 
 *  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 "sftkdb.h"
#include "sftkdbti.h"
#include "sdb.h"
#include "prsystem.h"
#include "prprf.h"
#include "prenv.h"
#include "lgglue.h"
#include "secerr.h"
#include "softoken.h"

static LGOpenFunc legacy_glue_open = NULL;
static LGReadSecmodFunc legacy_glue_readSecmod = NULL;
static LGReleaseSecmodFunc legacy_glue_releaseSecmod = NULL;
static LGDeleteSecmodFunc legacy_glue_deleteSecmod = NULL;
static LGAddSecmodFunc legacy_glue_addSecmod = NULL;
static LGShutdownFunc legacy_glue_shutdown = NULL;

/*
 * The following 3 functions duplicate the work done by bl_LoadLibrary.
 * We should make bl_LoadLibrary a global and replace the call to
 * sftkdb_LoadLibrary(const char *libname) with it.
 */
#ifdef XP_UNIX
#include <unistd.h>
#define LG_MAX_LINKS 20
static char *
sftkdb_resolvePath(const char *orig)
{
    int count = 0;
    int len =0;
    int ret = -1;
    char *resolved = NULL;
    char *source = NULL;

    len = 1025; /* MAX PATH +1*/
    if (strlen(orig)+1 > len) {
	/* PATH TOO LONG */
	return NULL;
    }
    resolved = PORT_Alloc(len);
    if (!resolved) {
	return NULL;
    }
    source = PORT_Alloc(len);
    if (!source) {
	goto loser;
    }
    PORT_Strcpy(source, orig);
    /* Walk down all the links */
    while ( count++ < LG_MAX_LINKS) {
	char *tmp;
	/* swap our previous sorce out with resolved */
	/* read it */
	ret = readlink(source, resolved, len-1);
	if (ret  < 0) {
	    break;
 	}
	resolved[ret] = 0;
	tmp = source; source = resolved; resolved = tmp;
    }
    if (count > 1) {
	ret = 0;
    }
loser:
    if (resolved) {
	PORT_Free(resolved);
    }
    if (ret < 0) {
	if (source) {
	    PORT_Free(source);
	    source = NULL;
	}
    }
    return source;
}

#endif

static PRLibrary *
sftkdb_LoadFromPath(const char *path, const char *libname)
{
    char *c;
    int pathLen, nameLen, fullPathLen;
    char *fullPathName = NULL;
    PRLibSpec libSpec;
    PRLibrary *lib = NULL;


    /* strip of our parent's library name */ 
    c = strrchr(path, PR_GetDirectorySeparator());
    if (!c) {
	return NULL; /* invalid path */
    }
    pathLen = (c-path)+1;
    nameLen = strlen(libname);
    fullPathLen = pathLen + nameLen +1;
    fullPathName = (char *)PORT_Alloc(fullPathLen);
    if (fullPathName == NULL) {
	return NULL; /* memory allocation error */
    }
    PORT_Memcpy(fullPathName, path, pathLen);
    PORT_Memcpy(fullPathName+pathLen, libname, nameLen);
    fullPathName[fullPathLen-1] = 0;

    libSpec.type = PR_LibSpec_Pathname;
    libSpec.value.pathname = fullPathName;
    lib = PR_LoadLibraryWithFlags(libSpec, PR_LD_NOW | PR_LD_LOCAL);
    PORT_Free(fullPathName);
    return lib;
}


static PRLibrary *
sftkdb_LoadLibrary(const char *libname)
{
    PRLibrary *lib = NULL;
    PRFuncPtr fn_addr;
    char *parentLibPath = NULL;

    fn_addr  = (PRFuncPtr) &sftkdb_LoadLibrary;
    parentLibPath = PR_GetLibraryFilePathname(SOFTOKEN_LIB_NAME, fn_addr);

    if (!parentLibPath) {
	goto done;
    }

    lib = sftkdb_LoadFromPath(parentLibPath, libname);
#ifdef XP_UNIX
    /* handle symbolic link case */
    if (!lib) {
	char *trueParentLibPath = sftkdb_resolvePath(parentLibPath);
	if (!trueParentLibPath) {
	    goto done;
	}
    	lib = sftkdb_LoadFromPath(trueParentLibPath, libname);
	PORT_Free(trueParentLibPath);
    }
#endif

done:
    if (parentLibPath) {
	PORT_Free(parentLibPath);
    }

    /* still couldn't load it, try the generic path */
    if (!lib) {
	PRLibSpec libSpec;
	libSpec.type = PR_LibSpec_Pathname;
	libSpec.value.pathname = libname;
	lib = PR_LoadLibraryWithFlags(libSpec, PR_LD_NOW | PR_LD_LOCAL);
    }

    return lib;
}

/*
 * stub files for legacy db's to be able to encrypt and decrypt
 * various keys and attributes.
 */
static SECStatus
sftkdb_encrypt_stub(PLArenaPool *arena, SDB *sdb, SECItem *plainText,
		    SECItem **cipherText)
{
    SFTKDBHandle *handle = sdb->app_private;
    SECStatus rv;

    if (handle == NULL) {
	return SECFailure;
    }

    /* if we aren't the key handle, try the other handle */
    if (handle->type != SFTK_KEYDB_TYPE) {
	handle = handle->peerDB;
    }

    /* not a key handle */
    if (handle == NULL || handle->passwordLock == NULL) {
	return SECFailure;
    }

    PZ_Lock(handle->passwordLock);
    if (handle->passwordKey.data == NULL) {
	PZ_Unlock(handle->passwordLock);
	/* PORT_SetError */
	return SECFailure;
    }

    rv = sftkdb_EncryptAttribute(arena, 
	handle->newKey?handle->newKey:&handle->passwordKey, 
	plainText, cipherText);
    PZ_Unlock(handle->passwordLock);

    return rv;
}

/*
 * stub files for legacy db's to be able to encrypt and decrypt
 * various keys and attributes.
 */
static SECStatus
sftkdb_decrypt_stub(SDB *sdb, SECItem *cipherText, SECItem **plainText) 
{
    SFTKDBHandle *handle = sdb->app_private;
    SECStatus rv;
    SECItem *oldKey = NULL;

    if (handle == NULL) {
	return SECFailure;
    }

    /* if we aren't th handle, try the other handle */
    oldKey = handle->oldKey;
    if (handle->type != SFTK_KEYDB_TYPE) {
	handle = handle->peerDB;
    }

    /* not a key handle */
    if (handle == NULL || handle->passwordLock == NULL) {
	return SECFailure;
    }

    PZ_Lock(handle->passwordLock);
    if (handle->passwordKey.data == NULL) {
	PZ_Unlock(handle->passwordLock);
	/* PORT_SetError */
	return SECFailure;
    }
    rv = sftkdb_DecryptAttribute( oldKey ? oldKey : &handle->passwordKey,
		cipherText, plainText);
    PZ_Unlock(handle->passwordLock);

    return rv;
}

static const char *LEGACY_LIB_NAME = 
	SHLIB_PREFIX"nssdbm"SHLIB_VERSION"."SHLIB_SUFFIX;
/*
 * 2 bools to tell us if we've check the legacy library successfully or
 * not. Initialize on startup to false by the C BSS segment;
 */
static PRBool legacy_glue_libCheckFailed;    /* set if we failed the check */
static PRBool legacy_glue_libCheckSucceeded; /* set if we passed the check */
static PRLibrary *legacy_glue_lib = NULL;
static SECStatus 
sftkdbLoad_Legacy(PRBool isFIPS)
{
    PRLibrary *lib = NULL;
    LGSetCryptFunc setCryptFunction = NULL;

    if (legacy_glue_lib) {
	/* this check is necessary because it's possible we loaded the
	 * legacydb to read secmod.db, which told us whether we were in
	 * FIPS mode or not. */
	if (isFIPS && !legacy_glue_libCheckSucceeded) {
	    if (legacy_glue_libCheckFailed || 
		!BLAPI_SHVerify(LEGACY_LIB_NAME,(PRFuncPtr)legacy_glue_open)) {
    	    	legacy_glue_libCheckFailed = PR_TRUE;
		/* don't clobber legacy glue to avoid race. just let it
		 * get cleared in shutdown */
		return SECFailure;
	    }
    	    legacy_glue_libCheckSucceeded = PR_TRUE;
	} 
	return SECSuccess;
    }

    lib = sftkdb_LoadLibrary(LEGACY_LIB_NAME);
    if (lib == NULL) {
	return SECFailure;
    }
    
    legacy_glue_open = (LGOpenFunc)PR_FindFunctionSymbol(lib, "legacy_Open");
    legacy_glue_readSecmod = (LGReadSecmodFunc) PR_FindFunctionSymbol(lib,
						 "legacy_ReadSecmodDB");
    legacy_glue_releaseSecmod = (LGReleaseSecmodFunc) PR_FindFunctionSymbol(lib,
					 	 "legacy_ReleaseSecmodDBData");
    legacy_glue_deleteSecmod = (LGDeleteSecmodFunc) PR_FindFunctionSymbol(lib,
						 "legacy_DeleteSecmodDB");
    legacy_glue_addSecmod = (LGAddSecmodFunc)PR_FindFunctionSymbol(lib, 
						 "legacy_AddSecmodDB");
    legacy_glue_shutdown = (LGShutdownFunc) PR_FindFunctionSymbol(lib, 
						"legacy_Shutdown");
    setCryptFunction = (LGSetCryptFunc) PR_FindFunctionSymbol(lib, 
						"legacy_SetCryptFunctions");

    if (!legacy_glue_open || !legacy_glue_readSecmod || 
	    !legacy_glue_releaseSecmod || !legacy_glue_deleteSecmod || 
	    !legacy_glue_addSecmod || !setCryptFunction) {
	PR_UnloadLibrary(lib);
	return SECFailure;
    }

    /* verify the loaded library if we are in FIPS mode */
    if (isFIPS) {
	if (!BLAPI_SHVerify(LEGACY_LIB_NAME,(PRFuncPtr)legacy_glue_open)) {
	    PR_UnloadLibrary(lib);
	    return SECFailure;
	}
    	legacy_glue_libCheckSucceeded = PR_TRUE;
    } 

    setCryptFunction(sftkdb_encrypt_stub,sftkdb_decrypt_stub);
    legacy_glue_lib = lib;
    return SECSuccess;
}

CK_RV
sftkdbCall_open(const char *dir, const char *certPrefix, const char *keyPrefix, 
		int certVersion, int keyVersion, int flags, PRBool isFIPS,
		SDB **certDB, SDB **keyDB)
{
    SECStatus rv;

    rv = sftkdbLoad_Legacy(isFIPS);
    if (rv != SECSuccess) {
	return CKR_GENERAL_ERROR;
    }
    if (!legacy_glue_open) {
	PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
	return SECFailure;
    }
    return (*legacy_glue_open)(dir, certPrefix, keyPrefix, 
				certVersion, keyVersion,
				flags, certDB, keyDB);
}

char **
sftkdbCall_ReadSecmodDB(const char *appName, const char *filename, 
			const char *dbname, char *params, PRBool rw)
{
    SECStatus rv;

    rv = sftkdbLoad_Legacy(PR_FALSE);
    if (rv != SECSuccess) {
	return NULL;
    }
    if (!legacy_glue_readSecmod) {
	PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
	return NULL;
    }
    return (*legacy_glue_readSecmod)(appName, filename, dbname, params, rw);
}

SECStatus
sftkdbCall_ReleaseSecmodDBData(const char *appName, 
			const char *filename, const char *dbname, 
			char **moduleSpecList, PRBool rw)
{
    SECStatus rv;

    rv = sftkdbLoad_Legacy(PR_FALSE);
    if (rv != SECSuccess) {
	return rv;
    }
    if (!legacy_glue_releaseSecmod) {
	PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
	return SECFailure;
    }
    return (*legacy_glue_releaseSecmod)(appName, filename, dbname, 
					  moduleSpecList, rw);
}

SECStatus
sftkdbCall_DeleteSecmodDB(const char *appName, 
		      const char *filename, const char *dbname, 
		      char *args, PRBool rw)
{
    SECStatus rv;

    rv = sftkdbLoad_Legacy(PR_FALSE);
    if (rv != SECSuccess) {
	return rv;
    }
    if (!legacy_glue_deleteSecmod) {
	PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
	return SECFailure;
    }
    return (*legacy_glue_deleteSecmod)(appName, filename, dbname, args, rw);
}

SECStatus
sftkdbCall_AddSecmodDB(const char *appName, 
		   const char *filename, const char *dbname, 
		   char *module, PRBool rw)
{
    SECStatus rv;

    rv = sftkdbLoad_Legacy(PR_FALSE);
    if (rv != SECSuccess) {
	return rv;
    }
    if (!legacy_glue_addSecmod) {
	PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
	return SECFailure;
    }
    return (*legacy_glue_addSecmod)(appName, filename, dbname, module, rw);
}

CK_RV
sftkdbCall_Shutdown(void)
{
    CK_RV crv = CKR_OK;
    char *disableUnload = NULL;
    if (!legacy_glue_lib) {
	return CKR_OK;
    }
    if (legacy_glue_shutdown) {
#ifdef NO_FORK_CHECK
	PRBool parentForkedAfterC_Initialize = PR_FALSE;
#endif
	crv = (*legacy_glue_shutdown)(parentForkedAfterC_Initialize);
    }
    disableUnload = PR_GetEnv("NSS_DISABLE_UNLOAD");
    if (!disableUnload) {
        PR_UnloadLibrary(legacy_glue_lib);
    }
    legacy_glue_lib = NULL;
    legacy_glue_open = NULL;
    legacy_glue_readSecmod = NULL;
    legacy_glue_releaseSecmod = NULL;
    legacy_glue_deleteSecmod = NULL;
    legacy_glue_addSecmod = NULL;
    legacy_glue_libCheckFailed    = PR_FALSE;
    legacy_glue_libCheckSucceeded = PR_FALSE;
    return crv;
}
    

