/*
 * TLS support for CUPS on Windows using the Security Support Provider
 * Interface (SSPI).
 *
 * Copyright © 2020-2022 by OpenPrinting.
 * Copyright © 2010-2018 by Apple Inc.
 *
 * Licensed under Apache License v2.0.  See the file "LICENSE" for more
 * information.
 */

/**** This file is included from tls.c ****/

/*
 * Include necessary headers...
 */

#include "debug-private.h"
#include <string.h>


/*
 * Include necessary libraries...
 */

#pragma comment(lib, "Crypt32.lib")
#pragma comment(lib, "Secur32.lib")
#pragma comment(lib, "Ws2_32.lib")


/*
 * Constants...
 */

#ifndef SECURITY_FLAG_IGNORE_UNKNOWN_CA
#  define SECURITY_FLAG_IGNORE_UNKNOWN_CA         0x00000100 /* Untrusted root */
#endif /* SECURITY_FLAG_IGNORE_UNKNOWN_CA */

#ifndef SECURITY_FLAG_IGNORE_CERT_CN_INVALID
#  define SECURITY_FLAG_IGNORE_CERT_CN_INVALID	  0x00001000 /* Common name does not match */
#endif /* !SECURITY_FLAG_IGNORE_CERT_CN_INVALID */

#ifndef SECURITY_FLAG_IGNORE_CERT_DATE_INVALID
#  define SECURITY_FLAG_IGNORE_CERT_DATE_INVALID  0x00002000 /* Expired X509 Cert. */
#endif /* !SECURITY_FLAG_IGNORE_CERT_DATE_INVALID */


/*
 * Local globals...
 */

static int		tls_options = -1,/* Options for TLS connections */
			tls_min_version = _HTTP_TLS_1_0,
			tls_max_version = _HTTP_TLS_MAX;


/*
 * Local functions...
 */

static _http_sspi_t *http_sspi_alloc(void);
static int	http_sspi_client(http_t *http, const char *hostname);
static PCCERT_CONTEXT http_sspi_create_credential(http_credential_t *cred);
static BOOL	http_sspi_find_credentials(http_t *http, const LPWSTR containerName, const char *common_name);
static void	http_sspi_free(_http_sspi_t *sspi);
static BOOL	http_sspi_make_credentials(_http_sspi_t *sspi, const LPWSTR containerName, const char *common_name, _http_mode_t mode, int years);
static int	http_sspi_server(http_t *http, const char *hostname);
static void	http_sspi_set_error(const char *title);
static const char *http_sspi_strerror(char *buffer, size_t bufsize, DWORD code);
static DWORD	http_sspi_verify(PCCERT_CONTEXT cert, const char *common_name, DWORD dwCertFlags);


/*
 * 'cupsMakeServerCredentials()' - Make a self-signed certificate and private key pair.
 */

int					/* O - 1 on success, 0 on failure */
cupsMakeServerCredentials(
    const char *path,			/* I - Keychain path or @code NULL@ for default */
    const char *common_name,		/* I - Common name */
    int        num_alt_names,		/* I - Number of subject alternate names */
    const char **alt_names,		/* I - Subject Alternate Names */
    time_t     expiration_date)		/* I - Expiration date */
{
  _http_sspi_t	*sspi;			/* SSPI data */
  int		ret;			/* Return value */


  DEBUG_printf(("cupsMakeServerCredentials(path=\"%s\", common_name=\"%s\", num_alt_names=%d, alt_names=%p, expiration_date=%d)", path, common_name, num_alt_names, alt_names, (int)expiration_date));

  (void)path;
  (void)num_alt_names;
  (void)alt_names;

  sspi = http_sspi_alloc();
  ret  = http_sspi_make_credentials(sspi, L"ServerContainer", common_name, _HTTP_MODE_SERVER, (int)((expiration_date - time(NULL) + 86399) / 86400 / 365));

  http_sspi_free(sspi);

  return (ret);
}


/*
 * 'cupsSetServerCredentials()' - Set the default server credentials.
 *
 * Note: The server credentials are used by all threads in the running process.
 * This function is threadsafe.
 */

int					/* O - 1 on success, 0 on failure */
cupsSetServerCredentials(
    const char *path,			/* I - Keychain path or @code NULL@ for default */
    const char *common_name,		/* I - Default common name for server */
    int        auto_create)		/* I - 1 = automatically create self-signed certificates */
{
  DEBUG_printf(("cupsSetServerCredentials(path=\"%s\", common_name=\"%s\", auto_create=%d)", path, common_name, auto_create));

  (void)path;
  (void)common_name;
  (void)auto_create;

  return (0);
}


/*
 * 'httpCopyCredentials()' - Copy the credentials associated with the peer in
 *                           an encrypted connection.
 */

int					/* O - Status of call (0 = success) */
httpCopyCredentials(
    http_t	 *http,			/* I - Connection to server */
    cups_array_t **credentials)		/* O - Array of credentials */
{
  DEBUG_printf(("httpCopyCredentials(http=%p, credentials=%p)", http, credentials));

  if (!http || !http->tls || !http->tls->remoteCert || !credentials)
  {
    if (credentials)
      *credentials = NULL;

    return (-1);
  }

  *credentials = cupsArrayNew(NULL, NULL);
  httpAddCredential(*credentials, http->tls->remoteCert->pbCertEncoded, http->tls->remoteCert->cbCertEncoded);

  return (0);
}


/*
 * '_httpCreateCredentials()' - Create credentials in the internal format.
 */

http_tls_credentials_t			/* O - Internal credentials */
_httpCreateCredentials(
    cups_array_t *credentials)		/* I - Array of credentials */
{
  return (http_sspi_create_credential((http_credential_t *)cupsArrayFirst(credentials)));
}


/*
 * 'httpCredentialsAreValidForName()' - Return whether the credentials are valid for the given name.
 */

int					/* O - 1 if valid, 0 otherwise */
httpCredentialsAreValidForName(
    cups_array_t *credentials,		/* I - Credentials */
    const char   *common_name)		/* I - Name to check */
{
  int		valid = 1;		/* Valid name? */
  PCCERT_CONTEXT cert = http_sspi_create_credential((http_credential_t *)cupsArrayFirst(credentials));
					/* Certificate */
  char		cert_name[1024];	/* Name from certificate */


  if (cert)
  {
    if (CertNameToStrA(X509_ASN_ENCODING, &(cert->pCertInfo->Subject), CERT_SIMPLE_NAME_STR, cert_name, sizeof(cert_name)))
    {
     /*
      * Extract common name at end...
      */

      char  *ptr = strrchr(cert_name, ',');
      if (ptr && ptr[1])
        _cups_strcpy(cert_name, ptr + 2);
    }
    else
      strlcpy(cert_name, "unknown", sizeof(cert_name));

    CertFreeCertificateContext(cert);
  }
  else
    strlcpy(cert_name, "unknown", sizeof(cert_name));

 /*
  * Compare the common names...
  */

  if (_cups_strcasecmp(common_name, cert_name))
  {
   /*
    * Not an exact match for the common name, check for wildcard certs...
    */

    const char	*domain = strchr(common_name, '.');
					/* Domain in common name */

    if (strncmp(cert_name, "*.", 2) || !domain || _cups_strcasecmp(domain, cert_name + 1))
    {
     /*
      * Not a wildcard match.
      */

      /* TODO: Check subject alternate names */
      valid = 0;
    }
  }

  return (valid);
}


/*
 * 'httpCredentialsGetTrust()' - Return the trust of credentials.
 */

http_trust_t				/* O - Level of trust */
httpCredentialsGetTrust(
    cups_array_t *credentials,		/* I - Credentials */
    const char   *common_name)		/* I - Common name for trust lookup */
{
  http_trust_t	trust = HTTP_TRUST_OK;	/* Level of trust */
  PCCERT_CONTEXT cert = NULL;		/* Certificate to validate */
  DWORD		certFlags = 0;		/* Cert verification flags */
  _cups_globals_t *cg = _cupsGlobals();	/* Per-thread global data */


  if (!common_name)
    return (HTTP_TRUST_UNKNOWN);

  cert = http_sspi_create_credential((http_credential_t *)cupsArrayFirst(credentials));
  if (!cert)
    return (HTTP_TRUST_UNKNOWN);

  if (cg->any_root < 0)
    _cupsSetDefaults();

  if (cg->any_root)
    certFlags |= SECURITY_FLAG_IGNORE_UNKNOWN_CA;

  if (cg->expired_certs)
    certFlags |= SECURITY_FLAG_IGNORE_CERT_DATE_INVALID;

  if (!cg->validate_certs)
    certFlags |= SECURITY_FLAG_IGNORE_CERT_CN_INVALID;

  if (http_sspi_verify(cert, common_name, certFlags) != SEC_E_OK)
    trust = HTTP_TRUST_INVALID;

  CertFreeCertificateContext(cert);

  return (trust);
}


/*
 * 'httpCredentialsGetExpiration()' - Return the expiration date of the credentials.
 */

time_t					/* O - Expiration date of credentials */
httpCredentialsGetExpiration(
    cups_array_t *credentials)		/* I - Credentials */
{
  time_t	expiration_date = 0;	/* Expiration data of credentials */
  PCCERT_CONTEXT cert = http_sspi_create_credential((http_credential_t *)cupsArrayFirst(credentials));
					/* Certificate */

  if (cert)
  {
    SYSTEMTIME	systime;		/* System time */
    struct tm	tm;			/* UNIX date/time */

    FileTimeToSystemTime(&(cert->pCertInfo->NotAfter), &systime);

    tm.tm_year = systime.wYear - 1900;
    tm.tm_mon  = systime.wMonth - 1;
    tm.tm_mday = systime.wDay;
    tm.tm_hour = systime.wHour;
    tm.tm_min  = systime.wMinute;
    tm.tm_sec  = systime.wSecond;

    expiration_date = mktime(&tm);

    CertFreeCertificateContext(cert);
  }

  return (expiration_date);
}


/*
 * 'httpCredentialsString()' - Return a string representing the credentials.
 */

