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

/*
 * cmsutil -- A command to work with CMS data
 */

#include "nspr.h"
#include "secutil.h"
#include "plgetopt.h"
#include "secpkcs7.h"
#include "cert.h"
#include "certdb.h"
#include "secoid.h"
#include "cms.h"
#include "nss.h"
#include "smime.h"
#include "pk11func.h"
#include "sechash.h"

#if defined(XP_UNIX)
#include <unistd.h>
#endif

#if defined(_WIN32)
#include "fcntl.h"
#include "io.h"
#endif

#include <stdio.h>
#include <string.h>

char *progName = NULL;
static int cms_verbose = 0;
static secuPWData pwdata = { PW_NONE, 0 };
static PK11PasswordFunc pwcb = NULL;
static void *pwcb_arg = NULL;

/* XXX stolen from cmsarray.c
 * nss_CMSArray_Count - count number of elements in array
 */
int
nss_CMSArray_Count(void **array)
{
    int n = 0;
    if (array == NULL)
        return 0;
    while (*array++ != NULL)
        n++;
    return n;
}

static SECStatus
DigestFile(PLArenaPool *poolp, SECItem ***digests, SECItem *input,
           SECAlgorithmID **algids)
{
    NSSCMSDigestContext *digcx;
    SECStatus rv;

    digcx = NSS_CMSDigestContext_StartMultiple(algids);
    if (digcx == NULL)
        return SECFailure;

    NSS_CMSDigestContext_Update(digcx, input->data, input->len);

    rv = NSS_CMSDigestContext_FinishMultiple(digcx, poolp, digests);
    return rv;
}

static void
Usage(void)
{
    fprintf(stderr,
            "Usage:  %s [-C|-D|-E|-O|-S] [<options>] [-d dbdir] [-u certusage]\n"
            " -C            create a CMS encrypted data message\n"
            " -D            decode a CMS message\n"
            "  -b           decode a batch of files named in infile\n"
            "  -c content   use this detached content\n"
            "  -n           suppress output of content\n"
            "  -h num       display num levels of CMS message info as email headers\n"
            "  -k           keep decoded encryption certs in perm cert db\n"
            " -E            create a CMS enveloped data message\n"
            "  -r id,...    create envelope for these recipients,\n"
            "               where id can be a certificate nickname or email address\n"
            " -S            create a CMS signed data message\n"
            "  -G           include a signing time attribute\n"
            "  -H hash      use hash (default:SHA256)\n"
            "  -N nick      use certificate named \"nick\" for signing\n"
            "  -P           include a SMIMECapabilities attribute\n"
            "  -T           do not include content in CMS message\n"
            "  -Y nick      include a EncryptionKeyPreference attribute with cert\n"
            "                 (use \"NONE\" to omit)\n"
            " -O            create a CMS signed message containing only certificates\n"
            " General Options:\n"
            " -d dbdir      key/cert database directory (default: ~/.netscape)\n"
            " -e envelope   enveloped data message in this file is used for bulk key\n"
            " -i infile     use infile as source of data (default: stdin)\n"
            " -o outfile    use outfile as destination of data (default: stdout)\n"
            " -p password   use password as key db password (default: prompt)\n"
            " -f pwfile     use password file to set password on all PKCS#11 tokens)\n"
            " -u certusage  set type of certificate usage (default: certUsageEmailSigner)\n"
            " -v            print debugging information\n"
            "\n"
            "Cert usage codes:\n",
            progName);
    fprintf(stderr, "%-25s  0 - certUsageSSLClient\n", " ");
    fprintf(stderr, "%-25s  1 - certUsageSSLServer\n", " ");
    fprintf(stderr, "%-25s  2 - certUsageSSLServerWithStepUp\n", " ");
    fprintf(stderr, "%-25s  3 - certUsageSSLCA\n", " ");
    fprintf(stderr, "%-25s  4 - certUsageEmailSigner\n", " ");
    fprintf(stderr, "%-25s  5 - certUsageEmailRecipient\n", " ");
    fprintf(stderr, "%-25s  6 - certUsageObjectSigner\n", " ");
    fprintf(stderr, "%-25s  7 - certUsageUserCertImport\n", " ");
    fprintf(stderr, "%-25s  8 - certUsageVerifyCA\n", " ");
    fprintf(stderr, "%-25s  9 - certUsageProtectedObjectSigner\n", " ");
    fprintf(stderr, "%-25s 10 - certUsageStatusResponder\n", " ");
    fprintf(stderr, "%-25s 11 - certUsageAnyCA\n", " ");
    fprintf(stderr, "%-25s 12 - certUsageIPsec\n", " ");

    exit(-1);
}

struct optionsStr {
    char *pwfile;
    char *password;
    SECCertUsage certUsage;
    CERTCertDBHandle *certHandle;
};

struct decodeOptionsStr {
    struct optionsStr *options;
    SECItem content;
    int headerLevel;
    PRBool suppressContent;
    NSSCMSGetDecryptKeyCallback dkcb;
    PK11SymKey *bulkkey;
    PRBool keepCerts;
};

struct signOptionsStr {
    struct optionsStr *options;
    char *nickname;
    char *encryptionKeyPreferenceNick;
    PRBool signingTime;
    PRBool smimeProfile;
    PRBool detached;
    SECOidTag hashAlgTag;
};

struct envelopeOptionsStr {
    struct optionsStr *options;
    char **recipients;
};

struct certsonlyOptionsStr {
    struct optionsStr *options;
    char **recipients;
};

struct encryptOptionsStr {
    struct optionsStr *options;
    char **recipients;
    NSSCMSMessage *envmsg;
    SECItem *input;
    FILE *outfile;
    PRFileDesc *envFile;
    PK11SymKey *bulkkey;
    SECOidTag bulkalgtag;
    int keysize;
};

