/*-
 ***********************************************************************
 *
 * $Id: dsv.c,v 1.11 2012/01/07 07:56:14 mavrik Exp $
 *
 ***********************************************************************
 *
 * Copyright 2006-2012 The WebJob Project, All Rights Reserved.
 *
 ***********************************************************************
 */
#include "all-includes.h"

/*-
 ***********************************************************************
 *
 * DsvAddCertNode
 *
 ***********************************************************************
 */
DSV_CERT_NODE *
DsvAddCertNode(DSV_CERT_NODE *psHead, DSV_CERT_NODE *psNode)
{
  DSV_CERT_NODE      *psTemp = NULL;
  DSV_CERT_NODE      *psTail = NULL;

  /*-
   *********************************************************************
   *
   * If the new node is NULL, there's nothing to do.
   *
   *********************************************************************
   */
  if (psNode == NULL)
  {
    return psHead;
  }

  /*-
   *********************************************************************
   *
   * If the head is NULL, make the new node the head. Otherwise, find
   * the tail, and attach the new node to it.
   *
   *********************************************************************
   */
  if (psHead == NULL)
  {
    psHead = psNode;
  }
  else
  {
    psTemp = psHead;
    while (psTemp != NULL)
    {
      if (psTemp->psNext == NULL)
      {
        psTail = psTemp;
      }
      psTemp = psTemp->psNext;
    }
    psTail->psNext = psNode;
  }
  psTail = psNode;
  psTail->psNext = NULL;

  return psHead;
}


/*-
 ***********************************************************************
 *
 * DsvChopTrailingEquals
 *
 ***********************************************************************
 */
int
DsvChopTrailingEquals(char *pcData, int iFlags)
{
  int                 iLength = 0;

  iLength = strlen(pcData);
  while (iLength > 0 && pcData[iLength - 1] == '=')
  {
    if ((iFlags & DSV_CHOP) == DSV_CHOP)
    {
      pcData[--iLength] = 0;
    }
    else
    {
      --iLength; /* Adjust the length, but do not chop. */
    }
  }
  return iLength;
}


/*-
 ***********************************************************************
 *
 * DsvChopTrailingSlashes
 *
 ***********************************************************************
 */
int
DsvChopTrailingSlashes(char *pcData, int iFlags)
{
  int                 iLength = 0;

  iLength = strlen(pcData);
  while (iLength > 0 && pcData[iLength - 1] == DSV_SLASHCHAR)
  {
    if ((iFlags & DSV_CHOP) == DSV_CHOP)
    {
      pcData[--iLength] = 0;
    }
    else
    {
      --iLength; /* Adjust the length, but do not chop. */
    }
  }
  return iLength;
}


/*-
 ***********************************************************************
 *
 * DsvChopTrailingWhitespace
 *
 ***********************************************************************
 */
int
DsvChopTrailingWhitespace(char *pcData, int iFlags)
{
  int                 iLength = 0;

  iLength = strlen(pcData);
  while (iLength > 0 && isspace((int) pcData[iLength - 1]))
  {
    if ((iFlags & DSV_CHOP) == DSV_CHOP)
    {
      pcData[--iLength] = 0;
    }
    else
    {
      --iLength; /* Adjust the length, but do not chop. */
    }
  }

  return iLength;
}


/*-
 ***********************************************************************
 *
 * DsvDecodeSignature
 *
 ***********************************************************************
 */
int
DsvDecodeSignature(DSV_SIGNATURE *psSignature, char *pcError)
{
  const char          acRoutine[] = "DsvDecodeSignature()";
  int                 iError = 0;
  int                 iActualBase64Length = 0;
  int                 iActualBinaryLength = 0;
  int                 iActualBufferSize = 0;
  int                 iLength = 0;
  int                 iNChars = 0;

  /*-
   *********************************************************************
   *
   * Check required inputs.
   *
   *********************************************************************
   */
  if (psSignature->pucBase64Signature == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: Undefined base64 signature.", acRoutine);
    return ER;
  }
  iActualBase64Length = strlen((char *) psSignature->pucBase64Signature);

  /*-
   *********************************************************************
   *
   * Calculate the signature's actual length. Then, calculate how many
   * bytes are required to hold it safely -- i.e., so EVP_DecodeBlock()
   * won't overflow the buffer.
   *
   *********************************************************************
   */
  iLength = DsvChopTrailingEquals((char *) psSignature->pucBase64Signature, 0);
  iNChars = iActualBase64Length - (iActualBase64Length - iLength);
  iActualBinaryLength = (iNChars / 4 * 3) + ((iNChars % 4) ? (iNChars % 4) - 1 : 0);
  iActualBufferSize = iActualBase64Length / 4 * 3;

  /*-
   *********************************************************************
   *
   * Allocate and clear memory to hold the signature.
   *
   *********************************************************************
   */
  psSignature->pucBinarySignature = (unsigned char *) calloc(iActualBufferSize, 1);
  if (psSignature->pucBinarySignature == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: calloc(): %s", acRoutine, strerror(errno));
    return ER;
  }

  /*-
   *********************************************************************
   *
   * Decode the signature (base64 --> binary).
   *
   *********************************************************************
   */
  iError = EVP_DecodeBlock(psSignature->pucBinarySignature, psSignature->pucBase64Signature, iActualBase64Length);
  if (iError == ER)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: EVP_DecodeBlock(): Unable to decode signature.", acRoutine);
    return ER;
  }
  psSignature->iBinaryLength = iActualBinaryLength;

  return ER_OK;
}


