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

/*
 *  SIGNTOOL
 *
 *  A command line tool to create manifest files
 *  from a directory hierarchy. It is assumed that
 *  the tree will be equivalent to what resides
 *  or will reside in an archive. 
 *
 * 
 */

#include "nss.h"
#include "signtool.h"
#include "prmem.h"
#include "prio.h"

/***********************************************************************
 * Global Variable Definitions
 */
char	*progName; /* argv[0] */

/* password data */
secuPWData  pwdata = { PW_NONE, 0 };

/* directories or files to exclude in descent */
PLHashTable *excludeDirs = NULL;
static PRBool exclusionsGiven = PR_FALSE;

/* zatharus is the man who knows no time, dies tragic death */
int	no_time = 0;

/* -b basename of .rsa, .sf files */
char	*base = DEFAULT_BASE_NAME;

/* Only sign files with this extension */
PLHashTable *extensions = NULL;
PRBool extensionsGiven = PR_FALSE;

char	*scriptdir = NULL;

int	verbosity = 0;

PRFileDesc *outputFD = NULL, *errorFD = NULL;

int	errorCount = 0, warningCount = 0;

int	compression_level = DEFAULT_COMPRESSION_LEVEL;
PRBool compression_level_specified = PR_FALSE;

int	xpi_arc = 0;

/* Command-line arguments */
static char	*genkey = NULL;
static char	*verify = NULL;
static char	*zipfile = NULL;
static char	*cert_dir = NULL;
static int	javascript = 0;
static char	*jartree = NULL;
static char	*keyName = NULL;
static char	*metafile = NULL;
static char	*install_script = NULL;
static int	list_certs = 0;
static int	list_modules = 0;
static int	optimize = 0;
static int	enableOCSP = 0;
static char	*tell_who = NULL;
static char	*outfile = NULL;
static char	*cmdFile = NULL;
static PRBool noRecurse = PR_FALSE;
static PRBool leaveArc = PR_FALSE;
static int	keySize = -1;
static char	*token = NULL;

typedef enum {
    UNKNOWN_OPT,
    HELP_OPT,
    LONG_HELP_OPT,
    BASE_OPT,
    COMPRESSION_OPT,
    CERT_DIR_OPT,
    EXTENSION_OPT,
    INSTALL_SCRIPT_OPT,
    SCRIPTDIR_OPT,
    CERTNAME_OPT,
    LIST_OBJSIGN_CERTS_OPT,
    LIST_ALL_CERTS_OPT,
    METAFILE_OPT,
    OPTIMIZE_OPT,
    ENABLE_OCSP_OPT,
    PASSWORD_OPT,
    VERIFY_OPT,
    WHO_OPT,
    EXCLUDE_OPT,
    NO_TIME_OPT,
    JAVASCRIPT_OPT,
    ZIPFILE_OPT,
    GENKEY_OPT,
    MODULES_OPT,
    NORECURSE_OPT,
    SIGNDIR_OPT,
    OUTFILE_OPT,
    COMMAND_FILE_OPT,
    LEAVE_ARC_OPT,
    VERBOSITY_OPT,
    KEYSIZE_OPT,
    TOKEN_OPT,
    XPI_ARC_OPT
} 


OPT_TYPE;

typedef enum {
    DUPLICATE_OPTION_ERR = 0,
    OPTION_NEEDS_ARG_ERR
} 


Error;

static char	*errStrings[] = {
    "warning: %s option specified more than once.\n"
    "Only last specification will be used.\n",
    "ERROR: option \"%s\" requires an argument.\n"
};


static int	ProcessOneOpt(OPT_TYPE type, char *arg);

/*********************************************************************
 *
 * P r o c e s s C o m m a n d F i l e
 */
