/* 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 decide that later.
 */
#include "secport.h"
#include "prprf.h"
#include "prenv.h"
#include "utilpars.h"
#include "utilmodt.h"

/*
 * return the expected matching quote value for the one specified
 */
PRBool
NSSUTIL_ArgGetPair(char c)
{
    switch (c) {
        case '\'':
            return c;
        case '\"':
            return c;
        case '<':
            return '>';
        case '{':
            return '}';
        case '[':
            return ']';
        case '(':
            return ')';
        default:
            break;
    }
    return ' ';
}

PRBool
NSSUTIL_ArgIsBlank(char c)
{
    return isspace((unsigned char)c);
}

PRBool
NSSUTIL_ArgIsEscape(char c)
{
    return c == '\\';
}

PRBool
NSSUTIL_ArgIsQuote(char c)
{
    switch (c) {
        case '\'':
        case '\"':
        case '<':
        case '{': /* } end curly to keep vi bracket matching working */
        case '(': /* ) */
        case '[': /* ] */
            return PR_TRUE;
        default:
            break;
    }
    return PR_FALSE;
}

const char *
NSSUTIL_ArgStrip(const char *c)
{
    while (*c && NSSUTIL_ArgIsBlank(*c))
        c++;
    return c;
}

/*
 * find the end of the current tag/value pair. string should be pointing just
 * after the equal sign. Handles quoted characters.
 */
const char *
NSSUTIL_ArgFindEnd(const char *string)
{
    char endChar = ' ';
    PRBool lastEscape = PR_FALSE;

    if (NSSUTIL_ArgIsQuote(*string)) {
        endChar = NSSUTIL_ArgGetPair(*string);
        string++;
    }

    for (; *string; string++) {
        if (lastEscape) {
            lastEscape = PR_FALSE;
            continue;
        }
        if (NSSUTIL_ArgIsEscape(*string) && !lastEscape) {
            lastEscape = PR_TRUE;
            continue;
        }
        if ((endChar == ' ') && NSSUTIL_ArgIsBlank(*string))
            break;
        if (*string == endChar) {
            break;
        }
    }

    return string;
}

/*
 * get the value pointed to by string. string should be pointing just beyond
 * the equal sign.
 */
char *
NSSUTIL_ArgFetchValue(const char *string, int *pcount)
{
    const char *end = NSSUTIL_ArgFindEnd(string);
    char *retString, *copyString;
    PRBool lastEscape = PR_FALSE;
    int len;

    len = end - string;
    if (len == 0) {
        *pcount = 0;
        return NULL;
    }

    copyString = retString = (char *)PORT_Alloc(len + 1);

    if (*end)
        len++;
    *pcount = len;
    if (retString == NULL)
        return NULL;

    if (NSSUTIL_ArgIsQuote(*string))
        string++;
    for (; string < end; string++) {
        if (NSSUTIL_ArgIsEscape(*string) && !lastEscape) {
            lastEscape = PR_TRUE;
            continue;
        }
        lastEscape = PR_FALSE;
        *copyString++ = *string;
    }
    *copyString = 0;
    return retString;
}

/*
 * point to the next parameter in string
 */
const char *
NSSUTIL_ArgSkipParameter(const char *string)
{
    const char *end;
    /* look for the end of the <name>= */
    for (; *string; string++) {
        if (*string == '=') {
            string++;
            break;
        }
        if (NSSUTIL_ArgIsBlank(*string))
            return (string);
    }

    end = NSSUTIL_ArgFindEnd(string);
    if (*end)
        end++;
    return end;
}

/*
 * get the value from that tag value pair.
 */
char *
NSSUTIL_ArgGetParamValue(const char *paramName, const char *parameters)
{
    char searchValue[256];
    size_t paramLen = strlen(paramName);
    char *returnValue = NULL;
    int next;

    if ((parameters == NULL) || (*parameters == 0))
        return NULL;

    PORT_Assert(paramLen + 2 < sizeof(searchValue));

    PORT_Strcpy(searchValue, paramName);
    PORT_Strcat(searchValue, "=");
    while (*parameters) {
        if (PORT_Strncasecmp(parameters, searchValue, paramLen + 1) == 0) {
            parameters += paramLen + 1;
            returnValue = NSSUTIL_ArgFetchValue(parameters, &next);
            break;
        } else {
            parameters = NSSUTIL_ArgSkipParameter(parameters);
        }
        parameters = NSSUTIL_ArgStrip(parameters);
    }
    return returnValue;
}

/*
 * find the next flag in the parameter list
 */
const char *
NSSUTIL_ArgNextFlag(const char *flags)
{
    for (; *flags; flags++) {
        if (*flags == ',') {
            flags++;
            break;
        }
    }
    return flags;
}

/*
 * return true if the flag is set in the label parameter.
 */
PRBool
NSSUTIL_ArgHasFlag(const char *label, const char *flag, const char *parameters)
{
    char *flags;
    const char *index;
    int len = strlen(flag);
    PRBool found = PR_FALSE;

    flags = NSSUTIL_ArgGetParamValue(label, parameters);
    if (flags == NULL)
        return PR_FALSE;

    for (index = flags; *index; index = NSSUTIL_ArgNextFlag(index)) {
        if (PORT_Strncasecmp(index, flag, len) == 0) {
            found = PR_TRUE;
            break;
        }
    }
    PORT_Free(flags);
    return found;
}