/*-
 ***********************************************************************
 *
 * DsvEncodeSignature
 *
 ***********************************************************************
 */
int
DsvEncodeSignature(DSV_SIGNATURE *psSignature, char *pcError)
{
  const char          acRoutine[] = "DsvEncodeSignature()";
  int                 iActualBase64Length = 0;
  int                 iTargetBase64Length = 0;

  /*-
   *********************************************************************
   *
   * Check required inputs.
   *
   *********************************************************************
   */
  if (psSignature->pucBinarySignature == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: Undefined binary signature.", acRoutine);
    return ER;
  }

  /*-
   *********************************************************************
   *
   * Calculate the number of bytes are required to hold the signature.
   *
   *********************************************************************
   */
  iTargetBase64Length = ((psSignature->iBinaryLength / 3) + ((psSignature->iBinaryLength % 3) ? 1 : 0)) * 4;

  /*-
   *********************************************************************
   *
   * Allocate and clear memory to hold the signature.
   *
   *********************************************************************
   */
  psSignature->pucBase64Signature = (unsigned char *) calloc(iTargetBase64Length + 1, 1);
  if (psSignature->pucBase64Signature == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: calloc(): %s", acRoutine, strerror(errno));
    return ER;
  }

  /*-
   *********************************************************************
   *
   * Encode the signature (binary --> base64).
   *
   *********************************************************************
   */
  iActualBase64Length = EVP_EncodeBlock(psSignature->pucBase64Signature, psSignature->pucBinarySignature, psSignature->iBinaryLength);
  if (iActualBase64Length != iTargetBase64Length)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: Base64 signature length mismatch!: [%d] != [%d]", acRoutine, iActualBase64Length, iTargetBase64Length);
    return ER;
  }
  psSignature->pucBase64Signature[iActualBase64Length] = 0;
  psSignature->iBase64Length = iActualBase64Length;

  return ER_OK;
}


/*-
 ***********************************************************************
 *
 * DsvFreeCertNode
 *
 ***********************************************************************
 */
void
DsvFreeCertNode(DSV_CERT_NODE *psCertNode)
{
  if (psCertNode != NULL)
  {
    if (psCertNode->pcFile != NULL)
    {
      free(psCertNode->pcFile);
    }
    if (psCertNode->pcCommonName != NULL)
    {
      free(psCertNode->pcCommonName);
    }
    if (psCertNode->psPublicKey != NULL)
    {
      EVP_PKEY_free(psCertNode->psPublicKey);
    }
    if (psCertNode->psX509Cert != NULL)
    {
      X509_free(psCertNode->psX509Cert);
    }
    free(psCertNode);
    psCertNode = NULL;
  }
}


/*-
 ***********************************************************************
 *
 * DsvFreeSignature
 *
 ***********************************************************************
 */
void
DsvFreeSignature(DSV_SIGNATURE *psSignature)
{
  if (psSignature != NULL)
  {
    if (psSignature->pucBase64Signature != NULL)
    {
      free(psSignature->pucBase64Signature);
    }
    if (psSignature->pucBinarySignature != NULL)
    {
      free(psSignature->pucBinarySignature);
    }
#if (OPENSSL_VERSION_NUMBER >= 0x00090700fL) /* EVP_MD_CTX_cleanup was added in OpenSSL 0.9.7. */
    EVP_MD_CTX_cleanup(&psSignature->sMdRsaCtx);
    EVP_MD_CTX_cleanup(&psSignature->sMdDsaCtx);
#endif
    free(psSignature);
  }
}


/*-
 ***********************************************************************
 *
 * DsvGenerateSeed
 *
 ***********************************************************************
 */
unsigned char *
DsvGenerateSeed(unsigned char *pucSeed, unsigned long iLength)
{
  unsigned long       i;
  unsigned long       j;
  unsigned long       ulLRS32b;

#ifdef WIN32
  ulLRS32b = (unsigned long) GetTickCount() ^ (unsigned long) time(NULL);
#else
  ulLRS32b = (((unsigned long) getpid()) << 16) ^ (unsigned long) time(NULL);
#endif
  for (i = 0; i < iLength; i++)
  {
    for (j = 0, pucSeed[i] = 0; j < 8; j++)
    {
      pucSeed[i] |= (ulLRS32b & 1) << j;
      ulLRS32b = ((((ulLRS32b >> 7) ^ (ulLRS32b >> 6) ^ (ulLRS32b >> 2) ^ (ulLRS32b >> 0)) & 1) << 31) | (ulLRS32b >> 1);
    }
  }
  return pucSeed;
}


