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

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

static int is_dir(char *filename);

/***********************************************************
 * Nasty hackish function definitions
 */

long *mozilla_event_queue = 0;

#ifndef XP_WIN
char *
XP_GetString(int i)
{
    /* nasty hackish cast to avoid changing the signature of
     * JAR_init_callbacks() */
    return (char *)SECU_Strerror(i);
}
#endif

void
FE_SetPasswordEnabled()
{
}

void /*MWContext*/ *
FE_GetInitContext(void)
{
    return 0;
}

void /*MWContext*/ *
XP_FindSomeContext()
{
    /* No windows context in command tools */
    return NULL;
}

void
ET_moz_CallFunction()
{
}

/*
 *  R e m o v e A l l A r c
 *
 *  Remove .arc directories that are lingering
 *  from a previous run of signtool.
 *
 */
int
RemoveAllArc(char *tree)
{
    PRDir *dir;
    PRDirEntry *entry;
    char *archive = NULL;
    int retval = 0;

    dir = PR_OpenDir(tree);
    if (!dir)
        return -1;

    for (entry = PR_ReadDir(dir, 0); entry; entry = PR_ReadDir(dir,
                                                               0)) {

        if (entry->name[0] == '.') {
            continue;
        }

        if (archive)
            PR_Free(archive);
        archive = PR_smprintf("%s/%s", tree, entry->name);

        if (PL_strcaserstr(entry->name, ".arc") ==
            (entry->name + strlen(entry->name) - 4)) {

            if (verbosity >= 0) {
                PR_fprintf(outputFD, "removing: %s\n", archive);
            }

            if (rm_dash_r(archive)) {
                PR_fprintf(errorFD, "Error removing %s\n", archive);
                errorCount++;
                retval = -1;
                goto finish;
            }
        } else if (is_dir(archive)) {
            if (RemoveAllArc(archive)) {
                retval = -1;
                goto finish;
            }
        }
    }

finish:
    PR_CloseDir(dir);
    if (archive)
        PR_Free(archive);

    return retval;
}

/*
 *  r m _ d a s h _ r
 *
 *  Remove a file, or a directory recursively.
 *
 */
int
rm_dash_r(char *path)
{
    PRDir *dir;
    PRDirEntry *entry;
    PRFileInfo fileinfo;
    char filename[FNSIZE];

    if (PR_GetFileInfo(path, &fileinfo) != PR_SUCCESS) {
        /*fprintf(stderr, "Error: Unable to access %s\n", filename);*/
        return -1;
    }
    if (fileinfo.type == PR_FILE_DIRECTORY) {

        dir = PR_OpenDir(path);
        if (!dir) {
            PR_fprintf(errorFD, "Error: Unable to open directory %s.\n", path);
            errorCount++;
            return -1;
        }

        /* Recursively delete all entries in the directory */
        while ((entry = PR_ReadDir(dir, PR_SKIP_BOTH)) != NULL) {
            sprintf(filename, "%s/%s", path, entry->name);
            if (rm_dash_r(filename))
                return -1;
        }

        if (PR_CloseDir(dir) != PR_SUCCESS) {
            PR_fprintf(errorFD, "Error: Could not close %s.\n", path);
            errorCount++;
            return -1;
        }

        /* Delete the directory itself */
        if (PR_RmDir(path) != PR_SUCCESS) {
            PR_fprintf(errorFD, "Error: Unable to delete %s\n", path);
            errorCount++;
            return -1;
        }
    } else {
        if (PR_Delete(path) != PR_SUCCESS) {
            PR_fprintf(errorFD, "Error: Unable to delete %s\n", path);
            errorCount++;
            return -1;
        }
    }
    return 0;
}

/*
 *  u s a g e
 *
 *  Print some useful help information
 *
 */