/*
 * decode a number. handle octal (leading '0'), hex (leading '0x') or decimal
 */
long
NSSUTIL_ArgDecodeNumber(const char *num)
{
    int radix = 10;
    unsigned long value = 0;
    long retValue = 0;
    int sign = 1;
    int digit;

    if (num == NULL)
        return retValue;

    num = NSSUTIL_ArgStrip(num);

    if (*num == '-') {
        sign = -1;
        num++;
    }

    if (*num == '0') {
        radix = 8;
        num++;
        if ((*num == 'x') || (*num == 'X')) {
            radix = 16;
            num++;
        }
    }

    for (; *num; num++) {
        if (isdigit((unsigned char)*num)) {
            digit = *num - '0';
        } else if ((*num >= 'a') && (*num <= 'f')) {
            digit = *num - 'a' + 10;
        } else if ((*num >= 'A') && (*num <= 'F')) {
            digit = *num - 'A' + 10;
        } else {
            break;
        }
        if (digit >= radix)
            break;
        value = value * radix + digit;
    }

    retValue = ((int)value) * sign;
    return retValue;
}

/*
 * parameters are tag value pairs. This function returns the tag or label (the
 * value before the equal size.
 */
char *
NSSUTIL_ArgGetLabel(const char *inString, int *next)
{
    char *name = NULL;
    const char *string;
    int len;

    /* look for the end of the <label>= */
    for (string = inString; *string; string++) {
        if (*string == '=') {
            break;
        }
        if (NSSUTIL_ArgIsBlank(*string))
            break;
    }

    len = string - inString;

    *next = len;
    if (*string == '=')
        (*next) += 1;
    if (len > 0) {
        name = PORT_Alloc(len + 1);
        PORT_Strncpy(name, inString, len);
        name[len] = 0;
    }
    return name;
}

/*
 * read an argument at a Long integer
 */
long
NSSUTIL_ArgReadLong(const char *label, const char *params,
                    long defValue, PRBool *isdefault)
{
    char *value;
    long retValue;
    if (isdefault)
        *isdefault = PR_FALSE;

    value = NSSUTIL_ArgGetParamValue(label, params);
    if (value == NULL) {
        if (isdefault)
            *isdefault = PR_TRUE;
        return defValue;
    }
    retValue = NSSUTIL_ArgDecodeNumber(value);
    if (value)
        PORT_Free(value);

    return retValue;
}

/*
 * prepare a string to be quoted with 'quote' marks. We do that by adding
 * appropriate escapes.
 */
static int
nssutil_escapeQuotesSize(const char *string, char quote, PRBool addquotes)
{
    int escapes = 0, size = 0;
    const char *src;

    size = addquotes ? 2 : 0;
    for (src = string; *src; src++) {
        if ((*src == quote) || (*src == '\\'))
            escapes++;
        size++;
    }
    return size + escapes + 1;
}

static char *
nssutil_escapeQuotes(const char *string, char quote, PRBool addquotes)
{
    char *newString = 0;
    int size = 0;
    const char *src;
    char *dest;

    size = nssutil_escapeQuotesSize(string, quote, addquotes);

    dest = newString = PORT_ZAlloc(size);
    if (newString == NULL) {
        return NULL;
    }

    if (addquotes)
        *dest++ = quote;
    for (src = string; *src; src++, dest++) {
        if ((*src == '\\') || (*src == quote)) {
            *dest++ = '\\';
        }
        *dest = *src;
    }
    if (addquotes)
        *dest = quote;

    return newString;
}

int
NSSUTIL_EscapeSize(const char *string, char quote)
{
    return nssutil_escapeQuotesSize(string, quote, PR_FALSE);
}

char *
NSSUTIL_Escape(const char *string, char quote)
{
    return nssutil_escapeQuotes(string, quote, PR_FALSE);
}

int
NSSUTIL_QuoteSize(const char *string, char quote)
{
    return nssutil_escapeQuotesSize(string, quote, PR_TRUE);
}

char *
NSSUTIL_Quote(const char *string, char quote)
{
    return nssutil_escapeQuotes(string, quote, PR_TRUE);
}

int
NSSUTIL_DoubleEscapeSize(const char *string, char quote1, char quote2)
{
    int escapes = 0, size = 0;
    const char *src;
    for (src = string; *src; src++) {
        if (*src == '\\')
            escapes += 3; /* \\\\ */
        if (*src == quote1)
            escapes += 2; /* \\quote1 */
        if (*src == quote2)
            escapes++; /* \quote2 */
        size++;
    }

    return escapes + size + 1;
}

char *
NSSUTIL_DoubleEscape(const char *string, char quote1, char quote2)
{
    char *round1 = NULL;
    char *retValue = NULL;
    if (string == NULL) {
        goto done;
    }
    round1 = nssutil_escapeQuotes(string, quote1, PR_FALSE);
    if (round1) {
        retValue = nssutil_escapeQuotes(round1, quote2, PR_FALSE);
        PORT_Free(round1);
    }

done:
    if (retValue == NULL) {
        retValue = PORT_Strdup("");
    }
    return retValue;
}

/************************************************************************
 * These functions are used in contructing strings.
 * NOTE: they will always return a string, but sometimes it will return
 * a specific NULL string. These strings must be freed with util_freePair.
 */

/* string to return on error... */
static char *nssutil_nullString = "";