size_t					/* O - Total size of credentials string */
httpCredentialsString(
    cups_array_t *credentials,		/* I - Credentials */
    char         *buffer,		/* I - Buffer or @code NULL@ */
    size_t       bufsize)		/* I - Size of buffer */
{
  http_credential_t	*first = (http_credential_t *)cupsArrayFirst(credentials);
					/* First certificate */
  PCCERT_CONTEXT 	cert;		/* Certificate */


  DEBUG_printf(("httpCredentialsString(credentials=%p, buffer=%p, bufsize=" CUPS_LLFMT ")", credentials, buffer, CUPS_LLCAST bufsize));

  if (!buffer)
    return (0);

  if (buffer && bufsize > 0)
    *buffer = '\0';

  cert = http_sspi_create_credential(first);

  if (cert)
  {
    char		cert_name[256];	/* Common name */
    SYSTEMTIME		systime;	/* System time */
    struct tm		tm;		/* UNIX date/time */
    time_t		expiration;	/* Expiration date of cert */
    unsigned char	md5_digest[16];	/* MD5 result */

    FileTimeToSystemTime(&(cert->pCertInfo->NotAfter), &systime);

    tm.tm_year = systime.wYear - 1900;
    tm.tm_mon  = systime.wMonth - 1;
    tm.tm_mday = systime.wDay;
    tm.tm_hour = systime.wHour;
    tm.tm_min  = systime.wMinute;
    tm.tm_sec  = systime.wSecond;

    expiration = mktime(&tm);

    if (CertNameToStrA(X509_ASN_ENCODING, &(cert->pCertInfo->Subject), CERT_SIMPLE_NAME_STR, cert_name, sizeof(cert_name)))
    {
     /*
      * Extract common name at end...
      */

      char  *ptr = strrchr(cert_name, ',');
      if (ptr && ptr[1])
        _cups_strcpy(cert_name, ptr + 2);
    }
    else
      strlcpy(cert_name, "unknown", sizeof(cert_name));

    cupsHashData("md5", first->data, first->datalen, md5_digest, sizeof(md5_digest));

    snprintf(buffer, bufsize, "%s / %s / %02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", cert_name, httpGetDateString(expiration), md5_digest[0], md5_digest[1], md5_digest[2], md5_digest[3], md5_digest[4], md5_digest[5], md5_digest[6], md5_digest[7], md5_digest[8], md5_digest[9], md5_digest[10], md5_digest[11], md5_digest[12], md5_digest[13], md5_digest[14], md5_digest[15]);

    CertFreeCertificateContext(cert);
  }

  DEBUG_printf(("1httpCredentialsString: Returning \"%s\".", buffer));

  return (strlen(buffer));
}


/*
 * '_httpFreeCredentials()' - Free internal credentials.
 */

void
_httpFreeCredentials(
    http_tls_credentials_t credentials)	/* I - Internal credentials */
{
  if (!credentials)
    return;

  CertFreeCertificateContext(credentials);
}


/*
 * 'httpLoadCredentials()' - Load X.509 credentials from a keychain file.
 */

int					/* O - 0 on success, -1 on error */
httpLoadCredentials(
    const char   *path,			/* I  - Keychain path or @code NULL@ for default */
    cups_array_t **credentials,		/* IO - Credentials */
    const char   *common_name)		/* I  - Common name for credentials */
{
  HCERTSTORE	store = NULL;		/* Certificate store */
  PCCERT_CONTEXT storedContext = NULL;	/* Context created from the store */
  DWORD		dwSize = 0; 		/* 32 bit size */
  PBYTE		p = NULL;		/* Temporary storage */
  HCRYPTPROV	hProv = (HCRYPTPROV)NULL;
					/* Handle to a CSP */
  CERT_NAME_BLOB sib;			/* Arbitrary array of bytes */
#ifdef DEBUG
  char		error[1024];		/* Error message buffer */
#endif /* DEBUG */


  DEBUG_printf(("httpLoadCredentials(path=\"%s\", credentials=%p, common_name=\"%s\")", path, credentials, common_name));

  (void)path;

  if (credentials)
  {
    *credentials = NULL;
  }
  else
  {
    DEBUG_puts("1httpLoadCredentials: NULL credentials pointer, returning -1.");
    return (-1);
  }

  if (!common_name)
  {
    DEBUG_puts("1httpLoadCredentials: Bad common name, returning -1.");
    return (-1);
  }

  if (!CryptAcquireContextW(&hProv, L"RememberedContainer", MS_DEF_PROV_W, PROV_RSA_FULL, CRYPT_NEWKEYSET /*| CRYPT_MACHINE_KEYSET*/))
  {
    if (GetLastError() == NTE_EXISTS)
    {
      if (!CryptAcquireContextW(&hProv, L"RememberedContainer", MS_DEF_PROV_W, PROV_RSA_FULL, 0 /*CRYPT_MACHINE_KEYSET*/))
      {
        DEBUG_printf(("1httpLoadCredentials: CryptAcquireContext failed: %s", http_sspi_strerror(error, sizeof(error), GetLastError())));
        http_sspi_set_error("CryptAquireContext");
        goto cleanup;
      }
    }
  }

  store = CertOpenStore(CERT_STORE_PROV_SYSTEM, X509_ASN_ENCODING|PKCS_7_ASN_ENCODING, hProv, CERT_SYSTEM_STORE_LOCAL_MACHINE | CERT_STORE_NO_CRYPT_RELEASE_FLAG | CERT_STORE_OPEN_EXISTING_FLAG, L"MY");

  if (!store)
  {
    DEBUG_printf(("1httpLoadCredentials: CertOpenSystemStore failed: %s", http_sspi_strerror(error, sizeof(error), GetLastError())));
    http_sspi_set_error("CertOpenSystemStore");
    goto cleanup;
  }

  dwSize = 0;

  if (!CertStrToNameA(X509_ASN_ENCODING, common_name, CERT_OID_NAME_STR, NULL, NULL, &dwSize, NULL))
  {
    DEBUG_printf(("1httpLoadCredentials: CertStrToName failed: %s", http_sspi_strerror(error, sizeof(error), GetLastError())));
    http_sspi_set_error("CertStrToName");
    goto cleanup;
  }

  p = (PBYTE)malloc(dwSize);

  if (!p)
  {
    DEBUG_printf(("1httpLoadCredentials: malloc failed for %d bytes.", dwSize));
    _cupsSetHTTPError(HTTP_STATUS_ERROR);
    goto cleanup;
  }

  if (!CertStrToNameA(X509_ASN_ENCODING, common_name, CERT_OID_NAME_STR, NULL, p, &dwSize, NULL))
  {
    DEBUG_printf(("1httpLoadCredentials: CertStrToName failed: %s", http_sspi_strerror(error, sizeof(error), GetLastError())));
    http_sspi_set_error("CertStrToName");
    goto cleanup;
  }

  sib.cbData = dwSize;
  sib.pbData = p;

  storedContext = CertFindCertificateInStore(store, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0, CERT_FIND_SUBJECT_NAME, &sib, NULL);

  if (!storedContext)
  {
    DEBUG_printf(("1httpLoadCredentials: Unable to find credentials for \"%s\".", common_name));
    goto cleanup;
  }

  *credentials = cupsArrayNew(NULL, NULL);
  httpAddCredential(*credentials, storedContext->pbCertEncoded, storedContext->cbCertEncoded);

cleanup:

 /*
  * Cleanup
  */

  if (storedContext)
    CertFreeCertificateContext(storedContext);

  if (p)
    free(p);

  if (store)
    CertCloseStore(store, 0);

  if (hProv)
    CryptReleaseContext(hProv, 0);

  DEBUG_printf(("1httpLoadCredentials: Returning %d.", *credentials ? 0 : -1));

  return (*credentials ? 0 : -1);
}


/*
 * 'httpSaveCredentials()' - Save X.509 credentials to a keychain file.
 */

int					/* O - -1 on error, 0 on success */
httpSaveCredentials(
    const char   *path,			/* I - Keychain path or @code NULL@ for default */
    cups_array_t *credentials,		/* I - Credentials */
    const char   *common_name)		/* I - Common name for credentials */
{
  HCERTSTORE	store = NULL;		/* Certificate store */
  PCCERT_CONTEXT storedContext = NULL;	/* Context created from the store */
  PCCERT_CONTEXT createdContext = NULL;	/* Context created by us */
  DWORD		dwSize = 0; 		/* 32 bit size */
  PBYTE		p = NULL;		/* Temporary storage */
  HCRYPTPROV	hProv = (HCRYPTPROV)NULL;
					/* Handle to a CSP */
  CRYPT_KEY_PROV_INFO ckp;		/* Handle to crypto key */
  int		ret = -1;		/* Return value */
#ifdef DEBUG
  char		error[1024];		/* Error message buffer */
#endif /* DEBUG */


  DEBUG_printf(("httpSaveCredentials(path=\"%s\", credentials=%p, common_name=\"%s\")", path, credentials, common_name));

  (void)path;

  if (!common_name)
  {
    DEBUG_puts("1httpSaveCredentials: Bad common name, returning -1.");
    return (-1);
  }

  createdContext = http_sspi_create_credential((http_credential_t *)cupsArrayFirst(credentials));
  if (!createdContext)
  {
    DEBUG_puts("1httpSaveCredentials: Bad credentials, returning -1.");
    return (-1);
  }

  if (!CryptAcquireContextW(&hProv, L"RememberedContainer", MS_DEF_PROV_W, PROV_RSA_FULL, CRYPT_NEWKEYSET /*| CRYPT_MACHINE_KEYSET*/))
  {
    if (GetLastError() == NTE_EXISTS)
    {
      if (!CryptAcquireContextW(&hProv, L"RememberedContainer", MS_DEF_PROV_W, PROV_RSA_FULL, 0 /*CRYPT_MACHINE_KEYSET*/))
      {
        DEBUG_printf(("1httpSaveCredentials: CryptAcquireContext failed: %s", http_sspi_strerror(error, sizeof(error), GetLastError())));
        http_sspi_set_error("CryptAquireContext");
        goto cleanup;
      }
    }
  }

  store = CertOpenStore(CERT_STORE_PROV_SYSTEM, X509_ASN_ENCODING|PKCS_7_ASN_ENCODING, hProv, CERT_SYSTEM_STORE_LOCAL_MACHINE | CERT_STORE_NO_CRYPT_RELEASE_FLAG | CERT_STORE_OPEN_EXISTING_FLAG, L"MY");

  if (!store)
  {
    DEBUG_printf(("1httpSaveCredentials: CertOpenSystemStore failed: %s", http_sspi_strerror(error, sizeof(error), GetLastError())));
    http_sspi_set_error("CertOpenSystemStore");
    goto cleanup;
  }

  dwSize = 0;

  if (!CertStrToNameA(X509_ASN_ENCODING, common_name, CERT_OID_NAME_STR, NULL, NULL, &dwSize, NULL))
  {
    DEBUG_printf(("1httpSaveCredentials: CertStrToName failed: %s", http_sspi_strerror(error, sizeof(error), GetLastError())));
    http_sspi_set_error("CertStrToName");
    goto cleanup;
  }

  p = (PBYTE)malloc(dwSize);

  if (!p)
  {
    DEBUG_printf(("1httpSaveCredentials: malloc failed for %d bytes.", dwSize));
    _cupsSetHTTPError(HTTP_STATUS_ERROR);
    goto cleanup;
  }

  if (!CertStrToNameA(X509_ASN_ENCODING, common_name, CERT_OID_NAME_STR, NULL, p, &dwSize, NULL))
  {
    DEBUG_printf(("1httpSaveCredentials: CertStrToName failed: %s", http_sspi_strerror(error, sizeof(error), GetLastError())));
    http_sspi_set_error("CertToToName");
    goto cleanup;
  }

 /*
  * Add the created context to the named store, and associate it with the named
  * container...
  */

  if (!CertAddCertificateContextToStore(store, createdContext, CERT_STORE_ADD_REPLACE_EXISTING, &storedContext))
  {
    DEBUG_printf(("1httpSaveCredentials: CertAddCertificateContextToStore failed: %s", http_sspi_strerror(error, sizeof(error), GetLastError())));
    http_sspi_set_error("CertAddCertificateContextToStore");
    goto cleanup;
  }

  ZeroMemory(&ckp, sizeof(ckp));
  ckp.pwszContainerName = L"RememberedContainer";
  ckp.pwszProvName      = MS_DEF_PROV_W;
  ckp.dwProvType        = PROV_RSA_FULL;
  ckp.dwFlags           = 0 /*CRYPT_MACHINE_KEYSET*/;
  ckp.dwKeySpec         = AT_KEYEXCHANGE;

  if (!CertSetCertificateContextProperty(storedContext, CERT_KEY_PROV_INFO_PROP_ID, 0, &ckp))
  {
    DEBUG_printf(("1httpSaveCredentials: CertSetCertificateContextProperty failed: %s", http_sspi_strerror(error, sizeof(error), GetLastError())));
    http_sspi_set_error("CertSetCertificateContextProperty");
    goto cleanup;
  }

  ret = 0;

cleanup:

 /*
  * Cleanup
  */

  if (createdContext)
    CertFreeCertificateContext(createdContext);

  if (storedContext)
    CertFreeCertificateContext(storedContext);

  if (p)
    free(p);

  if (store)
    CertCloseStore(store, 0);

  if (hProv)
    CryptReleaseContext(hProv, 0);

  DEBUG_printf(("1httpSaveCredentials: Returning %d.", ret));
  return (ret);
}