/*-
 ***********************************************************************
 *
 * DsvGetEnvValue
 *
 ***********************************************************************
 */
char *
DsvGetEnvValue(char *pcName)
{
#ifdef WIN32
  char               *pcValue = NULL;
  DWORD               dwCount = 0;

#define DSV_MAX_ENV_SIZE 32767 /* Source = MSDN documentation for GetEnvironmentVariable() */
  pcValue = calloc(DSV_MAX_ENV_SIZE, 1);
  if (pcValue == NULL)
  {
    return NULL;
  }
  dwCount = GetEnvironmentVariable(TEXT(pcName), pcValue, DSV_MAX_ENV_SIZE);
  return (dwCount == 0 || dwCount > DSV_MAX_ENV_SIZE) ? NULL : pcValue;
#else
  return getenv(pcName);
#endif
}


/*-
 ***********************************************************************
 *
 * DsvLoadPrivateKey
 *
 ***********************************************************************
 */
EVP_PKEY *
DsvLoadPrivateKey(char *pcFile, char *pcError)
{
  const char          acRoutine[] = "DsvLoadPrivateKey()";
  char                acLocalError[MESSAGE_SIZE] = "";
  char               *pcEnvValue = NULL;
  char               *pcPassPhrase = NULL;
  EVP_PKEY           *psPKey = NULL;
  FILE               *pFile = NULL;
  int                 iError = 0;
  int                 iLength = 0;

  /*-
   *********************************************************************
   *
   * Check the environment for a passphrase. Enforce a hard size limit
   * to help prevent environment-based abuses.
   *
   *********************************************************************
   */
#ifdef WIN32
  pcEnvValue = DsvGetEnvValue("DSV_PASSPHRASE");
#else
  pcEnvValue = DsvGetEnvValue("DSV_PASSPHRASE");
#endif
  if (pcEnvValue != NULL)
  {
    iLength = strlen(pcEnvValue);
    if (iLength >= 1 && iLength <= DSV_MAX_PASSPHRASE_LENGTH)
    {
      iError = DsvSetDynamicString(&pcPassPhrase, pcEnvValue, acLocalError);
      if (iError != ER_OK)
      {
        snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
        return NULL;
      }
    }
  }

  /*-
   *********************************************************************
   *
   * Conditionally generate and tap the LRS pool.
   *
   *********************************************************************
   */
  if (pcPassPhrase == NULL && DSV_POOL_SEED && DSV_POOL_TAPS > 1)
  {
    unsigned char aucPool[DSV_POOL_SIZE];
    unsigned char aucTaps[DSV_POOL_TAPS];
    DSV_NEW_POOL(aucPool, DSV_POOL_SIZE, DSV_POOL_SEED);
    DSV_TAP_POOL(aucTaps, aucPool);
    iError = DsvSetDynamicString(&pcPassPhrase, (char *) aucTaps, acLocalError);
    if (iError != ER_OK)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
      return NULL;
    }
  }

  /*-
   *********************************************************************
   *
   * Open and process the specified private key.
   *
   *********************************************************************
   */
  pFile = fopen(pcFile, "rb");
  if (pFile == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: fopen(): File = [%s], %s", acRoutine, pcFile, strerror(errno));
    return NULL;
  }
  psPKey = PEM_read_PrivateKey(pFile, NULL, NULL, pcPassPhrase);
  if (psPKey == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: PEM_read_PrivateKey(): Error reading private key from %s: %s", acRoutine, pcFile, ERR_error_string(ERR_get_error(), NULL));
    fclose(pFile);
    return NULL;
  }
  fclose(pFile);

  /*-
   *********************************************************************
   *
   * Conditionally clear and free our copy of the passphrase.
   *
   *********************************************************************
   */
  if (pcPassPhrase != NULL)
  {
    memset(pcPassPhrase, 0, strlen(pcPassPhrase));
    free(pcPassPhrase);
  }

  return psPKey;
}


/*-
 ***********************************************************************
 *
 * DsvLoadPublicKey
 *
 ***********************************************************************
 */
