/*-
 ***********************************************************************
 *
 * $Id: webjob-dsvtool.c,v 1.18 2012/05/01 06:33:27 mavrik Exp $
 *
 ***********************************************************************
 *
 * Copyright 2006-2012 The WebJob Project, All Rights Reserved.
 *
 ***********************************************************************
 */
#include "all-includes.h"

/*-
 ***********************************************************************
 *
 * Global Variables.
 *
 ***********************************************************************
 */
static DSV_PROPERTIES *gpsProperties;

static char gacChar95CharacterSet[] = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~";
static char gacChar92CharacterSet[] = "!#$%&()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_abcdefghijklmnopqrstuvwxyz{|}~";
static char gacAlphaCharacterSet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
static char gacAlphaNumericCharacterSet[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
static char gacLowerCharacterSet[] = "abcdefghijklmnopqrstuvwxyz";
static char gacUpperCharacterSet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
static char gacBase16CharacterSet[] = "0123456789abcdef";
static char gacBase10CharacterSet[] = "0123456789";
static char gacBase8CharacterSet[] = "01234567";
static char gacBase2CharacterSet[] = "01";

/*-
 ***********************************************************************
 *
 * DsvMain
 *
 ***********************************************************************
 */
int
main(int iArgumentCount, char *ppcArgumentVector[])
{
  const char          acRoutine[] = "Main()";
  char                acLocalError[MESSAGE_SIZE] = "";
  char               *pcPassword = NULL;
  int                 iError = 0;
  DSA                *psDsa = NULL;
  DSV_PROPERTIES     *psProperties = NULL;
  int                 iTries = 0;
  int                 iIndex = 0;
  int                 iMaxTries = 0;
  RSA                *psRsa = NULL;

  /*-
   *********************************************************************
   *
   * Punch in and go to work.
   *
   *********************************************************************
   */
  iError = DsvBootStrap(acLocalError);
  if (iError != ER_OK)
  {
    fprintf(stderr, "%s: %s: %s\n", PROGRAM_NAME, acRoutine, acLocalError);
    DsvShutdown(XER_BootStrap);
  }
  psProperties = (DSV_PROPERTIES *) DsvGetPropertiesReference();

  /*-
   *********************************************************************
   *
   * Process command line arguments.
   *
   *********************************************************************
   */
  iError = DsvProcessArguments(iArgumentCount, ppcArgumentVector, psProperties, acLocalError);
  if (iError != ER_OK)
  {
    fprintf(stderr, "%s: %s: %s\n", PROGRAM_NAME, acRoutine, acLocalError);
    DsvShutdown(XER_ProcessArguments);
  }

  /*-
   *********************************************************************
   *
   * Answer the mail.
   *
   *********************************************************************
   */
  switch (psProperties->iRunMode)
  {
  case DSV_SIGN:
    iError = DsvSign(psProperties, acLocalError);
    if (iError != ER_OK)
    {
      fprintf(stderr, "%s: %s: %s\n", PROGRAM_NAME, acRoutine, acLocalError);
      DsvShutdown(XER_PayloadSigningError);
    }
    break;
  case DSV_KEYGEN:
    if (psProperties->iKeyBits % 256 || psProperties->iKeyBits < 1024 || psProperties->iKeyBits > 4096)
    {
      fprintf(stderr, "%s: %s: The number of key bits must be a multiple of 256 and in the range [1024-4096].\n", PROGRAM_NAME, acRoutine);
      DsvShutdown(XER_KeyGenerationError);
    }
    if (psProperties->iKeyType == DSV_KEY_TYPE_RSA)
    {
      psRsa = RSA_generate_key(psProperties->iKeyBits, 65537, NULL, NULL);
      if (psRsa == NULL)
      {
        fprintf(stderr, "%s: %s: Unable to generate RSA key (%s).\n", PROGRAM_NAME, acRoutine, ERR_error_string(ERR_get_error(), NULL));
        DsvShutdown(XER_KeyGenerationError);
      }
      if (!PEM_write_RSAPrivateKey(stdout, psRsa, NULL, NULL, 0, NULL, NULL))
      {
        fprintf(stderr, "%s: %s: Unable to write private key.\n", PROGRAM_NAME, acRoutine);
        DsvShutdown(XER_KeyGenerationError);
      }
    }
    else
    {
      psDsa = DSA_generate_parameters(psProperties->iKeyBits, NULL, 0, NULL, NULL, NULL, NULL);
      if (psDsa == NULL)
      {
        fprintf(stderr, "%s: %s: Unable to generate DSA parameters (%s).\n", PROGRAM_NAME, acRoutine, ERR_error_string(ERR_get_error(), NULL));
        DsvShutdown(XER_KeyGenerationError);
      }
      if (!DSA_generate_key(psDsa))
      {
        fprintf(stderr, "%s: %s: Unable to generate DSA key (%s).\n", PROGRAM_NAME, acRoutine, ERR_error_string(ERR_get_error(), NULL));
        DsvShutdown(XER_KeyGenerationError);
      }
      if (!PEM_write_DSAPrivateKey(stdout, psDsa, NULL, NULL, 0, NULL, NULL))
      {
        fprintf(stderr, "%s: %s: Unable to write private key.\n", PROGRAM_NAME, acRoutine);
        DsvShutdown(XER_KeyGenerationError);
      }
    }
    break;
  case DSV_PASSGEN:
    pcPassword = (char *) calloc(psProperties->iPasswordLength + 1, 1);
    if (pcPassword == NULL)
    {
      fprintf(stderr, "%s: %s: Unable to allocate buffer (%s).\n", PROGRAM_NAME, acRoutine, strerror(errno));
      DsvShutdown(XER_PasswordGenerationError);
    }
    if
    (
         psProperties->iCharacterSetLength < DSV_MIN_CHARACTER_SET_LENGTH
      || psProperties->iCharacterSetLength > DSV_MAX_CHARACTER_SET_LENGTH
    )
    {
      fprintf(stderr, "%s: %s: Character set must be in the range [%d-%d].\n", PROGRAM_NAME, acRoutine, DSV_MIN_CHARACTER_SET_LENGTH, DSV_MAX_CHARACTER_SET_LENGTH);
      DsvShutdown(XER_PasswordGenerationError);
    }
    else if /* Handle 1-, 2-, and 4-bit character set lengths as a special case. */
    (
         psProperties->iCharacterSetLength == 2
      || psProperties->iCharacterSetLength == 4
      || psProperties->iCharacterSetLength == 16
    )
    {
      int iMask = psProperties->iCharacterSetLength - 1;
      int iDelta = (psProperties->iCharacterSetLength == 16) ? 4 : (psProperties->iCharacterSetLength >> 1);
      int iShift = 0;
      unsigned char auc[1] = { 0 };

      iIndex = 0;
      while (iIndex < psProperties->iPasswordLength)
      {
        if (iShift == 0)
        {
          if (!RAND_bytes(auc, 1))
          {
            fprintf(stderr, "%s: %s: Unable to generate random number (%s).\n", PROGRAM_NAME, acRoutine, ERR_error_string(ERR_get_error(), NULL));
            DsvShutdown(XER_PasswordGenerationError);
          }
        }
        pcPassword[iIndex++] = psProperties->pcCharacterSet[((auc[0] >> iShift) & iMask)];
        iShift += iDelta;
        if (iShift == 8)
        {
          iShift = 0;
        }
      }
    }
    else
    {
      int iRemainder = 65536 % psProperties->iCharacterSetLength;
      int iRandValue = 0;
      unsigned char auc[2] = { 0 };

      iMaxTries = (psProperties->iPasswordLength * DSV_MAX_TRIES_MULTIPLIER) + DSV_MAX_TRIES_CONSTANT;
      iTries = 0;
      iIndex = 0;
      while (iIndex < psProperties->iPasswordLength)
      {
        if (!RAND_bytes(auc, 2))
        {
          fprintf(stderr, "%s: %s: Unable to generate random number (%s).\n", PROGRAM_NAME, acRoutine, ERR_error_string(ERR_get_error(), NULL));
          DsvShutdown(XER_PasswordGenerationError);
        }
        iRandValue = (auc[0] << 8) | auc[1];
        if (iRandValue >= iRemainder)
        {
          pcPassword[iIndex++] = psProperties->pcCharacterSet[iRandValue % psProperties->iCharacterSetLength];
        }
        else if (++iTries > iMaxTries)
        {
          fprintf(stderr, "%s: %s: Password generation aborted (maximum number of tries exceeded).\n", PROGRAM_NAME, acRoutine);
          DsvShutdown(XER_PasswordGenerationError);
        }
      }
    }
    pcPassword[iIndex] = 0;
    fprintf(stdout, "%s\n", pcPassword);
    break;
  case DSV_VERIFY:
    psProperties->iFileType = DSV_FILE_TYPE_CERT;
    iError = DsvVerify(psProperties, acLocalError);
    if (iError != ER_OK)
    {
      fprintf(stderr, "%s: %s: %s\n", PROGRAM_NAME, acRoutine, acLocalError);
      if (iError == ER_SignatureVerificationMixed)
      {
        DsvShutdown(XER_SignatureVerificationMixed);
      }
      else if (iError == ER_SignatureVerificationFailed)
      {
        DsvShutdown(XER_SignatureVerificationFailed);
      }
      else
      {
        DsvShutdown(XER_SignatureVerificationError);
      }
    }
    break;
  default:
    fprintf(stderr, "%s: %s: Invalid run mode (%d). That shouldn't happen!\n", PROGRAM_NAME, acRoutine, psProperties->iRunMode);
    DsvShutdown(XER_RunMode);
    break;
  }

  /*-
   *********************************************************************
   *
   * Shutdown and go home.
   *
   *********************************************************************
   */
  DsvShutdown(XER_OK);

  return XER_OK;
}


/*-
 ***********************************************************************
 *
 * DsvBootStrap
 *
 ***********************************************************************
 */
int
DsvBootStrap(char *pcError)
{
  const char          acRoutine[] = "DsvBootStrap()";
  char                acLocalError[MESSAGE_SIZE] = "";
  DSV_PROPERTIES     *psProperties = NULL;
  unsigned char       aucSeed[DSV_SEED_LENGTH];

  /*-
   *********************************************************************
   *
   * Initialize OpenSSL components.
   *
   * Note: OpenSSL_add_all_algorithms() may be required to prevent the
   *       following error:
   *
   *       error:0906B072:PEM routines:PEM_get_EVP_CIPHER_INFO:unsupported encryption
   *
   *       which occurrs when the program is run in sign mode.
   *
   *********************************************************************
   */
  ERR_load_crypto_strings();
  OpenSSL_add_all_algorithms();
  RAND_seed(DsvGenerateSeed(aucSeed, DSV_SEED_LENGTH), DSV_SEED_LENGTH);

  /*-
   *********************************************************************
   *
   * Allocate and initialize the properties structure.
   *
   *********************************************************************
   */
  psProperties = (DSV_PROPERTIES *) DsvNewProperties(acLocalError);
  if (psProperties == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    return ER;
  }
  DsvSetPropertiesReference(psProperties);

  return ER_OK;
}


/*-
 ***********************************************************************
 *
 * DsvFreeProperties
 *
 ***********************************************************************
 */
void
DsvFreeProperties(DSV_PROPERTIES *psProperties)
{
  DSV_CERT_NODE      *psTemp = NULL;
  DSV_CERT_NODE      *psFree = NULL;

  if (psProperties != NULL)
  {
    psTemp = psProperties->psCertList;
    while (psTemp != NULL)
    {
      psFree = psTemp;
      psTemp = psTemp->psNext;
      DsvFreeCertNode(psFree);
    }
    free(psProperties);
  }
}


/*-
 ***********************************************************************
 *
 * DsvGetPropertiesReference
 *
 ***********************************************************************
 */
DSV_PROPERTIES *
DsvGetPropertiesReference(void)
{
  return gpsProperties;
}


/*-
 ***********************************************************************
 *
 * DsvNewProperties
 *
 ***********************************************************************
 */
DSV_PROPERTIES *
DsvNewProperties(char *pcError)
{
  const char          acRoutine[] = "DsvNewProperties()";
  DSV_PROPERTIES     *psProperties = NULL;

  /*-
   *********************************************************************
   *
   * Allocate and clear memory for the structure.
   *
   *********************************************************************
   */
  psProperties = (DSV_PROPERTIES *) calloc(sizeof(DSV_PROPERTIES), 1);
  if (psProperties == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: calloc(): %s", acRoutine, strerror(errno));
    return NULL;
  }

  /*-
   *********************************************************************
   *
   * Initialize members.
   *
   *********************************************************************
   */
  psProperties->pcCharacterSet = gacAlphaNumericCharacterSet;
  psProperties->iCharacterSetLength = strlen(gacAlphaNumericCharacterSet);
  psProperties->iPasswordLength = DSV_DEFAULT_PASSWORD_LENGTH;

  return psProperties;
}


/*-
 ***********************************************************************
 *
 * DsvOptionHandler
 *
 ***********************************************************************
 */
int
DsvOptionHandler(OPTIONS_TABLE *psOption, char *pcValue, DSV_PROPERTIES *psProperties, char *pcError)
{
  const char          acRoutine[] = "DsvOptionHandler()";
  int                 iLength = 0;

  iLength = (pcValue == NULL) ? 0 : strlen(pcValue);

  switch (psOption->iId)
  {
  case OPT_Bits:
    if (iLength < 1)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: option=[%s], value=[%s]: value is null", acRoutine, psOption->atcFullName, pcValue);
      return ER;
    }
    while (iLength > 0)
    {
      if (!isdigit((int) pcValue[iLength - 1]))
      {
        snprintf(pcError, MESSAGE_SIZE, "%s: option=[%s], value=[%s]: value must be a positive integer (digits only)", acRoutine, psOption->atcFullName, pcValue);
        return ER;
      }
      iLength--;
    }
    psProperties->iKeyBits = (int) strtol(pcValue, (char **) NULL, 10);
    if (errno == ERANGE)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: option=[%s], value=[%s]: %s", acRoutine, psOption->atcFullName, pcValue, strerror(errno));
      return ER;
    }
    break;
  case OPT_CharacterClass:
    if (strcasecmp(pcValue, "char95") == 0)
    {
      psProperties->pcCharacterSet = gacChar95CharacterSet;
      psProperties->iCharacterSetLength = strlen(gacChar95CharacterSet);
    }
    else if (strcasecmp(pcValue, "char92") == 0)
    {
      psProperties->pcCharacterSet = gacChar92CharacterSet;
      psProperties->iCharacterSetLength = strlen(gacChar92CharacterSet);
    }
    else if (strcasecmp(pcValue, "alpha") == 0)
    {
      psProperties->pcCharacterSet = gacAlphaCharacterSet;
      psProperties->iCharacterSetLength = strlen(gacAlphaCharacterSet);
    }
    else if (strcasecmp(pcValue, "alnum") == 0)
    {
      psProperties->pcCharacterSet = gacAlphaNumericCharacterSet;
      psProperties->iCharacterSetLength = strlen(gacAlphaNumericCharacterSet);
    }
    else if (strcasecmp(pcValue, "lower") == 0)
    {
      psProperties->pcCharacterSet = gacLowerCharacterSet;
      psProperties->iCharacterSetLength = strlen(gacLowerCharacterSet);
    }
    else if (strcasecmp(pcValue, "upper") == 0)
    {
      psProperties->pcCharacterSet = gacUpperCharacterSet;
      psProperties->iCharacterSetLength = strlen(gacUpperCharacterSet);
    }
    else if (strcasecmp(pcValue, "base16") == 0 || strcasecmp(pcValue, "hex") == 0)
    {
      psProperties->pcCharacterSet = gacBase16CharacterSet;
      psProperties->iCharacterSetLength = strlen(gacBase16CharacterSet);
    }
    else if (strcasecmp(pcValue, "digit") == 0 || strcasecmp(pcValue, "base10") == 0)
    {
      psProperties->pcCharacterSet = gacBase10CharacterSet;
      psProperties->iCharacterSetLength = strlen(gacBase10CharacterSet);
    }
    else if (strcasecmp(pcValue, "octal") == 0 || strcasecmp(pcValue, "base8") == 0)
    {
      psProperties->pcCharacterSet = gacBase8CharacterSet;
      psProperties->iCharacterSetLength = strlen(gacBase8CharacterSet);
    }
    else if (strcasecmp(pcValue, "binary") == 0 || strcasecmp(pcValue, "base2") == 0)
    {
      psProperties->pcCharacterSet = gacBase2CharacterSet;
      psProperties->iCharacterSetLength = strlen(gacBase2CharacterSet);
    }
    else
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: option=[%s], value=[%s]: invalid character class", acRoutine, psOption->atcFullName, pcValue);
      return ER;
    }
    break;
  case OPT_CharacterSet:
    psProperties->pcCharacterSet = pcValue;
    psProperties->iCharacterSetLength = strlen(pcValue);
    break;
  case OPT_CertFile:
    psProperties->iVerifyMode = DSV_VERIFY_VIA_FILE;
    psProperties->pcCertFile = pcValue;
    break;
  case OPT_CertTree:
    psProperties->iVerifyMode = DSV_VERIFY_VIA_TREE;
    psProperties->pcCertTree = pcValue;
    break;
  case OPT_Key:
    psProperties->pcKeyFile = pcValue;
    break;
  case OPT_Length:
    if (iLength < 1)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: option=[%s], value=[%s]: value is null", acRoutine, psOption->atcFullName, pcValue);
      return ER;
    }
    while (iLength > 0)
    {
      if (!isdigit((int) pcValue[iLength - 1]))
      {
        snprintf(pcError, MESSAGE_SIZE, "%s: option=[%s], value=[%s]: value must be a positive integer (digits only)", acRoutine, psOption->atcFullName, pcValue);
        return ER;
      }
      iLength--;
    }
    psProperties->iPasswordLength = (int) strtol(pcValue, (char **) NULL, 10);
    if (errno == ERANGE)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: option=[%s], value=[%s]: %s", acRoutine, psOption->atcFullName, pcValue, strerror(errno));
      return ER;
    }
    if (psProperties->iPasswordLength < DSV_MIN_PASSWORD_LENGTH || psProperties->iPasswordLength > DSV_MAX_PASSWORD_LENGTH)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: option=[%s], value=[%s]: value must be in the range [%d-%d]", acRoutine, psOption->atcFullName, pcValue, DSV_MIN_PASSWORD_LENGTH, DSV_MAX_PASSWORD_LENGTH);
      return ER;
    }
    break;
  case OPT_Type:
    if (strcasecmp(pcValue, "rsa") == 0)
    {
      psProperties->iKeyType = DSV_KEY_TYPE_RSA;
    }
    else if (strcasecmp(pcValue, "dsa") == 0)
    {
      psProperties->iKeyType = DSV_KEY_TYPE_DSA;
    }
    break;
  default:
    snprintf(pcError, MESSAGE_SIZE, "%s: invalid option (%d)", acRoutine, psOption->iId);
    return ER;
    break;
  }

  return ER_OK;
}