/*
 * '_httpTLSInitialize()' - Initialize the TLS stack.
 */

void
_httpTLSInitialize(void)
{
 /*
  * Nothing to do...
  */
}


/*
 * '_httpTLSPending()' - Return the number of pending TLS-encrypted bytes.
 */

size_t					/* O - Bytes available */
_httpTLSPending(http_t *http)		/* I - HTTP connection */
{
  if (http->tls)
  {
    DEBUG_printf(("4_httpTLSPending: Returning %d.", http->tls->readBufferUsed + http->tls->decryptBufferUsed));
    return (http->tls->readBufferUsed + http->tls->decryptBufferUsed);
  }
  else
  {
    DEBUG_puts("4_httpTLSPending: Returning 0.");
    return (0);
  }
}


/*
 * '_httpTLSRead()' - Read from a SSL/TLS connection.
 */

int					/* O - Bytes read */
_httpTLSRead(http_t *http,		/* I - HTTP connection */
	     char   *buf,		/* I - Buffer to store data */
	     int    len)		/* I - Length of buffer */
{
  int		i;			/* Looping var */
  _http_sspi_t	*sspi = http->tls;	/* SSPI data */
  SecBufferDesc	message;		/* Array of SecBuffer struct */
  SecBuffer	buffers[4] = { 0 };	/* Security package buffer */
  int		num = 0;		/* Return value */
  PSecBuffer	pDataBuffer;		/* Data buffer */
  PSecBuffer	pExtraBuffer;		/* Excess data buffer */
  SECURITY_STATUS scRet;		/* SSPI status */


  DEBUG_printf(("4_httpTLSRead(http=%p, buf=%p, len=%d)", http, buf, len));

 /*
  * If there are bytes that have already been decrypted and have not yet been
  * read, return those...
  */

  if (sspi->readBufferUsed > 0)
  {
    int bytesToCopy = min(sspi->readBufferUsed, len);
					/* Number of bytes to copy */

    memcpy(buf, sspi->readBuffer, bytesToCopy);
    sspi->readBufferUsed -= bytesToCopy;

    if (sspi->readBufferUsed > 0)
      memmove(sspi->readBuffer, sspi->readBuffer + bytesToCopy, sspi->readBufferUsed);

    DEBUG_printf(("5_httpTLSRead: Returning %d bytes previously decrypted.", bytesToCopy));

    return (bytesToCopy);
  }

 /*
  * Initialize security buffer structs
  */

  message.ulVersion = SECBUFFER_VERSION;
  message.cBuffers  = 4;
  message.pBuffers  = buffers;

  do
  {
   /*
    * If there is not enough space in the buffer, then increase its size...
    */

    if (sspi->decryptBufferLength <= sspi->decryptBufferUsed)
    {
      BYTE *temp;			/* New buffer */

      if (sspi->decryptBufferLength >= 262144)
      {
	WSASetLastError(E_OUTOFMEMORY);
        DEBUG_puts("5_httpTLSRead: Decryption buffer too large (>256k)");
	return (-1);
      }

      if ((temp = realloc(sspi->decryptBuffer, sspi->decryptBufferLength + 4096)) == NULL)
      {
	DEBUG_printf(("5_httpTLSRead: Unable to allocate %d byte decryption buffer.", sspi->decryptBufferLength + 4096));
	WSASetLastError(E_OUTOFMEMORY);
	return (-1);
      }

      sspi->decryptBufferLength += 4096;
      sspi->decryptBuffer       = temp;

      DEBUG_printf(("5_httpTLSRead: Resized decryption buffer to %d bytes.", sspi->decryptBufferLength));
    }

    buffers[0].pvBuffer	  = sspi->decryptBuffer;
    buffers[0].cbBuffer	  = (unsigned long)sspi->decryptBufferUsed;
    buffers[0].BufferType = SECBUFFER_DATA;
    buffers[1].BufferType = SECBUFFER_EMPTY;
    buffers[2].BufferType = SECBUFFER_EMPTY;
    buffers[3].BufferType = SECBUFFER_EMPTY;

    DEBUG_printf(("5_httpTLSRead: decryptBufferUsed=%d", sspi->decryptBufferUsed));

    scRet = DecryptMessage(&sspi->context, &message, 0, NULL);

    if (scRet == SEC_E_INCOMPLETE_MESSAGE)
    {
      num = recv(http->fd, sspi->decryptBuffer + sspi->decryptBufferUsed, (int)(sspi->decryptBufferLength - sspi->decryptBufferUsed), 0);
      if (num < 0)
      {
	DEBUG_printf(("5_httpTLSRead: recv failed: %d", WSAGetLastError()));
	return (-1);
      }
      else if (num == 0)
      {
	DEBUG_puts("5_httpTLSRead: Server disconnected.");
	return (0);
      }

      DEBUG_printf(("5_httpTLSRead: Read %d bytes into decryption buffer.", num));

      sspi->decryptBufferUsed += num;
    }
  }
  while (scRet == SEC_E_INCOMPLETE_MESSAGE);

  if (scRet == SEC_I_CONTEXT_EXPIRED)
  {
    http_sspi_set_error("DecryptMessage");
    DEBUG_puts("5_httpTLSRead: Context expired.");
    WSASetLastError(WSAECONNRESET);
    return (-1);
  }
  else if (scRet != SEC_E_OK)
  {
    http_sspi_set_error("DecryptMessage");
    DEBUG_printf(("5_httpTLSRead: DecryptMessage failed: %s", http_sspi_strerror(sspi->error, sizeof(sspi->error), scRet)));
    WSASetLastError(WSASYSCALLFAILURE);
    return (-1);
  }

 /*
  * The decryption worked.  Now, locate data buffer.
  */

  pDataBuffer  = NULL;
  pExtraBuffer = NULL;

  for (i = 1; i < 4; i++)
  {
    if (buffers[i].BufferType == SECBUFFER_DATA)
      pDataBuffer = &buffers[i];
    else if (!pExtraBuffer && (buffers[i].BufferType == SECBUFFER_EXTRA))
      pExtraBuffer = &buffers[i];
  }

 /*
  * If a data buffer is found, then copy the decrypted bytes to the passed-in
  * buffer...
  */

  if (pDataBuffer)
  {
    int bytesToCopy = min((int)pDataBuffer->cbBuffer, len);
				      /* Number of bytes to copy into buf */
    int bytesToSave = pDataBuffer->cbBuffer - bytesToCopy;
				      /* Number of bytes to save in our read buffer */

    if (bytesToCopy)
      memcpy(buf, pDataBuffer->pvBuffer, bytesToCopy);

   /*
    * If there are more decrypted bytes than can be copied to the passed in
    * buffer, then save them...
    */

    if (bytesToSave)
    {
      if ((sspi->readBufferLength - sspi->readBufferUsed) < bytesToSave)
      {
        BYTE *temp;			/* New buffer pointer */

        if ((temp = realloc(sspi->readBuffer, sspi->readBufferUsed + bytesToSave)) == NULL)
	{
	  _cupsSetHTTPError(HTTP_STATUS_ERROR);
	  DEBUG_printf(("5_httpTLSRead: Unable to allocate %d bytes.", sspi->readBufferUsed + bytesToSave));
	  WSASetLastError(E_OUTOFMEMORY);
	  return (-1);
	}

	sspi->readBufferLength = sspi->readBufferUsed + bytesToSave;
	sspi->readBuffer       = temp;
      }

      memcpy(((BYTE *)sspi->readBuffer) + sspi->readBufferUsed, ((BYTE *)pDataBuffer->pvBuffer) + bytesToCopy, bytesToSave);

      sspi->readBufferUsed += bytesToSave;
    }

    num = bytesToCopy;
  }
  else
  {
    DEBUG_puts("5_httpTLSRead: Unable to find data buffer.");
    _cupsSetError(IPP_STATUS_ERROR_CUPS_PKI, "Unable to find data buffer.", 0);
    WSASetLastError(WSASYSCALLFAILURE);
    return (-1);
  }

 /*
  * If the decryption process left extra bytes, then save those back in
  * decryptBuffer.  They will be processed the next time through the loop.
  */

  if (pExtraBuffer)
  {
    memmove(sspi->decryptBuffer, pExtraBuffer->pvBuffer, pExtraBuffer->cbBuffer);
    sspi->decryptBufferUsed = pExtraBuffer->cbBuffer;
  }
  else
  {
    sspi->decryptBufferUsed = 0;
  }

  return (num);
}


/*
 * '_httpTLSSetOptions()' - Set TLS protocol and cipher suite options.
 */

void
_httpTLSSetOptions(int options,		/* I - Options */
                   int min_version,	/* I - Minimum TLS version */
                   int max_version)	/* I - Maximum TLS version */
{
  if (!(options & _HTTP_TLS_SET_DEFAULT) || tls_options < 0)
  {
    tls_options     = options;
    tls_min_version = min_version;
    tls_max_version = max_version;
  }
}


/*
 * '_httpTLSStart()' - Set up SSL/TLS support on a connection.
 */