DSV_CERT_NODE *
DsvLoadPublicKey(char *pcFile, int iType, char *pcError)
{
  const char          acRoutine[] = "DsvLoadPublicKey()";
  char                acLocalError[MESSAGE_SIZE] = "";
  DSV_CERT_NODE      *psCertNode = NULL;
  FILE               *pFile = NULL;
  int                 iLength = 0;

  /*-
   *********************************************************************
   *
   * Allocate a new certificate node.
   *
   *********************************************************************
   */
  psCertNode = DsvNewCertNode(pcFile, acLocalError);
  if (psCertNode == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    return NULL;
  }

  /*-
   *********************************************************************
   *
   * Open and process the specified file.
   *
   *********************************************************************
   */
  pFile = fopen(pcFile, "rb");
  if (pFile == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: fopen(): File = [%s], %s", acRoutine, pcFile, strerror(errno));
    DsvFreeCertNode(psCertNode);
    return NULL;
  }

switch (iType)
{
case DSV_FILE_TYPE_CERT:
  psCertNode->psX509Cert = PEM_read_X509(pFile, NULL, NULL, NULL);
  if (psCertNode->psX509Cert == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: PEM_read_X509(): File = [%s], %s", acRoutine, pcFile, ERR_error_string(ERR_get_error(), NULL));
    DsvFreeCertNode(psCertNode);
    fclose(pFile);
    return NULL;
  }
  /*-
   *********************************************************************
   *
   * Extract the public key.
   *
   *********************************************************************
   */
  psCertNode->psPublicKey = X509_get_pubkey(psCertNode->psX509Cert);
  if (psCertNode->psPublicKey == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: X509_get_pubkey(): File = [%s], %s", acRoutine, pcFile, ERR_error_string(ERR_get_error(), NULL));
    DsvFreeCertNode(psCertNode);
    fclose(pFile);
    return NULL;
  }
  /*-
   *********************************************************************
   *
   * Extract the common name.
   *
   *********************************************************************
   */
  iLength = X509_NAME_get_text_by_NID(X509_get_subject_name(psCertNode->psX509Cert), NID_commonName, NULL, 0);
  iLength = (iLength < 0) ? 1 : iLength + 1;
  psCertNode->pcCommonName = (char *) calloc(iLength, 1);
  if (psCertNode->pcCommonName == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: calloc(): %s", acRoutine, strerror(errno));
    DsvFreeCertNode(psCertNode);
    fclose(pFile);
    return NULL;
  }
  if (iLength == 1)
  {
    psCertNode->pcCommonName[0] = 0; /* No common name was found or there was an error. */
  }
  else
  {
    X509_NAME_get_text_by_NID(X509_get_subject_name(psCertNode->psX509Cert), NID_commonName, psCertNode->pcCommonName, iLength);
  }
  break;
case DSV_FILE_TYPE_PUBKEY:
  psCertNode->psPublicKey = PEM_read_PUBKEY(pFile, NULL, NULL, NULL);
  if (psCertNode->psPublicKey == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: PEM_read_PUBKEY(): File = [%s], %s", acRoutine, pcFile, ERR_error_string(ERR_get_error(), NULL));
    DsvFreeCertNode(psCertNode);
    fclose(pFile);
    return NULL;
  }
  break;
default:
  snprintf(pcError, MESSAGE_SIZE, "%s: [%s], Invalid file type (%d). That shouldn't happen!", acRoutine, pcFile, iType);
  DsvFreeCertNode(psCertNode);
  fclose(pFile);
  return NULL;
  break;
}
  fclose(pFile);

  /*-
   *********************************************************************
   *
   * Verify that the key type is valid.
   *
   *********************************************************************
   */
  switch (EVP_PKEY_type(psCertNode->psPublicKey->type))
  {
  case EVP_PKEY_RSA:
  case EVP_PKEY_DSA:
    break;
  default:
    snprintf(pcError, MESSAGE_SIZE, "%s: EVP_PKEY_type(): File = [%s], File contains an unknown or unsupported public key type.", acRoutine, pcFile);
    DsvFreeCertNode(psCertNode);
    return NULL;
    break;
  }

  return psCertNode;
}


/*-
 ***********************************************************************
 *
 * DsvLoadPublicKeys
 *
 ***********************************************************************
 */