static NSSCMSMessage *
decode(FILE *out, SECItem *input, const struct decodeOptionsStr *decodeOptions)
{
    NSSCMSDecoderContext *dcx;
    SECStatus rv;
    NSSCMSMessage *cmsg;
    int nlevels, i;
    SECItem sitem = { 0, 0, 0 };

    PORT_SetError(0);
    dcx = NSS_CMSDecoder_Start(NULL,
                               NULL, NULL,          /* content callback     */
                               pwcb, pwcb_arg,      /* password callback    */
                               decodeOptions->dkcb, /* decrypt key callback */
                               decodeOptions->bulkkey);
    if (dcx == NULL) {
        fprintf(stderr, "%s: failed to set up message decoder.\n", progName);
        return NULL;
    }
    rv = NSS_CMSDecoder_Update(dcx, (char *)input->data, input->len);
    if (rv != SECSuccess) {
        fprintf(stderr, "%s: failed to decode message.\n", progName);
        NSS_CMSDecoder_Cancel(dcx);
        return NULL;
    }
    cmsg = NSS_CMSDecoder_Finish(dcx);
    if (cmsg == NULL) {
        fprintf(stderr, "%s: failed to decode message.\n", progName);
        return NULL;
    }

    if (decodeOptions->headerLevel >= 0) {
        /*fprintf(out, "SMIME: ", decodeOptions->headerLevel, i);*/
        fprintf(out, "SMIME: ");
    }

    nlevels = NSS_CMSMessage_ContentLevelCount(cmsg);
    for (i = 0; i < nlevels; i++) {
        NSSCMSContentInfo *cinfo;
        SECOidTag typetag;

        cinfo = NSS_CMSMessage_ContentLevel(cmsg, i);
        typetag = NSS_CMSContentInfo_GetContentTypeTag(cinfo);

        if (decodeOptions->headerLevel >= 0)
            fprintf(out, "\tlevel=%d.%d; ", decodeOptions->headerLevel, nlevels - i);

        switch (typetag) {
            case SEC_OID_PKCS7_SIGNED_DATA: {
                NSSCMSSignedData *sigd = NULL;
                SECItem **digests = NULL;
                int nsigners;
                int j;

                if (decodeOptions->headerLevel >= 0)
                    fprintf(out, "type=signedData; ");
                sigd = (NSSCMSSignedData *)NSS_CMSContentInfo_GetContent(cinfo);
                if (sigd == NULL) {
                    SECU_PrintError(progName, "signedData component missing");
                    goto loser;
                }

                /* if we have a content file, but no digests for this signedData */
                if (decodeOptions->content.data != NULL &&
                    !NSS_CMSSignedData_HasDigests(sigd)) {
                    PLArenaPool *poolp;
                    SECAlgorithmID **digestalgs;

                    /* detached content: grab content file */
                    sitem = decodeOptions->content;

                    if ((poolp = PORT_NewArena(1024)) == NULL) {
                        fprintf(stderr, "cmsutil: Out of memory.\n");
                        goto loser;
                    }
                    digestalgs = NSS_CMSSignedData_GetDigestAlgs(sigd);
                    if (DigestFile(poolp, &digests, &sitem, digestalgs) !=
                        SECSuccess) {
                        SECU_PrintError(progName,
                                        "problem computing message digest");
                        PORT_FreeArena(poolp, PR_FALSE);
                        goto loser;
                    }
                    if (NSS_CMSSignedData_SetDigests(sigd, digestalgs, digests) !=
                        SECSuccess) {
                        SECU_PrintError(progName,
                                        "problem setting message digests");
                        PORT_FreeArena(poolp, PR_FALSE);
                        goto loser;
                    }
                    PORT_FreeArena(poolp, PR_FALSE);
                }

                /* import the certificates */
                if (NSS_CMSSignedData_ImportCerts(sigd,
                                                  decodeOptions->options->certHandle,
                                                  decodeOptions->options->certUsage,
                                                  decodeOptions->keepCerts) !=
                    SECSuccess) {
                    SECU_PrintError(progName, "cert import failed");
                    goto loser;
                }

                /* find out about signers */
                nsigners = NSS_CMSSignedData_SignerInfoCount(sigd);
                if (decodeOptions->headerLevel >= 0)
                    fprintf(out, "nsigners=%d; ", nsigners);
                if (nsigners == 0) {
                    /* Might be a cert transport message
                    ** or might be an invalid message, such as a QA test message
                    ** or a message from an attacker.
                    */
                    rv = NSS_CMSSignedData_VerifyCertsOnly(sigd,
                                                           decodeOptions->options->certHandle,
                                                           decodeOptions->options->certUsage);
                    if (rv != SECSuccess) {
                        fprintf(stderr, "cmsutil: Verify certs-only failed!\n");
                        goto loser;
                    }
                    return cmsg;
                }

                /* still no digests? */
                if (!NSS_CMSSignedData_HasDigests(sigd)) {
                    SECU_PrintError(progName, "no message digests");
                    goto loser;
                }

                for (j = 0; j < nsigners; j++) {
                    const char *svs;
                    NSSCMSSignerInfo *si;
                    NSSCMSVerificationStatus vs;
                    SECStatus bad;

                    si = NSS_CMSSignedData_GetSignerInfo(sigd, j);
                    if (decodeOptions->headerLevel >= 0) {
                        char *signercn;
                        static char empty[] = { "" };

                        signercn = NSS_CMSSignerInfo_GetSignerCommonName(si);
                        if (signercn == NULL)
                            signercn = empty;
                        fprintf(out, "\n\t\tsigner%d.id=\"%s\"; ", j, signercn);
                        if (signercn != empty)
                            PORT_Free(signercn);
                    }
                    bad = NSS_CMSSignedData_VerifySignerInfo(sigd, j,
                                                             decodeOptions->options->certHandle,
                                                             decodeOptions->options->certUsage);
                    vs = NSS_CMSSignerInfo_GetVerificationStatus(si);
                    svs = NSS_CMSUtil_VerificationStatusToString(vs);
                    if (decodeOptions->headerLevel >= 0) {
                        fprintf(out, "signer%d.status=%s; ", j, svs);
                        /* goto loser ? */
                    } else if (bad && out) {
                        fprintf(stderr, "signer %d status = %s\n", j, svs);
                        goto loser;
                    }
                    /* if the signatures validate and we asked to keep
                     * the certs, save the profiles */
                    if (decodeOptions->keepCerts) {
                        rv = NSS_SMIMESignerInfo_SaveSMIMEProfile(si);
                        if (rv != SECSuccess) {
                            SECU_PrintError(progName, "SMIME profile import failed");
                            goto loser;
                        }
                    }
                }
            } break;
            case SEC_OID_PKCS7_ENVELOPED_DATA: {
                NSSCMSEnvelopedData *envd;
                if (decodeOptions->headerLevel >= 0)
                    fprintf(out, "type=envelopedData; ");
                envd = (NSSCMSEnvelopedData *)NSS_CMSContentInfo_GetContent(cinfo);
                if (envd == NULL) {
                    SECU_PrintError(progName, "envelopedData component missing");
                    goto loser;
                }
            } break;
            case SEC_OID_PKCS7_ENCRYPTED_DATA: {
                NSSCMSEncryptedData *encd;
                if (decodeOptions->headerLevel >= 0)
                    fprintf(out, "type=encryptedData; ");
                encd = (NSSCMSEncryptedData *)NSS_CMSContentInfo_GetContent(cinfo);
                if (encd == NULL) {
                    SECU_PrintError(progName, "encryptedData component missing");
                    goto loser;
                }
            } break;
            case SEC_OID_PKCS7_DATA:
                if (decodeOptions->headerLevel >= 0)
                    fprintf(out, "type=data; ");
                break;
            default:
                break;
        }
        if (decodeOptions->headerLevel >= 0)
            fprintf(out, "\n");
    }

    if (!decodeOptions->suppressContent && out) {
        SECItem *item = (sitem.data ? &sitem
                                    : NSS_CMSMessage_GetContent(cmsg));
        if (item && item->data && item->len) {
            fwrite(item->data, item->len, 1, out);
        }
    }
    return cmsg;

loser:
    if (cmsg)
        NSS_CMSMessage_Destroy(cmsg);
    return NULL;
}

/* example of a callback function to use with encoder */
/*
static void
writeout(void *arg, const char *buf, unsigned long len)
{
    FILE *f = (FILE *)arg;

    if (f != NULL && buf != NULL)
        (void)fwrite(buf, len, 1, f);
}
*/

