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

/*
 * Test program for client-side OCSP.
 *
 * $Id: ocspclnt.c,v 1.4 2002/07/03 20:22:27 javi%netscape.com Exp $
 */

#include "secutil.h"
#include "nspr.h"
#include "plgetopt.h"
#include "nss.h"
#include "cert.h"
#include "ocsp.h"
#include "xconst.h"	/*
			 * XXX internal header file; needed to get at
			 * cert_DecodeAuthInfoAccessExtension -- would be
			 * nice to not need this, but that would require
			 * better/different APIs.
			 */

#ifndef NO_PP		/*
			 * Compile with this every once in a while to be
			 * sure that no dependencies on it get added
			 * outside of the pretty-printing routines.
			 */
#include "ocspti.h"	/* internals for pretty-printing routines *only* */
#endif	/* NO_PP */

#define DEFAULT_DB_DIR	"~/.netscape"


static void
synopsis (char *program_name)
{
    PRFileDesc *pr_stderr;

    pr_stderr = PR_STDERR;
    PR_fprintf (pr_stderr, "Usage:");
    PR_fprintf (pr_stderr,
		"\t%s -p [-d <dir>]\n",
		program_name);
    PR_fprintf (pr_stderr,
		"\t%s -P [-d <dir>]\n",
		program_name);
    PR_fprintf (pr_stderr,
		"\t%s -r <name> [-L] [-s <name>] [-d <dir>]\n",
		program_name);
    PR_fprintf (pr_stderr,
		"\t%s -R <name> [-l <location>] [-s <name>] [-d <dir>]\n",
		program_name);
    PR_fprintf (pr_stderr,
		"\t%s -S <name> [-l <location> -t <name>]\n",
		program_name);
    PR_fprintf (pr_stderr,
		"\t\t [-s <name>] [-w <time>] [-d <dir>]\n");
    PR_fprintf (pr_stderr,
		"\t%s -V <name> -u <usage> [-l <location> -t <name>]\n",
		program_name);
    PR_fprintf (pr_stderr,
		"\t\t [-s <name>] [-w <time>] [-d <dir>]\n");
}


static void
short_usage (char *program_name)
{
    PR_fprintf (PR_STDERR,
		"Type %s -H for more detailed descriptions\n",
		program_name);
    synopsis (program_name);
}


static void
long_usage (char *program_name)
{
    PRFileDesc *pr_stderr;

    pr_stderr = PR_STDERR;
    synopsis (program_name);
    PR_fprintf (pr_stderr, "\nCommands (must specify exactly one):\n");
    PR_fprintf (pr_stderr,
		"  %-13s Pretty-print a binary request read from stdin\n",
		"-p");
    PR_fprintf (pr_stderr,
		"  %-13s Pretty-print a binary response read from stdin\n",
		"-P");
    PR_fprintf (pr_stderr,
		"  %-13s Create a request for cert \"nickname\" on stdout\n",
		"-r nickname");
    PR_fprintf (pr_stderr,
		"  %-13s Get response for cert \"nickname\", dump to stdout\n",
		"-R nickname");
    PR_fprintf (pr_stderr,
		"  %-13s Get status for cert \"nickname\"\n",
		"-S nickname");
    PR_fprintf (pr_stderr,
		"  %-13s Fully verify cert \"nickname\", w/ status check\n",
		"-V nickname");
    PR_fprintf (pr_stderr, "Options:\n");
    PR_fprintf (pr_stderr,
		"  %-13s Add the service locator extension to the request\n",
		"-L");
    PR_fprintf (pr_stderr,
		"  %-13s Find security databases in \"dbdir\" (default %s)\n",
		"-d dbdir", DEFAULT_DB_DIR);
    PR_fprintf (pr_stderr,
		"  %-13s Use \"location\" as URL of responder\n",
		"-l location");
    PR_fprintf (pr_stderr,
		"  %-13s Trust cert \"nickname\" as response signer\n",
		"-t nickname");
    PR_fprintf (pr_stderr,
		"  %-13s Sign requests with cert \"nickname\"\n",
		"-s nickname");
    PR_fprintf (pr_stderr,
		"  %-13s Type of certificate usage for verification:\n",
		"-u usage");
    PR_fprintf (pr_stderr,
		"%-17s c   SSL Client\n", "");
    PR_fprintf (pr_stderr,
		"%-17s s   SSL Server\n", "");
    PR_fprintf (pr_stderr,
		"%-17s e   Email Recipient\n", "");
    PR_fprintf (pr_stderr,
		"%-17s E   Email Signer\n", "");
    PR_fprintf (pr_stderr,
		"%-17s S   Object Signer\n", "");
    PR_fprintf (pr_stderr,
		"%-17s C   CA\n", "");
    PR_fprintf (pr_stderr,
		"  %-13s Validity time (default current time), one of:\n",
		"-w time");
    PR_fprintf (pr_stderr,
		"%-17s %-25s (GMT)\n", "", "YYMMDDhhmm[ss]Z");
    PR_fprintf (pr_stderr,
		"%-17s %-25s (later than GMT)\n", "", "YYMMDDhhmm[ss]+hhmm");
    PR_fprintf (pr_stderr,
		"%-17s %-25s (earlier than GMT)\n", "", "YYMMDDhhmm[ss]-hhmm");
}