DSV_CERT_NODE *
DsvLoadPublicKeys(char *pcTree, int iType, char *pcError)
{
  const char          acRoutine[] = "DsvLoadPublicKeys()";
  char                acLocalError[MESSAGE_SIZE] = "";
  char               *pcPath = NULL;
  DSV_CERT_NODE      *psCertList = NULL;
  DSV_CERT_NODE      *psCertNode = NULL;
  int                 iLength = 0;
  int                 iSize = 0;
#ifdef WIN32
  BOOL                bFinished = FALSE;
  char                acSearchPath[DSV_MAX_PATH] = { 0 };
  HANDLE              hSearch;
  WIN32_FIND_DATA     sFindData = { 0 };
#else
  struct stat         sStatEntry = { 0 };
  DIR                *psTree = NULL;
  struct dirent      *psDirEntry = NULL;
#endif

  /*-
   *********************************************************************
   *
   * Chop trailing slashes, and finalize the tree's length.
   *
   *********************************************************************
   */
  iLength = DsvChopTrailingSlashes(pcTree, DSV_CHOP);

#ifdef WIN32
  /*-
   *********************************************************************
   *
   * Append "\*" to the specified tree to create a search path.
   *
   *********************************************************************
   */
  if (iLength < 1 || iLength > DSV_MAX_PATH - 3)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: SearchPath = [%s\\*], Length (%d) falls outside allowed limits.", acRoutine, pcTree, iLength);
    return NULL;
  }
  snprintf(acSearchPath, DSV_MAX_PATH, "%s\\*", pcTree);

  /*-
   *********************************************************************
   *
   * Acquire a search handle for the specified tree.
   *
   *********************************************************************
   */
  hSearch = FindFirstFile(acSearchPath, &sFindData);
  if (hSearch == INVALID_HANDLE_VALUE)
  {
/* FIXME Replace GetLastError() with a text-based message. */
    snprintf(pcError, MESSAGE_SIZE, "%s: FindFirstFile(): Unable to open %s: %d", acRoutine, pcTree, GetLastError());
    return NULL;
  }

  /*-
   *********************************************************************
   *
   * Attempt to load all certificate files in the specified tree. Add
   * a node to the certificate list for each successful load. Because
   * there can be many certificates in the specified tree, it is more
   * important to keep going than it is to abort on a load error.
   *
   *********************************************************************
   */
  bFinished = FALSE;
  while (!bFinished)
  {
    if (!(sFindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) /* If it's not a dir, then its assumed to be a file. */
    {
      iSize = iLength + strlen(sFindData.cFileName) + 2;
      pcPath = malloc(iSize);
      if (pcPath != NULL)
      {
        snprintf(pcPath, iSize, "%s\\%s", pcTree, sFindData.cFileName);
        psCertNode = DsvLoadPublicKey(pcPath, iType, acLocalError);
        if (psCertNode != NULL)
        {
          psCertList = DsvAddCertNode(psCertList, psCertNode);
        }
        free(pcPath);
      }
    }
    if (!FindNextFile(hSearch, &sFindData))
    {
      if (GetLastError() == ERROR_NO_MORE_FILES)
      {
        bFinished = TRUE;
      }
    }
  }
  FindClose(hSearch);
#else
  /*-
   *********************************************************************
   *
   * Acquire a directory handle for the specified tree.
   *
   *********************************************************************
   */
  if ((psTree = opendir(pcTree)) == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: opendir(): Unable to open %s: %s", acRoutine, pcTree, strerror(errno));
    return NULL;
  }

  /*-
   *********************************************************************
   *
   * Attempt to load all certificate files in the specified tree. Add
   * a node to the certificate list for each successful load. Because
   * there can be many certificates in the specified tree, it is more
   * important to keep going than it is to abort on a load error.
   *
   *********************************************************************
   */
  while ((psDirEntry = readdir(psTree)) != NULL)
  {
    iSize = iLength + strlen(psDirEntry->d_name) + 2;
    pcPath = malloc(iSize);
    if (pcPath != NULL)
    {
      snprintf(pcPath, iSize, "%s/%s", pcTree, psDirEntry->d_name);
      if (stat(pcPath, &sStatEntry) == ER_OK && (sStatEntry.st_mode & S_IFMT) == S_IFREG)
      {
        psCertNode = DsvLoadPublicKey(pcPath, iType, acLocalError);
        if (psCertNode != NULL)
        {
          psCertList = DsvAddCertNode(psCertList, psCertNode);
        }
      }
      free(pcPath);
    }
  }
  closedir(psTree);
#endif

  if (psCertList == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: The certificate list is empty. At least one valid certificate is required.", acRoutine);
    return NULL;
  }

  return psCertList;
}


/*-
 ***********************************************************************
 *
 * DsvLoadSignature
 *
 ***********************************************************************
 */
DSV_SIGNATURE *
DsvLoadSignature(char *pcFile, char *pcError)
{
  const char          acRoutine[] = "DsvLoadSignature()";
  char                acLocalError[MESSAGE_SIZE] = "";
  DSV_SIGNATURE      *psSignature = NULL;
  FILE               *pFile = NULL;
  int                 iError = 0;
  int                 iNRead = 0;
  int                 iLength = 0;
  struct stat         sStatEntry = { 0 };

  /*-
   *********************************************************************
   *
   * Allocate a new signature.
   *
   *********************************************************************
   */
  psSignature = DsvNewSignature(acLocalError);
  if (psSignature == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    return NULL;
  }

  /*-
   *********************************************************************
   *
   * Open the signature file in binary mode, determine its size, and
   * abort if it's bigger than the size of the read buffer, which is
   * currently much bigger than a signature can be.
   *
   *********************************************************************
   */
  pFile = fopen(pcFile, "rb");
  if (pFile == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: fopen(): File = [%s], %s", acRoutine, pcFile, strerror(errno));
    DsvFreeSignature(psSignature);
    return NULL;
  }

  if (fstat(fileno(pFile), &sStatEntry) == ER)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: fstat(): File = [%s], %s", acRoutine, pcFile, strerror(errno));
    DsvFreeSignature(psSignature);
    fclose(pFile);
    return NULL;
  }

  iLength = (int) sStatEntry.st_size;

  if (iLength < 0 || iLength > DSV_READ_SIZE)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: File = [%s], Size (%d) falls outside allowed limits.", acRoutine, pcFile, iLength);
    DsvFreeSignature(psSignature);
    fclose(pFile);
    return NULL;
  }

  /*-
   *********************************************************************
   *
   * Allocate and clear memory to hold the encoded signature.
   *
   *********************************************************************
   */
  psSignature->pucBase64Signature = (unsigned char *) calloc(iLength + 1, 1);
  if (psSignature->pucBase64Signature == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: calloc(): %s", acRoutine, strerror(errno));
    DsvFreeSignature(psSignature);
    fclose(pFile);
    return NULL;
  }

  /*-
   *********************************************************************
   *
   * Read the encoded signature. Then, verify that the number of bytes
   * we got was the number we requested, and terminate the string.
   *
   *********************************************************************
   */
  iNRead = fread(psSignature->pucBase64Signature, 1, iLength, pFile);
  if (ferror(pFile))
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: fread(): File = [%s], %s", acRoutine, pcFile, strerror(errno));
    DsvFreeSignature(psSignature);
    fclose(pFile);
    return NULL;
  }
  fclose(pFile);
  if (iNRead != iLength)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: File = [%s], Expected %d bytes, but got %d instead.", acRoutine, pcFile, iLength, iNRead);
    DsvFreeSignature(psSignature);
    return NULL;
  }
  psSignature->pucBase64Signature[iLength] = 0;

  /*-
   *********************************************************************
   *
   * Chop trailing white space, and finalize the signature's length.
   *
   *********************************************************************
   */
  psSignature->iBase64Length = DsvChopTrailingWhitespace((char *) psSignature->pucBase64Signature, DSV_CHOP);

  /*-
   *********************************************************************
   *
   * Decode the signature (base64 --> binary).
   *
   *********************************************************************
   */
  iError = DsvDecodeSignature(psSignature, acLocalError);
  if (iError != ER_OK)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    DsvFreeSignature(psSignature);
    return NULL;
  }

  return psSignature;
}


