/* 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/. */

/* To edit this file, set TABSTOPS to 4 spaces.
 * This is not the normal NSS convention.
 */

#include "modutil.h"
#include "install.h"
#include <plstr.h>
#include "certdb.h" /* for CERT_DB_FILE_VERSION */
#include "nss.h"

static void install_error(char* message);
static char* PR_fgets(char* buf, int size, PRFileDesc* file);
static char* progName;

/* This enum must be kept in sync with the commandNames list */
typedef enum {
    NO_COMMAND,
    ADD_COMMAND,
    CHANGEPW_COMMAND,
    CREATE_COMMAND,
    DEFAULT_COMMAND,
    DELETE_COMMAND,
    DISABLE_COMMAND,
    ENABLE_COMMAND,
    FIPS_COMMAND,
    JAR_COMMAND,
    LIST_COMMAND,
    RAW_LIST_COMMAND,
    RAW_ADD_COMMAND,
    CHKFIPS_COMMAND,
    UNDEFAULT_COMMAND
} Command;

/* This list must be kept in sync with the Command enum */
static char* commandNames[] = {
    "(no command)",
    "-add",
    "-changepw",
    "-create",
    "-default",
    "-delete",
    "-disable",
    "-enable",
    "-fips",
    "-jar",
    "-list",
    "-rawlist",
    "-rawadd",
    "-chkfips",
    "-undefault"
};

/* this enum must be kept in sync with the optionStrings list */
typedef enum {
    ADD_ARG = 0,
    RAW_ADD_ARG,
    CHANGEPW_ARG,
    CIPHERS_ARG,
    CREATE_ARG,
    DBDIR_ARG,
    DBPREFIX_ARG,
    DEFAULT_ARG,
    DELETE_ARG,
    DISABLE_ARG,
    ENABLE_ARG,
    FIPS_ARG,
    FORCE_ARG,
    JAR_ARG,
    LIBFILE_ARG,
    LIST_ARG,
    RAW_LIST_ARG,
    MECHANISMS_ARG,
    NEWPWFILE_ARG,
    PWFILE_ARG,
    SLOT_ARG,
    UNDEFAULT_ARG,
    INSTALLDIR_ARG,
    TEMPDIR_ARG,
    SECMOD_ARG,
    NOCERTDB_ARG,
    STRING_ARG,
    CHKFIPS_ARG,

    NUM_ARGS /* must be last */
} Arg;

/* This list must be kept in sync with the Arg enum */
static char* optionStrings[] = {
    "-add",
    "-rawadd",
    "-changepw",
    "-ciphers",
    "-create",
    "-dbdir",
    "-dbprefix",
    "-default",
    "-delete",
    "-disable",
    "-enable",
    "-fips",
    "-force",
    "-jar",
    "-libfile",
    "-list",
    "-rawlist",
    "-mechanisms",
    "-newpwfile",
    "-pwfile",
    "-slot",
    "-undefault",
    "-installdir",
    "-tempdir",
    "-secmod",
    "-nocertdb",
    "-string",
    "-chkfips",
};

char* msgStrings[] = {
    "FIPS mode enabled.\n",
    "FIPS mode disabled.\n",
    "Using database directory %s...\n",
    "Creating \"%s\"...",
    "Module \"%s\" added to database.\n",
    "Module \"%s\" deleted from database.\n",
    "Token \"%s\" password changed successfully.\n",
    "Incorrect password, try again...\n",
    "Passwords do not match, try again...\n",
    "done.\n",
    "Slot \"%s\" %s.\n",
    "Successfully changed defaults.\n",
    "Successfully changed defaults.\n",
    "\nWARNING: Performing this operation while the browser is running could cause"
    "\ncorruption of your security databases. If the browser is currently running,"
    "\nyou should exit browser before continuing this operation. Type "
    "\n'q <enter>' to abort, or <enter> to continue: ",
    "\nAborting...\n",
    "\nWARNING: Manually adding a module while p11-kit is enabled could cause"
    "\nduplicate module registration in your security database. It is suggested "
    "\nto configure the module through p11-kit configuration file instead.\n"
    "\nType 'q <enter>' to abort, or <enter> to continue: "
};

/* Increment i if doing so would have i still be less than j.  If you
   are able to do this, return 0.  Otherwise return 1. */
#define TRY_INC(i, j) (((i + 1) < j) ? (++i, 0) : 1)

/********************************************************************
 *
 * file-wide variables obtained from the command line
 */