int					/* O - 0 on success, -1 on failure */
_httpTLSStart(http_t *http)		/* I - HTTP connection */
{
  char	hostname[256],			/* Hostname */
	*hostptr;			/* Pointer into hostname */


  DEBUG_printf(("3_httpTLSStart(http=%p)", http));

  if (tls_options < 0)
  {
    DEBUG_puts("4_httpTLSStart: Setting defaults.");
    _cupsSetDefaults();
    DEBUG_printf(("4_httpTLSStart: tls_options=%x", tls_options));
  }

  if ((http->tls = http_sspi_alloc()) == NULL)
    return (-1);

  if (http->mode == _HTTP_MODE_CLIENT)
  {
   /*
    * Client: determine hostname...
    */

    if (httpAddrLocalhost(http->hostaddr))
    {
      strlcpy(hostname, "localhost", sizeof(hostname));
    }
    else
    {
     /*
      * Otherwise make sure the hostname we have does not end in a trailing dot.
      */

      strlcpy(hostname, http->hostname, sizeof(hostname));
      if ((hostptr = hostname + strlen(hostname) - 1) >= hostname &&
	  *hostptr == '.')
	*hostptr = '\0';
    }

    return (http_sspi_client(http, hostname));
  }
  else
  {
   /*
    * Server: determine hostname to use...
    */

    if (http->fields[HTTP_FIELD_HOST])
    {
     /*
      * Use hostname for TLS upgrade...
      */

      strlcpy(hostname, http->fields[HTTP_FIELD_HOST], sizeof(hostname));
    }
    else
    {
     /*
      * Resolve hostname from connection address...
      */

      http_addr_t	addr;		/* Connection address */
      socklen_t		addrlen;	/* Length of address */

      addrlen = sizeof(addr);
      if (getsockname(http->fd, (struct sockaddr *)&addr, &addrlen))
      {
	DEBUG_printf(("4_httpTLSStart: Unable to get socket address: %s", strerror(errno)));
	hostname[0] = '\0';
      }
      else if (httpAddrLocalhost(&addr))
	hostname[0] = '\0';
      else
      {
	httpAddrLookup(&addr, hostname, sizeof(hostname));
        DEBUG_printf(("4_httpTLSStart: Resolved socket address to \"%s\".", hostname));
      }
    }

//    fprintf(stderr, "_httpTLSStart: Using hostname '%s'.\n", hostname);

    return (http_sspi_server(http, hostname));
  }
}


/*
 * '_httpTLSStop()' - Shut down SSL/TLS on a connection.
 */

void
_httpTLSStop(http_t *http)		/* I - HTTP connection */
{
  _http_sspi_t	*sspi = http->tls;	/* SSPI data */


  if (sspi->contextInitialized && http->fd >= 0)
  {
    SecBufferDesc	message;	/* Array of SecBuffer struct */
    SecBuffer		buffers[1] = { 0 };
					/* Security package buffer */
    DWORD		dwType;		/* Type */
    DWORD		status;		/* Status */

  /*
   * Notify schannel that we are about to close the connection.
   */

   dwType = SCHANNEL_SHUTDOWN;

   buffers[0].pvBuffer   = &dwType;
   buffers[0].BufferType = SECBUFFER_TOKEN;
   buffers[0].cbBuffer   = sizeof(dwType);

   message.cBuffers  = 1;
   message.pBuffers  = buffers;
   message.ulVersion = SECBUFFER_VERSION;

   status = ApplyControlToken(&sspi->context, &message);

   if (SUCCEEDED(status))
   {
     PBYTE	pbMessage;		/* Message buffer */
     DWORD	cbMessage;		/* Message buffer count */
     DWORD	cbData;			/* Data count */
     DWORD	dwSSPIFlags;		/* SSL attributes we requested */
     DWORD	dwSSPIOutFlags;		/* SSL attributes we received */
     TimeStamp	tsExpiry;		/* Time stamp */

     dwSSPIFlags = ASC_REQ_SEQUENCE_DETECT     |
                   ASC_REQ_REPLAY_DETECT       |
                   ASC_REQ_CONFIDENTIALITY     |
                   ASC_REQ_EXTENDED_ERROR      |
                   ASC_REQ_ALLOCATE_MEMORY     |
                   ASC_REQ_STREAM;

     buffers[0].pvBuffer   = NULL;
     buffers[0].BufferType = SECBUFFER_TOKEN;
     buffers[0].cbBuffer   = 0;

     message.cBuffers  = 1;
     message.pBuffers  = buffers;
     message.ulVersion = SECBUFFER_VERSION;

     status = AcceptSecurityContext(&sspi->creds, &sspi->context, NULL,
                                    dwSSPIFlags, SECURITY_NATIVE_DREP, NULL,
                                    &message, &dwSSPIOutFlags, &tsExpiry);

      if (SUCCEEDED(status))
      {
        pbMessage = buffers[0].pvBuffer;
        cbMessage = buffers[0].cbBuffer;

       /*
        * Send the close notify message to the client.
        */

        if (pbMessage && cbMessage)
        {
          cbData = send(http->fd, pbMessage, cbMessage, 0);
          if ((cbData == SOCKET_ERROR) || (cbData == 0))
          {
            status = WSAGetLastError();
            DEBUG_printf(("4_httpTLSStop: sending close notify failed: %d", status));
          }
          else
          {
            FreeContextBuffer(pbMessage);
          }
        }
      }
      else
      {
	http_sspi_set_error("AcceptSecurityContext");
        DEBUG_printf(("4_httpTLSStop: AcceptSecurityContext failed: %s", http_sspi_strerror(sspi->error, sizeof(sspi->error), status)));
      }
    }
    else
    {
      http_sspi_set_error("ApplyControlToken");
      DEBUG_printf(("4_httpTLSStop: ApplyControlToken failed: %s", http_sspi_strerror(sspi->error, sizeof(sspi->error), status)));
    }
  }

  http_sspi_free(sspi);

  http->tls = NULL;
}


/*
 * '_httpTLSWrite()' - Write to a SSL/TLS connection.
 */

int					/* O - Bytes written */
_httpTLSWrite(http_t     *http,		/* I - HTTP connection */
	      const char *buf,		/* I - Buffer holding data */
	      int        len)		/* I - Length of buffer */
{
  _http_sspi_t	*sspi = http->tls;	/* SSPI data */
  SecBufferDesc	message;		/* Array of SecBuffer struct */
  SecBuffer	buffers[4] = { 0 };	/* Security package buffer */
  int		bufferLen;		/* Buffer length */
  int		bytesLeft;		/* Bytes left to write */
  const char	*bufptr;		/* Pointer into buffer */
  int		num = 0;		/* Return value */


  bufferLen = sspi->streamSizes.cbMaximumMessage + sspi->streamSizes.cbHeader + sspi->streamSizes.cbTrailer;

  if (bufferLen > sspi->writeBufferLength)
  {
    BYTE *temp;				/* New buffer pointer */

    if ((temp = (BYTE *)realloc(sspi->writeBuffer, bufferLen)) == NULL)
    {
      _cupsSetHTTPError(HTTP_STATUS_ERROR);
      DEBUG_printf(("5_httpTLSWrite: Unable to allocate buffer of %d bytes.", bufferLen));
      WSASetLastError(E_OUTOFMEMORY);
      return (-1);
    }

    sspi->writeBuffer       = temp;
    sspi->writeBufferLength = bufferLen;
  }

  bytesLeft = len;
  bufptr    = buf;

  while (bytesLeft)
  {
    int chunk = min((int)sspi->streamSizes.cbMaximumMessage, bytesLeft);
					/* Size of data to write */
    SECURITY_STATUS scRet;		/* SSPI status */

   /*
    * Copy user data into the buffer, starting just past the header...
    */

    memcpy(sspi->writeBuffer + sspi->streamSizes.cbHeader, bufptr, chunk);

   /*
    * Setup the SSPI buffers
    */

    message.ulVersion = SECBUFFER_VERSION;
    message.cBuffers  = 4;
    message.pBuffers  = buffers;

    buffers[0].pvBuffer   = sspi->writeBuffer;
    buffers[0].cbBuffer   = sspi->streamSizes.cbHeader;
    buffers[0].BufferType = SECBUFFER_STREAM_HEADER;
    buffers[1].pvBuffer   = sspi->writeBuffer + sspi->streamSizes.cbHeader;
    buffers[1].cbBuffer   = (unsigned long) chunk;
    buffers[1].BufferType = SECBUFFER_DATA;
    buffers[2].pvBuffer   = sspi->writeBuffer + sspi->streamSizes.cbHeader + chunk;
    buffers[2].cbBuffer   = sspi->streamSizes.cbTrailer;
    buffers[2].BufferType = SECBUFFER_STREAM_TRAILER;
    buffers[3].BufferType = SECBUFFER_EMPTY;

   /*
    * Encrypt the data
    */

    scRet = EncryptMessage(&sspi->context, 0, &message, 0);

    if (FAILED(scRet))
    {
      http_sspi_set_error("EncryptMessage");
      DEBUG_printf(("5_httpTLSWrite: EncryptMessage failed: %s", http_sspi_strerror(sspi->error, sizeof(sspi->error), scRet)));
      WSASetLastError(WSASYSCALLFAILURE);
      return (-1);
    }

   /*
    * Send the data. Remember the size of the total data to send is the size
    * of the header, the size of the data the caller passed in and the size
    * of the trailer...
    */

    num = send(http->fd, sspi->writeBuffer, buffers[0].cbBuffer + buffers[1].cbBuffer + buffers[2].cbBuffer, 0);

    if (num <= 0)
    {
      DEBUG_printf(("5_httpTLSWrite: send failed: %ld", WSAGetLastError()));
      return (num);
    }

    bytesLeft -= chunk;
    bufptr    += chunk;
  }

  return (len);
}


/*
 * 'http_sspi_alloc()' - Allocate SSPI object.
 */

static _http_sspi_t *			/* O  - New SSPI/SSL object */
http_sspi_alloc(void)
{
  return ((_http_sspi_t *)calloc(sizeof(_http_sspi_t), 1));
}


/*
 * 'http_sspi_client()' - Negotiate a TLS connection as a client.
 */

