/*-
 ***********************************************************************
 *
 * $Id: webjob.c,v 1.153 2012/05/01 06:13:07 mavrik Exp $
 *
 ***********************************************************************
 *
 * Copyright 2001-2012 The WebJob Project, All Rights Reserved.
 *
 ***********************************************************************
 */
#include "all-includes.h"
#ifdef USE_EMBEDDED_PERL
#include <EXTERN.h>
#include <perl.h>
#ifdef USE_EMBEDDED_PERL_XSUB
#include <XSUB.h>
#endif
#endif

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

#ifdef USE_EMBEDDED_PERL
/*-
 ***********************************************************************
 *
 * Glue code for embedded Perl (taken from perlembed.pod).
 *
 ***********************************************************************
 */
EXTERN_C void xs_init (pTHX);
EXTERN_C void boot_DynaLoader (pTHX_ CV *cv);

EXTERN_C void
xs_init(pTHX)
{
  char               *pcFile = __FILE__;

  newXS("DynaLoader::boot_DynaLoader", boot_DynaLoader, pcFile);
}
#endif


/*-
 ***********************************************************************
 *
 * Main
 *
 ***********************************************************************
 */
int
main(int iArgumentCount, char *ppcArgumentVector[])
{
  const char          acRoutine[] = "Main()";
  char                acLocalError[MESSAGE_SIZE] = "";
  int                 iError = ER_OK;
  int                 iErrorCount = 0;
  int                 iLastError = XER_OK;
  WEBJOB_PROPERTIES  *psProperties = NULL;

  /*-
   *********************************************************************
   *
   * Punch in and go to work.
   *
   *********************************************************************
   */
  iError = WebJobBootStrap(iArgumentCount, ppcArgumentVector, acLocalError);
  if (iError != ER_OK)
  {
    fprintf(stderr, "%s: %s\n", acRoutine, acLocalError);
    iLastError = XER_BootStrap; iErrorCount++;
    return WebJobShutdown(psProperties, iLastError, iErrorCount);
  }
  psProperties = WebJobGetPropertiesReference();

  /*-
   *********************************************************************
   *
   * Process command line arguments.
   *
   *********************************************************************
   */
  iError = WebJobProcessArguments(psProperties, iArgumentCount, ppcArgumentVector, acLocalError);
  if (iError != ER_OK)
  {
    fprintf(stderr, "%s: %s\n", acRoutine, acLocalError);
    iLastError = XER_ProcessArguments; iErrorCount++;
    return WebJobShutdown(psProperties, iLastError, iErrorCount);
  }

  /*-
   *********************************************************************
   *
   * Conditionally clone the properties structure and advance to the
   * second copy. If there are multiple config files, the first copy
   * of the properties structure is only used for cloning purposes.
   * Otherwise, it serves as the primary properties structure.
   *
   *********************************************************************
   */
  iError = WebJobCloneProperties(psProperties, acLocalError);
  if (iError != ER_OK)
  {
    fprintf(stderr, "%s: %s\n", acRoutine, acLocalError);
    iLastError = XER_CloneProperties; iErrorCount++;
    return WebJobShutdown(psProperties, iLastError, iErrorCount);
  }

  if (psProperties->psNext)
  {
    psProperties = psProperties->psNext;
  }

  /*-
   *********************************************************************
   *
   * Loop over the defined configurations until the minimum milestone
   * (iGetOk) is reached.
   *
   *********************************************************************
   */
for (/* empty */; psProperties; psProperties = psProperties->psNext)
{
  /*-
   *********************************************************************
   *
   * Reset the error count and last error. This is done since multiple
   * config files may be in play. Errors prior to this for-loop are
   * fatal, so there's no requirement to preserve them or increment
   * the counter. Also, when multiple config files are in play, the
   * overall success or failure of a given run is based on the last
   * config file used.
   *
   *********************************************************************
   */
  iErrorCount = iLastError = 0;

  /*-
   *********************************************************************
   *
   * Handle WEBJOB_GETMODE here and exit. This routine (i.e., main())
   * will need to be redesigned to handle several modes of operation.
   *
   *********************************************************************
   */
  if (psProperties->iRunMode == WEBJOB_GETMODE)
  {
    if (psProperties->pcConfigFile != NULL)
    {
      iError = PropertiesReadFile(psProperties->pcConfigFile, psProperties, acLocalError);
      if (iError != ER_OK)
      {
        fprintf(stderr, "%s: %s\n", acRoutine, acLocalError);
        if (psProperties->psNext)
        {
          fprintf(stderr, "%s: Failed to reach the required milestone. Trying the next configuration.\n", acRoutine);
          continue;
        }
        return XER_ReadProperties;
      }
    }
    iError = WebJobGetUrl(psProperties, acLocalError);
    if (iError != ER_OK)
    {
      fprintf(stderr, "%s: %s\n", acRoutine, acLocalError);
      if (psProperties->psNext)
      {
        fprintf(stderr, "%s: Failed to reach the required milestone. Trying the next configuration.\n", acRoutine);
        continue;
      }
      return XER_GetUrl;
    }
    return XER_OK;
  }

  /*-
   *********************************************************************
   *
   * Read the properties file.
   *
   *********************************************************************
   */
  iError = PropertiesReadFile(psProperties->pcConfigFile, psProperties, acLocalError);
  if (iError != ER_OK)
  {
    fprintf(stderr, "%s: %s\n", acRoutine, acLocalError);
    iLastError = XER_ReadProperties; iErrorCount++;
    if (psProperties->psNext)
    {
      fprintf(stderr, "%s: Failed to reach the required milestone. Trying the next configuration.\n", acRoutine);
      WebJobShutdown(psProperties, iLastError, iErrorCount);
      continue;
    }
    return WebJobShutdown(psProperties, iLastError, iErrorCount);
  }

  /*-
   *********************************************************************
   *
   * Check dependencies.
   *
   *********************************************************************
   */
  iError = WebJobCheckDependencies(psProperties, acLocalError);
  if (iError != ER_OK)
  {
    fprintf(stderr, "%s: %s\n", acRoutine, acLocalError);
    iLastError = XER_CheckDependencies; iErrorCount++;
    if (psProperties->psNext)
    {
      fprintf(stderr, "%s: Failed to reach the required milestone. Trying the next configuration.\n", acRoutine);
      WebJobShutdown(psProperties, iLastError, iErrorCount);
      continue;
    }
    return WebJobShutdown(psProperties, iLastError, iErrorCount);
  }

  /*-
   *********************************************************************
   *
   * Prepare for GET, RUN, and PUT stages.
   *
   *********************************************************************
   */
  iError = WebJobDoConfigure(psProperties, acLocalError);
  if (iError != ER_OK)
  {
    fprintf(stderr, "%s: %s\n", acRoutine, acLocalError);
    iLastError = XER_Configure; iErrorCount++;
    if (psProperties->psNext)
    {
      fprintf(stderr, "%s: Failed to reach the required milestone. Trying the next configuration.\n", acRoutine);
      WebJobShutdown(psProperties, iLastError, iErrorCount);
      continue;
    }
    return WebJobShutdown(psProperties, iLastError, iErrorCount);
  }

  /*-
   *********************************************************************
   *
   * Do GET stage (i.e., download the specified program).
   *
   *********************************************************************
   */
  iError = WebJobDoGetStage(psProperties, acLocalError);
  if (iError != ER_OK)
  {
    snprintf(psProperties->acGetError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    fprintf(stderr, "%s\n", psProperties->acGetError);
    iLastError = XER_GetStage; iErrorCount++;
    if (psProperties->psNext && !psProperties->iGetOk)
    {
      fprintf(stderr, "%s: Failed to reach the required milestone. Trying the next configuration.\n", acRoutine);
      WebJobShutdown(psProperties, iLastError, iErrorCount);
      continue;
    }
    if (psProperties->psPutUrl == NULL || (!psProperties->iGetOk && !psProperties->iUploadOnGetFailure))
    {
      return WebJobShutdown(psProperties, iLastError, iErrorCount);
    }
    psProperties->iRunStageDisabled = 1;
  }

  /*-
   *********************************************************************
   *
   * Do RUN stage (i.e., execute the specified program).
   *
   *********************************************************************
   */
  iError = WebJobDoRunStage(psProperties, acLocalError);
  if (iError != ER_OK)
  {
    snprintf(psProperties->acRunError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
#ifdef WIN32
    fprintf(stderr, "%s\n", psProperties->acRunError);
#else
    if (psProperties->sWEBJOB.iKidPid == 0)
    {
      fprintf(psProperties->sWEBJOB.pStdErr, "%s\n", psProperties->acRunError);
      return XER_Abort;
    }
    else
    {
      fprintf(stderr, "%s\n", psProperties->acRunError);
    }
#endif
    iLastError = XER_RunStage; iErrorCount++;
    if (psProperties->psPutUrl == NULL)
    {
      return WebJobShutdown(psProperties, iLastError, iErrorCount);
    }
  }

  /*-
   *********************************************************************
   *
   * Do PUT stage (i.e., conditionally upload output).
   *
   *********************************************************************
   */
  iError = WebJobDoPutStage(psProperties, acLocalError);
  if (iError != ER_OK)
  {
    snprintf(psProperties->acPutError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    fprintf(stderr, "%s\n", psProperties->acPutError);
    iLastError = XER_PutStage; iErrorCount++;
    return WebJobShutdown(psProperties, iLastError, iErrorCount);
  }

  /*-
   *********************************************************************
   *
   * The minimum milestone (iGetOk) was reached, so the current config
   * file and connection are considered good, and that means this loop
   * should be terminated. However, this implies nothing about whether
   * the downloaded job ran successfully or even ran at all.
   *
   *********************************************************************
   */
  break;
}

  /*-
   *********************************************************************
   *
   * Shutdown and go home.
   *
   *********************************************************************
   */
  return WebJobShutdown(psProperties, iLastError, iErrorCount);
}


/*-
 ***********************************************************************
 *
 * BuildCommandLine
 *
 ***********************************************************************
 */
char *
BuildCommandLine(int iArgumentCount, char *ppcArgumentVector[], int iQuoteArgument0)
{
  char               *pcCommandLine;
  int                 i;
  int                 iIndex;
  int                 iLength;

  for (i = iLength = 0; i < iArgumentCount; i++)
  {
    iLength += ((i > 0) ? 1 : 0) + strlen(ppcArgumentVector[i]);
  }
  if (iQuoteArgument0)
  {
    iLength += 2;
  }
  pcCommandLine = malloc(iLength + 1);
  if (pcCommandLine == NULL)
  {
    return NULL;
  }
  for (i = iIndex = 0; i < iArgumentCount; i++)
  {
    if (iIndex > iLength)
    {
      free(pcCommandLine);
      return NULL;
    }
    if (i == 0 && iQuoteArgument0)
    {
      pcCommandLine[iIndex++] = '"';
    }
    iIndex += snprintf(&pcCommandLine[iIndex], iLength + 1 - iIndex, "%s%s", (i > 0) ? " " : "", ppcArgumentVector[i]);
    if (i == 0 && iQuoteArgument0)
    {
      pcCommandLine[iIndex++] = '"';
    }
  }
  return pcCommandLine;
}


#ifdef WIN32
/*-
 ***********************************************************************
 *
 * FormatWin32Error
 *
 ***********************************************************************
 */
void
FormatWin32Error(DWORD dwError, char **ppcMessage)
{
  static char         acMessage[MESSAGE_SIZE];
  char               *pc;
  int                 i;
  int                 j;
  LPVOID              lpvMessage;

  acMessage[0] = 0;

  *ppcMessage = acMessage;

  FormatMessage(
    FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
    NULL,
    dwError,
    MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), /* default language */
    (LPTSTR) & lpvMessage,
    0,
    NULL
    );

  /*-
   *********************************************************************
   *
   * Replace linefeeds with spaces. Eliminate carriage returns.
   *
   *********************************************************************
   */
  for (i = 0, j = 0, pc = (char *) lpvMessage; (i < (int) strlen(lpvMessage)) && (i < (MESSAGE_SIZE)); i++, j++)
  {
    if (pc[i] == '\n')
    {
      acMessage[j] = ' ';
    }
    else if (pc[i] == '\r')
    {
      j--;
      continue;
    }
    else
    {
      acMessage[j] = pc[i];
    }
  }
  acMessage[j] = 0;

  LocalFree(lpvMessage);
}
#endif


/*-
 ***********************************************************************
 *
 * GetHostname
 *
 ***********************************************************************
 */
char *
GetHostname(void)
{
#define MAX_HOSTNAME_LENGTH 256
  static char         acHostname[MAX_HOSTNAME_LENGTH] = "";
#ifdef UNIX
  struct utsname      utsName;

  memset(&utsName, 0, sizeof(struct utsname));
  if (uname(&utsName) != -1)
  {
    snprintf(acHostname, MAX_HOSTNAME_LENGTH, "%s", (utsName.nodename[0]) ? utsName.nodename : "");
  }
#endif
#ifdef WIN32
  char                acTempname[MAX_HOSTNAME_LENGTH];
  DWORD               dwTempNameLength = sizeof(acTempname);

  if (GetComputerName(acTempname, &dwTempNameLength) == TRUE)
  {
    snprintf(acHostname, MAX_HOSTNAME_LENGTH, "%s", acTempname);
  }
#endif
  return acHostname;
}


/*-
 ***********************************************************************
 *
 * GetSystemOS
 *
 ***********************************************************************
 */
char *
GetSystemOS(void)
{
#define MAX_SYSTEMOS_LENGTH 256
  static char         acSystemOS[MAX_SYSTEMOS_LENGTH] = "NA";
#ifdef UNIX
  struct utsname      utsName;

  memset(&utsName, 0, sizeof(struct utsname));
  if (uname(&utsName) != -1)
  {
#ifdef WEBJOB_AIX
    snprintf(acSystemOS, MAX_SYSTEMOS_LENGTH, "%s %s %s.%s",
      utsName.machine,
      utsName.sysname,
      utsName.version,
      utsName.release
      );
#else
    snprintf(acSystemOS, MAX_SYSTEMOS_LENGTH, "%s %s %s",
      utsName.machine,
      utsName.sysname,
      utsName.release
      );
#endif
  }
#endif
#ifdef WIN32
  char                acOS[16];
  char                acPlatform[16];
  OSVERSIONINFO       osvi;
  SYSTEM_INFO         si;

  memset(&si, 0, sizeof(SYSTEM_INFO));
  GetSystemInfo(&si);
  switch (si.wProcessorArchitecture)
  {
  case PROCESSOR_ARCHITECTURE_INTEL:
    strncpy(acPlatform, "INTEL", sizeof(acPlatform));
    break;
  case PROCESSOR_ARCHITECTURE_MIPS:
    strncpy(acPlatform, "MIPS", sizeof(acPlatform));
    break;
  case PROCESSOR_ARCHITECTURE_ALPHA:
    strncpy(acPlatform, "ALPHA", sizeof(acPlatform));
    break;
  case PROCESSOR_ARCHITECTURE_PPC:
    strncpy(acPlatform, "PPC", sizeof(acPlatform));
    break;
  default:
    strncpy(acPlatform, "NA", sizeof(acPlatform));
    break;
  }

  memset(&osvi, 0, sizeof(OSVERSIONINFO));
  osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
  if (GetVersionEx(&osvi) == TRUE)
  {
    switch (osvi.dwPlatformId)
    {
    case VER_PLATFORM_WIN32s:
      strncpy(acOS, "Windows 3.1", sizeof(acOS));
      break;
    case VER_PLATFORM_WIN32_WINDOWS:
      strncpy(acOS, "Windows 98", sizeof(acOS));
      break;
    case VER_PLATFORM_WIN32_NT:
      strncpy(acOS, "Windows NT", sizeof(acOS));
      break;
    default:
      strncpy(acOS, "NA", sizeof(acOS));
      break;
    }

    snprintf(acSystemOS, MAX_SYSTEMOS_LENGTH, "%s %s %u.%u Build %u %s",
      acPlatform,
      acOS,
      osvi.dwMajorVersion,
      osvi.dwMinorVersion,
      osvi.dwBuildNumber,
      osvi.szCSDVersion
      );
  }
#endif
  return acSystemOS;
}


/*-
 ***********************************************************************
 *
 * HttpFinalizeCredentials
 *
 ***********************************************************************
 */
int
HttpFinalizeCredentials(HTTP_URL *psUrl, char *pcUser, char *pcPass, char *pcError)
{
  const char          acRoutine[] = "HttpFinalizeCredentials()";
  char                acLocalError[MESSAGE_SIZE] = "";
  int                 iError;

  psUrl->iAuthType = HTTP_AUTH_TYPE_BASIC;

  if (psUrl->pcUser[0] == 0 || strcasecmp(psUrl->pcUser, WEBJOB_USER_TOKEN) == 0)
  {
    if (pcUser[0])
    {
      iError = HttpSetUrlUser(psUrl, pcUser, acLocalError);
      if (iError != ER_OK)
      {
        snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
        return ER;
      }
    }
    else
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: Missing username for basic authentication", acRoutine);
      return ER;
    }
  }

  if (psUrl->pcPass[0] == 0 || strcasecmp(psUrl->pcPass, WEBJOB_PASS_TOKEN) == 0)
  {
    if (pcPass[0])
    {
      iError = HttpSetUrlPass(psUrl, pcPass, acLocalError);
      if (iError != ER_OK)
      {
        snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
        return ER;
      }
    }
    else if (HTTP_POOL_SEED && HTTP_POOL_TAPS > 1)
    {
      unsigned char aucPool[HTTP_POOL_SIZE];
      unsigned char aucTaps[HTTP_POOL_TAPS];
      HTTP_NEW_POOL(aucPool, HTTP_POOL_SIZE, HTTP_POOL_SEED);
      HTTP_TAP_POOL(aucTaps, aucPool);
      iError = HttpSetUrlPass(psUrl, (char *) aucTaps, acLocalError);
      if (iError != ER_OK)
      {
        snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
        return ER;
      }
    }
    else
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: Missing password for basic authentication", acRoutine);
      return ER;
    }
  }
  return ER_OK;
}


/*-
 ***********************************************************************
 *
 * HttpFinalizeProxyCredentials
 *
 ***********************************************************************
 */
int
HttpFinalizeProxyCredentials(HTTP_URL *psUrl, char *pcProxyUser, char *pcProxyPass, char *pcError)
{
  const char          acRoutine[] = "HttpFinalizeProxyCredentials()";
  char                acLocalError[MESSAGE_SIZE] = "";
  int                 iError;

  psUrl->iProxyAuthType = HTTP_AUTH_TYPE_BASIC;

  if (psUrl->pcProxyUser[0] == 0 || strcasecmp(psUrl->pcProxyUser, WEBJOB_PROXY_USER_TOKEN) == 0)
  {
    if (pcProxyUser[0])
    {
      iError = HttpSetUrlProxyUser(psUrl, pcProxyUser, acLocalError);
      if (iError != ER_OK)
      {
        snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
        return ER;
      }
    }
    else
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: Missing username for basic authentication", acRoutine);
      return ER;
    }
  }

  if (psUrl->pcProxyPass[0] == 0 || strcasecmp(psUrl->pcProxyPass, WEBJOB_PROXY_PASS_TOKEN) == 0)
  {
    if (pcProxyPass[0])
    {
      iError = HttpSetUrlProxyPass(psUrl, pcProxyPass, acLocalError);
      if (iError != ER_OK)
      {
        snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
        return ER;
      }
    }
    else
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: Missing password for basic authentication", acRoutine);
      return ER;
    }
  }
  return ER_OK;
}


/*-
 ***********************************************************************
 *
 * MakeNonce
 *
 ***********************************************************************
 */
char *
MakeNonce(char *pcError)
{
  const char          acRoutine[] = "MakeNonce()";
  char               *pcNonce = NULL;
  unsigned char       a = 0;
  unsigned char       b = 0;
  unsigned char       c = 0;
  unsigned char       d = 0;
  long                lValue = 0;

#ifdef WIN32
  lValue = (GetTickCount() << 16) | rand();
#else
  lValue = random();
#endif
  a = (unsigned char) ((lValue >> 24) & 0xff);
  b = (unsigned char) ((lValue >> 16) & 0xff);
  c = (unsigned char) ((lValue >>  8) & 0xff);
  d = (unsigned char) ((lValue >>  0) & 0xff);
  pcNonce = calloc(WEBJOB_NONCE_SIZE, 1);
  if (pcNonce == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: calloc(): %s", acRoutine, strerror(errno));
    return NULL;
  }
  snprintf(pcNonce, WEBJOB_NONCE_SIZE, "%02x%02x%02x%02x", a, b, c, d);
  return pcNonce;
}


/*-
 ***********************************************************************
 *
 * MakeRandomExtension
 *
 ***********************************************************************
 */
char *
MakeRandomExtension(void)
{
#define MAX_EXTENSION_LENGTH 9
  static char         acExtension[MAX_EXTENSION_LENGTH];
  unsigned char       a;
  unsigned char       b;
  unsigned char       c;
  unsigned char       d;
  long                lValue;

#ifdef WIN32
  lValue = (GetTickCount() << 16) | rand();
#else
  lValue = random();
#endif
  a = (unsigned char) ((lValue >> 24) & 0xff);
  b = (unsigned char) ((lValue >> 16) & 0xff);
  c = (unsigned char) ((lValue >>  8) & 0xff);
  d = (unsigned char) ((lValue >>  0) & 0xff);
  snprintf(acExtension, MAX_EXTENSION_LENGTH, "%02x%02x%02x%02x", a, b, c, d);
  return acExtension;
}


#ifdef USE_SSL
/*-
 ***********************************************************************
 *
 * SslCheckDependencies
 *
 ***********************************************************************
 */