/*-
 ***********************************************************************
 *
 * DsvNewCertNode
 *
 ***********************************************************************
 */
DSV_CERT_NODE *
DsvNewCertNode(char *pcFile, char *pcError)
{
  const char          acRoutine[] = "DsvNewCertNode()";
  DSV_CERT_NODE      *psCertNode = NULL;
  int                 iLength = 0;

  psCertNode = (DSV_CERT_NODE *) calloc(sizeof(DSV_CERT_NODE), 1);
  if (psCertNode == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: calloc(): %s", acRoutine, strerror(errno));
    return NULL;
  }

  iLength = strlen(pcFile);

  psCertNode->pcFile = (char *) calloc(iLength + 1, 1);
  if (psCertNode->pcFile == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: calloc(): %s", acRoutine, strerror(errno));
    DsvFreeCertNode(psCertNode);
    return NULL;
  }
  strncpy(psCertNode->pcFile, pcFile, iLength + 1);

  return psCertNode;
}


/*-
 ***********************************************************************
 *
 * DsvNewSignature
 *
 ***********************************************************************
 */
DSV_SIGNATURE *
DsvNewSignature(char *pcError)
{
  const char          acRoutine[] = "DsvNewSignature()";
  DSV_SIGNATURE      *psSignature = NULL;

  psSignature = (DSV_SIGNATURE *) calloc(sizeof(DSV_SIGNATURE), 1);
  if (psSignature == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: calloc(): %s", acRoutine, strerror(errno));
    return NULL;
  }
  return psSignature;
}


/*-
 ***********************************************************************
 *
 * DsvProcessPayloadFile
 *
 ***********************************************************************
 */
int
DsvProcessPayloadFile(char *pcFile, DSV_SIGNATURE *psSignature, char *pcError)
{
  const char          acRoutine[] = "DsvProcessPayloadFile()";
  char                acLocalError[MESSAGE_SIZE] = "";
  FILE               *pFile = NULL;
  int                 iError = 0;

  pFile = fopen(pcFile, "rb");
  if (pFile == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: fopen(): File = [%s], %s", acRoutine, pcFile, strerror(errno));
    return ER;
  }
  iError = DsvProcessPayloadStream(pFile, psSignature, acLocalError);
  if (iError != ER_OK)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: File = [%s], %s", acRoutine, pcFile, acLocalError);
    fclose(pFile);
    return ER;
  }
  fclose(pFile);

  return ER_OK;
}


/*-
 ***********************************************************************
 *
 * DsvProcessPayloadStream
 *
 ***********************************************************************
 */
int
DsvProcessPayloadStream(FILE *pFile, DSV_SIGNATURE *psSignature, char *pcError)
{
  const char          acRoutine[] = "DsvProcessPayloadStream()";
  unsigned char       aucData[DSV_READ_SIZE] = "";
  int                 iNRead = 0;

  /*-
   *********************************************************************
   *
   * We hedge our bets by calculating digests for both types of keys.
   * This amounts to more work here, but it's still better than hashing
   * the file once for each key in the certificates directory. The only
   * exception to that is when there is only one certificate to check.
   *
   *********************************************************************
   */
  EVP_VerifyInit(&psSignature->sMdRsaCtx, EVP_sha1());
  EVP_VerifyInit(&psSignature->sMdDsaCtx, EVP_dss1());
  while ((iNRead = fread(aucData, 1, DSV_READ_SIZE, pFile)) > 0)
  {
    EVP_VerifyUpdate(&psSignature->sMdRsaCtx, aucData, iNRead);
    EVP_VerifyUpdate(&psSignature->sMdDsaCtx, aucData, iNRead);
  }
  if (ferror(pFile))
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: fread(): %s", acRoutine, strerror(errno));
    return ER;
  }

  return ER_OK;
}