static NSSCMSMessage *
signed_data(struct signOptionsStr *signOptions)
{
    NSSCMSMessage *cmsg = NULL;
    NSSCMSContentInfo *cinfo;
    NSSCMSSignedData *sigd;
    NSSCMSSignerInfo *signerinfo = NULL;
    CERTCertificate *cert = NULL, *ekpcert = NULL;

    if (cms_verbose) {
        fprintf(stderr, "Input to signed_data:\n");
        if (signOptions->options->password)
            fprintf(stderr, "password [%s]\n", signOptions->options->password);
        else if (signOptions->options->pwfile)
            fprintf(stderr, "password file [%s]\n", signOptions->options->pwfile);
        else
            fprintf(stderr, "password [NULL]\n");
        fprintf(stderr, "certUsage [%d]\n", signOptions->options->certUsage);
        if (signOptions->options->certHandle)
            fprintf(stderr, "certdb [%p]\n", signOptions->options->certHandle);
        else
            fprintf(stderr, "certdb [NULL]\n");
        if (signOptions->nickname)
            fprintf(stderr, "nickname [%s]\n", signOptions->nickname);
        else
            fprintf(stderr, "nickname [NULL]\n");
    }
    if (signOptions->nickname == NULL) {
        fprintf(stderr,
                "ERROR: please indicate the nickname of a certificate to sign with.\n");
        return NULL;
    }
    if ((cert = CERT_FindUserCertByUsage(signOptions->options->certHandle,
                                         signOptions->nickname,
                                         signOptions->options->certUsage,
                                         PR_FALSE,
                                         &pwdata)) == NULL) {
        SECU_PrintError(progName,
                        "the corresponding cert for key \"%s\" does not exist",
                        signOptions->nickname);
        return NULL;
    }
    if (cms_verbose) {
        fprintf(stderr, "Found certificate for %s\n", signOptions->nickname);
    }
    /*
     * create the message object
     */
    cmsg = NSS_CMSMessage_Create(NULL); /* create a message on its own pool */
    if (cmsg == NULL) {
        fprintf(stderr, "ERROR: cannot create CMS message.\n");
        return NULL;
    }
    /*
     * build chain of objects: message->signedData->data
     */
    if ((sigd = NSS_CMSSignedData_Create(cmsg)) == NULL) {
        fprintf(stderr, "ERROR: cannot create CMS signedData object.\n");
        goto loser;
    }
    cinfo = NSS_CMSMessage_GetContentInfo(cmsg);
    if (NSS_CMSContentInfo_SetContent_SignedData(cmsg, cinfo, sigd) !=
        SECSuccess) {
        fprintf(stderr, "ERROR: cannot attach CMS signedData object.\n");
        goto loser;
    }
    cinfo = NSS_CMSSignedData_GetContentInfo(sigd);
    /* we're always passing data in and detaching optionally */
    if (NSS_CMSContentInfo_SetContent_Data(cmsg, cinfo, NULL,
                                           signOptions->detached) !=
        SECSuccess) {
        fprintf(stderr, "ERROR: cannot attach CMS data object.\n");
        goto loser;
    }
    /*
     * create & attach signer information
     */
    signerinfo = NSS_CMSSignerInfo_Create(cmsg, cert, signOptions->hashAlgTag);
    if (signerinfo == NULL) {
        fprintf(stderr, "ERROR: cannot create CMS signerInfo object.\n");
        goto loser;
    }
    if (cms_verbose) {
        fprintf(stderr,
                "Created CMS message, added signed data w/ signerinfo\n");
    }
    signerinfo->cmsg->pwfn_arg = pwcb_arg;
    /* we want the cert chain included for this one */
    if (NSS_CMSSignerInfo_IncludeCerts(signerinfo, NSSCMSCM_CertChain,
                                       signOptions->options->certUsage) !=
        SECSuccess) {
        fprintf(stderr, "ERROR: cannot find cert chain.\n");
        goto loser;
    }
    if (cms_verbose) {
        fprintf(stderr, "imported certificate\n");
    }
    if (signOptions->signingTime) {
        if (NSS_CMSSignerInfo_AddSigningTime(signerinfo, PR_Now()) !=
            SECSuccess) {
            fprintf(stderr, "ERROR: cannot add signingTime attribute.\n");
            goto loser;
        }
    }
    if (signOptions->smimeProfile) {
        if (NSS_CMSSignerInfo_AddSMIMECaps(signerinfo) != SECSuccess) {
            fprintf(stderr, "ERROR: cannot add SMIMECaps attribute.\n");
            goto loser;
        }
    }

    if (!signOptions->encryptionKeyPreferenceNick) {
        /* check signing cert for fitness as encryption cert */
        SECStatus FitForEncrypt = CERT_CheckCertUsage(cert,
                                                      certUsageEmailRecipient);

        if (SECSuccess == FitForEncrypt) {
            /* if yes, add signing cert as EncryptionKeyPreference */
            if (NSS_CMSSignerInfo_AddSMIMEEncKeyPrefs(signerinfo, cert,
                                                      signOptions->options->certHandle) !=
                SECSuccess) {
                fprintf(stderr,
                        "ERROR: cannot add default SMIMEEncKeyPrefs attribute.\n");
                goto loser;
            }
            if (NSS_CMSSignerInfo_AddMSSMIMEEncKeyPrefs(signerinfo, cert,
                                                        signOptions->options->certHandle) !=
                SECSuccess) {
                fprintf(stderr,
                        "ERROR: cannot add default MS SMIMEEncKeyPrefs attribute.\n");
                goto loser;
            }
        } else {
            /* this is a dual-key cert case, we need to look for the encryption
               certificate under the same nickname as the signing cert */
            /* get the cert, add it to the message */
            if ((ekpcert = CERT_FindUserCertByUsage(
                     signOptions->options->certHandle,
                     signOptions->nickname,
                     certUsageEmailRecipient,
                     PR_FALSE,
                     &pwdata)) == NULL) {
                SECU_PrintError(progName,
                                "the corresponding cert for key \"%s\" does not exist",
                                signOptions->encryptionKeyPreferenceNick);
                goto loser;
            }
            if (NSS_CMSSignerInfo_AddSMIMEEncKeyPrefs(signerinfo, ekpcert,
                                                      signOptions->options->certHandle) !=
                SECSuccess) {
                fprintf(stderr,
                        "ERROR: cannot add SMIMEEncKeyPrefs attribute.\n");
                goto loser;
            }
            if (NSS_CMSSignerInfo_AddMSSMIMEEncKeyPrefs(signerinfo, ekpcert,
                                                        signOptions->options->certHandle) !=
                SECSuccess) {
                fprintf(stderr,
                        "ERROR: cannot add MS SMIMEEncKeyPrefs attribute.\n");
                goto loser;
            }
            if (NSS_CMSSignedData_AddCertificate(sigd, ekpcert) != SECSuccess) {
                fprintf(stderr, "ERROR: cannot add encryption certificate.\n");
                goto loser;
            }
        }
    } else if (PL_strcmp(signOptions->encryptionKeyPreferenceNick, "NONE") == 0) {
        /* No action */
    } else {
        /* get the cert, add it to the message */
        if ((ekpcert = CERT_FindUserCertByUsage(
                 signOptions->options->certHandle,
                 signOptions->encryptionKeyPreferenceNick,
                 certUsageEmailRecipient, PR_FALSE, &pwdata)) ==
            NULL) {
            SECU_PrintError(progName,
                            "the corresponding cert for key \"%s\" does not exist",
                            signOptions->encryptionKeyPreferenceNick);
            goto loser;
        }
        if (NSS_CMSSignerInfo_AddSMIMEEncKeyPrefs(signerinfo, ekpcert,
                                                  signOptions->options->certHandle) !=
            SECSuccess) {
            fprintf(stderr, "ERROR: cannot add SMIMEEncKeyPrefs attribute.\n");
            goto loser;
        }
        if (NSS_CMSSignerInfo_AddMSSMIMEEncKeyPrefs(signerinfo, ekpcert,
                                                    signOptions->options->certHandle) !=
            SECSuccess) {
            fprintf(stderr, "ERROR: cannot add MS SMIMEEncKeyPrefs attribute.\n");
            goto loser;
        }
        if (NSS_CMSSignedData_AddCertificate(sigd, ekpcert) != SECSuccess) {
            fprintf(stderr, "ERROR: cannot add encryption certificate.\n");
            goto loser;
        }
    }

    if (NSS_CMSSignedData_AddSignerInfo(sigd, signerinfo) != SECSuccess) {
        fprintf(stderr, "ERROR: cannot add CMS signerInfo object.\n");
        goto loser;
    }
    signerinfo = NULL; /* sigd has adopted signerinfo */
    if (cms_verbose) {
        fprintf(stderr, "created signed-data message\n");
    }
    if (ekpcert) {
        CERT_DestroyCertificate(ekpcert);
    }
    if (cert) {
        CERT_DestroyCertificate(cert);
    }
    return cmsg;
loser:
    if (ekpcert) {
        CERT_DestroyCertificate(ekpcert);
    }
    if (cert) {
        CERT_DestroyCertificate(cert);
    }
    if (signerinfo) {
        NSS_CMSSignerInfo_Destroy(signerinfo);
    }
    NSS_CMSMessage_Destroy(cmsg);
    return NULL;
}