int
SslCheckDependencies(SSL_PROPERTIES *psProperties, char *pcError)
{
  const char          acRoutine[] = "SslCheckDependencies()";

  if (psProperties->iUseCertificate)
  {
    if (psProperties->pcPublicCertFile == NULL || psProperties->pcPublicCertFile[0] == 0)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: Missing SslPublicCertFile.", acRoutine);
      return ER;
    }

    if (psProperties->pcPrivateKeyFile == NULL || psProperties->pcPrivateKeyFile[0] == 0)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: Missing SslPrivateKeyFile.", acRoutine);
      return ER;
    }
  }

  if (psProperties->iVerifyPeerCert)
  {
    if (psProperties->pcBundledCAsFile == NULL || psProperties->pcBundledCAsFile[0] == 0)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: Missing SslBundledCAsFile.", acRoutine);
      return ER;
    }

    if (psProperties->pcExpectedPeerCN == NULL || psProperties->pcExpectedPeerCN[0] == 0)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: Missing SslExpectedPeerCN.", acRoutine);
      return ER;
    }
  }

  return ER_OK;
}
#endif


/*-
 ***********************************************************************
 *
 * SeedRandom
 *
 ***********************************************************************
 */
int
SeedRandom(unsigned long ulTime1, unsigned long ulTime2, char *pcError)
{
  const char          acRoutine[] = "SeedRandom()";

  if (ulTime1 == (unsigned long) ~0 || ulTime2 == (unsigned long) ~0)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: Seed values are blatantly abnormal!: Time1 = [%08lx], Time2 = [%08lx]", acRoutine, ulTime1, ulTime2);
    return ER;
  }

#ifdef WIN32
    srand((unsigned long) (0xE97482AD ^ ((ulTime1 << 16) | (ulTime1 >> 16)) ^ ulTime2 ^ getpid() ^ GetTickCount()));
#else
  srandom((unsigned long) (0xE97482AD ^ ((ulTime1 << 16) | (ulTime1 >> 16)) ^ ulTime2 ^ getpid()));
#endif
  return ER_OK;
}


/*-
 ***********************************************************************
 *
 * WebJobBootStrap
 *
 ***********************************************************************
 */
int
WebJobBootStrap(int iArgumentCount, char *ppcArgumentVector[], char *pcError)
{
  const char          acRoutine[] = "WebJobBootStrap()";
  char                acLocalError[MESSAGE_SIZE] = "";
  char               *pcPathToSelf = NULL;
  int                 iError = 0;
  WEBJOB_PROPERTIES  *psProperties;

#ifdef WIN32
  /*-
   *********************************************************************
   *
   * Suppress critical-error-handler message boxes.
   *
   *********************************************************************
   */
  SetErrorMode(SEM_FAILCRITICALERRORS);
#endif

  /*-
   *********************************************************************
   *
   * Initialize Base64 lookup tables.
   *
   *********************************************************************
   */
  Base64BootStrap();

#ifdef USE_SSL
  /*-
   *********************************************************************
   *
   * Initialize SSL library, error strings, and random seed.
   *
   *********************************************************************
   */
  SslBoot();
#endif

#ifdef USE_DSV
  /*-
   *********************************************************************
   *
   * Initialize DSV/SSL-related crypto strings and algorithms.
   *
   *********************************************************************
   */
  ERR_load_crypto_strings();
  OpenSSL_add_all_algorithms();
#endif

  /*-
   *********************************************************************
   *
   * Get the path to this executable.
   *
   *********************************************************************
   */
  pcPathToSelf = WebJobGetPathToSelf(ppcArgumentVector[0], acLocalError);
  if (pcPathToSelf == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    return ER;
  }

  /*-
   *********************************************************************
   *
   * Conditionally clone this executable and exit.
   *
   *********************************************************************
   */
  iError = WebJobCloneSelf(pcPathToSelf, acLocalError);
  if (iError != ER_OK)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    return ER;
  }

  /*-
   *********************************************************************
   *
   * Allocate and initialize a new properties structure.
   *
   *********************************************************************
   */
  psProperties = WebJobNewProperties(acLocalError);
  if (psProperties == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    return ER;
  }
  WebJobSetPropertiesReference(psProperties);

  /*-
   *********************************************************************
   *
   * Set the path to this executable.
   *
   *********************************************************************
   */
  psProperties->pcPathToSelf = pcPathToSelf;

  /*-
   *********************************************************************
   *
   * Get the current working directory path or a handle to it.
   *
   *********************************************************************
   */
#ifdef WIN32
  if (GetCurrentDirectory(MAX_PATH + 1, psProperties->acCwd) == 0)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: GetCurrentDirectory(): Unable to get current directory.", acRoutine);
    return ER;
  }
#else
  if ((psProperties->psCwd = opendir(".")) == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: opendir(): %s", acRoutine, strerror(errno));
    return ER;
  }
#endif

  return ER_OK;
}


/*-
 ***********************************************************************
 *
 * WebJobCheckDependencies
 *
 ***********************************************************************
 */
int
WebJobCheckDependencies(WEBJOB_PROPERTIES *psProperties, char *pcError)
{
  const char          acRoutine[] = "WebJobCheckDependencies()";
#ifdef USE_SSL
  char                acLocalError[MESSAGE_SIZE] = "";
  int                 iError;
#endif

  /*-
   *********************************************************************
   *
   * The specified request must not include any path separators.
   *
   *********************************************************************
   */
  if (
       (strstr(psProperties->pcGetFile,  "/") != NULL) ||
       (strstr(psProperties->pcGetFile, "\\") != NULL)
     )
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: Program = [%s], Argument must not include any path separators.", acRoutine, psProperties->pcGetFile);
    return ER;
  }

  /*-
   *********************************************************************
   *
   * A GetUrl is required.
   *
   *********************************************************************
   */
  if (psProperties->psGetUrl == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: Missing UrlGetUrl key/value pair.", acRoutine);
    return ER;
  }

  /*-
   *********************************************************************
   *
   * A ClientId is required.
   *
   *********************************************************************
   */
  if (psProperties->acClientId[0] == 0)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: Missing ClientId key/value pair.", acRoutine);
    return ER;
  }

  /*-
   *********************************************************************
   *
   * Timeout range = [WEBJOB_MIN_TIME_LIMIT,WEBJOB_MAX_TIME_LIMIT]
   *
   *********************************************************************
   */
  if (psProperties->iGetTimeLimit < WEBJOB_MIN_TIME_LIMIT || psProperties->iGetTimeLimit > WEBJOB_MAX_TIME_LIMIT)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: GetTimeLimit = [%d], Value must be in the range [%d,%d].",
      acRoutine,
      psProperties->iGetTimeLimit,
      WEBJOB_MIN_TIME_LIMIT,
      WEBJOB_MAX_TIME_LIMIT
      );
    return ER;
  }
  if (psProperties->iRunTimeLimit < WEBJOB_MIN_TIME_LIMIT || psProperties->iRunTimeLimit > WEBJOB_MAX_TIME_LIMIT)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: RunTimeLimit = [%d], Value must be in the range [%d,%d].",
      acRoutine,
      psProperties->iRunTimeLimit,
      WEBJOB_MIN_TIME_LIMIT,
      WEBJOB_MAX_TIME_LIMIT
      );
    return ER;
  }
  if (psProperties->iPutTimeLimit < WEBJOB_MIN_TIME_LIMIT || psProperties->iPutTimeLimit > WEBJOB_MAX_TIME_LIMIT)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: PutTimeLimit = [%d], Value must be in the range [%d,%d].",
      acRoutine,
      psProperties->iPutTimeLimit,
      WEBJOB_MIN_TIME_LIMIT,
      WEBJOB_MAX_TIME_LIMIT
      );
    return ER;
  }

#ifdef USE_SSL
  /*-
   *********************************************************************
   *
   * Check SSL dependencies.
   *
   *********************************************************************
   */
  iError = SslCheckDependencies(psProperties->psSslProperties, acLocalError);
  if (iError != ER_OK)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    return ER;
  }
#endif

  /*-
   *********************************************************************
   *
   * GetHookCommandLine and GetHookStatus are required when GetHook is
   * enabled.
   *
   *********************************************************************
   */
  if (psProperties->psGetHook->iActive)
  {
    if (psProperties->psGetHook->pcOriginalCommandLine == NULL || psProperties->psGetHook->pcOriginalCommandLine[0] == 0)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: Missing GetHookCommandLine key/value pair.", acRoutine);
      return ER;
    }

    if (psProperties->psGetHook->iTargetStatus == -1)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: Missing GetHookStatus key/value pair.", acRoutine);
      return ER;
    }
  }

  /*-
   *********************************************************************
   *
   * UrlGetProxyHost is required when UrlGetProxyEnable is enabled.
   *
   *********************************************************************
   */
  if (psProperties->iUrlGetProxyEnable)
  {
    if (psProperties->acUrlGetProxyHost[0] == 0)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: Missing UrlGetProxyHost key/value pair.", acRoutine);
      return ER;
    }
  }

  /*-
   *********************************************************************
   *
   * UrlPutProxyHost is required when UrlPutProxyEnable is enabled.
   *
   *********************************************************************
   */
  if (psProperties->iUrlPutProxyEnable)
  {
    if (psProperties->acUrlPutProxyHost[0] == 0)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: Missing UrlPutProxyHost key/value pair.", acRoutine);
      return ER;
    }
  }

  return ER_OK;
}


/*-
 ***********************************************************************
 *
 * WebJobCloneProperties
 *
 ***********************************************************************
 */
int
WebJobCloneProperties(WEBJOB_PROPERTIES *psProperties, char *pcError)
{
  const char          acRoutine[] = "WebJobCloneProperties()";
  char                acSeparators[] = ",";
  char               *pcConfigFile = NULL;
  char               *pcConfigList = NULL;
  char               *pcConfigNext = NULL;
  int                 iLength = 0;
  int                 iStdins = 0;
  WEBJOB_PROPERTIES  *psTempProperties;
  WEBJOB_PROPERTIES  *psNextProperties;

  /*-
   *********************************************************************
   *
   * Clone the properties structure for each file in the config list.
   *
   *********************************************************************
   */
  if (psProperties->pcConfigList != NULL && strstr(psProperties->pcConfigList, ",") != NULL)
  {
    iLength = strlen(psProperties->pcConfigList);
    pcConfigList = malloc(iLength + 1);
    if (pcConfigList == NULL)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: malloc(): %s", acRoutine, strerror(errno));
      return ER;
    }
    strncpy(pcConfigList, psProperties->pcConfigList, iLength + 1);

    psTempProperties = psProperties;
    for (pcConfigNext = strtok(pcConfigList, acSeparators); pcConfigNext; pcConfigNext = strtok(NULL, acSeparators))
    {
      if (strcmp(pcConfigNext, "-") == 0 && ++iStdins > 1)
      {
        snprintf(pcError, MESSAGE_SIZE, "%s: Only one config file may be specified as stdin (i.e., '-').", acRoutine);
        return ER;
      }
      iLength = strlen(pcConfigNext);
      pcConfigFile = malloc(iLength + 1);
      if (pcConfigFile == NULL)
      {
        snprintf(pcError, MESSAGE_SIZE, "%s: malloc(): %s", acRoutine, strerror(errno));
        return ER;
      }
      strncpy(pcConfigFile, pcConfigNext, iLength + 1);
      psNextProperties = (WEBJOB_PROPERTIES *) calloc(sizeof(WEBJOB_PROPERTIES), 1);
      if (psNextProperties == NULL)
      {
        snprintf(pcError, MESSAGE_SIZE, "%s: calloc(): %s", acRoutine, strerror(errno));
        return ER;
      }
      memcpy(psNextProperties, psProperties, sizeof(WEBJOB_PROPERTIES));
      psNextProperties->pcConfigFile = pcConfigFile;
      psNextProperties->psNext = NULL;
      psTempProperties->psNext = psNextProperties;
      psTempProperties = psNextProperties;
    }
    free(pcConfigList);
  }
  else
  {
    psProperties->pcConfigFile = psProperties->pcConfigList;
  }

  return ER_OK;
}


/*-
 ***********************************************************************
 *
 * WebJobCloneSelf
 *
 ***********************************************************************
 */
int
WebJobCloneSelf(char *pcPathToSelf, char *pcError)
{
  const char          acRoutine[] = "WebJobCloneSelf()";
  char                acLocalError[MESSAGE_SIZE] = "";
  char               *pc = NULL;
  char               *pcPathToMain = NULL;
  int                 iError = 0;
  int                 iLength = 0;
  struct stat         statMain = { 0 };
  struct stat         statSelf = { 0 };

  /*-
   *********************************************************************
   *
   * Make sure we exist.
   *
   *********************************************************************
   */
  if (stat(pcPathToSelf, &statSelf) == -1)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: stat(): File = [%s], %s", acRoutine, pcPathToSelf, strerror(errno));
    return ER;
  }

  /*-
   *********************************************************************
   *
   * Make a copy of the path to self.
   *
   *********************************************************************
   */
  iError = HookSetDynamicString(&pcPathToMain, pcPathToSelf, acLocalError);
  if (iError != ER_OK)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    return ER;
  }

  /*-
   *********************************************************************
   *
   * Scan backwards until the first path separator.
   *
   *********************************************************************
   */
  iLength = strlen(pcPathToSelf);
  while (iLength && (pcPathToSelf[iLength - 1] != WEBJOB_SLASHCHAR))
  {
    iLength--;
  }
  pc = &pcPathToSelf[iLength];

  /*-
   *********************************************************************
   *
   * Finalize the main path. Only true clones are allowed to continue.
   *
   *********************************************************************
   */
#ifdef WIN32
  if (strcmp(pc, "webjob-clone.exe") != 0)
  {
    free(pcPathToMain);
    return ER_OK;
  }
  iLength = strlen(pcPathToMain) - strlen("-clone.exe");
  pcPathToMain[iLength++] = '.';
  pcPathToMain[iLength++] = 'e';
  pcPathToMain[iLength++] = 'x';
  pcPathToMain[iLength++] = 'e';
#else
  if (strcmp(pc, "webjob-clone") != 0)
  {
    free(pcPathToMain);
    return ER_OK;
  }
  iLength = strlen(pcPathToMain) - strlen("-clone");
#endif
  pcPathToMain[iLength] = 0;

  /*-
   *********************************************************************
   *
   * Clone self to main, but only if main does not exist.
   *
   *********************************************************************
   */
  iError = ER_OK;
  if (stat(pcPathToMain, &statMain) == -1)
  {
    if (errno == ENOENT)
    {
      fprintf(stdout, "Main executable is missing -- clone operation invoked.\n");
      if (WebJobCopyFile(pcPathToSelf, pcPathToMain, acLocalError) != ER_OK)
      {
        snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
        iError = ER;
      }
      else
      {
#ifdef UNIX
        statSelf.st_mode |= S_IXUSR | S_IXGRP | S_IXOTH;
#endif
        if (chmod(pcPathToMain, statSelf.st_mode) == -1)
        {
          snprintf(pcError, MESSAGE_SIZE, "%s: chmod(): File = [%s], %s", acRoutine, pcPathToMain, strerror(errno));
          iError = ER;
        }
      }
    }
    else
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: stat(): File = [%s], %s", acRoutine, pcPathToMain, strerror(errno));
      iError = ER;
    }
  }
  else
  {
    fprintf(stdout, "Main executable is present -- clone operation skipped.\n");
  }

  /*-
   *********************************************************************
   *
   * If the operation was successful, shutdown and go home. Otherwise,
   * return an error.
   *
   *********************************************************************
   */
  free(pcPathToMain);

  if (iError == ER_OK)
  {
    exit(XER_OK);
  }

  return ER;
}


/*-
 ***********************************************************************
 *
 * WebJobCopyFile
 *
 ***********************************************************************
 */
int
WebJobCopyFile(char *pcSourceFile, char *pcTargetFile, char *pcError)
{
  const char          acRoutine[] = "WebJobCopyFile()";
  FILE               *pSourceFile = NULL;
  FILE               *pTargetFile = NULL;
  int                 iCopyError = 0;
  int                 iNRead = 0;
  int                 iNWritten = 0;
  unsigned char       aucData[WEBJOB_READ_SIZE] = "";

  pSourceFile = fopen(pcSourceFile, "rb");
  if (pSourceFile == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: fopen(): File = [%s], %s", acRoutine, pcSourceFile, strerror(errno));
    return ER;
  }

  pTargetFile = fopen(pcTargetFile, "wb");
  if (pTargetFile == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: fopen(): File = [%s], %s", acRoutine, pcTargetFile, strerror(errno));
    return ER;
  }

  while (!feof(pSourceFile))
  {
    iNRead = fread(aucData, 1, WEBJOB_READ_SIZE, pSourceFile);
    if (iNRead < WEBJOB_READ_SIZE && ferror(pSourceFile))
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: fread(): File = [%s], %s", acRoutine, pcSourceFile, strerror(errno));
      iCopyError = 1;
      break;
    }
    iNWritten = fwrite(aucData, 1, iNRead, pTargetFile);
    if (iNWritten != iNRead)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: fwrite(): File = [%s], %s", acRoutine, pcTargetFile, strerror(errno));
      iCopyError = 1;
      break;
    }
  }
  fclose(pSourceFile);
  fclose(pTargetFile);

  if (iCopyError)
  {
    unlink(pcTargetFile);
    return ER;
  }

  return ER_OK;
}


/*-
 ***********************************************************************
 *
 * WebJobDecodeValue
 *
 ***********************************************************************
 */
char *
WebJobDecodeValue(char *pcEncoded, char *pcError)
{
  const char          acRoutine[] = "WebJobDecodeValue()";
  char               *pc = NULL;
  char               *pcBase64 = NULL;
  char               *pcDecoded = NULL;
  char               *pcDecode1 = NULL;
  char               *pcDecode2 = NULL;
  char               *pcEnd = NULL;
  char               *pcScheme = NULL;
  int                 iLength = 0;
  int                 iScheme = 0;
  int                 n = 0;
  unsigned char       aucPool[HTTP_POOL_SIZE];
  unsigned long       ulTap = 0;
  unsigned long       ulState = 0;

  /*-
   *********************************************************************
   *
   * Allocate a work buffer, and make a copy the input value.
   *
   *********************************************************************
   */
  iLength = strlen(pcEncoded);
  pcDecode1 = calloc(iLength + 1, 1);
  if (pcDecode1 == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: calloc(): %s", acRoutine, strerror(errno));
    return NULL;
  }
  strncpy(pcDecode1, pcEncoded, iLength + 1);
  pcDecoded = pcDecode1;

  /*-
   *********************************************************************
   *
   * If the string doesn't have the expected format, assume it's just
   * a plain old password and return it to the caller without further
   * ado. The expected format is as follows:
   *
   *   <scheme-id>:<base64-value>
   *
   * where <scheme-id> is a decimal number that identifies the scheme
   * used to encode the password and <base64-value> is a string which
   * represents the result of that transformation.
   *
   *********************************************************************
   */
  if (strstr(pcDecode1, ":") == NULL)
  {
    return pcDecoded;
  }
  pcScheme = strtok(pcDecode1, ":");

  /*-
   *********************************************************************
   *
   * Determine the encoding scheme, base64-decode the value, apply the
   * inverse transformation, and return the result.
   *
   *********************************************************************
   */
  iScheme = strtoul(pcScheme, &pcEnd, 10);
  if (*pcEnd != 0 || errno == ERANGE)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: Unable to convert scheme identifier to a decimal number.", acRoutine);
    free(pcDecode1);
    return NULL;
  }

  pcDecode2 = calloc(iLength + 1, 1);
  if (pcDecode2 == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: calloc(): %s", acRoutine, strerror(errno));
    free(pcDecode1);
    return NULL;
  }

  pcBase64 = pcScheme + strlen(pcScheme) + 1;
  if (Base64Decode(pcBase64, (unsigned char *) pcDecode2, strlen(pcBase64)) == ER)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: Base64Decode(): Unable to base64-decode the value.", acRoutine);
    free(pcDecode1);
    free(pcDecode2);
    return NULL;
  }

  free(pcDecode1);

  switch (iScheme)
  {
  case 0:
    pcDecoded = pcDecode2;
    break;
  case 1:
    pc = strstr(pcDecode2, ":");
    if (pc == NULL)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: Invalid format for the specified encoding scheme.", acRoutine);
      free(pcDecode2);
      return NULL;
    }
    *pc++ = 0;
    ulState = strtoul(pcDecode2, &pcEnd, 16);
    if (*pcEnd != 0 || errno == ERANGE || ulState == 0)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: Invalid seed or value could not be converted to a number.", acRoutine);
      free(pcDecode2);
      return NULL;
    }
    pcDecode1 = pc;
    HTTP_NEW_POOL(aucPool, HTTP_POOL_SIZE, ulState);
    pcDecoded = calloc(iLength + 1, 1);
    if (pcDecoded == NULL)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: calloc(): %s", acRoutine, strerror(errno));
      free(pcDecode2);
      return NULL;
    }
    for (pc = strtok(pcDecode1, ","); pc != NULL; pc = strtok(NULL, ","))
    {
      pcEnd = NULL;
      ulTap = strtoul(pc, &pcEnd, 10);
      if (*pcEnd != 0 || errno == ERANGE || ulTap < 0 || ulTap > HTTP_POOL_SIZE)
      {
        snprintf(pcError, MESSAGE_SIZE, "%s: Invalid tap or value could not be converted to a number.", acRoutine);
        free(pcDecode2);
        free(pcDecoded);
        return NULL;
      }
      n += sprintf(&pcDecoded[n], "%c", aucPool[ulTap]);
    }
    pcDecoded[n] = 0;
    free(pcDecode2);
    break;
  default:
    snprintf(pcError, MESSAGE_SIZE, "%s: Invalid encoding scheme (%d).", acRoutine, iScheme);
    free(pcDecode2);
    return NULL;
    break;
  }

  return pcDecoded;
}