static Command command = NO_COMMAND;
static char* pwFile = NULL;
static char* newpwFile = NULL;
static char* moduleName = NULL;
static char* moduleSpec = NULL;
static char* slotName = NULL;
static char* secmodName = NULL;
static char* tokenName = NULL;
static char* libFile = NULL;
static char* dbdir = NULL;
static char* dbprefix = "";
static char* secmodString = NULL;
static char* mechanisms = NULL;
static char* ciphers = NULL;
static char* fipsArg = NULL;
static char* jarFile = NULL;
static char* installDir = NULL;
static char* tempDir = NULL;
static short force = 0;
static PRBool nocertdb = PR_FALSE;

/*******************************************************************
 *
 * p a r s e _ a r g s
 */
static Error
parse_args(int argc, char* argv[])
{
    int i;
    char* arg;
    int optionType;

    /* Loop over all arguments */
    for (i = 1; i < argc; i++) {
        arg = argv[i];

        /* Make sure this is an option and not some floating argument */
        if (arg[0] != '-') {
            PR_fprintf(PR_STDERR, errStrings[UNEXPECTED_ARG_ERR], argv[i]);
            return UNEXPECTED_ARG_ERR;
        }

        /* Find which option this is */
        for (optionType = 0; optionType < NUM_ARGS; optionType++) {
            if (!strcmp(arg, optionStrings[optionType])) {
                break;
            }
        }

        /* Deal with this specific option */
        switch (optionType) {
            case NUM_ARGS:
            default:
                PR_fprintf(PR_STDERR, errStrings[UNKNOWN_OPTION_ERR], arg);
                return UNKNOWN_OPTION_ERR;
                break;
            case ADD_ARG:
                if (command != NO_COMMAND) {
                    PR_fprintf(PR_STDERR, errStrings[MULTIPLE_COMMAND_ERR], arg);
                    return MULTIPLE_COMMAND_ERR;
                }
                command = ADD_COMMAND;
                if (TRY_INC(i, argc)) {
                    PR_fprintf(PR_STDERR, errStrings[OPTION_NEEDS_ARG_ERR], arg);
                    return OPTION_NEEDS_ARG_ERR;
                }
                moduleName = argv[i];
                break;
            case CHANGEPW_ARG:
                if (command != NO_COMMAND) {
                    PR_fprintf(PR_STDERR, errStrings[MULTIPLE_COMMAND_ERR], arg);
                    return MULTIPLE_COMMAND_ERR;
                }
                command = CHANGEPW_COMMAND;
                if (TRY_INC(i, argc)) {
                    PR_fprintf(PR_STDERR, errStrings[OPTION_NEEDS_ARG_ERR], arg);
                    return OPTION_NEEDS_ARG_ERR;
                }
                tokenName = argv[i];
                break;
            case CIPHERS_ARG:
                if (ciphers != NULL) {
                    PR_fprintf(PR_STDERR, errStrings[DUPLICATE_OPTION_ERR], arg);
                    return DUPLICATE_OPTION_ERR;
                }
                if (TRY_INC(i, argc)) {
                    PR_fprintf(PR_STDERR, errStrings[OPTION_NEEDS_ARG_ERR], arg);
                    return OPTION_NEEDS_ARG_ERR;
                }
                ciphers = argv[i];
                break;
            case CREATE_ARG:
                if (command != NO_COMMAND) {
                    PR_fprintf(PR_STDERR, errStrings[MULTIPLE_COMMAND_ERR], arg);
                    return MULTIPLE_COMMAND_ERR;
                }
                command = CREATE_COMMAND;
                break;
            case DBDIR_ARG:
                if (dbdir != NULL) {
                    PR_fprintf(PR_STDERR, errStrings[DUPLICATE_OPTION_ERR], arg);
                    return DUPLICATE_OPTION_ERR;
                }
                if (TRY_INC(i, argc)) {
                    PR_fprintf(PR_STDERR, errStrings[OPTION_NEEDS_ARG_ERR], arg);
                    return OPTION_NEEDS_ARG_ERR;
                }
                dbdir = argv[i];
                break;
            case DBPREFIX_ARG:
                if (TRY_INC(i, argc)) {
                    PR_fprintf(PR_STDERR, errStrings[OPTION_NEEDS_ARG_ERR], arg);
                    return OPTION_NEEDS_ARG_ERR;
                }
                dbprefix = argv[i];
                break;
            case UNDEFAULT_ARG:
            case DEFAULT_ARG:
                if (command != NO_COMMAND) {
                    PR_fprintf(PR_STDERR, errStrings[MULTIPLE_COMMAND_ERR], arg);
                    return MULTIPLE_COMMAND_ERR;
                }
                if (optionType == DEFAULT_ARG) {
                    command = DEFAULT_COMMAND;
                } else {
                    command = UNDEFAULT_COMMAND;
                }
                if (TRY_INC(i, argc)) {
                    PR_fprintf(PR_STDERR, errStrings[OPTION_NEEDS_ARG_ERR], arg);
                    return OPTION_NEEDS_ARG_ERR;
                }
                moduleName = argv[i];
                break;
            case DELETE_ARG:
                if (command != NO_COMMAND) {
                    PR_fprintf(PR_STDERR, errStrings[MULTIPLE_COMMAND_ERR], arg);
                    return MULTIPLE_COMMAND_ERR;
                }
                command = DELETE_COMMAND;
                if (TRY_INC(i, argc)) {
                    PR_fprintf(PR_STDERR, errStrings[OPTION_NEEDS_ARG_ERR], arg);
                    return OPTION_NEEDS_ARG_ERR;
                }
                moduleName = argv[i];
                break;
            case DISABLE_ARG:
                if (command != NO_COMMAND) {
                    PR_fprintf(PR_STDERR, errStrings[MULTIPLE_COMMAND_ERR], arg);
                    return MULTIPLE_COMMAND_ERR;
                }
                command = DISABLE_COMMAND;
                if (TRY_INC(i, argc)) {
                    PR_fprintf(PR_STDERR, errStrings[OPTION_NEEDS_ARG_ERR], arg);
                    return OPTION_NEEDS_ARG_ERR;
                }
                moduleName = argv[i];
                break;
            case ENABLE_ARG:
                if (command != NO_COMMAND) {
                    PR_fprintf(PR_STDERR, errStrings[MULTIPLE_COMMAND_ERR], arg);
                    return MULTIPLE_COMMAND_ERR;
                }
                command = ENABLE_COMMAND;
                if (TRY_INC(i, argc)) {
                    PR_fprintf(PR_STDERR, errStrings[OPTION_NEEDS_ARG_ERR], arg);
                    return OPTION_NEEDS_ARG_ERR;
                }
                moduleName = argv[i];
                break;
            case FIPS_ARG:
                if (command != NO_COMMAND) {
                    PR_fprintf(PR_STDERR, errStrings[MULTIPLE_COMMAND_ERR], arg);
                    return MULTIPLE_COMMAND_ERR;
                }
                command = FIPS_COMMAND;
                if (TRY_INC(i, argc)) {
                    PR_fprintf(PR_STDERR, errStrings[OPTION_NEEDS_ARG_ERR], arg);
                    return OPTION_NEEDS_ARG_ERR;
                }
                fipsArg = argv[i];
                break;
            case CHKFIPS_ARG:
                if (command != NO_COMMAND) {
                    PR_fprintf(PR_STDERR, errStrings[MULTIPLE_COMMAND_ERR], arg);
                    return MULTIPLE_COMMAND_ERR;
                }
                command = CHKFIPS_COMMAND;
                if (TRY_INC(i, argc)) {
                    PR_fprintf(PR_STDERR, errStrings[OPTION_NEEDS_ARG_ERR], arg);
                    return OPTION_NEEDS_ARG_ERR;
                }
                fipsArg = argv[i];
                break;
            case FORCE_ARG:
                force = 1;
                break;
            case NOCERTDB_ARG:
                nocertdb = PR_TRUE;
                break;
            case INSTALLDIR_ARG:
                if (installDir != NULL) {
                    PR_fprintf(PR_STDERR, errStrings[DUPLICATE_OPTION_ERR], arg);
                    return DUPLICATE_OPTION_ERR;
                }
                if (TRY_INC(i, argc)) {
                    PR_fprintf(PR_STDERR, errStrings[OPTION_NEEDS_ARG_ERR], arg);
                    return OPTION_NEEDS_ARG_ERR;
                }
                installDir = argv[i];
                break;
            case TEMPDIR_ARG:
                if (tempDir != NULL) {
                    PR_fprintf(PR_STDERR, errStrings[DUPLICATE_OPTION_ERR], arg);
                    return DUPLICATE_OPTION_ERR;
                }
                if (TRY_INC(i, argc)) {
                    PR_fprintf(PR_STDERR, errStrings[OPTION_NEEDS_ARG_ERR], arg);
                    return OPTION_NEEDS_ARG_ERR;
                }
                tempDir = argv[i];
                break;
            case JAR_ARG:
                if (command != NO_COMMAND) {
                    PR_fprintf(PR_STDERR, errStrings[MULTIPLE_COMMAND_ERR], arg);
                    return MULTIPLE_COMMAND_ERR;
                }
                command = JAR_COMMAND;
                if (TRY_INC(i, argc)) {
                    PR_fprintf(PR_STDERR, errStrings[OPTION_NEEDS_ARG_ERR], arg);
                    return OPTION_NEEDS_ARG_ERR;
                }
                jarFile = argv[i];
                break;
            case LIBFILE_ARG:
                if (libFile != NULL) {
                    PR_fprintf(PR_STDERR, errStrings[DUPLICATE_OPTION_ERR], arg);
                    return DUPLICATE_OPTION_ERR;
                }
                if (TRY_INC(i, argc)) {
                    PR_fprintf(PR_STDERR, errStrings[OPTION_NEEDS_ARG_ERR], arg);
                    return OPTION_NEEDS_ARG_ERR;
                }
                libFile = argv[i];
                break;
            case LIST_ARG:
                if (command != NO_COMMAND) {
                    PR_fprintf(PR_STDERR, errStrings[MULTIPLE_COMMAND_ERR], arg);
                    return MULTIPLE_COMMAND_ERR;
                }
                command = LIST_COMMAND;
                /* This option may or may not have an argument */
                if ((i + 1 < argc) && (argv[i + 1][0] != '-')) {
                    moduleName = argv[++i];
                }
                break;
            case RAW_LIST_ARG:
                if (command != NO_COMMAND) {
                    PR_fprintf(PR_STDERR, errStrings[MULTIPLE_COMMAND_ERR], arg);
                    return MULTIPLE_COMMAND_ERR;
                }
                command = RAW_LIST_COMMAND;
                /* This option may or may not have an argument */
                if ((i + 1 < argc) && (argv[i + 1][0] != '-')) {
                    moduleName = argv[++i];
                }
                break;
            case RAW_ADD_ARG:
                if (command != NO_COMMAND) {
                    PR_fprintf(PR_STDERR, errStrings[MULTIPLE_COMMAND_ERR], arg);
                    return MULTIPLE_COMMAND_ERR;
                }
                command = RAW_ADD_COMMAND;
                if (TRY_INC(i, argc)) {
                    PR_fprintf(PR_STDERR, errStrings[OPTION_NEEDS_ARG_ERR], arg);
                    return OPTION_NEEDS_ARG_ERR;
                }
                moduleSpec = argv[i];
                break;
            case MECHANISMS_ARG:
                if (mechanisms != NULL) {
                    PR_fprintf(PR_STDERR, errStrings[DUPLICATE_OPTION_ERR], arg);
                    return DUPLICATE_OPTION_ERR;
                }
                if (TRY_INC(i, argc)) {
                    PR_fprintf(PR_STDERR, errStrings[OPTION_NEEDS_ARG_ERR], arg);
                    return OPTION_NEEDS_ARG_ERR;
                }
                mechanisms = argv[i];
                break;
            case NEWPWFILE_ARG:
                if (newpwFile != NULL) {
                    PR_fprintf(PR_STDERR, errStrings[DUPLICATE_OPTION_ERR], arg);
                    return DUPLICATE_OPTION_ERR;
                }
                if (TRY_INC(i, argc)) {
                    PR_fprintf(PR_STDERR, errStrings[OPTION_NEEDS_ARG_ERR], arg);
                    return OPTION_NEEDS_ARG_ERR;
                }
                newpwFile = argv[i];
                break;
            case PWFILE_ARG:
                if (pwFile != NULL) {
                    PR_fprintf(PR_STDERR, errStrings[DUPLICATE_OPTION_ERR], arg);
                    return DUPLICATE_OPTION_ERR;
                }
                if (TRY_INC(i, argc)) {
                    PR_fprintf(PR_STDERR, errStrings[OPTION_NEEDS_ARG_ERR], arg);
                    return OPTION_NEEDS_ARG_ERR;
                }
                pwFile = argv[i];
                break;
            case SLOT_ARG:
                if (slotName != NULL) {
                    PR_fprintf(PR_STDERR, errStrings[DUPLICATE_OPTION_ERR], arg);
                    return DUPLICATE_OPTION_ERR;
                }
                if (TRY_INC(i, argc)) {
                    PR_fprintf(PR_STDERR, errStrings[OPTION_NEEDS_ARG_ERR], arg);
                    return OPTION_NEEDS_ARG_ERR;
                }
                slotName = argv[i];
                break;
            case SECMOD_ARG:
                if (secmodName != NULL) {
                    PR_fprintf(PR_STDERR, errStrings[DUPLICATE_OPTION_ERR], arg);
                    return DUPLICATE_OPTION_ERR;
                }
                if (TRY_INC(i, argc)) {
                    PR_fprintf(PR_STDERR, errStrings[OPTION_NEEDS_ARG_ERR], arg);
                    return OPTION_NEEDS_ARG_ERR;
                }
                secmodName = argv[i];
                break;
            case STRING_ARG:
                if (secmodString != NULL) {
                    PR_fprintf(PR_STDERR, errStrings[DUPLICATE_OPTION_ERR], arg);
                    return DUPLICATE_OPTION_ERR;
                }
                if (TRY_INC(i, argc)) {
                    PR_fprintf(PR_STDERR, errStrings[OPTION_NEEDS_ARG_ERR], arg);
                    return OPTION_NEEDS_ARG_ERR;
                }
                secmodString = argv[i];
                break;
        }
    }
    return SUCCESS;
}