static NSSCMSMessage *
enveloped_data(struct envelopeOptionsStr *envelopeOptions)
{
    NSSCMSMessage *cmsg = NULL;
    NSSCMSContentInfo *cinfo;
    NSSCMSEnvelopedData *envd;
    NSSCMSRecipientInfo *recipientinfo;
    CERTCertificate **recipientcerts = NULL;
    CERTCertDBHandle *dbhandle;
    PLArenaPool *tmppoolp = NULL;
    SECOidTag bulkalgtag;
    int keysize, i = 0;
    int cnt;
    dbhandle = envelopeOptions->options->certHandle;
    /* count the recipients */
    if ((cnt = nss_CMSArray_Count((void **)envelopeOptions->recipients)) == 0) {
        fprintf(stderr, "ERROR: please name at least one recipient.\n");
        goto loser;
    }
    if ((tmppoolp = PORT_NewArena(1024)) == NULL) {
        fprintf(stderr, "ERROR: out of memory.\n");
        goto loser;
    }
    /* XXX find the recipient's certs by email address or nickname */
    if ((recipientcerts =
             (CERTCertificate **)PORT_ArenaZAlloc(tmppoolp,
                                                  (cnt + 1) * sizeof(CERTCertificate *))) ==
        NULL) {
        fprintf(stderr, "ERROR: out of memory.\n");
        goto loser;
    }
    for (i = 0; envelopeOptions->recipients[i] != NULL; i++) {
        if ((recipientcerts[i] =
                 CERT_FindCertByNicknameOrEmailAddr(dbhandle,
                                                    envelopeOptions->recipients[i])) ==
            NULL) {
            SECU_PrintError(progName, "cannot find certificate for \"%s\"",
                            envelopeOptions->recipients[i]);
            i = 0;
            goto loser;
        }
    }
    recipientcerts[i] = NULL;
    i = 0;
    /* find a nice bulk algorithm */
    if (NSS_SMIMEUtil_FindBulkAlgForRecipients(recipientcerts, &bulkalgtag,
                                               &keysize) != SECSuccess) {
        fprintf(stderr, "ERROR: cannot find common bulk algorithm.\n");
        goto loser;
    }
    /*
     * create the message object
     */
    cmsg = NSS_CMSMessage_Create(NULL); /* create a message on its own pool */
    if (cmsg == NULL) {
        fprintf(stderr, "ERROR: cannot create CMS message.\n");
        goto loser;
    }
    /*
     * build chain of objects: message->envelopedData->data
     */
    if ((envd = NSS_CMSEnvelopedData_Create(cmsg, bulkalgtag, keysize)) ==
        NULL) {
        fprintf(stderr, "ERROR: cannot create CMS envelopedData object.\n");
        goto loser;
    }
    cinfo = NSS_CMSMessage_GetContentInfo(cmsg);
    if (NSS_CMSContentInfo_SetContent_EnvelopedData(cmsg, cinfo, envd) !=
        SECSuccess) {
        fprintf(stderr, "ERROR: cannot attach CMS envelopedData object.\n");
        goto loser;
    }
    cinfo = NSS_CMSEnvelopedData_GetContentInfo(envd);
    /* we're always passing data in, so the content is NULL */
    if (NSS_CMSContentInfo_SetContent_Data(cmsg, cinfo, NULL, PR_FALSE) !=
        SECSuccess) {
        fprintf(stderr, "ERROR: cannot attach CMS data object.\n");
        goto loser;
    }
    /*
     * create & attach recipient information
     */
    for (i = 0; recipientcerts[i] != NULL; i++) {
        if ((recipientinfo = NSS_CMSRecipientInfo_Create(cmsg,
                                                         recipientcerts[i])) ==
            NULL) {
            fprintf(stderr, "ERROR: cannot create CMS recipientInfo object.\n");
            goto loser;
        }
        if (NSS_CMSEnvelopedData_AddRecipient(envd, recipientinfo) !=
            SECSuccess) {
            fprintf(stderr, "ERROR: cannot add CMS recipientInfo object.\n");
            goto loser;
        }
        CERT_DestroyCertificate(recipientcerts[i]);
    }
    if (tmppoolp)
        PORT_FreeArena(tmppoolp, PR_FALSE);
    return cmsg;
loser:
    if (recipientcerts) {
        for (; recipientcerts[i] != NULL; i++) {
            CERT_DestroyCertificate(recipientcerts[i]);
        }
    }
    if (cmsg)
        NSS_CMSMessage_Destroy(cmsg);
    if (tmppoolp)
        PORT_FreeArena(tmppoolp, PR_FALSE);
    return NULL;
}

PK11SymKey *
dkcb(void *arg, SECAlgorithmID *algid)
{
    return (PK11SymKey *)arg;
}

static SECStatus
get_enc_params(struct encryptOptionsStr *encryptOptions)
{
    struct envelopeOptionsStr envelopeOptions;
    SECStatus rv = SECFailure;
    NSSCMSMessage *env_cmsg;
    NSSCMSContentInfo *cinfo;
    int i, nlevels;
    /*
     * construct an enveloped data message to obtain bulk keys
     */
    if (encryptOptions->envmsg) {
        env_cmsg = encryptOptions->envmsg; /* get it from an old message */
    } else {
        SECItem dummyOut = { 0, 0, 0 };
        SECItem dummyIn = { 0, 0, 0 };
        char str[] = "Hello!";
        PLArenaPool *tmparena = PORT_NewArena(1024);
        dummyIn.data = (unsigned char *)str;
        dummyIn.len = strlen(str);
        envelopeOptions.options = encryptOptions->options;
        envelopeOptions.recipients = encryptOptions->recipients;
        env_cmsg = enveloped_data(&envelopeOptions);
        NSS_CMSDEREncode(env_cmsg, &dummyIn, &dummyOut, tmparena);
        PR_Write(encryptOptions->envFile, dummyOut.data, dummyOut.len);
        PORT_FreeArena(tmparena, PR_FALSE);
    }
    /*
     * get the content info for the enveloped data
     */
    nlevels = NSS_CMSMessage_ContentLevelCount(env_cmsg);
    for (i = 0; i < nlevels; i++) {
        SECOidTag typetag;
        cinfo = NSS_CMSMessage_ContentLevel(env_cmsg, i);
        typetag = NSS_CMSContentInfo_GetContentTypeTag(cinfo);
        if (typetag == SEC_OID_PKCS7_DATA) {
            /*
             * get the symmetric key
             */
            encryptOptions->bulkalgtag = NSS_CMSContentInfo_GetContentEncAlgTag(cinfo);
            encryptOptions->keysize = NSS_CMSContentInfo_GetBulkKeySize(cinfo);
            encryptOptions->bulkkey = NSS_CMSContentInfo_GetBulkKey(cinfo);
            rv = SECSuccess;
            break;
        }
    }
    if (i == nlevels) {
        fprintf(stderr, "%s: could not retrieve enveloped data.", progName);
    }
    if (env_cmsg)
        NSS_CMSMessage_Destroy(env_cmsg);
    return rv;
}