static char *
nssutil_formatValue(PLArenaPool *arena, char *value, char quote)
{
    char *vp, *vp2, *retval;
    int size = 0, escapes = 0;

    for (vp = value; *vp; vp++) {
        if ((*vp == quote) || (*vp == NSSUTIL_ARG_ESCAPE))
            escapes++;
        size++;
    }
    if (arena) {
        retval = PORT_ArenaZAlloc(arena, size + escapes + 1);
    } else {
        retval = PORT_ZAlloc(size + escapes + 1);
    }
    if (retval == NULL)
        return NULL;
    vp2 = retval;
    for (vp = value; *vp; vp++) {
        if ((*vp == quote) || (*vp == NSSUTIL_ARG_ESCAPE))
            *vp2++ = NSSUTIL_ARG_ESCAPE;
        *vp2++ = *vp;
    }
    return retval;
}

static PRBool
nssutil_argHasChar(char *v, char c)
{
    for (; *v; v++) {
        if (*v == c)
            return PR_TRUE;
    }
    return PR_FALSE;
}

static PRBool
nssutil_argHasBlanks(char *v)
{
    for (; *v; v++) {
        if (NSSUTIL_ArgIsBlank(*v))
            return PR_TRUE;
    }
    return PR_FALSE;
}

static char *
nssutil_formatPair(char *name, char *value, char quote)
{
    char openQuote = quote;
    char closeQuote = NSSUTIL_ArgGetPair(quote);
    char *newValue = NULL;
    char *returnValue;
    PRBool need_quote = PR_FALSE;

    if (!value || (*value == 0))
        return nssutil_nullString;

    if (nssutil_argHasBlanks(value) || NSSUTIL_ArgIsQuote(value[0]))
        need_quote = PR_TRUE;

    if ((need_quote && nssutil_argHasChar(value, closeQuote)) || nssutil_argHasChar(value, NSSUTIL_ARG_ESCAPE)) {
        value = newValue = nssutil_formatValue(NULL, value, quote);
        if (newValue == NULL)
            return nssutil_nullString;
    }
    if (need_quote) {
        returnValue = PR_smprintf("%s=%c%s%c", name, openQuote, value, closeQuote);
    } else {
        returnValue = PR_smprintf("%s=%s", name, value);
    }
    if (returnValue == NULL)
        returnValue = nssutil_nullString;

    if (newValue)
        PORT_Free(newValue);

    return returnValue;
}

static char *
nssutil_formatIntPair(char *name, unsigned long value,
                      unsigned long def)
{
    char *returnValue;

    if (value == def)
        return nssutil_nullString;

    returnValue = PR_smprintf("%s=%d", name, value);

    return returnValue;
}

static void
nssutil_freePair(char *pair)
{
    if (pair && pair != nssutil_nullString) {
        PR_smprintf_free(pair);
    }
}

/************************************************************************
 * Parse the Slot specific parameters in the NSS params.
 */

struct nssutilArgSlotFlagTable {
    char *name;
    int len;
    unsigned long value;
};

#define NSSUTIL_ARG_ENTRY(arg, flag) \
    {                                \
#arg, sizeof(#arg) - 1, flag \
    }
static struct nssutilArgSlotFlagTable nssutil_argSlotFlagTable[] = {
    NSSUTIL_ARG_ENTRY(RSA, SECMOD_RSA_FLAG),
    NSSUTIL_ARG_ENTRY(ECC, SECMOD_ECC_FLAG),
    NSSUTIL_ARG_ENTRY(DSA, SECMOD_RSA_FLAG),
    NSSUTIL_ARG_ENTRY(RC2, SECMOD_RC4_FLAG),
    NSSUTIL_ARG_ENTRY(RC4, SECMOD_RC2_FLAG),
    NSSUTIL_ARG_ENTRY(DES, SECMOD_DES_FLAG),
    NSSUTIL_ARG_ENTRY(DH, SECMOD_DH_FLAG),
    NSSUTIL_ARG_ENTRY(FORTEZZA, SECMOD_FORTEZZA_FLAG),
    NSSUTIL_ARG_ENTRY(RC5, SECMOD_RC5_FLAG),
    NSSUTIL_ARG_ENTRY(SHA1, SECMOD_SHA1_FLAG),
    NSSUTIL_ARG_ENTRY(SHA256, SECMOD_SHA256_FLAG),
    NSSUTIL_ARG_ENTRY(SHA512, SECMOD_SHA512_FLAG),
    NSSUTIL_ARG_ENTRY(MD5, SECMOD_MD5_FLAG),
    NSSUTIL_ARG_ENTRY(MD2, SECMOD_MD2_FLAG),
    NSSUTIL_ARG_ENTRY(SSL, SECMOD_SSL_FLAG),
    NSSUTIL_ARG_ENTRY(TLS, SECMOD_TLS_FLAG),
    NSSUTIL_ARG_ENTRY(AES, SECMOD_AES_FLAG),
    NSSUTIL_ARG_ENTRY(Camellia, SECMOD_CAMELLIA_FLAG),
    NSSUTIL_ARG_ENTRY(SEED, SECMOD_SEED_FLAG),
    NSSUTIL_ARG_ENTRY(PublicCerts, SECMOD_FRIENDLY_FLAG),
    NSSUTIL_ARG_ENTRY(RANDOM, SECMOD_RANDOM_FLAG),
    NSSUTIL_ARG_ENTRY(Disable, SECMOD_DISABLE_FLAG),
};