/*-
 ***********************************************************************
 *
 * DsvProcessArguments
 *
 ***********************************************************************
 */
int
DsvProcessArguments(int iArgumentCount, char *ppcArgumentVector[], DSV_PROPERTIES *psProperties, char *pcError)
{
  const char          acRoutine[] = "DsvProcessArguments()";
  char                acLocalError[MESSAGE_SIZE] = "";
  char               *pcMode = NULL;
  int                 iError = 0;
  int                 iOperandIndex = 0;
  int                 iOperandCount = 0;
  OPTIONS_CONTEXT    *psOptionsContext = NULL;
  static OPTIONS_TABLE asKeyGenOptions[] =
  {
    { OPT_Bits, "-b", "--bits", 0, 0, 1, 1, DsvOptionHandler },
    { OPT_Type, "-t", "--type", 0, 0, 1, 1, DsvOptionHandler },
  };
  static OPTIONS_TABLE asPassGenOptions[] =
  {
    { OPT_CharacterClass, "-C", "--character-class", 0, 0, 1, 0, DsvOptionHandler },
    { OPT_CharacterSet, "-c", "--character-set", 0, 0, 1, 0, DsvOptionHandler },
    { OPT_Length, "-l", "--length", 0, 0, 1, 0, DsvOptionHandler },
  };
  static OPTIONS_TABLE asSignOptions[] =
  {
    { OPT_Key, "-k", "--key-file", 0, 0, 1, 1, DsvOptionHandler },
  };
  static OPTIONS_TABLE asVerifyOptions[] =
  {
    { OPT_CertFile, "-f", "--cert-file", 0, 0, 1, 0, DsvOptionHandler }, /* This is conditionally required (see check below). */
    { OPT_CertTree, "-t", "--cert-tree", 0, 0, 1, 0, DsvOptionHandler }, /* This is conditionally required (see check below). */
  };

  /*-
   *********************************************************************
   *
   * Initialize the options context.
   *
   *********************************************************************
   */
  psOptionsContext = OptionsNewOptionsContext(iArgumentCount, ppcArgumentVector, acLocalError);
  if (psOptionsContext == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    return ER;
  }

  /*-
   *********************************************************************
   *
   * Determine the run mode.
   *
   *********************************************************************
   */
  pcMode = OptionsGetFirstArgument(psOptionsContext);
  if (pcMode == NULL)
  {
    DsvUsage();
  }
  else
  {
    if (strcmp(pcMode, "-g") == 0 || strcmp(pcMode, "--generate-key") == 0)
    {
      psProperties->iRunMode = DSV_KEYGEN;
      OptionsSetOptions(psOptionsContext, asKeyGenOptions, (sizeof(asKeyGenOptions) / sizeof(asKeyGenOptions[0])));
    }
    else if (strcmp(pcMode, "-p") == 0 || strcmp(pcMode, "--generate-password") == 0)
    {
      psProperties->iRunMode = DSV_PASSGEN;
      OptionsSetOptions(psOptionsContext, asPassGenOptions, (sizeof(asPassGenOptions) / sizeof(asPassGenOptions[0])));
    }
    else if (strcmp(pcMode, "-s") == 0 || strcmp(pcMode, "--sign-payload") == 0)
    {
      psProperties->iRunMode = DSV_SIGN;
      OptionsSetOptions(psOptionsContext, asSignOptions, (sizeof(asSignOptions) / sizeof(asSignOptions[0])));
    }
    else if (strcmp(pcMode, "-c") == 0 || strcmp(pcMode, "--check-signature") == 0 || strcmp(pcMode, "-V") == 0 || strcmp(pcMode, "--verify-signature") == 0)
    {
      psProperties->iRunMode = DSV_VERIFY;
      OptionsSetOptions(psOptionsContext, asVerifyOptions, (sizeof(asVerifyOptions) / sizeof(asVerifyOptions[0])));
    }
    else if (strcmp(pcMode, "-v") == 0 || strcmp(pcMode, "--version") == 0)
    {
      DsvVersion();
    }
    else
    {
      DsvUsage();
    }
  }

  /*-
   *********************************************************************
   *
   * Process options.
   *
   *********************************************************************
   */
  iError = OptionsProcessOptions(psOptionsContext, (void *) psProperties, acLocalError);
  switch (iError)
  {
  case OPTIONS_ER:
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    return ER;
    break;
  case OPTIONS_OK:
    break;
  case OPTIONS_USAGE:
  default:
    DsvUsage();
    break;
  }

  /*-
   *********************************************************************
   *
   * Handle any special cases and/or remaining arguments.
   *
   *********************************************************************
   */
  iOperandCount = OptionsGetArgumentsLeft(psOptionsContext);
  switch (psProperties->iRunMode)
  {
  case DSV_SIGN:
    if (iOperandCount < 1)
    {
      DsvUsage();
    }
    iOperandIndex = OptionsGetArgumentIndex(psOptionsContext) + 1;
    psProperties->ppcFileVector = &ppcArgumentVector[iOperandIndex];
    psProperties->iFileCount = iOperandCount;
    break;
  case DSV_VERIFY:
    if
    (
         !OptionsHaveSpecifiedOption(psOptionsContext, OPT_CertFile)
      && !OptionsHaveSpecifiedOption(psOptionsContext, OPT_CertTree)
    )
    {
      DsvUsage(); /* Either OPT_CertFile or OPT_CertTree is required, but not both. */
    }
    if (iOperandCount < 1)
    {
      DsvUsage();
    }
    iOperandIndex = OptionsGetArgumentIndex(psOptionsContext) + 1;
    psProperties->ppcFileVector = &ppcArgumentVector[iOperandIndex];
    psProperties->iFileCount = iOperandCount;
    break;
  case DSV_PASSGEN:
    if
    (
         OptionsHaveSpecifiedOption(psOptionsContext, OPT_CharacterClass)
      && OptionsHaveSpecifiedOption(psOptionsContext, OPT_CharacterSet)
    )
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: Specify either a character class or character set, but not both.", acRoutine);
      return ER;
    }
  default:
    if (iOperandCount > 0)
    {
      DsvUsage();
    }
    break;
  }

  /*-
   *********************************************************************
   *
   * If any required arguments are missing, it's an error.
   *
   *********************************************************************
   */
  if (OptionsHaveRequiredOptions(psOptionsContext) == 0)
  {
    DsvUsage();
  }

  return ER_OK;
}