static int				/* O - 0 on success, -1 on failure */
http_sspi_client(http_t     *http,	/* I - Client connection */
                 const char *hostname)	/* I - Server hostname */
{
  _http_sspi_t	*sspi = http->tls;	/* SSPI data */
  DWORD		dwSSPIFlags;		/* SSL connection attributes we want */
  DWORD		dwSSPIOutFlags;		/* SSL connection attributes we got */
  TimeStamp	tsExpiry;		/* Time stamp */
  SECURITY_STATUS scRet;		/* Status */
  int		cbData;			/* Data count */
  SecBufferDesc	inBuffer;		/* Array of SecBuffer structs */
  SecBuffer	inBuffers[2];		/* Security package buffer */
  SecBufferDesc	outBuffer;		/* Array of SecBuffer structs */
  SecBuffer	outBuffers[1];		/* Security package buffer */
  int		ret = 0;		/* Return value */


  DEBUG_printf(("4http_sspi_client(http=%p, hostname=\"%s\")", http, hostname));

  dwSSPIFlags = ISC_REQ_SEQUENCE_DETECT   |
                ISC_REQ_REPLAY_DETECT     |
                ISC_REQ_CONFIDENTIALITY   |
                ISC_RET_EXTENDED_ERROR    |
                ISC_REQ_ALLOCATE_MEMORY   |
                ISC_REQ_STREAM;

 /*
  * Lookup the client certificate...
  */

  if (!http_sspi_find_credentials(http, L"ClientContainer", NULL))
  {
    DEBUG_puts("5http_sspi_client: Unable to get client credentials.");
    return (-1);
  }

 /*
  * Initiate a ClientHello message and generate a token.
  */

  outBuffers[0].pvBuffer   = NULL;
  outBuffers[0].BufferType = SECBUFFER_TOKEN;
  outBuffers[0].cbBuffer   = 0;

  outBuffer.cBuffers  = 1;
  outBuffer.pBuffers  = outBuffers;
  outBuffer.ulVersion = SECBUFFER_VERSION;

  scRet = InitializeSecurityContext(&sspi->creds, NULL, TEXT(""), dwSSPIFlags, 0, SECURITY_NATIVE_DREP, NULL, 0, &sspi->context, &outBuffer, &dwSSPIOutFlags, &tsExpiry);

  if (scRet != SEC_I_CONTINUE_NEEDED)
  {
    http_sspi_set_error("InitializeSecurityContext");
    DEBUG_printf(("5http_sspi_client: InitializeSecurityContext(1) failed: %s", http_sspi_strerror(sspi->error, sizeof(sspi->error), scRet)));
    return (-1);
  }

 /*
  * Send response to server if there is one.
  */

  if (outBuffers[0].cbBuffer && outBuffers[0].pvBuffer)
  {
    if ((cbData = send(http->fd, outBuffers[0].pvBuffer, outBuffers[0].cbBuffer, 0)) <= 0)
    {
      DEBUG_printf(("5http_sspi_client: send failed: %d", WSAGetLastError()));
      FreeContextBuffer(outBuffers[0].pvBuffer);
      DeleteSecurityContext(&sspi->context);
      return (-1);
    }

    DEBUG_printf(("5http_sspi_client: %d bytes of handshake data sent.", cbData));

    FreeContextBuffer(outBuffers[0].pvBuffer);
    outBuffers[0].pvBuffer = NULL;
  }

  dwSSPIFlags = ISC_REQ_MANUAL_CRED_VALIDATION |
		ISC_REQ_SEQUENCE_DETECT        |
                ISC_REQ_REPLAY_DETECT          |
                ISC_REQ_CONFIDENTIALITY        |
                ISC_RET_EXTENDED_ERROR         |
                ISC_REQ_ALLOCATE_MEMORY        |
                ISC_REQ_STREAM;

  sspi->decryptBufferUsed = 0;

 /*
  * Loop until the handshake is finished or an error occurs.
  */

  scRet = SEC_I_CONTINUE_NEEDED;

  while(scRet == SEC_I_CONTINUE_NEEDED        ||
        scRet == SEC_E_INCOMPLETE_MESSAGE     ||
        scRet == SEC_I_INCOMPLETE_CREDENTIALS)
  {
    if (sspi->decryptBufferUsed == 0 || scRet == SEC_E_INCOMPLETE_MESSAGE)
    {
      if (sspi->decryptBufferLength <= sspi->decryptBufferUsed)
      {
	BYTE *temp;			/* New buffer */

	if (sspi->decryptBufferLength >= 262144)
	{
	  WSASetLastError(E_OUTOFMEMORY);
	  DEBUG_puts("5http_sspi_client: Decryption buffer too large (>256k)");
	  return (-1);
	}

	if ((temp = realloc(sspi->decryptBuffer, sspi->decryptBufferLength + 4096)) == NULL)
	{
	  _cupsSetHTTPError(HTTP_STATUS_ERROR);
	  DEBUG_printf(("5http_sspi_client: Unable to allocate %d byte buffer.", sspi->decryptBufferLength + 4096));
	  WSASetLastError(E_OUTOFMEMORY);
	  return (-1);
	}

	sspi->decryptBufferLength += 4096;
	sspi->decryptBuffer       = temp;
      }

      cbData = recv(http->fd, sspi->decryptBuffer + sspi->decryptBufferUsed, (int)(sspi->decryptBufferLength - sspi->decryptBufferUsed), 0);

      if (cbData < 0)
      {
        DEBUG_printf(("5http_sspi_client: recv failed: %d", WSAGetLastError()));
        return (-1);
      }
      else if (cbData == 0)
      {
        DEBUG_printf(("5http_sspi_client: Server unexpectedly disconnected."));
        return (-1);
      }

      DEBUG_printf(("5http_sspi_client: %d bytes of handshake data received", cbData));

      sspi->decryptBufferUsed += cbData;
    }

   /*
    * Set up the input buffers. Buffer 0 is used to pass in data received from
    * the server.  Schannel will consume some or all of this.  Leftover data
    * (if any) will be placed in buffer 1 and given a buffer type of
    * SECBUFFER_EXTRA.
    */

    inBuffers[0].pvBuffer   = sspi->decryptBuffer;
    inBuffers[0].cbBuffer   = (unsigned long)sspi->decryptBufferUsed;
    inBuffers[0].BufferType = SECBUFFER_TOKEN;

    inBuffers[1].pvBuffer   = NULL;
    inBuffers[1].cbBuffer   = 0;
    inBuffers[1].BufferType = SECBUFFER_EMPTY;

    inBuffer.cBuffers       = 2;
    inBuffer.pBuffers       = inBuffers;
    inBuffer.ulVersion      = SECBUFFER_VERSION;

   /*
    * Set up the output buffers. These are initialized to NULL so as to make it
    * less likely we'll attempt to free random garbage later.
    */

    outBuffers[0].pvBuffer   = NULL;
    outBuffers[0].BufferType = SECBUFFER_TOKEN;
    outBuffers[0].cbBuffer   = 0;

    outBuffer.cBuffers       = 1;
    outBuffer.pBuffers       = outBuffers;
    outBuffer.ulVersion      = SECBUFFER_VERSION;

   /*
    * Call InitializeSecurityContext.
    */

    scRet = InitializeSecurityContext(&sspi->creds, &sspi->context, NULL, dwSSPIFlags, 0, SECURITY_NATIVE_DREP, &inBuffer, 0, NULL, &outBuffer, &dwSSPIOutFlags, &tsExpiry);

   /*
    * If InitializeSecurityContext was successful (or if the error was one of
    * the special extended ones), send the contents of the output buffer to the
    * server.
    */

    if (scRet == SEC_E_OK                ||
        scRet == SEC_I_CONTINUE_NEEDED   ||
        FAILED(scRet) && (dwSSPIOutFlags & ISC_RET_EXTENDED_ERROR))
    {
      if (outBuffers[0].cbBuffer && outBuffers[0].pvBuffer)
      {
        cbData = send(http->fd, outBuffers[0].pvBuffer, outBuffers[0].cbBuffer, 0);

        if (cbData <= 0)
        {
          DEBUG_printf(("5http_sspi_client: send failed: %d", WSAGetLastError()));
          FreeContextBuffer(outBuffers[0].pvBuffer);
          DeleteSecurityContext(&sspi->context);
          return (-1);
        }

        DEBUG_printf(("5http_sspi_client: %d bytes of handshake data sent.", cbData));

       /*
        * Free output buffer.
        */

        FreeContextBuffer(outBuffers[0].pvBuffer);
        outBuffers[0].pvBuffer = NULL;
      }
    }

   /*
    * If InitializeSecurityContext returned SEC_E_INCOMPLETE_MESSAGE, then we
    * need to read more data from the server and try again.
    */

    if (scRet == SEC_E_INCOMPLETE_MESSAGE)
      continue;

   /*
    * If InitializeSecurityContext returned SEC_E_OK, then the handshake
    * completed successfully.
    */

    if (scRet == SEC_E_OK)
    {
     /*
      * If the "extra" buffer contains data, this is encrypted application
      * protocol layer stuff. It needs to be saved. The application layer will
      * later decrypt it with DecryptMessage.
      */

      DEBUG_puts("5http_sspi_client: Handshake was successful.");

      if (inBuffers[1].BufferType == SECBUFFER_EXTRA)
      {
        memmove(sspi->decryptBuffer, sspi->decryptBuffer + sspi->decryptBufferUsed - inBuffers[1].cbBuffer, inBuffers[1].cbBuffer);

        sspi->decryptBufferUsed = inBuffers[1].cbBuffer;

        DEBUG_printf(("5http_sspi_client: %d bytes of app data was bundled with handshake data", sspi->decryptBufferUsed));
      }
      else
        sspi->decryptBufferUsed = 0;

     /*
      * Bail out to quit
      */

      break;
    }

   /*
    * Check for fatal error.
    */

    if (FAILED(scRet))
    {
      http_sspi_set_error("InitializeSecurityContext");
      DEBUG_printf(("5http_sspi_client: InitializeSecurityContext(2) failed: %s", http_sspi_strerror(sspi->error, sizeof(sspi->error), scRet)));
      ret = -1;
      break;
    }

   /*
    * If InitializeSecurityContext returned SEC_I_INCOMPLETE_CREDENTIALS,
    * then the server just requested client authentication.
    */

    if (scRet == SEC_I_INCOMPLETE_CREDENTIALS)
    {
     /*
      * Unimplemented
      */

      _cupsSetError(IPP_STATUS_ERROR_CUPS_PKI, "Need client credentials.", 0);
      DEBUG_printf(("5http_sspi_client: server requested client credentials."));
      ret = -1;
      break;
    }

   /*
    * Copy any leftover data from the "extra" buffer, and go around again.
    */

    if (inBuffers[1].BufferType == SECBUFFER_EXTRA)
    {
      memmove(sspi->decryptBuffer, sspi->decryptBuffer + sspi->decryptBufferUsed - inBuffers[1].cbBuffer, inBuffers[1].cbBuffer);

      sspi->decryptBufferUsed = inBuffers[1].cbBuffer;
    }
    else
    {
      sspi->decryptBufferUsed = 0;
    }
  }

  if (!ret)
  {
   /*
    * Success!  Get the server cert
    */

    sspi->contextInitialized = TRUE;

    scRet = QueryContextAttributes(&sspi->context, SECPKG_ATTR_REMOTE_CERT_CONTEXT, (VOID *)&(sspi->remoteCert));

    if (scRet != SEC_E_OK)
    {
      http_sspi_set_error("QueryContextAttributes");
      DEBUG_printf(("5http_sspi_client: QueryContextAttributes failed(SECPKG_ATTR_REMOTE_CERT_CONTEXT): %s", http_sspi_strerror(sspi->error, sizeof(sspi->error), scRet)));
      return (-1);
    }

   /*
    * Find out how big the header/trailer will be:
    */

    scRet = QueryContextAttributes(&sspi->context, SECPKG_ATTR_STREAM_SIZES, &sspi->streamSizes);

    if (scRet != SEC_E_OK)
    {
      http_sspi_set_error("QueryContextAttributes");
      DEBUG_printf(("5http_sspi_client: QueryContextAttributes failed(SECPKG_ATTR_STREAM_SIZES): %s", http_sspi_strerror(sspi->error, sizeof(sspi->error), scRet)));
      ret = -1;
    }
  }

  return (ret);
}


/*
 * 'http_sspi_create_credential()' - Create an SSPI certificate context.
 */

static PCCERT_CONTEXT			/* O - Certificate context */
http_sspi_create_credential(
    http_credential_t *cred)		/* I - Credential */
{
  if (cred)
    return (CertCreateCertificateContext(X509_ASN_ENCODING, cred->data, (DWORD)cred->datalen));
  else
    return (NULL);
}


/*
 * 'http_sspi_find_credentials()' - Retrieve a TLS certificate from the system store.
 */