void
Usage(void)
{
#define FPS PR_fprintf(outputFD,
    FPS "%s %s -a signing tool for jar files\n", LONG_PROGRAM_NAME,NSS_VERSION);
    FPS "\n\nType %s -H for more detailed descriptions\n", PROGRAM_NAME);
    FPS "\nUsage:  %s -k keyName [-b basename] [-c Compression Level]\n"
        "\t\t [-d cert-dir] [-i installer script] [-m metafile] [-x name]\n"
        "\t\t [-e extension] [-o] [-z] [-X] [--outfile] [--verbose value]\n"
        "\t\t [--norecurse] [--leavearc] [-j directory] [-Z jarfile] [-O]\n"
        "\t\t [-p password] directory-tree\n", PROGRAM_NAME);
    FPS "\t%s -J -k keyName [-b basename] [-c Compression Level]\n"
        "\t\t [-d cert-dir][-i installer script] [-m metafile] [-x name]\n"
        "\t\t [-e extension] [-o] [-z] [-X] [--outfile] [--verbose value]\n"
        "\t\t [--norecurse] [--leavearc] [-j directory] [-p password] [-O] \n"
        "\t\t directory-tree\n", PROGRAM_NAME);
    FPS "\t%s -h \n", PROGRAM_NAME);
    FPS "\t%s -H \n", PROGRAM_NAME);
    FPS "\t%s -l [-k keyName] [-d cert-dir] [--outfile] [-O] \n", PROGRAM_NAME);
    FPS "\t%s -L [-k keyName] [-d cert-dir] [--outfile] [-O] \n", PROGRAM_NAME);
    FPS "\t%s -M [--outfile] [-O] \n", PROGRAM_NAME);
    FPS "\t%s -v [-d cert-dir] [--outfile] [-O] archive\n", PROGRAM_NAME);
    FPS "\t%s -w [--outfile] [-O] archive\n" , PROGRAM_NAME);
    FPS "\t%s -G nickname [--keysize|-s size] [-t |--token tokenname]\n"
        "\t\t [--outfile] [-O] \n", PROGRAM_NAME);
    FPS "\t%s -f filename\n" , PROGRAM_NAME);
    exit(ERRX);
}