/*
 * XXX This is a generic function that would probably make a good
 * replacement for SECU_DER_Read (which is not at all specific to DER,
 * despite its name), but that requires fixing all of the tools...
 * Still, it should be done, whenenver I/somebody has the time.
 * (Also, consider whether this actually belongs in the security
 * library itself, not just in the command library.)
 *
 * This function takes an open file (a PRFileDesc *) and reads the
 * entire file into a SECItem.  (Obviously, the file is intended to
 * be small enough that such a thing is advisable.)  Both the SECItem
 * and the buffer it points to are allocated from the heap; the caller
 * is expected to free them.  ("SECITEM_FreeItem(item, PR_TRUE)")
 */
static SECItem *
read_file_into_item (PRFileDesc *in_file, SECItemType si_type)
{
    PRStatus	 prv;
    SECItem 	*item;
    PRFileInfo	 file_info;
    PRInt32	 bytes_read;

    prv = PR_GetOpenFileInfo (in_file, &file_info);
    if (prv != PR_SUCCESS)
	return NULL;

    if (file_info.size ==  0) {
	/* XXX Need a better error; just grabbed this one for expediency. */
	PORT_SetError (SEC_ERROR_INPUT_LEN);
	return NULL;
    }

    if (file_info.size > 0xffff) {	/* I think this is too big. */
	PORT_SetError (SEC_ERROR_NO_MEMORY);
	return NULL;
    }

    item = PORT_Alloc (sizeof (SECItem));
    if (item == NULL)
	return NULL;

    item->type = si_type;
    item->len = (unsigned int) file_info.size;
    item->data = PORT_Alloc ((size_t)item->len);
    if (item->data == NULL)
	goto loser;

    bytes_read = PR_Read (in_file, item->data, (PRInt32) item->len);
    if (bytes_read < 0) {
	/* Something went wrong; error is already set for us. */
	goto loser;
    } else if (bytes_read == 0) {
	/* Something went wrong; we read nothing.  But no system/nspr error. */
	/* XXX Need to set an error here. */
	goto loser;
    } else if (item->len != (unsigned int)bytes_read) {
	/* Something went wrong; we read less (or more!?) than we expected. */
	/* XXX Need to set an error here. */
	goto loser;
    }

    return item;

loser:
    SECITEM_FreeItem (item, PR_TRUE);
    return NULL;
}


/*
 * Create a DER-encoded OCSP request (for the certificate whose nickname
 * is "name") and dump it out.
 */
static SECStatus
create_request (FILE *out_file, CERTCertDBHandle *handle, const char *cert_name,
		PRBool add_service_locator, PRBool add_acceptable_responses)
{
    CERTCertificate *cert = NULL;
    CERTCertList *certs = NULL;
    CERTOCSPRequest *request = NULL;
    int64 now = PR_Now();
    SECItem *encoding = NULL;
    SECStatus rv = SECFailure;

    if (handle == NULL || cert_name == NULL)
	goto loser;

    cert = CERT_FindCertByNicknameOrEmailAddr (handle, (char *) cert_name);
    if (cert == NULL)
	goto loser;

    /*
     * We need to create a list of one.
     */
    certs = CERT_NewCertList();
    if (certs == NULL)
	goto loser;

    if (CERT_AddCertToListTail (certs, cert) != SECSuccess)
	goto loser;

    /*
     * Now that cert is included in the list, we need to be careful
     * that we do not try to destroy it twice.  This will prevent that.
     */
    cert = NULL;

    request = CERT_CreateOCSPRequest (certs, now, add_service_locator, NULL);
    if (request == NULL)
	goto loser;

    if (add_acceptable_responses) {
	rv = CERT_AddOCSPAcceptableResponses(request,
					     SEC_OID_PKIX_OCSP_BASIC_RESPONSE);
	if (rv != SECSuccess)
	    goto loser;
    }

    encoding = CERT_EncodeOCSPRequest (NULL, request, NULL);
    if (encoding == NULL)
	goto loser;

    if (fwrite (encoding->data, encoding->len, 1, out_file) != 1)
	goto loser;

    rv = SECSuccess;

loser:
    if (encoding != NULL)
	SECITEM_FreeItem(encoding, PR_TRUE);
    if (request != NULL)
	CERT_DestroyOCSPRequest(request);
    if (certs != NULL)
	CERT_DestroyCertList (certs);
    if (cert != NULL)
	CERT_DestroyCertificate (cert);

    return rv;
}


