blob: 7bbae6a737e385e3bf2d5ad2216a9e9da50bfe38 [file] [log] [blame]
/*******************************************************************************
* Copyright (C) 2004-2006 Intel Corp. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* - Neither the name of Intel Corp. nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL Intel Corp. OR THE CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*******************************************************************************/
/**
* @author Vadim Revyakin
*/
#ifdef _WIN32
#include <winsock2.h>
#endif
/* system */
#include <windows.h>
#include <crtdbg.h>
#include <winhttp.h>
#include <tchar.h>
#include <stdio.h>
#include <intrin.h>
#pragma intrinsic(_InterlockedExchange)
/* local */
#include "u/libu.h"
#include "wsman-types.h"
#include "wsman-client.h"
#include "wsman-soap.h"
#include "wsman-xml.h"
#include "wsman-debug.h"
#include "wsman-client-transport.h"
#define BUFLEN 8096
#define MAX_NUM_OF_OIDS 2
#define CLIENT_CERT_STORE "MY"
#define CERT_MAX_STR_LEN 256
#define OID_CLIENT "1.3.6.1.5.5.7.3.2"
/* kerberos auth */
#define WINHTTP_OPTION_SPN 96
// values for WINHTTP_OPTION_SPN
#define WINHTTP_DISABLE_SPN_SERVER_PORT 0x00000000
#define WINHTTP_ENABLE_SPN_SERVER_PORT 0x00000001
#define AUTH_SCHEME_NTLM 0x00000004
/* ensure that the winhttp library is linked */
#pragma comment( lib, "winhttp.lib" )
static BOOL find_cert(const _TCHAR * oid,
const _TCHAR * certName,
BOOL localMachine,
PCCERT_CONTEXT *pCertContext,
int* errorLast);
void wsman_client_handler( WsManClient *cl, WsXmlDocH rqstDoc, void* user_data);
static wchar_t *convert_to_unicode(char *str)
{
wchar_t *unicode_str = NULL;
if (str == NULL) {
return NULL;
}
unicode_str =
(wchar_t *) malloc((strlen(str) + 1) * sizeof(wchar_t));
if (unicode_str) {
if (mbstowcs(unicode_str, str, strlen(str) + 1) <= 0) {
error("No -> Unicode: %s", str);
u_free(unicode_str);
return NULL;
}
} else {
error("Out of memory");
}
return unicode_str;
}
int wsmc_transport_init(WsManClient *cl, void *arg)
{
wchar_t *agent;
wchar_t *proxy;
if (cl->session_handle != NULL) {
return 0;
}
agent = convert_to_unicode(wsman_transport_get_agent(cl));
if (agent == NULL) {
return 1;
}
while (InterlockedExchange(&cl->lock_session_handle, 1L));
if (cl->session_handle != NULL) {
cl->lock_session_handle = 0L;
return 0;
}
if(!cl->proxy_data.proxy){
cl->session_handle = WinHttpOpen(agent,
WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
WINHTTP_NO_PROXY_NAME,
WINHTTP_NO_PROXY_BYPASS, 0);
}
else
{
proxy = convert_to_unicode(cl->proxy_data.proxy);
cl->session_handle = WinHttpOpen(agent,
WINHTTP_ACCESS_TYPE_NAMED_PROXY,
proxy,
WINHTTP_NO_PROXY_BYPASS, 0);
if (proxy)
u_free(proxy);
}
cl->lock_session_handle = 0L;
u_free(agent);
if (cl->session_handle == NULL) {
error("Could not open session");
}
return cl->session_handle ? 0 : 1;
}
void wsmc_transport_fini(WsManClient *cl)
{
if (cl->session_handle)
WinHttpCloseHandle(cl->session_handle);
}
void wsman_transport_close_transport(WsManClient * cl)
{
if (cl->transport) {
WinHttpCloseHandle((HINTERNET) cl->transport);
cl->transport = NULL;
}
}
static void *init_win_transport(WsManClient * cl)
{
HINTERNET connect;
wchar_t *host = convert_to_unicode(cl->data.hostname);
if (host == NULL) {
error("No host");
return NULL;
}
if (cl->session_handle == NULL) {
error("could not initialize session");
return NULL;
}
connect = WinHttpConnect(cl->session_handle, host, cl->data.port, 0);
u_free(host);
if (connect == NULL) {
error("could not establish connection");
return NULL;
}
cl->transport = (void *) connect;
return (void *) connect;
}
static DWORD ChooseAuthScheme(DWORD dwSupportedSchemes, int ws_auth)
{
// It is the server's responsibility only to accept
// authentication schemes that provide a sufficient
// level of security to protect the servers resources.
//
// The client is also obligated only to use an authentication
// scheme that adequately protects its username and password.
//
if (dwSupportedSchemes & WINHTTP_AUTH_SCHEME_NEGOTIATE) {
if ((ws_auth == 0) || (ws_auth == WS_GSSNEGOTIATE_AUTH)) {
return WINHTTP_AUTH_SCHEME_NEGOTIATE;
}
}
if (dwSupportedSchemes & WINHTTP_AUTH_SCHEME_NTLM) {
if ((ws_auth == 0) || (ws_auth == WS_NTLM_AUTH)) {
return WINHTTP_AUTH_SCHEME_NTLM;
}
}
if (dwSupportedSchemes & WINHTTP_AUTH_SCHEME_PASSPORT) {
if ((ws_auth == 0) || (ws_auth == WS_PASS_AUTH)) {
return WINHTTP_AUTH_SCHEME_PASSPORT;
}
}
if (dwSupportedSchemes & WINHTTP_AUTH_SCHEME_DIGEST) {
if ((ws_auth == 0) || (ws_auth == WS_DIGEST_AUTH)) {
return WINHTTP_AUTH_SCHEME_DIGEST;
}
}
if (dwSupportedSchemes & WINHTTP_AUTH_SCHEME_BASIC) {
if ((ws_auth == 0) || (ws_auth == WS_BASIC_AUTH)) {
return WINHTTP_AUTH_SCHEME_BASIC;
}
}
return 0;
}
static DWORD Auth2Scheme(int ws_auth)
{
if (ws_auth == WS_GSSNEGOTIATE_AUTH) {
return WINHTTP_AUTH_SCHEME_NEGOTIATE;
}
if (ws_auth == WS_NTLM_AUTH) {
return WINHTTP_AUTH_SCHEME_NTLM;
}
if (ws_auth == WS_PASS_AUTH) {
return WINHTTP_AUTH_SCHEME_PASSPORT;
}
if (ws_auth == WS_DIGEST_AUTH) {
return WINHTTP_AUTH_SCHEME_DIGEST;
}
if (ws_auth == WS_BASIC_AUTH) {
return WINHTTP_AUTH_SCHEME_BASIC;
}
return 0;
}
static int cleanup_request_data(HINTERNET request)
{
LPSTR buffer[BUFLEN];
DWORD dwDownloaded = 0;
DWORD dwSize = 0;
while (1) {
// Verify available data.
dwSize = 0;
if (!WinHttpQueryDataAvailable(request, &dwSize)) {
error("Error %u in WinHttpQueryDataAvailable.",
GetLastError());
return 1;
}
dwSize = (dwSize > BUFLEN) ? BUFLEN : dwSize;
if (!WinHttpReadData(request, (LPVOID) buffer,
dwSize, &dwDownloaded)) {
error("Error %u in WinHttpReadData.",
GetLastError());
return 1;
}
if (dwDownloaded == 0) {
break;
}
}
return 0;
}
void
wsmc_handler(WsManClient * cl, WsXmlDocH rqstDoc, void *user_data)
{
HINTERNET connect;
HINTERNET request = NULL;
unsigned long flags = 0;
char *buf = NULL;
int errLen;
DWORD dwStatusCode = 0;
DWORD dwSupportedSchemes;
DWORD dwFirstScheme;
DWORD dwSelectedScheme;
DWORD dwTarget;
DWORD dwLastStatus = 0;
DWORD dwSize = sizeof(DWORD);
BOOL bResult = FALSE;
BOOL bResults = FALSE;
BOOL bDone = FALSE;
DWORD dwDownloaded = 0;
BOOL updated;
int ws_auth;
wchar_t *pwd;
wchar_t *usr;
int lastErr = 0;
char *p;
size_t len;
u_buf_t *ubuf;
char pszAnsi[128];
wchar_t *pwsz;
PCCERT_CONTEXT certificate;
wchar_t *proxy_username;
wchar_t *proxy_password;
if (cl->session_handle == NULL && wsmc_transport_init(cl, NULL)) {
error("could not initialize transport");
lastErr = GetLastError();
goto DONE;
}
if (cl->transport == NULL) {
init_win_transport(cl);
}
if (cl->transport == NULL) {
lastErr = GetLastError();
goto DONE;
}
connect = (HINTERNET) cl->transport;
if (strnicmp(cl->data.endpoint, "https", 5) == 0)
{
flags |= WINHTTP_FLAG_SECURE;
}
/* request = WinHttpOpenRequest(connect, L"POST",
cl->data.path, NULL,
WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES,
flags); */
pwsz = convert_to_unicode(cl->data.path);
request =
WinHttpOpenRequest(connect, L"POST", pwsz, L"HTTP/1.1",
WINHTTP_NO_REFERER,
WINHTTP_DEFAULT_ACCEPT_TYPES, flags);
u_free(pwsz);
if (request == NULL) {
dwStatusCode = 400;
goto DONE;
}
snprintf(pszAnsi, 128, "Content-Type: application/soap+xml;charset=%s\r\n", cl->content_encoding);
pwsz = convert_to_unicode(pszAnsi);
bResult = WinHttpAddRequestHeaders(request,
pwsz,
-1,
WINHTTP_ADDREQ_FLAG_ADD_IF_NEW);
u_free(pwsz);
if (!bResult) {
error("can't add Content-Type header");
dwStatusCode = 400;
goto DONE;
}
ws_xml_dump_memory_enc(rqstDoc, &buf, &errLen, cl->content_encoding);
updated = 0;
ws_auth = wsmc_transport_get_auth_value(cl);
if(ws_auth == AUTH_SCHEME_NTLM)
{
DWORD d = WINHTTP_ENABLE_SPN_SERVER_PORT;
bResults =WinHttpSetOption(request,
WINHTTP_OPTION_SPN,
(LPVOID) (&d),
sizeof(DWORD));
if (!bResults)
{
lastErr = GetLastError();
bDone = TRUE;
}
bResults = FALSE;
}
if(cl->proxy_data.proxy_username)
{
proxy_username = convert_to_unicode(cl->proxy_data.proxy_username);
bResults = WinHttpSetOption(request, WINHTTP_OPTION_PROXY_USERNAME,
proxy_username, wcslen(proxy_username));
u_free(proxy_username);
if (!bResults)
{
lastErr = GetLastError();
bDone = TRUE;
}
bResults = FALSE;
}
if(cl->proxy_data.proxy_password)
{
proxy_password = convert_to_unicode(cl->proxy_data.proxy_password);
bResults = WinHttpSetOption(request, WINHTTP_OPTION_PROXY_PASSWORD,
proxy_password, wcslen(proxy_password));
u_free(proxy_password);
if (!bResults)
{
lastErr = GetLastError();
bDone = TRUE;
}
bResults = FALSE;
}
if(0==cl->authentication.verify_host || 0==cl->authentication.verify_peer)
{
if(0==cl->authentication.verify_host)
flags = flags | SECURITY_FLAG_IGNORE_CERT_CN_INVALID;
if(0==cl->authentication.verify_peer)
flags = flags | SECURITY_FLAG_IGNORE_CERT_DATE_INVALID | SECURITY_FLAG_IGNORE_UNKNOWN_CA;
bResult = WinHttpSetOption(request,WINHTTP_OPTION_SECURITY_FLAGS,(LPVOID) (&flags),sizeof(DWORD));
if (!bResult) {
//log the error and proceed
error("cannot ignore server certificate");
}
}
while (!bDone) {
bResult = WinHttpSendRequest(request,
WINHTTP_NO_ADDITIONAL_HEADERS,
(DWORD) 0, (LPVOID) buf,
(DWORD) errLen,
(DWORD) errLen,
(DWORD_PTR) NULL);
if (bResult) {
bResults = WinHttpReceiveResponse(request, NULL);
}
// Resend the request in case of
// ERROR_WINHTTP_RESEND_REQUEST error.
if (!bResults) {
lastErr = GetLastError();
if (ERROR_WINHTTP_RESEND_REQUEST == lastErr) {
lastErr = 0;
continue;
}
}
// Check the status code.
if (bResults) {
bResults = WinHttpQueryHeaders(request,
WINHTTP_QUERY_STATUS_CODE
|
WINHTTP_QUERY_FLAG_NUMBER,
WINHTTP_HEADER_NAME_BY_INDEX, &dwStatusCode,
&dwSize, WINHTTP_NO_HEADER_INDEX);
}
if (!bResults) {
if (updated) {
bDone = TRUE;
break;
}
if (ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED ==
lastErr) {
lastErr = 0;
if (!find_cert( (const _TCHAR *) cl->authentication.caoid,
(const _TCHAR *) cl->authentication.cainfo,
cl->authentication.calocal, &certificate, &lastErr)) {
debug("No certificate");
bDone = TRUE;
break;
}
bResults = WinHttpSetOption(request,
WINHTTP_OPTION_CLIENT_CERT_CONTEXT,
(LPVOID)
certificate,
(DWORD) (sizeof
(CERT_CONTEXT)));
if (!bResults) {
lastErr = GetLastError();
bDone = TRUE;
break;
} else {
bResults = 0;
updated = 1;
continue;
}
} else {
bResults = WinHttpQueryAuthSchemes(request,
&dwSupportedSchemes,
&dwFirstScheme,
&dwTarget);
// Set the credentials before resending the request.
if (bResults) {
if (WS_MAX_AUTH == ws_auth || !cl->data.user ||
!cl->data.pwd) {
// we don't have credentials
bDone = TRUE;
bResults = 0;
break;
}
dwSelectedScheme =
ChooseAuthScheme(dwSupportedSchemes,
ws_auth);
if (dwSelectedScheme == 0) {
bDone = TRUE;
bResults = 0;
break;
}
pwd = convert_to_unicode(cl->data.pwd);
usr = convert_to_unicode(cl->data.user);
if ((pwd == NULL) || (usr == NULL)) {
bDone = TRUE;
bResults = 0;
break;
}
bResults = WinHttpSetCredentials(request,
dwTarget,
dwSelectedScheme,
usr, pwd,
NULL);
u_free(pwd);
u_free(usr);
}
if (cleanup_request_data(request)) {
// the problems to read data
bDone = TRUE;
break;
}
lastErr = 0;
bResults = 0;
updated = 1;
continue;
}
}
switch (dwStatusCode) {
case 200:
// The resource was successfully retrieved.
// You can use WinHttpReadData to read the
// contents of the server's response.
bDone = TRUE;
break;
case 400:
debug("Error. Status code %d returned.",
dwStatusCode);
bDone = TRUE;
break;
case 500:
debug("Error. Status code %d returned.",
dwStatusCode);
bDone = TRUE;
break;
case 401:
// The server requires authentication.
// Obtain the supported and preferred schemes.
// If the same credentials are requested twice, abort the
// request.
if (dwLastStatus == 401) {
bResults = 0;
bDone = TRUE;
break;
}
if(ws_auth == AUTH_SCHEME_NTLM)
{
DWORD data = WINHTTP_AUTOLOGON_SECURITY_LEVEL_MEDIUM;
DWORD dataSize = sizeof(DWORD);
bResults = WinHttpSetOption(
request,
WINHTTP_OPTION_AUTOLOGON_POLICY,
&data,
dataSize);
if (!bResults)
{
lastErr = GetLastError();
bDone = TRUE;
}
if (cleanup_request_data(request)) {
// the problems to read data
bDone = TRUE;
}
break;
}
bResults = WinHttpQueryAuthSchemes(request,
&dwSupportedSchemes,
&dwFirstScheme,
&dwTarget);
// Set the credentials before resending the request.
if (bResults) {
if (WS_MAX_AUTH == ws_auth || !cl->data.user ||
!cl->data.pwd) {
// we don't have credentials
bDone = TRUE;
bResults = 0;
break;
}
dwSelectedScheme =
ChooseAuthScheme(dwSupportedSchemes,
ws_auth);
if (dwSelectedScheme == 0) {
bDone = TRUE;
bResults = 0;
break;
}
pwd = convert_to_unicode(cl->data.pwd);
usr = convert_to_unicode(cl->data.user);
if ((pwd == NULL) || (usr == NULL)) {
bDone = TRUE;
bResults = 0;
break;
}
bResults = WinHttpSetCredentials(request,
dwTarget,
dwSelectedScheme,
usr, pwd,
NULL);
u_free(pwd);
u_free(usr);
}
if (cleanup_request_data(request)) {
// the problems to read data
bDone = TRUE;
}
break;
/*
case 407:
// The proxy requires authentication.
// Obtain the supported and preferred schemes.
bResults = WinHttpQueryAuthSchemes( hRequest,
&dwSupportedSchemes,
&dwFirstScheme,
&dwTarget );
// Set the credentials before resending the request.
if( bResults )
dwProxyAuthScheme = ChooseAuthScheme(dwSupportedSchemes);
// If the same credentials are requested twice, abort the
// request. For simplicity, this sample does not check
// for a repeated sequence of status codes.
if( dwLastStatus == 407 )
bDone = TRUE;
break;
*/
default:
// The status code does not indicate success.
debug("Error. Status code %d returned.",
dwStatusCode);
bResults = 0;
bDone = TRUE;
}
// Keep track of the last status code.
dwLastStatus = dwStatusCode;
// If there are any errors, break out of the loop.
if (!bResults) {
bDone = TRUE;
}
} // while
// Read data
if (!bResults) {
goto DONE;
}
ubuf = cl->connection->response;
while (1) {
// Verify available data.
dwSize = 0;
bResults = WinHttpQueryDataAvailable(request, &dwSize);
if (!bResults) {
lastErr = GetLastError();
error("Error %u in WinHttpQueryDataAvailable.",
lastErr);
break;
}
if (dwSize > 0) {
u_buf_append(ubuf, NULL, (size_t) dwSize);
}
p = (char *) u_buf_ptr(ubuf);
len = u_buf_len(ubuf);
bResults = WinHttpReadData(request,
(LPVOID) (p + len), dwSize,
&dwDownloaded);
if (!bResults) {
lastErr = GetLastError();
error("Error %u in WinHttpReadData.", lastErr);
break;
}
if (dwDownloaded == 0) {
// end of data
break;
}
u_buf_set_len(ubuf, len + dwDownloaded);
}
DONE:
cl->response_code = dwStatusCode;
cl->last_error = lastErr;
ws_xml_free_memory(buf);
if (request) {
WinHttpCloseHandle(request);
}
}
// in future change this to return a list of certs...
BOOL find_cert(const _TCHAR * oid,
const _TCHAR * certName,
BOOL localMachine,
PCCERT_CONTEXT *pCertContext,
int* errorLast)
{
_TCHAR pszNameString[CERT_MAX_STR_LEN];
HANDLE hStoreHandle = NULL;
LPSTR oids[MAX_NUM_OF_OIDS] = {OID_CLIENT,oid};
BOOL certSuccess = FALSE;
CERT_ENHKEY_USAGE od ={ oid ? 2 : 1, oids };
int lastErr =0;
/* Choose which personal store to use : Current User or Local Machine*/
DWORD flags;
if (localMachine)
{
flags = CERT_SYSTEM_STORE_LOCAL_MACHINE;
}
else
{
flags = CERT_SYSTEM_STORE_CURRENT_USER;
}
/* Open the personal store */
if ( !(hStoreHandle = CertOpenStore(
CERT_STORE_PROV_SYSTEM, // The store provider type
0, // The encoding type is not needed
NULL, // Use the default HCRYPTPROV
flags, // Set the store location in a registry location
L"MY" // The store name as a Unicode string
)))
{
/* Cannot open the certificates store - exit immediately */
lastErr = GetLastError();
error("error %d in CertOpenStore", lastErr);
return TRUE;
}
// Search for the first client certificate
*pCertContext = CertFindCertificateInStore(
hStoreHandle,
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
CERT_FIND_EXT_ONLY_ENHKEY_USAGE_FLAG,
CERT_FIND_ENHKEY_USAGE,
&od,
NULL);
/*
If certificate was found - determinate its name. Keep search
while the certificate's Common Name doesn't match the name
defined by the user
*/
while (*pCertContext != NULL) {
if (!CertGetNameString(*pCertContext,
CERT_NAME_SIMPLE_DISPLAY_TYPE,
0,
NULL,
pszNameString,
CERT_MAX_STR_LEN - 1)) {
/* obtaining certificate name failed */
lastErr = GetLastError();
error("error %d in CertGetNameString.", lastErr);
break;
}
if (certName == NULL ||
_tcscmp(pszNameString, certName) == 0) {
certSuccess = TRUE;
break;
}
*pCertContext =
CertFindCertificateInStore(hStoreHandle,
X509_ASN_ENCODING |
PKCS_7_ASN_ENCODING,
CERT_FIND_EXT_ONLY_ENHKEY_USAGE_FLAG,
CERT_FIND_ENHKEY_USAGE,
&od, *pCertContext);
}
if (*pCertContext == NULL) {
lastErr = GetLastError();
error("error %d (%s) in CertFindCertificateInStore.",
lastErr);
}
if (errorLast) {
*errorLast = lastErr;
}
return certSuccess;
}