void
LongUsage(void)
{
    FPS "%s %s -a signing tool for jar files\n", LONG_PROGRAM_NAME,NSS_VERSION);
    FPS "\n%-20s  Signs the directory-tree\n",
        "signtool directory-tree");
    FPS "%-30s Nickname (key) of the certificate to sign with\n",
        "   -k keyname");
    FPS "%-30s Base filename for the .rsa and.sf files in the\n",
        "   -b basename");
    FPS "%-30s META-INF directory\n"," ");
    FPS "%-30s Set the compression level. 0-9, 0=none\n",
    "   -c CompressionLevel");
    FPS "%-30s Certificate database directory containing cert*db\n",
    "   -d certificate directory");
    FPS "%-30s and key*db\n"," ");
    FPS "%-30s Name of the installer script for SmartUpdate\n",
    "   -i installer script");
    FPS "%-30s Name of a metadata control file\n",
    "   -m metafile");
    FPS "%-30s For optimizing the archive for size.\n",
    "   -o");
    FPS "%-30s Omit Optional Headers\n"," ");
    FPS "%-30s Excludes the specified directory or file from\n",
    "   -x  directory or file name");
    FPS "%-30s signing\n"," ");
    FPS "%-30s To not store the signing time in digital\n",
    "   -z  directory or file name");
    FPS "%-30s signature\n"," ");
    FPS "%-30s Create XPI Compatible Archive. It requires -Z\n",
    "   -X  directory or file name");
    FPS "%-30s option\n"," ");
    FPS "%-30s Sign only files with the given extension\n",
    "   -e");
    FPS "%-30s Causes the specified directory to be signed and\n",
    "   -j");
    FPS "%-30s tags its entries as inline JavaScript\n"," ");
    FPS "%-30s Creates a JAR file with the specified name.\n",
    "   -Z");
    FPS "%-30s -Z option cannot be used with -J option\n"," ");
    FPS "%-30s Specifies a password for the private-key database\n",
    "   -p");
    FPS "%-30s (insecure)\n"," ");
    FPS "%-30s File to receive redirected output\n",
        "   --outfile filename");
    FPS "%-30s Sets the quantity of information generated in\n",
    "   --verbosity value");
    FPS "%-30s operation\n"," ");
    FPS "%-30s Blocks recursion into subdirectories\n",
    "   --norecurse");
    FPS "%-30s Retains the temporary .arc (archive) directories\n",
    "   --leavearc");
    FPS "%-30s -J option creates\n"," ");

    FPS "\n%-20s Signs a directory of HTML files containing JavaScript and\n",
    "-J" );
    FPS "%-20s creates as many archive files as are in the HTML tags.\n"," ");

    FPS "%-20s The options are same as without any command option given\n"," ");
    FPS "%-20s above. -Z and -J options are not allowed together\n"," ");

    FPS "\n%-20s Generates a new private-public key pair and corresponding\n",
    "-G nickname");
    FPS "%-20s object-signing certificates with the given nickname\n"," ");
    FPS "%-30s Specifies the size of the key for generated \n",
    "   --keysize|-s keysize");
    FPS "%-30s certificate\n"," ");
    FPS "%-30s Specifies which available token should generate\n",
    "   --token|-t token name ");
    FPS "%-30s the key and receive the certificate\n"," ");
    FPS "%-30s Specifies a file to receive redirected output\n",
    "   --outfile filename ");

    FPS "\n%-20s Display signtool help\n",
    "-h ");

    FPS "\n%-20s Display signtool help(Detailed)\n",
    "-H ");

    FPS "\n%-20s Lists signing certificates, including issuing CAs\n",
    "-l ");
    FPS "%-30s Certificate database directory containing cert*db\n",
    "   -d certificate directory");
    FPS "%-30s and key*db\n"," ");

    FPS "%-30s Specifies a file to receive redirected output\n",
    "   --outfile filename ");
    FPS "%-30s Specifies the nickname (key) of the certificate\n",
    "   -k keyname");

    FPS "\n%-20s Lists the certificates in your database\n",
    "-L ");
    FPS "%-30s Certificate database directory containing cert*db\n",
    "   -d certificate directory");
    FPS "%-30s and key*db\n"," ");

    FPS "%-30s Specifies a file to receive redirected output\n",
    "   --outfile filename ");
    FPS "%-30s Specifies the nickname (key) of the certificate\n",
    "   -k keyname");

    FPS "\n%-20s Lists the PKCS #11 modules available to signtool\n",
    "-M ");

    FPS "\n%-20s Displays the contents of an archive and verifies\n",
    "-v archive");
    FPS "%-20s cryptographic integrity\n"," ");
    FPS "%-30s Certificate database directory containing cert*db\n",
    "   -d certificate directory");
    FPS "%-30s and key*db\n"," ");
    FPS "%-30s Specifies a file to receive redirected output\n",
    "   --outfile filename ");

    FPS "\n%-20s Displays the names of signers in the archive\n",
    "-w archive");
    FPS "%-30s Specifies a file to receive redirected output\n",
    "   --outfile filename ");

    FPS "\n%-30s Common option to all the above.\n",
    "   -O");
    FPS "%-30s Enable OCSP checking\n"," ");

    FPS "\n%-20s Specifies a text file containing options and arguments in\n",
    "-f command-file");
    FPS "%-20s keyword=value format. Commands are taken from this file\n"," ");

    FPS  "\n\n\n");
    FPS  "Example:\n");
    FPS  "%-10s -d \"certificate directory\" -k \"certnickname\" \\",
         PROGRAM_NAME);
    FPS  "\n%-10s -p \"password\"  -X -Z \"file.xpi\" directory-tree\n"," " );
    FPS  "Common syntax to create an XPInstall compatible"
         " signed archive\n\n"," ");
    FPS  "\nCommand File Keywords and Example:\n");
    FPS "\nKeyword\t\tValue\n");
    FPS "basename\tSame as -b option\n");
    FPS "compression\tSame as -c option\n");
    FPS "certdir\t\tSame as -d option\n");
    FPS "extension\tSame as -e option\n");
    FPS "generate\tSame as -G option\n");
    FPS "installscript\tSame as -i option\n");
    FPS "javascriptdir\tSame as -j option\n");
    FPS "htmldir\t\tSame as -J option\n");
    FPS "certname\tNickname of certificate, as with -k  option\n");
    FPS "signdir\t\tThe directory to be signed, as with -k option\n");
    FPS "list\t\tSame as -l option. Value is ignored,\n"
        "    \t\tbut = sign must be present\n");
    FPS "listall\t\tSame as -L option. Value is ignored\n"
        "       \t\tbut = sign must be present\n");
    FPS "metafile\tSame as -m option\n");
    FPS "modules\t\tSame as -M option. Value is ignored,\n"
        "       \t\tbut = sign must be present\n");
    FPS "optimize\tSame as -o option. Value is ignored,\n"
        "        \tbut = sign must be present\n");
    FPS "ocsp\t\tSame as -O option\n");
    FPS "password\tSame as -p option\n");
    FPS "verify\t\tSame as -v option\n");
    FPS "who\t\tSame as -w option\n");
    FPS "exclude\t\tSame as -x option\n");
    FPS "notime\t\tSame as -z option. Value is ignored,\n"
        "      \t\tbut = sign must be present\n");
    FPS "jarfile\t\tSame as -Z option\n");
    FPS "outfile\t\tSame as --outfile option. The argument\n");
    FPS "       \t\tis the name of a file to which output\n");
    FPS "       \t\tof a file and error messages will be  \n");
    FPS "       \t\tredirected\n");
    FPS "leavearc\tSame as --leavearc option\n");
    FPS "verbosity\tSame as --verbosity option\n");
    FPS "keysize\t\tSame as -s option\n");
    FPS "token\t\tSame as -t option\n");
    FPS "xpi\t\tSame as -X option\n");
    FPS "\n\n");
    FPS "Here's an example of the use of the command file. The command\n\n");
    FPS "   signtool -d c:\\netscape\\users\\james -k mycert -Z myjar.jar \\\n"
        "   signdir > output.txt\n\n");
    FPS "becomes\n\n");
    FPS "   signtool -f somefile\n\n");
    FPS "where somefile contains the following lines:\n\n");
    FPS "   certdir=c:\\netscape\\users\\james\n"," ");
    FPS "   certname=mycert\n"," ");
    FPS "   jarfile=myjar.jar\n"," ");
    FPS "   signdir=signdir\n"," ");
    FPS "   outfile=output.txt\n"," ");
    exit(ERRX);