int
ProcessCommandFile()
{
    PRFileDesc * fd;
#define CMD_FILE_BUFSIZE 1024
    char	buf[CMD_FILE_BUFSIZE];
    char	*equals;
    int	linenum = 0;
    int	retval = -1;
    OPT_TYPE type;

    fd = PR_Open(cmdFile, PR_RDONLY, 0777);
    if (!fd) {
	PR_fprintf(errorFD, "ERROR: Unable to open command file %s.\n");
	errorCount++;
	return - 1;
    }

    while (pr_fgets(buf, CMD_FILE_BUFSIZE, fd)) {
	char	*eol;
	linenum++;

	/* Chop off final newline */
	eol = PL_strchr(buf, '\r');
	if (!eol) {
	    eol = PL_strchr(buf, '\n');
	}
	if (eol) 
	    *eol = '\0';

	equals = PL_strchr(buf, '=');
	if (!equals) {
	    continue;
	}

	*equals = '\0';
	equals++;

	/* Now buf points to the attribute, and equals points to the value. */

	/* This is pretty straightforward, just deal with whatever attribute
		 * this is */
	if (!PL_strcasecmp(buf, "basename")) {
	    type = BASE_OPT;
	} else if (!PL_strcasecmp(buf, "compression")) {
	    type = COMPRESSION_OPT;
	} else if (!PL_strcasecmp(buf, "certdir")) {
	    type = CERT_DIR_OPT;
	} else if (!PL_strcasecmp(buf, "extension")) {
	    type = EXTENSION_OPT;
	} else if (!PL_strcasecmp(buf, "generate")) {
	    type = GENKEY_OPT;
	} else if (!PL_strcasecmp(buf, "installScript")) {
	    type = INSTALL_SCRIPT_OPT;
	} else if (!PL_strcasecmp(buf, "javascriptdir")) {
	    type = SCRIPTDIR_OPT;
	} else if (!PL_strcasecmp(buf, "htmldir")) {
	    type = JAVASCRIPT_OPT;
	    if (jartree) {
		PR_fprintf(errorFD,
		    "warning: directory to be signed specified more than once."
		    " Only last specification will be used.\n");
		warningCount++;
		PR_Free(jartree); 
		jartree = NULL;
	    }
	    jartree = PL_strdup(equals);
	} else if (!PL_strcasecmp(buf, "certname")) {
	    type = CERTNAME_OPT;
	} else if (!PL_strcasecmp(buf, "signdir")) {
	    type = SIGNDIR_OPT;
	} else if (!PL_strcasecmp(buf, "list")) {
	    type = LIST_OBJSIGN_CERTS_OPT;
	} else if (!PL_strcasecmp(buf, "listall")) {
	    type = LIST_ALL_CERTS_OPT;
	} else if (!PL_strcasecmp(buf, "metafile")) {
	    type = METAFILE_OPT;
	} else if (!PL_strcasecmp(buf, "modules")) {
	    type = MODULES_OPT;
	} else if (!PL_strcasecmp(buf, "optimize")) {
	    type = OPTIMIZE_OPT;
	} else if (!PL_strcasecmp(buf, "ocsp")) {
	    type = ENABLE_OCSP_OPT;
	} else if (!PL_strcasecmp(buf, "password")) {
	    type = PASSWORD_OPT;
	} else if (!PL_strcasecmp(buf, "verify")) {
	    type = VERIFY_OPT;
	} else if (!PL_strcasecmp(buf, "who")) {
	    type = WHO_OPT;
	} else if (!PL_strcasecmp(buf, "exclude")) {
	    type = EXCLUDE_OPT;
	} else if (!PL_strcasecmp(buf, "notime")) {
	    type = NO_TIME_OPT;
	} else if (!PL_strcasecmp(buf, "jarfile")) {
	    type = ZIPFILE_OPT;
	} else if (!PL_strcasecmp(buf, "outfile")) {
	    type = OUTFILE_OPT;
	} else if (!PL_strcasecmp(buf, "leavearc")) {
	    type = LEAVE_ARC_OPT;
	} else if (!PL_strcasecmp(buf, "verbosity")) {
	    type = VERBOSITY_OPT;
	} else if (!PL_strcasecmp(buf, "keysize")) {
	    type = KEYSIZE_OPT;
	} else if (!PL_strcasecmp(buf, "token")) {
	    type = TOKEN_OPT;
	} else if (!PL_strcasecmp(buf, "xpi")) {
	    type = XPI_ARC_OPT;
	} else {
	    PR_fprintf(errorFD,
	        "warning: unknown attribute \"%s\" in command file, line %d.\n",
	         					buf, linenum);
	    warningCount++;
	    type = UNKNOWN_OPT;
	}

	/* Process the option, whatever it is */
	if (type != UNKNOWN_OPT) {
	    if (ProcessOneOpt(type, equals) == -1) {
		goto finish;
	    }
	}
    }

    retval = 0;

finish:
    PR_Close(fd);
    return retval;
}