/*-
 ***********************************************************************
 *
 * WebJobDoConfigure
 *
 ***********************************************************************
 */
int
WebJobDoConfigure(WEBJOB_PROPERTIES *psProperties, char *pcError)
{
  const char          acRoutine[] = "WebJobDoConfigure()";
  char                acLocalError[MESSAGE_SIZE] = "";
  char               *pcTemp = NULL;
  char               *pcValue = NULL;
  int                 i = 0;
  int                 iError = 0;
  int                 iLength = 0;
#ifdef UNIX
  struct sigaction    signalAlarm;
#endif

  /*-
   *********************************************************************
   *
   * Change to the TempDirectory, and get/save the full path.
   *
   *********************************************************************
   */
  iError = chdir((const char *) psProperties->acTempDirectory);
  if (iError != ER_OK)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: chdir(): Directory = [%s], %s", acRoutine, psProperties->acTempDirectory, strerror(errno));
    return ER;
  }

  psProperties->pcTempDirectory = getcwd(NULL, WEBJOB_MAX_PATH);
  if (psProperties->pcTempDirectory == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: getcwd(): Directory = [%s], %s", acRoutine, psProperties->acTempDirectory, strerror(errno));
    return ER;
  }

#ifdef UNIX
  /*-
   *********************************************************************
   *
   * Establish a timeout signal handler, if required.
   *
   *********************************************************************
   */
  if (psProperties->iGetTimeLimit || psProperties->iRunTimeLimit || psProperties->iPutTimeLimit)
  {
    signalAlarm.sa_handler = WebJobTimeoutHandler;
    sigemptyset(&signalAlarm.sa_mask);
    signalAlarm.sa_flags = 0;
    iError = sigaction(SIGALRM, &signalAlarm, NULL);
    if (iError == -1)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: sigaction(): WebJobTimeoutHandler, %s", acRoutine, strerror(errno));
      return ER;
    }
  }
#endif

  /*-
   *********************************************************************
   *
   * Finalize priority. A value specified in the environment trumps
   * one specified in a config file.
   *
   *********************************************************************
   */
  pcValue = WebJobGetEnvValue("WEBJOB_PRIORITY");
  if (pcValue && strlen(pcValue) < WEBJOB_MAX_PRIORITY_LENGTH)
  {
    strncpy(psProperties->acPriority, pcValue, WEBJOB_MAX_PRIORITY_LENGTH);
  }

  if (psProperties->acPriority[0])
  {
    if (strcasecmp(psProperties->acPriority, "LOW") == 0)
    {
      psProperties->iPriority = WEBJOB_PRIORITY_LOW;
    }
    else if (strcasecmp(psProperties->acPriority, "BELOW_NORMAL") == 0)
    {
      psProperties->iPriority = WEBJOB_PRIORITY_BELOW_NORMAL;
    }
    else if (strcasecmp(psProperties->acPriority, "NORMAL") == 0)
    {
      psProperties->iPriority = WEBJOB_PRIORITY_NORMAL;
    }
    else if (strcasecmp(psProperties->acPriority, "ABOVE_NORMAL") == 0)
    {
      psProperties->iPriority = WEBJOB_PRIORITY_ABOVE_NORMAL;
    }
    else if (strcasecmp(psProperties->acPriority, "HIGH") == 0)
    {
      psProperties->iPriority = WEBJOB_PRIORITY_HIGH;
    }
    else
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: Priority (%s) must be one of [low|below_normal|normal|above_normal|high].", acRoutine, psProperties->acPriority);
      return ER;
    }
    for (i = 0; i < (int) strlen(psProperties->acPriority); i++)
    {
      psProperties->acPriority[i] = tolower(psProperties->acPriority[i]);
    }
  }

  /*-
   *********************************************************************
   *
   * Conditionally drop the PUT URL.
   *
   *********************************************************************
   */
  if (psProperties->psPutUrl != NULL && psProperties->iNoUpload)
  {
    HttpFreeUrl(psProperties->psPutUrl);
    psProperties->psPutUrl = NULL;
  }

#ifdef USE_SSL
  /*-
   *********************************************************************
   *
   * Initialize SSL Context.
   *
   *********************************************************************
   */
  if (
       (psProperties->psGetUrl != NULL && psProperties->psGetUrl->iScheme == HTTP_SCHEME_HTTPS) ||
       (psProperties->psPutUrl != NULL && psProperties->psPutUrl->iScheme == HTTP_SCHEME_HTTPS)
     )
  {
    if (SslInitializeCTX(psProperties->psSslProperties, acLocalError) == NULL)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
      return ER;
    }
    if (psProperties->psGetUrl != NULL && psProperties->psGetUrl->iScheme == HTTP_SCHEME_HTTPS)
    {
      psProperties->psGetUrl->psSslProperties = psProperties->psSslProperties;
    }
    if (psProperties->psPutUrl != NULL && psProperties->psPutUrl->iScheme == HTTP_SCHEME_HTTPS)
    {
      psProperties->psPutUrl->psSslProperties = psProperties->psSslProperties;
    }
  }
#endif

  /*-
   *********************************************************************
   *
   * Seed the random number generator, and generate a nonce.
   *
   *********************************************************************
   */
  WebJobGetEpoch(&psProperties->tvSRGEpoch);
  iError = SeedRandom((unsigned long) psProperties->tvJobEpoch.tv_usec, (unsigned long) psProperties->tvSRGEpoch.tv_usec, acLocalError);
  if (iError == -1)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    return ER;
  }

  psProperties->pcNonce = MakeNonce(acLocalError);
  if (psProperties->pcNonce == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    return ER;
  }

  /*-
   *********************************************************************
   *
   * Initialize the WorkDirectory and DoneDirectory variables. These
   * paths can't be set until the nonce is generated, and the nonce,
   * in turn, depends on seeding the pseudo-random number generator.
   *
   *********************************************************************
   */
  iLength =
    strlen(psProperties->pcTempDirectory) +
    strlen(WEBJOB_SLASH) +
    strlen(PROGRAM_NAME) +
    strlen("_") +
    strlen("4294967295") +
    strlen("_") +
    strlen(psProperties->pcNonce) +
    strlen(".d") +
    1;

  psProperties->pcWorkDirectory = calloc(iLength, 1);
  if (psProperties->pcWorkDirectory == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: calloc(): %s", acRoutine, strerror(errno));
    return ER;
  }
  snprintf(psProperties->pcWorkDirectory, iLength, "%s%s%s_%ld_%s",
    psProperties->pcTempDirectory,
    WEBJOB_SLASH,
    PROGRAM_NAME,
    (long) psProperties->tvJobEpoch.tv_sec,
    psProperties->pcNonce
    );

  psProperties->pcDoneDirectory = calloc(iLength, 1);
  if (psProperties->pcDoneDirectory == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: calloc(): %s", acRoutine, strerror(errno));
    return ER;
  }
  snprintf(psProperties->pcDoneDirectory, iLength, "%s.d", psProperties->pcWorkDirectory);

  /*-
   *********************************************************************
   *
   * Create the WorkDirectory and change to it.
   *
   *********************************************************************
   */
#ifdef WIN32
  iError = mkdir((const char *) psProperties->pcWorkDirectory);
#else
  iError = mkdir((const char *) psProperties->pcWorkDirectory, 0700);
#endif
  if (iError != ER_OK)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: mkdir(): Directory = [%s], %s", acRoutine, psProperties->pcWorkDirectory, strerror(errno));
    return ER;
  }
  psProperties->iWorkDirectoryCreated = 1;

  iError = chdir((const char *) psProperties->pcWorkDirectory);
  if (iError != ER_OK)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: chdir(): Directory = [%s], %s", acRoutine, psProperties->pcWorkDirectory, strerror(errno));
    return ER;
  }

#ifdef UNIX
  /*-
   *********************************************************************
   *
   * Prepare pipe handles.
   *
   *********************************************************************
   */
  iError = pipe(psProperties->sWEBJOB.iPipe);
  if (iError == -1)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: pipe(): %s", acRoutine, strerror(errno));
    return ER;
  }
#endif

  /*-
   *********************************************************************
   *
   * Prepare output file handles.
   *
   *********************************************************************
   */
  iError = WebJobPrepareHandles(psProperties, acLocalError);
  if (iError != ER_OK)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    return ER;
  }

#ifdef USE_DSV
  /*-
   *********************************************************************
   *
   * Finalize DSV configuration, if necessary.
   *
   *********************************************************************
   */
  if (psProperties->iDsvVerifySignature)
  {
    psProperties->psDsvCertList = DsvLoadPublicKeys(psProperties->acDsvCertificateTree, DSV_FILE_TYPE_CERT, acLocalError);
    if (psProperties->psDsvCertList == NULL)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
      return ER;
    }
    psProperties->psDsvGetSignature = DsvNewSignature(acLocalError);
    if (psProperties->psDsvGetSignature == NULL)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
      return ER;
    }
    psProperties->psDsvPutSignature = DsvNewSignature(acLocalError);
    if (psProperties->psDsvPutSignature == NULL)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
      return ER;
    }
  }
#endif

  /*-
   *********************************************************************
   *
   * Finalize GetHook configuration, if necessary.
   *
   *********************************************************************
   */
  if (psProperties->psGetHook->iActive)
  {
    iLength = strlen(psProperties->pcGetFile) + strlen(psProperties->psGetHook->pcSuffix);
    pcTemp = malloc(iLength + 1);
    if (pcTemp == NULL)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: malloc(): %s", acRoutine, strerror(errno));
      return ER;
    }
    snprintf(pcTemp, iLength + 1, "%s%s", psProperties->pcGetFile, psProperties->psGetHook->pcSuffix);

    iError = HookSetValue(psProperties->psGetHook, HOOK_VALUE_MODIFIED_FILENAME, pcTemp, acLocalError);
    if (iError != ER_OK)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
      free(pcTemp);
      return ER;
    }
    free(pcTemp);

    psProperties->pcGetFile = psProperties->psGetHook->pcModifiedFilename;

    pcTemp = HookExpandCommandLine(psProperties->psGetHook, acLocalError);
    if (pcTemp == NULL)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
      return ER;
    }

    iError = HookSetValue(psProperties->psGetHook, HOOK_VALUE_EXPANDED_COMMAND_LINE, pcTemp, acLocalError);
    if (iError != ER_OK)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
      free(pcTemp);
      return ER;
    }
    free(pcTemp);
  }

  /*-
   *********************************************************************
   *
   * Export environment variables.
   *
   *********************************************************************
   */
{ /* These braces are needed to declare/initialize asKeyValuePairs here. */
  WEBJOB_EXPORT_LIST asKeyValuePairs[] =
  {
    { "WEBJOB_CFG_FILE", psProperties->pcConfigFile },
    { "WEBJOB_CLIENTID", psProperties->acClientId },
    { "WEBJOB_HOSTNAME", psProperties->pcHostname },
    { "WEBJOB_QUEUETAG", ""}, /* Note: This variable must be cleared to prevent descendants from requesting the same job. */
    { "WEBJOB_TEMP_DIRECTORY", psProperties->pcTempDirectory },
    { "WEBJOB_WORK_DIRECTORY", psProperties->pcWorkDirectory }
  };
  iError = WebJobExportEnvironment(asKeyValuePairs, (sizeof(asKeyValuePairs) / sizeof(asKeyValuePairs[0])), acLocalError);
  if (iError != ER_OK)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    return ER;
  }
}

  return ER_OK;
}


#if defined(USE_EMBEDDED_LUA) || defined(USE_EMBEDDED_PERL)
/*-
 ***********************************************************************
 *
 * WebJobDoConfigureEmbedded
 *
 ***********************************************************************
 */
int
WebJobDoConfigureEmbedded(WEBJOB_PROPERTIES *psProperties, char *pcError)
{
  const char          acRoutine[] = "WebJobDoConfigureEmbedded()";
#ifdef USE_EMBEDDED_LUA
#define WEBJOB_LUA_MAGIC "#!webjob-lua"
#define WEBJOB_LUA_MAGIC_SIZE 13
  char                acData[WEBJOB_LUA_MAGIC_SIZE] = { 0 };
  char               *ppcPreamble[3] = { "webjob", "-r", "lua" };
#endif
#ifdef USE_EMBEDDED_PERL
#define WEBJOB_PERL_MAGIC "#!webjob-perl"
#define WEBJOB_PERL_MAGIC_SIZE 14
#define WEBJOB_SH_PERL_MAGIC_1 "#!/bin/sh\n#!webjob-perl"
#define WEBJOB_SH_PERL_MAGIC_1_SIZE 24
#define WEBJOB_SH_PERL_MAGIC_2 "#!/bin/sh\r\n#!webjob-perl"
#define WEBJOB_SH_PERL_MAGIC_2_SIZE 25
  char                acData[WEBJOB_SH_PERL_MAGIC_2_SIZE] = { 0 };
  char               *ppcPreamble[4] = { "webjob", "-r", "perl", "-x" };
#endif
  FILE               *pFile = NULL;
  int                 i = 0;
  int                 iNRead = 0;
  int                 iPreambleCount = 0;

  /*-
   *********************************************************************
   *
   * Note that the GetHook, if enabled, may create/modify the target
   * program. Therefore, this routine should not be executed until the
   * hook has finished. This should always be the case since the hook
   * is called during the GET stage.
   *
   *********************************************************************
   */

  /*-
   *********************************************************************
   *
   * Open the target program, and look for a supported magic value. If
   * one is found, initialize the embedded variables. If the read
   * returns fewer bytes than required or no magic value is found,
   * just fall through.
   *
   *********************************************************************
   */
  pFile = fopen(psProperties->sWEBJOB.pcCommand, "rb");
  if (pFile == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: fopen(): File = [%s], %s", acRoutine, psProperties->sWEBJOB.pcCommand, strerror(errno));
    return ER;
  }

#ifdef USE_EMBEDDED_LUA
  iNRead = fread(acData, 1, WEBJOB_LUA_MAGIC_SIZE, pFile);
  if (ferror(pFile))
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: fread(): File = [%s], Unexpected read error.", acRoutine, psProperties->sWEBJOB.pcCommand);
    fclose(pFile);
    return ER;
  }
  fclose(pFile);
  if (iNRead == WEBJOB_LUA_MAGIC_SIZE && strncmp(acData, WEBJOB_LUA_MAGIC, WEBJOB_LUA_MAGIC_SIZE - 1) == 0)
  {
    iPreambleCount = 3;
  }
  else
  {
    return ER_OK; /* Unrecognized magic. Let it go. */
  }
#endif
#ifdef USE_EMBEDDED_PERL
  iNRead = fread(acData, 1, WEBJOB_SH_PERL_MAGIC_2_SIZE, pFile);
  if (ferror(pFile))
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: fread(): File = [%s], Unexpected read error.", acRoutine, psProperties->sWEBJOB.pcCommand);
    fclose(pFile);
    return ER;
  }
  fclose(pFile);
  if (iNRead == WEBJOB_SH_PERL_MAGIC_2_SIZE && strncmp(acData, WEBJOB_SH_PERL_MAGIC_2, WEBJOB_SH_PERL_MAGIC_2_SIZE - 1) == 0)
  {
    iPreambleCount = 4;
  }
  else if (iNRead >= WEBJOB_SH_PERL_MAGIC_1_SIZE && strncmp(acData, WEBJOB_SH_PERL_MAGIC_1, WEBJOB_SH_PERL_MAGIC_1_SIZE - 1) == 0)
  {
    iPreambleCount = 4;
  }
  else if (iNRead >= WEBJOB_PERL_MAGIC_SIZE && strncmp(acData, WEBJOB_PERL_MAGIC, WEBJOB_PERL_MAGIC_SIZE - 1) == 0)
  {
    iPreambleCount = 3;
  }
  else
  {
    return ER_OK; /* Unrecognized magic. Let it go. */
  }
#endif

  psProperties->sWEBJOB.iEmbeddedArgumentCount = psProperties->sWEBJOB.iArgumentCount + iPreambleCount;
  psProperties->sWEBJOB.ppcEmbeddedArguments = malloc(sizeof(char **) * (psProperties->sWEBJOB.iEmbeddedArgumentCount + 1));
  if (psProperties->sWEBJOB.ppcEmbeddedArguments == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: malloc(): %s", acRoutine, strerror(errno));
    return ER;
  }
  psProperties->sWEBJOB.ppcEmbeddedArguments[0] = psProperties->pcPathToSelf;
  psProperties->sWEBJOB.ppcEmbeddedArguments[1] = ppcPreamble[1];
  psProperties->sWEBJOB.ppcEmbeddedArguments[2] = ppcPreamble[2];
  if (iPreambleCount == 4)
  {
    psProperties->sWEBJOB.ppcEmbeddedArguments[3] = ppcPreamble[3];
  }
  for (i = 0; i < psProperties->sWEBJOB.iArgumentCount; i++)
  {
    psProperties->sWEBJOB.ppcEmbeddedArguments[i + iPreambleCount] = psProperties->sWEBJOB.ppcArguments[i];
  }
  psProperties->sWEBJOB.ppcEmbeddedArguments[i + iPreambleCount] = NULL;
  psProperties->sWEBJOB.pcEmbeddedCommand = psProperties->sWEBJOB.ppcEmbeddedArguments[0];
  psProperties->sWEBJOB.pcEmbeddedCommandLine = BuildCommandLine(psProperties->sWEBJOB.iEmbeddedArgumentCount, psProperties->sWEBJOB.ppcEmbeddedArguments, 1);
  if (psProperties->sWEBJOB.pcEmbeddedCommandLine == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: Unable to construct the embedded command line.", acRoutine);
    return ER;
  }
  psProperties->iRunEmbedded = 1;

  return ER_OK;
}
#endif


/*-
 ***********************************************************************
 *
 * WebJobDoGetHook
 *
 ***********************************************************************
 */
int
WebJobDoGetHook(WEBJOB_PROPERTIES *psProperties, char *pcError)
{
  const char          acRoutine[] = "WebJobDoGetHook()";
  char                acLocalError[MESSAGE_SIZE] = "";
  int                 iError;
#ifdef UNIX
  struct stat         statEntry;
#endif

  iError = HookRunSystemCommand(psProperties->psGetHook, acLocalError);
  if (iError != ER_OK)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    return ER;
  }

#ifdef UNIX
  if (stat(psProperties->sWEBJOB.pcCommand, &statEntry) == -1)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: stat(): %s", acRoutine, strerror(errno));
    return ER;
  }
  if ((statEntry.st_mode & S_IXUSR) != S_IXUSR)
  {
    statEntry.st_mode |= S_IXUSR;
    if (chmod(psProperties->sWEBJOB.pcCommand, statEntry.st_mode) == -1)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: chmod(): %s", acRoutine, strerror(errno));
      return ER;
    }
  }
#endif

  return ER_OK;
}


/*-
 ***********************************************************************
 *
 * WebJobDoGetRequest
 *
 ***********************************************************************
 */
int
WebJobDoGetRequest(WEBJOB_PROPERTIES *psProperties, char *pcError)
{
  const char          acRoutine[] = "WebJobDoGetRequest()";
  char                acLocalError[MESSAGE_SIZE] = "";
  char                acQuery[WEBJOB_MAX_QUERY_LENGTH];
  char               *apcEscaped[6];
  int                 i;
  int                 iEscaped;
  int                 iError;
  int                 iFile = 0;
  int                 iOpenFlags;
  HTTP_URL           *psUrl;
  HTTP_RESPONSE_HDR   sResponseHeader;

  /*-
   *********************************************************************
   *
   * If the URL is not defined, abort.
   *
   *********************************************************************
   */
  psUrl = psProperties->psGetUrl;
  if (psUrl == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: Undefined URL.", acRoutine);
    return ER;
  }

  /*-
   *********************************************************************
   *
   * Conditionally set proxy properties.
   *
   *********************************************************************
   */
  if (psProperties->iUrlGetProxyEnable)
  {
    psUrl->iUseProxy = 1;
    iError = HttpSetUrlProxyHost(psUrl, psProperties->acUrlGetProxyHost, acLocalError);
    if (iError == -1)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
      return ER;
    }
    iError = HttpSetUrlProxyPort(psUrl, psProperties->acUrlGetProxyPort, acLocalError);
    if (iError == -1)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
      return ER;
    }
    if (psProperties->iUrlGetProxyAuthType == HTTP_AUTH_TYPE_BASIC)
    {
      iError = HttpFinalizeProxyCredentials(psUrl, psProperties->acUrlGetProxyUsername, psProperties->pcUrlGetProxyPassword, acLocalError);
      if (iError == -1)
      {
        snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
        return ER;
      }
    }
  }

  /*-
   *********************************************************************
   *
   * Finalize the user's HTTP credentials, if necessary.
   *
   *********************************************************************
   */
  if (psProperties->iUrlAuthType == HTTP_AUTH_TYPE_BASIC)
  {
    iError = HttpFinalizeCredentials(psUrl, psProperties->acUrlUsername, psProperties->pcUrlPassword, acLocalError);
    if (iError == -1)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
      return ER;
    }
  }

  /*-
   *********************************************************************
   *
   * Set the Path/Query string.
   *
   *********************************************************************
   */
  iEscaped = 0;
  apcEscaped[iEscaped] = HttpEscape(VersionGetVersion(), acLocalError);
  if (apcEscaped[iEscaped++] == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    for (i = 0; i < iEscaped; i++)
    {
      HttpFreeData(apcEscaped[i]);
    }
    return ER;
  }
  apcEscaped[iEscaped] = HttpEscape(GetSystemOS(), acLocalError);
  if (apcEscaped[iEscaped++] == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    for (i = 0; i < iEscaped; i++)
    {
      HttpFreeData(apcEscaped[i]);
    }
    return ER;
  }
  apcEscaped[iEscaped] = HttpEscape(psProperties->acClientId, acLocalError);
  if (apcEscaped[iEscaped++] == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    for (i = 0; i < iEscaped; i++)
    {
      HttpFreeData(apcEscaped[i]);
    }
    return ER;
  }
  apcEscaped[iEscaped] = HttpEscape(psProperties->pcGetFile, acLocalError);
  if (apcEscaped[iEscaped++] == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    for (i = 0; i < iEscaped; i++)
    {
      HttpFreeData(apcEscaped[i]);
    }
    return ER;
  }
  apcEscaped[iEscaped] = HttpEscape(psProperties->acQueueTag, acLocalError);
  if (apcEscaped[iEscaped++] == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    for (i = 0; i < iEscaped; i++)
    {
      HttpFreeData(apcEscaped[i]);
    }
    return ER;
  }

  snprintf(acQuery, WEBJOB_MAX_QUERY_LENGTH, "VERSION=%s&SYSTEM=%s&CLIENTID=%s&FILENAME=%s&QUEUETAG=%s",
    apcEscaped[0],
    apcEscaped[1],
    apcEscaped[2],
    apcEscaped[3],
    apcEscaped[4]
    );

  for (i = 0; i < iEscaped; i++)
  {
    HttpFreeData(apcEscaped[i]);
  }

  iError = HttpSetUrlQuery(psUrl, acQuery, acLocalError);
  if (iError != ER_OK)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    return ER;
  }

  /*-
   *********************************************************************
   *
   * Set the request method.
   *
   *********************************************************************
   */
  iError = HttpSetUrlMeth(psUrl, "GET", acLocalError);
  if (iError != ER_OK)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    return ER;
  }

  /*-
   *********************************************************************
   *
   * Set the download limit.
   *
   *********************************************************************
   */
  HttpSetUrlDownloadLimit(psUrl, (APP_UI32) psProperties->iUrlDownloadLimit);

  /*-
   *********************************************************************
   *
   * Check the overwrite flag, and do the appropriate open.
   *
   *********************************************************************
   */
  iOpenFlags = O_CREAT | O_TRUNC | O_EXCL | O_RDWR;