#undef FPS
}

/*
 *  p r i n t _ e r r o r
 *
 *  For the undocumented -E function. If an older version
 *  of communicator gives you a numeric error, we can see what
 *  really happened without doing hex math.
 *
 */

void
print_error(int err)
{
    PR_fprintf(errorFD, "Error %d: %s\n", err, JAR_get_error(err));
    errorCount++;
    give_help(err);
}

/*
 *  o u t _ o f _ m e m o r y
 *
 *  Out of memory, exit Signtool.
 *
 */
void
out_of_memory(void)
{
    PR_fprintf(errorFD, "%s: out of memory\n", PROGRAM_NAME);
    errorCount++;
    exit(ERRX);
}

/*
 *  V e r i f y C e r t D i r
 *
 *  Validate that the specified directory
 *  contains a certificate database
 *
 */
void
VerifyCertDir(char *dir, char *keyName)
{
    char fn[FNSIZE];

    /* don't try verifying if we don't have a local directory */
    if (strncmp(dir, "multiaccess:", sizeof("multiaccess:") - 1) == 0) {
        return;
    }
    /* this function is truly evil. Tools and applications should not have
     * any knowledge of actual cert databases! */
    return;

    /* This code is really broken because it makes underlying assumptions about
     * how the NSS profile directory is laid out, but these names can change
     * from release to release. */
    sprintf(fn, "%s/cert8.db", dir);

    if (PR_Access(fn, PR_ACCESS_EXISTS)) {
        PR_fprintf(errorFD, "%s: No certificate database in \"%s\"\n",
                   PROGRAM_NAME, dir);
        PR_fprintf(errorFD, "%s: Check the -d arguments that you gave\n",
                   PROGRAM_NAME);
        errorCount++;
        exit(ERRX);
    }

    if (verbosity >= 0) {
        PR_fprintf(outputFD, "using certificate directory: %s\n", dir);
    }

    if (keyName == NULL)
        return;

    /* if the user gave the -k key argument, verify that
     a key database already exists */

    sprintf(fn, "%s/key3.db", dir);

    if (PR_Access(fn, PR_ACCESS_EXISTS)) {
        PR_fprintf(errorFD, "%s: No private key database in \"%s\"\n",
                   PROGRAM_NAME,
                   dir);
        PR_fprintf(errorFD, "%s: Check the -d arguments that you gave\n",
                   PROGRAM_NAME);
        errorCount++;
        exit(ERRX);
    }
}