/*********************************************************************
 *
 * p a r s e _ a r g s
 */
static int	
parse_args(int argc, char *argv[])
{
    char	*opt;
    char	*arg;
    int	needsInc = 0;
    int	i;
    OPT_TYPE type;

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

	if (opt[0] == '-') {
	    if (opt[1] == '-') {
		/* word option */
		if (i < argc - 1) {
		    needsInc = 1;
		    arg = argv[i+1];
		} else {
		    arg = NULL;
		}

		if ( !PL_strcasecmp(opt + 2, "norecurse")) {
		    type = NORECURSE_OPT;
		} else if ( !PL_strcasecmp(opt + 2, "leavearc")) {
		    type = LEAVE_ARC_OPT;
		} else if ( !PL_strcasecmp(opt + 2, "verbosity")) {
		    type = VERBOSITY_OPT;
		} else if ( !PL_strcasecmp(opt + 2, "outfile")) {
		    type = OUTFILE_OPT;
		} else if ( !PL_strcasecmp(opt + 2, "keysize")) {
		    type = KEYSIZE_OPT;
		} else if ( !PL_strcasecmp(opt + 2, "token")) {
		    type = TOKEN_OPT;
		} else {
		    PR_fprintf(errorFD, "warning: unknown option: %s\n",
		         opt);
		    warningCount++;
		    type = UNKNOWN_OPT;
		}
	    } else {
		/* char option */
		if (opt[2] != '\0') {
		    arg = opt + 2;
		} else if (i < argc - 1) {
		    needsInc = 1;
		    arg = argv[i+1];
		} else {
		    arg = NULL;
		}

		switch (opt[1]) {
		case 'b':
		    type = BASE_OPT;
		    break;
		case 'c':
		    type = COMPRESSION_OPT;
		    break;
		case 'd':
		    type = CERT_DIR_OPT;
		    break;
		case 'e':
		    type = EXTENSION_OPT;
		    break;
		case 'f':
		    type = COMMAND_FILE_OPT;
		    break;
		case 'h':
		    type = HELP_OPT;
		    break;
		case 'H':
		    type = LONG_HELP_OPT;
		    break;
		case 'i':
		    type = INSTALL_SCRIPT_OPT;
		    break;
		case 'j':
		    type = SCRIPTDIR_OPT;
		    break;
		case 'k':
		    type = CERTNAME_OPT;
		    break;
		case 'l':
		    type = LIST_OBJSIGN_CERTS_OPT;
		    break;
		case 'L':
		    type = LIST_ALL_CERTS_OPT;
		    break;
		case 'm':
		    type = METAFILE_OPT;
		    break;
		case 'o':
		    type = OPTIMIZE_OPT;
		    break;
		case 'O':
		    type = ENABLE_OCSP_OPT;
		    break;
		case 'p':
		    type = PASSWORD_OPT;
		    break;
		case 'v':
		    type = VERIFY_OPT;
		    break;
		case 'w':
		    type = WHO_OPT;
		    break;
		case 'x':
		    type = EXCLUDE_OPT;
		    break;
		case 'X':
		    type = XPI_ARC_OPT;
		    break;
		case 'z':
		    type = NO_TIME_OPT;
		    break;
		case 'J':
		    type = JAVASCRIPT_OPT;
		    break;
		case 'Z':
		    type = ZIPFILE_OPT;
		    break;
		case 'G':
		    type = GENKEY_OPT;
		    break;
		case 'M':
		    type = MODULES_OPT;
		    break;
		case 's':
		    type = KEYSIZE_OPT;
		    break;
		case 't':
		    type = TOKEN_OPT;
		    break;
		default:
		    type = UNKNOWN_OPT;
		    PR_fprintf(errorFD, "warning: unrecognized option: -%c.\n",
		         
		        opt[1]);
		    warningCount++;
		    break;
		}
	    }
	} else {
	    type = UNKNOWN_OPT;
	    if (i == argc - 1) {
		if (jartree) {
		    PR_fprintf(errorFD,
		        "warning: directory to be signed specified more than once.\n"
		        " Only last specification will be used.\n");
		    warningCount++;
		    PR_Free(jartree); 
		    jartree = NULL;
		}
		jartree = PL_strdup(opt);
	    } else {
		PR_fprintf(errorFD, "warning: unrecognized option: %s\n", opt);
		warningCount++;
	    }
	}

	if (type != UNKNOWN_OPT) {
	    short	ateArg = ProcessOneOpt(type, arg);
	    if (ateArg == -1) {
		/* error */
		return - 1;
	    } 
	    if (ateArg && needsInc) {
		i++;
	    }
	}
    }

    return 0;
}