/*
 * Create a DER-encoded OCSP request (for the certificate whose nickname is
 * "cert_name"), then get and dump a corresponding response.  The responder
 * location is either specified explicitly (as "responder_url") or found
 * via the AuthorityInfoAccess URL in the cert.
 */
static SECStatus
dump_response (FILE *out_file, CERTCertDBHandle *handle, const char *cert_name,
	       const char *responder_url)
{
    CERTCertificate *cert = NULL;
    CERTCertList *certs = NULL;
    char *loc = NULL;
    int64 now = PR_Now();
    SECItem *response = NULL;
    SECStatus rv = SECFailure;
    PRBool includeServiceLocator;

    if (handle == NULL || cert_name == NULL)
	goto loser;

    cert = CERT_FindCertByNicknameOrEmailAddr (handle, (char *) cert_name);
    if (cert == NULL)
	goto loser;

    if (responder_url != NULL) {
	loc = (char *) responder_url;
	includeServiceLocator = PR_TRUE;
    } else {
	loc = CERT_GetOCSPAuthorityInfoAccessLocation (cert);
	if (loc == NULL)
	    goto loser;
	includeServiceLocator = PR_FALSE;
    }

    /*
     * We need to create a list of one.
     */
    certs = CERT_NewCertList();
    if (certs == NULL)
	goto loser;

    if (CERT_AddCertToListTail (certs, cert) != SECSuccess)
	goto loser;

    /*
     * Now that cert is included in the list, we need to be careful
     * that we do not try to destroy it twice.  This will prevent that.
     */
    cert = NULL;

    response = CERT_GetEncodedOCSPResponse (NULL, certs, loc, now,
					    includeServiceLocator,
					    NULL, NULL, NULL);
    if (response == NULL)
	goto loser;

    if (fwrite (response->data, response->len, 1, out_file) != 1)
	goto loser;

    rv = SECSuccess;

loser:
    if (response != NULL)
	SECITEM_FreeItem (response, PR_TRUE);
    if (certs != NULL)
	CERT_DestroyCertList (certs);
    if (loc != NULL && loc != responder_url)
	PORT_Free (loc);
    if (cert != NULL)
	CERT_DestroyCertificate (cert);

    return rv;
}


/*
 * Get the status for the specified certificate (whose nickname is "cert_name").
 * Directly use the OCSP function rather than doing a full verification.
 */
static SECStatus
get_cert_status (FILE *out_file, CERTCertDBHandle *handle,
		 const char *cert_name, int64 verify_time)
{
    CERTCertificate *cert = NULL;
    SECStatus rv = SECFailure;

    if (handle == NULL || cert_name == NULL)
	goto loser;

    cert = CERT_FindCertByNicknameOrEmailAddr (handle, (char *) cert_name);
    if (cert == NULL)
	goto loser;

    rv = CERT_CheckOCSPStatus (handle, cert, verify_time, NULL);

    fprintf (out_file, "Check of certificate \"%s\" ", cert_name); 
    if (rv == SECSuccess) {
	fprintf (out_file, "succeeded.\n"); 
    } else {
	const char *error_string = SECU_Strerror(PORT_GetError());
	fprintf (out_file, "failed.  Reason:\n");
	if (error_string != NULL && PORT_Strlen(error_string) > 0)
	    fprintf (out_file, "%s\n", error_string);
	else
	    fprintf (out_file, "Unknown\n");
    }

    rv = SECSuccess;

loser:
    if (cert != NULL)
	CERT_DestroyCertificate (cert);

    return rv;
}


/*
 * Verify the specified certificate (whose nickname is "cert_name").
 * OCSP is already turned on, so we just need to call the standard
 * certificate verification API and let it do all the work.
 */
