/* 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"
};

/* 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;
	}

	/* 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:
		/* The work was already done in init_crypto() */
		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;
}