/*
 *  f o r e a c h
 *
 *  A recursive function to loop through all names in
 *  the specified directory, as well as all subdirectories.
 *
 *  FIX: Need to see if all platforms allow multiple
 *  opendir's to be called.
 *
 */

int foreach (char *dirname, char *prefix,
             int (*fn)(char *relpath, char *basedir, char *reldir, char *filename,
                       void *arg),
             PRBool recurse, PRBool includeDirs, void *arg)
{
    char newdir[FNSIZE];
    int retval = 0;

    PRDir *dir;
    PRDirEntry *entry;

    strcpy(newdir, dirname);
    if (*prefix) {
        strcat(newdir, "/");
        strcat(newdir, prefix);
    }

    dir = PR_OpenDir(newdir);
    if (!dir)
        return -1;

    for (entry = PR_ReadDir(dir, 0); entry; entry = PR_ReadDir(dir, 0)) {
        if (strcmp(entry->name, ".") == 0 ||
            strcmp(entry->name, "..") == 0) {
            /* no infinite recursion, please */
            continue;
        }

        /* can't sign self */
        if (!strcmp(entry->name, "META-INF"))
            continue;

        /* -x option */
        if (PL_HashTableLookup(excludeDirs, entry->name))
            continue;

        strcpy(newdir, dirname);
        if (*dirname)
            strcat(newdir, "/");

        if (*prefix) {
            strcat(newdir, prefix);
            strcat(newdir, "/");
        }
        strcat(newdir, entry->name);

        if (!is_dir(newdir) || includeDirs) {
            char newpath[FNSIZE];

            strcpy(newpath, prefix);
            if (*newpath)
                strcat(newpath, "/");
            strcat(newpath, entry->name);

            if ((*fn)(newpath, dirname, prefix, (char *)entry->name,
                      arg)) {
                retval = -1;
                break;
            }
        }

        if (is_dir(newdir)) {
            if (recurse) {
                char newprefix[FNSIZE];

                strcpy(newprefix, prefix);
                if (*newprefix) {
                    strcat(newprefix, "/");
                }
                strcat(newprefix, entry->name);

                if (foreach (dirname, newprefix, fn, recurse,
                             includeDirs, arg)) {
                    retval = -1;
                    break;
                }
            }
        }
    }

    PR_CloseDir(dir);

    return retval;
}

/*
 *  i s _ d i r
 *
 *  Return 1 if file is a directory.
 *  Wonder if this runs on a mac, trust not.
 *
 */
static int
is_dir(char *filename)
{
    PRFileInfo finfo;

    if (PR_GetFileInfo(filename, &finfo) != PR_SUCCESS) {
        printf("Unable to get information about %s\n", filename);
        return 0;
    }

    return (finfo.type == PR_FILE_DIRECTORY);
}

/***************************************************************
 *
 * s e c E r r o r S t r i n g
 *
 * Returns an error string corresponding to the given error code.
 * Doesn't cover all errors; returns a default for many.
 * Returned string is only valid until the next call of this function.
 */
