/* 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. For the rest of NSS, only one kind of database handle exists: * * SFTKDBHandle * * There is one SFTKDBHandle for each key database and one for each cert * database. These databases are opened as associated pairs, one pair per * slot. SFTKDBHandles are reference counted objects. * * Each SFTKDBHandle points to a low level database handle (SDB). This handle * represents the underlying physical database. These objects are not * reference counted, and are 'owned' by their respective SFTKDBHandles. */ #include "prprf.h" #include "prsystem.h" #include "secport.h" #include "utilpars.h" #include "secerr.h" #if defined(_WIN32) #include #include #endif #ifdef XP_UNIX #include #endif #include #include #include #if defined(_WIN32) #define os_fdopen _fdopen #define os_truncate_open_flags _O_CREAT | _O_RDWR | _O_TRUNC #define os_append_open_flags _O_CREAT | _O_RDWR | _O_APPEND #define os_open_permissions_type int #define os_open_permissions_default _S_IREAD | _S_IWRITE #define os_stat_type struct _stat /* * Convert a UTF8 string to Unicode wide character */ LPWSTR _NSSUTIL_UTF8ToWide(const char *buf) { DWORD size; LPWSTR wide; if (!buf) { return NULL; } size = MultiByteToWideChar(CP_UTF8, 0, buf, -1, NULL, 0); if (size == 0) { return NULL; } wide = PORT_Alloc(sizeof(WCHAR) * size); if (!wide) { return NULL; } size = MultiByteToWideChar(CP_UTF8, 0, buf, -1, wide, size); if (size == 0) { PORT_Free(wide); return NULL; } return wide; } static int os_open(const char *filename, int oflag, int pmode) { int fd; if (!filename) { return -1; } wchar_t *filenameWide = _NSSUTIL_UTF8ToWide(filename); if (!filenameWide) { return -1; } fd = _wopen(filenameWide, oflag, pmode); PORT_Free(filenameWide); return fd; } static int os_stat(const char *path, os_stat_type *buffer) { int result; if (!path) { return -1; } wchar_t *pathWide = _NSSUTIL_UTF8ToWide(path); if (!pathWide) { return -1; } result = _wstat(pathWide, buffer); PORT_Free(pathWide); return result; } static FILE * os_fopen(const char *filename, const char *mode) { FILE *fp; if (!filename || !mode) { return NULL; } wchar_t *filenameWide = _NSSUTIL_UTF8ToWide(filename); if (!filenameWide) { return NULL; } wchar_t *modeWide = _NSSUTIL_UTF8ToWide(mode); if (!modeWide) { PORT_Free(filenameWide); return NULL; } fp = _wfopen(filenameWide, modeWide); PORT_Free(filenameWide); PORT_Free(modeWide); return fp; } PRStatus _NSSUTIL_Access(const char *path, PRAccessHow how) { int result; if (!path) { return PR_FAILURE; } int mode; switch (how) { case PR_ACCESS_WRITE_OK: mode = 2; break; case PR_ACCESS_READ_OK: mode = 4; break; case PR_ACCESS_EXISTS: mode = 0; break; default: return PR_FAILURE; } wchar_t *pathWide = _NSSUTIL_UTF8ToWide(path); if (!pathWide) { return PR_FAILURE; } result = _waccess(pathWide, mode); PORT_Free(pathWide); return result < 0 ? PR_FAILURE : PR_SUCCESS; } static PRStatus nssutil_Delete(const char *name) { BOOL result; if (!name) { return PR_FAILURE; } wchar_t *nameWide = _NSSUTIL_UTF8ToWide(name); if (!nameWide) { return PR_FAILURE; } result = DeleteFileW(nameWide); PORT_Free(nameWide); return result ? PR_SUCCESS : PR_FAILURE; } static PRStatus nssutil_Rename(const char *from, const char *to) { BOOL result; if (!from || !to) { return PR_FAILURE; } wchar_t *fromWide = _NSSUTIL_UTF8ToWide(from); if (!fromWide) { return PR_FAILURE; } wchar_t *toWide = _NSSUTIL_UTF8ToWide(to); if (!toWide) { PORT_Free(fromWide); return PR_FAILURE; } result = MoveFileW(fromWide, toWide); PORT_Free(fromWide); PORT_Free(toWide); return result ? PR_SUCCESS : PR_FAILURE; } #else #define os_fopen fopen #define os_open open #define os_fdopen fdopen #define os_stat stat #define os_truncate_open_flags O_CREAT | O_RDWR | O_TRUNC #define os_append_open_flags O_CREAT | O_RDWR | O_APPEND #define os_open_permissions_type mode_t #define os_open_permissions_default 0600 #define os_stat_type struct stat #define nssutil_Delete PR_Delete #define nssutil_Rename PR_Rename #endif /**************************************************************** * * Secmod database. * * The new secmod database is simply a text file with each of the module * entries in the following form: * * # * # This is a comment The next line is the library to load * library=libmypkcs11.so * name="My PKCS#11 module" * params="my library's param string" * nss="NSS parameters" * other="parameters for other libraries and applications" * * library=libmynextpk11.so * name="My other PKCS#11 module" */ /* * Smart string cat functions. Automatically manage the memory. * The first parameter is the destination string. If it's null, we * allocate memory for it. If it's not, we reallocate memory * so the the concanenated string fits. */ static char * nssutil_DupnCat(char *baseString, const char *str, int str_len) { int baseStringLen = baseString ? PORT_Strlen(baseString) : 0; int len = baseStringLen + 1; char *newString; len += str_len; newString = (char *)PORT_Realloc(baseString, len); if (newString == NULL) { PORT_Free(baseString); return NULL; } PORT_Memcpy(&newString[baseStringLen], str, str_len); newString[len - 1] = 0; return newString; } /* Same as nssutil_DupnCat except it concatenates the full string, not a * partial one */ static char * nssutil_DupCat(char *baseString, const char *str) { return nssutil_DupnCat(baseString, str, PORT_Strlen(str)); } /* function to free up all the memory associated with a null terminated * array of module specs */ static SECStatus nssutil_releaseSpecList(char **moduleSpecList) { if (moduleSpecList) { char **index; for (index = moduleSpecList; *index; index++) { PORT_Free(*index); } PORT_Free(moduleSpecList); } return SECSuccess; } #define SECMOD_STEP 10 static SECStatus nssutil_growList(char ***pModuleList, int *useCount, int last) { char **newModuleList; *useCount += SECMOD_STEP; newModuleList = (char **)PORT_Realloc(*pModuleList, *useCount * sizeof(char *)); if (newModuleList == NULL) { return SECFailure; } PORT_Memset(&newModuleList[last], 0, sizeof(char *) * SECMOD_STEP); *pModuleList = newModuleList; return SECSuccess; } static char * _NSSUTIL_GetOldSecmodName(const char *dbname, const char *filename) { char *file = NULL; char *dirPath = PORT_Strdup(dbname); char *sep; sep = PORT_Strrchr(dirPath, *NSSUTIL_PATH_SEPARATOR); #ifdef _WIN32 if (!sep) { /* utilparst.h defines NSSUTIL_PATH_SEPARATOR as "/" for all * platforms. */ sep = PORT_Strrchr(dirPath, '\\'); } #endif if (sep) { *sep = 0; file = PR_smprintf("%s" NSSUTIL_PATH_SEPARATOR "%s", dirPath, filename); } else { file = PR_smprintf("%s", filename); } PORT_Free(dirPath); return file; } static SECStatus nssutil_AddSecmodDBEntry(const char *appName, const char *filename, const char *dbname, const char *module, PRBool rw); enum lfopen_mode { lfopen_truncate, lfopen_append }; FILE * lfopen(const char *name, enum lfopen_mode om, os_open_permissions_type open_perms) { int fd; FILE *file; fd = os_open(name, (om == lfopen_truncate) ? os_truncate_open_flags : os_append_open_flags, open_perms); if (fd < 0) { return NULL; } file = os_fdopen(fd, (om == lfopen_truncate) ? "w+" : "a+"); if (!file) { close(fd); } /* file inherits fd */ return file; } #define MAX_LINE_LENGTH 2048 /* * Read all the existing modules in out of the file. */ static char ** nssutil_ReadSecmodDB(const char *appName, const char *filename, const char *dbname, char *params, PRBool rw) { FILE *fd = NULL; char **moduleList = NULL; int moduleCount = 1; int useCount = SECMOD_STEP; char line[MAX_LINE_LENGTH]; PRBool internal = PR_FALSE; PRBool skipParams = PR_FALSE; char *moduleString = NULL; char *paramsValue = NULL; PRBool failed = PR_TRUE; moduleList = (char **)PORT_ZAlloc(useCount * sizeof(char *)); if (moduleList == NULL) return NULL; if (dbname == NULL) { goto return_default; } /* do we really want to use streams here */ fd = os_fopen(dbname, "r"); if (fd == NULL) goto done; /* * the following loop takes line separated config lines and collapses * the lines to a single string, escaping and quoting as necessary. */ /* loop state variables */ moduleString = NULL; /* current concatenated string */ internal = PR_FALSE; /* is this an internal module */ skipParams = PR_FALSE; /* did we find an override parameter block*/ paramsValue = NULL; /* the current parameter block value */ do { int len; if (fgets(line, sizeof(line), fd) == NULL) { goto endloop; } /* remove the ending newline */ len = PORT_Strlen(line); if (len >= 2 && line[len - 2] == '\r' && line[len - 1] == '\n') { len = len - 2; line[len] = 0; } else if (len && (line[len - 1] == '\n' || line[len - 1] == '\r')) { len--; line[len] = 0; } if (*line == '#') { continue; } if (*line != 0) { /* * The PKCS #11 group standard assumes blocks of strings * separated by new lines, clumped by new lines. Internally * we take strings separated by spaces, so we may need to escape * certain spaces. */ char *value = PORT_Strchr(line, '='); /* there is no value, write out the stanza as is */ if (value == NULL || value[1] == 0) { if (moduleString) { moduleString = nssutil_DupnCat(moduleString, " ", 1); if (moduleString == NULL) goto loser; } moduleString = nssutil_DupCat(moduleString, line); if (moduleString == NULL) goto loser; /* value is already quoted, just write it out */ } else if (value[1] == '"') { if (moduleString) { moduleString = nssutil_DupnCat(moduleString, " ", 1); if (moduleString == NULL) goto loser; } moduleString = nssutil_DupCat(moduleString, line); if (moduleString == NULL) goto loser; /* we have an override parameter section, remember that * we found this (see following comment about why this * is necessary). */ if (PORT_Strncasecmp(line, "parameters", 10) == 0) { skipParams = PR_TRUE; } /* * The internal token always overrides it's parameter block * from the passed in parameters, so wait until then end * before we include the parameter block in case we need to * override it. NOTE: if the parameter block is quoted with ("), * this override does not happen. This allows you to override * the application's parameter configuration. * * parameter block state is controlled by the following variables: * skipParams - Bool : set to true of we have an override param * block (all other blocks, either implicit or explicit are * ignored). * paramsValue - char * : pointer to the current param block. In * the absence of overrides, paramsValue is set to the first * parameter block we find. All subsequent blocks are ignored. * When we find an internal token, the application passed * parameters take precident. */ } else if (PORT_Strncasecmp(line, "parameters", 10) == 0) { /* already have parameters */ if (paramsValue) { continue; } paramsValue = NSSUTIL_Quote(&value[1], '"'); if (paramsValue == NULL) goto loser; continue; } else { /* may need to quote */ char *newLine; if (moduleString) { moduleString = nssutil_DupnCat(moduleString, " ", 1); if (moduleString == NULL) goto loser; } moduleString = nssutil_DupnCat(moduleString, line, value - line + 1); if (moduleString == NULL) goto loser; newLine = NSSUTIL_Quote(&value[1], '"'); if (newLine == NULL) goto loser; moduleString = nssutil_DupCat(moduleString, newLine); PORT_Free(newLine); if (moduleString == NULL) goto loser; } /* check to see if it's internal? */ if (PORT_Strncasecmp(line, "NSS=", 4) == 0) { /* This should be case insensitive! reviewers make * me fix it if it's not */ if (PORT_Strstr(line, "internal")) { internal = PR_TRUE; /* override the parameters */ if (paramsValue) { PORT_Free(paramsValue); } paramsValue = NSSUTIL_Quote(params, '"'); } } continue; } if ((moduleString == NULL) || (*moduleString == 0)) { continue; } endloop: /* * if we are here, we have found a complete stanza. Now write out * any param section we may have found. */ if (paramsValue) { /* we had an override */ if (!skipParams) { moduleString = nssutil_DupnCat(moduleString, " parameters=", 12); if (moduleString == NULL) goto loser; moduleString = nssutil_DupCat(moduleString, paramsValue); if (moduleString == NULL) goto loser; } PORT_Free(paramsValue); paramsValue = NULL; } if ((moduleCount + 1) >= useCount) { SECStatus rv; rv = nssutil_growList(&moduleList, &useCount, moduleCount + 1); if (rv != SECSuccess) { goto loser; } } if (internal) { moduleList[0] = moduleString; } else { moduleList[moduleCount] = moduleString; moduleCount++; } moduleString = NULL; internal = PR_FALSE; skipParams = PR_FALSE; } while (!feof(fd)); if (moduleString) { PORT_Free(moduleString); moduleString = NULL; } done: /* if we couldn't open a pkcs11 database, look for the old one */ if (fd == NULL) { char *olddbname = _NSSUTIL_GetOldSecmodName(dbname, filename); PRStatus status; /* couldn't get the old name */ if (!olddbname) { goto bail; } /* old one exists */ status = _NSSUTIL_Access(olddbname, PR_ACCESS_EXISTS); if (status == PR_SUCCESS) { PR_smprintf_free(olddbname); PORT_ZFree(moduleList, useCount * sizeof(char *)); PORT_SetError(SEC_ERROR_LEGACY_DATABASE); return NULL; } bail: if (olddbname) { PR_smprintf_free(olddbname); } } return_default: if (!moduleList[0]) { char *newParams; moduleString = PORT_Strdup(NSSUTIL_DEFAULT_INTERNAL_INIT1); newParams = NSSUTIL_Quote(params, '"'); if (newParams == NULL) goto loser; moduleString = nssutil_DupCat(moduleString, newParams); PORT_Free(newParams); if (moduleString == NULL) goto loser; moduleString = nssutil_DupCat(moduleString, NSSUTIL_DEFAULT_INTERNAL_INIT2); if (moduleString == NULL) goto loser; moduleString = nssutil_DupCat(moduleString, NSSUTIL_DEFAULT_SFTKN_FLAGS); if (moduleString == NULL) goto loser; moduleString = nssutil_DupCat(moduleString, NSSUTIL_DEFAULT_INTERNAL_INIT3); if (moduleString == NULL) goto loser; moduleList[0] = moduleString; moduleString = NULL; } failed = PR_FALSE; loser: /* * cleanup */ /* deal with trust cert db here */ if (moduleString) { PORT_Free(moduleString); moduleString = NULL; } if (paramsValue) { PORT_Free(paramsValue); paramsValue = NULL; } if (failed || (moduleList[0] == NULL)) { /* This is wrong! FIXME */ nssutil_releaseSpecList(moduleList); moduleList = NULL; failed = PR_TRUE; } if (fd != NULL) { fclose(fd); } else if (!failed && rw) { /* update our internal module */ nssutil_AddSecmodDBEntry(appName, filename, dbname, moduleList[0], rw); } return moduleList; } static SECStatus nssutil_ReleaseSecmodDBData(const char *appName, const char *filename, const char *dbname, char **moduleSpecList, PRBool rw) { if (moduleSpecList) { nssutil_releaseSpecList(moduleSpecList); } return SECSuccess; } /* * Delete a module from the Data Base */ static SECStatus nssutil_DeleteSecmodDBEntry(const char *appName, const char *filename, const char *dbname, const char *args, PRBool rw) { /* SHDB_FIXME implement */ os_stat_type stat_existing; os_open_permissions_type file_mode; FILE *fd = NULL; FILE *fd2 = NULL; char line[MAX_LINE_LENGTH]; char *dbname2 = NULL; char *block = NULL; char *name = NULL; char *lib = NULL; int name_len = 0, lib_len = 0; PRBool skip = PR_FALSE; PRBool found = PR_FALSE; if (dbname == NULL) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } if (!rw) { PORT_SetError(SEC_ERROR_READ_ONLY); return SECFailure; } dbname2 = PORT_Strdup(dbname); if (dbname2 == NULL) goto loser; dbname2[strlen(dbname) - 1]++; /* get the permissions of the existing file, or use the default */ if (!os_stat(dbname, &stat_existing)) { file_mode = stat_existing.st_mode; } else { file_mode = os_open_permissions_default; } /* do we really want to use streams here */ fd = os_fopen(dbname, "r"); if (fd == NULL) goto loser; fd2 = lfopen(dbname2, lfopen_truncate, file_mode); if (fd2 == NULL) goto loser; name = NSSUTIL_ArgGetParamValue("name", args); if (name) { name_len = PORT_Strlen(name); } lib = NSSUTIL_ArgGetParamValue("library", args); if (lib) { lib_len = PORT_Strlen(lib); } /* * the following loop takes line separated config files and collapses * the lines to a single string, escaping and quoting as necessary. */ /* loop state variables */ block = NULL; skip = PR_FALSE; while (fgets(line, sizeof(line), fd) != NULL) { /* If we are processing a block (we haven't hit a blank line yet */ if (*line != '\n') { /* skip means we are in the middle of a block we are deleting */ if (skip) { continue; } /* if we haven't found the block yet, check to see if this block * matches our requirements */ if (!found && ((name && (PORT_Strncasecmp(line, "name=", 5) == 0) && (PORT_Strncmp(line + 5, name, name_len) == 0)) || (lib && (PORT_Strncasecmp(line, "library=", 8) == 0) && (PORT_Strncmp(line + 8, lib, lib_len) == 0)))) { /* yup, we don't need to save any more data, */ PORT_Free(block); block = NULL; /* we don't need to collect more of this block */ skip = PR_TRUE; /* we don't need to continue searching for the block */ found = PR_TRUE; continue; } /* not our match, continue to collect data in this block */ block = nssutil_DupCat(block, line); continue; } /* we've collected a block of data that wasn't the module we were * looking for, write it out */ if (block) { fwrite(block, PORT_Strlen(block), 1, fd2); PORT_Free(block); block = NULL; } /* If we didn't just delete the this block, keep the blank line */ if (!skip) { fputs(line, fd2); } /* we are definately not in a deleted block anymore */ skip = PR_FALSE; } fclose(fd); fclose(fd2); if (found) { /* rename dbname2 to dbname */ nssutil_Delete(dbname); nssutil_Rename(dbname2, dbname); } else { nssutil_Delete(dbname2); } PORT_Free(dbname2); PORT_Free(lib); PORT_Free(name); PORT_Free(block); return SECSuccess; loser: if (fd != NULL) { fclose(fd); } if (fd2 != NULL) { fclose(fd2); } if (dbname2) { nssutil_Delete(dbname2); PORT_Free(dbname2); } PORT_Free(lib); PORT_Free(name); return SECFailure; } /* * Add a module to the Data base */ static SECStatus nssutil_AddSecmodDBEntry(const char *appName, const char *filename, const char *dbname, const char *module, PRBool rw) { os_stat_type stat_existing; os_open_permissions_type file_mode; FILE *fd = NULL; char *block = NULL; PRBool libFound = PR_FALSE; if (dbname == NULL) { PORT_SetError(SEC_ERROR_INVALID_ARGS); return SECFailure; } /* can't write to a read only module */ if (!rw) { PORT_SetError(SEC_ERROR_READ_ONLY); return SECFailure; } /* remove the previous version if it exists */ (void)nssutil_DeleteSecmodDBEntry(appName, filename, dbname, module, rw); /* get the permissions of the existing file, or use the default */ if (!os_stat(dbname, &stat_existing)) { file_mode = stat_existing.st_mode; } else { file_mode = os_open_permissions_default; } fd = lfopen(dbname, lfopen_append, file_mode); if (fd == NULL) { return SECFailure; } module = NSSUTIL_ArgStrip(module); while (*module) { int count; char *keyEnd = PORT_Strchr(module, '='); char *value; if (PORT_Strncmp(module, "library=", 8) == 0) { libFound = PR_TRUE; } if (keyEnd == NULL) { block = nssutil_DupCat(block, module); break; } block = nssutil_DupnCat(block, module, keyEnd - module + 1); if (block == NULL) { goto loser; } value = NSSUTIL_ArgFetchValue(&keyEnd[1], &count); if (value) { block = nssutil_DupCat(block, NSSUTIL_ArgStrip(value)); PORT_Free(value); } if (block == NULL) { goto loser; } block = nssutil_DupnCat(block, "\n", 1); module = keyEnd + 1 + count; module = NSSUTIL_ArgStrip(module); } if (block) { if (!libFound) { fprintf(fd, "library=\n"); } fwrite(block, PORT_Strlen(block), 1, fd); fprintf(fd, "\n"); PORT_Free(block); block = NULL; } fclose(fd); return SECSuccess; loser: PORT_Free(block); fclose(fd); return SECFailure; } char ** NSSUTIL_DoModuleDBFunction(unsigned long function, char *parameters, void *args) { char *secmod = NULL; char *appName = NULL; char *filename = NULL; NSSDBType dbType = NSS_DB_TYPE_NONE; PRBool rw; static char *success = "Success"; char **rvstr = NULL; secmod = _NSSUTIL_GetSecmodName(parameters, &dbType, &appName, &filename, &rw); if ((dbType == NSS_DB_TYPE_LEGACY) || (dbType == NSS_DB_TYPE_MULTIACCESS)) { /* we can't handle the old database, only softoken can */ PORT_SetError(SEC_ERROR_LEGACY_DATABASE); rvstr = NULL; goto done; } switch (function) { case SECMOD_MODULE_DB_FUNCTION_FIND: rvstr = nssutil_ReadSecmodDB(appName, filename, secmod, (char *)parameters, rw); break; case SECMOD_MODULE_DB_FUNCTION_ADD: rvstr = (nssutil_AddSecmodDBEntry(appName, filename, secmod, (char *)args, rw) == SECSuccess) ? &success : NULL; break; case SECMOD_MODULE_DB_FUNCTION_DEL: rvstr = (nssutil_DeleteSecmodDBEntry(appName, filename, secmod, (char *)args, rw) == SECSuccess) ? &success : NULL; break; case SECMOD_MODULE_DB_FUNCTION_RELEASE: rvstr = (nssutil_ReleaseSecmodDBData(appName, filename, secmod, (char **)args, rw) == SECSuccess) ? &success : NULL; break; } done: if (secmod) PR_smprintf_free(secmod); if (appName) PORT_Free(appName); if (filename) PORT_Free(filename); return rvstr; }