#ifdef WIN32
  iOpenFlags |= O_BINARY;
#endif
  if (psProperties->iOverwriteExecutable)
  {
    iOpenFlags ^= O_EXCL;
  }

  iFile = open(psProperties->pcGetFile, iOpenFlags, 0700);
  if (iFile == ER)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: open(): Directory = [%s], File = [%s], %s", acRoutine, psProperties->pcWorkDirectory, psProperties->pcGetFile, strerror(errno));
    if (errno == EEXIST && !psProperties->iOverwriteExecutable)
    {
      psProperties->iUnlinkExecutable = 0;
    }
    return ER;
  }

  psProperties->pGetFile = fdopen(iFile, "wb+");
  if (psProperties->pGetFile == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: fdopen(): Directory = [%s], File = [%s], %s", acRoutine, psProperties->pcWorkDirectory, psProperties->pcGetFile, strerror(errno));
    return ER;
  }

  /*-
   *********************************************************************
   *
   * Submit the request, and check the response code.
   *
   *********************************************************************
   */
  iError = HttpSubmitRequest(psUrl, HTTP_IGNORE_INPUT, NULL, HTTP_STREAM_OUTPUT, psProperties->pGetFile, &sResponseHeader, acLocalError);
  if (iError != ER_OK)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    WEBJOB_SAFE_FCLOSE(psProperties->pGetFile);
    return ER;
  }

  if (sResponseHeader.iStatusCode < 200 || sResponseHeader.iStatusCode > 299)
  {
/* FIXME Add a member to psUrl to hold a string representation of the entire URL. */
    snprintf(pcError, MESSAGE_SIZE, "%s: URL = [%s://%s:%d%s%s%s], Status = [%d], Reason = [%s]",
      acRoutine,
      (psUrl->iScheme == HTTP_SCHEME_HTTPS) ? "https" : "http",
      psUrl->pcHost,
      psUrl->ui16Port,
      psUrl->pcPath,
      (psUrl->pcQuery[0]) ? "?" : "",
      (psUrl->pcQuery[0]) ? psUrl->pcQuery : "",
      sResponseHeader.iStatusCode,
      sResponseHeader.acReasonPhrase
      );
    WEBJOB_SAFE_FCLOSE(psProperties->pGetFile);
    return ER;
  }

  /*-
   *********************************************************************
   *
   * Record the job ID.
   *
   *********************************************************************
   */
  strncpy(psProperties->acJobId, sResponseHeader.acJobId, HTTP_JOB_ID_SIZE);

  /*-
   *********************************************************************
   *
   * Record the fact that the GET succeeded. This variable will be used
   * later on in conjunction with iUploadOnGetFailure to determine if a
   * PUT should be attempted.
   *
   *********************************************************************
   */
  psProperties->iGetOk = 1;

#ifdef USE_DSV
  /*-
   *********************************************************************
   *
   * Conditionally verify the payload. If the signature is not valid,
   * the GET stage is considered a failure.
   *
   *********************************************************************
   */
  if (psProperties->iDsvVerifySignature)
  {
    iError = WebJobVerifySignature(psProperties, &sResponseHeader, acLocalError);
    if (iError != ER_OK)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
      WEBJOB_SAFE_FCLOSE(psProperties->pGetFile);
      return ER;
    }
  }
#endif

  /*-
   *********************************************************************
   *
   * Close the payload file.
   *
   *********************************************************************
   */
  WEBJOB_SAFE_FCLOSE(psProperties->pGetFile);

  /*-
   *********************************************************************
   *
   * Conditionally execute the GetHook. This logic resides here because
   * it needs to execute under the umbrella of the GET stage/timer. If
   * the hook fails, the GET stage is considered a failure.
   *
   *********************************************************************
   */
  if (psProperties->psGetHook->iActive)
  {
    iError = WebJobDoGetHook(psProperties, acLocalError);
    if (iError != ER_OK)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
      return ER;
    }
  }

  return ER_OK;
}


/*-
 ***********************************************************************
 *
 * WebJobDoGetStage
 *
 ***********************************************************************
 */
int
WebJobDoGetStage(WEBJOB_PROPERTIES *psProperties, char *pcError)
{
  const char          acRoutine[] = "WebJobDoGetStage()";
  char                acLocalError[MESSAGE_SIZE] = "";
  int                 iTimeout;
#ifdef UNIX
  int                 iError;
#endif
#ifdef WIN32
  char               *pcLocalError;
  DWORD               dwStatus;
  DWORD               dwThreadID;
  HANDLE              hThread;
  WEBJOB_THREAD       sThreadArguments;
#endif

  /*-
   *********************************************************************
   *
   * Update the RunStage.
   *
   *********************************************************************
   */
  psProperties->iRunStage = WEBJOB_GET_STAGE;

  /*-
   *********************************************************************
   *
   * Record the time.
   *
   *********************************************************************
   */
  WebJobGetEpoch(&psProperties->tvGetEpoch);

  /*-
   *********************************************************************
   *
   * Set the timeout value -- zero means no timeout.
   *
   *********************************************************************
   */
#ifdef WIN32
  iTimeout = (psProperties->iGetTimeLimit == 0) ? INFINITE : psProperties->iGetTimeLimit * 1000;
#else
  iTimeout = psProperties->iGetTimeLimit;
#endif

#ifdef UNIX
  /*-
   *********************************************************************
   *
   * Execute the GET request. Abort on timeout.
   *
   *********************************************************************
   */
  alarm(iTimeout);
  if (setjmp(psProperties->sGetEnvironment) == 0)
  {
    iError = WebJobDoGetRequest(psProperties, acLocalError);
    if (iError != ER_OK)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
      return ER;
    }
  }
  else
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: setjmp(): GET stage timed out after %d seconds.", acRoutine, psProperties->iGetTimeLimit);
    return ER;
  }
  alarm(0);
#endif

#ifdef WIN32
  /*-
   *********************************************************************
   *
   * Create a worker thread and execute the GET request.
   *
   *********************************************************************
   */
  sThreadArguments.psProperties = psProperties;
  sThreadArguments.pcError = acLocalError;
  sThreadArguments.piRoutine = WebJobDoGetRequest;
  hThread = (HANDLE) _beginthreadex(
    NULL,
    0,
    (void *) WebJobThreadWrapper,
    &sThreadArguments,
    0,
    (void *) &dwThreadID
    );
  if (hThread == INVALID_HANDLE_VALUE)
  {
    FormatWin32Error(GetLastError(), &pcLocalError);
    snprintf(pcError, MESSAGE_SIZE, "%s: _beginthreadex(): %s", acRoutine, pcLocalError);
    return ER;
  }

  /*-
   *********************************************************************
   *
   * Wait for the thread to complete. Abort on timeout.
   *
   *********************************************************************
   */
  dwStatus = WaitForSingleObject(hThread, iTimeout);
  if (dwStatus == WAIT_TIMEOUT)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: WaitForSingleObject(): GET stage timed out after %d seconds.", acRoutine, psProperties->iGetTimeLimit);
    TerminateThread(hThread, ER);
    CloseHandle(hThread);
    return ER;
  }
  else
  {
    if (!GetExitCodeThread(hThread, &dwStatus))
    {
      FormatWin32Error(GetLastError(), &pcLocalError);
      snprintf(pcError, MESSAGE_SIZE, "%s: GetExitCodeThread(): %s", acRoutine, pcLocalError);
      CloseHandle(hThread);
      return ER;
    }
    if (dwStatus != (DWORD) ER_OK) /* The GET failed. */
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
      CloseHandle(hThread);
      return ER;
    }
  }
  CloseHandle(hThread);
#endif

  return ER_OK;
}


#ifdef UNIX
/*-
 ***********************************************************************
 *
 * WebJobDoKidStage
 *
 ***********************************************************************
 */
int
WebJobDoKidStage(WEBJOB_PROPERTIES *psProperties, char *pcError)
{
  const char          acRoutine[] = "WebJobDoKidStage()";
  int                 iError;

  /*-
   *********************************************************************
   *
   * If a priority was specified by the user, set it now.
   *
   *********************************************************************
   */
  if (psProperties->acPriority[0])
  {
    iError = setpriority(PRIO_PROCESS, 0, psProperties->iPriority);
    if (iError == -1)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: setpriority(): %s", acRoutine, strerror(errno));
      return ER;
    }
  }

  /*-
   *********************************************************************
   *
   * Set stdin to be the read-side of the provided pipe.
   *
   *********************************************************************
   */
  WEBJOB_SAFE_FCLOSE(stdin);
  iError = dup(psProperties->sWEBJOB.iPipe[WEBJOB_READER_HANDLE]);
  if (iError == -1)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: dup(): stdin, %s", acRoutine, strerror(errno));
    return ER;
  }
  close(psProperties->sWEBJOB.iPipe[WEBJOB_READER_HANDLE]);
  close(psProperties->sWEBJOB.iPipe[WEBJOB_WRITER_HANDLE]);

  /*-
   *********************************************************************
   *
   * If PutUrl was specified, use the provided handles as std{out|err}.
   *
   *********************************************************************
   */
  if (psProperties->psPutUrl != NULL)
  {
    WEBJOB_SAFE_FCLOSE(stdout);
    iError = dup(fileno(psProperties->sWEBJOB.pStdOut));
    if (iError == -1)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: dup(): stdout, %s", acRoutine, strerror(errno));
      return ER;
    }
    WEBJOB_SAFE_FCLOSE(stderr);
    iError = dup(fileno(psProperties->sWEBJOB.pStdErr));
    if (iError == -1)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: dup(): stderr, %s", acRoutine, strerror(errno));
      return ER;
    }
  }

  /*-
   *********************************************************************
   *
   * Execute the specified command line.
   *
   *********************************************************************
   */
  iError = execv(
    (psProperties->iRunEmbedded) ? psProperties->sWEBJOB.pcEmbeddedCommand : psProperties->sWEBJOB.pcCommand,
    (psProperties->iRunEmbedded) ? psProperties->sWEBJOB.ppcEmbeddedArguments : psProperties->sWEBJOB.ppcArguments
    );
  if (iError == -1)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: execv(): %s, %s",
      acRoutine,
      (psProperties->iRunEmbedded) ? psProperties->sWEBJOB.pcEmbeddedCommand : psProperties->sWEBJOB.pcCommand,
      strerror(errno)
      );
    return ER;
  }

  /*-
   *********************************************************************
   *
   * Execution beyond execv() should not happen.
   *
   *********************************************************************
   */
  snprintf(pcError, MESSAGE_SIZE, "%s: Execution has gone beyond execv(). This should not happen.", acRoutine);
  return ER;
}
#endif


/*-
 ***********************************************************************
 *
 * WebJobDoPutRequest
 *
 ***********************************************************************
 */
int
WebJobDoPutRequest(WEBJOB_PROPERTIES *psProperties, char *pcError)
{
  const char          acRoutine[] = "WebJobDoPutRequest()";
  char                acLocalError[MESSAGE_SIZE] = "";
  char                acQuery[WEBJOB_MAX_QUERY_LENGTH];
  char                aacStreams[3][7];
  char               *apcEscaped[7];
  int                 i = 0;
  int                 iEscaped = 0;
  int                 iError = 0;
  int                 iLimit = 0;
  int                 iNRead = 0;
#ifdef USE_DSV
  int                 iNWritten = 0;
#endif
  HTTP_URL           *psUrl = NULL;
  HTTP_STREAM_LIST    asStreamList[3];
  HTTP_RESPONSE_HDR   sResponseHeader;
  unsigned char       aucData[WEBJOB_READ_SIZE];

  /*-
   *********************************************************************
   *
   * If the URL is not defined, abort.
   *
   *********************************************************************
   */
  psUrl = psProperties->psPutUrl;
  if (psUrl == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: Undefined URL.", acRoutine);
    return ER;
  }

  /*-
   *********************************************************************
   *
   * Conditionally set proxy properties.
   *
   *********************************************************************
   */
  if (psProperties->iUrlPutProxyEnable)
  {
    psUrl->iUseProxy = 1;
    iError = HttpSetUrlProxyHost(psUrl, psProperties->acUrlPutProxyHost, acLocalError);
    if (iError == -1)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
      return ER;
    }
    iError = HttpSetUrlProxyPort(psUrl, psProperties->acUrlPutProxyPort, acLocalError);
    if (iError == -1)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
      return ER;
    }
    if (psProperties->iUrlPutProxyAuthType == HTTP_AUTH_TYPE_BASIC)
    {
      iError = HttpFinalizeProxyCredentials(psUrl, psProperties->acUrlPutProxyUsername, psProperties->pcUrlPutProxyPassword, acLocalError);
      if (iError == -1)
      {
        snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
        return ER;
      }
    }
  }

  /*-
   *********************************************************************
   *
   * Finalize the user's HTTP credentials, if necessary.
   *
   *********************************************************************
   */
  if (psProperties->iUrlAuthType == HTTP_AUTH_TYPE_BASIC)
  {
    iError = HttpFinalizeCredentials(psUrl, psProperties->acUrlUsername, psProperties->pcUrlPassword, acLocalError);
    if (iError == -1)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
      return ER;
    }
  }

  /*-
   *********************************************************************
   *
   * Build the HTTP_STREAM_LIST list.
   *
   *********************************************************************
   */
  strncpy(aacStreams[0], "stdout", sizeof(aacStreams[0]));
  strncpy(aacStreams[1], "stderr", sizeof(aacStreams[1]));
  strncpy(aacStreams[2], "stdenv", sizeof(aacStreams[2]));
  asStreamList[0].pFile = psProperties->sWEBJOB.pStdOut;
  asStreamList[1].pFile = psProperties->sWEBJOB.pStdErr;
  asStreamList[2].pFile = psProperties->sWEBJOB.pStdEnv;
  for (i = 0, iLimit = 3; i < iLimit; i++)
  {
    if (fseek(asStreamList[i].pFile, 0, SEEK_END) == ER)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: fseek(): Stream = [%s]: %s", acRoutine, aacStreams[i], strerror(errno));
      return ER;
    }
    asStreamList[i].ui32Size = (APP_UI32) ftell(asStreamList[i].pFile);
    if (asStreamList[i].ui32Size == (APP_UI32) ER)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: fseek(): Stream = [%s]: %s", acRoutine, aacStreams[i], strerror(errno));
      return ER;
    }
    rewind(asStreamList[i].pFile);
    asStreamList[i].psNext = (i < iLimit - 1) ? &asStreamList[i + 1] : NULL;
  }

  /*-
   *********************************************************************
   *
   * Set the query string.
   *
   *********************************************************************
   */
  iEscaped = 0;
  apcEscaped[iEscaped] = HttpEscape(VersionGetVersion(), acLocalError);
  if (apcEscaped[iEscaped++] == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    for (i = 0; i < iEscaped; i++)
    {
      HttpFreeData(apcEscaped[i]);
    }
    return ER;
  }
  apcEscaped[iEscaped] = HttpEscape(GetSystemOS(), acLocalError);
  if (apcEscaped[iEscaped++] == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    for (i = 0; i < iEscaped; i++)
    {
      HttpFreeData(apcEscaped[i]);
    }
    return ER;
  }
  apcEscaped[iEscaped] = HttpEscape(psProperties->acClientId, acLocalError);
  if (apcEscaped[iEscaped++] == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    for (i = 0; i < iEscaped; i++)
    {
      HttpFreeData(apcEscaped[i]);
    }
    return ER;
  }
  apcEscaped[iEscaped] = HttpEscape(psProperties->pcGetFile, acLocalError);
  if (apcEscaped[iEscaped++] == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    for (i = 0; i < iEscaped; i++)
    {
      HttpFreeData(apcEscaped[i]);
    }
    return ER;
  }
  apcEscaped[iEscaped] = HttpEscape(psProperties->acRunType, acLocalError);
  if (apcEscaped[iEscaped++] == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    for (i = 0; i < iEscaped; i++)
    {
      HttpFreeData(apcEscaped[i]);
    }
    return ER;
  }
  apcEscaped[iEscaped] = HttpEscape(psProperties->acQueueTag, acLocalError);
  if (apcEscaped[iEscaped++] == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    for (i = 0; i < iEscaped; i++)
    {
      HttpFreeData(apcEscaped[i]);
    }
    return ER;
  }

  snprintf(acQuery, WEBJOB_MAX_QUERY_LENGTH, "VERSION=%s&SYSTEM=%s&CLIENTID=%s&FILENAME=%s&RUNTYPE=%s&STDOUT_LENGTH=%u&STDERR_LENGTH=%u&STDENV_LENGTH=%u&QUEUETAG=%s",
    apcEscaped[0],
    apcEscaped[1],
    apcEscaped[2],
    apcEscaped[3],
    apcEscaped[4],
    asStreamList[0].ui32Size,
    asStreamList[1].ui32Size,
    asStreamList[2].ui32Size,
    apcEscaped[5]
    );

  for (i = 0; i < iEscaped; i++)
  {
    HttpFreeData(apcEscaped[i]);
  }

  iError = HttpSetUrlQuery(psUrl, acQuery, acLocalError);
  if (iError != ER_OK)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    return ER;
  }

  /*-
   *********************************************************************
   *
   * Set the request method.
   *
   *********************************************************************
   */
  iError = HttpSetUrlMeth(psUrl, "PUT", acLocalError);
  if (iError != ER_OK)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    return ER;
  }

  /*-
   *********************************************************************
   *
   * Set the download limit.
   *
   *********************************************************************
   */
  HttpSetUrlDownloadLimit(psUrl, (APP_UI32) psProperties->iUrlDownloadLimit);

  /*-
   *********************************************************************
   *
   * Set the job id.
   *
   *********************************************************************
   */
  if (psProperties->acJobId[0])
  {
    iError = HttpSetUrlJobId(psUrl, psProperties->acJobId, acLocalError);
    if (iError != ER_OK)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
      return ER;
    }
  }

  /*-
   *********************************************************************
   *
   * Open or set the payload file.
   *
   *********************************************************************
   */
#ifdef USE_DSV
  if (psProperties->iDsvVerifySignature)
  {
    psProperties->pPutFile = WebJobGetHandle(psProperties->pcPutFile, acLocalError);
    if (psProperties->pPutFile == NULL)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: Directory = [%s]: %s", acRoutine, psProperties->pcWorkDirectory, acLocalError);
      return ER;
    }
#ifdef UNIX
    unlink(psProperties->pcPutFile); /* Minimize file system availability by unlinking now. */
#endif
  }
  else
  {
    psProperties->pPutFile = stdout;
  }
#else
  psProperties->pPutFile = stdout;