const char *
secErrorString(long code)
{
    static char errstring[80]; /* dynamically constructed error string */
    char *c;                   /* the returned string */

    switch (code) {
        case SEC_ERROR_IO:
            c = "io error";
            break;
        case SEC_ERROR_LIBRARY_FAILURE:
            c = "security library failure";
            break;
        case SEC_ERROR_BAD_DATA:
            c = "bad data";
            break;
        case SEC_ERROR_OUTPUT_LEN:
            c = "output length";
            break;
        case SEC_ERROR_INPUT_LEN:
            c = "input length";
            break;
        case SEC_ERROR_INVALID_ARGS:
            c = "invalid args";
            break;
        case SEC_ERROR_EXPIRED_CERTIFICATE:
            c = "expired certificate";
            break;
        case SEC_ERROR_REVOKED_CERTIFICATE:
            c = "revoked certificate";
            break;
        case SEC_ERROR_INADEQUATE_KEY_USAGE:
            c = "inadequate key usage";
            break;
        case SEC_ERROR_INADEQUATE_CERT_TYPE:
            c = "inadequate certificate type";
            break;
        case SEC_ERROR_UNTRUSTED_CERT:
            c = "untrusted cert";
            break;
        case SEC_ERROR_NO_KRL:
            c = "no key revocation list";
            break;
        case SEC_ERROR_KRL_BAD_SIGNATURE:
            c = "key revocation list: bad signature";
            break;
        case SEC_ERROR_KRL_EXPIRED:
            c = "key revocation list expired";
            break;
        case SEC_ERROR_REVOKED_KEY:
            c = "revoked key";
            break;
        case SEC_ERROR_CRL_BAD_SIGNATURE:
            c = "certificate revocation list: bad signature";
            break;
        case SEC_ERROR_CRL_EXPIRED:
            c = "certificate revocation list expired";
            break;
        case SEC_ERROR_CRL_NOT_YET_VALID:
            c = "certificate revocation list not yet valid";
            break;
        case SEC_ERROR_UNKNOWN_ISSUER:
            c = "unknown issuer";
            break;
        case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE:
            c = "expired issuer certificate";
            break;
        case SEC_ERROR_BAD_SIGNATURE:
            c = "bad signature";
            break;
        case SEC_ERROR_BAD_KEY:
            c = "bad key";
            break;
        case SEC_ERROR_NOT_FORTEZZA_ISSUER:
            c = "not fortezza issuer";
            break;
        case SEC_ERROR_CA_CERT_INVALID:
            c = "Certificate Authority certificate invalid";
            break;
        case SEC_ERROR_EXTENSION_NOT_FOUND:
            c = "extension not found";
            break;
        case SEC_ERROR_CERT_NOT_IN_NAME_SPACE:
            c = "certificate not in name space";
            break;
        case SEC_ERROR_UNTRUSTED_ISSUER:
            c = "untrusted issuer";
            break;
        default:
            sprintf(errstring, "security error %ld", code);
            c = errstring;
            break;
    }

    return c;
}

/***************************************************************
 *
 * d i s p l a y V e r i f y L o g
 *
 * Prints the log of a cert verification.
 */
void
displayVerifyLog(CERTVerifyLog *log)
{
    CERTVerifyLogNode *node;
    CERTCertificate *cert;
    char *name;

    if (!log || (log->count <= 0)) {
        return;
    }

    for (node = log->head; node != NULL; node = node->next) {

        if (!(cert = node->cert)) {
            continue;
        }

        /* Get a name for this cert */
        if (cert->nickname != NULL) {
            name = cert->nickname;
        } else if (cert->emailAddr && cert->emailAddr[0]) {
            name = cert->emailAddr;
        } else {
            name = cert->subjectName;
        }

        printf("%s%s:\n", name,
               (node->depth > 0) ? " [Certificate Authority]" : "");

        printf("\t%s\n", secErrorString(node->error));
    }
}

/*
 *  J a r L i s t M o d u l e s
 *
 *  Print a list of the PKCS11 modules that are
 *  available. This is useful for smartcard people to
 *  make sure they have the drivers loaded.
 *
 */
