schannel_connect_step2(struct Curl_easy *data, struct connectdata *conn,
                       int sockindex)
{
  int i;
  ssize_t nread = -1, written = -1;
  struct ssl_connect_data *connssl = &conn->ssl[sockindex];
  unsigned char *reallocated_buffer;
  SecBuffer outbuf[3];
  SecBufferDesc outbuf_desc;
  SecBuffer inbuf[2];
  SecBufferDesc inbuf_desc;
  SECURITY_STATUS sspi_status = SEC_E_OK;
  CURLcode result;
  bool doread;
#ifndef CURL_DISABLE_PROXY
  char * const hostname = SSL_IS_PROXY() ? conn->http_proxy.host.name :
    conn->host.name;
#else
  char * const hostname = conn->host.name;
#endif
  const char *pubkey_ptr;

  doread = (connssl->connecting_state != ssl_connect_2_writing) ? TRUE : FALSE;

  DEBUGF(infof(data,
               "schannel: SSL/TLS connection with %s port %hu (step 2/3)\n",
               hostname, conn->remote_port));

  if(!BACKEND->cred || !BACKEND->ctxt)
    return CURLE_SSL_CONNECT_ERROR;

  /* buffer to store previously received and decrypted data */
  if(BACKEND->decdata_buffer == NULL) {
    BACKEND->decdata_offset = 0;
    BACKEND->decdata_length = CURL_SCHANNEL_BUFFER_INIT_SIZE;
    BACKEND->decdata_buffer = malloc(BACKEND->decdata_length);
    if(BACKEND->decdata_buffer == NULL) {
      failf(data, "schannel: unable to allocate memory");
      return CURLE_OUT_OF_MEMORY;
    }
  }

  /* buffer to store previously received and encrypted data */
  if(BACKEND->encdata_buffer == NULL) {
    BACKEND->encdata_is_incomplete = false;
    BACKEND->encdata_offset = 0;
    BACKEND->encdata_length = CURL_SCHANNEL_BUFFER_INIT_SIZE;
    BACKEND->encdata_buffer = malloc(BACKEND->encdata_length);
    if(BACKEND->encdata_buffer == NULL) {
      failf(data, "schannel: unable to allocate memory");
      return CURLE_OUT_OF_MEMORY;
    }
  }

  /* if we need a bigger buffer to read a full message, increase buffer now */
  if(BACKEND->encdata_length - BACKEND->encdata_offset <
     CURL_SCHANNEL_BUFFER_FREE_SIZE) {
    /* increase internal encrypted data buffer */
    size_t reallocated_length = BACKEND->encdata_offset +
      CURL_SCHANNEL_BUFFER_FREE_SIZE;
    reallocated_buffer = realloc(BACKEND->encdata_buffer,
                                 reallocated_length);

    if(reallocated_buffer == NULL) {
      failf(data, "schannel: unable to re-allocate memory");
      return CURLE_OUT_OF_MEMORY;
    }
    else {
      BACKEND->encdata_buffer = reallocated_buffer;
      BACKEND->encdata_length = reallocated_length;
    }
  }

  for(;;) {
    TCHAR *host_name;
    if(doread) {
      /* read encrypted handshake data from socket */
      result = Curl_read_plain(conn->sock[sockindex],
                               (char *) (BACKEND->encdata_buffer +
                                         BACKEND->encdata_offset),
                               BACKEND->encdata_length -
                               BACKEND->encdata_offset,
                               &nread);
      if(result == CURLE_AGAIN) {
        if(connssl->connecting_state != ssl_connect_2_writing)
          connssl->connecting_state = ssl_connect_2_reading;
        DEBUGF(infof(data, "schannel: failed to receive handshake, "
                     "need more data\n"));
        return CURLE_OK;
      }
      else if((result != CURLE_OK) || (nread == 0)) {
        failf(data, "schannel: failed to receive handshake, "
              "SSL/TLS connection failed");
        return CURLE_SSL_CONNECT_ERROR;
      }

      /* increase encrypted data buffer offset */
      BACKEND->encdata_offset += nread;
      BACKEND->encdata_is_incomplete = false;
      DEBUGF(infof(data, "schannel: encrypted data got %zd\n", nread));
    }

    DEBUGF(infof(data,
                 "schannel: encrypted data buffer: offset %zu length %zu\n",
                 BACKEND->encdata_offset, BACKEND->encdata_length));

    /* setup input buffers */
    InitSecBuffer(&inbuf[0], SECBUFFER_TOKEN, malloc(BACKEND->encdata_offset),
                  curlx_uztoul(BACKEND->encdata_offset));
    InitSecBuffer(&inbuf[1], SECBUFFER_EMPTY, NULL, 0);
    InitSecBufferDesc(&inbuf_desc, inbuf, 2);

    /* setup output buffers */
    InitSecBuffer(&outbuf[0], SECBUFFER_TOKEN, NULL, 0);
    InitSecBuffer(&outbuf[1], SECBUFFER_ALERT, NULL, 0);
    InitSecBuffer(&outbuf[2], SECBUFFER_EMPTY, NULL, 0);
    InitSecBufferDesc(&outbuf_desc, outbuf, 3);

    if(inbuf[0].pvBuffer == NULL) {
      failf(data, "schannel: unable to allocate memory");
      return CURLE_OUT_OF_MEMORY;
    }

    /* copy received handshake data into input buffer */
    memcpy(inbuf[0].pvBuffer, BACKEND->encdata_buffer,
           BACKEND->encdata_offset);

    host_name = curlx_convert_UTF8_to_tchar(hostname);
    if(!host_name)
      return CURLE_OUT_OF_MEMORY;

    /* https://msdn.microsoft.com/en-us/library/windows/desktop/aa375924.aspx
     */
    sspi_status = s_pSecFn->InitializeSecurityContext(
      &BACKEND->cred->cred_handle, &BACKEND->ctxt->ctxt_handle,
      host_name, BACKEND->req_flags, 0, 0, &inbuf_desc, 0, NULL,
      &outbuf_desc, &BACKEND->ret_flags, &BACKEND->ctxt->time_stamp);

    curlx_unicodefree(host_name);

    /* free buffer for received handshake data */
    Curl_safefree(inbuf[0].pvBuffer);

    /* check if the handshake was incomplete */
    if(sspi_status == SEC_E_INCOMPLETE_MESSAGE) {
      BACKEND->encdata_is_incomplete = true;
      connssl->connecting_state = ssl_connect_2_reading;
      DEBUGF(infof(data,
                   "schannel: received incomplete message, need more data\n"));
      return CURLE_OK;
    }

    /* If the server has requested a client certificate, attempt to continue
       the handshake without one. This will allow connections to servers which
       request a client certificate but do not require it. */
    if(sspi_status == SEC_I_INCOMPLETE_CREDENTIALS &&
       !(BACKEND->req_flags & ISC_REQ_USE_SUPPLIED_CREDS)) {
      BACKEND->req_flags |= ISC_REQ_USE_SUPPLIED_CREDS;
      connssl->connecting_state = ssl_connect_2_writing;
      DEBUGF(infof(data,
                   "schannel: a client certificate has been requested\n"));
      return CURLE_OK;
    }

    /* check if the handshake needs to be continued */
    if(sspi_status == SEC_I_CONTINUE_NEEDED || sspi_status == SEC_E_OK) {
      for(i = 0; i < 3; i++) {
        /* search for handshake tokens that need to be send */
        if(outbuf[i].BufferType == SECBUFFER_TOKEN && outbuf[i].cbBuffer > 0) {
          DEBUGF(infof(data, "schannel: sending next handshake data: "
                       "sending %lu bytes...\n", outbuf[i].cbBuffer));

          /* send handshake token to server */
          result = Curl_write_plain(data, conn->sock[sockindex],
                                    outbuf[i].pvBuffer, outbuf[i].cbBuffer,
                                    &written);
          if((result != CURLE_OK) ||
             (outbuf[i].cbBuffer != (size_t) written)) {
            failf(data, "schannel: failed to send next handshake data: "
                  "sent %zd of %lu bytes", written, outbuf[i].cbBuffer);
            return CURLE_SSL_CONNECT_ERROR;
          }
        }

        /* free obsolete buffer */
        if(outbuf[i].pvBuffer != NULL) {
          s_pSecFn->FreeContextBuffer(outbuf[i].pvBuffer);
        }
      }
    }
    else {
      char buffer[STRERROR_LEN];
      switch(sspi_status) {
      case SEC_E_INSUFFICIENT_MEMORY:
        failf(data, "schannel: next InitializeSecurityContext failed: %s",
              Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer)));
        return CURLE_OUT_OF_MEMORY;
      case SEC_E_WRONG_PRINCIPAL:
        failf(data, "schannel: SNI or certificate check failed: %s",
              Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer)));
        return CURLE_PEER_FAILED_VERIFICATION;
      case SEC_E_UNTRUSTED_ROOT:
        failf(data, "schannel: %s",
              Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer)));
        return CURLE_PEER_FAILED_VERIFICATION;
        /*
          case SEC_E_INVALID_HANDLE:
          case SEC_E_INVALID_TOKEN:
          case SEC_E_LOGON_DENIED:
          case SEC_E_TARGET_UNKNOWN:
          case SEC_E_NO_AUTHENTICATING_AUTHORITY:
          case SEC_E_INTERNAL_ERROR:
          case SEC_E_NO_CREDENTIALS:
          case SEC_E_UNSUPPORTED_FUNCTION:
          case SEC_E_APPLICATION_PROTOCOL_MISMATCH:
        */
      default:
        failf(data, "schannel: next InitializeSecurityContext failed: %s",
              Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer)));
        return CURLE_SSL_CONNECT_ERROR;
      }
    }

    /* check if there was additional remaining encrypted data */
    if(inbuf[1].BufferType == SECBUFFER_EXTRA && inbuf[1].cbBuffer > 0) {
      DEBUGF(infof(data, "schannel: encrypted data length: %lu\n",
                   inbuf[1].cbBuffer));
      /*
        There are two cases where we could be getting extra data here:
        1) If we're renegotiating a connection and the handshake is already
        complete (from the server perspective), it can encrypted app data
        (not handshake data) in an extra buffer at this point.
        2) (sspi_status == SEC_I_CONTINUE_NEEDED) We are negotiating a
        connection and this extra data is part of the handshake.
        We should process the data immediately; waiting for the socket to
        be ready may fail since the server is done sending handshake data.
      */
      /* check if the remaining data is less than the total amount
         and therefore begins after the already processed data */
      if(BACKEND->encdata_offset > inbuf[1].cbBuffer) {
        memmove(BACKEND->encdata_buffer,
                (BACKEND->encdata_buffer + BACKEND->encdata_offset) -
                inbuf[1].cbBuffer, inbuf[1].cbBuffer);
        BACKEND->encdata_offset = inbuf[1].cbBuffer;
        if(sspi_status == SEC_I_CONTINUE_NEEDED) {
          doread = FALSE;
          continue;
        }
      }
    }
    else {
      BACKEND->encdata_offset = 0;
    }
    break;
  }

  /* check if the handshake needs to be continued */
  if(sspi_status == SEC_I_CONTINUE_NEEDED) {
    connssl->connecting_state = ssl_connect_2_reading;
    return CURLE_OK;
  }

  /* check if the handshake is complete */
  if(sspi_status == SEC_E_OK) {
    connssl->connecting_state = ssl_connect_3;
    DEBUGF(infof(data, "schannel: SSL/TLS handshake complete\n"));
  }

  pubkey_ptr = SSL_IS_PROXY() ?
    data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY] :
    data->set.str[STRING_SSL_PINNEDPUBLICKEY];
  if(pubkey_ptr) {
    result = pkp_pin_peer_pubkey(data, conn, sockindex, pubkey_ptr);
    if(result) {
      failf(data, "SSL: public key does not match pinned public key!");
      return result;
    }
  }

#ifdef HAS_MANUAL_VERIFY_API
  if(conn->ssl_config.verifypeer && BACKEND->use_manual_cred_validation) {
    return Curl_verify_certificate(data, conn, sockindex);
  }
#endif

  return CURLE_OK;
}