#endif

  /*-
   *********************************************************************
   *
   * Submit the request.
   *
   *********************************************************************
   */
  iError = HttpSubmitRequest(psUrl, HTTP_STREAM_INPUT, asStreamList, HTTP_STREAM_OUTPUT, psProperties->pPutFile, &sResponseHeader, acLocalError);
  if (iError != ER_OK)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    if (psProperties->pPutFile != stdout)
    {
      WEBJOB_SAFE_FCLOSE(psProperties->pPutFile);
    }
    return ER;
  }

  /*-
   *********************************************************************
   *
   * Check the HTTP response code.
   *
   *********************************************************************
   */
  if (sResponseHeader.iStatusCode < 200 || sResponseHeader.iStatusCode > 299)
  {
/* FIXME Add a member to psUrl to hold a string representation of the entire URL. */
    snprintf(pcError, MESSAGE_SIZE, "%s: URL = [%s://%s:%d%s%s%s], Status = [%d], Reason = [%s]",
      acRoutine,
      (psUrl->iScheme == HTTP_SCHEME_HTTPS) ? "https" : "http",
      psUrl->pcHost,
      psUrl->ui16Port,
      psUrl->pcPath,
      (psUrl->pcQuery[0]) ? "?" : "",
      (psUrl->pcQuery[0]) ? psUrl->pcQuery : "",
      sResponseHeader.iStatusCode,
      sResponseHeader.acReasonPhrase
      );
    if (psProperties->pPutFile != stdout)
    {
      WEBJOB_SAFE_FCLOSE(psProperties->pPutFile);
    }
    return ER;
  }

  /*-
   *********************************************************************
   *
   * Record the fact that the PUT succeeded.
   *
   *********************************************************************
   */
  psProperties->iPutOk = 1;

#ifdef USE_DSV
  /*-
   *********************************************************************
   *
   * Conditionally verify the payload. If the signature is not valid,
   * the PUT stage is considered a failure. If it is valid, dump the
   * payload to stdout.
   *
   *********************************************************************
   */
  if (psProperties->iDsvVerifySignature)
  {
    iError = WebJobVerifySignature(psProperties, &sResponseHeader, acLocalError);
    if (iError != ER_OK)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
      WEBJOB_SAFE_FCLOSE(psProperties->pPutFile);
      return ER;
    }

    rewind(psProperties->pPutFile);
    while ((iNRead = fread(aucData, 1, WEBJOB_READ_SIZE, psProperties->pPutFile)) > 0)
    {
      iNWritten = fwrite(aucData, 1, iNRead, stdout);
      if (iNWritten != iNRead)
      {
        snprintf(pcError, MESSAGE_SIZE, "%s: fwrite(): %s", acRoutine, strerror(errno));
        WEBJOB_SAFE_FCLOSE(psProperties->pPutFile);
        return ER;
      }
    }
    if (ferror(psProperties->pPutFile))
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: fread(): %s", acRoutine, strerror(errno));
      WEBJOB_SAFE_FCLOSE(psProperties->pPutFile);
      return ER;
    }

    WEBJOB_SAFE_FCLOSE(psProperties->pPutFile);
  }
#endif

  return ER_OK;
}


/*-
 ***********************************************************************
 *
 * WebJobDoPutStage
 *
 ***********************************************************************
 */
int
WebJobDoPutStage(WEBJOB_PROPERTIES *psProperties, char *pcError)
{
  const char          acRoutine[] = "WebJobDoPutStage()";
  char                acLocalError[MESSAGE_SIZE] = "";
  int                 i;
  int                 iTimeout;
#ifdef UNIX
  int                 iError;
#endif
#ifdef WIN32
  char               *pcLocalError;
  DWORD               dwStatus;
  DWORD               dwThreadID;
  HANDLE              hThread;
  WEBJOB_THREAD       sThreadArguments;
#endif

  /*-
   *********************************************************************
   *
   * If there's no PutUrl, there's nothing to upload.
   *
   *********************************************************************
   */
  if (psProperties->psPutUrl == NULL)
  {
    return ER_OK;
  }

  /*-
   *********************************************************************
   *
   * Update the RunStage.
   *
   *********************************************************************
   */
  psProperties->iRunStage = WEBJOB_PUT_STAGE;

  /*-
   *********************************************************************
   *
   * Record the time.
   *
   *********************************************************************
   */
  WebJobGetEpoch(&psProperties->tvPutEpoch);

  /*-
   *********************************************************************
   *
   * Set the timeout value -- zero means no timeout.
   *
   *********************************************************************
   */
#ifdef WIN32
  iTimeout = (psProperties->iPutTimeLimit == 0) ? INFINITE : psProperties->iPutTimeLimit * 1000;
#else
  iTimeout = psProperties->iPutTimeLimit;
#endif

  /*-
   *********************************************************************
   *
   * Hash output streams.
   *
   *********************************************************************
   */
  rewind(psProperties->sWEBJOB.pStdOut);
  psProperties->piHashStream(psProperties->sWEBJOB.pStdOut, psProperties->pcStdOutHash, &psProperties->ui64StdOutSize);

  rewind(psProperties->sWEBJOB.pStdErr);
  psProperties->piHashStream(psProperties->sWEBJOB.pStdErr, psProperties->pcStdErrHash, &psProperties->ui64StdErrSize);

  /*-
   *********************************************************************
   *
   * Write out WebJob's environment data.
   *
   *********************************************************************
   */
  fprintf(psProperties->sWEBJOB.pStdEnv, "Version%s%s\n",
    WEBJOB_SEPARATOR_S,
    VersionGetVersion()
    );
  fprintf(psProperties->sWEBJOB.pStdEnv, "Hostname%s%s\n",
    WEBJOB_SEPARATOR_S,
    psProperties->pcHostname
    );
  fprintf(psProperties->sWEBJOB.pStdEnv, "SystemOS%s%s\n",
    WEBJOB_SEPARATOR_S,
    GetSystemOS()
    );
  fprintf(psProperties->sWEBJOB.pStdEnv, "ConfigList%s%s\n",
    WEBJOB_SEPARATOR_S,
    psProperties->pcConfigList
    );
  fprintf(psProperties->sWEBJOB.pStdEnv, "ConfigFile%s%s\n",
    WEBJOB_SEPARATOR_S,
    psProperties->pcConfigFile
    );
  fprintf(psProperties->sWEBJOB.pStdEnv, "ClientId%s%s\n",
    WEBJOB_SEPARATOR_S,
    psProperties->acClientId
    );
  if (psProperties->iUrlGetProxyEnable)
  {
    fprintf(psProperties->sWEBJOB.pStdEnv, "UrlGetProxyHost%s%s\n",
      WEBJOB_SEPARATOR_S,
      psProperties->acUrlGetProxyHost
      );
    fprintf(psProperties->sWEBJOB.pStdEnv, "UrlGetProxyPort%s%s\n",
      WEBJOB_SEPARATOR_S,
      psProperties->acUrlGetProxyPort
      );
  }
  if (psProperties->iUrlPutProxyEnable)
  {
    fprintf(psProperties->sWEBJOB.pStdEnv, "UrlPutProxyHost%s%s\n",
      WEBJOB_SEPARATOR_S,
      psProperties->acUrlPutProxyHost
      );
    fprintf(psProperties->sWEBJOB.pStdEnv, "UrlPutProxyPort%s%s\n",
      WEBJOB_SEPARATOR_S,
      psProperties->acUrlPutProxyPort
      );
  }
  fprintf(psProperties->sWEBJOB.pStdEnv, "GetRequest%s%s\n",
    WEBJOB_SEPARATOR_S,
    psProperties->pcGetFile
    );
  fprintf(psProperties->sWEBJOB.pStdEnv, "QueueTag%s%s\n",
    WEBJOB_SEPARATOR_S,
    psProperties->acQueueTag
    );
#ifdef USE_DSV
  if (psProperties->iDsvVerifySignature)
  {
    fprintf(psProperties->sWEBJOB.pStdEnv, "DsvStatus%s%d\n",
      WEBJOB_SEPARATOR_S,
      psProperties->iDsvGetStatus
      );
    fprintf(psProperties->sWEBJOB.pStdEnv, "DsvSigner%s%s\n",
      WEBJOB_SEPARATOR_S,
      (psProperties->iDsvGetStatus == DSV_SIGNATURE_VERIFICATION_PASSED && psProperties->psDsvGetSignature->pcCommonName && psProperties->psDsvGetSignature->pcCommonName[0]) ? psProperties->psDsvGetSignature->pcCommonName : ""
      );
  }
#endif
  if (psProperties->psGetHook->iActive)
  {
    fprintf(psProperties->sWEBJOB.pStdEnv, "GetHookCommandLine%s%s\n",
      WEBJOB_SEPARATOR_S,
      (psProperties->psGetHook->pcExpandedCommandLine) ? psProperties->psGetHook->pcExpandedCommandLine : "NONE"
      );
    fprintf(psProperties->sWEBJOB.pStdEnv, "GetHookStatus%s%d/%d (actual/target)\n",
      WEBJOB_SEPARATOR_S,
      psProperties->psGetHook->iActualStatus,
      psProperties->psGetHook->iTargetStatus
      );
  }
  fprintf(psProperties->sWEBJOB.pStdEnv, "Command%s%s\n",
    WEBJOB_SEPARATOR_S,
    (psProperties->sWEBJOB.pcCommand) ? psProperties->sWEBJOB.pcCommand: ""
    );
  fprintf(psProperties->sWEBJOB.pStdEnv, "CommandLine%s%s\n",
    WEBJOB_SEPARATOR_S,
    (psProperties->sWEBJOB.pcCommandLine) ? psProperties->sWEBJOB.pcCommandLine : ""
    );
  if (psProperties->iRunEmbedded)
  {
    fprintf(psProperties->sWEBJOB.pStdEnv, "EmbeddedCommand%s%s\n",
      WEBJOB_SEPARATOR_S,
      (psProperties->sWEBJOB.pcEmbeddedCommand) ? psProperties->sWEBJOB.pcEmbeddedCommand: ""
      );
    fprintf(psProperties->sWEBJOB.pStdEnv, "EmbeddedCommandLine%s%s\n",
      WEBJOB_SEPARATOR_S,
      (psProperties->sWEBJOB.pcEmbeddedCommandLine) ? psProperties->sWEBJOB.pcEmbeddedCommandLine : ""
      );
  }
  fprintf(psProperties->sWEBJOB.pStdEnv, "TempDirectory%s%s\n",
    WEBJOB_SEPARATOR_S,
    (psProperties->pcTempDirectory) ? psProperties->pcTempDirectory: ""
    );
  fprintf(psProperties->sWEBJOB.pStdEnv, "WorkDirectory%s%s\n",
    WEBJOB_SEPARATOR_S,
    (psProperties->pcWorkDirectory) ? psProperties->pcWorkDirectory: ""
    );
  fprintf(psProperties->sWEBJOB.pStdEnv, "Priority%s%s\n",
    WEBJOB_SEPARATOR_S,
    psProperties->acPriority
    );
  fprintf(psProperties->sWEBJOB.pStdEnv, "Jid%s%s\n",
    WEBJOB_SEPARATOR_S,
    (psProperties->acJobId[0]) ? psProperties->acJobId : "NA"
    );
  fprintf(psProperties->sWEBJOB.pStdEnv, "Pid%s%d\n",
    WEBJOB_SEPARATOR_S,
    (int) getpid()
    );
  fprintf(psProperties->sWEBJOB.pStdEnv, "KidPid%s%d\n",
    WEBJOB_SEPARATOR_S,
    psProperties->sWEBJOB.iKidPid
    );
  fprintf(psProperties->sWEBJOB.pStdEnv, "KidStatus%s%d\n",
    WEBJOB_SEPARATOR_S,
    psProperties->sWEBJOB.iKidStatus
    );
#ifdef UNIX
  fprintf(psProperties->sWEBJOB.pStdEnv, "KidSignal%s%d\n",
    WEBJOB_SEPARATOR_S,
    psProperties->sWEBJOB.iKidSignal
    );
#endif
  fprintf(psProperties->sWEBJOB.pStdEnv, "KidReason%s%s\n",
    WEBJOB_SEPARATOR_S,
    psProperties->sWEBJOB.acKidReason
    );
  fprintf(psProperties->sWEBJOB.pStdEnv, "JobEpoch%s%s (%ld.%06ld)\n",
    WEBJOB_SEPARATOR_S,
    WebJobFormatTime((time_t)psProperties->tvJobEpoch.tv_sec),
    (long) psProperties->tvJobEpoch.tv_sec,
    (long) psProperties->tvJobEpoch.tv_usec
    );
  fprintf(psProperties->sWEBJOB.pStdEnv, "GetEpoch%s%s (%ld.%06ld)\n",
    WEBJOB_SEPARATOR_S,
    WebJobFormatTime((time_t)psProperties->tvGetEpoch.tv_sec),
    (long) psProperties->tvGetEpoch.tv_sec,
    (long) psProperties->tvGetEpoch.tv_usec
    );
  fprintf(psProperties->sWEBJOB.pStdEnv, "RunEpoch%s%s (%ld.%06ld)\n",
    WEBJOB_SEPARATOR_S,
    WebJobFormatTime((time_t)psProperties->tvRunEpoch.tv_sec),
    (long) psProperties->tvRunEpoch.tv_sec,
    (long) psProperties->tvRunEpoch.tv_usec
    );
  fprintf(psProperties->sWEBJOB.pStdEnv, "PutEpoch%s%s (%ld.%06ld)\n",
    WEBJOB_SEPARATOR_S,
    WebJobFormatTime((time_t)psProperties->tvPutEpoch.tv_sec),
    (long) psProperties->tvPutEpoch.tv_sec,
    (long) psProperties->tvPutEpoch.tv_usec
    );
  fprintf(psProperties->sWEBJOB.pStdEnv, "HashType%s%s\n",
    WEBJOB_SEPARATOR_S,
    psProperties->acHashType
    );
  fprintf(psProperties->sWEBJOB.pStdEnv, "StdOutHash%s", WEBJOB_SEPARATOR_S);
  for (i = 0; i < psProperties->iHashSize; i++)
  {
    fprintf(psProperties->sWEBJOB.pStdEnv, "%02x", psProperties->pcStdOutHash[i]);
  }
  fprintf(psProperties->sWEBJOB.pStdEnv, "\n");
#if defined(WIN32) && !defined(MINGW32)
  fprintf(psProperties->sWEBJOB.pStdEnv, "StdOutSize%s%I64u\n", WEBJOB_SEPARATOR_S, (unsigned) psProperties->ui64StdOutSize);
#else
  fprintf(psProperties->sWEBJOB.pStdEnv, "StdOutSize%s%llu\n", WEBJOB_SEPARATOR_S, (unsigned long long) psProperties->ui64StdOutSize);
#endif
  fprintf(psProperties->sWEBJOB.pStdEnv, "StdErrHash%s", WEBJOB_SEPARATOR_S);
  for (i = 0; i < psProperties->iHashSize; i++)
  {
    fprintf(psProperties->sWEBJOB.pStdEnv, "%02x", psProperties->pcStdErrHash[i]);
  }
  fprintf(psProperties->sWEBJOB.pStdEnv, "\n");
#if defined(WIN32) && !defined(MINGW32)
  fprintf(psProperties->sWEBJOB.pStdEnv, "StdErrSize%s%I64u\n", WEBJOB_SEPARATOR_S, (unsigned) psProperties->ui64StdErrSize);
#else
  fprintf(psProperties->sWEBJOB.pStdEnv, "StdErrSize%s%llu\n", WEBJOB_SEPARATOR_S, (unsigned long long) psProperties->ui64StdErrSize);
#endif
  fprintf(psProperties->sWEBJOB.pStdEnv, "GetError%s%s\n",
    WEBJOB_SEPARATOR_S,
    (psProperties->acGetError[0]) ? psProperties->acGetError : "NA"
    );
  fprintf(psProperties->sWEBJOB.pStdEnv, "RunError%s%s\n",
    WEBJOB_SEPARATOR_S,
    (psProperties->acRunError[0]) ? psProperties->acRunError : "NA"
    );

#ifdef UNIX
  /*-
   *********************************************************************
   *
   * Execute the PUT request. Abort on timeout.
   *
   *********************************************************************
   */
  alarm(iTimeout);
  if (setjmp(psProperties->sPutEnvironment) == 0)
  {
    iError = WebJobDoPutRequest(psProperties, acLocalError);
    if (iError != ER_OK)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
      return ER;
    }
  }
  else
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: setjmp(): PUT stage timed out after %d seconds.", acRoutine, psProperties->iPutTimeLimit);
    return ER;
  }
  alarm(0);
#endif

#ifdef WIN32
  /*-
   *********************************************************************
   *
   * Create a worker thread and execute the PUT request.
   *
   *********************************************************************
   */
  sThreadArguments.psProperties = psProperties;
  sThreadArguments.pcError = acLocalError;
  sThreadArguments.piRoutine = WebJobDoPutRequest;
  hThread = (HANDLE) _beginthreadex(
    NULL,
    0,
    (void *) WebJobThreadWrapper,
    &sThreadArguments,
    0,
    (void *) &dwThreadID
    );
  if (hThread == INVALID_HANDLE_VALUE)
  {
    FormatWin32Error(GetLastError(), &pcLocalError);
    snprintf(pcError, MESSAGE_SIZE, "%s: _beginthreadex(): %s", acRoutine, pcLocalError);
    return ER;
  }

  /*-
   *********************************************************************
   *
   * Wait for the thread to complete. Abort on timeout.
   *
   *********************************************************************
   */
  dwStatus = WaitForSingleObject(hThread, iTimeout);
  if (dwStatus == WAIT_TIMEOUT)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: WaitForSingleObject(): PUT stage timed out after %d seconds.", acRoutine, psProperties->iPutTimeLimit);
    TerminateThread(hThread, ER);
    CloseHandle(hThread);
    return ER;
  }
  else
  {
    if (!GetExitCodeThread(hThread, &dwStatus))
    {
      FormatWin32Error(GetLastError(), &pcLocalError);
      snprintf(pcError, MESSAGE_SIZE, "%s: GetExitCodeThread(): %s", acRoutine, pcLocalError);
      CloseHandle(hThread);
      return ER;
    }
    if (dwStatus != (DWORD) ER_OK) /* The PUT failed. */
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
      CloseHandle(hThread);
      return ER;
    }
  }
  CloseHandle(hThread);
#endif

  return ER_OK;
}


/*-
 ***********************************************************************
 *
 * WebJobDoRunStage
 *
 ***********************************************************************
 */
int
WebJobDoRunStage(WEBJOB_PROPERTIES *psProperties, char *pcError)
{
  const char          acRoutine[] = "WebJobDoRunStage()";
#if defined(UNIX) || defined(USE_EMBEDDED_PERL)
  char                acLocalError[MESSAGE_SIZE] = "";
#endif
#if defined(USE_EMBEDDED_LUA) || defined(USE_EMBEDDED_PERL)
  int                 iError = 0;
#endif
  int                 iTimeout = 0;
#ifdef UNIX
  int                 iPid = 0;
  int                 iStatus = 0;
#endif
#ifdef WIN32
  BOOL                bStatus = FALSE;
  char               *pcLocalError = NULL;
  DWORD               dwCreationFlags = 0;
  DWORD               dwStatus = 0;
  int                 i = 0;
  PROCESS_INFORMATION sProcessInformation = { 0 };
  STARTUPINFO         sStartupInformation = { 0 };
#endif

  /*-
   *********************************************************************
   *
   * Update the RunStage.
   *
   *********************************************************************
   */
  psProperties->iRunStage = WEBJOB_RUN_STAGE;

  /*-
   *********************************************************************
   *
   * Record the time.
   *
   *********************************************************************
   */
  WebJobGetEpoch(&psProperties->tvRunEpoch);

  /*-
   *********************************************************************
   *
   * Return and error, if this stage has been disabled.
   *
   *********************************************************************
   */
  if (psProperties->iRunStageDisabled == 1)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: This stage has been disabled.", acRoutine);
    return ER;
  }

#if defined(USE_EMBEDDED_LUA) || defined(USE_EMBEDDED_PERL)
  /*-
   *********************************************************************
   *
   * Check to see if the target program should be run in an embedded
   * interpreter, and conditionally make the necessary preparations.
   *
   *********************************************************************
   */
  iError = WebJobDoConfigureEmbedded(psProperties, acLocalError);
  if (iError != ER_OK)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    return ER;
  }
#endif

  /*-
   *********************************************************************
   *
   * Set the timeout value -- zero means no timeout.
   *
   *********************************************************************
   */
#ifdef WIN32
  iTimeout = (psProperties->iRunTimeLimit == 0) ? INFINITE : psProperties->iRunTimeLimit * 1000;
#else
  iTimeout = psProperties->iRunTimeLimit;
#endif

#ifdef UNIX
  /*-
   *********************************************************************
   *
   * Create a new process and execute the specified command line.
   *
   *********************************************************************
   */
  switch ((psProperties->sWEBJOB.iKidPid = fork()))
  {
  case -1:
    snprintf(pcError, MESSAGE_SIZE, "%s: fork(): %s", acRoutine, strerror(errno));
    return ER;
    break;

  case 0:
    WebJobDoKidStage(psProperties, acLocalError);
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    return ER;
    break;
  }

  /*-
   *********************************************************************
   *
   * Close the stdin pipe. If this is not done, programs that expect
   * something on stdin are likely to block. If RunTimeLimit is zero,
   * then they will probably block forever.
   *
   * NOTE: This pipe was established so that future versions could
   * feed data to the child process. To implement that capability,
   * one could add some logic here (e.g. write loop).
   *
   *********************************************************************
   */
  close(psProperties->sWEBJOB.iPipe[WEBJOB_READER_HANDLE]);