/*-
 ***********************************************************************
 *
 * DsvSetPropertiesReference
 *
 ***********************************************************************
 */
void
DsvSetPropertiesReference(DSV_PROPERTIES *psProperties)
{
  gpsProperties = psProperties;
}


/*-
 ***********************************************************************
 *
 * DsvShutdown
 *
 ***********************************************************************
 */
void
DsvShutdown(int iError)
{
  DsvFreeProperties(DsvGetPropertiesReference());
  exit(iError);
}


/*-
 ***********************************************************************
 *
 * DsvSign
 *
 ***********************************************************************
 */
int
DsvSign(DSV_PROPERTIES *psProperties, char *pcError)
{
  const char          acRoutine[] = "DsvSign()";
  char                acLocalError[MESSAGE_SIZE] = "";
  int                 iError = 0;
  int                 iErrorCount = 0;
  int                 iIndex = 0;
  int                 iStdinCount = 0;
  DSV_SIGNATURE      *psSignature = NULL;
  EVP_PKEY           *psPrivateKey = NULL;

  /*-
   *********************************************************************
   *
   * Load the private key from the specified file. Then, sign all of
   * the specified payloads. Don't abort on error since there can be
   * more than one payload in any given run.
   *
   *********************************************************************
   */
  psPrivateKey = DsvLoadPrivateKey(psProperties->pcKeyFile, acLocalError);
  if (psPrivateKey == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    return ER;
  }
  for (iIndex = 0; iIndex < psProperties->iFileCount; iIndex++)
  {
    if (strcmp(psProperties->ppcFileVector[iIndex], "-") == 0)
    {
      if (++iStdinCount > 1)
      {
        fprintf(stderr, "%s: %s: Multiple instances of stdin (-) are not allowed in a single invocation.\n", PROGRAM_NAME, acRoutine);
        continue;
      }
    }
    psSignature = DsvNewSignature(acLocalError);
    if (psSignature == NULL)
    {
      fprintf(stderr, "%s: %s: %s\n", PROGRAM_NAME, acRoutine, acLocalError);
      iErrorCount++;
      continue;
    }
    iError = DsvSignPayload(psProperties->ppcFileVector[iIndex], psPrivateKey, psSignature, acLocalError);
    if (iError != ER_OK)
    {
      fprintf(stderr, "%s: %s: %s\n", PROGRAM_NAME, acRoutine, acLocalError);
      DsvFreeSignature(psSignature);
      iErrorCount++;
      continue;
    }
    iError = DsvWriteSignature(psProperties->ppcFileVector[iIndex], DSV_SIGNATURE_EXTENSION, (char *) psSignature->pucBase64Signature, acLocalError);
    if (iError != ER_OK)
    {
      fprintf(stderr, "%s: %s: %s\n", PROGRAM_NAME, acRoutine, acLocalError);
      DsvFreeSignature(psSignature);
      iErrorCount++;
      continue;
    }
    DsvFreeSignature(psSignature);
  }
  EVP_PKEY_free(psPrivateKey);

  if (iErrorCount)
  {
    if (iErrorCount == 1)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: There was %d signing error.", acRoutine, iErrorCount);
    }
    else
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: There were %d signing errors.", acRoutine, iErrorCount);
    }
    return ER;
  }

  return ER_OK;
}