/************************************************************************
 *
 * v e r i f y _ p a r a m s
 */
static Error
verify_params()
{
    switch (command) {
        case ADD_COMMAND:
            if (libFile == NULL) {
                PR_fprintf(PR_STDERR, errStrings[MISSING_PARAM_ERR],
                           commandNames[ADD_COMMAND], optionStrings[LIBFILE_ARG]);
                return MISSING_PARAM_ERR;
            }
            break;
        case CHANGEPW_COMMAND:
            break;
        case CREATE_COMMAND:
            break;
        case DELETE_COMMAND:
            break;
        case DISABLE_COMMAND:
            break;
        case ENABLE_COMMAND:
            break;
        case FIPS_COMMAND:
        case CHKFIPS_COMMAND:
            if (PL_strcasecmp(fipsArg, "true") &&
                PL_strcasecmp(fipsArg, "false")) {
                PR_fprintf(PR_STDERR, errStrings[INVALID_FIPS_ARG]);
                return INVALID_FIPS_ARG;
            }
            break;
        case JAR_COMMAND:
            if (installDir == NULL) {
                PR_fprintf(PR_STDERR, errStrings[MISSING_PARAM_ERR],
                           commandNames[JAR_COMMAND], optionStrings[INSTALLDIR_ARG]);
                return MISSING_PARAM_ERR;
            }
            break;
        case LIST_COMMAND:
        case RAW_LIST_COMMAND:
            break;
        case RAW_ADD_COMMAND:
            break;
        case UNDEFAULT_COMMAND:
        case DEFAULT_COMMAND:
            if (mechanisms == NULL) {
                PR_fprintf(PR_STDERR, errStrings[MISSING_PARAM_ERR],
                           commandNames[command], optionStrings[MECHANISMS_ARG]);
                return MISSING_PARAM_ERR;
            }
            break;
        default:
            /* Ignore this here */
            break;
    }

    return SUCCESS;
}