/* FIXME Add loop here when writing data to stdin. */
  close(psProperties->sWEBJOB.iPipe[WEBJOB_WRITER_HANDLE]);

  /*-
   *********************************************************************
   *
   * Wait for the process to finish, but terminate it on timeout.
   *
   *********************************************************************
   */
  alarm(iTimeout);
  while ((iPid = wait(&iStatus)) != psProperties->sWEBJOB.iKidPid)
  {
    if (iPid == -1)
    {
      if (errno == EINTR)
      {
        fprintf(stderr, "Main(): %s: wait(): RUN stage timed out after %d seconds.\n", acRoutine, psProperties->iRunTimeLimit);
        if (kill(psProperties->sWEBJOB.iKidPid, psProperties->iTimeoutSignal) != ER_OK)
        {
          snprintf(pcError, MESSAGE_SIZE, "%s: kill(): TimeoutSignal = [%d], KidPid = [%d], %s", acRoutine, psProperties->iTimeoutSignal, psProperties->sWEBJOB.iKidPid, strerror(errno));
          return ER;
        }
      }
      else
      {
        snprintf(pcError, MESSAGE_SIZE, "%s: wait(): %s", acRoutine, strerror(errno));
        return ER;
      }
    }
  }
  alarm(0);

  /*-
   *********************************************************************
   *
   * Determine the kid's exit status.
   *
   *********************************************************************
   */
  psProperties->sWEBJOB.iKidStatus = WEXITSTATUS(iStatus);
  psProperties->sWEBJOB.iKidSignal = WTERMSIG(iStatus);
  if (WIFEXITED(iStatus))
  {
    snprintf(psProperties->sWEBJOB.acKidReason, MESSAGE_SIZE, "The kid exited cleanly.");
  }
  else if (WIFSIGNALED(iStatus))
  {
#ifndef WCOREDUMP
#define WCOREDUMP(w) ((w)&0x80)
#endif
#ifdef WEBJOB_CYGWIN
    snprintf(psProperties->sWEBJOB.acKidReason, MESSAGE_SIZE, "The kid exited due to a signal.");
#else
    snprintf(psProperties->sWEBJOB.acKidReason, MESSAGE_SIZE, "The kid exited due to a signal. A core file %s created.", WCOREDUMP(iStatus) ? "was" : "was not");
#endif
  }
  else if (WIFSTOPPED(iStatus))
  {
    snprintf(psProperties->sWEBJOB.acKidReason, MESSAGE_SIZE, "The kid was stopped. This is not normal behavior.");
  }
  else
  {
    snprintf(psProperties->sWEBJOB.acKidReason, MESSAGE_SIZE, "The kid status could not be determined. This should not happen.");
  }
#endif

#ifdef WIN32
  /*-
   *********************************************************************
   *
   * Create a new process and execute the specified command line. If a
   * priority was specified by the user, add it to the creation flags.
   *
   *********************************************************************
   */
  GetStartupInfo(&sStartupInformation);
  if (psProperties->psPutUrl != NULL)
  {
    sStartupInformation.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
    sStartupInformation.hStdOutput = (HANDLE) _get_osfhandle(_fileno(psProperties->sWEBJOB.pStdOut));
    if (sStartupInformation.hStdOutput == (HANDLE) -1)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: _get_osfhandle(): stdout, %s", acRoutine, strerror(errno));
      return ER;
    }
    sStartupInformation.hStdError = (HANDLE) _get_osfhandle(_fileno(psProperties->sWEBJOB.pStdErr));
    if (sStartupInformation.hStdError == (HANDLE) -1)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: _get_osfhandle(): stderr, %s", acRoutine, strerror(errno));
      return ER;
    }
  }
  else
  {
    sStartupInformation.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
    sStartupInformation.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
    sStartupInformation.hStdError = GetStdHandle(STD_ERROR_HANDLE);
  }
  sStartupInformation.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
  sStartupInformation.wShowWindow = SW_HIDE;

  if (psProperties->acPriority[0])
  {
    dwCreationFlags |= (DWORD) psProperties->iPriority;
  }

  bStatus = CreateProcess(
    (psProperties->iRunEmbedded) ? psProperties->sWEBJOB.pcEmbeddedCommand : psProperties->sWEBJOB.pcCommand,
    (psProperties->iRunEmbedded) ? psProperties->sWEBJOB.pcEmbeddedCommandLine : psProperties->sWEBJOB.pcCommandLine,
    0,
    0,
    TRUE,
    dwCreationFlags,
    NULL,
    NULL,
    &sStartupInformation,
    &sProcessInformation
    );
  if (!bStatus)
  {
    FormatWin32Error(GetLastError(), &pcLocalError);
    snprintf(pcError, MESSAGE_SIZE, "%s: CreateProcess(): %s", acRoutine, pcLocalError);
    return ER;
  }

  /*-
   *********************************************************************
   *
   * Wait for the process to finish, but terminate it on timeout.
   *
   *********************************************************************
   */
  for (i = 0; i < 2 && (dwStatus = WaitForSingleObject(sProcessInformation.hProcess, iTimeout)) == WAIT_TIMEOUT; i++)
  {
    fprintf(stderr, "Main(): %s: WaitForSingleObject(): RUN stage timed out after %d seconds.", acRoutine, psProperties->iRunTimeLimit);
    TerminateProcess(sProcessInformation.hProcess, XER_Abort); /* NOTE: This will not kill grandkids. */
  }
  if (dwStatus == WAIT_FAILED)
  {
    FormatWin32Error(GetLastError(), &pcLocalError);
    snprintf(pcError, MESSAGE_SIZE, "%s: WaitForSingleObject(): %s", acRoutine, pcLocalError);
    TerminateProcess(sProcessInformation.hProcess, XER_Abort); /* NOTE: This will not kill grandkids. */
    return ER;
  }

  /*-
   *********************************************************************
   *
   * Determine the kid's exit status.
   *
   *********************************************************************
   */
  if (i == 0)
  {
    snprintf(psProperties->sWEBJOB.acKidReason, MESSAGE_SIZE, "The kid exited cleanly.");
  }
  else
  {
    snprintf(psProperties->sWEBJOB.acKidReason, MESSAGE_SIZE, "The kid was terminated. Any grandkids are now orphans.");
  }
  bStatus = GetExitCodeProcess(sProcessInformation.hProcess, &dwStatus);
  if (!bStatus)
  {
    FormatWin32Error(GetLastError(), &pcLocalError);
    snprintf(pcError, MESSAGE_SIZE, "%s: GetExitCodeProcess(): %s", acRoutine, pcLocalError);
    return ER;
  }
  psProperties->sWEBJOB.iKidPid = (int) sProcessInformation.dwProcessId;
  psProperties->sWEBJOB.iKidStatus = (int) dwStatus;

  CloseHandle(sProcessInformation.hProcess);
#endif

  return ER_OK;
}


/*-
 ***********************************************************************
 *
 * WebJobExportEnvironment
 *
 ***********************************************************************
 */
int
WebJobExportEnvironment(WEBJOB_EXPORT_LIST *psKeyValuePairs, int iNKeyValuePairs, char *pcError)
{
  const char          acRoutine[] = "WebJobExportEnvironment()";
  int                 i = 0;
#ifdef WIN32
  BOOL                bStatus = FALSE;
  char               *pcLocalError = NULL;
#else
  int                 iError = 0;
#endif

#ifdef WIN32
  for (i = 0; i < iNKeyValuePairs; i++)
  {
    bStatus = SetEnvironmentVariable(psKeyValuePairs[i].acKey, psKeyValuePairs[i].pcValue);
    if (!bStatus)
    {
      FormatWin32Error(GetLastError(), &pcLocalError);
      snprintf(pcError, MESSAGE_SIZE, "%s: SetEnvironmentVariable(): KeyValuePair = [%s=%s], %s",
        acRoutine,
        psKeyValuePairs[i].acKey,
        psKeyValuePairs[i].pcValue,
        pcLocalError
        );
      return ER;
    }
  }
#else
#if defined(HAVE_SETENV)
  for (i = 0; i < iNKeyValuePairs; i++)
  {
    iError = setenv(psKeyValuePairs[i].acKey, psKeyValuePairs[i].pcValue, 1);
    if (iError != ER_OK)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: setenv(): KeyValuePair = [%s=%s], %s",
        acRoutine,
        psKeyValuePairs[i].acKey,
        psKeyValuePairs[i].pcValue,
        strerror(errno)
        );
      return ER;
    }
  }
#elif defined(HAVE_PUTENV)
  char               *pcKeyValuePair = NULL;
  int                 iLength = 0;

  for (i = 0; i < iNKeyValuePairs; i++)
  {
    iLength = strlen(psKeyValuePairs[i].acKey) + strlen("=") + strlen(psKeyValuePairs[i].pcValue) + 1;
    pcKeyValuePair = calloc(iLength, 1);
    if (pcKeyValuePair == NULL)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: calloc(): %s", acRoutine, strerror(errno));
      return ER;
    }
    snprintf(pcKeyValuePair, iLength, "%s=%s", psKeyValuePairs[i].acKey, psKeyValuePairs[i].pcValue);
    iError = putenv(pcKeyValuePair); /* Don't free this memory. It belongs to the environment now. */
    if (iError != ER_OK)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: putenv(): KeyValuePair = [%s=%s], %s",
        acRoutine,
        psKeyValuePairs[i].acKey,
        psKeyValuePairs[i].pcValue,
        strerror(errno)
        );
      return ER;
    }
  }
#else
  snprintf(pcError, MESSAGE_SIZE, "%s: setenv()/putenv(): Not supported", acRoutine);
  return ER;
#endif
#endif

  return ER_OK;
}


/*-
 ***********************************************************************
 *
 * WebJobFormatTime
 *
 ***********************************************************************
 */
char *
WebJobFormatTime(time_t time)
{
  static char         acDateTimeZone[64];

#ifdef HAVE_LOCALTIME_R
  /*-
   *********************************************************************
   *
   * NOTE: If a threaded version embedded Perl is used, localtime() is
   * remapped to localtime_r(), and that leads to a bus error on MacOS
   * and possibly other platforms. If both routines are supported on a
   * given platform, opt for the latter one.
   *
   *********************************************************************
   */
  struct tm           sTime = { 0 };

  if (time < 0 || strftime(acDateTimeZone, 64, "%Y-%m-%d %H:%M:%S %Z", localtime_r(&time, &sTime)) == 0)
#else
  if (time < 0 || strftime(acDateTimeZone, 64, "%Y-%m-%d %H:%M:%S %Z", localtime(&time)) == 0)
#endif
  {
    strncpy(acDateTimeZone, "NA", sizeof(acDateTimeZone));
  }
  return acDateTimeZone;
}


/*-
 ***********************************************************************
 *
 * WebJobFreeProperties
 *
 ***********************************************************************
 */
void
WebJobFreeProperties(WEBJOB_PROPERTIES *psProperties)
{
  if (psProperties != NULL)
  {
    if (psProperties->pcStdOutHash != NULL)
    {
      free(psProperties->pcStdOutHash);
    }
    if (psProperties->pcStdErrHash != NULL)
    {
      free(psProperties->pcStdErrHash);
    }
    if (psProperties->sWEBJOB.pcCommandLine != NULL)
    {
      free(psProperties->sWEBJOB.pcCommandLine);
    }
    if (psProperties->sWEBJOB.pcEmbeddedCommandLine != NULL)
    {
      free(psProperties->sWEBJOB.pcEmbeddedCommandLine);
    }
    HttpFreeUrl(psProperties->psGetUrl);
    HttpFreeUrl(psProperties->psPutUrl);
#ifdef USE_SSL
    SslFreeProperties(psProperties->psSslProperties);
#endif
    HookFreeHook(psProperties->psGetHook);
    if (psProperties->pcNonce != NULL)
    {
      free(psProperties->pcNonce);
    }
    if (psProperties->pcDoneDirectory != NULL)
    {
      free(psProperties->pcDoneDirectory);
    }
    if (psProperties->pcTempDirectory != NULL)
    {
      free(psProperties->pcTempDirectory);
    }
    if (psProperties->pcWorkDirectory != NULL)
    {
      free(psProperties->pcWorkDirectory);
    }
    if (psProperties->pcUrlPassword != NULL)
    {
      free(psProperties->pcUrlPassword);
    }
    if (psProperties->pcUrlGetProxyPassword != NULL)
    {
      free(psProperties->pcUrlGetProxyPassword);
    }
    if (psProperties->pcUrlPutProxyPassword != NULL)
    {
      free(psProperties->pcUrlPutProxyPassword);
    }
#ifdef USE_EMBEDDED_LUA
/* FIXME Free all items associated with embedded Lua. */
#endif
#ifdef USE_EMBEDDED_PERL
/* FIXME Free all items associated with embedded Perl. */
#endif
#ifdef USE_DSV
/* FIXME Free all items associated with DSV. */
#endif
    free(psProperties);
  }
}


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

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


/*-
 ***********************************************************************
 *
 * WebJobGetEpoch
 *
 ***********************************************************************
 */
long
WebJobGetEpoch(struct timeval *tvEpoch)
{
  struct timeval      sTimeValue;
#ifdef WIN32
  FILETIME            sFileTime;
  SYSTEMTIME          sSystemTime;
  unsigned __int64    ui64Time;
#endif

  sTimeValue.tv_sec = sTimeValue.tv_usec = -1;

#ifdef WIN32
  GetSystemTime(&sSystemTime);
  if (SystemTimeToFileTime(&sSystemTime, &sFileTime))
  {
    ui64Time = ((unsigned __int64) (sFileTime.dwHighDateTime) << 32) +
                (unsigned __int64) (sFileTime.dwLowDateTime) -
                (unsigned __int64) (UNIX_EPOCH_IN_NT_TIME);
    sTimeValue.tv_sec = (long) (ui64Time / N_100ns_UNITS_IN_1s);
    sTimeValue.tv_usec = (long) ((ui64Time - ((unsigned __int64) sTimeValue.tv_sec * N_100ns_UNITS_IN_1s)) / N_100ns_UNITS_IN_1us);
  }
#else
  gettimeofday(&sTimeValue, NULL);
#endif
  if (tvEpoch != NULL)
  {
    tvEpoch->tv_sec = sTimeValue.tv_sec;
    tvEpoch->tv_usec = sTimeValue.tv_usec;
  }
  return sTimeValue.tv_sec;
}


/*-
 ***********************************************************************
 *
 * WebJobGetHandle
 *
 ***********************************************************************
 */
FILE *
WebJobGetHandle(char *pcFilename, char *pcError)
{
  const char          acRoutine[] = "WebJobGetHandle()";
  FILE               *pFile = NULL;
  int                 iFile = 0;
#ifdef WIN32
  char               *pcLocalError;
  HANDLE              hFile;
  SECURITY_ATTRIBUTES sSecurityAttributes;

  sSecurityAttributes.nLength= sizeof(SECURITY_ATTRIBUTES);
  sSecurityAttributes.lpSecurityDescriptor = NULL;
  sSecurityAttributes.bInheritHandle = TRUE;

  hFile = CreateFile
    (
    pcFilename,
    GENERIC_READ | GENERIC_WRITE,
    0,
    &sSecurityAttributes,
    CREATE_ALWAYS,
    FILE_ATTRIBUTE_NORMAL,
    NULL
    );
  if (hFile == INVALID_HANDLE_VALUE)
  {
    FormatWin32Error(GetLastError(), &pcLocalError);
    snprintf(pcError, MESSAGE_SIZE, "%s: CreateFile(): File = [%s], %s", acRoutine, pcFilename, pcLocalError);
    return NULL;
  }

  iFile = _open_osfhandle((long) hFile, 0);
  if (iFile == ER)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: open_osfhandle(): File = [%s], Handle association failed.", acRoutine, pcFilename);
    return NULL;
  }
#else

  iFile = open(pcFilename, (O_CREAT | O_TRUNC | O_RDWR), 0600);
  if (iFile == ER)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: open(): File = [%s], %s", acRoutine, pcFilename, strerror(errno));
    return NULL;
  }
#endif

  pFile = fdopen(iFile, "wb+");
  if (pFile == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: fdopen(): File = [%s], %s", acRoutine, pcFilename, strerror(errno));
    return NULL;
  }

  return pFile;
}

/*-
 ***********************************************************************
 *
 * WebJobGetPathToSelf
 *
 ***********************************************************************
 */
char *
WebJobGetPathToSelf(char *pcArgumentZero, char *pcError)
{
  const char          acRoutine[] = "WebJobGetPathToSelf()";
  char               *pcPathToSelf = NULL;
#ifdef WIN32
  char               *pcLocalError;
  DWORD               dwLength = 0;
  DWORD               dwSize = 4096;
  DWORD               dwTries = 0;
#else
#if !defined(WEBJOB_LINUX)
  char                acCwd[WEBJOB_MAX_PATH];
  char               *pc = NULL;
  char               *pcCwd = NULL;
  char               *pcDir = NULL;
  char               *pcPath = NULL;
  int                 iDone = 0;
  int                 iLength = 0;
  struct stat         statEntry = { 0 };
#endif
#endif

#ifdef WIN32
  /*-
   *********************************************************************
   *
   * According to the man page, the behavior of GetModuleFileName() is
   * different for XP/2000, and it's possible to get back a truncated
   * path that is not terminated and ERROR_INSUFFICIENT_BUFFER is not
   * set. As such, this code starts by allocating a buffer that should
   * handle most cases -- except for long names in the "\\?\" format.
   * Then, if the buffer appears to be too small, it's size is doubled
   * up to four times before giving up. According to the man page, the
   * the function returns dwSize if the buffer is too small.
   *
   *********************************************************************
   */
  do
  {
    if (pcPathToSelf != NULL)
    {
      free(pcPathToSelf);
      dwSize *= 2;
    }
    pcPathToSelf = calloc(dwSize, 1);
    if (pcPathToSelf == NULL)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: calloc(): %s", acRoutine, strerror(errno));
      return NULL;
    }
    dwLength = GetModuleFileNameA(NULL, pcPathToSelf, dwSize);
    if (dwLength == 0)
    {
      FormatWin32Error(GetLastError(), &pcLocalError);
      snprintf(pcError, MESSAGE_SIZE, "%s: GetModuleFileNameA(): %s", acRoutine, pcLocalError);
      return NULL;
    }
  } while (dwLength == dwSize && dwTries++ < 4);
  if (pcPathToSelf[dwSize - 1] != 0)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: Unable to determine path to self.", acRoutine);
    free(pcPathToSelf);
    return NULL;
  }
  return pcPathToSelf;
#else
#if defined(WEBJOB_LINUX)
  /*-
   *********************************************************************
   *
   * Linux provides "/proc/self/exe", which is very convenient.
   *
   *********************************************************************
   */
  pcPathToSelf = calloc(WEBJOB_MAX_PATH, 1);
  if (pcPathToSelf == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: calloc(): %s", acRoutine, strerror(errno));
    return NULL;
  }
  if (readlink("/proc/self/exe", pcPathToSelf, WEBJOB_MAX_PATH - 1) == ER)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: readlink(): %s", acRoutine, strerror(errno));
    free(pcPathToSelf);
    return NULL;
  }
  return pcPathToSelf;
#else
  /*-
   *********************************************************************
   *
   * If argument zero looks like it's a full path, run with it.
   *
   *********************************************************************
   */
  if (pcArgumentZero[0] == '/')
  {
    return pcArgumentZero;
  }

  /*-
   *********************************************************************
   *
   * The code below is sub-optimal, and there are some race conditions
   * associated with it. For example, it's a well-known fact that the
   * first argument can be an arbitrary value. This implies that
   * simply prepending a given direcotry path won't always work since
   * that directory could have been replaced, renamed, or deleted by
   * the time this routine is executed, and that means the resulting
   * path may be invalid or point to a different (perhaps unwanted)
   * executable. However, if webjob is installed with the correct
   * permissions (including parent directories) and it always runs in
   * the context of the user invoking it, then any scheme to abuse
   * this implementation must already have equal or higher privileges.
   * Therefore, there's nothing to be gained from an attacker's
   * perspective.
   *
   *********************************************************************
   */

  /*-
   *********************************************************************
   *
   * If argument zero has no path separators, search the current PATH.
   * Be sure to handle the following cases:
   *
   *   path_1:path_2   (typical)
   *   :path_1:path_2  (contains NULL which means current directory)
   *   path_1::path_2  (contains NULL which means current directory)
   *   path_1:path_2:  (contains NULL which means current directory)
   *   .:path_1:path_2 (contains  '.' which means current directory)
   *   path_1:.:path_2 (contains  '.' which means current directory)
   *   path_1:path_2:. (contains  '.' which means current directory)
   *
   * If no match is found, fall through to the next section of code.
   *
   * NOTE: The value returned by WebJobGetEnvValue() is a pointer to
   * the actual PATH stored in the environment. Since the for-loop
   * that parses this string may alter it, a local copy is required.
   * Otherwise, the real PATH could get truncated.
   *
   *********************************************************************
   */
  if (strstr(pcArgumentZero, WEBJOB_SLASH) == NULL)
  {
    pc = WebJobGetEnvValue("PATH");
    iLength = strlen(pc);
    pcPath = malloc(iLength + 1);
    if (pcPath == NULL)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: malloc(): %s", acRoutine, strerror(errno));
      return NULL;
    }
    strncpy(pcPath, pc, iLength + 1);
    for (pc = pcDir = pcPath, iDone = 0; pcPath && !iDone; pc++)
    {
      if (*pc == 0)
      {
        iDone = 1; /* The end of pcPath has been reached. */
      }
      if (*pc == ':' || *pc == 0)
      {
        *pc = 0;
        iLength = strlen(pcDir) + strlen(WEBJOB_SLASH) + strlen(pcArgumentZero);
        pcPathToSelf = realloc(pcPathToSelf, iLength + 1);
        if (pcPathToSelf == NULL)
        {
          snprintf(pcError, MESSAGE_SIZE, "%s: realloc(): %s", acRoutine, strerror(errno));
          if (pcPathToSelf)
          {
            free(pcPathToSelf);
          }
          return NULL;
        }
        if (pcDir[0])
        {
          snprintf(pcPathToSelf, iLength + 1, "%s%s%s", pcDir, WEBJOB_SLASH, pcArgumentZero);
        }
        else
        {
          snprintf(pcPathToSelf, iLength + 1, "%s", pcArgumentZero);
        }
        if (stat(pcPathToSelf, &statEntry) == ER_OK)
        {
          if ((statEntry.st_mode & S_IFMT) == S_IFREG)
          {
            return pcPathToSelf;
          }
        }
        pcDir = pc + 1;
      }
    }
  }

  /*-
   *********************************************************************
   *
   * Argument zero is relative, so try prepending the current directory.
   *
   *********************************************************************
   */
  pcCwd = getcwd(acCwd, WEBJOB_MAX_PATH);
  if (pcCwd == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: getcwd(): %s", acRoutine, strerror(errno));
    return NULL;
  }
  iLength = strlen(pcCwd) + strlen(WEBJOB_SLASH) + strlen(pcArgumentZero);
  pcPathToSelf = realloc(pcPathToSelf, iLength + 1);
  if (pcPathToSelf == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: realloc(): %s", acRoutine, strerror(errno));
    if (pcPathToSelf)
    {
      free(pcPathToSelf);
    }
    return NULL;
  }
  snprintf(pcPathToSelf, iLength + 1, "%s%s%s", pcCwd, WEBJOB_SLASH, pcArgumentZero);
  if (stat(pcPathToSelf, &statEntry) == ER_OK)
  {
    if ((statEntry.st_mode & S_IFMT) == S_IFREG)
    {
      return pcPathToSelf;
    }
  }
  snprintf(pcError, MESSAGE_SIZE, "%s: Unable to determine path to self.", acRoutine);
  if (pcPathToSelf)
  {
    free(pcPathToSelf);
  }
  return NULL;