static NSSCMSMessage *
encrypted_data(struct encryptOptionsStr *encryptOptions)
{
    SECStatus rv = SECFailure;
    NSSCMSMessage *cmsg = NULL;
    NSSCMSContentInfo *cinfo;
    NSSCMSEncryptedData *encd;
    NSSCMSEncoderContext *ecx = NULL;
    PLArenaPool *tmppoolp = NULL;
    SECItem derOut = { 0, 0, 0 };
    /* arena for output */
    tmppoolp = PORT_NewArena(1024);
    if (!tmppoolp) {
        fprintf(stderr, "%s: out of memory.\n", progName);
        return NULL;
    }
    /*
     * create the message object
     */
    cmsg = NSS_CMSMessage_Create(NULL);
    if (cmsg == NULL) {
        fprintf(stderr, "ERROR: cannot create CMS message.\n");
        goto loser;
    }
    /*
     * build chain of objects: message->encryptedData->data
     */
    if ((encd = NSS_CMSEncryptedData_Create(cmsg, encryptOptions->bulkalgtag,
                                            encryptOptions->keysize)) ==
        NULL) {
        fprintf(stderr, "ERROR: cannot create CMS encryptedData object.\n");
        goto loser;
    }
    cinfo = NSS_CMSMessage_GetContentInfo(cmsg);
    if (NSS_CMSContentInfo_SetContent_EncryptedData(cmsg, cinfo, encd) !=
        SECSuccess) {
        fprintf(stderr, "ERROR: cannot attach CMS encryptedData object.\n");
        goto loser;
    }
    cinfo = NSS_CMSEncryptedData_GetContentInfo(encd);
    /* we're always passing data in, so the content is NULL */
    if (NSS_CMSContentInfo_SetContent_Data(cmsg, cinfo, NULL, PR_FALSE) !=
        SECSuccess) {
        fprintf(stderr, "ERROR: cannot attach CMS data object.\n");
        goto loser;
    }
    ecx = NSS_CMSEncoder_Start(cmsg, NULL, NULL, &derOut, tmppoolp, NULL, NULL,
                               dkcb, encryptOptions->bulkkey, NULL, NULL);
    if (!ecx) {
        fprintf(stderr, "%s: cannot create encoder context.\n", progName);
        goto loser;
    }
    rv = NSS_CMSEncoder_Update(ecx, (char *)encryptOptions->input->data,
                               encryptOptions->input->len);
    if (rv) {
        fprintf(stderr, "%s: failed to add data to encoder.\n", progName);
        goto loser;
    }
    rv = NSS_CMSEncoder_Finish(ecx);
    if (rv) {
        fprintf(stderr, "%s: failed to encrypt data.\n", progName);
        goto loser;
    }
    fwrite(derOut.data, derOut.len, 1, encryptOptions->outfile);
    /*
    if (bulkkey)
        PK11_FreeSymKey(bulkkey);
        */
    if (tmppoolp)
        PORT_FreeArena(tmppoolp, PR_FALSE);
    return cmsg;
loser:
    /*
    if (bulkkey)
        PK11_FreeSymKey(bulkkey);
        */
    if (tmppoolp)
        PORT_FreeArena(tmppoolp, PR_FALSE);
    if (cmsg)
        NSS_CMSMessage_Destroy(cmsg);
    return NULL;
}

static NSSCMSMessage *
signed_data_certsonly(struct certsonlyOptionsStr *certsonlyOptions)
{
    NSSCMSMessage *cmsg = NULL;
    NSSCMSContentInfo *cinfo;
    NSSCMSSignedData *sigd;
    CERTCertificate **certs = NULL;
    CERTCertDBHandle *dbhandle;
    PLArenaPool *tmppoolp = NULL;
    int i = 0, cnt;
    dbhandle = certsonlyOptions->options->certHandle;
    if ((cnt = nss_CMSArray_Count((void **)certsonlyOptions->recipients)) == 0) {
        fprintf(stderr,
                "ERROR: please indicate the nickname of a certificate to sign with.\n");
        goto loser;
    }
    if (!(tmppoolp = PORT_NewArena(1024))) {
        fprintf(stderr, "ERROR: out of memory.\n");
        goto loser;
    }
    if (!(certs = PORT_ArenaZNewArray(tmppoolp, CERTCertificate *, cnt + 1))) {
        fprintf(stderr, "ERROR: out of memory.\n");
        goto loser;
    }
    for (i = 0; certsonlyOptions->recipients[i] != NULL; i++) {
        if ((certs[i] =
                 CERT_FindCertByNicknameOrEmailAddr(dbhandle,
                                                    certsonlyOptions->recipients[i])) ==
            NULL) {
            SECU_PrintError(progName, "cannot find certificate for \"%s\"",
                            certsonlyOptions->recipients[i]);
            i = 0;
            goto loser;
        }
    }
    certs[i] = NULL;
    i = 0;
    /*
     * create the message object
     */
    cmsg = NSS_CMSMessage_Create(NULL);
    if (cmsg == NULL) {
        fprintf(stderr, "ERROR: cannot create CMS message.\n");
        goto loser;
    }
    /*
     * build chain of objects: message->signedData->data
     */
    if ((sigd = NSS_CMSSignedData_CreateCertsOnly(cmsg, certs[0], PR_TRUE)) ==
        NULL) {
        fprintf(stderr, "ERROR: cannot create CMS signedData object.\n");
        goto loser;
    }
    CERT_DestroyCertificate(certs[0]);
    for (i = 1; i < cnt; i++) {
        if (NSS_CMSSignedData_AddCertChain(sigd, certs[i])) {
            fprintf(stderr, "ERROR: cannot add cert chain for \"%s\".\n",
                    certsonlyOptions->recipients[i]);
            goto loser;
        }
        CERT_DestroyCertificate(certs[i]);
    }
    cinfo = NSS_CMSMessage_GetContentInfo(cmsg);
    if (NSS_CMSContentInfo_SetContent_SignedData(cmsg, cinfo, sigd) !=
        SECSuccess) {
        fprintf(stderr, "ERROR: cannot attach CMS signedData object.\n");
        goto loser;
    }
    cinfo = NSS_CMSSignedData_GetContentInfo(sigd);
    if (NSS_CMSContentInfo_SetContent_Data(cmsg, cinfo, NULL, PR_FALSE) !=
        SECSuccess) {
        fprintf(stderr, "ERROR: cannot attach CMS data object.\n");
        goto loser;
    }
    if (tmppoolp)
        PORT_FreeArena(tmppoolp, PR_FALSE);
    return cmsg;
loser:
    if (certs) {
        for (; i < cnt; i++) {
            CERT_DestroyCertificate(certs[i]);
        }
    }
    if (cmsg)
        NSS_CMSMessage_Destroy(cmsg);
    if (tmppoolp)
        PORT_FreeArena(tmppoolp, PR_FALSE);
    return NULL;
}

static char *
pl_fgets(char *buf, int size, PRFileDesc *fd)
{
    char *bp = buf;
    int nb = 0;
    ;

    while (size > 1) {
        nb = PR_Read(fd, bp, 1);
        if (nb < 0) {
            /* deal with error */
            return NULL;
        } else if (nb == 0) {
            /* deal with EOF */
            return NULL;
        } else if (*bp == '\n') {
            /* deal with EOL */
            ++bp; /* keep EOL character */
            break;
        } else {
            /* ordinary character */
            ++bp;
            --size;
        }
    }
    *bp = '\0';
    return buf;
}