static BOOL				/* O - 1 on success, 0 on failure */
http_sspi_find_credentials(
    http_t       *http,			/* I - HTTP connection */
    const LPWSTR container,		/* I - Cert container name */
    const char   *common_name)		/* I - Common name of certificate */
{
  _http_sspi_t	*sspi = http->tls;	/* SSPI data */
  HCERTSTORE	store = NULL;		/* Certificate store */
  PCCERT_CONTEXT storedContext = NULL;	/* Context created from the store */
  DWORD		dwSize = 0; 		/* 32 bit size */
  PBYTE		p = NULL;		/* Temporary storage */
  HCRYPTPROV	hProv = (HCRYPTPROV)NULL;
					/* Handle to a CSP */
  CERT_NAME_BLOB sib;			/* Arbitrary array of bytes */
  SCHANNEL_CRED	SchannelCred;		/* Schannel credential data */
  TimeStamp	tsExpiry;		/* Time stamp */
  SECURITY_STATUS Status;		/* Status */
  BOOL		ok = TRUE;		/* Return value */


  if (!CryptAcquireContextW(&hProv, (LPWSTR)container, MS_DEF_PROV_W, PROV_RSA_FULL, CRYPT_NEWKEYSET /*| CRYPT_MACHINE_KEYSET*/))
  {
    if (GetLastError() == NTE_EXISTS)
    {
      if (!CryptAcquireContextW(&hProv, (LPWSTR)container, MS_DEF_PROV_W, PROV_RSA_FULL, 0 /*CRYPT_MACHINE_KEYSET*/))
      {
	http_sspi_set_error("CryptAquireContext");
        DEBUG_printf(("5http_sspi_find_credentials: CryptAcquireContext failed: %s", http_sspi_strerror(sspi->error, sizeof(sspi->error), GetLastError())));
        ok = FALSE;
        goto cleanup;
      }
    }
  }

  store = CertOpenStore(CERT_STORE_PROV_SYSTEM, X509_ASN_ENCODING|PKCS_7_ASN_ENCODING, hProv, CERT_SYSTEM_STORE_CURRENT_USER | CERT_STORE_NO_CRYPT_RELEASE_FLAG | CERT_STORE_OPEN_EXISTING_FLAG, L"MY");

  if (!store)
  {
    http_sspi_set_error("CertOpenSystemStore");
    DEBUG_printf(("5http_sspi_find_credentials: CertOpenSystemStore failed: %s", http_sspi_strerror(sspi->error, sizeof(sspi->error), GetLastError())));
    ok = FALSE;
    goto cleanup;
  }

  if (common_name)
  {
    dwSize = 0;

    if (!CertStrToNameA(X509_ASN_ENCODING, common_name, CERT_OID_NAME_STR, NULL, NULL, &dwSize, NULL))
    {
      http_sspi_set_error("CertStrToName");
      DEBUG_printf(("5http_sspi_find_credentials: CertStrToName failed: %s", http_sspi_strerror(sspi->error, sizeof(sspi->error), GetLastError())));
      ok = FALSE;
      goto cleanup;
    }

    p = (PBYTE)malloc(dwSize);

    if (!p)
    {
      _cupsSetHTTPError(HTTP_STATUS_ERROR);
      DEBUG_printf(("5http_sspi_find_credentials: malloc failed for %d bytes.", dwSize));
      ok = FALSE;
      goto cleanup;
    }

    if (!CertStrToNameA(X509_ASN_ENCODING, common_name, CERT_OID_NAME_STR, NULL, p, &dwSize, NULL))
    {
      http_sspi_set_error("CertStrToName");
      DEBUG_printf(("5http_sspi_find_credentials: CertStrToName failed: %s", http_sspi_strerror(sspi->error, sizeof(sspi->error), GetLastError())));
      ok = FALSE;
      goto cleanup;
    }

    sib.cbData = dwSize;
    sib.pbData = p;

    storedContext = CertFindCertificateInStore(store, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0, CERT_FIND_SUBJECT_NAME, &sib, NULL);

    if (!storedContext)
    {
      http_sspi_set_error("CertFindCertificateInStore");
      DEBUG_printf(("5http_sspi_find_credentials: Unable to find credentials for \"%s\".", common_name));
      ok = FALSE;
      goto cleanup;
    }
  }

  ZeroMemory(&SchannelCred, sizeof(SchannelCred));

  SchannelCred.dwVersion = SCHANNEL_CRED_VERSION;

  if (common_name)
  {
    SchannelCred.cCreds = 1;
    SchannelCred.paCred = &storedContext;
  }

 /*
  * Set supported protocols (can also be overriden in the registry...)
  */

#ifdef SP_PROT_TLS1_2_SERVER
  if (http->mode == _HTTP_MODE_SERVER)
  {
    if (tls_min_version > _HTTP_TLS_1_1)
      SchannelCred.grbitEnabledProtocols = SP_PROT_TLS1_2_SERVER;
    else if (tls_min_version > _HTTP_TLS_1_0)
      SchannelCred.grbitEnabledProtocols = SP_PROT_TLS1_2_SERVER | SP_PROT_TLS1_1_SERVER;
    else if (tls_min_version == _HTTP_TLS_SSL3)
      SchannelCred.grbitEnabledProtocols = SP_PROT_TLS1_2_SERVER | SP_PROT_TLS1_1_SERVER | SP_PROT_TLS1_0_SERVER | SP_PROT_SSL3_SERVER;
    else
      SchannelCred.grbitEnabledProtocols = SP_PROT_TLS1_2_SERVER | SP_PROT_TLS1_1_SERVER | SP_PROT_TLS1_0_SERVER;
  }
  else
  {
    if (tls_min_version > _HTTP_TLS_1_1)
      SchannelCred.grbitEnabledProtocols = SP_PROT_TLS1_2_CLIENT;
    else if (tls_min_version > _HTTP_TLS_1_0)
      SchannelCred.grbitEnabledProtocols = SP_PROT_TLS1_2_CLIENT | SP_PROT_TLS1_1_CLIENT;
    else if (tls_min_version == _HTTP_TLS_SSL3)
      SchannelCred.grbitEnabledProtocols = SP_PROT_TLS1_2_CLIENT | SP_PROT_TLS1_1_CLIENT | SP_PROT_TLS1_0_CLIENT | SP_PROT_SSL3_CLIENT;
    else
      SchannelCred.grbitEnabledProtocols = SP_PROT_TLS1_2_CLIENT | SP_PROT_TLS1_1_CLIENT | SP_PROT_TLS1_0_CLIENT;
  }

#else
  if (http->mode == _HTTP_MODE_SERVER)
  {
    if (tls_min_version == _HTTP_TLS_SSL3)
      SchannelCred.grbitEnabledProtocols = SP_PROT_TLS1_SERVER | SP_PROT_SSL3_SERVER;
    else
      SchannelCred.grbitEnabledProtocols = SP_PROT_TLS1_SERVER;
  }
  else
  {
    if (tls_min_version == _HTTP_TLS_SSL3)
      SchannelCred.grbitEnabledProtocols = SP_PROT_TLS1_CLIENT | SP_PROT_SSL3_CLIENT;
    else
      SchannelCred.grbitEnabledProtocols = SP_PROT_TLS1_CLIENT;
  }
#endif /* SP_PROT_TLS1_2_SERVER */

  /* TODO: Support _HTTP_TLS_ALLOW_RC4, _HTTP_TLS_ALLOW_DH, and _HTTP_TLS_DENY_CBC options; right now we'll rely on Windows registry to enable/disable RC4/DH/CBC... */

 /*
  * Create an SSPI credential.
  */

  Status = AcquireCredentialsHandle(NULL, UNISP_NAME, http->mode == _HTTP_MODE_SERVER ? SECPKG_CRED_INBOUND : SECPKG_CRED_OUTBOUND, NULL, &SchannelCred, NULL, NULL, &sspi->creds, &tsExpiry);
  if (Status != SEC_E_OK)
  {
    http_sspi_set_error("AcquireCredentialsHandle");
    DEBUG_printf(("5http_sspi_find_credentials: AcquireCredentialsHandle failed: %s", http_sspi_strerror(sspi->error, sizeof(sspi->error), Status)));
    ok = FALSE;
    goto cleanup;
  }

cleanup:

 /*
  * Cleanup
  */

  if (storedContext)
    CertFreeCertificateContext(storedContext);

  if (p)
    free(p);

  if (store)
    CertCloseStore(store, 0);

  if (hProv)
    CryptReleaseContext(hProv, 0);

  return (ok);
}


/*
 * 'http_sspi_free()' - Close a connection and free resources.
 */

static void
http_sspi_free(_http_sspi_t *sspi)	/* I - SSPI data */
{
  if (!sspi)
    return;

  if (sspi->contextInitialized)
    DeleteSecurityContext(&sspi->context);

  if (sspi->decryptBuffer)
    free(sspi->decryptBuffer);

  if (sspi->readBuffer)
    free(sspi->readBuffer);

  if (sspi->writeBuffer)
    free(sspi->writeBuffer);

  if (sspi->localCert)
    CertFreeCertificateContext(sspi->localCert);

  if (sspi->remoteCert)
    CertFreeCertificateContext(sspi->remoteCert);

  free(sspi);
}


/*
 * 'http_sspi_make_credentials()' - Create a TLS certificate in the system store.
 */