static SECStatus
verify_cert (FILE *out_file, CERTCertDBHandle *handle, const char *cert_name,
	     SECCertUsage cert_usage, int64 verify_time)
{
    CERTCertificate *cert = NULL;
    SECStatus rv = SECFailure;

    if (handle == NULL || cert_name == NULL)
	goto loser;

    cert = CERT_FindCertByNicknameOrEmailAddr (handle, (char *) cert_name);
    if (cert == NULL)
	goto loser;

    rv = CERT_VerifyCert (handle, cert, PR_TRUE, cert_usage, verify_time,
			  NULL, NULL);

    fprintf (out_file, "Verification of certificate \"%s\" ", cert_name); 
    if (rv == SECSuccess) {
	fprintf (out_file, "succeeded.\n"); 
    } else {
	const char *error_string = SECU_Strerror(PORT_GetError());
	fprintf (out_file, "failed.  Reason:\n");
	if (error_string != NULL && PORT_Strlen(error_string) > 0)
	    fprintf (out_file, "%s\n", error_string);
	else
	    fprintf (out_file, "Unknown\n");
    }

    rv = SECSuccess;

loser:
    if (cert != NULL)
	CERT_DestroyCertificate (cert);

    return rv;
}


#ifdef	NO_PP

static SECStatus
print_request (FILE *out_file, SECItem *data)
{
    fprintf (out_file, "Cannot pretty-print request compiled with NO_PP.\n");
    return SECSuccess;
}

static SECStatus
print_response (FILE *out_file, SECItem *data, CERTCertDBHandle *handle)
{
    fprintf (out_file, "Cannot pretty-print response compiled with NO_PP.\n");
    return SECSuccess;
}

#else /* NO_PP */

static void
print_ocsp_version (FILE *out_file, SECItem *version, int level)
{
    if (version->len > 0) {
	SECU_PrintInteger (out_file, version, "Version", level);
    } else {
	SECU_Indent (out_file, level);
	fprintf (out_file, "Version: DEFAULT\n");
    }
}


static void
print_ocsp_cert_id (FILE *out_file, CERTOCSPCertID *cert_id, int level)
{
    SECU_Indent (out_file, level);
    fprintf (out_file, "Cert ID:\n");
    level++;

    SECU_PrintAlgorithmID (out_file, &(cert_id->hashAlgorithm),
			   "Hash Algorithm", level);
    SECU_PrintAsHex (out_file, &(cert_id->issuerNameHash),
		     "Issuer Name Hash", level);
    SECU_PrintAsHex (out_file, &(cert_id->issuerKeyHash),
		     "Issuer Key Hash", level);
    SECU_PrintInteger (out_file, &(cert_id->serialNumber),
		       "Serial Number", level);
    /* XXX lookup the cert; if found, print something nice (nickname?) */
}


static void
print_raw_certificates (FILE *out_file, SECItem **raw_certs, int level)
{
    SECItem *raw_cert;
    int i = 0;
    char cert_label[50];

    SECU_Indent (out_file, level);

    if (raw_certs == NULL) {
	fprintf (out_file, "No Certificates.\n");
	return;
    }

    fprintf (out_file, "Certificate List:\n");
    while ((raw_cert = raw_certs[i++]) != NULL) {
	sprintf (cert_label, "Certificate (%d)", i);
	(void) SECU_PrintSignedData (out_file, raw_cert, cert_label, level + 1,
				     SECU_PrintCertificate);
    }
}


static void
print_ocsp_extensions (FILE *out_file, CERTCertExtension **extensions,
		       char *msg, int level)
{
    if (extensions) {
	SECU_PrintExtensions (out_file, extensions, msg, level);
    } else {
	SECU_Indent (out_file, level);
	fprintf (out_file, "No %s\n", msg);
    }
}


static void
print_single_request (FILE *out_file, ocspSingleRequest *single, int level)
{
    print_ocsp_cert_id (out_file, single->reqCert, level);
    print_ocsp_extensions (out_file, single->singleRequestExtensions,
			   "Single Request Extensions", level);
}


/*
 * Decode the DER/BER-encoded item "data" as an OCSP request
 * and pretty-print the subfields.
 */