/********************************************************************
 *
 * i n i t _ c r y p t o
 *
 * Does crypto initialization that all commands will require.
 * If -nocertdb option is specified, don't open key or cert db (we don't
 * need them if we aren't going to be verifying signatures).  This is
 * because serverland doesn't always have cert and key database files
 * available.
 *
 * This function is ill advised. Names and locations of databases are
 * private to NSS proper. Such functions only confuse other users.
 *
 */
static Error
check_crypto(PRBool create, PRBool readOnly)
{
    char* dir;
    char* moddbname = NULL;
    Error retval;
    static const char multiaccess[] = { "multiaccess:" };

    dir = SECU_ConfigDirectory(dbdir); /* dir is never NULL */
    if (dir[0] == '\0') {
        PR_fprintf(PR_STDERR, errStrings[NO_DBDIR_ERR]);
        retval = NO_DBDIR_ERR;
        goto loser;
    }
    if (strncmp(dir, multiaccess, sizeof multiaccess - 1) == 0) {
        /* won't attempt to handle the multiaccess case. */
        return SUCCESS;
    }
#ifdef notdef
    /* Make sure db directory exists and is readable */
    if (PR_Access(dir, PR_ACCESS_EXISTS) != PR_SUCCESS) {
        PR_fprintf(PR_STDERR, errStrings[DIR_DOESNT_EXIST_ERR], dir);
        retval = DIR_DOESNT_EXIST_ERR;
        goto loser;
    } else if (PR_Access(dir, PR_ACCESS_READ_OK) != PR_SUCCESS) {
        PR_fprintf(PR_STDERR, errStrings[DIR_NOT_READABLE_ERR], dir);
        retval = DIR_NOT_READABLE_ERR;
        goto loser;
    }

    if (secmodName == NULL) {
        secmodName = "secmod.db";
    }

    moddbname = PR_smprintf("%s/%s", dir, secmodName);
    if (!moddbname)
        return OUT_OF_MEM_ERR;

    /* Check for the proper permissions on databases */
    if (create) {
        /* Make sure dbs don't already exist, and the directory is
            writeable */
        if (PR_Access(moddbname, PR_ACCESS_EXISTS) == PR_SUCCESS) {
            PR_fprintf(PR_STDERR, errStrings[FILE_ALREADY_EXISTS_ERR],
                       moddbname);
            retval = FILE_ALREADY_EXISTS_ERR;
            goto loser;
        } else if (PR_Access(dir, PR_ACCESS_WRITE_OK) != PR_SUCCESS) {
            PR_fprintf(PR_STDERR, errStrings[DIR_NOT_WRITEABLE_ERR], dir);
            retval = DIR_NOT_WRITEABLE_ERR;
            goto loser;
        }
    } else {
        /* Make sure dbs are readable and writeable */
        if (PR_Access(moddbname, PR_ACCESS_READ_OK) != PR_SUCCESS) {
            PR_fprintf(PR_STDERR, errStrings[FILE_NOT_READABLE_ERR], moddbname);
            retval = FILE_NOT_READABLE_ERR;
            goto loser;
        }

        /* Check for write access if we'll be making changes */
        if (!readOnly) {
            if (PR_Access(moddbname, PR_ACCESS_WRITE_OK) != PR_SUCCESS) {
                PR_fprintf(PR_STDERR, errStrings[FILE_NOT_WRITEABLE_ERR],
                           moddbname);
                retval = FILE_NOT_WRITEABLE_ERR;
                goto loser;
            }
        }
        PR_fprintf(PR_STDOUT, msgStrings[USING_DBDIR_MSG],
                   SECU_ConfigDirectory(NULL));
    }
#endif
    retval = SUCCESS;
loser:
    if (moddbname) {
        PR_Free(moddbname);
    }
    return retval;
}