typedef enum { UNKNOWN,
               DECODE,
               SIGN,
               ENCRYPT,
               ENVELOPE,
               CERTSONLY } Mode;

static int
doBatchDecode(FILE *outFile, PRFileDesc *batchFile,
              const struct decodeOptionsStr *decodeOptions)
{
    char *str;
    int exitStatus = 0;
    char batchLine[512];

    while (NULL != (str = pl_fgets(batchLine, sizeof batchLine, batchFile))) {
        NSSCMSMessage *cmsg = NULL;
        PRFileDesc *inFile;
        int len = strlen(str);
        SECStatus rv;
        SECItem input = { 0, 0, 0 };
        char cc;

        while (len > 0 &&
               ((cc = str[len - 1]) == '\n' || cc == '\r')) {
            str[--len] = '\0';
        }
        if (!len) /* skip empty line */
            continue;
        if (str[0] == '#')
            continue; /* skip comment line */
        fprintf(outFile, "========== %s ==========\n", str);
        inFile = PR_Open(str, PR_RDONLY, 00660);
        if (inFile == NULL) {
            fprintf(outFile, "%s: unable to open \"%s\" for reading\n",
                    progName, str);
            exitStatus = 1;
            continue;
        }
        rv = SECU_FileToItem(&input, inFile);
        PR_Close(inFile);
        if (rv != SECSuccess) {
            SECU_PrintError(progName, "unable to read infile");
            exitStatus = 1;
            continue;
        }
        cmsg = decode(outFile, &input, decodeOptions);
        SECITEM_FreeItem(&input, PR_FALSE);
        if (cmsg)
            NSS_CMSMessage_Destroy(cmsg);
        else {
            SECU_PrintError(progName, "problem decoding");
            exitStatus = 1;
        }
    }
    return exitStatus;
}

/* legacy SHA2 table...
 * cmsutil took hash values of SHA256, SHA244, etc., the the
 * oid table has values of SHA-256, SHA-224. Use the follow
 * table to handle the old values. NOTE: no need to add new
 * hashes to this table, just use the actual oid table
 * values */
typedef struct LegacyHashNameStr {
    char *name;
    SECOidTag tag;
} LegacyHashName;

LegacyHashName legacyHashNamesTable[] = {
    { "SHA1", SEC_OID_SHA1 },
    { "SHA224", SEC_OID_SHA224 },
    { "SHA256", SEC_OID_SHA256 },
    { "SHA384", SEC_OID_SHA384 },
    { "SHA512", SEC_OID_SHA512 },
};
size_t legacyHashNamesTableSize = PR_ARRAY_SIZE(legacyHashNamesTable);

SECOidTag
CMSU_FindTagFromString(const char *cipherString)
{
    SECOidTag tag;
    size_t slen;

    /* future enhancement: accept dotted oid spec? */
    slen = PORT_Strlen(cipherString);
    tag = SECOID_FindOIDTagFromDescripton(cipherString, slen, PR_TRUE);
    if (tag != SEC_OID_UNKNOWN) {
        return tag;
    }

    if ((slen > 3) && (PORT_Strncasecmp(cipherString, "SHA", 3) == 0) &&
        (cipherString[3] != '-')) {
        int i;
        for (i = 0; i < legacyHashNamesTableSize; i++) {
            if (PORT_Strcasecmp(legacyHashNamesTable[i].name, cipherString) == 0) {
                return legacyHashNamesTable[i].tag;
            }
        }
        /* not on any table, must be invalid */
    }
    return SEC_OID_UNKNOWN;
}