static int nssutil_argSlotFlagTableSize =
    sizeof(nssutil_argSlotFlagTable) / sizeof(nssutil_argSlotFlagTable[0]);

/* turn the slot flags into a bit mask */
unsigned long
NSSUTIL_ArgParseSlotFlags(const char *label, const char *params)
{
    char *flags;
    const char *index;
    unsigned long retValue = 0;
    int i;
    PRBool all = PR_FALSE;

    flags = NSSUTIL_ArgGetParamValue(label, params);
    if (flags == NULL)
        return 0;

    if (PORT_Strcasecmp(flags, "all") == 0)
        all = PR_TRUE;

    for (index = flags; *index; index = NSSUTIL_ArgNextFlag(index)) {
        for (i = 0; i < nssutil_argSlotFlagTableSize; i++) {
            if (all ||
                (PORT_Strncasecmp(index, nssutil_argSlotFlagTable[i].name,
                                  nssutil_argSlotFlagTable[i].len) == 0)) {
                retValue |= nssutil_argSlotFlagTable[i].value;
            }
        }
    }
    PORT_Free(flags);
    return retValue;
}

/* parse a single slot specific parameter */
static void
nssutil_argDecodeSingleSlotInfo(char *name, char *params,
                                struct NSSUTILPreSlotInfoStr *slotInfo)
{
    char *askpw;

    slotInfo->slotID = NSSUTIL_ArgDecodeNumber(name);
    slotInfo->defaultFlags = NSSUTIL_ArgParseSlotFlags("slotFlags", params);
    slotInfo->timeout = NSSUTIL_ArgReadLong("timeout", params, 0, NULL);

    askpw = NSSUTIL_ArgGetParamValue("askpw", params);
    slotInfo->askpw = 0;

    if (askpw) {
        if (PORT_Strcasecmp(askpw, "every") == 0) {
            slotInfo->askpw = -1;
        } else if (PORT_Strcasecmp(askpw, "timeout") == 0) {
            slotInfo->askpw = 1;
        }
        PORT_Free(askpw);
        slotInfo->defaultFlags |= PK11_OWN_PW_DEFAULTS;
    }
    slotInfo->hasRootCerts = NSSUTIL_ArgHasFlag("rootFlags", "hasRootCerts",
                                                params);
    slotInfo->hasRootTrust = NSSUTIL_ArgHasFlag("rootFlags", "hasRootTrust",
                                                params);
}

/* parse all the slot specific parameters. */
struct NSSUTILPreSlotInfoStr *
NSSUTIL_ArgParseSlotInfo(PLArenaPool *arena, const char *slotParams,
                         int *retCount)
{
    const char *slotIndex;
    struct NSSUTILPreSlotInfoStr *slotInfo = NULL;
    int i = 0, count = 0, next;

    *retCount = 0;
    if ((slotParams == NULL) || (*slotParams == 0))
        return NULL;

    /* first count the number of slots */
    for (slotIndex = NSSUTIL_ArgStrip(slotParams); *slotIndex;
         slotIndex = NSSUTIL_ArgStrip(NSSUTIL_ArgSkipParameter(slotIndex))) {
        count++;
    }

    /* get the data structures */
    if (arena) {
        slotInfo = PORT_ArenaZNewArray(arena,
                                       struct NSSUTILPreSlotInfoStr, count);
    } else {
        slotInfo = PORT_ZNewArray(struct NSSUTILPreSlotInfoStr, count);
    }
    if (slotInfo == NULL)
        return NULL;

    for (slotIndex = NSSUTIL_ArgStrip(slotParams), i = 0;
         *slotIndex && i < count;) {
        char *name;
        name = NSSUTIL_ArgGetLabel(slotIndex, &next);
        slotIndex += next;

        if (!NSSUTIL_ArgIsBlank(*slotIndex)) {
            char *args = NSSUTIL_ArgFetchValue(slotIndex, &next);
            slotIndex += next;
            if (args) {
                nssutil_argDecodeSingleSlotInfo(name, args, &slotInfo[i]);
                i++;
                PORT_Free(args);
            }
        }
        if (name)
            PORT_Free(name);
        slotIndex = NSSUTIL_ArgStrip(slotIndex);
    }
    *retCount = i;
    return slotInfo;
}

/************************************************************************
 * make a new slot specific parameter
 */
/* first make the slot flags */
static char *
nssutil_mkSlotFlags(unsigned long defaultFlags)
{
    char *flags = NULL;
    unsigned int i;
    int j;

    for (i = 0; i < sizeof(defaultFlags) * 8; i++) {
        if (defaultFlags & (1UL << i)) {
            char *string = NULL;

            for (j = 0; j < nssutil_argSlotFlagTableSize; j++) {
                if (nssutil_argSlotFlagTable[j].value == (1UL << i)) {
                    string = nssutil_argSlotFlagTable[j].name;
                    break;
                }
            }
            if (string) {
                if (flags) {
                    char *tmp;
                    tmp = PR_smprintf("%s,%s", flags, string);
                    PR_smprintf_free(flags);
                    flags = tmp;
                } else {
                    flags = PR_smprintf("%s", string);
                }
            }
        }
    }

    return flags;
}