static Error
init_crypto(PRBool create, PRBool readOnly)
{

    PRUint32 flags = 0;
    SECStatus rv;
    Error retval;
    /* Open/create key database */

    if (readOnly)
        flags |= NSS_INIT_READONLY;
    if (nocertdb)
        flags |= NSS_INIT_NOCERTDB;
    rv = NSS_Initialize(SECU_ConfigDirectory(NULL), dbprefix, dbprefix,
                        secmodName, flags);
    if (rv != SECSuccess) {
        SECU_PrintPRandOSError(progName);
        retval = NSS_INITIALIZE_FAILED_ERR;
    } else
        retval = SUCCESS;

    return retval;
}

/*************************************************************************
 *
 * u s a g e
 */
static void
usage()
{
    PR_fprintf(PR_STDOUT,
               "\nNetscape Cryptographic Module Utility\n"
               "Usage: modutil [command] [options]\n\n"
               "                            COMMANDS\n"
               "---------------------------------------------------------------------------\n"
               "-add MODULE_NAME                 Add the named module to the module database\n"
               "   -libfile LIBRARY_FILE         The name of the file (.so or .dll)\n"
               "                                 containing the implementation of PKCS #11\n"
               "   [-ciphers CIPHER_LIST]        Enable the given ciphers on this module\n"
               "   [-mechanisms MECHANISM_LIST]  Make the module a default provider of the\n"
               "                                 given mechanisms\n"
               "   [-string CONFIG_STRING]       Pass a configuration string to this module\n"
               "-changepw TOKEN                  Change the password on the named token\n"
               "   [-pwfile FILE]                The old password is in this file\n"
               "   [-newpwfile FILE]             The new password is in this file\n"
               "-chkfips [ true | false ]        If true, verify  FIPS mode.  If false,\n"
               "                                 verify not FIPS mode\n"
               "-create                          Create a new set of security databases\n"
               "-default MODULE                  Make the given module a default provider\n"
               "   -mechanisms MECHANISM_LIST    of the given mechanisms\n"
               "   [-slot SLOT]                  limit change to only the given slot\n"
               "-delete MODULE                   Remove the named module from the module\n"
               "                                 database\n"
               "-disable MODULE                  Disable the named module\n"
               "   [-slot SLOT]                  Disable only the named slot on the module\n"
               "-enable MODULE                   Enable the named module\n"
               "   [-slot SLOT]                  Enable only the named slot on the module\n"
               "-fips [ true | false ]           If true, enable FIPS mode.  If false,\n"
               "                                 disable FIPS mode\n"
               "-force                           Do not run interactively\n"
               "-jar JARFILE                     Install a PKCS #11 module from the given\n"
               "                                 JAR file in the PKCS #11 JAR format\n"
               "   -installdir DIR               Use DIR as the root directory of the\n"
               "                                 installation\n"
               "   [-tempdir DIR]                Use DIR as the temporary installation\n"
               "                                 directory. If not specified, the current\n"
               "                                 directory is used\n"
               "-list [MODULE]                   Lists information about the specified module\n"
               "                                 or about all modules if none is specified\n"
               "-rawadd MODULESPEC               Add module spec string to secmod DB\n"
               "-rawlist [MODULE]                Display module spec(s) for one or all\n"
               "                                 loadable modules\n"
               "-undefault MODULE                The given module is NOT a default provider\n"
               "   -mechanisms MECHANISM_LIST    of the listed mechanisms\n"
               "   [-slot SLOT]                  limit change to only the given slot\n"
               "---------------------------------------------------------------------------\n"
               "\n"
               "                             OPTIONS\n"
               "---------------------------------------------------------------------------\n"
               "-dbdir DIR                       Directory DIR contains the security databases\n"
               "-dbprefix prefix                 Prefix for the security databases\n"
               "-nocertdb                        Do not load certificate or key databases. No\n"
               "                                 verification will be performed on JAR files.\n"
               "-secmod secmodName               Name of the security modules file\n"
               "---------------------------------------------------------------------------\n"
               "\n"
               "Mechanism lists are colon-separated.  The following mechanisms are recognized:\n"
               "RSA, DSA, DH, RC2, RC4, RC5, AES, CAMELLIA, DES, MD2, MD5, SHA1, SHA256, SHA512,\n"
               "SSL, TLS, RANDOM, and FRIENDLY\n"
               "\n"
               "Cipher lists are colon-separated.  The following ciphers are recognized:\n"
               "\n"
               "\nQuestions or bug reports should be sent to modutil-support@netscape.com.\n");
}

