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