/*-
 ***********************************************************************
 *
 * DsvSetDynamicString
 *
 ***********************************************************************
 */
int
DsvSetDynamicString(char **ppcValue, char *pcNewValue, char *pcError)
{
  const char          acRoutine[] = "DsvSetDynamicString()";
  char               *pcTempValue = NULL;
  int                 iLength = 0;

  /*-
   *********************************************************************
   *
   * The caller is expected to free this memory.
   *
   *********************************************************************
   */
  iLength = strlen(pcNewValue);
  pcTempValue = realloc(*ppcValue, iLength + 1);
  {
    if (pcTempValue == NULL)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: realloc(): %s", acRoutine, strerror(errno));
      return -1;
    }
    *ppcValue = pcTempValue;
  }
  strncpy(*ppcValue, pcNewValue, iLength + 1);

  return 0;
}


/*-
 ***********************************************************************
 *
 * DsvSignPayload
 *
 ***********************************************************************
 */
int
DsvSignPayload(char *pcFile, EVP_PKEY *psPrivateKey, DSV_SIGNATURE *psSignature, char *pcError)
{
  const char          acRoutine[] = "DsvSignPayload()";
  char                acLocalError[MESSAGE_SIZE] = "";
  char                acData[DSV_READ_SIZE] = "";
  EVP_MD_CTX          sMdCtx = { 0 };
  EVP_MD             *psMdType = NULL;
  FILE               *pFile = NULL;
  int                 iError = 0;
  int                 iLength = 0;

  /*-
   *********************************************************************
   *
   * Obtain and check the private key type.
   *
   *********************************************************************
   */
  switch (EVP_PKEY_type(psPrivateKey->type))
  {
  case EVP_PKEY_RSA:
    psMdType = (EVP_MD *) EVP_sha1();
    break;
  case EVP_PKEY_DSA:
    psMdType = (EVP_MD *) EVP_dss1();
    break;
  default:
    snprintf(pcError, MESSAGE_SIZE, "%s: EVP_PKEY_type(): Unknown or unsupported private key type.", acRoutine);
    return ER;
    break;
  }

  /*-
   *********************************************************************
   *
   * Allocate and clear memory for the signature.
   *
   *********************************************************************
   */
  psSignature->pucBinarySignature = (unsigned char *) calloc(EVP_PKEY_size(psPrivateKey), 1);
  if (psSignature->pucBinarySignature == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: calloc(): %s", acRoutine, strerror(errno));
    return ER;
  }

  /*-
   *********************************************************************
   *
   * Open the payload file.
   *
   *********************************************************************
   */
  if (strcmp(pcFile, "-") == 0)
  {
#ifdef WIN32 /* Prevent CRLF mappings. */
    if (_setmode(_fileno(stdin), _O_BINARY) == -1)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: _setmode(): %s", acRoutine, strerror(errno));
      return ER;
    }
#endif
    pFile = stdin;
  }
  else
  {
    pFile = fopen(pcFile, "rb");
    if (pFile == NULL)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: fopen(): File = [%s], %s", acRoutine, pcFile, strerror(errno));
      return ER;
    }
  }

  /*-
   *********************************************************************
   *
   * Initialize the signature.
   *
   *********************************************************************
   */
  EVP_SignInit(&sMdCtx, psMdType);

  /*-
   *********************************************************************
   *
   * Loop over the payload data updating the signature as we go.
   *
   *********************************************************************
   */
  while ((iLength = fread(acData, 1, DSV_READ_SIZE, pFile)) > 0)
  {
    /*-
     *******************************************************************
     *
     * Note: EVP_SignUpdate(a,b,c) is defined as EVP_DigestUpdate(a,b,c)
     * in evp.h. Prior to OpenSSL 0.9.7, EVP_SignUpdate() would return a
     * void. Now, it returns an int. Therefore, if the code is compiled
     * against an old version of OpenSSL, both cases must be handled. At
     * some point, we should just require a minimum version of OpenSSL.
     *
     * Here's the relevant message from the CHANGES file for the 0.9.7
     * release:
     *
     *   *) Modify EVP_Digest*() routines so they now return values.
     *      Although the internal software routines can never fail
     *      additional hardware versions might.
     *      [Steve Henson]
     *
     *******************************************************************
     */
#if (OPENSSL_VERSION_NUMBER >= 0x00090700fL)
    iError = EVP_SignUpdate(&sMdCtx, acData, iLength);
    if (iError != 1)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: EVP_SignUpdate(): %s", acRoutine, ERR_error_string(ERR_get_error(), NULL));
      return ER;
    }
#else
    EVP_SignUpdate(&sMdCtx, acData, iLength);