/* now make the root flags */
#define NSSUTIL_MAX_ROOT_FLAG_SIZE sizeof("hasRootCerts") + sizeof("hasRootTrust")
static char *
nssutil_mkRootFlags(PRBool hasRootCerts, PRBool hasRootTrust)
{
    char *flags = (char *)PORT_ZAlloc(NSSUTIL_MAX_ROOT_FLAG_SIZE);
    PRBool first = PR_TRUE;

    PORT_Memset(flags, 0, NSSUTIL_MAX_ROOT_FLAG_SIZE);
    if (hasRootCerts) {
        PORT_Strcat(flags, "hasRootCerts");
        first = PR_FALSE;
    }
    if (hasRootTrust) {
        if (!first)
            PORT_Strcat(flags, ",");
        PORT_Strcat(flags, "hasRootTrust");
    }
    return flags;
}

/* now make a full slot string */
char *
NSSUTIL_MkSlotString(unsigned long slotID, unsigned long defaultFlags,
                     unsigned long timeout, unsigned char askpw_in,
                     PRBool hasRootCerts, PRBool hasRootTrust)
{
    char *askpw, *flags, *rootFlags, *slotString;
    char *flagPair, *rootFlagsPair;

    switch (askpw_in) {
        case 0xff:
            askpw = "every";
            break;
        case 1:
            askpw = "timeout";
            break;
        default:
            askpw = "any";
            break;
    }
    flags = nssutil_mkSlotFlags(defaultFlags);
    rootFlags = nssutil_mkRootFlags(hasRootCerts, hasRootTrust);
    flagPair = nssutil_formatPair("slotFlags", flags, '\'');
    rootFlagsPair = nssutil_formatPair("rootFlags", rootFlags, '\'');
    if (flags)
        PR_smprintf_free(flags);
    if (rootFlags)
        PORT_Free(rootFlags);
    if (defaultFlags & PK11_OWN_PW_DEFAULTS) {
        slotString = PR_smprintf("0x%08lx=[%s askpw=%s timeout=%d %s]",
                                 (PRUint32)slotID, flagPair, askpw, timeout,
                                 rootFlagsPair);
    } else {
        slotString = PR_smprintf("0x%08lx=[%s %s]",
                                 (PRUint32)slotID, flagPair, rootFlagsPair);
    }
    nssutil_freePair(flagPair);
    nssutil_freePair(rootFlagsPair);
    return slotString;
}

/************************************************************************
 * Parse Full module specs into: library, commonName, module parameters,
 * and NSS specifi parameters.
 */
SECStatus
NSSUTIL_ArgParseModuleSpecEx(const char *modulespec, char **lib, char **mod,
                             char **parameters, char **nss,
                             char **config)
{
    int next;
    modulespec = NSSUTIL_ArgStrip(modulespec);

    *lib = *mod = *parameters = *nss = *config = 0;

    while (*modulespec) {
        NSSUTIL_HANDLE_STRING_ARG(modulespec, *lib, "library=", ;)
        NSSUTIL_HANDLE_STRING_ARG(modulespec, *mod, "name=", ;)
        NSSUTIL_HANDLE_STRING_ARG(modulespec, *parameters, "parameters=", ;)
        NSSUTIL_HANDLE_STRING_ARG(modulespec, *nss, "nss=", ;)
        NSSUTIL_HANDLE_STRING_ARG(modulespec, *config, "config=", ;)
        NSSUTIL_HANDLE_FINAL_ARG(modulespec)
    }
    return SECSuccess;
}

/************************************************************************
 * Parse Full module specs into: library, commonName, module parameters,
 * and NSS specifi parameters.
 */
SECStatus
NSSUTIL_ArgParseModuleSpec(const char *modulespec, char **lib, char **mod,
                           char **parameters, char **nss)
{
    int next;
    modulespec = NSSUTIL_ArgStrip(modulespec);

    *lib = *mod = *parameters = *nss = 0;

    while (*modulespec) {
        NSSUTIL_HANDLE_STRING_ARG(modulespec, *lib, "library=", ;)
        NSSUTIL_HANDLE_STRING_ARG(modulespec, *mod, "name=", ;)
        NSSUTIL_HANDLE_STRING_ARG(modulespec, *parameters, "parameters=", ;)
        NSSUTIL_HANDLE_STRING_ARG(modulespec, *nss, "nss=", ;)
        NSSUTIL_HANDLE_FINAL_ARG(modulespec)
    }
    return SECSuccess;
}

/************************************************************************
 * make a new module spec from it's components */
char *
NSSUTIL_MkModuleSpecEx(char *dllName, char *commonName, char *parameters,
                       char *NSS,
                       char *config)
{
    char *moduleSpec;
    char *lib, *name, *param, *nss, *conf;

    /*
     * now the final spec
     */
    lib = nssutil_formatPair("library", dllName, '\"');
    name = nssutil_formatPair("name", commonName, '\"');
    param = nssutil_formatPair("parameters", parameters, '\"');
    nss = nssutil_formatPair("NSS", NSS, '\"');
    if (config) {
        conf = nssutil_formatPair("config", config, '\"');
        moduleSpec = PR_smprintf("%s %s %s %s %s", lib, name, param, nss, conf);
        nssutil_freePair(conf);
    } else {
        moduleSpec = PR_smprintf("%s %s %s %s", lib, name, param, nss);
    }
    nssutil_freePair(lib);
    nssutil_freePair(name);
    nssutil_freePair(param);
    nssutil_freePair(nss);
    return (moduleSpec);
}

/************************************************************************
 * make a new module spec from it's components */