static SECStatus
print_request (FILE *out_file, SECItem *data)
{
    CERTOCSPRequest *request;
    ocspTBSRequest *tbsRequest;
    int level = 0;

    PORT_Assert (out_file != NULL);
    PORT_Assert (data != NULL);
    if (out_file == NULL || data == NULL) {
	PORT_SetError (SEC_ERROR_INVALID_ARGS);
	return SECFailure;
    }

    request = CERT_DecodeOCSPRequest (data);
    if (request == NULL || request->tbsRequest == NULL)
	return SECFailure;

    tbsRequest = request->tbsRequest;

    fprintf (out_file, "TBS Request:\n");
    level++;

    print_ocsp_version (out_file, &(tbsRequest->version), level);

    /*
     * XXX Probably should be an interface to get the signer name
     * without looking inside the tbsRequest at all.
     */
    if (tbsRequest->requestorName != NULL) {
	SECU_Indent (out_file, level);
	fprintf (out_file, "XXX print the requestorName\n");
    } else {
	SECU_Indent (out_file, level);
	fprintf (out_file, "No Requestor Name.\n");
    }

    if (tbsRequest->requestList != NULL) {
	int i;

	for (i = 0; tbsRequest->requestList[i] != NULL; i++) {
	    SECU_Indent (out_file, level);
	    fprintf (out_file, "Request %d:\n", i);
	    print_single_request (out_file, tbsRequest->requestList[i],
				  level + 1);
	}
    } else {
	fprintf (out_file, "Request list is empty.\n");
    }

    print_ocsp_extensions (out_file, tbsRequest->requestExtensions,
			   "Request Extensions", level);

    if (request->optionalSignature != NULL) {
	ocspSignature *whole_sig;
	SECItem rawsig;

	fprintf (out_file, "Signature:\n");

	whole_sig = request->optionalSignature;
	SECU_PrintAlgorithmID (out_file, &(whole_sig->signatureAlgorithm),
			       "Signature Algorithm", level);

	rawsig = whole_sig->signature;
	DER_ConvertBitString (&rawsig);
	SECU_PrintAsHex (out_file, &rawsig, "Signature", level);

	print_raw_certificates (out_file, whole_sig->derCerts, level);

	fprintf (out_file, "XXX verify the sig and print result\n");
    } else {
	fprintf (out_file, "No Signature\n");
    }

    CERT_DestroyOCSPRequest (request);
    return SECSuccess;
}


static void
print_revoked_info (FILE *out_file, ocspRevokedInfo *revoked_info, int level)
{
    SECU_PrintGeneralizedTime (out_file, &(revoked_info->revocationTime),
			       "Revocation Time", level);

    if (revoked_info->revocationReason != NULL) {
	SECU_PrintAsHex (out_file, revoked_info->revocationReason,
			 "Revocation Reason", level);
    } else {
	SECU_Indent (out_file, level);
	fprintf (out_file, "No Revocation Reason.\n");
    }
}


static void
print_cert_status (FILE *out_file, ocspCertStatus *status, int level)
{
    SECU_Indent (out_file, level);
    fprintf (out_file, "Status: ");

    switch (status->certStatusType) {
      case ocspCertStatus_good:
	fprintf (out_file, "Cert is good.\n");
	break;
      case ocspCertStatus_revoked:
	fprintf (out_file, "Cert has been revoked.\n");
	print_revoked_info (out_file, status->certStatusInfo.revokedInfo,
			    level + 1);
	break;
      case ocspCertStatus_unknown:
	fprintf (out_file, "Cert is unknown to responder.\n");
	break;
      default:
	fprintf (out_file, "Unrecognized status.\n");
	break;
    }
}


static void
print_single_response (FILE *out_file, CERTOCSPSingleResponse *single,
		       int level)
{
    print_ocsp_cert_id (out_file, single->certID, level);

    print_cert_status (out_file, single->certStatus, level);

    SECU_PrintGeneralizedTime (out_file, &(single->thisUpdate),
			       "This Update", level);

    if (single->nextUpdate != NULL) {
	SECU_PrintGeneralizedTime (out_file, single->nextUpdate,
				   "Next Update", level);
    } else {
	SECU_Indent (out_file, level);
	fprintf (out_file, "No Next Update\n");
    }

    print_ocsp_extensions (out_file, single->singleExtensions,
			   "Single Response Extensions", level);
}


static void
print_responder_id (FILE *out_file, ocspResponderID *responderID, int level)
{
    SECU_Indent (out_file, level);
    fprintf (out_file, "Responder ID ");

    switch (responderID->responderIDType) {
      case ocspResponderID_byName:
	fprintf (out_file, "(byName):\n");
	SECU_PrintName (out_file, &(responderID->responderIDValue.name),
			"Name", level + 1);
	break;
      case ocspResponderID_byKey:
	fprintf (out_file, "(byKey):\n");
	SECU_PrintAsHex (out_file, &(responderID->responderIDValue.keyHash),
			 "Key Hash", level + 1);
	break;
      default:
	fprintf (out_file, "Unrecognized Responder ID Type\n");
	break;
    }
}