void
JarListModules(void)
{
    int i;
    int count = 0;

    SECMODModuleList *modules = NULL;
    static SECMODListLock *moduleLock = NULL;

    SECMODModuleList *mlp;

    if ((moduleLock = SECMOD_GetDefaultModuleListLock()) == NULL) {
        /* this is the wrong text */
        PR_fprintf(errorFD, "%s: unable to acquire lock on module list\n",
                   PROGRAM_NAME);
        errorCount++;
        exit(ERRX);
    }

    SECMOD_GetReadLock(moduleLock);

    modules = SECMOD_GetDefaultModuleList();

    if (modules == NULL) {
        SECMOD_ReleaseReadLock(moduleLock);
        PR_fprintf(errorFD, "%s: Can't get module list\n", PROGRAM_NAME);
        errorCount++;
        exit(ERRX);
    }

    PR_fprintf(outputFD, "\nListing of PKCS11 modules\n");
    PR_fprintf(outputFD, "-----------------------------------------------\n");

    for (mlp = modules; mlp != NULL; mlp = mlp->next) {
        count++;
        PR_fprintf(outputFD, "%3d. %s\n", count, mlp->module->commonName);

        if (mlp->module->internal)
            PR_fprintf(outputFD, "          (this module is internally loaded)\n");
        else
            PR_fprintf(outputFD, "          (this is an external module)\n");

        if (mlp->module->dllName)
            PR_fprintf(outputFD, "          DLL name: %s\n",
                       mlp->module->dllName);

        if (mlp->module->slotCount == 0)
            PR_fprintf(outputFD, "          slots: There are no slots attached to this module\n");
        else
            PR_fprintf(outputFD, "          slots: %d slots attached\n",
                       mlp->module->slotCount);

        if (mlp->module->loaded == 0)
            PR_fprintf(outputFD, "          status: Not loaded\n");
        else
            PR_fprintf(outputFD, "          status: loaded\n");

        for (i = 0; i < mlp->module->slotCount; i++) {
            PK11SlotInfo *slot = mlp->module->slots[i];

            PR_fprintf(outputFD, "\n");
            PR_fprintf(outputFD, "    slot: %s\n", PK11_GetSlotName(slot));
            PR_fprintf(outputFD, "   token: %s\n", PK11_GetTokenName(slot));
        }
    }

    PR_fprintf(outputFD, "-----------------------------------------------\n");

    if (count == 0)
        PR_fprintf(outputFD,
                   "Warning: no modules were found (should have at least one)\n");

    SECMOD_ReleaseReadLock(moduleLock);
}

/**********************************************************************
 * c h o p
 *
 * Eliminates leading and trailing whitespace.  Returns a pointer to the
 * beginning of non-whitespace, or an empty string if it's all whitespace.
 */
char *
chop(char *str)
{
    char *start, *end;

    if (str) {
        start = str;

        /* Nip leading whitespace */
        while (isspace(*start)) {
            start++;
        }

        /* Nip trailing whitespace */
        if (*start) {
            end = start + strlen(start) - 1;
            while (isspace(*end) && end > start) {
                end--;
            }
            *(end + 1) = '\0';
        }

        return start;
    } else {
        return NULL;
    }
}

/***********************************************************************
 *
 * F a t a l E r r o r
 *
 * Outputs an error message and bails out of the program.
 */
void
FatalError(char *msg)
{
    if (!msg)
        msg = "";

    PR_fprintf(errorFD, "FATAL ERROR: %s\n", msg);
    errorCount++;
    exit(ERRX);
}

/*************************************************************************
 *
 * I n i t C r y p t o
 */