char *
NSSUTIL_MkModuleSpec(char *dllName, char *commonName, char *parameters,
                     char *NSS)
{
    return NSSUTIL_MkModuleSpecEx(dllName, commonName, parameters, NSS, NULL);
}

/* Count the number of name=value parameters in a parameter string. */
static size_t
nssutil_CountParams(const char *params)
{
    size_t count = 0;
    const char *p = params;
    while (*p) {
        p = NSSUTIL_ArgStrip(p);
        if (!*p) {
            break;
        }
        p = NSSUTIL_ArgSkipParameter(p);
        count++;
    }
    return count;
}

/************************************************************************
 * add a single flag to the Flags= section inside the spec's NSS= section */
char *
NSSUTIL_AddNSSFlagToModuleSpec(char *spec, char *addFlag)
{
    const char *prefix = "flags=";
    const size_t prefixLen = strlen(prefix);
    char *lib = NULL, *name = NULL, *param = NULL, *nss = NULL, *conf = NULL;
    char *nss2 = NULL, *result = NULL;
    SECStatus rv;

    rv = NSSUTIL_ArgParseModuleSpecEx(spec, &lib, &name, &param, &nss, &conf);
    if (rv != SECSuccess) {
        return NULL;
    }

    if (nss && NSSUTIL_ArgHasFlag("flags", addFlag, nss)) {
        /* It's already there, nothing to do! */
        PORT_Free(lib);
        PORT_Free(name);
        PORT_Free(param);
        PORT_Free(nss);
        PORT_Free(conf);
        return PORT_Strdup(spec);
    }

    if (!nss || !strlen(nss)) {
        nss2 = PORT_Alloc(prefixLen + strlen(addFlag) + 1);
        PORT_Strcpy(nss2, prefix);
        PORT_Strcat(nss2, addFlag);
    } else {
        const char *iNss = nss;
        PRBool alreadyAdded = PR_FALSE;
        // Allocate enough space for the current string, space delimiters
        // between all existing parameters, a space before the new flags
        // parameter, and a null terminator.
        size_t nParams = nssutil_CountParams(nss);
        size_t maxSize = strlen(nss) + nParams + strlen(addFlag) + prefixLen + 2;
        nss2 = PORT_Alloc(maxSize);
        *nss2 = 0;
        while (*iNss) {
            iNss = NSSUTIL_ArgStrip(iNss);
            if (PORT_Strncasecmp(iNss, prefix, prefixLen) == 0) {
                /* We found an existing Flags= section. */
                char *oldFlags;
                const char *valPtr;
                int valSize;
                valPtr = iNss + prefixLen;
                oldFlags = NSSUTIL_ArgFetchValue(valPtr, &valSize);
                iNss = valPtr + valSize;
                PORT_Strcat(nss2, prefix);
                PORT_Strcat(nss2, oldFlags);
                PORT_Strcat(nss2, ",");
                PORT_Strcat(nss2, addFlag);
                PORT_Strcat(nss2, " ");
                PORT_Free(oldFlags);
                alreadyAdded = PR_TRUE;
                iNss = NSSUTIL_ArgStrip(iNss);
                PORT_Strcat(nss2, iNss); /* remainder of input */
                break;
            } else {
                /* Append this other name=value pair and continue. */
                const char *startOfNext = NSSUTIL_ArgSkipParameter(iNss);
                PORT_Strncat(nss2, iNss, (startOfNext - iNss));
                if (nss2[strlen(nss2) - 1] != ' ') {
                    PORT_Strcat(nss2, " ");
                }
                iNss = startOfNext;
            }
            iNss = NSSUTIL_ArgStrip(iNss);
        }
        if (!alreadyAdded) {
            /* nss wasn't empty, and it didn't contain a Flags section. We can
             * assume that other content from nss has already been added to
             * nss2, which means we already have a trailing space separator. */
            PORT_Strcat(nss2, prefix);
            PORT_Strcat(nss2, addFlag);
        }
    }

    result = NSSUTIL_MkModuleSpecEx(lib, name, param, nss2, conf);
    PORT_Free(lib);
    PORT_Free(name);
    PORT_Free(param);
    PORT_Free(nss);
    PORT_Free(nss2);
    PORT_Free(conf);
    return result;
}

#define NSSUTIL_ARG_FORTEZZA_FLAG "FORTEZZA"
/******************************************************************************
 * Parse the cipher flags from the NSS parameter
 */
void
NSSUTIL_ArgParseCipherFlags(unsigned long *newCiphers, const char *cipherList)
{
    newCiphers[0] = newCiphers[1] = 0;
    if ((cipherList == NULL) || (*cipherList == 0))
        return;

    for (; *cipherList; cipherList = NSSUTIL_ArgNextFlag(cipherList)) {
        if (PORT_Strncasecmp(cipherList, NSSUTIL_ARG_FORTEZZA_FLAG,
                             sizeof(NSSUTIL_ARG_FORTEZZA_FLAG) - 1) == 0) {
            newCiphers[0] |= SECMOD_FORTEZZA_FLAG;
        }

        /* add additional flags here as necessary */
        /* direct bit mapping escape */
        if (*cipherList == 0) {
            if (cipherList[1] == 'l') {
                newCiphers[1] |= atoi(&cipherList[2]);
            } else {
                newCiphers[0] |= atoi(&cipherList[2]);
            }
        }
    }
}

/*********************************************************************
 * make NSS parameter...
 */