static void
print_response_data (FILE *out_file, ocspResponseData *responseData, int level)
{
    SECU_Indent (out_file, level);
    fprintf (out_file, "Response Data:\n");
    level++;

    print_ocsp_version (out_file, &(responseData->version), level);

    print_responder_id (out_file, responseData->responderID, level);

    SECU_PrintGeneralizedTime (out_file, &(responseData->producedAt),
			       "Produced At", level);

    if (responseData->responses != NULL) {
	int i;

	for (i = 0; responseData->responses[i] != NULL; i++) {
	    SECU_Indent (out_file, level);
	    fprintf (out_file, "Response %d:\n", i);
	    print_single_response (out_file, responseData->responses[i],
				   level + 1);
	}
    } else {
	fprintf (out_file, "Response list is empty.\n");
    }

    print_ocsp_extensions (out_file, responseData->responseExtensions,
			   "Response Extensions", level);
}


static void
print_basic_response (FILE *out_file, ocspBasicOCSPResponse *basic, int level)
{
    SECItem rawsig;

    SECU_Indent (out_file, level);
    fprintf (out_file, "Basic OCSP Response:\n");
    level++;

    print_response_data (out_file, basic->tbsResponseData, level);

    SECU_PrintAlgorithmID (out_file,
			   &(basic->responseSignature.signatureAlgorithm),
			   "Signature Algorithm", level);

    rawsig = basic->responseSignature.signature;
    DER_ConvertBitString (&rawsig);
    SECU_PrintAsHex (out_file, &rawsig, "Signature", level);

    print_raw_certificates (out_file, basic->responseSignature.derCerts, level);
}


/*
 * Note this must match (exactly) the enumeration ocspResponseStatus.
 */
static char *responseStatusNames[] = {
    "successful (Response has valid confirmations)",
    "malformedRequest (Illegal confirmation request)",
    "internalError (Internal error in issuer)",
    "tryLater (Try again later)",
    "unused ((4) is not used)",
    "sigRequired (Must sign the request)",
    "unauthorized (Request unauthorized)",
    "other (Status value out of defined range)"
};

/*
 * Decode the DER/BER-encoded item "data" as an OCSP response
 * and pretty-print the subfields.
 */
static SECStatus
print_response (FILE *out_file, SECItem *data, CERTCertDBHandle *handle)
{
    CERTOCSPResponse *response;
    int level = 0;

    PORT_Assert (out_file != NULL);
    PORT_Assert (data != NULL);
    if (out_file == NULL || data == NULL) {
	PORT_SetError (SEC_ERROR_INVALID_ARGS);
	return SECFailure;
    }

    response = CERT_DecodeOCSPResponse (data);
    if (response == NULL)
	return SECFailure;

    PORT_Assert (response->statusValue <= ocspResponse_other);
    fprintf (out_file, "Response Status: %s\n",
	     responseStatusNames[response->statusValue]);

    if (response->statusValue == ocspResponse_successful) {
	ocspResponseBytes *responseBytes = response->responseBytes;
	SECStatus sigStatus;
	CERTCertificate *signerCert = NULL;

	PORT_Assert (responseBytes != NULL);

	level++;
	fprintf (out_file, "Response Bytes:\n");
	SECU_PrintObjectID (out_file, &(responseBytes->responseType),
			    "Response Type", level);
	switch (response->responseBytes->responseTypeTag) {
	  case SEC_OID_PKIX_OCSP_BASIC_RESPONSE:
	    print_basic_response (out_file,
				  responseBytes->decodedResponse.basic,
				  level);
	    break;
	  default:
	    SECU_Indent (out_file, level);
	    fprintf (out_file, "Unknown response syntax\n");
	    break;
	}

	sigStatus = CERT_VerifyOCSPResponseSignature (response, handle,
						      NULL, &signerCert, NULL);
	SECU_Indent (out_file, level);
	fprintf (out_file, "Signature verification ");
	if (sigStatus != SECSuccess) {
	    fprintf (out_file, "failed: %s\n", SECU_Strerror (PORT_GetError()));
	} else {
	    fprintf (out_file, "succeeded.\n");
	    if (signerCert != NULL) {
		SECU_PrintName (out_file, &signerCert->subject, "Signer",
				level);
		CERT_DestroyCertificate (signerCert);
	    } else {
		SECU_Indent (out_file, level);
		fprintf (out_file, "No signer cert returned?\n");
	    }
	}
    } else {
	SECU_Indent (out_file, level);
	fprintf (out_file, "Unsuccessful response, no more information.\n");
    }

    CERT_DestroyOCSPResponse (response);
    return SECSuccess;
}