/*********************************************************************
 *
 * P r o c e s s O n e O p t
 *
 * Since options can come from different places (command file, word options,
 * char options), this is a central function that is called to deal with
 * them no matter where they come from.
 *
 * type is the type of option.
 * arg is the argument to the option, possibly NULL.
 * Returns 1 if the argument was eaten, 0 if it wasn't, and -1 for error.
 */
static int	
ProcessOneOpt(OPT_TYPE type, char *arg)
{
    int	ate = 0;

    switch (type) {
    case HELP_OPT:
	Usage();
	break;
    case LONG_HELP_OPT:
	LongUsage();
	break;
    case BASE_OPT:
	if (base) {
	    PR_fprintf(errorFD, errStrings[DUPLICATE_OPTION_ERR], "-b");
	    warningCount++;
	    PR_Free(base); 
	    base = NULL;
	}
	if (!arg) {
	    PR_fprintf(errorFD, errStrings[OPTION_NEEDS_ARG_ERR], "-b");
	    errorCount++;
	    goto loser;
	}
	base = PL_strdup(arg);
	ate = 1;
	break;
    case COMPRESSION_OPT:
	if (compression_level_specified) {
	    PR_fprintf(errorFD, errStrings[DUPLICATE_OPTION_ERR], "-c");
	    warningCount++;
	}
	if ( !arg ) {
	    PR_fprintf(errorFD, errStrings[OPTION_NEEDS_ARG_ERR], "-c");
	    errorCount++;
	    goto loser;
	}
	compression_level = atoi(arg);
	compression_level_specified = PR_TRUE;
	ate = 1;
	break;
    case CERT_DIR_OPT:
	if (cert_dir) {
	    PR_fprintf(errorFD, errStrings[DUPLICATE_OPTION_ERR], "-d");
	    warningCount++;
	    PR_Free(cert_dir); 
	    cert_dir = NULL;
	}
	if (!arg) {
	    PR_fprintf(errorFD, errStrings[OPTION_NEEDS_ARG_ERR], "-d");
	    errorCount++;
	    goto loser;
	}
	cert_dir = PL_strdup(arg);
	ate = 1;
	break;
    case EXTENSION_OPT:
	if (!arg) {
	    PR_fprintf(errorFD, errStrings[OPTION_NEEDS_ARG_ERR],
	         				"extension (-e)");
	    errorCount++;
	    goto loser;
	}
	PL_HashTableAdd(extensions, arg, arg);
	extensionsGiven = PR_TRUE;
	ate = 1;
	break;
    case INSTALL_SCRIPT_OPT:
	if (install_script) {
	    PR_fprintf(errorFD, errStrings[DUPLICATE_OPTION_ERR],
	         				"installScript (-i)");
	    warningCount++;
	    PR_Free(install_script); 
	    install_script = NULL;
	}
	if (!arg) {
	    PR_fprintf(errorFD, errStrings[OPTION_NEEDS_ARG_ERR],
	         				"installScript (-i)");
	    errorCount++;
	    goto loser;
	}
	install_script = PL_strdup(arg);
	ate = 1;
	break;
    case SCRIPTDIR_OPT:
	if (scriptdir) {
	    PR_fprintf(errorFD, errStrings[DUPLICATE_OPTION_ERR],
	         				"javascriptdir (-j)");
	    warningCount++;
	    PR_Free(scriptdir); 
	    scriptdir = NULL;
	}
	if (!arg) {
	    PR_fprintf(errorFD, errStrings[OPTION_NEEDS_ARG_ERR],
	         				"javascriptdir (-j)");
	    errorCount++;
	    goto loser;
	}
	scriptdir = PL_strdup(arg);
	ate = 1;
	break;
    case CERTNAME_OPT:
	if (keyName) {
	    PR_fprintf(errorFD, errStrings[DUPLICATE_OPTION_ERR],
	         				"keyName (-k)");
	    warningCount++;
	    PR_Free(keyName); 
	    keyName = NULL;
	}
	if (!arg) {
	    PR_fprintf(errorFD, errStrings[OPTION_NEEDS_ARG_ERR],
	         				"keyName (-k)");
	    errorCount++;
	    goto loser;
	}
	keyName = PL_strdup(arg);
	ate = 1;
	break;
    case LIST_OBJSIGN_CERTS_OPT:
    case LIST_ALL_CERTS_OPT:
	if (list_certs != 0) {
	    PR_fprintf(errorFD,
	        "warning: only one of -l and -L may be specified.\n");
	    warningCount++;
	}
	list_certs = (type == LIST_OBJSIGN_CERTS_OPT ? 1 : 2);
	break;
    case METAFILE_OPT:
	if (metafile) {
	    PR_fprintf(errorFD, errStrings[DUPLICATE_OPTION_ERR],
	         				"metafile (-m)");
	    warningCount++;
	    PR_Free(metafile); 
	    metafile = NULL;
	}
	if (!arg) {
	    PR_fprintf(errorFD, errStrings[OPTION_NEEDS_ARG_ERR],
	         				"metafile (-m)");
	    errorCount++;
	    goto loser;
	}
	metafile = PL_strdup(arg);
	ate = 1;
	break;
    case OPTIMIZE_OPT:
	optimize = 1;
	break;
    case ENABLE_OCSP_OPT:
	enableOCSP = 1;
	break;
    case PASSWORD_OPT:
	if (pwdata.data) {
	    PR_fprintf(errorFD, errStrings[DUPLICATE_OPTION_ERR],
	         				"password (-p)");
	    warningCount++;
	    PR_Free(pwdata.data); 
	    pwdata.data = NULL;
	}
	if (!arg) {
	    PR_fprintf(errorFD, errStrings[OPTION_NEEDS_ARG_ERR],
	         				"password (-p)");
	    errorCount++;
	    goto loser;
	}
        pwdata.source = PW_PLAINTEXT;
	pwdata.data = PL_strdup(arg);
	ate = 1;
	break;
    case VERIFY_OPT:
	if (verify) {
	    PR_fprintf(errorFD, errStrings[DUPLICATE_OPTION_ERR],
	         				"verify (-v)");
	    warningCount++;
	    PR_Free(verify); 
	    verify = NULL;
	}
	if (!arg) {
	    PR_fprintf(errorFD, errStrings[OPTION_NEEDS_ARG_ERR],
	         				"verify (-v)");
	    errorCount++;
	    goto loser;
	}
	verify = PL_strdup(arg);
	ate = 1;
	break;
    case WHO_OPT:
	if (tell_who) {
	    PR_fprintf(errorFD, errStrings[DUPLICATE_OPTION_ERR],
	         				"who (-v)");
	    warningCount++;
	    PR_Free(tell_who); 
	    tell_who = NULL;
	}
	if (!arg) {
	    PR_fprintf(errorFD, errStrings[OPTION_NEEDS_ARG_ERR],
	         				"who (-w)");
	    errorCount++;
	    goto loser;
	}
	tell_who = PL_strdup(arg);
	ate = 1;
	break;
    case EXCLUDE_OPT:
	if (!arg) {
	    PR_fprintf(errorFD, errStrings[OPTION_NEEDS_ARG_ERR],
	         				"exclude (-x)");
	    errorCount++;
	    goto loser;
	}
	PL_HashTableAdd(excludeDirs, arg, arg);
	exclusionsGiven = PR_TRUE;
	ate = 1;
	break;
    case NO_TIME_OPT:
	no_time = 1;
	break;
    case JAVASCRIPT_OPT:
	javascript++;
	break;
    case ZIPFILE_OPT:
	if (zipfile) {
	    PR_fprintf(errorFD, errStrings[DUPLICATE_OPTION_ERR],
	         				"jarfile (-Z)");
	    warningCount++;
	    PR_Free(zipfile); 
	    zipfile = NULL;
	}
	if (!arg) {
	    PR_fprintf(errorFD, errStrings[OPTION_NEEDS_ARG_ERR],
	         				"jarfile (-Z)");
	    errorCount++;
	    goto loser;
	}
	zipfile = PL_strdup(arg);
	ate = 1;
	break;
    case GENKEY_OPT:
	if (genkey) {
	    PR_fprintf(errorFD, errStrings[DUPLICATE_OPTION_ERR],
	         				"generate (-G)");
	    warningCount++;
	    PR_Free(genkey); 
	    genkey = NULL;
	}
	if (!arg) {
	    PR_fprintf(errorFD, errStrings[OPTION_NEEDS_ARG_ERR],
	         				"generate (-G)");
	    errorCount++;
	    goto loser;
	}
	genkey = PL_strdup(arg);
	ate = 1;
	break;
    case MODULES_OPT:
	list_modules++;
	break;
    case SIGNDIR_OPT:
	if (jartree) {
	    PR_fprintf(errorFD, errStrings[DUPLICATE_OPTION_ERR],
	         				"signdir");
	    warningCount++;
	    PR_Free(jartree); 
	    jartree = NULL;
	}
	if (!arg) {
	    PR_fprintf(errorFD, errStrings[OPTION_NEEDS_ARG_ERR],
	         "signdir");
	    errorCount++;
	    goto loser;
	}
	jartree = PL_strdup(arg);
	ate = 1;
	break;
    case OUTFILE_OPT:
	if (outfile) {
	    PR_fprintf(errorFD, errStrings[DUPLICATE_OPTION_ERR],
	         				"outfile");
	    warningCount++;
	    PR_Free(outfile); 
	    outfile = NULL;
	}
	if (!arg) {
	    PR_fprintf(errorFD, errStrings[OPTION_NEEDS_ARG_ERR],
	         "outfile");
	    errorCount++;
	    goto loser;
	}
	outfile = PL_strdup(arg);
	ate = 1;
	break;
    case COMMAND_FILE_OPT:
	if (cmdFile) {
	    PR_fprintf(errorFD, errStrings[DUPLICATE_OPTION_ERR],
	         "-f");
	    warningCount++;
	    PR_Free(cmdFile); 
	    cmdFile = NULL;
	}
	if (!arg) {
	    PR_fprintf(errorFD, errStrings[OPTION_NEEDS_ARG_ERR],
	         "-f");
	    errorCount++;
	    goto loser;
	}
	cmdFile = PL_strdup(arg);
	ate = 1;
	break;
    case NORECURSE_OPT:
	noRecurse = PR_TRUE;
	break;
    case LEAVE_ARC_OPT:
	leaveArc = PR_TRUE;
	break;
    case VERBOSITY_OPT:
	if (!arg) {
	    PR_fprintf(errorFD, errStrings[OPTION_NEEDS_ARG_ERR],
	         			  "--verbosity");
	    errorCount++;
	    goto loser;
	}
	verbosity = atoi(arg);
	ate = 1;
	break;
    case KEYSIZE_OPT:
	if ( keySize != -1 ) {
	    PR_fprintf(errorFD, errStrings[DUPLICATE_OPTION_ERR], "-s");
	    warningCount++;
	}
	keySize = atoi(arg);
	ate = 1;
	if ( keySize < 1 || keySize > MAX_RSA_KEY_SIZE ) {
	    PR_fprintf(errorFD, "Invalid key size: %d.\n", keySize);
	    errorCount++;
	    goto loser;
	}
	break;
    case TOKEN_OPT:
	if ( token ) {
	    PR_fprintf(errorFD, errStrings[DUPLICATE_OPTION_ERR], "-t");
	    PR_Free(token); 
	    token = NULL;
	}
	if ( !arg ) {
	    PR_fprintf(errorFD, errStrings[OPTION_NEEDS_ARG_ERR], "-t");
	    errorCount++;
	    goto loser;
	}
	token = PL_strdup(arg);
	ate = 1;
	break;
    case XPI_ARC_OPT:
	xpi_arc = 1;
	break;
    default:
	PR_fprintf(errorFD, "warning: unknown option\n");
	warningCount++;
	break;
    }

    return ate;
loser:
    return - 1;
}