/* First make NSS specific flags */
#define MAX_FLAG_SIZE sizeof("internal") + sizeof("FIPS") + sizeof("moduleDB") + \
                          sizeof("moduleDBOnly") + sizeof("critical")
static char *
nssutil_mkNSSFlags(PRBool internal, PRBool isFIPS,
                   PRBool isModuleDB, PRBool isModuleDBOnly, PRBool isCritical)
{
    char *flags = (char *)PORT_ZAlloc(MAX_FLAG_SIZE);
    PRBool first = PR_TRUE;

    PORT_Memset(flags, 0, MAX_FLAG_SIZE);
    if (internal) {
        PORT_Strcat(flags, "internal");
        first = PR_FALSE;
    }
    if (isFIPS) {
        if (!first)
            PORT_Strcat(flags, ",");
        PORT_Strcat(flags, "FIPS");
        first = PR_FALSE;
    }
    if (isModuleDB) {
        if (!first)
            PORT_Strcat(flags, ",");
        PORT_Strcat(flags, "moduleDB");
        first = PR_FALSE;
    }
    if (isModuleDBOnly) {
        if (!first)
            PORT_Strcat(flags, ",");
        PORT_Strcat(flags, "moduleDBOnly");
        first = PR_FALSE;
    }
    if (isCritical) {
        if (!first)
            PORT_Strcat(flags, ",");
        PORT_Strcat(flags, "critical");
    }
    return flags;
}

/* construct the NSS cipher flags */
static char *
nssutil_mkCipherFlags(unsigned long ssl0, unsigned long ssl1)
{
    char *cipher = NULL;
    unsigned int i;

    for (i = 0; i < sizeof(ssl0) * 8; i++) {
        if (ssl0 & (1UL << i)) {
            char *string;
            if ((1UL << i) == SECMOD_FORTEZZA_FLAG) {
                string = PR_smprintf("%s", NSSUTIL_ARG_FORTEZZA_FLAG);
            } else {
                string = PR_smprintf("0h0x%08lx", 1UL << i);
            }
            if (cipher) {
                char *tmp;
                tmp = PR_smprintf("%s,%s", cipher, string);
                PR_smprintf_free(cipher);
                PR_smprintf_free(string);
                cipher = tmp;
            } else {
                cipher = string;
            }
        }
    }
    for (i = 0; i < sizeof(ssl0) * 8; i++) {
        if (ssl1 & (1UL << i)) {
            if (cipher) {
                char *tmp;
                tmp = PR_smprintf("%s,0l0x%08lx", cipher, 1UL << i);
                PR_smprintf_free(cipher);
                cipher = tmp;
            } else {
                cipher = PR_smprintf("0l0x%08lx", 1UL << i);
            }
        }
    }

    return cipher;
}

/* Assemble a full NSS string. */
char *
NSSUTIL_MkNSSString(char **slotStrings, int slotCount, PRBool internal,
                    PRBool isFIPS, PRBool isModuleDB, PRBool isModuleDBOnly,
                    PRBool isCritical, unsigned long trustOrder,
                    unsigned long cipherOrder, unsigned long ssl0, unsigned long ssl1)
{
    int slotLen, i;
    char *slotParams, *ciphers, *nss, *nssFlags;
    const char *tmp;
    char *trustOrderPair, *cipherOrderPair, *slotPair, *cipherPair, *flagPair;

    /* now let's build up the string
     * first the slot infos
     */
    slotLen = 0;
    for (i = 0; i < (int)slotCount; i++) {
        slotLen += PORT_Strlen(slotStrings[i]) + 1;
    }
    slotLen += 1; /* space for the final NULL */

    slotParams = (char *)PORT_ZAlloc(slotLen);
    PORT_Memset(slotParams, 0, slotLen);
    for (i = 0; i < (int)slotCount; i++) {
        PORT_Strcat(slotParams, slotStrings[i]);
        PORT_Strcat(slotParams, " ");
        PR_smprintf_free(slotStrings[i]);
        slotStrings[i] = NULL;
    }

    /*
     * now the NSS structure
     */
    nssFlags = nssutil_mkNSSFlags(internal, isFIPS, isModuleDB, isModuleDBOnly,
                                  isCritical);
    /* for now only the internal module is critical */
    ciphers = nssutil_mkCipherFlags(ssl0, ssl1);

    trustOrderPair = nssutil_formatIntPair("trustOrder", trustOrder,
                                           NSSUTIL_DEFAULT_TRUST_ORDER);
    cipherOrderPair = nssutil_formatIntPair("cipherOrder", cipherOrder,
                                            NSSUTIL_DEFAULT_CIPHER_ORDER);
    slotPair = nssutil_formatPair("slotParams", slotParams, '{'); /* } */
    if (slotParams)
        PORT_Free(slotParams);
    cipherPair = nssutil_formatPair("ciphers", ciphers, '\'');
    if (ciphers)
        PR_smprintf_free(ciphers);
    flagPair = nssutil_formatPair("Flags", nssFlags, '\'');
    if (nssFlags)
        PORT_Free(nssFlags);
    nss = PR_smprintf("%s %s %s %s %s", trustOrderPair,
                      cipherOrderPair, slotPair, cipherPair, flagPair);
    nssutil_freePair(trustOrderPair);
    nssutil_freePair(cipherOrderPair);
    nssutil_freePair(slotPair);
    nssutil_freePair(cipherPair);
    nssutil_freePair(flagPair);
    tmp = NSSUTIL_ArgStrip(nss);
    if (*tmp == '\0') {
        PR_smprintf_free(nss);
        nss = NULL;
    }
    return nss;
}