/*************************************************************************
 *
 * m a i n
 */
int
main(int argc, char* argv[])
{
    int errcode = SUCCESS;
    PRBool createdb, readOnly;
#define STDINBUF_SIZE 80
    char stdinbuf[STDINBUF_SIZE];

    progName = strrchr(argv[0], '/');
    progName = progName ? progName + 1 : argv[0];

    PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);

    if (parse_args(argc, argv) != SUCCESS) {
        usage();
        errcode = INVALID_USAGE_ERR;
        goto loser;
    }

    if (verify_params() != SUCCESS) {
        usage();
        errcode = INVALID_USAGE_ERR;
        goto loser;
    }

    if (command == NO_COMMAND) {
        PR_fprintf(PR_STDERR, errStrings[NO_COMMAND_ERR]);
        usage();
        errcode = INVALID_USAGE_ERR;
        goto loser;
    }

    /* Set up crypto stuff */
    createdb = command == CREATE_COMMAND;
    readOnly = ((command == LIST_COMMAND) ||
                (command == CHKFIPS_COMMAND) ||
                (command == RAW_LIST_COMMAND));

    /* Make sure browser is not running if we're writing to a database */
    /* Do this before initializing crypto */
    if (!readOnly && !force) {
        char* response;

        PR_fprintf(PR_STDOUT, msgStrings[BROWSER_RUNNING_MSG]);
        if (!PR_fgets(stdinbuf, STDINBUF_SIZE, PR_STDIN)) {
            PR_fprintf(PR_STDERR, errStrings[STDIN_READ_ERR]);
            errcode = STDIN_READ_ERR;
            goto loser;
        }
        if ((response = strtok(stdinbuf, " \r\n\t"))) {
            if (!PL_strcasecmp(response, "q")) {
                PR_fprintf(PR_STDOUT, msgStrings[ABORTING_MSG]);
                errcode = SUCCESS;
                goto loser;
            }
        }
        PR_fprintf(PR_STDOUT, "\n");
    }

    errcode = check_crypto(createdb, readOnly);
    if (errcode != SUCCESS) {
        goto loser;
    }

    if ((command == RAW_LIST_COMMAND) || (command == RAW_ADD_COMMAND)) {
        if (!moduleName) {
            char *readOnlyStr, *noCertDBStr, *sep;
            if (!secmodName)
                secmodName = "secmod.db";
            if (!dbprefix)
                dbprefix = "";
            sep = ((command == RAW_LIST_COMMAND) && nocertdb) ? "," : " ";
            readOnlyStr = (command == RAW_LIST_COMMAND) ? "readOnly" : "";
            noCertDBStr = nocertdb ? "noCertDB" : "";
            SECU_ConfigDirectory(dbdir);

            moduleName = PR_smprintf(
                "name=\"NSS default Module DB\" parameters=\"configdir=%s certPrefix=%s "
                "keyPrefix=%s secmod=%s flags=%s%s%s\" NSS=\"flags=internal,moduleDB,"
                "moduleDBOnly,critical\"",
                SECU_ConfigDirectory(NULL), dbprefix, dbprefix,
                secmodName, readOnlyStr, sep, noCertDBStr);
        }
        if (command == RAW_LIST_COMMAND) {
            errcode = RawListModule(moduleName);
        } else {
            PORT_Assert(moduleSpec);
            errcode = RawAddModule(moduleName, moduleSpec);
        }
        goto loser;
    }

    errcode = init_crypto(createdb, readOnly);
    if (errcode != SUCCESS) {
        goto loser;
    }

    errcode = LoadMechanismList();
    if (errcode != SUCCESS) {
        goto loser;
    }

    /* Warn if we are adding a module while p11-kit is enabled in the
     * database. */
    if ((command == ADD_COMMAND || command == RAW_ADD_COMMAND) &&
        IsP11KitEnabled()) {
        char* response;

        PR_fprintf(PR_STDOUT, msgStrings[P11_KIT_ENABLED_MSG]);
        if (!PR_fgets(stdinbuf, STDINBUF_SIZE, PR_STDIN)) {
            PR_fprintf(PR_STDERR, errStrings[STDIN_READ_ERR]);
            errcode = STDIN_READ_ERR;
            goto loser;
        }
        if ((response = strtok(stdinbuf, " \r\n\t"))) {
            if (!PL_strcasecmp(response, "q")) {
                PR_fprintf(PR_STDOUT, msgStrings[ABORTING_MSG]);
                errcode = SUCCESS;
                goto loser;
            }
        }
        PR_fprintf(PR_STDOUT, "\n");
    }

    /* Execute the command */
    switch (command) {
        case ADD_COMMAND:
            errcode = AddModule(moduleName, libFile, ciphers, mechanisms, secmodString);
            break;
        case CHANGEPW_COMMAND:
            errcode = ChangePW(tokenName, pwFile, newpwFile);
            break;
        case CREATE_COMMAND:
            errcode = InitPW();
            break;
        case DEFAULT_COMMAND:
            errcode = SetDefaultModule(moduleName, slotName, mechanisms);
            break;
        case DELETE_COMMAND:
            errcode = DeleteModule(moduleName);
            break;
        case DISABLE_COMMAND:
            errcode = EnableModule(moduleName, slotName, PR_FALSE);
            break;
        case ENABLE_COMMAND:
            errcode = EnableModule(moduleName, slotName, PR_TRUE);
            break;
        case FIPS_COMMAND:
            errcode = FipsMode(fipsArg);
            break;
        case CHKFIPS_COMMAND:
            errcode = ChkFipsMode(fipsArg);
            break;
        case JAR_COMMAND:
            Pk11Install_SetErrorHandler(install_error);
            errcode = Pk11Install_DoInstall(jarFile, installDir, tempDir,
                                            PR_STDOUT, force, nocertdb);
            break;
        case LIST_COMMAND:
            if (moduleName) {
                errcode = ListModule(moduleName);
            } else {
                errcode = ListModules();
            }
            break;
        case UNDEFAULT_COMMAND:
            errcode = UnsetDefaultModule(moduleName, slotName, mechanisms);
            break;
        default:
            PR_fprintf(PR_STDERR, "This command is not supported yet.\n");
            errcode = INVALID_USAGE_ERR;
            break;
    }

    if (NSS_Shutdown() != SECSuccess) {
        exit(1);
    }

loser:
    PR_Cleanup();
    return errcode;
}

/************************************************************************
 *
 * i n s t a l l _ e r r o r
 *
 * Callback function to handle errors in PK11 JAR file installation.
 */
static void
install_error(char* message)
{
    PR_fprintf(PR_STDERR, "Install error: %s\n", message);
}

/*************************************************************************
 *
 * o u t _ o f _ m e m o r y
 */
void
out_of_memory(void)
{
    PR_fprintf(PR_STDERR, errStrings[OUT_OF_MEM_ERR]);
    exit(OUT_OF_MEM_ERR);
}

/**************************************************************************
 *
 * P R _ f g e t s
 *
 * fgets implemented with NSPR.
 */
static char*
PR_fgets(char* buf, int size, PRFileDesc* file)
{
    int i;
    int status;
    char c;

    i = 0;
    while (i < size - 1) {
        status = PR_Read(file, (void*)&c, 1);
        if (status == -1) {
            return NULL;
        } else if (status == 0) {
            break;
        }
        buf[i++] = c;
        if (c == '\n') {
            break;
        }
    }
    buf[i] = '\0';

    return buf;
}
