diff --git a/configure.ac b/configure.ac index 736f097c1..ec6ec9bb3 100644 --- a/configure.ac +++ b/configure.ac @@ -161,6 +161,7 @@ ARG_ENABL_SET([curl], [enable CURL fetcher plugin to fetch files via l ARG_ENABL_SET([ldap], [enable LDAP fetching plugin to fetch files via libldap. Requires openLDAP.]) ARG_ENABL_SET([soup], [enable soup fetcher plugin to fetch from HTTP via libsoup. Requires libsoup.]) ARG_ENABL_SET([unbound], [enable UNBOUND resolver plugin to perform DNS queries via libunbound. Requires libldns and libunbound.]) +ARG_ENABL_SET([winhttp], [enable WinHTTP based HTTP/HTTPS fetching plugin.]) # database plugins ARG_ENABL_SET([mysql], [enable MySQL database support. Requires libmysqlclient_r.]) ARG_ENABL_SET([sqlite], [enable SQLite database support. Requires libsqlite3.]) @@ -1160,6 +1161,7 @@ t_plugins= ADD_PLUGIN([test-vectors], [s charon scepclient pki]) ADD_PLUGIN([curl], [s charon scepclient scripts nm cmd]) +ADD_PLUGIN([winhttp], [s charon scripts]) ADD_PLUGIN([soup], [s charon scripts nm cmd]) ADD_PLUGIN([unbound], [s charon scripts]) ADD_PLUGIN([ldap], [s charon scepclient scripts nm cmd]) @@ -1305,6 +1307,7 @@ AC_SUBST(t_plugins) # ----------------------- AM_CONDITIONAL(USE_TEST_VECTORS, test x$test_vectors = xtrue) AM_CONDITIONAL(USE_CURL, test x$curl = xtrue) +AM_CONDITIONAL(USE_WINHTTP, test x$winhttp = xtrue) AM_CONDITIONAL(USE_UNBOUND, test x$unbound = xtrue) AM_CONDITIONAL(USE_SOUP, test x$soup = xtrue) AM_CONDITIONAL(USE_LDAP, test x$ldap = xtrue) @@ -1571,6 +1574,7 @@ AC_CONFIG_FILES([ src/libstrongswan/plugins/sshkey/Makefile src/libstrongswan/plugins/pem/Makefile src/libstrongswan/plugins/curl/Makefile + src/libstrongswan/plugins/winhttp/Makefile src/libstrongswan/plugins/unbound/Makefile src/libstrongswan/plugins/soup/Makefile src/libstrongswan/plugins/ldap/Makefile diff --git a/src/libstrongswan/Makefile.am b/src/libstrongswan/Makefile.am index 2602a9eba..9a08825fd 100644 --- a/src/libstrongswan/Makefile.am +++ b/src/libstrongswan/Makefile.am @@ -425,6 +425,13 @@ if MONOLITHIC endif endif +if USE_WINHTTP + SUBDIRS += plugins/winhttp +if MONOLITHIC + libstrongswan_la_LIBADD += plugins/winhttp/libstrongswan-winhttp.la +endif +endif + if USE_UNBOUND SUBDIRS += plugins/unbound if MONOLITHIC diff --git a/src/libstrongswan/plugins/winhttp/Makefile.am b/src/libstrongswan/plugins/winhttp/Makefile.am new file mode 100644 index 000000000..f6b00a71e --- /dev/null +++ b/src/libstrongswan/plugins/winhttp/Makefile.am @@ -0,0 +1,18 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/libstrongswan + +AM_CFLAGS = \ + $(PLUGIN_CFLAGS) + +if MONOLITHIC +noinst_LTLIBRARIES = libstrongswan-winhttp.la +else +plugin_LTLIBRARIES = libstrongswan-winhttp.la +endif + +libstrongswan_winhttp_la_SOURCES = \ + winhttp_fetcher.c winhttp_fetcher.h \ + winhttp_plugin.c winhttp_plugin.h + +libstrongswan_winhttp_la_LDFLAGS = -module -avoid-version +libstrongswan_winhttp_la_LIBADD = -lwinhttp diff --git a/src/libstrongswan/plugins/winhttp/winhttp_fetcher.c b/src/libstrongswan/plugins/winhttp/winhttp_fetcher.c new file mode 100644 index 000000000..600fa9658 --- /dev/null +++ b/src/libstrongswan/plugins/winhttp/winhttp_fetcher.c @@ -0,0 +1,342 @@ +/* + * 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 . + * + * 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 +#include +#include + +#include "winhttp_fetcher.h" + +#include + +/** + * 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; +}; + +/** + * 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]; + + if (!WinHttpReceiveResponse(request, NULL)) + { + DBG1(DBG_LIB, "reading HTTP response header failed: %u", GetLastError()); + 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 cancelled"); + 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, + 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, + .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, countof(path) - comps.dwUrlPathLength - 1); + } + if (comps.nPort) + { + *port = comps.nPort; + } + return TRUE; +} + +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], *method; + + if (this->request.len) + { + method = L"POST"; + } + else + { + method = L"GET"; + } + + if (parse_uri(this, uri, host, countof(host), path, countof(path), + &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 (send_request(this, request) && + read_result(this, request, userdata)) + { + status = SUCCESS; + } + WinHttpCloseHandle(request); + } + else + { + DBG1(DBG_LIB, "opening request failed: %u", GetLastError()); + } + WinHttpCloseHandle(connect); + } + 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_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; +} diff --git a/src/libstrongswan/plugins/winhttp/winhttp_fetcher.h b/src/libstrongswan/plugins/winhttp/winhttp_fetcher.h new file mode 100644 index 000000000..6129eb889 --- /dev/null +++ b/src/libstrongswan/plugins/winhttp/winhttp_fetcher.h @@ -0,0 +1,46 @@ +/* + * 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 . + * + * 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. + */ + +/** + * @defgroup winhttp_fetcher winhttp_fetcher + * @{ @ingroup winhttp_p + */ + +#ifndef WINHTTP_FETCHER_H_ +#define WINHTTP_FETCHER_H_ + +#include + +typedef struct winhttp_fetcher_t winhttp_fetcher_t; + +/** + * Fetcher implementation using Microsofts WinHTTP. + */ +struct winhttp_fetcher_t { + + /** + * Implements fetcher interface. + */ + fetcher_t interface; +}; + +/** + * Create a winhttp_fetcher instance + * + * @return WinHTTP based fetcher + */ +winhttp_fetcher_t *winhttp_fetcher_create(); + +#endif /** WINHTTP_FETCHER_H_ @}*/ diff --git a/src/libstrongswan/plugins/winhttp/winhttp_plugin.c b/src/libstrongswan/plugins/winhttp/winhttp_plugin.c new file mode 100644 index 000000000..8b67ff58b --- /dev/null +++ b/src/libstrongswan/plugins/winhttp/winhttp_plugin.c @@ -0,0 +1,74 @@ +/* + * 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 . + * + * 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 "winhttp_plugin.h" +#include "winhttp_fetcher.h" + +typedef struct private_winhttp_plugin_t private_winhttp_plugin_t; + +/** + * Private data of winhttp_plugin + */ +struct private_winhttp_plugin_t { + + /** + * Public functions + */ + winhttp_plugin_t public; +}; + +METHOD(plugin_t, get_name, char*, + private_winhttp_plugin_t *this) +{ + return "winhttp"; +} + +METHOD(plugin_t, get_features, int, + private_winhttp_plugin_t *this, plugin_feature_t *features[]) +{ + static plugin_feature_t f[] = { + PLUGIN_REGISTER(FETCHER, winhttp_fetcher_create), + PLUGIN_PROVIDE(FETCHER, "http://"), + PLUGIN_PROVIDE(FETCHER, "https://"), + }; + *features = f; + return countof(f); +} + +METHOD(plugin_t, destroy, void, + private_winhttp_plugin_t *this) +{ + free(this); +} + +/* + * see header file + */ +plugin_t *winhttp_plugin_create() +{ + private_winhttp_plugin_t *this; + + INIT(this, + .public = { + .plugin = { + .get_name = _get_name, + .get_features = _get_features, + .destroy = _destroy, + }, + }, + ); + + return &this->public.plugin; +} diff --git a/src/libstrongswan/plugins/winhttp/winhttp_plugin.h b/src/libstrongswan/plugins/winhttp/winhttp_plugin.h new file mode 100644 index 000000000..30cd0518a --- /dev/null +++ b/src/libstrongswan/plugins/winhttp/winhttp_plugin.h @@ -0,0 +1,42 @@ +/* + * 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 . + * + * 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. + */ + +/** + * @defgroup winhttp_p winhttp + * @ingroup plugins + * + * @defgroup winhttp_plugin winhttp_plugin + * @{ @ingroup winhttp_p + */ + +#ifndef WINHTTP_PLUGIN_H_ +#define WINHTTP_PLUGIN_H_ + +#include + +typedef struct winhttp_plugin_t winhttp_plugin_t; + +/** + * Plugin implementing fetcher interface using Microsofts WinHTTP. + */ +struct winhttp_plugin_t { + + /** + * Implements plugin interface. + */ + plugin_t plugin; +}; + +#endif /** WINHTTP_PLUGIN_H_ @}*/