/*****************************************************************************
 *
 * Private calls for use by softoken and utilmod.c
 */

#define SQLDB "sql:"
#define EXTERNDB "extern:"
#define LEGACY "dbm:"
#define MULTIACCESS "multiaccess:"
#define SECMOD_DB "secmod.db"
const char *
_NSSUTIL_EvaluateConfigDir(const char *configdir,
                           NSSDBType *pdbType, char **appName)
{
    NSSDBType dbType;
    PRBool checkEnvDefaultDB = PR_FALSE;
    *appName = NULL;
    /* force the default */
    dbType = NSS_DB_TYPE_SQL;
    if (configdir == NULL) {
        checkEnvDefaultDB = PR_TRUE;
    } else if (PORT_Strncmp(configdir, MULTIACCESS, sizeof(MULTIACCESS) - 1) == 0) {
        char *cdir;
        dbType = NSS_DB_TYPE_MULTIACCESS;

        *appName = PORT_Strdup(configdir + sizeof(MULTIACCESS) - 1);
        if (*appName == NULL) {
            return configdir;
        }
        cdir = *appName;
        while (*cdir && *cdir != ':') {
            cdir++;
        }
        if (*cdir == ':') {
            *cdir = 0;
            cdir++;
        }
        configdir = cdir;
    } else if (PORT_Strncmp(configdir, SQLDB, sizeof(SQLDB) - 1) == 0) {
        dbType = NSS_DB_TYPE_SQL;
        configdir = configdir + sizeof(SQLDB) - 1;
    } else if (PORT_Strncmp(configdir, EXTERNDB, sizeof(EXTERNDB) - 1) == 0) {
        dbType = NSS_DB_TYPE_EXTERN;
        configdir = configdir + sizeof(EXTERNDB) - 1;
    } else if (PORT_Strncmp(configdir, LEGACY, sizeof(LEGACY) - 1) == 0) {
        dbType = NSS_DB_TYPE_LEGACY;
        configdir = configdir + sizeof(LEGACY) - 1;
    } else {
        checkEnvDefaultDB = PR_TRUE;
    }

    /* look up the default from the environment */
    if (checkEnvDefaultDB) {
        char *defaultType = PR_GetEnvSecure("NSS_DEFAULT_DB_TYPE");
        if (defaultType != NULL) {
            if (PORT_Strncmp(defaultType, SQLDB, sizeof(SQLDB) - 2) == 0) {
                dbType = NSS_DB_TYPE_SQL;
            } else if (PORT_Strncmp(defaultType, EXTERNDB, sizeof(EXTERNDB) - 2) == 0) {
                dbType = NSS_DB_TYPE_EXTERN;
            } else if (PORT_Strncmp(defaultType, LEGACY, sizeof(LEGACY) - 2) == 0) {
                dbType = NSS_DB_TYPE_LEGACY;
            }
        }
    }
    /* if the caller has already set a type, don't change it */
    if (*pdbType == NSS_DB_TYPE_NONE) {
        *pdbType = dbType;
    }
    return configdir;
}

char *
_NSSUTIL_GetSecmodName(const char *param, NSSDBType *dbType, char **appName,
                       char **filename, PRBool *rw)
{
    int next;
    char *configdir = NULL;
    char *secmodName = NULL;
    char *value = NULL;
    const char *save_params = param;
    const char *lconfigdir;
    PRBool noModDB = PR_FALSE;
    param = NSSUTIL_ArgStrip(param);

    while (*param) {
        NSSUTIL_HANDLE_STRING_ARG(param, configdir, "configDir=", ;)
        NSSUTIL_HANDLE_STRING_ARG(param, secmodName, "secmod=", ;)
        NSSUTIL_HANDLE_FINAL_ARG(param)
    }

    *rw = PR_TRUE;
    if (NSSUTIL_ArgHasFlag("flags", "readOnly", save_params)) {
        *rw = PR_FALSE;
    }

    if (!secmodName || *secmodName == '\0') {
        if (secmodName)
            PORT_Free(secmodName);
        secmodName = PORT_Strdup(SECMOD_DB);
    }

    *filename = secmodName;
    lconfigdir = _NSSUTIL_EvaluateConfigDir(configdir, dbType, appName);

    if (NSSUTIL_ArgHasFlag("flags", "noModDB", save_params)) {
        /* there isn't a module db, don't load the legacy support */
        noModDB = PR_TRUE;
        *dbType = NSS_DB_TYPE_SQL;
        PORT_Free(*filename);
        *filename = NULL;
        *rw = PR_FALSE;
    }

    /* only use the renamed secmod for legacy databases */
    if ((*dbType != NSS_DB_TYPE_LEGACY) &&
        (*dbType != NSS_DB_TYPE_MULTIACCESS) &&
        !NSSUTIL_ArgHasFlag("flags", "forceSecmodChoice", save_params)) {
        secmodName = "pkcs11.txt";
    }

    if (noModDB) {
        value = NULL;
    } else if (lconfigdir && lconfigdir[0] != '\0') {
        value = PR_smprintf("%s" NSSUTIL_PATH_SEPARATOR "%s",
                            lconfigdir, secmodName);
    } else {
        value = PR_smprintf("%s", secmodName);
    }
    if (configdir)
        PORT_Free(configdir);
    return value;
}