#endif	/* NO_PP */


static SECStatus
cert_usage_from_char (const char *cert_usage_str, SECCertUsage *cert_usage)
{
    PORT_Assert (cert_usage_str != NULL);
    PORT_Assert (cert_usage != NULL);

    if (PORT_Strlen (cert_usage_str) != 1)
	return SECFailure;

    switch (*cert_usage_str) {
      case 'c':
	*cert_usage = certUsageSSLClient;
	break;
      case 's':
	*cert_usage = certUsageSSLServer;
	break;
      case 'e':
	*cert_usage = certUsageEmailRecipient;
	break;
      case 'E':
	*cert_usage = certUsageEmailSigner;
	break;
      case 'S':
	*cert_usage = certUsageObjectSigner;
	break;
      case 'C':
	*cert_usage = certUsageVerifyCA;
	break;
      default:
	return SECFailure;
    }

    return SECSuccess;
}


int
main (int argc, char **argv)
{
    int		 retval;
    char	*program_name;
    PRFileDesc	*in_file;
    FILE	*out_file;	/* not PRFileDesc until SECU accepts it */
    int		 crequest, dresponse;
    int		 prequest, presponse;
    int		 ccert, vcert;
    const char	*db_dir, *date_str, *cert_usage_str, *name;
    const char	*responder_name, *responder_url, *signer_name;
    PRBool	 add_acceptable_responses, add_service_locator;
    SECItem	*data = NULL;
    PLOptState	*optstate;
    SECStatus	 rv;
    CERTCertDBHandle *handle = NULL;
    SECCertUsage cert_usage;
    int64	 verify_time;

    retval = -1;		/* what we return/exit with on error */

    program_name = PL_strrchr(argv[0], '/');
    program_name = program_name ? (program_name + 1) : argv[0];

    in_file = PR_STDIN;
    out_file = stdout;

    crequest = 0;
    dresponse = 0;
    prequest = 0;
    presponse = 0;
    ccert = 0;
    vcert = 0;

    db_dir = NULL;
    date_str = NULL;
    cert_usage_str = NULL;
    name = NULL;
    responder_name = NULL;
    responder_url = NULL;
    signer_name = NULL;

    add_acceptable_responses = PR_FALSE;
    add_service_locator = PR_FALSE;

    optstate = PL_CreateOptState (argc, argv, "AHLPR:S:V:d:l:pr:s:t:u:w:");
    if (optstate == NULL) {
	SECU_PrintError (program_name, "PL_CreateOptState failed");
	return retval;
    }

    while (PL_GetNextOpt (optstate) == PL_OPT_OK) {
	switch (optstate->option) {
	  case '?':
	    short_usage (program_name);
	    return retval;

	  case 'A':
	    add_acceptable_responses = PR_TRUE;
	    break;

	  case 'H':
	    long_usage (program_name);
	    return retval;

	  case 'L':
	    add_service_locator = PR_TRUE;
	    break;

	  case 'P':
	    presponse = 1;
	    break;

	  case 'R':
	    dresponse = 1;
	    name = optstate->value;
	    break;

	  case 'S':
	    ccert = 1;
	    name = optstate->value;
	    break;

	  case 'V':
	    vcert = 1;
	    name = optstate->value;
	    break;

	  case 'd':
	    db_dir = optstate->value;
	    break;

	  case 'l':
	    responder_url = optstate->value;
	    break;

	  case 'p':
	    prequest = 1;
	    break;

	  case 'r':
	    crequest = 1;
	    name = optstate->value;
	    break;

	  case 's':
	    signer_name = optstate->value;
	    break;

	  case 't':
	    responder_name = optstate->value;
	    break;

	  case 'u':
	    cert_usage_str = optstate->value;
	    break;

	  case 'w':
	    date_str = optstate->value;
	    break;
	}
    }

    if ((crequest + dresponse + prequest + presponse + ccert + vcert) != 1) {
	PR_fprintf (PR_STDERR, "%s: must specify exactly one command\n\n",
		    program_name);
	short_usage (program_name);
	return retval;
    }

    if (vcert) {
	if (cert_usage_str == NULL) {
	    PR_fprintf (PR_STDERR, "%s: verification requires cert usage\n\n",
			program_name);
	    short_usage (program_name);
	    return retval;
	}

	rv = cert_usage_from_char (cert_usage_str, &cert_usage);
	if (rv != SECSuccess) {
	    PR_fprintf (PR_STDERR, "%s: invalid cert usage (\"%s\")\n\n",
			program_name, cert_usage_str);
	    long_usage (program_name);
	    return retval;
	}
    }

    if (ccert + vcert) {
	if (responder_url != NULL || responder_name != NULL) {
	    /*
	     * To do a full status check, both the URL and the cert name
	     * of the responder must be specified if either one is.
	     */
	    if (responder_url == NULL || responder_name == NULL) {
		if (responder_url == NULL)
		    PR_fprintf (PR_STDERR,
				"%s: must also specify responder location\n\n",
				program_name);
		else
		    PR_fprintf (PR_STDERR,
				"%s: must also specify responder name\n\n",
				program_name);
		short_usage (program_name);
		return retval;
	    }
	}

	if (date_str != NULL) {
	    rv = DER_AsciiToTime (&verify_time, (char *) date_str);
	    if (rv != SECSuccess) {
		SECU_PrintError (program_name, "error converting time string");
		PR_fprintf (PR_STDERR, "\n");
		long_usage (program_name);
		return retval;
	    }
	} else {
	    verify_time = PR_Now();
	}
    }

    retval = -2;		/* errors change from usage to runtime */

    /*
     * Initialize the NSPR and Security libraries.
     */
    PR_Init (PR_SYSTEM_THREAD, PR_PRIORITY_NORMAL, 1);
    db_dir = SECU_ConfigDirectory (db_dir);
    rv = NSS_Init (db_dir);
    if (rv != SECSuccess) {
	SECU_PrintError (program_name, "NSS_Init failed");
	goto prdone;
    }

    if (prequest + presponse) {
	data = read_file_into_item (in_file, siBuffer);
	if (data == NULL) {
	    SECU_PrintError (program_name, "problem reading input");
	    goto nssdone;
	}
    }

    if (crequest + dresponse + presponse + ccert + vcert) {
	handle = CERT_GetDefaultCertDB();
	if (handle == NULL) {
	    SECU_PrintError (program_name, "problem getting certdb handle");
	    goto nssdone;
	}

	/*
	 * It would be fine to do the enable for all of these commands,
	 * but this way we check that everything but an overall verify
	 * can be done without it.  That is, that the individual pieces
	 * work on their own.
	 */
	if (vcert) {
	    rv = CERT_EnableOCSPChecking (handle);
	    if (rv != SECSuccess) {
		SECU_PrintError (program_name, "error enabling OCSP checking");
		goto nssdone;
	    }
	}

	if ((ccert + vcert) && (responder_name != NULL)) {
	    rv = CERT_SetOCSPDefaultResponder (handle, responder_url,
					       responder_name);
	    if (rv != SECSuccess) {
		SECU_PrintError (program_name,
				 "error setting default responder");
		goto nssdone;
	    }

	    rv = CERT_EnableOCSPDefaultResponder (handle);
	    if (rv != SECSuccess) {
		SECU_PrintError (program_name,
				 "error enabling default responder");
		goto nssdone;
	    }
	}
    }

#define NOTYET(opt)							\
	{								\
	    PR_fprintf (PR_STDERR, "%s not yet working\n", opt);	\
	    exit (-1);							\
	}

    if (crequest) {
	if (signer_name != NULL) {
	    NOTYET("-s");
	}
	rv = create_request (out_file, handle, name, add_service_locator,
			     add_acceptable_responses);
    } else if (dresponse) {
	if (signer_name != NULL) {
	    NOTYET("-s");
	}
	rv = dump_response (out_file, handle, name, responder_url);
    } else if (prequest) {
	rv = print_request (out_file, data);
    } else if (presponse) {
	rv = print_response (out_file, data, handle);
    } else if (ccert) {
	if (signer_name != NULL) {
	    NOTYET("-s");
	}
	rv = get_cert_status (out_file, handle, name, verify_time);
    } else if (vcert) {
	if (signer_name != NULL) {
	    NOTYET("-s");
	}
	rv = verify_cert (out_file, handle, name, cert_usage, verify_time);
    }

    if (rv != SECSuccess)
	SECU_PrintError (program_name, "error performing requested operation");
    else
	retval = 0;

nssdone:
    if (data != NULL) {
	SECITEM_FreeItem (data, PR_TRUE);
    }

    if (handle != NULL) {
	(void) CERT_DisableOCSPChecking (handle);
    }

    NSS_Shutdown ();

prdone:
    PR_Cleanup ();
    return retval;
}
