strongswan/src/libstrongswan/plugins/winhttp/winhttp_fetcher.c

397 lines
8.6 KiB
C

/*
* Copyright (C) 2014 Martin Willi
* Copyright (C) 2014 revosec AG
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include <winsock2.h>
#include <windows.h>
#include <winhttp.h>
#include "winhttp_fetcher.h"
#include <library.h>
/**
* Timeout for DNS resolution, in ms
*/
#define RESOLVE_TIMEOUT 5000
/**
* Timeout for TCP connect, in ms
*/
#define CONNECT_TIMEOUT 10000
typedef struct private_winhttp_fetcher_t private_winhttp_fetcher_t;
/**
* Private data of a winhttp_fetcher_t.
*/
struct private_winhttp_fetcher_t {
/**
* Public interface
*/
winhttp_fetcher_t public;
/**
* WinHTTP session handle
*/
HINTERNET session;
/**
* POST request data
*/
chunk_t request;
/**
* HTTP version string to use
*/
LPWSTR version;
/**
* Optional HTTP headers, as allocated LPWSTR
*/
linked_list_t *headers;
/**
* Callback function
*/
fetcher_callback_t cb;
/**
* Timeout for operations, in ms
*/
u_long timeout;
/**
* User pointer to store HTTP status code to
*/
u_int *result;
};
/**
* Configure and send the HTTP request
*/
static bool send_request(private_winhttp_fetcher_t *this, HINTERNET request)
{
WCHAR headers[512] = L"";
LPWSTR hdr;
/* Set timeout. By default, send/receive does not time out */
if (!WinHttpSetTimeouts(request, RESOLVE_TIMEOUT, CONNECT_TIMEOUT,
this->timeout, this->timeout))
{
DBG1(DBG_LIB, "opening HTTP request failed: %u", GetLastError());
return FALSE;
}
while (this->headers->remove_first(this->headers, (void**)&hdr) == SUCCESS)
{
wcsncat(headers, hdr, countof(headers) - wcslen(headers) - 1);
if (this->headers->get_count(this->headers))
{
wcsncat(headers, L"\r\n", countof(headers) - wcslen(headers) - 1);
}
free(hdr);
}
if (!WinHttpSendRequest(request, headers, wcslen(headers),
this->request.ptr, this->request.len, this->request.len, 0))
{
DBG1(DBG_LIB, "sending HTTP request failed: %u", GetLastError());
return FALSE;
}
return TRUE;
}
/**
* Read back result and invoke receive callback
*/
static bool read_result(private_winhttp_fetcher_t *this, HINTERNET request,
void *user)
{
DWORD received;
char buf[1024];
uint32_t code;
DWORD codelen = sizeof(code);
if (!WinHttpReceiveResponse(request, NULL))
{
DBG1(DBG_LIB, "reading HTTP response header failed: %u", GetLastError());
return FALSE;
}
if (!WinHttpQueryHeaders(request,
WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
NULL, &code, &codelen, NULL))
{
DBG1(DBG_LIB, "reading HTTP status code failed: %u", GetLastError());
return FALSE;
}
if (this->result)
{
*this->result = code;
}
if (code < 200 || code >= 300)
{ /* non-successful HTTP status code */
if (!this->result)
{
DBG1(DBG_LIB, "HTTP request failed with status %u", code);
}
return FALSE;
}
if (this->cb == fetcher_default_callback)
{
*(chunk_t*)user = chunk_empty;
}
while (TRUE)
{
if (!WinHttpReadData(request, buf, sizeof(buf), &received))
{
DBG1(DBG_LIB, "reading HTTP response failed: %u", GetLastError());
return FALSE;
}
if (received == 0)
{
/* end of response */
break;
}
if (!this->cb(user, chunk_create(buf, received)))
{
DBG1(DBG_LIB, "processing response failed or canceled");
return FALSE;
}
}
return TRUE;
}
/**
* Parse an uri to wide string host and path, optionally set flags and port
*/
static bool parse_uri(private_winhttp_fetcher_t *this, char *uri,
LPWSTR host, int hostlen, LPWSTR path, int pathlen,
LPWSTR user, int userlen, LPWSTR pass, int passlen,
DWORD *flags, INTERNET_PORT *port)
{
WCHAR wuri[512], extra[256];
URL_COMPONENTS comps = {
.dwStructSize = sizeof(URL_COMPONENTS),
.lpszHostName = host,
.dwHostNameLength = hostlen,
.lpszUrlPath = path,
.dwUrlPathLength = pathlen,
.lpszUserName = user,
.dwUserNameLength = userlen,
.lpszPassword = pass,
.dwPasswordLength = passlen,
.lpszExtraInfo = extra,
.dwExtraInfoLength = countof(extra),
};
if (!MultiByteToWideChar(CP_THREAD_ACP, 0, uri, -1, wuri, countof(wuri)))
{
DBG1(DBG_LIB, "converting URI failed: %u", GetLastError());
return FALSE;
}
if (!WinHttpCrackUrl(wuri, 0, ICU_ESCAPE, &comps))
{
DBG1(DBG_LIB, "cracking URI failed: %u", GetLastError());
return FALSE;
}
if (comps.nScheme == INTERNET_SCHEME_HTTPS)
{
*flags |= WINHTTP_FLAG_SECURE;
}
if (comps.dwExtraInfoLength)
{
wcsncat(path, extra, pathlen - comps.dwUrlPathLength - 1);
}
if (comps.nPort)
{
*port = comps.nPort;
}
return TRUE;
}
/**
* Set credentials for basic authentication, if given
*/
static bool set_credentials(private_winhttp_fetcher_t *this,
HINTERNET *request, LPWSTR user, LPWSTR pass)
{
if (!wcslen(user) && !wcslen(pass))
{ /* skip */
return TRUE;
}
return WinHttpSetCredentials(request, WINHTTP_AUTH_TARGET_SERVER,
WINHTTP_AUTH_SCHEME_BASIC, user, pass, NULL);
}
METHOD(fetcher_t, fetch, status_t,
private_winhttp_fetcher_t *this, char *uri, void *userdata)
{
INTERNET_PORT port = INTERNET_DEFAULT_PORT;
status_t status = FAILED;
DWORD flags = 0;
HINTERNET connection, request;
WCHAR host[256], path[512], user[256], pass[256], *method;
if (this->request.len)
{
method = L"POST";
}
else
{
method = L"GET";
}
if (this->result)
{ /* zero-initialize for early failures */
*this->result = 0;
}
if (parse_uri(this, uri, host, countof(host), path, countof(path),
user, countof(user), pass, countof(pass), &flags, &port))
{
connection = WinHttpConnect(this->session, host, port, 0);
if (connection)
{
request = WinHttpOpenRequest(connection, method, path, this->version,
WINHTTP_NO_REFERER,
WINHTTP_DEFAULT_ACCEPT_TYPES, flags);
if (request)
{
if (set_credentials(this, request, user, pass) &&
send_request(this, request) &&
read_result(this, request, userdata))
{
status = SUCCESS;
}
WinHttpCloseHandle(request);
}
else
{
DBG1(DBG_LIB, "opening request failed: %u", GetLastError());
}
WinHttpCloseHandle(connection);
}
else
{
DBG1(DBG_LIB, "connection failed: %u", GetLastError());
}
}
return status;
}
/**
* Append an header as wide string
*/
static bool append_header(private_winhttp_fetcher_t *this, char *name)
{
int len;
LPWSTR buf;
len = MultiByteToWideChar(CP_THREAD_ACP, 0, name, -1, NULL, 0);
if (!len)
{
return FALSE;
}
buf = calloc(len, sizeof(WCHAR));
if (!MultiByteToWideChar(CP_THREAD_ACP, 0, name, -1, buf, len))
{
free(buf);
return FALSE;
}
this->headers->insert_last(this->headers, buf);
return TRUE;
}
METHOD(fetcher_t, set_option, bool,
private_winhttp_fetcher_t *this, fetcher_option_t option, ...)
{
bool supported = TRUE;
char buf[128];
va_list args;
va_start(args, option);
switch (option)
{
case FETCH_REQUEST_DATA:
this->request = va_arg(args, chunk_t);
break;
case FETCH_REQUEST_TYPE:
snprintf(buf, sizeof(buf), "Content-Type: %s", va_arg(args, char*));
supported = append_header(this, buf);
break;
case FETCH_REQUEST_HEADER:
supported = append_header(this, va_arg(args, char*));
break;
case FETCH_HTTP_VERSION_1_0:
this->version = L"HTTP/1.0";
break;
case FETCH_TIMEOUT:
this->timeout = va_arg(args, u_int) * 1000;
break;
case FETCH_CALLBACK:
this->cb = va_arg(args, fetcher_callback_t);
break;
case FETCH_RESPONSE_CODE:
this->result = va_arg(args, u_int*);
break;
case FETCH_SOURCEIP:
/* not supported, FALL */
default:
supported = FALSE;
break;
}
va_end(args);
return supported;
}
METHOD(fetcher_t, destroy, void,
private_winhttp_fetcher_t *this)
{
WinHttpCloseHandle(this->session);
this->headers->destroy_function(this->headers, free);
free(this);
}
/*
* Described in header.
*/
winhttp_fetcher_t *winhttp_fetcher_create()
{
private_winhttp_fetcher_t *this;
INIT(this,
.public = {
.interface = {
.fetch = _fetch,
.set_option = _set_option,
.destroy = _destroy,
},
},
.version = L"HTTP/1.1",
.cb = fetcher_default_callback,
.headers = linked_list_create(),
.session = WinHttpOpen(L"strongSwan WinHTTP fetcher",
WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
WINHTTP_NO_PROXY_NAME,
WINHTTP_NO_PROXY_BYPASS, 0),
);
if (!this->session)
{
free(this);
return NULL;
}
return &this->public;
}