#endif
  }
  if (ferror(pFile))
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: fread(): File = [%s], %s", acRoutine, pcFile, strerror(errno));
    return ER;
  }
  if (pFile != stdin)
  {
    fclose(pFile);
  }

  /*-
   *********************************************************************
   *
   * Finalize the signature.
   *
   *********************************************************************
   */
  iError = EVP_SignFinal(&sMdCtx, psSignature->pucBinarySignature, (unsigned *) &psSignature->iBinaryLength, psPrivateKey);
  if (iError != 1)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: EVP_SignFinal(): %s", acRoutine, ERR_error_string(ERR_get_error(), NULL));
    return ER;
  }

  /*-
   *********************************************************************
   *
   * Encode the signature.
   *
   *********************************************************************
   */
  iError = DsvEncodeSignature(psSignature, acLocalError);
  if (iError != ER_OK)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    return ER;
  }

  return ER_OK;
}


/*-
 ***********************************************************************
 *
 * DsvVerifySignature
 *
 ***********************************************************************
 */
int
DsvVerifySignature(DSV_CERT_NODE *psCertList, int iVerifyMode, DSV_SIGNATURE *psSignature, char *pcError)
{
  const char          acRoutine[] = "DsvVerifySignature()";
  int                 iError = 0;
  DSV_CERT_NODE      *psTemp = NULL;

  /*-
   *********************************************************************
   *
   * Finalize the signature for each key in the certificate list, and
   * return a pass/fail result. However, if the caller specified file
   * verify mode, then we are required to check for and report errors
   * as well. This makes it possible for the caller to debug/verify a
   * single certificate.
   *
   *********************************************************************
   */
  for (psTemp = psCertList; psTemp != NULL; psTemp = psTemp->psNext)
  {
    if (EVP_PKEY_type(psTemp->psPublicKey->type) == EVP_PKEY_RSA)
    {
      iError = EVP_VerifyFinal(&psSignature->sMdRsaCtx, psSignature->pucBinarySignature, psSignature->iBinaryLength, psTemp->psPublicKey);
    }
    else
    {
      iError = EVP_VerifyFinal(&psSignature->sMdDsaCtx, psSignature->pucBinarySignature, psSignature->iBinaryLength, psTemp->psPublicKey);
    }
    if (iError == 1)
    {
      psSignature->pcCommonName = psTemp->pcCommonName;
      return DSV_SIGNATURE_VERIFICATION_PASSED;
    }
    else
    {
      if (iVerifyMode == DSV_VERIFY_VIA_FILE && iError == -1)
      {
        snprintf(pcError, MESSAGE_SIZE, "%s: EVP_VerifyFinal(): %s", acRoutine, ERR_error_string(ERR_get_error(), NULL));
        return DSV_SIGNATURE_VERIFICATION_ERROR;
      }
    }
  }

  return DSV_SIGNATURE_VERIFICATION_FAILED;
}


/*-
 ***********************************************************************
 *
 * DsvWriteSignature
 *
 ***********************************************************************
 */
int
DsvWriteSignature(char *pcFile, char *pcExtension, char *pcSignature, char *pcError)
{
  const char          acRoutine[] = "DsvWriteSignature()";
  char               *pcSigFile = NULL;
  FILE               *pFile = NULL;
  int                 iLength = 0;

  /*-
   *********************************************************************
   *
   * If the payload came from stdin, the signature goes to stdout.
   *
   *********************************************************************
   */
  if (strcmp(pcFile, "-") == 0)
  {
    pFile = stdout;
    fprintf(pFile, "%s\n", pcSignature);
    if (ferror(pFile))
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: fprintf(): File = [%s], %s", acRoutine, pcFile, strerror(errno));
      return ER;
    }
    return ER_OK;
  }

  /*-
   *********************************************************************
   *
   * Otherwise, create a signature file, and write to that instead.
   *
   *********************************************************************
   */
  iLength = strlen(pcFile) + 1 + strlen(pcExtension) + 1;
  if (iLength < 1 || iLength > DSV_MAX_PATH - 1)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: File = [%s.%s], Length (%d) falls outside allowed limits.", acRoutine, pcFile, pcExtension, iLength);
    return ER;
  }
  pcSigFile = (char *) calloc(iLength, 1);
  if (pcSigFile == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: calloc(): %s", acRoutine, strerror(errno));
    return ER;
  }
  snprintf(pcSigFile, iLength, "%s.%s", pcFile, pcExtension);
  pFile = fopen(pcSigFile, "wb");
  if (pFile == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: fopen(): File = [%s], %s", acRoutine, pcFile, strerror(errno));
    free(pcSigFile);
    return ER;
  }
  fprintf(pFile, "%s\n", pcSignature);
  if (ferror(pFile))
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: fprintf(): File = [%s], %s", acRoutine, pcFile, strerror(errno));
    free(pcSigFile);
    fclose(pFile);
    return ER;
  }
  free(pcSigFile);
  fclose(pFile);

  return ER_OK;
}