#endif
#endif
}


/*-
 ***********************************************************************
 *
 * WebJobGetPropertiesReference
 *
 ***********************************************************************
 */
WEBJOB_PROPERTIES *
WebJobGetPropertiesReference(void)
{
  return gpsProperties;
}


/*-
 ***********************************************************************
 *
 * WebJobGetUrl
 *
 ***********************************************************************
 */
int
WebJobGetUrl(WEBJOB_PROPERTIES *psProperties, char *pcError)
{
  const char          acRoutine[] = "WebJobGetUrl()";
  char                acLocalError[MESSAGE_SIZE] = "";
  FILE               *pFile = NULL;
  int                 iError = 0;
  HTTP_URL           *psUrl = NULL;
  HTTP_RESPONSE_HDR   sResponseHeader;

  /*-
   *********************************************************************
   *
   * If the URL is not defined, abort.
   *
   *********************************************************************
   */
  psUrl = HttpParseUrl(psProperties->pcUrl, acLocalError);
  if (psUrl == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    return ER;
  }

  /*-
   *********************************************************************
   *
   * Strict Content-Length checks must be disabled since the request is
   * not, in general, going to be going to nph-webjob.cgi, and there is
   * no guarantee that the remote web server will supply this header.
   *
   *********************************************************************
   */
  psUrl->iFlags ^= HTTP_FLAG_CONTENT_LENGTH_OPTIONAL;

  /*-
   *********************************************************************
   *
   * Force the request to use HTTP/1.0 since chunked transfers are not
   * yet supported.
   *
   *********************************************************************
   */
  psUrl->iFlags ^= HTTP_FLAG_USE_HTTP_1_0;

#ifdef USE_SSL
  /*-
   *********************************************************************
   *
   * Initialize SSL Context.
   *
   *********************************************************************
   */
  if (psUrl->iScheme == HTTP_SCHEME_HTTPS)
  {
    if (SslInitializeCTX(psProperties->psSslProperties, acLocalError) == NULL)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
      return ER;
    }
    psUrl->psSslProperties = psProperties->psSslProperties;
  }
#endif

  /*-
   *********************************************************************
   *
   * Conditionally set proxy properties.
   *
   *********************************************************************
   */
  if (psProperties->iUrlGetProxyEnable)
  {
    psUrl->iUseProxy = 1;
    iError = HttpSetUrlProxyHost(psUrl, psProperties->acUrlGetProxyHost, acLocalError);
    if (iError == -1)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
      return ER;
    }
    iError = HttpSetUrlProxyPort(psUrl, psProperties->acUrlGetProxyPort, acLocalError);
    if (iError == -1)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
      return ER;
    }
    if (psProperties->iUrlGetProxyAuthType == HTTP_AUTH_TYPE_BASIC)
    {
      iError = HttpFinalizeProxyCredentials(psUrl, psProperties->acUrlGetProxyUsername, psProperties->pcUrlGetProxyPassword, acLocalError);
      if (iError == -1)
      {
        snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
        return ER;
      }
    }
  }

  /*-
   *********************************************************************
   *
   * Finalize the user's HTTP credentials, if necessary.
   *
   *********************************************************************
   */
  if (psProperties->iUrlAuthType == HTTP_AUTH_TYPE_BASIC)
  {
    iError = HttpFinalizeCredentials(psUrl, psProperties->acUrlUsername, psProperties->pcUrlPassword, acLocalError);
    if (iError == -1)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
      return ER;
    }
  }

  /*-
   *********************************************************************
   *
   * Set the request method.
   *
   *********************************************************************
   */
  iError = HttpSetUrlMeth(psUrl, "GET", acLocalError);
  if (iError != ER_OK)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    return ER;
  }

  /*-
   *********************************************************************
   *
   * Set the download limit.
   *
   *********************************************************************
   */
  HttpSetUrlDownloadLimit(psUrl, (APP_UI32) psProperties->iUrlDownloadLimit);

  /*-
   *********************************************************************
   *
   * Open or set the output file.
   *
   *********************************************************************
   */
/* FIXME This is reserved for future use. */
/*
  if (strcmp(pcFile, "-") == 0)
  {
    pFile = stdout;
  }
  else
  {
    pFile = fopen(pcFile, "wb");
    if (pFile == NULL)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: fopen(): File = [%s], %s", acRoutine, pcFile, strerror(errno));
      return ER;
    }
  }
*/
#ifdef WIN32 /* Prevent CRLF mappings. */
  if (_setmode(_fileno(stdout), _O_BINARY) == -1)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: _setmode(): %s", acRoutine, strerror(errno));
    return ER;
  }
#endif
  pFile = stdout;

  /*-
   *********************************************************************
   *
   * Submit the request, and check the response code.
   *
   *********************************************************************
   */
  iError = HttpSubmitRequest(psUrl, HTTP_IGNORE_INPUT, NULL, HTTP_STREAM_OUTPUT, pFile, &sResponseHeader, acLocalError);
  if (iError != ER_OK)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    if (pFile != stdout)
    {
      WEBJOB_SAFE_FCLOSE(pFile);
    }
    return ER;
  }

  if (sResponseHeader.iStatusCode < 200 || sResponseHeader.iStatusCode > 299)
  {
/* FIXME Add a member to psUrl to hold a string representation of the entire URL. */
    snprintf(pcError, MESSAGE_SIZE, "%s: URL = [%s://%s:%d%s%s%s], Status = [%d], Reason = [%s]",
      acRoutine,
      (psUrl->iScheme == HTTP_SCHEME_HTTPS) ? "https" : "http",
      psUrl->pcHost,
      psUrl->ui16Port,
      psUrl->pcPath,
      (psUrl->pcQuery[0]) ? "?" : "",
      (psUrl->pcQuery[0]) ? psUrl->pcQuery : "",
      sResponseHeader.iStatusCode,
      sResponseHeader.acReasonPhrase
      );
    if (pFile != stdout)
    {
      WEBJOB_SAFE_FCLOSE(pFile);
    }
    return ER;
  }

  /*-
   *********************************************************************
   *
   * Close the output file.
   *
   *********************************************************************
   */
  if (pFile != stdout)
  {
    WEBJOB_SAFE_FCLOSE(pFile);
  }

  return ER_OK;
}


/*-
 ***********************************************************************
 *
 * WebJobHashSum
 *
 ***********************************************************************
 */
void
WebJobHashSum(char *pcFilename, char *pcHashType)
{
  const char          acRoutine[] = "WebJobHashSum()";
  int                 i;
  int                 iHashSize;
  int               (*piHashStream)();
  FILE               *pFile;
  APP_UI64            ui64Size;
  struct stat         statEntry;
  unsigned char      *pucHash;

  /*-
   *********************************************************************
   *
   * Determine hash type and set various hash related parameters.
   *
   *********************************************************************
   */
  if (strcasecmp(pcHashType, WEBJOB_HASHTYPE_MD5) == 0)
  {
    iHashSize = MD5_HASH_SIZE;
    piHashStream = MD5HashStream;
  }
  else if (strcasecmp(pcHashType, WEBJOB_HASHTYPE_SHA1) == 0)
  {
    iHashSize = SHA1_HASH_SIZE;
    piHashStream = SHA1HashStream;
  }
  else if (strcasecmp(pcHashType, WEBJOB_HASHTYPE_SHA256) == 0)
  {
    iHashSize = SHA256_HASH_SIZE;
    piHashStream = SHA256HashStream;
  }
  else
  {
    fprintf(stderr, "%s: HashType (i.e., digest) must be one of [%s|%s|%s].\n",
      acRoutine,
      WEBJOB_HASHTYPE_MD5,
      WEBJOB_HASHTYPE_SHA1,
      WEBJOB_HASHTYPE_SHA256
      );
    exit(XER_Abort);
  }

  /*-
   *********************************************************************
   *
   * Prepare input stream. Abort, if a directory was specified.
   *
   *********************************************************************
   */
  if (strcmp(pcFilename, "-") == 0)
  {
#ifdef WIN32 /* Prevent CRLF mappings. */
    if (_setmode(_fileno(stdin), _O_BINARY) == -1)
    {
      fprintf(stderr, "%s: _setmode(): %s\n", acRoutine, strerror(errno));
      exit(XER_Abort);
    }
#endif
    pFile = stdin;
  }
  else
  {
    if (stat(pcFilename, &statEntry) == -1)
    {
      fprintf(stderr, "%s: stat(): %s\n", acRoutine, strerror(errno));
      exit(XER_Abort);
    }
    if ((statEntry.st_mode & S_IFMT) == S_IFDIR)
    {
      fprintf(stderr, "%s: Directory hashing is not supported.\n", acRoutine);
      exit(XER_Abort);
    }
    if ((pFile = fopen(pcFilename, "rb")) == NULL)
    {
      fprintf(stderr, "%s: fopen(): %s\n", acRoutine, strerror(errno));
      exit(XER_Abort);
    }
  }

  /*-
   *********************************************************************
   *
   * Initialize memory, compute hash, and write it to stdout.
   *
   *********************************************************************
   */
  if ((pucHash = calloc(iHashSize, 1)) == NULL)
  {
    fprintf(stderr, "%s: calloc(): %s\n", acRoutine, strerror(errno));
    WEBJOB_SAFE_FCLOSE(pFile);
    exit(XER_Abort);
  }
  piHashStream(pFile, pucHash, &ui64Size);
  WEBJOB_SAFE_FCLOSE(pFile);
  for (i = 0; i < iHashSize; i++)
  {
    fprintf(stdout, "%02x", pucHash[i]);
  }
  fprintf(stdout, "\n");
  free(pucHash);

  exit(XER_OK);
}


/*-
 ***********************************************************************
 *
 * WebJobNewProperties
 *
 ***********************************************************************
 */
WEBJOB_PROPERTIES *
WebJobNewProperties(char *pcError)
{
  const char          acRoutine[] = "WebJobNewProperties()";
  char                acLocalError[MESSAGE_SIZE] = "";
  char               *pcValue = NULL;
  WEBJOB_PROPERTIES  *psProperties;

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

  /*-
   *********************************************************************
   *
   * Initialize variables that require a non-zero value.
   *
   *********************************************************************
   */
  WebJobGetEpoch(&psProperties->tvJobEpoch);
  psProperties->iRunMode = WEBJOB_RUNMODE;
#ifdef UNIX
  psProperties->iTimeoutSignal = SIGKILL;
#endif
  psProperties->pcHostname = GetHostname();
  psProperties->pcGetFile = WEBJOB_GET_FILE;
  psProperties->pcPutFile = WEBJOB_PUT_FILE;

  /*-
   *********************************************************************
   *
   * Initialize properties that can be set in the environment.
   *
   *********************************************************************
   */
#ifdef WIN32
  pcValue = WebJobGetEnvValue("TEMP");
#else
  pcValue = WebJobGetEnvValue("TMPDIR");
#endif
  if (pcValue == NULL)
  {
    pcValue = WebJobGetEnvValue("TMP");
  }
  if (pcValue == NULL || strlen(pcValue) > sizeof(psProperties->acTempDirectory) - 1)
  {
    pcValue = WEBJOB_TEMP_DIRECTORY;
  }
  strncpy(psProperties->acTempDirectory, pcValue, sizeof(psProperties->acTempDirectory));

  pcValue = WebJobGetEnvValue("WEBJOB_QUEUETAG");
  if (pcValue == NULL || strlen(pcValue) > sizeof(psProperties->acQueueTag) - 1)
  {
    psProperties->acQueueTag[0] = 0;
  }
  else
  {
    strncpy(psProperties->acQueueTag, pcValue, sizeof(psProperties->acQueueTag));
  }

  /*-
   *********************************************************************
   *
   * Initialize RunType variable.
   *
   *********************************************************************
   */
  strncpy(psProperties->acRunType, WEBJOB_RUNTYPE_SNAPSHOT, sizeof(psProperties->acRunType));

  /*-
   *********************************************************************
   *
   * Initialize Hash properties.
   *
   *********************************************************************
   */
  strncpy(psProperties->acHashType, WEBJOB_HASHTYPE_MD5, sizeof(psProperties->acHashType));
  psProperties->iHashSize = MD5_HASH_SIZE;
  psProperties->piHashStream = MD5HashStream;
  psProperties->pcStdOutHash = calloc(psProperties->iHashSize, 1);
  if (psProperties->pcStdOutHash == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: calloc(): %s", acRoutine, strerror(errno));
    WebJobFreeProperties(psProperties);
    return NULL;
  }
  psProperties->pcStdErrHash = calloc(psProperties->iHashSize, 1);
  if (psProperties->pcStdErrHash == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: calloc(): %s", acRoutine, strerror(errno));
    WebJobFreeProperties(psProperties);
    return NULL;
  }

#ifdef USE_SSL
  /*-
   *********************************************************************
   *
   * Initialize SSL properties.
   *
   *********************************************************************
   */
  psProperties->psSslProperties = SslNewProperties(acLocalError);
  if (psProperties->psSslProperties == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    WebJobFreeProperties(psProperties);
    return NULL;
  }
#endif

  /*-
   *********************************************************************
   *
   * Initialize GetHook properties.
   *
   *********************************************************************
   */
  psProperties->psGetHook = HookNewHook(acLocalError);
  if (psProperties->psGetHook == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    WebJobFreeProperties(psProperties);
    return NULL;
  }

#ifdef USE_DSV
  /*-
   *********************************************************************
   *
   * Initialize DSV properties.
   *
   *********************************************************************
   */
  strncpy(psProperties->acDsvCertificateTree, WEBJOB_DSV_CERT_DIRECTORY, WEBJOB_MAX_PATHNAME_LENGTH);
  psProperties->iDsvGetStatus = DSV_SIGNATURE_VERIFICATION_NOFLY;
  psProperties->iDsvPutStatus = DSV_SIGNATURE_VERIFICATION_NOFLY;
#endif

  /*-
   *********************************************************************
   *
   * Initialize Kid properties.
   *
   *********************************************************************
   */
  psProperties->sWEBJOB.iKidPid = -1;
  psProperties->sWEBJOB.iKidStatus = -1;
  psProperties->sWEBJOB.iKidSignal = -1;
  snprintf(psProperties->sWEBJOB.acKidReason, MESSAGE_SIZE, "NA");

  /*-
   *********************************************************************
   *
   * Initialize proxy properties.
   *
   *********************************************************************
   */
  snprintf(psProperties->acUrlGetProxyPort, WEBJOB_MAX_PORT_LENGTH, "%d", HTTP_DEFAULT_PROXY_PORT);
  snprintf(psProperties->acUrlPutProxyPort, WEBJOB_MAX_PORT_LENGTH, "%d", HTTP_DEFAULT_PROXY_PORT);

  return psProperties;
}


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

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

  switch (psOption->iId)
  {
  case OPT_File:
    psProperties->pcConfigList = pcValue;
    break;
  case OPT_HashType:
    psProperties->pcHashType = pcValue;
    break;
  case OPT_NoUpload:
    psProperties->iNoUpload = 1;
    break;
  case OPT_GetTimeLimit:
    if (iLength < 1 || iLength > WEBJOB_MAX_32BIT_NUMBER_LENGTH - 1)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: option=[%s]: Argument length (%d) must be in the range [1-%d].", acRoutine, psOption->atcFullName, iLength, WEBJOB_MAX_32BIT_NUMBER_LENGTH - 1);
      return ER;
    }
    while (iLength > 0)
    {
      if (!isdigit((int) pcValue[iLength - 1]))
      {
        snprintf(pcError, MESSAGE_SIZE, "%s: option=[%s]: Argument must contain all digits.", acRoutine, psOption->atcFullName);
        return ER;
      }
      iLength--;
    }
    iValue = atoi(pcValue);
    if (iValue < WEBJOB_MIN_TIME_LIMIT || iValue > WEBJOB_MAX_TIME_LIMIT)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: option=[%s]: Argument (%d) must be in the range [%d-%d].", acRoutine, psOption->atcFullName, iValue, WEBJOB_MIN_TIME_LIMIT, WEBJOB_MAX_TIME_LIMIT);
      return ER;
    }
    psProperties->iGetTimeLimit = iValue;
    break;
  case OPT_PutTimeLimit:
    if (iLength < 1 || iLength > WEBJOB_MAX_32BIT_NUMBER_LENGTH - 1)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: option=[%s]: Argument length (%d) must be in the range [1-%d].", acRoutine, psOption->atcFullName, iLength, WEBJOB_MAX_32BIT_NUMBER_LENGTH - 1);
      return ER;
    }
    while (iLength > 0)
    {
      if (!isdigit((int) pcValue[iLength - 1]))
      {
        snprintf(pcError, MESSAGE_SIZE, "%s: option=[%s]: Argument must contain all digits.", acRoutine, psOption->atcFullName);
        return ER;
      }
      iLength--;
    }
    iValue = atoi(pcValue);
    if (iValue < WEBJOB_MIN_TIME_LIMIT || iValue > WEBJOB_MAX_TIME_LIMIT)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: option=[%s]: Argument (%d) must be in the range [%d-%d].", acRoutine, psOption->atcFullName, iValue, WEBJOB_MIN_TIME_LIMIT, WEBJOB_MAX_TIME_LIMIT);
      return ER;
    }
    psProperties->iPutTimeLimit = iValue;
    break;
  case OPT_RunTimeLimit:
    if (iLength < 1 || iLength > WEBJOB_MAX_32BIT_NUMBER_LENGTH - 1)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: option=[%s]: Argument length (%d) must be in the range [1-%d].", acRoutine, psOption->atcFullName, iLength, WEBJOB_MAX_32BIT_NUMBER_LENGTH - 1);
      return ER;
    }
    while (iLength > 0)
    {
      if (!isdigit((int) pcValue[iLength - 1]))
      {
        snprintf(pcError, MESSAGE_SIZE, "%s: option=[%s]: Argument must contain all digits.", acRoutine, psOption->atcFullName);
        return ER;
      }
      iLength--;
    }
    iValue = atoi(pcValue);
    if (iValue < WEBJOB_MIN_TIME_LIMIT || iValue > WEBJOB_MAX_TIME_LIMIT)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: option=[%s]: Argument (%d) must be in the range [%d-%d].", acRoutine, psOption->atcFullName, iValue, WEBJOB_MIN_TIME_LIMIT, WEBJOB_MAX_TIME_LIMIT);
      return ER;
    }
    psProperties->iRunTimeLimit = iValue;
    break;
#ifdef UNIX
  case OPT_TimeoutSignal:
    if (iLength < 1 || iLength > WEBJOB_MAX_32BIT_NUMBER_LENGTH - 1)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: option=[%s]: Argument length (%d) must be in the range [1-%d].", acRoutine, psOption->atcFullName, iLength, WEBJOB_MAX_32BIT_NUMBER_LENGTH - 1);
      return ER;
    }
    while (iLength > 0)
    {
      if (!isdigit((int) pcValue[iLength - 1]))
      {
        snprintf(pcError, MESSAGE_SIZE, "%s: option=[%s]: Argument must contain all digits.", acRoutine, psOption->atcFullName);
        return ER;
      }
      iLength--;
    }
    iValue = atoi(pcValue);
    if (iValue < WEBJOB_MIN_SIGNAL || iValue > WEBJOB_MAX_SIGNAL)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: option=[%s]: Argument (%d) must be in the range [%d-%d].", acRoutine, psOption->atcFullName, iValue, WEBJOB_MIN_SIGNAL, WEBJOB_MAX_SIGNAL);
      return ER;
    }
    psProperties->iTimeoutSignal = iValue;
    break;
#endif
  default:
    snprintf(pcError, MESSAGE_SIZE, "%s: Invalid option ID (%d). This should not happen.", acRoutine, psOption->iId);
    return ER;
    break;
  }

  return ER_OK;
}


/*-
 ***********************************************************************
 *
 * WebJobPrepareHandles
 *
 ***********************************************************************
 */