int
main(int argc, char **argv)
{
    FILE *outFile;
    NSSCMSMessage *cmsg = NULL;
    PRFileDesc *inFile;
    PLOptState *optstate;
    PLOptStatus status;
    Mode mode = UNKNOWN;
    struct decodeOptionsStr decodeOptions = { 0 };
    struct signOptionsStr signOptions = { 0 };
    struct envelopeOptionsStr envelopeOptions = { 0 };
    struct certsonlyOptionsStr certsonlyOptions = { 0 };
    struct encryptOptionsStr encryptOptions = { 0 };
    struct optionsStr options = { 0 };
    int exitstatus;
    static char *ptrarray[128] = { 0 };
    int nrecipients = 0;
    char *str, *tok;
    char *envFileName;
    SECItem input = { 0, 0, 0 };
    SECItem envmsg = { 0, 0, 0 };
    SECStatus rv;
    PRFileDesc *contentFile = NULL;
    PRBool batch = PR_FALSE;

#ifdef NISCC_TEST
    const char *ev = PR_GetEnvSecure("NSS_DISABLE_ARENA_FREE_LIST");
    PORT_Assert(ev);
    ev = PR_GetEnvSecure("NSS_STRICT_SHUTDOWN");
    PORT_Assert(ev);
#endif

    SECOID_Init();

    progName = strrchr(argv[0], '/');
    if (!progName)
        progName = strrchr(argv[0], '\\');
    progName = progName ? progName + 1 : argv[0];

    inFile = PR_STDIN;
    outFile = stdout;
    envFileName = NULL;
    mode = UNKNOWN;
    decodeOptions.content.data = NULL;
    decodeOptions.content.len = 0;
    decodeOptions.suppressContent = PR_FALSE;
    decodeOptions.headerLevel = -1;
    decodeOptions.keepCerts = PR_FALSE;
    options.certUsage = certUsageEmailSigner;
    options.password = NULL;
    options.pwfile = NULL;
    signOptions.nickname = NULL;
    signOptions.detached = PR_FALSE;
    signOptions.signingTime = PR_FALSE;
    signOptions.smimeProfile = PR_FALSE;
    signOptions.encryptionKeyPreferenceNick = NULL;
    signOptions.hashAlgTag = SEC_OID_SHA256;
    envelopeOptions.recipients = NULL;
    encryptOptions.recipients = NULL;
    encryptOptions.envmsg = NULL;
    encryptOptions.envFile = NULL;
    encryptOptions.bulkalgtag = SEC_OID_UNKNOWN;
    encryptOptions.bulkkey = NULL;
    encryptOptions.keysize = -1;

    /*
     * Parse command line arguments
     */
    optstate = PL_CreateOptState(argc, argv,
                                 "CDEGH:N:OPSTY:bc:d:e:f:h:i:kno:p:r:s:u:v");
    while ((status = PL_GetNextOpt(optstate)) == PL_OPT_OK) {
        switch (optstate->option) {
            case 'C':
                mode = ENCRYPT;
                break;
            case 'D':
                mode = DECODE;
                break;
            case 'E':
                mode = ENVELOPE;
                break;
            case 'G':
                if (mode != SIGN) {
                    fprintf(stderr,
                            "%s: option -G only supported with option -S.\n",
                            progName);
                    Usage();
                    exit(1);
                }
                signOptions.signingTime = PR_TRUE;
                break;
            case 'H':
                if (mode != SIGN) {
                    fprintf(stderr,
                            "%s: option -H only supported with option -S.\n",
                            progName);
                    Usage();
                    exit(1);
                }
                decodeOptions.suppressContent = PR_TRUE;
                /* lookup hash value from our oid table and make sure it's a hash
                 * using HASH_ functions */
                signOptions.hashAlgTag = CMSU_FindTagFromString(optstate->value);
                if (HASH_GetHashTypeByOidTag(signOptions.hashAlgTag) == HASH_AlgNULL) {
                    char *comma = "";
                    int i;
                    /* it wasn't, use the HASH_ functions to find the valid values
                     * and print it as an error */
                    fprintf(stderr,
                            "%s: -H requires one of ", progName);
                    for (i = HASH_AlgNULL + 1; PR_TRUE; i++) {
                        SECOidTag hashTag = HASH_GetHashOidTagByHashType(i);
                        if (hashTag == SEC_OID_UNKNOWN) {
                            fprintf(stderr, "\n");
                            exit(1);
                        }
                        fprintf(stderr, "%s%s", comma, SECOID_FindOIDTagDescription(hashTag));
                        comma = ",";
                    }
                    /* NOT REACHED */
                }
                break;
            case 'N':
                if (mode != SIGN) {
                    fprintf(stderr,
                            "%s: option -N only supported with option -S.\n",
                            progName);
                    Usage();
                    exit(1);
                }
                signOptions.nickname = PORT_Strdup(optstate->value);
                break;
            case 'O':
                mode = CERTSONLY;
                break;
            case 'P':
                if (mode != SIGN) {
                    fprintf(stderr,
                            "%s: option -P only supported with option -S.\n",
                            progName);
                    Usage();
                    exit(1);
                }
                signOptions.smimeProfile = PR_TRUE;
                break;
            case 'S':
                mode = SIGN;
                break;
            case 'T':
                if (mode != SIGN) {
                    fprintf(stderr,
                            "%s: option -T only supported with option -S.\n",
                            progName);
                    Usage();
                    exit(1);
                }
                signOptions.detached = PR_TRUE;
                break;
            case 'Y':
                if (mode != SIGN) {
                    fprintf(stderr,
                            "%s: option -Y only supported with option -S.\n",
                            progName);
                    Usage();
                    exit(1);
                }
                signOptions.encryptionKeyPreferenceNick = strdup(optstate->value);
                break;

            case 'b':
                if (mode != DECODE) {
                    fprintf(stderr,
                            "%s: option -b only supported with option -D.\n",
                            progName);
                    Usage();
                    exit(1);
                }
                batch = PR_TRUE;
                break;

            case 'c':
                if (mode != DECODE) {
                    fprintf(stderr,
                            "%s: option -c only supported with option -D.\n",
                            progName);
                    Usage();
                    exit(1);
                }
                contentFile = PR_Open(optstate->value, PR_RDONLY, 006600);
                if (contentFile == NULL) {
                    fprintf(stderr, "%s: unable to open \"%s\" for reading.\n",
                            progName, optstate->value);
                    exit(1);
                }

                rv = SECU_FileToItem(&decodeOptions.content, contentFile);
                PR_Close(contentFile);
                if (rv != SECSuccess) {
                    SECU_PrintError(progName, "problem reading content file");
                    exit(1);
                }
                if (!decodeOptions.content.data) {
                    /* file was zero length */
                    decodeOptions.content.data = (unsigned char *)PORT_Strdup("");
                    decodeOptions.content.len = 0;
                }

                break;
            case 'd':
                SECU_ConfigDirectory(optstate->value);
                break;
            case 'e':
                envFileName = PORT_Strdup(optstate->value);
                encryptOptions.envFile = PR_Open(envFileName, PR_RDONLY, 00660);
                break;

            case 'h':
                if (mode != DECODE) {
                    fprintf(stderr,
                            "%s: option -h only supported with option -D.\n",
                            progName);
                    Usage();
                    exit(1);
                }
                decodeOptions.headerLevel = atoi(optstate->value);
                if (decodeOptions.headerLevel < 0) {
                    fprintf(stderr, "option -h cannot have a negative value.\n");
                    exit(1);
                }
                break;
            case 'i':
                if (!optstate->value) {
                    fprintf(stderr, "-i option requires filename argument\n");
                    exit(1);
                }
                inFile = PR_Open(optstate->value, PR_RDONLY, 00660);
                if (inFile == NULL) {
                    fprintf(stderr, "%s: unable to open \"%s\" for reading\n",
                            progName, optstate->value);
                    exit(1);
                }
                break;

            case 'k':
                if (mode != DECODE) {
                    fprintf(stderr,
                            "%s: option -k only supported with option -D.\n",
                            progName);
                    Usage();
                    exit(1);
                }
                decodeOptions.keepCerts = PR_TRUE;
                break;

            case 'n':
                if (mode != DECODE) {
                    fprintf(stderr,
                            "%s: option -n only supported with option -D.\n",
                            progName);
                    Usage();
                    exit(1);
                }
                decodeOptions.suppressContent = PR_TRUE;
                break;
            case 'o':
                outFile = fopen(optstate->value, "wb");
                if (outFile == NULL) {
                    fprintf(stderr, "%s: unable to open \"%s\" for writing\n",
                            progName, optstate->value);
                    exit(1);
                }
                break;
            case 'p':
                if (!optstate->value) {
                    fprintf(stderr, "%s: option -p must have a value.\n", progName);
                    Usage();
                    exit(1);
                }

                options.password = strdup(optstate->value);
                break;

            case 'f':
                if (!optstate->value) {
                    fprintf(stderr, "%s: option -f must have a value.\n", progName);
                    Usage();
                    exit(1);
                }

                options.pwfile = strdup(optstate->value);
                break;

            case 'r':
                if (!optstate->value) {
                    fprintf(stderr, "%s: option -r must have a value.\n", progName);
                    Usage();
                    exit(1);
                }
                envelopeOptions.recipients = ptrarray;
                str = (char *)optstate->value;
                do {
                    tok = strchr(str, ',');
                    if (tok)
                        *tok = '\0';
                    envelopeOptions.recipients[nrecipients++] = strdup(str);
                    if (tok)
                        str = tok + 1;
                } while (tok);
                envelopeOptions.recipients[nrecipients] = NULL;
                encryptOptions.recipients = envelopeOptions.recipients;
                certsonlyOptions.recipients = envelopeOptions.recipients;
                break;

            case 'u': {
                int usageType;

                usageType = atoi(strdup(optstate->value));
                if (usageType < certUsageSSLClient || usageType > certUsageAnyCA)
                    return -1;
                options.certUsage = (SECCertUsage)usageType;
                break;
            }
            case 'v':
                cms_verbose = 1;
                break;
        }
    }
    if (status == PL_OPT_BAD)
        Usage();
    PL_DestroyOptState(optstate);

    if (mode == UNKNOWN)
        Usage();

    if (mode != CERTSONLY && !batch) {
        rv = SECU_FileToItem(&input, inFile);
        if (rv != SECSuccess) {
            SECU_PrintError(progName, "unable to read infile");
            exit(1);
        }
    }
    if (cms_verbose) {
        fprintf(stderr, "received commands\n");
    }

    /* Call the NSS initialization routines */
    PR_Init(PR_SYSTEM_THREAD, PR_PRIORITY_NORMAL, 1);
    rv = NSS_InitReadWrite(SECU_ConfigDirectory(NULL));
    if (SECSuccess != rv) {
        SECU_PrintError(progName, "NSS_Init failed");
        exit(1);
    }
    if (cms_verbose) {
        fprintf(stderr, "NSS has been initialized.\n");
    }
    options.certHandle = CERT_GetDefaultCertDB();
    if (!options.certHandle) {
        SECU_PrintError(progName, "No default cert DB");
        exit(1);
    }
    if (cms_verbose) {
        fprintf(stderr, "Got default certdb\n");
    }
    if (options.password) {
        pwdata.source = PW_PLAINTEXT;
        pwdata.data = options.password;
    }
    if (options.pwfile) {
        pwdata.source = PW_FROMFILE;
        pwdata.data = options.pwfile;
    }
    pwcb = SECU_GetModulePassword;
    pwcb_arg = (void *)&pwdata;

    PK11_SetPasswordFunc(&SECU_GetModulePassword);

#if defined(_WIN32)
    if (outFile == stdout) {
        /* If we're going to write binary data to stdout, we must put stdout
        ** into O_BINARY mode or else outgoing \n's will become \r\n's.
        */
        int smrv = _setmode(_fileno(stdout), _O_BINARY);
        if (smrv == -1) {
            fprintf(stderr,
                    "%s: Cannot change stdout to binary mode. Use -o option instead.\n",
                    progName);
            return smrv;
        }
    }
#endif

    exitstatus = 0;
    switch (mode) {
        case DECODE: /* -D */
            decodeOptions.options = &options;
            if (encryptOptions.envFile) {
                /* Decoding encrypted-data, so get the bulkkey from an
                 * enveloped-data message.
                 */
                SECU_FileToItem(&envmsg, encryptOptions.envFile);
                decodeOptions.options = &options;
                encryptOptions.envmsg = decode(NULL, &envmsg, &decodeOptions);
                if (!encryptOptions.envmsg) {
                    SECU_PrintError(progName, "problem decoding env msg");
                    exitstatus = 1;
                    break;
                }
                rv = get_enc_params(&encryptOptions);
                decodeOptions.dkcb = dkcb;
                decodeOptions.bulkkey = encryptOptions.bulkkey;
            }
            if (!batch) {
                cmsg = decode(outFile, &input, &decodeOptions);
                if (!cmsg) {
                    SECU_PrintError(progName, "problem decoding");
                    exitstatus = 1;
                }
            } else {
                exitstatus = doBatchDecode(outFile, inFile, &decodeOptions);
            }
            break;
        case SIGN: /* -S */
            signOptions.options = &options;
            cmsg = signed_data(&signOptions);
            if (!cmsg) {
                SECU_PrintError(progName, "problem signing");
                exitstatus = 1;
            }
            break;
        case ENCRYPT: /* -C */
            if (!envFileName) {
                fprintf(stderr, "%s: you must specify an envelope file with -e.\n",
                        progName);
                exit(1);
            }
            encryptOptions.options = &options;
            encryptOptions.input = &input;
            encryptOptions.outfile = outFile;
            /* decode an enveloped-data message to get the bulkkey (create
             * a new one if neccessary)
             */
            if (!encryptOptions.envFile) {
                encryptOptions.envFile = PR_Open(envFileName,
                                                 PR_WRONLY | PR_CREATE_FILE, 00660);
                if (!encryptOptions.envFile) {
                    fprintf(stderr, "%s: failed to create file %s.\n", progName,
                            envFileName);
                    exit(1);
                }
            } else {
                SECU_FileToItem(&envmsg, encryptOptions.envFile);
                decodeOptions.options = &options;
                encryptOptions.envmsg = decode(NULL, &envmsg, &decodeOptions);
                if (encryptOptions.envmsg == NULL) {
                    SECU_PrintError(progName, "problem decrypting env msg");
                    exitstatus = 1;
                    break;
                }
            }
            rv = get_enc_params(&encryptOptions);
            /* create the encrypted-data message */
            cmsg = encrypted_data(&encryptOptions);
            if (!cmsg) {
                SECU_PrintError(progName, "problem encrypting");
                exitstatus = 1;
            }
            if (encryptOptions.bulkkey) {
                PK11_FreeSymKey(encryptOptions.bulkkey);
                encryptOptions.bulkkey = NULL;
            }
            break;
        case ENVELOPE: /* -E */
            envelopeOptions.options = &options;
            cmsg = enveloped_data(&envelopeOptions);
            if (!cmsg) {
                SECU_PrintError(progName, "problem enveloping");
                exitstatus = 1;
            }
            break;
        case CERTSONLY: /* -O */
            certsonlyOptions.options = &options;
            cmsg = signed_data_certsonly(&certsonlyOptions);
            if (!cmsg) {
                SECU_PrintError(progName, "problem with certs-only");
                exitstatus = 1;
            }
            break;
        default:
            fprintf(stderr, "One of options -D, -S or -E must be set.\n");
            Usage();
            exitstatus = 1;
    }

    if (signOptions.nickname) {
        PORT_Free(signOptions.nickname);
    }

    if ((mode == SIGN || mode == ENVELOPE || mode == CERTSONLY) &&
        (!exitstatus)) {
        PLArenaPool *arena = PORT_NewArena(1024);
        NSSCMSEncoderContext *ecx;
        SECItem output = { 0, 0, 0 };

        if (!arena) {
            fprintf(stderr, "%s: out of memory.\n", progName);
            exit(1);
        }

        if (cms_verbose) {
            fprintf(stderr, "cmsg [%p]\n", cmsg);
            fprintf(stderr, "arena [%p]\n", arena);
            if (pwcb_arg && (PW_PLAINTEXT == ((secuPWData *)pwcb_arg)->source))
                fprintf(stderr, "password [%s]\n",
                        ((secuPWData *)pwcb_arg)->data);
            else
                fprintf(stderr, "password [NULL]\n");
        }
        ecx = NSS_CMSEncoder_Start(cmsg,
                                   NULL, NULL,     /* DER output callback  */
                                   &output, arena, /* destination storage  */
                                   pwcb, pwcb_arg, /* password callback    */
                                   NULL, NULL,     /* decrypt key callback */
                                   NULL, NULL);    /* detached digests    */
        if (!ecx) {
            fprintf(stderr, "%s: cannot create encoder context.\n", progName);
            exitstatus = 1;
            goto loser;
        }
        if (cms_verbose) {
            fprintf(stderr, "input len [%d]\n", input.len);
            {
                unsigned int j;
                for (j = 0; j < input.len; j++)
                    fprintf(stderr, "%2x%c", input.data[j], (j > 0 && j % 35 == 0) ? '\n' : ' ');
            }
        }
        if (input.len > 0) { /* skip if certs-only (or other zero content) */
            rv = NSS_CMSEncoder_Update(ecx, (char *)input.data, input.len);
            if (rv) {
                fprintf(stderr,
                        "%s: failed to add data to encoder.\n", progName);
                (void)NSS_CMSEncoder_Finish(ecx);
                PORT_FreeArena(arena, PR_FALSE);
                exitstatus = 1;
                goto loser;
            }
        }
        rv = NSS_CMSEncoder_Finish(ecx);
        if (rv) {
            SECU_PrintError(progName, "failed to encode data");
            PORT_FreeArena(arena, PR_FALSE);
            exitstatus = 1;
            goto loser;
        }

        if (cms_verbose) {
            fprintf(stderr, "encoding passed\n");
        }
        fwrite(output.data, output.len, 1, outFile);
        if (cms_verbose) {
            fprintf(stderr, "wrote to file\n");
        }
        PORT_FreeArena(arena, PR_FALSE);
    }
loser:
    if (cmsg)
        NSS_CMSMessage_Destroy(cmsg);
    if (outFile != stdout)
        fclose(outFile);

    if (inFile != PR_STDIN) {
        PR_Close(inFile);
    }
    if (envFileName) {
        PORT_Free(envFileName);
    }
    if (encryptOptions.envFile) {
        PR_Close(encryptOptions.envFile);
    }

    SECITEM_FreeItem(&decodeOptions.content, PR_FALSE);
    SECITEM_FreeItem(&envmsg, PR_FALSE);
    SECITEM_FreeItem(&input, PR_FALSE);
    if (NSS_Shutdown() != SECSuccess) {
        SECU_PrintError(progName, "NSS_Shutdown failed");
        exitstatus = 1;
    }
    PR_Cleanup();
    return exitstatus;
}
