/*
 * The contents of this file are subject to the Mozilla Public
 * License Version 1.1 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.mozilla.org/MPL/
 * 
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 * 
 * The Original Code is the Netscape security libraries.
 * 
 * The Initial Developer of the Original Code is Netscape
 * Communications Corporation.  Portions created by Netscape are 
 * Copyright (C) 1994-2000 Netscape Communications Corporation.  All
 * Rights Reserved.
 * 
 * Contributor(s):
 * 
 * Alternatively, the contents of this file may be used under the
 * terms of the GNU General Public License Version 2 or later (the
 * "GPL"), in which case the provisions of the GPL are applicable 
 * instead of those above.  If you wish to allow use of your 
 * version of this file only under the terms of the GPL and not to
 * allow others to use your version of this file under the MPL,
 * indicate your decision by deleting the provisions above and
 * replace them with the notice and other provisions required by
 * the GPL.  If you do not delete the provisions above, a recipient
 * may use your version of this file under either the MPL or the
 * GPL.
 */

/*
 *  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 "signtool.h"
#include <prmem.h>
#include <prio.h>

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

/* password on command line. Use for build testing only */
char *password = NULL;

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

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

typedef enum {
	DUPLICATE_OPTION_ERR=0,
	OPTION_NEEDS_ARG_ERR
} Error;

static char *errStrings[] = {
"warning: %s option specified more than once. 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), buf && *buf!='\0') {
		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, "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 {
			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;
	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 {
					needsInc = 0;
					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') {
					needsInc = 0;
					arg = opt+2;
				} else if(i < argc-1) {
					needsInc = 1;
					arg = argv[i+1];
				} else {
					needsInc = 0;
					arg = NULL;
				}

				switch(opt[1]) {
				case '?':
					type = QUESTION_OPT;
					break;
				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 '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 'p':
					type = PASSWORD_OPT;
					break;
				case 'v':
					type = VERIFY_OPT;
					break;
				case 'w':
					type = WHO_OPT;
					break;
				case 'x':
					type = EXCLUDE_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 {
			if(i == argc-1) {
				type = UNKNOWN_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(opt);
			} else {
				type = UNKNOWN_OPT;
				PR_fprintf(errorFD, "warning: unrecognized option: %s\n", opt);
				warningCount++;
			}
		}

		if(type != UNKNOWN_OPT) {
			short ateArg;

			ateArg = ProcessOneOpt(type, arg);
			if(ateArg==-1) {
				/* error */
				return -1;
			} else 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 QUESTION_OPT:
		usage();
		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 PASSWORD_OPT:
		if(password) {
			PR_fprintf(errorFD, errStrings[DUPLICATE_OPTION_ERR],
				"password (-p)");
			warningCount++;
			PR_Free(password); password= NULL;
		}
		if(!arg) {
			PR_fprintf(errorFD, errStrings[OPTION_NEEDS_ARG_ERR],
				"password (-p)");
			errorCount++;
			goto loser;
		}
		password = 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(zipfile); zipfile = 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;
	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;
    }

  /* 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 (verify)
    {
    VerifyJar(verify);
    }
  else if (list_certs)
    {
    ListCerts(keyName, list_certs);
    }
  else if (list_modules)
    {
    JarListModules();
    }
  else if (genkey)
    {
    GenerateCert(genkey, keySize, token);
    }
  else if (tell_who)
    {
    JarWho(tell_who);
    }
  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 */
    SignAllArc(jartree, keyName, javascript, metafile, install_script,
		optimize, !noRecurse);

	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);
  return retval;
}