int
WebJobPrepareHandles(WEBJOB_PROPERTIES *psProperties, char *pcError)
{
  const char          acRoutine[] = "WebJobPrepareHandles()";
  char                acLocalError[MESSAGE_SIZE] = "";
  char                aacExtensions[3][4];
  char               *apcFilenames[3];
  FILE              **appFiles[3];
  int                 i;

  /*-
   *********************************************************************
   *
   * If PutUrl is not specified, output goes to the terminal.
   *
   *********************************************************************
   */
  if (psProperties->psPutUrl == NULL)
  {
    psProperties->sWEBJOB.pStdOut = stdout;
    psProperties->sWEBJOB.pStdErr = stderr;
    return ER_OK;
  }

  appFiles[0] = &psProperties->sWEBJOB.pStdOut;
  appFiles[1] = &psProperties->sWEBJOB.pStdErr;
  appFiles[2] = &psProperties->sWEBJOB.pStdEnv;

  apcFilenames[0] = psProperties->acStdOutFile;
  apcFilenames[1] = psProperties->acStdErrFile;
  apcFilenames[2] = psProperties->acStdEnvFile;

  strncpy(aacExtensions[0], "out", sizeof(aacExtensions[0]));
  strncpy(aacExtensions[1], "err", sizeof(aacExtensions[1]));
  strncpy(aacExtensions[2], "env", sizeof(aacExtensions[2]));

  for (i = 0; i < 3; i++)
  {
    snprintf(apcFilenames[i], WEBJOB_MAX_PATHNAME_LENGTH, "%s.%s", PROGRAM_NAME, aacExtensions[i]);
    *appFiles[i] = WebJobGetHandle(apcFilenames[i], acLocalError);
    if (*appFiles[i] == NULL)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: Directory = [%s]: %s", acRoutine, psProperties->pcWorkDirectory, acLocalError);
      return ER;
    }
#ifdef UNIX
    if (psProperties->iUnlinkOutput)
    {
      unlink(apcFilenames[i]); /* Minimize file system availability by unlinking now. */
    }
#endif
  }
  return ER_OK;
}


/*-
 ***********************************************************************
 *
 * WebJobProcessArguments
 *
 ***********************************************************************
 */
int
WebJobProcessArguments(WEBJOB_PROPERTIES *psProperties, int iArgumentCount, char *ppcArgumentVector[], char *pcError)
{
  const char          acRoutine[] = "WebJobProcessArguments()";
  char                acLocalError[MESSAGE_SIZE] = "";
  char               *pcCommand = NULL;
  char               *pcHashFile = NULL;
#if defined(USE_EMBEDDED_LUA) || defined(USE_EMBEDDED_PERL)
  char               *pcInterpreter = NULL;
#endif
  char               *pcMode = NULL;
  int                 iError = 0;
  int                 iOperandIndex = 0;
  int                 iOperandCount = 0;
  OPTIONS_CONTEXT    *psOptionsContext = NULL;
  static OPTIONS_TABLE asExecuteOptions[] =
  {
    { OPT_File, "-f", "--file", 0, 0, 1, 1, WebJobOptionHandler },
    { OPT_NoUpload, "", "--NoUpload", 0, 0, 0, 0, WebJobOptionHandler },
    { OPT_GetTimeLimit, "", "--GetTimeLimit", 0, 0, 1, 0, WebJobOptionHandler },
    { OPT_PutTimeLimit, "", "--PutTimeLimit", 0, 0, 1, 0, WebJobOptionHandler },
    { OPT_RunTimeLimit, "", "--RunTimeLimit", 0, 0, 1, 0, WebJobOptionHandler },
#ifdef UNIX
    { OPT_TimeoutSignal, "", "--TimeoutSignal", 0, 0, 1, 0, WebJobOptionHandler },
#endif
  };
  static OPTIONS_TABLE asGetUrlOptions[] =
  {
    { OPT_File, "-f", "--file", 0, 0, 1, 0, WebJobOptionHandler },
  };
  static OPTIONS_TABLE asHashsumOptions[] =
  {
    { OPT_HashType, "-t", "--type", 0, 0, 1, 1, WebJobOptionHandler },
  };

  /*-
   *********************************************************************
   *
   * 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)
  {
    WebJobUsage();
  }
  else
  {
    if (strcmp(pcMode, "-e") == 0 || strcmp(pcMode, "--execute") == 0)
    {
      psProperties->iRunMode = WEBJOB_RUNMODE;
      OptionsSetOptions(psOptionsContext, asExecuteOptions, (sizeof(asExecuteOptions) / sizeof(asExecuteOptions[0])));
    }
    else if (strcmp(pcMode, "-g") == 0 || strcmp(pcMode, "--get-url") == 0)
    {
      psProperties->iRunMode = WEBJOB_GETMODE;
      OptionsSetOptions(psOptionsContext, asGetUrlOptions, (sizeof(asGetUrlOptions) / sizeof(asGetUrlOptions[0])));
    }
    else if (strcmp(pcMode, "-h") == 0 || strcmp(pcMode, "--hashsum") == 0)
    {
      psProperties->iRunMode = WEBJOB_HASHSUM;
      OptionsSetOptions(psOptionsContext, asHashsumOptions, (sizeof(asHashsumOptions) / sizeof(asHashsumOptions[0])));
    }
#if defined(USE_EMBEDDED_LUA) || defined(USE_EMBEDDED_PERL)
    else if (strcmp(pcMode, "-r") == 0 || strcmp(pcMode, "--run-embedded") == 0)
    {
      psProperties->iRunMode = WEBJOB_RUN_EMBEDDED;
      OptionsSetOptions(psOptionsContext, NULL, 0); /* This mode has no options. */
    }
#endif
    else if (strcmp(pcMode, "-v") == 0 || strcmp(pcMode, "--version") == 0)
    {
      WebJobVersion();
    }
    else
    {
      WebJobUsage();
    }
  }

  /*-
   *********************************************************************
   *
   * 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:
    WebJobUsage();
    break;
  }

  /*-
   *********************************************************************
   *
   * Handle any special cases and/or remaining arguments.
   *
   *********************************************************************
   */
  iOperandCount = OptionsGetArgumentsLeft(psOptionsContext);
  switch (psProperties->iRunMode)
  {
  case WEBJOB_RUNMODE:
    if (iOperandCount < 1)
    {
      WebJobUsage();
    }
    pcCommand = OptionsGetNextArgument(psOptionsContext);
    if (pcCommand == NULL)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: Unable to get command argument.", acRoutine);
      return ER;
    }
    iOperandIndex = OptionsGetArgumentIndex(psOptionsContext);
    psProperties->pcGetFile = pcCommand;
    psProperties->sWEBJOB.pcCommand = pcCommand;
    psProperties->sWEBJOB.pcCommandLine = BuildCommandLine(iOperandCount, &ppcArgumentVector[iOperandIndex], 0);
    psProperties->sWEBJOB.ppcArguments = &ppcArgumentVector[iOperandIndex];
    psProperties->sWEBJOB.iArgumentCount = iOperandCount;
    break;
  case WEBJOB_GETMODE:
    if (iOperandCount != 1)
    {
      WebJobUsage();
    }
    psProperties->pcUrl = OptionsGetNextArgument(psOptionsContext);
    if (psProperties->pcUrl == NULL)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: Unable to get URL argument.", acRoutine);
      return ER;
    }
    break;
  case WEBJOB_HASHSUM:
    if (iOperandCount != 1)
    {
      WebJobUsage();
    }
    pcHashFile = OptionsGetNextArgument(psOptionsContext);
    if (pcHashFile == NULL)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: Unable to get hash file argument.", acRoutine);
      return ER;
    }
    WebJobHashSum(pcHashFile, psProperties->pcHashType);
    break;
#if defined(USE_EMBEDDED_LUA) || defined(USE_EMBEDDED_PERL)
  case WEBJOB_RUN_EMBEDDED:
    if (iOperandCount < 1)
    {
      WebJobUsage();
    }
    pcInterpreter = OptionsGetNextArgument(psOptionsContext);
    if (pcInterpreter == NULL)
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: Unable to get interpreter argument.", acRoutine);
      return ER;
    }
    iOperandIndex = OptionsGetArgumentIndex(psOptionsContext);
#ifdef USE_EMBEDDED_LUA
    if (strcmp(pcInterpreter, "lua") == 0)
    {
      WebJobRunLua(iOperandCount, &ppcArgumentVector[iOperandIndex]);
    }
#endif
#ifdef USE_EMBEDDED_PERL
    if (strcmp(pcInterpreter, "perl") == 0)
    {
      WEBJOB_EXPORT_LIST asKeyValuePairs[] =
      {
        { "PERL5LIB", PERL5LIB },
      };
      iError = WebJobExportEnvironment(asKeyValuePairs, (sizeof(asKeyValuePairs) / sizeof(asKeyValuePairs[0])), acLocalError);
      if (iError != ER_OK)
      {
        snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
        return ER;
      }
      WebJobRunPerl(iOperandCount, &ppcArgumentVector[iOperandIndex]);
    }
#endif
    else
    {
      snprintf(pcError, MESSAGE_SIZE, "%s: Invalid interpreter (%s).", acRoutine, pcInterpreter);
      return ER;
    }
    break;
#endif
  default:
    if (iOperandCount > 0)
    {
      WebJobUsage();
    }
    break;
  }

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

  return ER_OK;
}


#ifdef USE_EMBEDDED_LUA
/*-
 ***********************************************************************
 *
 * WebJobRunLua
 *
 ***********************************************************************
 */
void
WebJobRunLua(int iArgumentCount, char *ppcArgumentVector[])
{
  const char          acRoutine[] = "WebJobRunLua()";
  int                 iStatus = 0;
  lua_State          *psLuaState = NULL;

  psLuaState = luaL_newstate();
  if (psLuaState == NULL)
  {
    fprintf(stderr, "%s: luaL_newstate(): Unable to allocate a Lua interpreter.\n", acRoutine);
    exit(XER_Abort);
  }
  luaL_openlibs(psLuaState);
  iStatus = luaL_dofile(psLuaState, ppcArgumentVector[iArgumentCount - 1]);
  if (iStatus != ER_OK)
  {
    fprintf(stderr, "%s\n", lua_tostring(psLuaState, -1));
  }
  lua_close(psLuaState);
  exit(iStatus);
}
#endif


#ifdef USE_EMBEDDED_PERL
/*-
 ***********************************************************************
 *
 * WebJobRunPerl
 *
 ***********************************************************************
 */
void
WebJobRunPerl(int iArgumentCount, char *ppcArgumentVector[])
{
  const char          acRoutine[] = "WebJobRunPerl()";
  int                 iPerlStatus = 0;
/* FIXME Using a name other than my_perl causes MinGW builds to fail for Perl 5.8.8. */
  PerlInterpreter    *my_perl = NULL;

  PERL_SYS_INIT(&iArgumentCount, &ppcArgumentVector);
  my_perl = perl_alloc();
  if (my_perl == NULL)
  {
    fprintf(stderr, "%s: perl_alloc(): Unable to allocate a Perl interpreter.\n", acRoutine);
    exit(XER_Abort);
  }
  perl_construct(my_perl);
/* FIXME PL_exit_flags is related to the my_perl issue stated above. */
  PL_exit_flags |= PERL_EXIT_DESTRUCT_END; /* Run END in perl_destruct(). */
  iPerlStatus = perl_parse(my_perl, xs_init, iArgumentCount, ppcArgumentVector, NULL);
  if (iPerlStatus == ER_OK)
  {
    iPerlStatus = perl_run(my_perl);
  }
  perl_destruct(my_perl);
  perl_free(my_perl);
  PERL_SYS_TERM();
  exit(iPerlStatus);
}
#endif


/*-
 ***********************************************************************
 *
 * WebJobSetPropertiesReference
 *
 ***********************************************************************
 */
void
WebJobSetPropertiesReference(WEBJOB_PROPERTIES *psProperties)
{
  gpsProperties = psProperties;
}


/*-
 ***********************************************************************
 *
 * WebJobShutdown
 *
 ***********************************************************************
 */
int
WebJobShutdown(WEBJOB_PROPERTIES *psProperties, int iLastError, int iErrorCount)
{
  const char          acRoutine[] = "WebJobShutdown()";
  int                 iFinalError = iLastError;

  /*-
   *********************************************************************
   *
   * Return, if the properties structure has not been defined.
   *
   *********************************************************************
   */
  if (psProperties == NULL)
  {
    return iFinalError;
  }

  /*-
   *********************************************************************
   *
   * Make final cleanup adjustments.  If the error occurred in the
   * configure stage, then the job never got the point where output
   * files would be useful, so they should be deleted.  If there
   * were multiple errors, set the external error to XER_MultiStage.
   *
   *********************************************************************
   */
  if (iLastError != XER_OK)
  {
    if (iLastError == XER_Configure)
    {
      psProperties->iUnlinkOutput = 1;
    }
    if (iErrorCount > 1)
    {
      iFinalError = XER_MultiStage;
    }
  }

  /*-
   *********************************************************************
   *
   * Close/Unlink files as required.
   *
   *********************************************************************
   */
  if (psProperties->iUnlinkExecutable)
  {
    unlink(psProperties->sWEBJOB.pcCommand);
    if (psProperties->psGetHook->iActive)
    {
      unlink(psProperties->pcGetFile);
    }
  }
  if (psProperties->sWEBJOB.pStdOut != stdout)
  {
    WEBJOB_SAFE_FCLOSE(psProperties->sWEBJOB.pStdOut);
  }
  if (psProperties->sWEBJOB.pStdErr != stderr)
  {
    WEBJOB_SAFE_FCLOSE(psProperties->sWEBJOB.pStdErr);
  }
  WEBJOB_SAFE_FCLOSE(psProperties->sWEBJOB.pStdEnv);
  if (psProperties->iUnlinkOutput)
  {
    unlink(psProperties->acStdOutFile);
    unlink(psProperties->acStdErrFile);
    unlink(psProperties->acStdEnvFile);
  }

#ifdef USE_DSV
  if (psProperties->iDsvVerifySignature && psProperties->psPutUrl)
  {
    unlink(psProperties->pcPutFile);
  }
#endif

  /*-
   *********************************************************************
   *
   * Change to the parent directory, which should be the origianl temp
   * directory. Next, tag the work directory as done by renaming it --
   * this can be treated as an indicator to external processes that it
   * is safe to delete the directory. Then, conditionally attempt to
   * delete the done directory. Note that this operation could fail if
   * the job does not clean up after itself. In other words, any files
   * left in the directory by the job will cause the rmdir() to fail
   * since there is no logic here to remove those files.
   *
   *********************************************************************
   */
  if (psProperties->iWorkDirectoryCreated)
  {
    if (chdir((const char *) psProperties->pcTempDirectory) == ER)
    {
      fprintf(stderr, "Main(): %s: chdir(): Unable to change back to %s (%s).\n", acRoutine, psProperties->pcTempDirectory, strerror(errno));
    }

    if (rename(psProperties->pcWorkDirectory, psProperties->pcDoneDirectory) == ER)
    {
      fprintf(stderr, "Main(): %s: rename(): Unable to tag %s as done (%s).\n", acRoutine, psProperties->pcWorkDirectory, strerror(errno));
      return iFinalError + XER_Cleanup;
    }

    if
    (
      (psProperties->psPutUrl == NULL && psProperties->iUnlinkExecutable) ||
      (psProperties->psPutUrl != NULL && psProperties->iUnlinkExecutable && psProperties->iUnlinkOutput)
    )
    {
      if (rmdir(psProperties->pcDoneDirectory) == ER)
      {
        fprintf(stderr, "Main(): %s: rmdir(): Unable to delete %s (%s).\n", acRoutine, psProperties->pcDoneDirectory, strerror(errno));
        return iFinalError + XER_Cleanup;
      }
    }
  }

  /*-
   *********************************************************************
   *
   * Change to the original directory.
   *
   *********************************************************************
   */
#ifdef WIN32
  SetCurrentDirectory(psProperties->acCwd);
#else
  #ifdef HAVE_DIRFD
    if (fchdir(dirfd(psProperties->psCwd)) == ER) { /* Empty */ }
  #else
    if (fchdir(psProperties->psCwd->dd_fd) == ER) { /* Empty */ }
  #endif
#endif

  return iFinalError;
}


#ifdef WIN32
/*-
 ***********************************************************************
 *
 * WebJobThreadWrapper
 *
 ***********************************************************************
 */
DWORD WINAPI
WebJobThreadWrapper(LPVOID lpArgument)
{
  const char          acRoutine[] = "WebJobThreadWrapper()";
  char                acLocalError[MESSAGE_SIZE] = "";
  int                 iError;
  WEBJOB_THREAD      *psThreadArguments;

  psThreadArguments = (WEBJOB_THREAD *) lpArgument;

  iError = psThreadArguments->piRoutine(psThreadArguments->psProperties, acLocalError);
  if (iError != ER_OK)
  {
    snprintf(psThreadArguments->pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    return ER;
  }
  return ER_OK;
}
#endif


/*-
 ***********************************************************************
 *
 * WebJobTimeoutHandler
 *
 ***********************************************************************
 */
#ifdef UNIX
void
WebJobTimeoutHandler()
{
  WEBJOB_PROPERTIES  *psProperties;

  psProperties = WebJobGetPropertiesReference();
  switch (psProperties->iRunStage)
  {
  case WEBJOB_GET_STAGE:
    longjmp(psProperties->sGetEnvironment, ER);
    break;
  case WEBJOB_RUN_STAGE:
    break;
  case WEBJOB_PUT_STAGE:
    longjmp(psProperties->sPutEnvironment, ER);
    break;
  default:
    break;
  }
  return;
}
#endif


/*-
 ***********************************************************************
 *
 * WebJobUsage
 *
 ***********************************************************************
 */
void
WebJobUsage(void)
{
  fprintf(stderr, "\n");
  fprintf(stderr, "Usage: webjob {-e|--execute} {-f|--file} config[,config[...]] program [options]\n");
  fprintf(stderr, "       webjob {-g|--get-url} [{-f|--file} config[,config[...]]] url\n");
  fprintf(stderr, "       webjob {-h|--hashsum} {-t|--type} digest file\n");
#if defined(USE_EMBEDDED_LUA)
  fprintf(stderr, "       webjob {-r|--run-embedded} lua [...]\n");
#endif
#if defined(USE_EMBEDDED_PERL)
  fprintf(stderr, "       webjob {-r|--run-embedded} perl [...]\n");
#endif
  fprintf(stderr, "       webjob {-v|--version}\n");
  fprintf(stderr, "\n");
  exit(XER_Usage);
}


#ifdef USE_DSV
/*-
 ***********************************************************************
 *
 * WebJobVerifySignature
 *
 ***********************************************************************
 */
int
WebJobVerifySignature(WEBJOB_PROPERTIES *psProperties, HTTP_RESPONSE_HDR *psResponseHeader, char *pcError)
{
  const char          acRoutine[] = "WebJobVerifySignature()";
  char                acLocalError[MESSAGE_SIZE] = "";
  DSV_SIGNATURE      *psDsvPayloadSignature = NULL;
  FILE               *pPayloadFile = NULL;
  int                 iError = 0;
  int                 iLength = 0;
  int                *piDsvStatus = NULL;
  unsigned char      *pucBase64Signature = NULL;

  if (psProperties->iRunStage == WEBJOB_GET_STAGE)
  {
    pPayloadFile = psProperties->pGetFile;
    psDsvPayloadSignature = psProperties->psDsvGetSignature;
    piDsvStatus = &psProperties->iDsvGetStatus;
  }
  else
  {
    pPayloadFile = psProperties->pPutFile;
    psDsvPayloadSignature = psProperties->psDsvPutSignature;
    piDsvStatus = &psProperties->iDsvPutStatus;
  }

  if (!psResponseHeader->iWebJobPayloadSignatureFound || psResponseHeader->acWebJobPayloadSignature[0] == 0)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: Payload signature is missing or undefined.", acRoutine);
    return ER;
  }

  rewind(pPayloadFile);

  iError = DsvProcessPayloadStream(pPayloadFile, psDsvPayloadSignature, acLocalError);
  if (iError != ER_OK)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    return ER;
  }

  iLength = strlen(psResponseHeader->acWebJobPayloadSignature);
  pucBase64Signature = (unsigned char *) calloc(iLength + 1, 1);
  if (pucBase64Signature == NULL)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: calloc(): %s", acRoutine, strerror(errno));
    return ER;
  }
  strncpy((char *) pucBase64Signature, psResponseHeader->acWebJobPayloadSignature, iLength + 1);
  psDsvPayloadSignature->iBase64Length = iLength;
  psDsvPayloadSignature->pucBase64Signature = pucBase64Signature;

  iError = DsvDecodeSignature(psDsvPayloadSignature, acLocalError);
  if (iError != ER_OK)
  {
    snprintf(pcError, MESSAGE_SIZE, "%s: %s", acRoutine, acLocalError);
    return ER;
  }

  *piDsvStatus = DsvVerifySignature(psProperties->psDsvCertList, DSV_VERIFY_VIA_TREE, psDsvPayloadSignature, acLocalError);
  switch (*piDsvStatus)
  {
  case DSV_SIGNATURE_VERIFICATION_ERROR:
    snprintf(pcError, MESSAGE_SIZE, "%s: Signature verification failed due to errors.: %s", acRoutine, acLocalError);
    return ER;
    break;
  case DSV_SIGNATURE_VERIFICATION_FAILED:
    snprintf(pcError, MESSAGE_SIZE, "%s: Signature verification failed.", acRoutine);
    return ER;
    break;
  case DSV_SIGNATURE_VERIFICATION_PASSED:
    break;
  default:
    snprintf(pcError, MESSAGE_SIZE, "%s: Invalid verification status (%d). That shouldn't happen!", acRoutine, *piDsvStatus);
    return ER;
    break;
  }

  return ER_OK;
}
#endif


/*-
 ***********************************************************************
 *
 * WebJobVersion
 *
 ***********************************************************************
 */
void
WebJobVersion(void)
{
  fprintf(stdout, "%s\n", VersionGetVersion());
  exit(XER_OK);
}