static BOOL				/* O - 1 on success, 0 on failure */
http_sspi_make_credentials(
    _http_sspi_t *sspi,			/* I - SSPI data */
    const LPWSTR container,		/* I - Cert container name */
    const char   *common_name,		/* I - Common name of certificate */
    _http_mode_t mode,			/* I - Client or server? */
    int          years)			/* I - Years until expiration */
{
  HCERTSTORE	store = NULL;		/* Certificate store */
  PCCERT_CONTEXT storedContext = NULL;	/* Context created from the store */
  PCCERT_CONTEXT createdContext = NULL;	/* Context created by us */
  DWORD		dwSize = 0; 		/* 32 bit size */
  PBYTE		p = NULL;		/* Temporary storage */
  HCRYPTPROV	hProv = (HCRYPTPROV)NULL;
					/* Handle to a CSP */
  CERT_NAME_BLOB sib;			/* Arbitrary array of bytes */
  SCHANNEL_CRED	SchannelCred;		/* Schannel credential data */
  TimeStamp	tsExpiry;		/* Time stamp */
  SECURITY_STATUS Status;		/* Status */
  HCRYPTKEY	hKey = (HCRYPTKEY)NULL;	/* Handle to crypto key */
  CRYPT_KEY_PROV_INFO kpi;		/* Key container info */
  SYSTEMTIME	et;			/* System time */
  CERT_EXTENSIONS exts;			/* Array of cert extensions */
  CRYPT_KEY_PROV_INFO ckp;		/* Handle to crypto key */
  BOOL		ok = TRUE;		/* Return value */


  DEBUG_printf(("4http_sspi_make_credentials(sspi=%p, container=%p, common_name=\"%s\", mode=%d, years=%d)", sspi, container, common_name, mode, years));

  if (!CryptAcquireContextW(&hProv, (LPWSTR)container, MS_DEF_PROV_W, PROV_RSA_FULL, CRYPT_NEWKEYSET /* | CRYPT_MACHINE_KEYSET*/))
  {
    if (GetLastError() == NTE_EXISTS)
    {
      if (!CryptAcquireContextW(&hProv, (LPWSTR)container, MS_DEF_PROV_W, PROV_RSA_FULL, 0 /*CRYPT_MACHINE_KEYSET*/))
      {
	http_sspi_set_error("CryptAquireContext");
        DEBUG_printf(("5http_sspi_make_credentials: CryptAcquireContext failed: %s", http_sspi_strerror(sspi->error, sizeof(sspi->error), GetLastError())));
//        fprintf(stderr, "5http_sspi_make_credentials: CryptAcquireContext failed: %s\n", http_sspi_strerror(sspi->error, sizeof(sspi->error), GetLastError()));
        ok = FALSE;
        goto cleanup;
      }
    }
  }

  store = CertOpenStore(CERT_STORE_PROV_SYSTEM, X509_ASN_ENCODING|PKCS_7_ASN_ENCODING, hProv, CERT_SYSTEM_STORE_CURRENT_USER | CERT_STORE_NO_CRYPT_RELEASE_FLAG | CERT_STORE_OPEN_EXISTING_FLAG, L"MY");

  if (!store)
  {
    http_sspi_set_error("CertOpenSystemStore");
    DEBUG_printf(("5http_sspi_make_credentials: CertOpenSystemStore failed: %s", http_sspi_strerror(sspi->error, sizeof(sspi->error), GetLastError())));
//    fprintf(stderr, "5http_sspi_make_credentials: CertOpenSystemStore failed: %s\n", http_sspi_strerror(sspi->error, sizeof(sspi->error), GetLastError()));
    ok = FALSE;
    goto cleanup;
  }

  dwSize = 0;

  if (!CertStrToNameA(X509_ASN_ENCODING, common_name, CERT_OID_NAME_STR, NULL, NULL, &dwSize, NULL))
  {
    http_sspi_set_error("CertStrToName");
    DEBUG_printf(("5http_sspi_make_credentials: CertStrToName failed: %s", http_sspi_strerror(sspi->error, sizeof(sspi->error), GetLastError())));
//    fprintf(stderr, "5http_sspi_make_credentials: CertStrToName failed: %s\n", http_sspi_strerror(sspi->error, sizeof(sspi->error), GetLastError()));
    ok = FALSE;
    goto cleanup;
  }

  p = (PBYTE)malloc(dwSize);

  if (!p)
  {
    _cupsSetHTTPError(HTTP_STATUS_ERROR);
    DEBUG_printf(("5http_sspi_make_credentials: malloc failed for %d bytes", dwSize));
//    fprintf(stderr, "5http_sspi_make_credentials: malloc failed for %d bytes\n", dwSize);
    ok = FALSE;
    goto cleanup;
  }

  if (!CertStrToNameA(X509_ASN_ENCODING, common_name, CERT_OID_NAME_STR, NULL, p, &dwSize, NULL))
  {
    http_sspi_set_error("CertStrToName");
    DEBUG_printf(("5http_sspi_make_credentials: CertStrToName failed: %s", http_sspi_strerror(sspi->error, sizeof(sspi->error), GetLastError())));
//    fprintf(stderr, "5http_sspi_make_credentials: CertStrToName failed: %s\n", http_sspi_strerror(sspi->error, sizeof(sspi->error), GetLastError()));
    ok = FALSE;
    goto cleanup;
  }

  sib.cbData = dwSize;
  sib.pbData = p;

 /*
  * Create a private key and self-signed certificate...
  */

  if (!CryptGenKey(hProv, AT_KEYEXCHANGE, CRYPT_EXPORTABLE | RSA1024BIT_KEY, &hKey))
  {
    http_sspi_set_error("CryptGenKey");
    DEBUG_printf(("5http_sspi_make_credentials: CryptGenKey failed: %s", http_sspi_strerror(sspi->error, sizeof(sspi->error), GetLastError())));
//    fprintf(stderr, "5http_sspi_make_credentials: CryptGenKey failed: %s\n", http_sspi_strerror(sspi->error, sizeof(sspi->error), GetLastError()));
    ok = FALSE;
    goto cleanup;
  }

  ZeroMemory(&kpi, sizeof(kpi));
  kpi.pwszContainerName = (LPWSTR)container;
  kpi.pwszProvName      = MS_DEF_PROV_W;
  kpi.dwProvType        = PROV_RSA_FULL;
  kpi.dwFlags           = CERT_SET_KEY_CONTEXT_PROP_ID;
  kpi.dwKeySpec         = AT_KEYEXCHANGE;

  GetSystemTime(&et);
  et.wYear += years;
  if (et.wMonth == 2 && et.wDay == 29)
    et.wDay = 28;			/* Avoid Feb 29th due to leap years */

  ZeroMemory(&exts, sizeof(exts));

  createdContext = CertCreateSelfSignCertificate(hProv, &sib, 0, &kpi, NULL, NULL, &et, &exts);

  if (!createdContext)
  {
    http_sspi_set_error("CertCreateSelfSignCertificate");
    DEBUG_printf(("5http_sspi_make_credentials: CertCreateSelfSignCertificate failed: %s", http_sspi_strerror(sspi->error, sizeof(sspi->error), GetLastError())));
//    fprintf(stderr, "5http_sspi_make_credentials: CertCreateSelfSignCertificate failed: %s\n", http_sspi_strerror(sspi->error, sizeof(sspi->error), GetLastError()));
    ok = FALSE;
    goto cleanup;
  }

 /*
  * Add the created context to the named store, and associate it with the named
  * container...
  */

  if (!CertAddCertificateContextToStore(store, createdContext, CERT_STORE_ADD_REPLACE_EXISTING, &storedContext))
  {
    http_sspi_set_error("CertAddCertificateContextToStore");
    DEBUG_printf(("5http_sspi_make_credentials: CertAddCertificateContextToStore failed: %s", http_sspi_strerror(sspi->error, sizeof(sspi->error), GetLastError())));
    ok = FALSE;
    goto cleanup;
  }

  ZeroMemory(&ckp, sizeof(ckp));
  ckp.pwszContainerName = (LPWSTR) container;
  ckp.pwszProvName      = MS_DEF_PROV_W;
  ckp.dwProvType        = PROV_RSA_FULL;
  ckp.dwFlags           = 0 /*CRYPT_MACHINE_KEYSET*/;
  ckp.dwKeySpec         = AT_KEYEXCHANGE;

  if (!CertSetCertificateContextProperty(storedContext, CERT_KEY_PROV_INFO_PROP_ID, 0, &ckp))
  {
    http_sspi_set_error("CertSetCertificateContextProperty");
    DEBUG_printf(("5http_sspi_make_credentials: CertSetCertificateContextProperty failed: %s", http_sspi_strerror(sspi->error, sizeof(sspi->error), GetLastError())));
    ok = FALSE;
    goto cleanup;
  }

 /*
  * Get a handle to use the certificate...
  */

  ZeroMemory(&SchannelCred, sizeof(SchannelCred));

  SchannelCred.dwVersion = SCHANNEL_CRED_VERSION;
  SchannelCred.cCreds    = 1;
  SchannelCred.paCred    = &storedContext;

 /*
  * Create an SSPI credential.
  */

  Status = AcquireCredentialsHandle(NULL, UNISP_NAME, mode == _HTTP_MODE_SERVER ? SECPKG_CRED_INBOUND : SECPKG_CRED_OUTBOUND, NULL, &SchannelCred, NULL, NULL, &sspi->creds, &tsExpiry);
  if (Status != SEC_E_OK)
  {
    http_sspi_set_error("AcquireCredentialsHandle");
    DEBUG_printf(("5http_sspi_make_credentials: AcquireCredentialsHandle failed: %s", http_sspi_strerror(sspi->error, sizeof(sspi->error), Status)));
//    fprintf(stderr, "5http_sspi_make_credentials: AcquireCredentialsHandle failed: %s\n", http_sspi_strerror(sspi->error, sizeof(sspi->error), Status));
    ok = FALSE;
    goto cleanup;
  }

cleanup:

 /*
  * Cleanup
  */

  if (hKey)
    CryptDestroyKey(hKey);

  if (createdContext)
    CertFreeCertificateContext(createdContext);

  if (storedContext)
    CertFreeCertificateContext(storedContext);

  if (p)
    free(p);

  if (store)
    CertCloseStore(store, 0);

  if (hProv)
    CryptReleaseContext(hProv, 0);

  return (ok);
}


/*
 * 'http_sspi_server()' - Negotiate a TLS connection as a server.
 */