/*-
 ***********************************************************************
 *
 * DsvUsage
 *
 ***********************************************************************
 */
void
DsvUsage(void)
{
  fprintf(stderr, "\n");
  fprintf(stderr, "Usage: %s {-s|--sign-payload} {-k|--key-file} key {payload|-} [payload ...]\n", PROGRAM_NAME);
  fprintf(stderr, "       %s {-c|--check-signature} {-f|--cert-file} cert signature [signature ...]\n", PROGRAM_NAME);
  fprintf(stderr, "       %s {-c|--check-signature} {-t|--cert-tree} tree signature [signature ...]\n", PROGRAM_NAME);
  fprintf(stderr, "       %s {-g|--generate-key} {-b|--bits} bits {-t|--type} type\n", PROGRAM_NAME);
  fprintf(stderr, "       %s {-p|--generate-password} [{-C|--character-class} class] [{-c|--character-set} set] [{-l|--length} length]\n", PROGRAM_NAME);
  fprintf(stderr, "       %s {-v|--version}\n", PROGRAM_NAME);
  fprintf(stderr, "\n");
  exit(XER_Usage);
}


/*-
 ***********************************************************************
 *
 * DsvVerify
 *
 ***********************************************************************
 */
int
DsvVerify(DSV_PROPERTIES *psProperties, char *pcError)
{
  const char          acRoutine[] = "DsvVerify()";
  char                acLocalError[MESSAGE_SIZE] = "";
  char               *pcExtension = NULL;
  char               *pcPayload = NULL;
  char               *pcSigFile = NULL;
  int                 iError = 0;
  int                 iErrorCount = 0;
  int                 iExtensionLength = 0;
  int                 iFailureCount = 0;
  int                 iIndex = 0;
  int                 iSigFileLength = 0;
  int                 iSuccessCount = 0;
  int                 iTotalCount = 0;
  DSV_SIGNATURE      *psSignature = NULL;

  /*-
   *********************************************************************
   *
   * Load the public keys. If we are in file verify mode, there will
   * only be one key to load. If we are in tree verify mode, attempt
   * to load every file in the specified tree, but do not abort on a
   * load error. In either case, the result should be a cert list.
   *
   *********************************************************************
   */
  switch (psProperties->iVerifyMode)
  {
  case DSV_VERIFY_VIA_FILE:
    psProperties->psCertList = DsvLoadPublicKey(psProperties->pcCertFile, psProperties->iFileType, acLocalError);
    if (psProperties->psCertList == NULL)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
      return ER;
    }
    break;
  case DSV_VERIFY_VIA_TREE:
    psProperties->psCertList = DsvLoadPublicKeys(psProperties->pcCertTree, psProperties->iFileType, acLocalError);
    if (psProperties->psCertList == NULL)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
      return ER;
    }
    break;
  default:
    snprintf(pcError, MESSAGE_SIZE, "%s: Invalid verify mode (%d). That shouldn't happen!", acRoutine, psProperties->iVerifyMode);
    return ER;
    break;
  }

  /*-
   *********************************************************************
   *
   * Create the required extension to use for name checking.
   *
   *********************************************************************
   */
  iExtensionLength = 1 + strlen(DSV_SIGNATURE_EXTENSION);
  pcExtension = (char *) calloc(iExtensionLength + 1, 1);
  if (pcExtension == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: calloc(): %s", acRoutine, strerror(errno));
    return ER;
  }
  snprintf(pcExtension, iExtensionLength + 1, ".%s", DSV_SIGNATURE_EXTENSION);

  /*-
   *********************************************************************
   *
   * Verify the specified signatures. Don't abort on error since there
   * can be more than one signature in any given run.
   *
   *********************************************************************
   */
  for (iIndex = 0; iIndex < psProperties->iFileCount; iIndex++)
  {
    /*-
     *******************************************************************
     *
     * Make sure the signature file has the required extension.
     *
     *******************************************************************
     */
    pcSigFile = psProperties->ppcFileVector[iIndex];
    iSigFileLength = strlen(pcSigFile);
    if ((iSigFileLength - iExtensionLength) < 1 || strcmp(&pcSigFile[(iSigFileLength - iExtensionLength)], pcExtension) != 0)
    {
      fprintf(stdout, "Status=\"Signature verification failed due to an error.\"; SigFile=\"%s\"; Error=\"Invalid or missing extension.\";\n", pcSigFile);
      iErrorCount++;
      continue;
    }

    /*-
     *******************************************************************
     *
     * Load the base64-encoded signature from the specified file.
     *
     *******************************************************************
     */
    psSignature = DsvLoadSignature(pcSigFile, acLocalError);
    if (psSignature == NULL)
    {
      fprintf(stdout, "Status=\"Signature verification failed due to an error.\"; SigFile=\"%s\"; Error=\"%s\";\n", pcSigFile, acLocalError);
      iErrorCount++;
      continue;
    }

    /*-
     *******************************************************************
     *
     * Allocate memory for and initialize the payload name.
     *
     *******************************************************************
     */
    pcPayload = calloc((iSigFileLength + 1), 1);
    if (pcPayload == NULL)
    {
      fprintf(stdout, "Status=\"Signature verification failed due to an error.\"; SigFile=\"%s\"; Error=\"%s\";\n", pcSigFile, strerror(errno));
      iErrorCount++;
      continue;
    }
    snprintf(pcPayload, iSigFileLength + 1, "%s", pcSigFile);
    pcPayload[iSigFileLength - iExtensionLength] = 0; /* Chop off the extension. */

    /*-
     *******************************************************************
     *
     * Begin the verification process by hashing the payload file. This
     * step need only be done once, and if there are multiple keys to
     * check, it saves us from having to rehash the payload each time.
     *
     *******************************************************************
     */
    iError = DsvProcessPayloadFile(pcPayload, psSignature, acLocalError);
    if (iError != ER_OK)
    {
      fprintf(stdout, "Status=\"Signature verification failed due to an error.\"; SigFile=\"%s\"; Error=\"%s\";\n", pcSigFile, acLocalError);
      iErrorCount++;
      continue;
    }

    /*-
     *******************************************************************
     *
     * Loop through the keys until a valid signature is found.
     *
     *******************************************************************
     */
    iError = DsvVerifySignature(psProperties->psCertList, psProperties->iVerifyMode, psSignature, acLocalError);
    switch (iError)
    {
    case DSV_SIGNATURE_VERIFICATION_ERROR:
      fprintf(stdout, "Status=\"Signature verification failed due to an error.\"; SigFile=\"%s\"; Error=\"%s\";\n", pcSigFile, acLocalError);
      iErrorCount++;
      continue;
      break;
    case DSV_SIGNATURE_VERIFICATION_PASSED:
      if (psSignature->pcCommonName == NULL)
      {
        fprintf(stdout, "Status=\"Signature is valid.\"; SigFile=\"%s\";\n", pcSigFile);
      }
      else
      {
        fprintf(stdout, "Status=\"Signature is valid.\"; SigFile=\"%s\"; CommonName=\"%s\";\n", pcSigFile, psSignature->pcCommonName);
      }
      iSuccessCount++;
      continue;
      break;
    case DSV_SIGNATURE_VERIFICATION_FAILED:
      fprintf(stdout, "Status=\"Signature verification failed.\"; SigFile=\"%s\";\n", pcSigFile);
      iFailureCount++;
      continue;
      break;
    default:
      fprintf(stdout, "Status=\"Signature verification failed due to an error.\"; SigFile=\"%s\"; Error=\"Invalid verification status (%d). That shouldn't happen!\";\n", pcSigFile, iError);
      iErrorCount++;
      continue;
      break;
    }
  }

  /*-
   *********************************************************************
   *
   * Tally the results and return the appropriate error code.
   *
   *********************************************************************
   */
  iTotalCount = iSuccessCount + iFailureCount + iErrorCount;
  snprintf(pcError, MESSAGE_SIZE, "Out of %d signature%s, %d passed, %d failed, and %d aborted due to an error.",
    iTotalCount,
    (iTotalCount == 1) ? "" : "s",
    iSuccessCount,
    iFailureCount,
    iErrorCount
    );
  if (iFailureCount > 0 && iErrorCount > 0)
  {
    return XER_SignatureVerificationMixed;
  }
  if (iFailureCount > 0)
  {
    return ER_SignatureVerificationFailed;
  }
  if (iErrorCount > 0)
  {
    return ER;
  }

  return ER_OK;
}


/*-
 ***********************************************************************
 *
 * DsvVersion
 *
 ***********************************************************************
 */
void
DsvVersion(void)
{
  fprintf(stdout, "%s %s %d-bit %s\n", PROGRAM_NAME, VERSION, (int) (sizeof(&DsvVersion) * 8), SslGetVersion());
  exit(XER_OK);
}
