/* * read-wrapper to support reading from stdin on Windows. */ static ssize_t read_wincon(int fd, void *buf, size_t count) { HANDLE handle = NULL; DWORD mode, rcount = 0; BOOL success; if(fd == fileno(stdin)) { handle = GetStdHandle(STD_INPUT_HANDLE); } else { return read(fd, buf, count); } if(GetConsoleMode(handle, &mode)) { success = ReadConsole(handle, buf, curlx_uztoul(count), &rcount, NULL); } else { success = ReadFile(handle, buf, curlx_uztoul(count), &rcount, NULL); } if(success) { return rcount; } errno = GetLastError(); return -1; }
/* * write-wrapper to support writing to stdout and stderr on Windows. */ static ssize_t write_wincon(int fd, const void *buf, size_t count) { HANDLE handle = NULL; DWORD mode, wcount = 0; BOOL success; if(fd == fileno(stdout)) { handle = GetStdHandle(STD_OUTPUT_HANDLE); } else if(fd == fileno(stderr)) { handle = GetStdHandle(STD_ERROR_HANDLE); } else { return write(fd, buf, count); } if(GetConsoleMode(handle, &mode)) { success = WriteConsole(handle, buf, curlx_uztoul(count), &wcount, NULL); } else { success = WriteFile(handle, buf, curlx_uztoul(count), &wcount, NULL); } if(success) { return wcount; } errno = GetLastError(); return -1; }
/* * Curl_sasl_create_ntlm_type3_message() * * This is used to generate an already encoded NTLM type-3 message ready for * sending to the recipient. * * Parameters: * * data [in] - The session handle. * userp [in] - The user name in the format User or Domain\User. * passdwp [in] - The user's password. * ntlm [in/out] - The ntlm data struct being used and modified. * outptr [in/out] - The address where a pointer to newly allocated memory * holding the result will be stored upon completion. * outlen [out] - The length of the output message. * * Returns CURLE_OK on success. */ CURLcode Curl_sasl_create_ntlm_type3_message(struct SessionHandle *data, const char *userp, const char *passwdp, struct ntlmdata *ntlm, char **outptr, size_t *outlen) { CURLcode result = CURLE_OK; SecBuffer type_2_buf; SecBuffer type_3_buf; SecBufferDesc type_2_desc; SecBufferDesc type_3_desc; SECURITY_STATUS status; unsigned long attrs; TimeStamp expiry; /* For Windows 9x compatibility of SSPI calls */ (void) passwdp; (void) userp; /* Setup the type-2 "input" security buffer */ type_2_desc.ulVersion = SECBUFFER_VERSION; type_2_desc.cBuffers = 1; type_2_desc.pBuffers = &type_2_buf; type_2_buf.BufferType = SECBUFFER_TOKEN; type_2_buf.pvBuffer = ntlm->input_token; type_2_buf.cbBuffer = curlx_uztoul(ntlm->input_token_len); /* Setup the type-3 "output" security buffer */ type_3_desc.ulVersion = SECBUFFER_VERSION; type_3_desc.cBuffers = 1; type_3_desc.pBuffers = &type_3_buf; type_3_buf.BufferType = SECBUFFER_TOKEN; type_3_buf.pvBuffer = ntlm->output_token; type_3_buf.cbBuffer = curlx_uztoul(ntlm->token_max); /* Generate our type-3 message */ status = s_pSecFn->InitializeSecurityContext(ntlm->credentials, ntlm->context, (TCHAR *) TEXT(""), 0, 0, SECURITY_NETWORK_DREP, &type_2_desc, 0, ntlm->context, &type_3_desc, &attrs, &expiry); if(status != SEC_E_OK) { infof(data, "NTLM handshake failure (type-3 message): Status=%x\n", status); return CURLE_RECV_ERROR; } /* Base64 encode the response */ result = Curl_base64_encode(data, (char *) ntlm->output_token, type_3_buf.cbBuffer, outptr, outlen); Curl_sasl_ntlm_cleanup(ntlm); return result; }
/* * Curl_override_sspi_http_realm() * * This is used to populate the domain in a SSPI identity structure * The realm is extracted from the challenge message and used as the * domain if it is not already explicitly set. * * Parameters: * * chlg [in] - The challenge message. * identity [in/out] - The identity structure. * * Returns CURLE_OK on success. */ CURLcode Curl_override_sspi_http_realm(const char *chlg, SEC_WINNT_AUTH_IDENTITY *identity) { xcharp_u domain, dup_domain; /* If domain is blank or unset, check challenge message for realm */ if(!identity->Domain || !identity->DomainLength) { for(;;) { char value[DIGEST_MAX_VALUE_LENGTH]; char content[DIGEST_MAX_CONTENT_LENGTH]; /* Pass all additional spaces here */ while(*chlg && ISSPACE(*chlg)) chlg++; /* Extract a value=content pair */ if(Curl_auth_digest_get_pair(chlg, value, content, &chlg)) { if(strcasecompare(value, "realm")) { /* Setup identity's domain and length */ domain.tchar_ptr = Curl_convert_UTF8_to_tchar((char *) content); if(!domain.tchar_ptr) return CURLE_OUT_OF_MEMORY; dup_domain.tchar_ptr = _tcsdup(domain.tchar_ptr); if(!dup_domain.tchar_ptr) { Curl_unicodefree(domain.tchar_ptr); return CURLE_OUT_OF_MEMORY; } free(identity->Domain); identity->Domain = dup_domain.tbyte_ptr; identity->DomainLength = curlx_uztoul(_tcslen(dup_domain.tchar_ptr)); dup_domain.tchar_ptr = NULL; Curl_unicodefree(domain.tchar_ptr); } else { /* Unknown specifier, ignore it! */ } } else break; /* We're done here */ /* Pass all additional spaces here */ while(*chlg && ISSPACE(*chlg)) chlg++; /* Allow the list to be comma-separated */ if(',' == *chlg) chlg++; } } return CURLE_OK; }
static CURLcode schannel_connect_step2(struct connectdata *conn, int sockindex) { int i; ssize_t nread = -1, written = -1; struct SessionHandle *data = conn->data; struct ssl_connect_data *connssl = &conn->ssl[sockindex]; SecBuffer outbuf[2]; SecBufferDesc outbuf_desc; SecBuffer inbuf[2]; SecBufferDesc inbuf_desc; SECURITY_STATUS sspi_status = SEC_E_OK; TCHAR *host_name; CURLcode code; bool doread; doread = (connssl->connecting_state != ssl_connect_2_writing) ? TRUE : FALSE; infof(data, "schannel: SSL/TLS connection with %s port %hu (step 2/3)\n", conn->host.name, conn->remote_port); /* buffer to store previously received and encrypted data */ if(connssl->encdata_buffer == NULL) { connssl->encdata_offset = 0; connssl->encdata_length = CURL_SCHANNEL_BUFFER_INIT_SIZE; connssl->encdata_buffer = malloc(connssl->encdata_length); if(connssl->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(connssl->encdata_length - connssl->encdata_offset < CURL_SCHANNEL_BUFFER_FREE_SIZE) { /* increase internal encrypted data buffer */ connssl->encdata_length *= CURL_SCHANNEL_BUFFER_STEP_FACTOR; connssl->encdata_buffer = realloc(connssl->encdata_buffer, connssl->encdata_length); if(connssl->encdata_buffer == NULL) { failf(data, "schannel: unable to re-allocate memory"); return CURLE_OUT_OF_MEMORY; } } for(;;) { if(doread) { /* read encrypted handshake data from socket */ code = Curl_read_plain(conn->sock[sockindex], (char *) (connssl->encdata_buffer + connssl->encdata_offset), connssl->encdata_length - connssl->encdata_offset, &nread); if(code == CURLE_AGAIN) { if(connssl->connecting_state != ssl_connect_2_writing) connssl->connecting_state = ssl_connect_2_reading; infof(data, "schannel: failed to receive handshake, " "need more data\n"); return CURLE_OK; } else if((code != 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 */ connssl->encdata_offset += nread; } infof(data, "schannel: encrypted data buffer: offset %zu length %zu\n", connssl->encdata_offset, connssl->encdata_length); /* setup input buffers */ InitSecBuffer(&inbuf[0], SECBUFFER_TOKEN, malloc(connssl->encdata_offset), curlx_uztoul(connssl->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); InitSecBufferDesc(&outbuf_desc, outbuf, 2); 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, connssl->encdata_buffer, connssl->encdata_offset); host_name = Curl_convert_UTF8_to_tchar(conn->host.name); if(!host_name) return CURLE_OUT_OF_MEMORY; /* http://msdn.microsoft.com/en-us/library/windows/desktop/aa375924.aspx */ sspi_status = s_pSecFn->InitializeSecurityContext( &connssl->cred->cred_handle, &connssl->ctxt->ctxt_handle, host_name, connssl->req_flags, 0, 0, &inbuf_desc, 0, NULL, &outbuf_desc, &connssl->ret_flags, &connssl->ctxt->time_stamp); Curl_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) { connssl->connecting_state = ssl_connect_2_reading; infof(data, "schannel: received incomplete message, need more data\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 < 2; i++) { /* search for handshake tokens that need to be send */ if(outbuf[i].BufferType == SECBUFFER_TOKEN && outbuf[i].cbBuffer > 0) { infof(data, "schannel: sending next handshake data: " "sending %lu bytes...\n", outbuf[i].cbBuffer); /* send handshake token to server */ code = Curl_write_plain(conn, conn->sock[sockindex], outbuf[i].pvBuffer, outbuf[i].cbBuffer, &written); if((code != 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 { if(sspi_status == SEC_E_WRONG_PRINCIPAL) failf(data, "schannel: SNI or certificate check failed: %s", Curl_sspi_strerror(conn, sspi_status)); else failf(data, "schannel: next InitializeSecurityContext failed: %s", Curl_sspi_strerror(conn, sspi_status)); return CURLE_SSL_CONNECT_ERROR; } /* check if there was additional remaining encrypted data */ if(inbuf[1].BufferType == SECBUFFER_EXTRA && inbuf[1].cbBuffer > 0) { 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(connssl->encdata_offset > inbuf[1].cbBuffer) { memmove(connssl->encdata_buffer, (connssl->encdata_buffer + connssl->encdata_offset) - inbuf[1].cbBuffer, inbuf[1].cbBuffer); connssl->encdata_offset = inbuf[1].cbBuffer; if(sspi_status == SEC_I_CONTINUE_NEEDED) { doread = FALSE; continue; } } } else { connssl->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; infof(data, "schannel: SSL/TLS handshake complete\n"); } #ifdef _WIN32_WCE /* Windows CE doesn't do any server certificate validation. We have to do it manually. */ if(data->set.ssl.verifypeer) return verify_certificate(conn, sockindex); #endif return CURLE_OK; }
CURLcode Curl_input_negotiate(struct connectdata *conn, bool proxy, const char *header) { BYTE *input_token = NULL; SecBufferDesc out_buff_desc; SecBuffer out_sec_buff; SecBufferDesc in_buff_desc; SecBuffer in_sec_buff; SECURITY_STATUS status; unsigned long attrs; TimeStamp expiry; /* For Windows 9x compatibility of SSPI calls */ size_t len = 0, input_token_len = 0; CURLcode result; /* Point to the username and password */ const char *userp; const char *passwdp; /* Point to the correct struct with this */ struct negotiatedata *neg_ctx; if(proxy) { userp = conn->proxyuser; passwdp = conn->proxypasswd; neg_ctx = &conn->data->state.proxyneg; } else { userp = conn->user; passwdp = conn->passwd; neg_ctx = &conn->data->state.negotiate; } /* Not set means empty */ if(!userp) userp = ""; if(!passwdp) passwdp = ""; if(neg_ctx->context && neg_ctx->status == SEC_E_OK) { /* We finished successfully our part of authentication, but server * rejected it (since we're again here). Exit with an error since we * can't invent anything better */ Curl_cleanup_negotiate(conn->data); return CURLE_LOGIN_DENIED; } if(!neg_ctx->server_name) { /* Check proxy auth requested but no given proxy name */ if(proxy && !conn->proxy.name) return CURLE_BAD_FUNCTION_ARGUMENT; /* Generate our SPN */ neg_ctx->server_name = Curl_sasl_build_spn("HTTP", proxy ? conn->proxy.name : conn->host.name); if(!neg_ctx->server_name) return CURLE_OUT_OF_MEMORY; } if(!neg_ctx->output_token) { PSecPkgInfo SecurityPackage; status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_NEGOTIATE), &SecurityPackage); if(status != SEC_E_OK) return CURLE_NOT_BUILT_IN; /* Allocate input and output buffers according to the max token size as indicated by the security package */ neg_ctx->token_max = SecurityPackage->cbMaxToken; neg_ctx->output_token = malloc(neg_ctx->token_max); s_pSecFn->FreeContextBuffer(SecurityPackage); } /* Obtain the input token, if any */ header += strlen("Negotiate"); while(*header && ISSPACE(*header)) header++; len = strlen(header); if(!len) { /* Is this the first call in a new negotiation? */ if(neg_ctx->context) { /* The server rejected our authentication and hasn't suppled any more negotiation mechanisms */ return CURLE_LOGIN_DENIED; } /* We have to acquire credentials and allocate memory for the context */ neg_ctx->credentials = malloc(sizeof(CredHandle)); neg_ctx->context = malloc(sizeof(CtxtHandle)); if(!neg_ctx->credentials || !neg_ctx->context) return CURLE_OUT_OF_MEMORY; if(userp && *userp) { /* Populate our identity structure */ result = Curl_create_sspi_identity(userp, passwdp, &neg_ctx->identity); if(result) return result; /* Allow proper cleanup of the identity structure */ neg_ctx->p_identity = &neg_ctx->identity; } else /* Use the current Windows user */ neg_ctx->p_identity = NULL; /* Acquire our credientials handle */ neg_ctx->status = s_pSecFn->AcquireCredentialsHandle(NULL, (TCHAR *) TEXT(SP_NAME_NEGOTIATE), SECPKG_CRED_OUTBOUND, NULL, neg_ctx->p_identity, NULL, NULL, neg_ctx->credentials, &expiry); if(neg_ctx->status != SEC_E_OK) return CURLE_LOGIN_DENIED; } else { result = Curl_base64_decode(header, (unsigned char **)&input_token, &input_token_len); if(result) return result; if(!input_token_len) { infof(conn->data, "Negotiate handshake failure (empty challenge message)\n"); return CURLE_BAD_CONTENT_ENCODING; } } /* Setup the "output" security buffer */ out_buff_desc.ulVersion = SECBUFFER_VERSION; out_buff_desc.cBuffers = 1; out_buff_desc.pBuffers = &out_sec_buff; out_sec_buff.BufferType = SECBUFFER_TOKEN; out_sec_buff.pvBuffer = neg_ctx->output_token; out_sec_buff.cbBuffer = curlx_uztoul(neg_ctx->token_max); /* Setup the "input" security buffer if present */ if(input_token) { in_buff_desc.ulVersion = SECBUFFER_VERSION; in_buff_desc.cBuffers = 1; in_buff_desc.pBuffers = &in_sec_buff; in_sec_buff.BufferType = SECBUFFER_TOKEN; in_sec_buff.pvBuffer = input_token; in_sec_buff.cbBuffer = curlx_uztoul(input_token_len); } /* Generate our message */ neg_ctx->status = s_pSecFn->InitializeSecurityContext( neg_ctx->credentials, input_token ? neg_ctx->context : NULL, neg_ctx->server_name, ISC_REQ_CONFIDENTIALITY, 0, SECURITY_NATIVE_DREP, input_token ? &in_buff_desc : NULL, 0, neg_ctx->context, &out_buff_desc, &attrs, &expiry); free(input_token); if(GSS_ERROR(neg_ctx->status)) return CURLE_OUT_OF_MEMORY; if(neg_ctx->status == SEC_I_COMPLETE_NEEDED || neg_ctx->status == SEC_I_COMPLETE_AND_CONTINUE) { neg_ctx->status = s_pSecFn->CompleteAuthToken(neg_ctx->context, &out_buff_desc); if(GSS_ERROR(neg_ctx->status)) return CURLE_RECV_ERROR; } neg_ctx->output_token_length = out_sec_buff.cbBuffer; return CURLE_OK; }
static ssize_t schannel_send(struct connectdata *conn, int sockindex, const void *buf, size_t len, CURLcode *err) { ssize_t written = -1; size_t data_len = 0; unsigned char *data = NULL; struct ssl_connect_data *connssl = &conn->ssl[sockindex]; SecBuffer outbuf[4]; SecBufferDesc outbuf_desc; SECURITY_STATUS sspi_status = SEC_E_OK; CURLcode code; /* check if the maximum stream sizes were queried */ if(connssl->stream_sizes.cbMaximumMessage == 0) { sspi_status = s_pSecFn->QueryContextAttributes( &connssl->ctxt->ctxt_handle, SECPKG_ATTR_STREAM_SIZES, &connssl->stream_sizes); if(sspi_status != SEC_E_OK) { *err = CURLE_SEND_ERROR; return -1; } } /* check if the buffer is longer than the maximum message length */ if(len > connssl->stream_sizes.cbMaximumMessage) { *err = CURLE_SEND_ERROR; return -1; } /* calculate the complete message length and allocate a buffer for it */ data_len = connssl->stream_sizes.cbHeader + len + connssl->stream_sizes.cbTrailer; data = (unsigned char*) malloc(data_len); if(data == NULL) { *err = CURLE_OUT_OF_MEMORY; return -1; } /* setup output buffers (header, data, trailer, empty) */ InitSecBuffer(&outbuf[0], SECBUFFER_STREAM_HEADER, data, connssl->stream_sizes.cbHeader); InitSecBuffer(&outbuf[1], SECBUFFER_DATA, data + connssl->stream_sizes.cbHeader, curlx_uztoul(len)); InitSecBuffer(&outbuf[2], SECBUFFER_STREAM_TRAILER, data + connssl->stream_sizes.cbHeader + len, connssl->stream_sizes.cbTrailer); InitSecBuffer(&outbuf[3], SECBUFFER_EMPTY, NULL, 0); InitSecBufferDesc(&outbuf_desc, outbuf, 4); /* copy data into output buffer */ memcpy(outbuf[1].pvBuffer, buf, len); /* http://msdn.microsoft.com/en-us/library/windows/desktop/aa375390.aspx */ sspi_status = s_pSecFn->EncryptMessage(&connssl->ctxt->ctxt_handle, 0, &outbuf_desc, 0); /* check if the message was encrypted */ if(sspi_status == SEC_E_OK) { written = 0; /* send the encrypted message including header, data and trailer */ len = outbuf[0].cbBuffer + outbuf[1].cbBuffer + outbuf[2].cbBuffer; /* It's important to send the full message which includes the header, encrypted payload, and trailer. Until the client receives all the data a coherent message has not been delivered and the client can't read any of it. If we wanted to buffer the unwritten encrypted bytes, we would tell the client that all data it has requested to be sent has been sent. The unwritten encrypted bytes would be the first bytes to send on the next invocation. Here's the catch with this - if we tell the client that all the bytes have been sent, will the client call this method again to send the buffered data? Looking at who calls this function, it seems the answer is NO. */ /* send entire message or fail */ while(len > (size_t)written) { ssize_t this_write; long timeleft; int what; this_write = 0; timeleft = Curl_timeleft(conn->data, NULL, TRUE); if(timeleft < 0) { /* we already got the timeout */ failf(conn->data, "schannel: timed out sending data " "(bytes sent: %zd)", written); *err = CURLE_OPERATION_TIMEDOUT; written = -1; break; } what = Curl_socket_ready(CURL_SOCKET_BAD, conn->sock[sockindex], timeleft); if(what < 0) { /* fatal error */ failf(conn->data, "select/poll on SSL socket, errno: %d", SOCKERRNO); *err = CURLE_SEND_ERROR; written = -1; break; } else if(0 == what) { failf(conn->data, "schannel: timed out sending data " "(bytes sent: %zd)", written); *err = CURLE_OPERATION_TIMEDOUT; written = -1; break; } /* socket is writable */ code = Curl_write_plain(conn, conn->sock[sockindex], data + written, len - written, &this_write); if(code == CURLE_AGAIN) continue; else if(code != CURLE_OK) { *err = code; written = -1; break; } written += this_write; } } else if(sspi_status == SEC_E_INSUFFICIENT_MEMORY) { *err = CURLE_OUT_OF_MEMORY; } else{ *err = CURLE_SEND_ERROR; } Curl_safefree(data); if(len == (size_t)written) /* Encrypted message including header, data and trailer entirely sent. The return value is the number of unencrypted bytes that were sent. */ written = outbuf[1].cbBuffer; return written; }
/* * Curl_sasl_create_gssapi_user_message() * * This is used to generate an already encoded GSSAPI (Kerberos V5) user token * message ready for sending to the recipient. * * Parameters: * * data [in] - The session handle. * userp [in] - The user name in the format User or Domain\User. * passdwp [in] - The user's password. * service [in] - The service type such as www, smtp, pop or imap. * mutual_auth [in] - Flag specifing whether or not mutual authentication * is enabled. * chlg64 [in] - The optional base64 encoded challenge message. * krb5 [in/out] - The gssapi data struct being used and modified. * outptr [in/out] - The address where a pointer to newly allocated memory * holding the result will be stored upon completion. * outlen [out] - The length of the output message. * * Returns CURLE_OK on success. */ CURLcode Curl_sasl_create_gssapi_user_message(struct SessionHandle *data, const char *userp, const char *passwdp, const char *service, const bool mutual_auth, const char *chlg64, struct kerberos5data *krb5, char **outptr, size_t *outlen) { CURLcode result = CURLE_OK; size_t chlglen = 0; unsigned char *chlg = NULL; CtxtHandle context; PSecPkgInfo SecurityPackage; SecBuffer chlg_buf; SecBuffer resp_buf; SecBufferDesc chlg_desc; SecBufferDesc resp_desc; SECURITY_STATUS status; unsigned long attrs; TimeStamp expiry; /* For Windows 9x compatibility of SSPI calls */ if(!krb5->credentials) { /* Query the security package for Kerberos */ status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_KERBEROS), &SecurityPackage); if(status != SEC_E_OK) { return CURLE_NOT_BUILT_IN; } krb5->token_max = SecurityPackage->cbMaxToken; /* Release the package buffer as it is not required anymore */ s_pSecFn->FreeContextBuffer(SecurityPackage); /* Allocate our response buffer */ krb5->output_token = malloc(krb5->token_max); if(!krb5->output_token) return CURLE_OUT_OF_MEMORY; /* Generate our SPN */ krb5->spn = Curl_sasl_build_spn(service, data->easy_conn->host.name); if(!krb5->spn) return CURLE_OUT_OF_MEMORY; if(userp && *userp) { /* Populate our identity structure */ result = Curl_create_sspi_identity(userp, passwdp, &krb5->identity); if(result) return result; /* Allow proper cleanup of the identity structure */ krb5->p_identity = &krb5->identity; } else /* Use the current Windows user */ krb5->p_identity = NULL; /* Allocate our credentials handle */ krb5->credentials = malloc(sizeof(CredHandle)); if(!krb5->credentials) return CURLE_OUT_OF_MEMORY; memset(krb5->credentials, 0, sizeof(CredHandle)); /* Acquire our credentials handle */ status = s_pSecFn->AcquireCredentialsHandle(NULL, (TCHAR *) TEXT(SP_NAME_KERBEROS), SECPKG_CRED_OUTBOUND, NULL, krb5->p_identity, NULL, NULL, krb5->credentials, &expiry); if(status != SEC_E_OK) return CURLE_LOGIN_DENIED; /* Allocate our new context handle */ krb5->context = malloc(sizeof(CtxtHandle)); if(!krb5->context) return CURLE_OUT_OF_MEMORY; memset(krb5->context, 0, sizeof(CtxtHandle)); } else { /* Decode the base-64 encoded challenge message */ if(strlen(chlg64) && *chlg64 != '=') { result = Curl_base64_decode(chlg64, &chlg, &chlglen); if(result) return result; } /* Ensure we have a valid challenge message */ if(!chlg) { infof(data, "GSSAPI handshake failure (empty challenge message)\n"); return CURLE_BAD_CONTENT_ENCODING; } /* Setup the challenge "input" security buffer */ chlg_desc.ulVersion = SECBUFFER_VERSION; chlg_desc.cBuffers = 1; chlg_desc.pBuffers = &chlg_buf; chlg_buf.BufferType = SECBUFFER_TOKEN; chlg_buf.pvBuffer = chlg; chlg_buf.cbBuffer = curlx_uztoul(chlglen); } /* Setup the response "output" security buffer */ resp_desc.ulVersion = SECBUFFER_VERSION; resp_desc.cBuffers = 1; resp_desc.pBuffers = &resp_buf; resp_buf.BufferType = SECBUFFER_TOKEN; resp_buf.pvBuffer = krb5->output_token; resp_buf.cbBuffer = curlx_uztoul(krb5->token_max); /* Generate our challenge-response message */ status = s_pSecFn->InitializeSecurityContext(krb5->credentials, chlg ? krb5->context : NULL, krb5->spn, (mutual_auth ? ISC_REQ_MUTUAL_AUTH : 0), 0, SECURITY_NATIVE_DREP, chlg ? &chlg_desc : NULL, 0, &context, &resp_desc, &attrs, &expiry); if(status != SEC_E_OK && status != SEC_I_CONTINUE_NEEDED) { free(chlg); return CURLE_RECV_ERROR; } if(memcmp(&context, krb5->context, sizeof(context))) { s_pSecFn->DeleteSecurityContext(krb5->context); memcpy(krb5->context, &context, sizeof(context)); } if(resp_buf.cbBuffer) { /* Base64 encode the response */ result = Curl_base64_encode(data, (char *)resp_buf.pvBuffer, resp_buf.cbBuffer, outptr, outlen); } /* Free the decoded challenge */ free(chlg); return result; }
/* * Curl_sasl_create_digest_http_message() * * This is used to generate a HTTP DIGEST response message ready for sending * to the recipient. * * Parameters: * * data [in] - The session handle. * userp [in] - The user name in the format User or Domain\User. * passdwp [in] - The user's password. * request [in] - The HTTP request. * uripath [in] - The path of the HTTP uri. * digest [in/out] - The digest data struct being used and modified. * outptr [in/out] - The address where a pointer to newly allocated memory * holding the result will be stored upon completion. * outlen [out] - The length of the output message. * * Returns CURLE_OK on success. */ CURLcode Curl_sasl_create_digest_http_message(struct SessionHandle *data, const char *userp, const char *passwdp, const unsigned char *request, const unsigned char *uripath, struct digestdata *digest, char **outptr, size_t *outlen) { size_t token_max; CredHandle credentials; CtxtHandle context; char *resp; BYTE *output_token; PSecPkgInfo SecurityPackage; SEC_WINNT_AUTH_IDENTITY identity; SEC_WINNT_AUTH_IDENTITY *p_identity; SecBuffer chlg_buf[3]; SecBuffer resp_buf; SecBufferDesc chlg_desc; SecBufferDesc resp_desc; SECURITY_STATUS status; unsigned long attrs; TimeStamp expiry; /* For Windows 9x compatibility of SSPI calls */ TCHAR *spn; (void) data; /* Query the security package for DigestSSP */ status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_DIGEST), &SecurityPackage); if(status != SEC_E_OK) return CURLE_NOT_BUILT_IN; token_max = SecurityPackage->cbMaxToken; /* Release the package buffer as it is not required anymore */ s_pSecFn->FreeContextBuffer(SecurityPackage); /* Allocate the output buffer according to the max token size as indicated by the security package */ output_token = malloc(token_max); if(!output_token) return CURLE_OUT_OF_MEMORY; if(userp && *userp) { /* Populate our identity structure */ if(Curl_create_sspi_identity(userp, passwdp, &identity)) return CURLE_OUT_OF_MEMORY; /* Populate our identity domain */ if(Curl_override_sspi_http_realm((const char*)digest->input_token, &identity)) return CURLE_OUT_OF_MEMORY; /* Allow proper cleanup of the identity structure */ p_identity = &identity; } else /* Use the current Windows user */ p_identity = NULL; /* Acquire our credentials handle */ status = s_pSecFn->AcquireCredentialsHandle(NULL, (TCHAR *) TEXT(SP_NAME_DIGEST), SECPKG_CRED_OUTBOUND, NULL, p_identity, NULL, NULL, &credentials, &expiry); if(status != SEC_E_OK) { Curl_sspi_free_identity(p_identity); free(output_token); return CURLE_LOGIN_DENIED; } /* Setup the challenge "input" security buffer if present */ chlg_desc.ulVersion = SECBUFFER_VERSION; chlg_desc.cBuffers = 3; chlg_desc.pBuffers = chlg_buf; chlg_buf[0].BufferType = SECBUFFER_TOKEN; chlg_buf[0].pvBuffer = digest->input_token; chlg_buf[0].cbBuffer = curlx_uztoul(digest->input_token_len); chlg_buf[1].BufferType = SECBUFFER_PKG_PARAMS; chlg_buf[1].pvBuffer = (void *)request; chlg_buf[1].cbBuffer = curlx_uztoul(strlen((const char *) request)); chlg_buf[2].BufferType = SECBUFFER_PKG_PARAMS; chlg_buf[2].pvBuffer = NULL; chlg_buf[2].cbBuffer = 0; /* Setup the response "output" security buffer */ resp_desc.ulVersion = SECBUFFER_VERSION; resp_desc.cBuffers = 1; resp_desc.pBuffers = &resp_buf; resp_buf.BufferType = SECBUFFER_TOKEN; resp_buf.pvBuffer = output_token; resp_buf.cbBuffer = curlx_uztoul(token_max); spn = Curl_convert_UTF8_to_tchar((char *) uripath); if(!spn) { Curl_sspi_free_identity(p_identity); free(output_token); return CURLE_OUT_OF_MEMORY; } /* Generate our reponse message */ status = s_pSecFn->InitializeSecurityContext(&credentials, NULL, spn, ISC_REQ_USE_HTTP_STYLE, 0, 0, &chlg_desc, 0, &context, &resp_desc, &attrs, &expiry); Curl_unicodefree(spn); if(status == SEC_I_COMPLETE_NEEDED || status == SEC_I_COMPLETE_AND_CONTINUE) s_pSecFn->CompleteAuthToken(&credentials, &resp_desc); else if(status != SEC_E_OK && status != SEC_I_CONTINUE_NEEDED) { s_pSecFn->FreeCredentialsHandle(&credentials); Curl_sspi_free_identity(p_identity); free(output_token); return CURLE_OUT_OF_MEMORY; } resp = malloc(resp_buf.cbBuffer + 1); if(!resp) { s_pSecFn->DeleteSecurityContext(&context); s_pSecFn->FreeCredentialsHandle(&credentials); Curl_sspi_free_identity(p_identity); free(output_token); return CURLE_OUT_OF_MEMORY; } /* Copy the generated reponse */ memcpy(resp, resp_buf.pvBuffer, resp_buf.cbBuffer); resp[resp_buf.cbBuffer] = 0x00; /* Return the response */ *outptr = resp; *outlen = resp_buf.cbBuffer; /* Free our handles */ s_pSecFn->DeleteSecurityContext(&context); s_pSecFn->FreeCredentialsHandle(&credentials); /* Free the identity structure */ Curl_sspi_free_identity(p_identity); /* Free the response buffer */ free(output_token); return CURLE_OK; }
/* * Curl_sasl_create_ntlm_type1_message() * * This is used to generate an already encoded NTLM type-1 message ready for * sending to the recipient. * * Parameters: * * userp [in] - The user name in the format User or Domain\User. * passdwp [in] - The user's password. * ntlm [in/out] - The ntlm data struct being used and modified. * outptr [in/out] - The address where a pointer to newly allocated memory * holding the result will be stored upon completion. * outlen [out] - The length of the output message. * * Returns CURLE_OK on success. */ CURLcode Curl_sasl_create_ntlm_type1_message(const char *userp, const char *passwdp, struct ntlmdata *ntlm, char **outptr, size_t *outlen) { PSecPkgInfo SecurityPackage; SecBuffer type_1_buf; SecBufferDesc type_1_desc; SECURITY_STATUS status; unsigned long attrs; TimeStamp expiry; /* For Windows 9x compatibility of SSPI calls */ /* Clean up any former leftovers and initialise to defaults */ Curl_sasl_ntlm_cleanup(ntlm); /* Query the security package for NTLM */ status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_NTLM), &SecurityPackage); if(status != SEC_E_OK) return CURLE_NOT_BUILT_IN; ntlm->token_max = SecurityPackage->cbMaxToken; /* Release the package buffer as it is not required anymore */ s_pSecFn->FreeContextBuffer(SecurityPackage); /* Allocate our output buffer */ ntlm->output_token = malloc(ntlm->token_max); if(!ntlm->output_token) return CURLE_OUT_OF_MEMORY; if(userp && *userp) { CURLcode result; /* Populate our identity structure */ result = Curl_create_sspi_identity(userp, passwdp, &ntlm->identity); if(result) return result; /* Allow proper cleanup of the identity structure */ ntlm->p_identity = &ntlm->identity; } else /* Use the current Windows user */ ntlm->p_identity = NULL; /* Allocate our credentials handle */ ntlm->credentials = malloc(sizeof(CredHandle)); if(!ntlm->credentials) return CURLE_OUT_OF_MEMORY; memset(ntlm->credentials, 0, sizeof(CredHandle)); /* Acquire our credentials handle */ status = s_pSecFn->AcquireCredentialsHandle(NULL, (TCHAR *) TEXT(SP_NAME_NTLM), SECPKG_CRED_OUTBOUND, NULL, ntlm->p_identity, NULL, NULL, ntlm->credentials, &expiry); if(status != SEC_E_OK) return CURLE_LOGIN_DENIED; /* Allocate our new context handle */ ntlm->context = malloc(sizeof(CtxtHandle)); if(!ntlm->context) return CURLE_OUT_OF_MEMORY; memset(ntlm->context, 0, sizeof(CtxtHandle)); /* Setup the type-1 "output" security buffer */ type_1_desc.ulVersion = SECBUFFER_VERSION; type_1_desc.cBuffers = 1; type_1_desc.pBuffers = &type_1_buf; type_1_buf.BufferType = SECBUFFER_TOKEN; type_1_buf.pvBuffer = ntlm->output_token; type_1_buf.cbBuffer = curlx_uztoul(ntlm->token_max); /* Generate our type-1 message */ status = s_pSecFn->InitializeSecurityContext(ntlm->credentials, NULL, (TCHAR *) TEXT(""), 0, 0, SECURITY_NETWORK_DREP, NULL, 0, ntlm->context, &type_1_desc, &attrs, &expiry); if(status == SEC_I_COMPLETE_NEEDED || status == SEC_I_COMPLETE_AND_CONTINUE) s_pSecFn->CompleteAuthToken(ntlm->context, &type_1_desc); else if(status != SEC_E_OK && status != SEC_I_CONTINUE_NEEDED) return CURLE_RECV_ERROR; /* Base64 encode the response */ return Curl_base64_encode(NULL, (char *) ntlm->output_token, type_1_buf.cbBuffer, outptr, outlen); }
/* * Curl_sasl_create_gssapi_security_message() * * This is used to generate an already encoded GSSAPI (Kerberos V5) security * token message ready for sending to the recipient. * * Parameters: * * data [in] - The session handle. * chlg64 [in] - The optional base64 encoded challenge message. * krb5 [in/out] - The gssapi data struct being used and modified. * outptr [in/out] - The address where a pointer to newly allocated memory * holding the result will be stored upon completion. * outlen [out] - The length of the output message. * * Returns CURLE_OK on success. */ CURLcode Curl_sasl_create_gssapi_security_message(struct SessionHandle *data, const char *chlg64, struct kerberos5data *krb5, char **outptr, size_t *outlen) { CURLcode result = CURLE_OK; size_t offset = 0; size_t chlglen = 0; size_t messagelen = 0; size_t appdatalen = 0; unsigned char *chlg = NULL; unsigned char *trailer = NULL; unsigned char *message = NULL; unsigned char *padding = NULL; unsigned char *appdata = NULL; SecBuffer input_buf[2]; SecBuffer wrap_buf[3]; SecBufferDesc input_desc; SecBufferDesc wrap_desc; unsigned long indata = 0; unsigned long outdata = 0; unsigned long qop = 0; unsigned long sec_layer = 0; unsigned long max_size = 0; SecPkgContext_Sizes sizes; SecPkgCredentials_Names names; SECURITY_STATUS status; char *user_name; /* Decode the base-64 encoded input message */ if(strlen(chlg64) && *chlg64 != '=') { result = Curl_base64_decode(chlg64, &chlg, &chlglen); if(result) return result; } /* Ensure we have a valid challenge message */ if(!chlg) { infof(data, "GSSAPI handshake failure (empty security message)\n"); return CURLE_BAD_CONTENT_ENCODING; } /* Get our response size information */ status = s_pSecFn->QueryContextAttributes(krb5->context, SECPKG_ATTR_SIZES, &sizes); if(status != SEC_E_OK) { free(chlg); return CURLE_OUT_OF_MEMORY; } /* Get the fully qualified username back from the context */ status = s_pSecFn->QueryCredentialsAttributes(krb5->credentials, SECPKG_CRED_ATTR_NAMES, &names); if(status != SEC_E_OK) { free(chlg); return CURLE_RECV_ERROR; } /* Setup the "input" security buffer */ input_desc.ulVersion = SECBUFFER_VERSION; input_desc.cBuffers = 2; input_desc.pBuffers = input_buf; input_buf[0].BufferType = SECBUFFER_STREAM; input_buf[0].pvBuffer = chlg; input_buf[0].cbBuffer = curlx_uztoul(chlglen); input_buf[1].BufferType = SECBUFFER_DATA; input_buf[1].pvBuffer = NULL; input_buf[1].cbBuffer = 0; /* Decrypt the inbound challenge and obtain the qop */ status = s_pSecFn->DecryptMessage(krb5->context, &input_desc, 0, &qop); if(status != SEC_E_OK) { infof(data, "GSSAPI handshake failure (empty security message)\n"); free(chlg); return CURLE_BAD_CONTENT_ENCODING; } /* Not 4 octets long so fail as per RFC4752 Section 3.1 */ if(input_buf[1].cbBuffer != 4) { infof(data, "GSSAPI handshake failure (invalid security data)\n"); free(chlg); return CURLE_BAD_CONTENT_ENCODING; } /* Copy the data out and free the challenge as it is not required anymore */ memcpy(&indata, input_buf[1].pvBuffer, 4); s_pSecFn->FreeContextBuffer(input_buf[1].pvBuffer); free(chlg); /* Extract the security layer */ sec_layer = indata & 0x000000FF; if(!(sec_layer & KERB_WRAP_NO_ENCRYPT)) { infof(data, "GSSAPI handshake failure (invalid security layer)\n"); return CURLE_BAD_CONTENT_ENCODING; } /* Extract the maximum message size the server can receive */ max_size = ntohl(indata & 0xFFFFFF00); if(max_size > 0) { /* The server has told us it supports a maximum receive buffer, however, as we don't require one unless we are encrypting data, we tell the server our receive buffer is zero. */ max_size = 0; } /* Allocate the trailer */ trailer = malloc(sizes.cbSecurityTrailer); if(!trailer) return CURLE_OUT_OF_MEMORY; /* Convert the user name to UTF8 when operating with Unicode */ user_name = Curl_convert_tchar_to_UTF8(names.sUserName); if(!user_name) { free(trailer); return CURLE_OUT_OF_MEMORY; } /* Allocate our message */ messagelen = sizeof(outdata) + strlen(user_name) + 1; message = malloc(messagelen); if(!message) { free(trailer); Curl_unicodefree(user_name); return CURLE_OUT_OF_MEMORY; } /* Populate the message with the security layer, client supported receive message size and authorization identity including the 0x00 based terminator. Note: Dispite RFC4752 Section 3.1 stating "The authorization identity is not terminated with the zero-valued (%x00) octet." it seems necessary to include it. */ outdata = htonl(max_size) | sec_layer; memcpy(message, &outdata, sizeof(outdata)); strcpy((char *) message + sizeof(outdata), user_name); Curl_unicodefree(user_name); /* Allocate the padding */ padding = malloc(sizes.cbBlockSize); if(!padding) { free(message); free(trailer); return CURLE_OUT_OF_MEMORY; } /* Setup the "authentication data" security buffer */ wrap_desc.ulVersion = SECBUFFER_VERSION; wrap_desc.cBuffers = 3; wrap_desc.pBuffers = wrap_buf; wrap_buf[0].BufferType = SECBUFFER_TOKEN; wrap_buf[0].pvBuffer = trailer; wrap_buf[0].cbBuffer = sizes.cbSecurityTrailer; wrap_buf[1].BufferType = SECBUFFER_DATA; wrap_buf[1].pvBuffer = message; wrap_buf[1].cbBuffer = curlx_uztoul(messagelen); wrap_buf[2].BufferType = SECBUFFER_PADDING; wrap_buf[2].pvBuffer = padding; wrap_buf[2].cbBuffer = sizes.cbBlockSize; /* Encrypt the data */ status = s_pSecFn->EncryptMessage(krb5->context, KERB_WRAP_NO_ENCRYPT, &wrap_desc, 0); if(status != SEC_E_OK) { free(padding); free(message); free(trailer); return CURLE_OUT_OF_MEMORY; } /* Allocate the encryption (wrap) buffer */ appdatalen = wrap_buf[0].cbBuffer + wrap_buf[1].cbBuffer + wrap_buf[2].cbBuffer; appdata = malloc(appdatalen); if(!appdata) { free(padding); free(message); free(trailer); return CURLE_OUT_OF_MEMORY; } /* Populate the encryption buffer */ memcpy(appdata, wrap_buf[0].pvBuffer, wrap_buf[0].cbBuffer); offset += wrap_buf[0].cbBuffer; memcpy(appdata + offset, wrap_buf[1].pvBuffer, wrap_buf[1].cbBuffer); offset += wrap_buf[1].cbBuffer; memcpy(appdata + offset, wrap_buf[2].pvBuffer, wrap_buf[2].cbBuffer); /* Base64 encode the response */ result = Curl_base64_encode(data, (char *)appdata, appdatalen, outptr, outlen); /* Free all of our local buffers */ free(appdata); free(padding); free(message); free(trailer); return result; }
/* * Curl_sasl_create_digest_md5_message() * * This is used to generate an already encoded DIGEST-MD5 response message * ready for sending to the recipient. * * Parameters: * * data [in] - The session handle. * chlg64 [in] - The base64 encoded challenge message. * userp [in] - The user name in the format User or Domain\User. * passdwp [in] - The user's password. * service [in] - The service type such as www, smtp, pop or imap. * outptr [in/out] - The address where a pointer to newly allocated memory * holding the result will be stored upon completion. * outlen [out] - The length of the output message. * * Returns CURLE_OK on success. */ CURLcode Curl_sasl_create_digest_md5_message(struct SessionHandle *data, const char *chlg64, const char *userp, const char *passwdp, const char *service, char **outptr, size_t *outlen) { CURLcode result = CURLE_OK; TCHAR *spn = NULL; size_t chlglen = 0; size_t token_max = 0; unsigned char *input_token = NULL; unsigned char *output_token = NULL; CredHandle credentials; CtxtHandle context; PSecPkgInfo SecurityPackage; SEC_WINNT_AUTH_IDENTITY identity; SEC_WINNT_AUTH_IDENTITY *p_identity; SecBuffer chlg_buf; SecBuffer resp_buf; SecBufferDesc chlg_desc; SecBufferDesc resp_desc; SECURITY_STATUS status; unsigned long attrs; TimeStamp expiry; /* For Windows 9x compatibility of SSPI calls */ /* Decode the base-64 encoded challenge message */ if(strlen(chlg64) && *chlg64 != '=') { result = Curl_base64_decode(chlg64, &input_token, &chlglen); if(result) return result; } /* Ensure we have a valid challenge message */ if(!input_token) { infof(data, "DIGEST-MD5 handshake failure (empty challenge message)\n"); return CURLE_BAD_CONTENT_ENCODING; } /* Query the security package for DigestSSP */ status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_DIGEST), &SecurityPackage); if(status != SEC_E_OK) { free(input_token); return CURLE_NOT_BUILT_IN; } token_max = SecurityPackage->cbMaxToken; /* Release the package buffer as it is not required anymore */ s_pSecFn->FreeContextBuffer(SecurityPackage); /* Allocate our response buffer */ output_token = malloc(token_max); if(!output_token) { free(input_token); return CURLE_OUT_OF_MEMORY; } /* Generate our SPN */ spn = Curl_sasl_build_spn(service, data->easy_conn->host.name); if(!spn) { free(output_token); free(input_token); return CURLE_OUT_OF_MEMORY; } if(userp && *userp) { /* Populate our identity structure */ result = Curl_create_sspi_identity(userp, passwdp, &identity); if(result) { free(spn); free(output_token); free(input_token); return result; } /* Allow proper cleanup of the identity structure */ p_identity = &identity; } else /* Use the current Windows user */ p_identity = NULL; /* Acquire our credentials handle */ status = s_pSecFn->AcquireCredentialsHandle(NULL, (TCHAR *) TEXT(SP_NAME_DIGEST), SECPKG_CRED_OUTBOUND, NULL, p_identity, NULL, NULL, &credentials, &expiry); if(status != SEC_E_OK) { Curl_sspi_free_identity(p_identity); free(spn); free(output_token); free(input_token); return CURLE_LOGIN_DENIED; } /* Setup the challenge "input" security buffer */ chlg_desc.ulVersion = SECBUFFER_VERSION; chlg_desc.cBuffers = 1; chlg_desc.pBuffers = &chlg_buf; chlg_buf.BufferType = SECBUFFER_TOKEN; chlg_buf.pvBuffer = input_token; chlg_buf.cbBuffer = curlx_uztoul(chlglen); /* Setup the response "output" security buffer */ resp_desc.ulVersion = SECBUFFER_VERSION; resp_desc.cBuffers = 1; resp_desc.pBuffers = &resp_buf; resp_buf.BufferType = SECBUFFER_TOKEN; resp_buf.pvBuffer = output_token; resp_buf.cbBuffer = curlx_uztoul(token_max); /* Generate our response message */ status = s_pSecFn->InitializeSecurityContext(&credentials, NULL, spn, 0, 0, 0, &chlg_desc, 0, &context, &resp_desc, &attrs, &expiry); if(status == SEC_I_COMPLETE_NEEDED || status == SEC_I_COMPLETE_AND_CONTINUE) s_pSecFn->CompleteAuthToken(&credentials, &resp_desc); else if(status != SEC_E_OK && status != SEC_I_CONTINUE_NEEDED) { s_pSecFn->FreeCredentialsHandle(&credentials); Curl_sspi_free_identity(p_identity); free(spn); free(output_token); free(input_token); return CURLE_RECV_ERROR; } /* Base64 encode the response */ result = Curl_base64_encode(data, (char *) output_token, resp_buf.cbBuffer, outptr, outlen); /* Free our handles */ s_pSecFn->DeleteSecurityContext(&context); s_pSecFn->FreeCredentialsHandle(&credentials); /* Free the identity structure */ Curl_sspi_free_identity(p_identity); /* Free the SPN */ free(spn); /* Free the response buffer */ free(output_token); /* Free the decoded challenge message */ free(input_token); return result; }
/* returning zero (0) means success, everything else is treated as "failure" with no care exactly what the failure was */ int Curl_input_negotiate(struct connectdata *conn, bool proxy, const char *header) { struct negotiatedata *neg_ctx = proxy?&conn->data->state.proxyneg: &conn->data->state.negotiate; BYTE *input_token = 0; SecBufferDesc out_buff_desc; SecBuffer out_sec_buff; SecBufferDesc in_buff_desc; SecBuffer in_sec_buff; unsigned long context_attributes; TimeStamp lifetime; TCHAR *sname; int ret; size_t len = 0, input_token_len = 0; bool gss = FALSE; const char* protocol; CURLcode error; while(*header && ISSPACE(*header)) header++; if(checkprefix("GSS-Negotiate", header)) { protocol = "GSS-Negotiate"; gss = TRUE; } else if(checkprefix("Negotiate", header)) { protocol = "Negotiate"; gss = FALSE; } else return -1; if(neg_ctx->context) { if(neg_ctx->gss != gss) { return -1; } } else { neg_ctx->protocol = protocol; neg_ctx->gss = gss; } if(neg_ctx->context && neg_ctx->status == SEC_E_OK) { /* We finished successfully our part of authentication, but server * rejected it (since we're again here). Exit with an error since we * can't invent anything better */ Curl_cleanup_negotiate(conn->data); return -1; } if(0 == strlen(neg_ctx->server_name)) { ret = get_gss_name(conn, proxy, neg_ctx); if(ret) return ret; } if(!neg_ctx->output_token) { PSecPkgInfo SecurityPackage; ret = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT("Negotiate"), &SecurityPackage); if(ret != SEC_E_OK) return -1; /* Allocate input and output buffers according to the max token size as indicated by the security package */ neg_ctx->max_token_length = SecurityPackage->cbMaxToken; neg_ctx->output_token = malloc(neg_ctx->max_token_length); s_pSecFn->FreeContextBuffer(SecurityPackage); } /* Obtain the input token, if any */ header += strlen(neg_ctx->protocol); while(*header && ISSPACE(*header)) header++; len = strlen(header); if(!len) { /* first call in a new negotation, we have to acquire credentials, and allocate memory for the context */ neg_ctx->credentials = malloc(sizeof(CredHandle)); neg_ctx->context = malloc(sizeof(CtxtHandle)); if(!neg_ctx->credentials || !neg_ctx->context) return -1; neg_ctx->status = s_pSecFn->AcquireCredentialsHandle(NULL, (TCHAR *) TEXT("Negotiate"), SECPKG_CRED_OUTBOUND, NULL, NULL, NULL, NULL, neg_ctx->credentials, &lifetime); if(neg_ctx->status != SEC_E_OK) return -1; } else { input_token = malloc(neg_ctx->max_token_length); if(!input_token) return -1; error = Curl_base64_decode(header, (unsigned char **)&input_token, &input_token_len); if(error || input_token_len == 0) return -1; } /* prepare the output buffers, and input buffers if present */ out_buff_desc.ulVersion = 0; out_buff_desc.cBuffers = 1; out_buff_desc.pBuffers = &out_sec_buff; out_sec_buff.cbBuffer = curlx_uztoul(neg_ctx->max_token_length); out_sec_buff.BufferType = SECBUFFER_TOKEN; out_sec_buff.pvBuffer = neg_ctx->output_token; if(input_token) { in_buff_desc.ulVersion = 0; in_buff_desc.cBuffers = 1; in_buff_desc.pBuffers = &in_sec_buff; in_sec_buff.cbBuffer = curlx_uztoul(input_token_len); in_sec_buff.BufferType = SECBUFFER_TOKEN; in_sec_buff.pvBuffer = input_token; } sname = Curl_convert_UTF8_to_tchar(neg_ctx->server_name); if(!sname) return CURLE_OUT_OF_MEMORY; neg_ctx->status = s_pSecFn->InitializeSecurityContext( neg_ctx->credentials, input_token ? neg_ctx->context : 0, sname, ISC_REQ_CONFIDENTIALITY, 0, SECURITY_NATIVE_DREP, input_token ? &in_buff_desc : 0, 0, neg_ctx->context, &out_buff_desc, &context_attributes, &lifetime); Curl_unicodefree(sname); if(GSS_ERROR(neg_ctx->status)) return -1; if(neg_ctx->status == SEC_I_COMPLETE_NEEDED || neg_ctx->status == SEC_I_COMPLETE_AND_CONTINUE) { neg_ctx->status = s_pSecFn->CompleteAuthToken(neg_ctx->context, &out_buff_desc); if(GSS_ERROR(neg_ctx->status)) return -1; } neg_ctx->output_token_length = out_sec_buff.cbBuffer; return 0; }
/* * Curl_auth_decode_spnego_message() * * This is used to decode an already encoded SPNEGO (Negotiate) challenge * message. * * Parameters: * * data [in] - The session handle. * userp [in] - The user name in the format User or Domain\User. * passdwp [in] - The user's password. * service [in] - The service type such as http, smtp, pop or imap. * host [in] - The host name. * chlg64 [in] - The optional base64 encoded challenge message. * nego [in/out] - The Negotiate data struct being used and modified. * * Returns CURLE_OK on success. */ CURLcode Curl_auth_decode_spnego_message(struct Curl_easy *data, const char *user, const char *password, const char *service, const char *host, const char *chlg64, struct negotiatedata *nego) { CURLcode result = CURLE_OK; size_t chlglen = 0; unsigned char *chlg = NULL; PSecPkgInfo SecurityPackage; SecBuffer chlg_buf; SecBuffer resp_buf; SecBufferDesc chlg_desc; SecBufferDesc resp_desc; unsigned long attrs; TimeStamp expiry; /* For Windows 9x compatibility of SSPI calls */ #if defined(CURL_DISABLE_VERBOSE_STRINGS) (void) data; #endif if(nego->context && nego->status == SEC_E_OK) { /* We finished successfully our part of authentication, but server * rejected it (since we're again here). Exit with an error since we * can't invent anything better */ Curl_auth_spnego_cleanup(nego); return CURLE_LOGIN_DENIED; } if(!nego->spn) { /* Generate our SPN */ nego->spn = Curl_auth_build_spn(service, host, NULL); if(!nego->spn) return CURLE_OUT_OF_MEMORY; } if(!nego->output_token) { /* Query the security package for Negotiate */ nego->status = s_pSecFn->QuerySecurityPackageInfo((TCHAR *) TEXT(SP_NAME_NEGOTIATE), &SecurityPackage); if(nego->status != SEC_E_OK) return CURLE_NOT_BUILT_IN; nego->token_max = SecurityPackage->cbMaxToken; /* Release the package buffer as it is not required anymore */ s_pSecFn->FreeContextBuffer(SecurityPackage); /* Allocate our output buffer */ nego->output_token = malloc(nego->token_max); if(!nego->output_token) return CURLE_OUT_OF_MEMORY; } if(!nego->credentials) { /* Do we have credientials to use or are we using single sign-on? */ if(user && *user) { /* Populate our identity structure */ result = Curl_create_sspi_identity(user, password, &nego->identity); if(result) return result; /* Allow proper cleanup of the identity structure */ nego->p_identity = &nego->identity; } else /* Use the current Windows user */ nego->p_identity = NULL; /* Allocate our credentials handle */ nego->credentials = malloc(sizeof(CredHandle)); if(!nego->credentials) return CURLE_OUT_OF_MEMORY; memset(nego->credentials, 0, sizeof(CredHandle)); /* Acquire our credentials handle */ nego->status = s_pSecFn->AcquireCredentialsHandle(NULL, (TCHAR *)TEXT(SP_NAME_NEGOTIATE), SECPKG_CRED_OUTBOUND, NULL, nego->p_identity, NULL, NULL, nego->credentials, &expiry); if(nego->status != SEC_E_OK) return CURLE_LOGIN_DENIED; /* Allocate our new context handle */ nego->context = malloc(sizeof(CtxtHandle)); if(!nego->context) return CURLE_OUT_OF_MEMORY; memset(nego->context, 0, sizeof(CtxtHandle)); } if(chlg64 && *chlg64) { /* Decode the base-64 encoded challenge message */ if(*chlg64 != '=') { result = Curl_base64_decode(chlg64, &chlg, &chlglen); if(result) return result; } /* Ensure we have a valid challenge message */ if(!chlg) { infof(data, "SPNEGO handshake failure (empty challenge message)\n"); return CURLE_BAD_CONTENT_ENCODING; } /* Setup the challenge "input" security buffer */ chlg_desc.ulVersion = SECBUFFER_VERSION; chlg_desc.cBuffers = 1; chlg_desc.pBuffers = &chlg_buf; chlg_buf.BufferType = SECBUFFER_TOKEN; chlg_buf.pvBuffer = chlg; chlg_buf.cbBuffer = curlx_uztoul(chlglen); } /* Setup the response "output" security buffer */ resp_desc.ulVersion = SECBUFFER_VERSION; resp_desc.cBuffers = 1; resp_desc.pBuffers = &resp_buf; resp_buf.BufferType = SECBUFFER_TOKEN; resp_buf.pvBuffer = nego->output_token; resp_buf.cbBuffer = curlx_uztoul(nego->token_max); /* Generate our challenge-response message */ nego->status = s_pSecFn->InitializeSecurityContext(nego->credentials, chlg ? nego->context : NULL, nego->spn, ISC_REQ_CONFIDENTIALITY, 0, SECURITY_NATIVE_DREP, chlg ? &chlg_desc : NULL, 0, nego->context, &resp_desc, &attrs, &expiry); /* Free the decoded challenge as it is not required anymore */ free(chlg); if(GSS_ERROR(nego->status)) { return CURLE_OUT_OF_MEMORY; } if(nego->status == SEC_I_COMPLETE_NEEDED || nego->status == SEC_I_COMPLETE_AND_CONTINUE) { nego->status = s_pSecFn->CompleteAuthToken(nego->context, &resp_desc); if(GSS_ERROR(nego->status)) { return CURLE_RECV_ERROR; } } nego->output_token_length = resp_buf.cbBuffer; return result; }
/* * Curl_create_sspi_identity() * * This is used to populate a SSPI identity structure based on the supplied * username and password. * * Parameters: * * userp [in] - The user name in the format User or Domain\User. * passdwp [in] - The user's password. * identity [in/out] - The identity structure. * * Returns CURLE_OK on success. */ CURLcode Curl_create_sspi_identity(const char *userp, const char *passwdp, SEC_WINNT_AUTH_IDENTITY *identity) { xcharp_u useranddomain; xcharp_u user, dup_user; xcharp_u domain, dup_domain; xcharp_u passwd, dup_passwd; size_t domlen = 0; domain.const_tchar_ptr = TEXT(""); /* Initialize the identity */ memset(identity, 0, sizeof(*identity)); useranddomain.tchar_ptr = Curl_convert_UTF8_to_tchar((char *)userp); if(!useranddomain.tchar_ptr) return CURLE_OUT_OF_MEMORY; user.const_tchar_ptr = _tcschr(useranddomain.const_tchar_ptr, TEXT('\\')); if(!user.const_tchar_ptr) user.const_tchar_ptr = _tcschr(useranddomain.const_tchar_ptr, TEXT('/')); if(user.tchar_ptr) { domain.tchar_ptr = useranddomain.tchar_ptr; domlen = user.tchar_ptr - useranddomain.tchar_ptr; user.tchar_ptr++; } else { user.tchar_ptr = useranddomain.tchar_ptr; domain.const_tchar_ptr = TEXT(""); domlen = 0; } /* Setup the identity's user and length */ dup_user.tchar_ptr = _tcsdup(user.tchar_ptr); if(!dup_user.tchar_ptr) { Curl_unicodefree(useranddomain.tchar_ptr); return CURLE_OUT_OF_MEMORY; } identity->User = dup_user.tbyte_ptr; identity->UserLength = curlx_uztoul(_tcslen(dup_user.tchar_ptr)); dup_user.tchar_ptr = NULL; /* Setup the identity's domain and length */ dup_domain.tchar_ptr = malloc(sizeof(TCHAR) * (domlen + 1)); if(!dup_domain.tchar_ptr) { Curl_unicodefree(useranddomain.tchar_ptr); return CURLE_OUT_OF_MEMORY; } _tcsncpy(dup_domain.tchar_ptr, domain.tchar_ptr, domlen); *(dup_domain.tchar_ptr + domlen) = TEXT('\0'); identity->Domain = dup_domain.tbyte_ptr; identity->DomainLength = curlx_uztoul(domlen); dup_domain.tchar_ptr = NULL; Curl_unicodefree(useranddomain.tchar_ptr); /* Setup ntlm identity's password and length */ passwd.tchar_ptr = Curl_convert_UTF8_to_tchar((char *)passwdp); if(!passwd.tchar_ptr) return CURLE_OUT_OF_MEMORY; dup_passwd.tchar_ptr = _tcsdup(passwd.tchar_ptr); if(!dup_passwd.tchar_ptr) { Curl_unicodefree(passwd.tchar_ptr); return CURLE_OUT_OF_MEMORY; } identity->Password = dup_passwd.tbyte_ptr; identity->PasswordLength = curlx_uztoul(_tcslen(dup_passwd.tchar_ptr)); dup_passwd.tchar_ptr = NULL; Curl_unicodefree(passwd.tchar_ptr); /* Setup the identity's flags */ identity->Flags = SECFLAG_WINNT_AUTH_IDENTITY; return CURLE_OK; }
/* * Curl_ntlm_decode_type2_message() * * This is used to decode a ntlm type-2 message received from a: HTTP, SMTP * or POP3 server. The message is first decoded from a base64 string into a * raw ntlm message and checked for validity before the appropriate data for * creating a type-3 message is written to the given ntlm data structure. * * Parameters: * * data [in] - Pointer to session handle. * header [in] - Pointer to the input buffer. * ntlm [in] - Pointer to ntlm data struct being used and modified. * * Returns CURLE_OK on success. */ CURLcode Curl_ntlm_decode_type2_message(struct SessionHandle *data, const char* header, struct ntlmdata* ntlm) { #ifndef USE_WINDOWS_SSPI static const char type2_marker[] = { 0x02, 0x00, 0x00, 0x00 }; #endif /* NTLM type-2 message structure: Index Description Content 0 NTLMSSP Signature Null-terminated ASCII "NTLMSSP" (0x4e544c4d53535000) 8 NTLM Message Type long (0x02000000) 12 Target Name security buffer 20 Flags long 24 Challenge 8 bytes (32) Context 8 bytes (two consecutive longs) (*) (40) Target Information security buffer (*) (48) OS Version Structure 8 bytes (*) 32 (48) (56) Start of data block (*) (*) -> Optional */ size_t size = 0; unsigned char *buffer = NULL; CURLcode error; #if defined(CURL_DISABLE_VERBOSE_STRINGS) || defined(USE_WINDOWS_SSPI) (void)data; #endif error = Curl_base64_decode(header, &buffer, &size); if(error) return error; if(!buffer) { infof(data, "NTLM handshake failure (unhandled condition)\n"); return CURLE_REMOTE_ACCESS_DENIED; } #ifdef USE_WINDOWS_SSPI ntlm->type_2 = malloc(size + 1); if(ntlm->type_2 == NULL) { free(buffer); return CURLE_OUT_OF_MEMORY; } ntlm->n_type_2 = curlx_uztoul(size); memcpy(ntlm->type_2, buffer, size); #else ntlm->flags = 0; if((size < 32) || (memcmp(buffer, NTLMSSP_SIGNATURE, 8) != 0) || (memcmp(buffer + 8, type2_marker, sizeof(type2_marker)) != 0)) { /* This was not a good enough type-2 message */ free(buffer); infof(data, "NTLM handshake failure (bad type-2 message)\n"); return CURLE_REMOTE_ACCESS_DENIED; } ntlm->flags = readint_le(&buffer[20]); memcpy(ntlm->nonce, &buffer[24], 8); DEBUG_OUT({ fprintf(stderr, "**** TYPE2 header flags=0x%08.8lx ", ntlm->flags); ntlm_print_flags(stderr, ntlm->flags); fprintf(stderr, "\n nonce="); ntlm_print_hex(stderr, (char *)ntlm->nonce, 8); fprintf(stderr, "\n****\n"); fprintf(stderr, "**** Header %s\n ", header); });
static ssize_t schannel_recv(struct connectdata *conn, int sockindex, char *buf, size_t len, CURLcode *err) { size_t size = 0; ssize_t nread = 0, ret = -1; CURLcode retcode; struct SessionHandle *data = conn->data; struct ssl_connect_data *connssl = &conn->ssl[sockindex]; bool done = FALSE; SecBuffer inbuf[4]; SecBufferDesc inbuf_desc; SECURITY_STATUS sspi_status = SEC_E_OK; infof(data, "schannel: client wants to read %zu bytes\n", len); *err = CURLE_OK; /* buffer to store previously received and decrypted data */ if(connssl->decdata_buffer == NULL) { connssl->decdata_offset = 0; connssl->decdata_length = CURL_SCHANNEL_BUFFER_INIT_SIZE; connssl->decdata_buffer = malloc(connssl->decdata_length); if(connssl->decdata_buffer == NULL) { failf(data, "schannel: unable to allocate memory"); *err = CURLE_OUT_OF_MEMORY; return -1; } } /* increase buffer in order to fit the requested amount of data */ while(connssl->encdata_length - connssl->encdata_offset < CURL_SCHANNEL_BUFFER_FREE_SIZE || connssl->encdata_length < len) { /* increase internal encrypted data buffer */ connssl->encdata_length *= CURL_SCHANNEL_BUFFER_STEP_FACTOR; connssl->encdata_buffer = realloc(connssl->encdata_buffer, connssl->encdata_length); if(connssl->encdata_buffer == NULL) { failf(data, "schannel: unable to re-allocate memory"); *err = CURLE_OUT_OF_MEMORY; return -1; } } /* read encrypted data from socket */ infof(data, "schannel: encrypted data buffer: offset %zu length %zu\n", connssl->encdata_offset, connssl->encdata_length); size = connssl->encdata_length - connssl->encdata_offset; if(size > 0) { *err = Curl_read_plain(conn->sock[sockindex], (char *) (connssl->encdata_buffer + connssl->encdata_offset), size, &nread); /* check for received data */ if(*err != CURLE_OK) ret = -1; else { if(nread > 0) /* increase encrypted data buffer offset */ connssl->encdata_offset += nread; ret = nread; } infof(data, "schannel: encrypted data got %zd\n", ret); } infof(data, "schannel: encrypted data buffer: offset %zu length %zu\n", connssl->encdata_offset, connssl->encdata_length); /* check if we still have some data in our buffers */ while(connssl->encdata_offset > 0 && sspi_status == SEC_E_OK && connssl->decdata_offset < len) { /* prepare data buffer for DecryptMessage call */ InitSecBuffer(&inbuf[0], SECBUFFER_DATA, connssl->encdata_buffer, curlx_uztoul(connssl->encdata_offset)); /* we need 3 more empty input buffers for possible output */ InitSecBuffer(&inbuf[1], SECBUFFER_EMPTY, NULL, 0); InitSecBuffer(&inbuf[2], SECBUFFER_EMPTY, NULL, 0); InitSecBuffer(&inbuf[3], SECBUFFER_EMPTY, NULL, 0); InitSecBufferDesc(&inbuf_desc, inbuf, 4); /* http://msdn.microsoft.com/en-us/library/windows/desktop/aa375348.aspx */ sspi_status = s_pSecFn->DecryptMessage(&connssl->ctxt->ctxt_handle, &inbuf_desc, 0, NULL); /* check if we need more data */ if(sspi_status == SEC_E_INCOMPLETE_MESSAGE) { infof(data, "schannel: failed to decrypt data, need more data\n"); *err = CURLE_AGAIN; return -1; } /* check if everything went fine (server may want to renegotiate context) */ if(sspi_status == SEC_E_OK || sspi_status == SEC_I_RENEGOTIATE || sspi_status == SEC_I_CONTEXT_EXPIRED) { /* check for successfully decrypted data */ if(inbuf[1].BufferType == SECBUFFER_DATA) { infof(data, "schannel: decrypted data length: %lu\n", inbuf[1].cbBuffer); /* increase buffer in order to fit the received amount of data */ size = inbuf[1].cbBuffer > CURL_SCHANNEL_BUFFER_FREE_SIZE ? inbuf[1].cbBuffer : CURL_SCHANNEL_BUFFER_FREE_SIZE; while(connssl->decdata_length - connssl->decdata_offset < size || connssl->decdata_length < len) { /* increase internal decrypted data buffer */ connssl->decdata_length *= CURL_SCHANNEL_BUFFER_STEP_FACTOR; connssl->decdata_buffer = realloc(connssl->decdata_buffer, connssl->decdata_length); if(connssl->decdata_buffer == NULL) { failf(data, "schannel: unable to re-allocate memory"); *err = CURLE_OUT_OF_MEMORY; return -1; } } /* copy decrypted data to internal buffer */ size = inbuf[1].cbBuffer; if(size > 0) { memcpy(connssl->decdata_buffer + connssl->decdata_offset, inbuf[1].pvBuffer, size); connssl->decdata_offset += size; } infof(data, "schannel: decrypted data added: %zu\n", size); infof(data, "schannel: decrypted data cached: offset %zu length %zu\n", connssl->decdata_offset, connssl->decdata_length); } /* check for remaining encrypted data */ if(inbuf[3].BufferType == SECBUFFER_EXTRA && inbuf[3].cbBuffer > 0) { infof(data, "schannel: encrypted data length: %lu\n", inbuf[3].cbBuffer); /* check if the remaining data is less than the total amount * and therefore begins after the already processed data */ if(connssl->encdata_offset > inbuf[3].cbBuffer) { /* move remaining encrypted data forward to the beginning of buffer */ memmove(connssl->encdata_buffer, (connssl->encdata_buffer + connssl->encdata_offset) - inbuf[3].cbBuffer, inbuf[3].cbBuffer); connssl->encdata_offset = inbuf[3].cbBuffer; } infof(data, "schannel: encrypted data cached: offset %zu length %zu\n", connssl->encdata_offset, connssl->encdata_length); } else{ /* reset encrypted buffer offset, because there is no data remaining */ connssl->encdata_offset = 0; } } /* check if server wants to renegotiate the connection context */ if(sspi_status == SEC_I_RENEGOTIATE) { infof(data, "schannel: remote party requests SSL/TLS renegotiation\n"); /* begin renegotiation */ infof(data, "schannel: renegotiating SSL/TLS connection\n"); connssl->state = ssl_connection_negotiating; connssl->connecting_state = ssl_connect_2_writing; retcode = schannel_connect_common(conn, sockindex, FALSE, &done); if(retcode) *err = retcode; else { infof(data, "schannel: SSL/TLS connection renegotiated\n"); /* now retry receiving data */ return schannel_recv(conn, sockindex, buf, len, err); } } } infof(data, "schannel: decrypted data buffer: offset %zu length %zu\n", connssl->decdata_offset, connssl->decdata_length); /* copy requested decrypted data to supplied buffer */ size = len < connssl->decdata_offset ? len : connssl->decdata_offset; if(size > 0) { memcpy(buf, connssl->decdata_buffer, size); ret = size; /* move remaining decrypted data forward to the beginning of buffer */ memmove(connssl->decdata_buffer, connssl->decdata_buffer + size, connssl->decdata_offset - size); connssl->decdata_offset -= size; infof(data, "schannel: decrypted data returned %zd\n", size); infof(data, "schannel: decrypted data buffer: offset %zu length %zu\n", connssl->decdata_offset, connssl->decdata_length); } /* check if the server closed the connection */ if(ret <= 0 && ( /* special check for Windows 2000 Professional */ sspi_status == SEC_I_CONTEXT_EXPIRED || (sspi_status == SEC_E_OK && connssl->encdata_offset > 0 && connssl->encdata_buffer[0] == 0x15))) { infof(data, "schannel: server closed the connection\n"); *err = CURLE_OK; return 0; } /* check if something went wrong and we need to return an error */ if(ret < 0 && sspi_status != SEC_E_OK) { infof(data, "schannel: failed to read data from server: %s\n", Curl_sspi_strerror(conn, sspi_status)); *err = CURLE_RECV_ERROR; return -1; } return ret; }