static int				/* O - 0 on success, -1 on failure */
http_sspi_server(http_t     *http,	/* I - HTTP connection */
                 const char *hostname)	/* I - Hostname of server */
{
  _http_sspi_t	*sspi = http->tls;	/* SSPI data */
  char		common_name[512];	/* Common name for cert */
  DWORD		dwSSPIFlags;		/* SSL connection attributes we want */
  DWORD		dwSSPIOutFlags;		/* SSL connection attributes we got */
  TimeStamp	tsExpiry;		/* Time stamp */
  SECURITY_STATUS scRet;		/* SSPI Status */
  SecBufferDesc	inBuffer;		/* Array of SecBuffer structs */
  SecBuffer	inBuffers[2];		/* Security package buffer */
  SecBufferDesc	outBuffer;		/* Array of SecBuffer structs */
  SecBuffer	outBuffers[1];		/* Security package buffer */
  int		num = 0;		/* 32 bit status value */
  BOOL		fInitContext = TRUE;	/* Has the context been init'd? */
  int		ret = 0;		/* Return value */


  DEBUG_printf(("4http_sspi_server(http=%p, hostname=\"%s\")", http, hostname));

  dwSSPIFlags = ASC_REQ_SEQUENCE_DETECT  |
                ASC_REQ_REPLAY_DETECT    |
                ASC_REQ_CONFIDENTIALITY  |
                ASC_REQ_EXTENDED_ERROR   |
                ASC_REQ_ALLOCATE_MEMORY  |
                ASC_REQ_STREAM;

  sspi->decryptBufferUsed = 0;

 /*
  * Lookup the server certificate...
  */

  snprintf(common_name, sizeof(common_name), "CN=%s", hostname);

  if (!http_sspi_find_credentials(http, L"ServerContainer", common_name))
    if (!http_sspi_make_credentials(http->tls, L"ServerContainer", common_name, _HTTP_MODE_SERVER, 10))
    {
      DEBUG_puts("5http_sspi_server: Unable to get server credentials.");
      return (-1);
    }

 /*
  * Set OutBuffer for AcceptSecurityContext call
  */

  outBuffer.cBuffers  = 1;
  outBuffer.pBuffers  = outBuffers;
  outBuffer.ulVersion = SECBUFFER_VERSION;

  scRet = SEC_I_CONTINUE_NEEDED;

  while (scRet == SEC_I_CONTINUE_NEEDED    ||
         scRet == SEC_E_INCOMPLETE_MESSAGE ||
         scRet == SEC_I_INCOMPLETE_CREDENTIALS)
  {
    if (sspi->decryptBufferUsed == 0 || scRet == SEC_E_INCOMPLETE_MESSAGE)
    {
      if (sspi->decryptBufferLength <= sspi->decryptBufferUsed)
      {
	BYTE *temp;			/* New buffer */

	if (sspi->decryptBufferLength >= 262144)
	{
	  WSASetLastError(E_OUTOFMEMORY);
	  DEBUG_puts("5http_sspi_server: Decryption buffer too large (>256k)");
	  return (-1);
	}

	if ((temp = realloc(sspi->decryptBuffer, sspi->decryptBufferLength + 4096)) == NULL)
	{
	  _cupsSetHTTPError(HTTP_STATUS_ERROR);
	  DEBUG_printf(("5http_sspi_server: Unable to allocate %d byte buffer.", sspi->decryptBufferLength + 4096));
	  WSASetLastError(E_OUTOFMEMORY);
	  return (-1);
	}

	sspi->decryptBufferLength += 4096;
	sspi->decryptBuffer       = temp;
      }

      for (;;)
      {
        num = recv(http->fd, sspi->decryptBuffer + sspi->decryptBufferUsed, (int)(sspi->decryptBufferLength - sspi->decryptBufferUsed), 0);

        if (num == -1 && WSAGetLastError() == WSAEWOULDBLOCK)
          Sleep(1);
        else
          break;
      }

      if (num < 0)
      {
        DEBUG_printf(("5http_sspi_server: recv failed: %d", WSAGetLastError()));
        return (-1);
      }
      else if (num == 0)
      {
        DEBUG_puts("5http_sspi_server: client disconnected");
        return (-1);
      }

      DEBUG_printf(("5http_sspi_server: received %d (handshake) bytes from client.", num));
      sspi->decryptBufferUsed += num;
    }

   /*
    * InBuffers[1] is for getting extra data that SSPI/SCHANNEL doesn't process
    * on this run around the loop.
    */

    inBuffers[0].pvBuffer   = sspi->decryptBuffer;
    inBuffers[0].cbBuffer   = (unsigned long)sspi->decryptBufferUsed;
    inBuffers[0].BufferType = SECBUFFER_TOKEN;

    inBuffers[1].pvBuffer   = NULL;
    inBuffers[1].cbBuffer   = 0;
    inBuffers[1].BufferType = SECBUFFER_EMPTY;

    inBuffer.cBuffers       = 2;
    inBuffer.pBuffers       = inBuffers;
    inBuffer.ulVersion      = SECBUFFER_VERSION;

   /*
    * Initialize these so if we fail, pvBuffer contains NULL, so we don't try to
    * free random garbage at the quit.
    */

    outBuffers[0].pvBuffer   = NULL;
    outBuffers[0].BufferType = SECBUFFER_TOKEN;
    outBuffers[0].cbBuffer   = 0;

    scRet = AcceptSecurityContext(&sspi->creds, (fInitContext?NULL:&sspi->context), &inBuffer, dwSSPIFlags, SECURITY_NATIVE_DREP, (fInitContext?&sspi->context:NULL), &outBuffer, &dwSSPIOutFlags, &tsExpiry);

    fInitContext = FALSE;

    if (scRet == SEC_E_OK              ||
        scRet == SEC_I_CONTINUE_NEEDED ||
        (FAILED(scRet) && ((dwSSPIOutFlags & ISC_RET_EXTENDED_ERROR) != 0)))
    {
      if (outBuffers[0].cbBuffer && outBuffers[0].pvBuffer)
      {
       /*
        * Send response to server if there is one.
        */

        num = send(http->fd, outBuffers[0].pvBuffer, outBuffers[0].cbBuffer, 0);

        if (num <= 0)
        {
          DEBUG_printf(("5http_sspi_server: handshake send failed: %d", WSAGetLastError()));
	  return (-1);
        }

        DEBUG_printf(("5http_sspi_server: sent %d handshake bytes to client.", outBuffers[0].cbBuffer));

        FreeContextBuffer(outBuffers[0].pvBuffer);
        outBuffers[0].pvBuffer = NULL;
      }
    }

    if (scRet == SEC_E_OK)
    {
     /*
      * If there's extra data then save it for next time we go to decrypt.
      */

      if (inBuffers[1].BufferType == SECBUFFER_EXTRA)
      {
        memcpy(sspi->decryptBuffer, (LPBYTE)(sspi->decryptBuffer + sspi->decryptBufferUsed - inBuffers[1].cbBuffer), inBuffers[1].cbBuffer);
        sspi->decryptBufferUsed = inBuffers[1].cbBuffer;
      }
      else
      {
        sspi->decryptBufferUsed = 0;
      }
      break;
    }
    else if (FAILED(scRet) && scRet != SEC_E_INCOMPLETE_MESSAGE)
    {
      http_sspi_set_error("AcceptSecurityContext");
      DEBUG_printf(("5http_sspi_server: AcceptSecurityContext failed: %s", http_sspi_strerror(sspi->error, sizeof(sspi->error), scRet)));
      ret = -1;
      break;
    }

    if (scRet != SEC_E_INCOMPLETE_MESSAGE &&
        scRet != SEC_I_INCOMPLETE_CREDENTIALS)
    {
      if (inBuffers[1].BufferType == SECBUFFER_EXTRA)
      {
        memcpy(sspi->decryptBuffer, (LPBYTE)(sspi->decryptBuffer + sspi->decryptBufferUsed - inBuffers[1].cbBuffer), inBuffers[1].cbBuffer);
        sspi->decryptBufferUsed = inBuffers[1].cbBuffer;
      }
      else
      {
        sspi->decryptBufferUsed = 0;
      }
    }
  }

  if (!ret)
  {
    sspi->contextInitialized = TRUE;

   /*
    * Find out how big the header will be:
    */

    scRet = QueryContextAttributes(&sspi->context, SECPKG_ATTR_STREAM_SIZES, &sspi->streamSizes);

    if (scRet != SEC_E_OK)
    {
      http_sspi_set_error("QueryContextAttributes");
      DEBUG_printf(("5http_sspi_server: QueryContextAttributes failed: %s", http_sspi_strerror(sspi->error, sizeof(sspi->error), scRet)));
      ret = -1;
    }
  }

  return (ret);
}


//
// 'http_sspi_set_error()' - Copy the Windows error string to the CUPS error string.
//

static void
http_sspi_set_error(const char *title)	// I - Prefix/title for error
{
  char		temp[8192];		// Error string
  size_t	templen;		// Length of prefix


  snprintf(temp, sizeof(temp), "%s (%08x): ", title, GetLastError());
  templen = strlen(temp);
  http_sspi_strerror(temp + templen, sizeof(temp) - templen, GetLastError());
  _cupsSetError(IPP_STATUS_ERROR_CUPS_PKI, temp, 0);
}


/*
 * 'http_sspi_strerror()' - Return a string for the specified error code.
 */

static const char *			/* O - String for error */
http_sspi_strerror(char   *buffer,	/* I - Error message buffer */
                   size_t bufsize,	/* I - Size of buffer */
                   DWORD  code)		/* I - Error code */
{
  if (FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, NULL, code, 0, buffer, (DWORD)bufsize, NULL))
  {
   /*
    * Strip trailing CR + LF...
    */

    char	*ptr;			/* Pointer into error message */

    for (ptr = buffer + strlen(buffer) - 1; ptr >= buffer; ptr --)
      if (*ptr == '\n' || *ptr == '\r')
        *ptr = '\0';
      else
        break;
  }
  else
    snprintf(buffer, bufsize, "Unknown error %x", code);

  return (buffer);
}


/*
 * 'http_sspi_verify()' - Verify a certificate.
 */

static DWORD				/* O - Error code (0 == No error) */
http_sspi_verify(
    PCCERT_CONTEXT cert,		/* I - Server certificate */
    const char     *common_name,	/* I - Common name */
    DWORD          dwCertFlags)		/* I - Verification flags */
{
  HTTPSPolicyCallbackData httpsPolicy;	/* HTTPS Policy Struct */
  CERT_CHAIN_POLICY_PARA policyPara;	/* Cert chain policy parameters */
  CERT_CHAIN_POLICY_STATUS policyStatus;/* Cert chain policy status */
  CERT_CHAIN_PARA	chainPara;	/* Used for searching and matching criteria */
  PCCERT_CHAIN_CONTEXT	chainContext = NULL;
					/* Certificate chain */
  PWSTR			commonNameUnicode = NULL;
					/* Unicode common name */
  LPSTR			rgszUsages[] = { szOID_PKIX_KP_SERVER_AUTH,
                                         szOID_SERVER_GATED_CRYPTO,
                                         szOID_SGC_NETSCAPE };
					/* How are we using this certificate? */
  DWORD			cUsages = sizeof(rgszUsages) / sizeof(LPSTR);
					/* Number of ites in rgszUsages */
  DWORD			count;		/* 32 bit count variable */
  DWORD			status;		/* Return value */
#ifdef DEBUG
  char			error[1024];	/* Error message string */
#endif /* DEBUG */


  if (!cert)
    return (SEC_E_WRONG_PRINCIPAL);

 /*
  * Convert common name to Unicode.
  */

  if (!common_name || !*common_name)
    return (SEC_E_WRONG_PRINCIPAL);

  count             = MultiByteToWideChar(CP_ACP, 0, common_name, -1, NULL, 0);
  commonNameUnicode = LocalAlloc(LMEM_FIXED, count * sizeof(WCHAR));
  if (!commonNameUnicode)
    return (SEC_E_INSUFFICIENT_MEMORY);

  if (!MultiByteToWideChar(CP_ACP, 0, common_name, -1, commonNameUnicode, count))
  {
    LocalFree(commonNameUnicode);
    return (SEC_E_WRONG_PRINCIPAL);
  }

 /*
  * Build certificate chain.
  */

  ZeroMemory(&chainPara, sizeof(chainPara));

  chainPara.cbSize					= sizeof(chainPara);
  chainPara.RequestedUsage.dwType			= USAGE_MATCH_TYPE_OR;
  chainPara.RequestedUsage.Usage.cUsageIdentifier	= cUsages;
  chainPara.RequestedUsage.Usage.rgpszUsageIdentifier	= rgszUsages;

  if (!CertGetCertificateChain(NULL, cert, NULL, cert->hCertStore, &chainPara, 0, NULL, &chainContext))
  {
    status = GetLastError();

    http_sspi_set_error("CertGetCertificateChain");
    DEBUG_printf(("5http_sspi_verify: CertGetCertificateChain returned: %s", http_sspi_strerror(error, sizeof(error), status)));

    LocalFree(commonNameUnicode);
    return (status);
  }

 /*
  * Validate certificate chain.
  */

  ZeroMemory(&httpsPolicy, sizeof(HTTPSPolicyCallbackData));
  httpsPolicy.cbStruct		= sizeof(HTTPSPolicyCallbackData);
  httpsPolicy.dwAuthType	= AUTHTYPE_SERVER;
  httpsPolicy.fdwChecks		= dwCertFlags;
  httpsPolicy.pwszServerName	= commonNameUnicode;

  memset(&policyPara, 0, sizeof(policyPara));
  policyPara.cbSize		= sizeof(policyPara);
  policyPara.pvExtraPolicyPara	= &httpsPolicy;

  memset(&policyStatus, 0, sizeof(policyStatus));
  policyStatus.cbSize = sizeof(policyStatus);

  if (!CertVerifyCertificateChainPolicy(CERT_CHAIN_POLICY_SSL, chainContext, &policyPara, &policyStatus))
  {
    status = GetLastError();

    http_sspi_set_error("CertVerifyCertificateChainPolicy");
    DEBUG_printf(("5http_sspi_verify: CertVerifyCertificateChainPolicy returned %s", http_sspi_strerror(error, sizeof(error), status)));
  }
  else if (policyStatus.dwError)
    status = policyStatus.dwError;
  else
    status = SEC_E_OK;

  if (chainContext)
    CertFreeCertificateChain(chainContext);

  if (commonNameUnicode)
    LocalFree(commonNameUnicode);

  return (status);
}