/*********************************************************************
 *
 * m a i n
 */
int
main(int argc, char *argv[])
{
    PRBool readOnly;
    int	retval = 0;

    outputFD = PR_STDOUT;
    errorFD = PR_STDERR;

    progName = argv[0];

    if (argc < 2) {
	Usage();
    }

    excludeDirs = PL_NewHashTable(10, PL_HashString, PL_CompareStrings,
         					PL_CompareStrings, NULL, NULL);
    extensions = PL_NewHashTable(10, PL_HashString, PL_CompareStrings,
         					PL_CompareStrings, NULL, NULL);

    if (parse_args(argc, argv)) {
	retval = -1;
	goto cleanup;
    }

    /* Parse the command file if one was given */
    if (cmdFile) {
	if (ProcessCommandFile()) {
	    retval = -1;
	    goto cleanup;
	}
    }

    /* Set up output redirection */
    if (outfile) {
	if (PR_Access(outfile, PR_ACCESS_EXISTS) == PR_SUCCESS) {
	    /* delete the file if it is already present */
	    PR_fprintf(errorFD,
	        "warning: %s already exists and will be overwritten.\n",
	         				outfile);
	    warningCount++;
	    if (PR_Delete(outfile) != PR_SUCCESS) {
		PR_fprintf(errorFD, "ERROR: unable to delete %s.\n", outfile);
		errorCount++;
		exit(ERRX);
	    }
	}
	outputFD = PR_Open(outfile,
	    PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, 0777);
	if (!outputFD) {
	    PR_fprintf(errorFD, "ERROR: Unable to create %s.\n",
	         outfile);
	    errorCount++;
	    exit(ERRX);
	}
	errorFD = outputFD;
    }

    /* This seems to be a fairly common user error */

    if (verify && list_certs > 0) {
	PR_fprintf (errorFD, "%s: Can't use -l and -v at the same time\n",
	     		PROGRAM_NAME);
	errorCount++;
	retval = -1;
	goto cleanup;
    }

    /* -J assumes -Z now */

    if (javascript && zipfile) {
	PR_fprintf (errorFD, "%s: Can't use -J and -Z at the same time\n",
	     		PROGRAM_NAME);
	PR_fprintf (errorFD, "%s: -J option will create the jar files for you\n",
	     		PROGRAM_NAME);
	errorCount++;
	retval = -1;
	goto cleanup;
    }

    /* -X needs -Z */

    if (xpi_arc && !zipfile) {
	PR_fprintf (errorFD, "%s: option XPI (-X) requires option jarfile (-Z)\n",
	     		PROGRAM_NAME);
	errorCount++;
	retval = -1;
	goto cleanup;
    }

    /* Less common mixing of -L with various options */

    if (list_certs > 0 && 
        (tell_who || zipfile || javascript || 
        scriptdir || extensionsGiven || exclusionsGiven || install_script)) {
	PR_fprintf(errorFD, "%s: Can't use -l or -L with that option\n",
	     			PROGRAM_NAME);
	errorCount++;
	retval = -1;
	goto cleanup;
    }


    if (!cert_dir)
	cert_dir = get_default_cert_dir();

    VerifyCertDir(cert_dir, keyName);


    if (	compression_level < MIN_COMPRESSION_LEVEL || 
        compression_level > MAX_COMPRESSION_LEVEL) {
	PR_fprintf(errorFD, "Compression level must be between %d and %d.\n",
	     		 MIN_COMPRESSION_LEVEL, MAX_COMPRESSION_LEVEL);
	errorCount++;
	retval = -1;
	goto cleanup;
    }

    if (jartree && !keyName) {
	PR_fprintf(errorFD, "You must specify a key with which to sign.\n");
	errorCount++;
	retval = -1;
	goto cleanup;
    }

    readOnly = (genkey == NULL); /* only key generation requires write */
    if (InitCrypto(cert_dir, readOnly)) {
	PR_fprintf(errorFD, "ERROR: Cryptographic initialization failed.\n");
	errorCount++;
	retval = -1;
	goto cleanup;
    }

    if (enableOCSP) {
	SECStatus rv = CERT_EnableOCSPChecking(CERT_GetDefaultCertDB());
	if (rv != SECSuccess) {
	    PR_fprintf(errorFD, "ERROR: Attempt to enable OCSP Checking failed.\n");
	    errorCount++;
	    retval = -1;
	}
    }

    if (verify) {
	if (VerifyJar(verify)) {
	    errorCount++;
	    retval = -1;
	    goto cleanup;
	}
    } else if (list_certs) {
	if (ListCerts(keyName, list_certs)) {
	    errorCount++;
	    retval = -1;
	    goto cleanup;
	}
    } else if (list_modules) {
	JarListModules();
    } else if (genkey) {
	if (GenerateCert(genkey, keySize, token)) {
	    errorCount++;
	    retval = -1;
	    goto cleanup;
	}
    } else if (tell_who) {
	if (JarWho(tell_who)) {
	    errorCount++;
	    retval = -1;
	    goto cleanup;
	}
    } else if (javascript && jartree) {
	/* make sure directory exists */
	PRDir * dir;
	dir = PR_OpenDir(jartree);
	if (!dir) {
	    PR_fprintf(errorFD, "ERROR: unable to open directory %s.\n",
	         jartree);
	    errorCount++;
	    retval = -1;
	    goto cleanup;
	} else {
	    PR_CloseDir(dir);
	}

	/* undo junk from prior runs of signtool*/
	if (RemoveAllArc(jartree)) {
	    PR_fprintf(errorFD, "Error removing archive directories under %s\n",
	         jartree);
	    errorCount++;
	    retval = -1;
	    goto cleanup;
	}

	/* traverse all the htm|html files in the directory */
	if (InlineJavaScript(jartree, !noRecurse)) {
	    retval = -1;
	    goto cleanup;
	}

	/* sign any resultant .arc directories created in above step */
	if (SignAllArc(jartree, keyName, javascript, metafile, install_script,
	     		optimize, !noRecurse)) {
	    retval = -1;
	    goto cleanup;
	}

	if (!leaveArc) {
	    RemoveAllArc(jartree);
	}

	if (errorCount > 0 || warningCount > 0) {
	    PR_fprintf(outputFD, "%d error%s, %d warning%s.\n",
	         errorCount,
	        errorCount == 1 ? "" : "s", warningCount, warningCount
	        == 1 ? "" : "s");
	} else {
	    PR_fprintf(outputFD, "Directory %s signed successfully.\n",
	         jartree);
	}
    } else if (jartree) {
	SignArchive(jartree, keyName, zipfile, javascript, metafile,
	     		install_script, optimize, !noRecurse);
    } else
	Usage();

cleanup:
    if (extensions) {
	PL_HashTableDestroy(extensions); 
	extensions = NULL;
    }
    if (excludeDirs) {
	PL_HashTableDestroy(excludeDirs); 
	excludeDirs = NULL;
    }
    if (outputFD != PR_STDOUT) {
	PR_Close(outputFD);
    }
    rm_dash_r(TMP_OUTPUT);
    if (retval == 0) {
	if (NSS_Shutdown() != SECSuccess) {
	    exit(1);
	}
    }
    return retval;
}