int
InitCrypto(char *cert_dir, PRBool readOnly)
{
    SECStatus rv;
    static int prior = 0;
    PK11SlotInfo *slotinfo;

    if (prior == 0) {
        /* some functions such as OpenKeyDB expect this path to be
     * implicitly set prior to calling */
        if (readOnly) {
            rv = NSS_Init(cert_dir);
        } else {
            rv = NSS_InitReadWrite(cert_dir);
        }
        if (rv != SECSuccess) {
            SECU_PrintPRandOSError(PROGRAM_NAME);
            exit(-1);
        }

        SECU_ConfigDirectory(cert_dir);

        /* Been there done that */
        prior++;

        PK11_SetPasswordFunc(SECU_GetModulePassword);

        /* Must login to FIPS before you do anything else */
        if (PK11_IsFIPS()) {
            slotinfo = PK11_GetInternalSlot();
            if (!slotinfo) {
                fprintf(stderr, "%s: Unable to get PKCS #11 Internal Slot."
                                "\n",
                        PROGRAM_NAME);
                return -1;
            }
            if (PK11_Authenticate(slotinfo, PR_FALSE /*loadCerts*/,
                                  &pwdata) != SECSuccess) {
                fprintf(stderr, "%s: Unable to authenticate to %s.\n",
                        PROGRAM_NAME, PK11_GetSlotName(slotinfo));
                PK11_FreeSlot(slotinfo);
                return -1;
            }
            PK11_FreeSlot(slotinfo);
        }

        /* Make sure there is a password set on the internal key slot */
        slotinfo = PK11_GetInternalKeySlot();
        if (!slotinfo) {
            fprintf(stderr, "%s: Unable to get PKCS #11 Internal Key Slot."
                            "\n",
                    PROGRAM_NAME);
            return -1;
        }
        if (PK11_NeedUserInit(slotinfo)) {
            PR_fprintf(errorFD,
                       "\nWARNING: No password set on internal key database.  Most operations will fail."
                       "\nYou must create a password.\n");
            warningCount++;
        }

        /* Make sure we can authenticate to the key slot in FIPS mode */
        if (PK11_IsFIPS()) {
            if (PK11_Authenticate(slotinfo, PR_FALSE /*loadCerts*/,
                                  &pwdata) != SECSuccess) {
                fprintf(stderr, "%s: Unable to authenticate to %s.\n",
                        PROGRAM_NAME, PK11_GetSlotName(slotinfo));
                PK11_FreeSlot(slotinfo);
                return -1;
            }
        }
        PK11_FreeSlot(slotinfo);
    }

    return 0;
}

/* Windows foolishness is now in the secutil lib */

/*****************************************************************
 *  g e t _ d e f a u l t _ c e r t _ d i r
 *
 *  Attempt to locate a certificate directory.
 *  Failing that, complain that the user needs to
 *  use the -d(irectory) parameter.
 *
 */
char *
get_default_cert_dir(void)
{
    char *home;

    char *cd = NULL;
    static char db[FNSIZE];

#ifdef XP_UNIX
    home = PR_GetEnvSecure("HOME");

    if (home && *home) {
        sprintf(db, "%s/.netscape", home);
        cd = db;
    }
#endif

#ifdef XP_PC
    FILE *fp;

    /* first check the environment override */

    home = PR_GetEnvSecure("JAR_HOME");

    if (home && *home) {
        sprintf(db, "%s/cert7.db", home);

        if ((fp = fopen(db, "r")) != NULL) {
            fclose(fp);
            cd = home;
        }
    }

    /* try the old navigator directory */

    if (cd == NULL) {
        home = "c:/Program Files/Netscape/Navigator";

        sprintf(db, "%s/cert7.db", home);

        if ((fp = fopen(db, "r")) != NULL) {
            fclose(fp);
            cd = home;
        }
    }

    /* Try the current directory, I wonder if this
     is really a good idea. Remember, Windows only.. */

    if (cd == NULL) {
        home = ".";

        sprintf(db, "%s/cert7.db", home);

        if ((fp = fopen(db, "r")) != NULL) {
            fclose(fp);
            cd = home;
        }
    }

#endif

    if (!cd) {
        PR_fprintf(errorFD,
                   "You must specify the location of your certificate directory\n");
        PR_fprintf(errorFD,
                   "with the -d option. Example: -d ~/.netscape in many cases with Unix.\n");
        errorCount++;
        exit(ERRX);
    }

    return cd;
}

/************************************************************************
 * g i v e _ h e l p
 */
void
give_help(int status)
{
    if (status == SEC_ERROR_UNKNOWN_ISSUER) {
        PR_fprintf(errorFD,
                   "The Certificate Authority (CA) for this certificate\n");
        PR_fprintf(errorFD,
                   "does not appear to be in your database. You should contact\n");
        PR_fprintf(errorFD,
                   "the organization which issued this certificate to obtain\n");
        PR_fprintf(errorFD, "a copy of its CA Certificate.\n");
    }
}

/**************************************************************************
 *
 * p r _ f g e t s
 *
 * fgets implemented with NSPR.
 */
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, &c, 1);
        if (status == -1) {
            return NULL;
        } else if (status == 0) {
            if (i == 0) {
                return NULL;
            }
            break;
        }
        buf[i++] = c;
        if (c == '\n') {
            break;
        }
    }
    buf[i] = '\0';

    return buf;
}