winhttp: Implement a http(s) fetcher based on Microsofts WinHTTP API
authorMartin Willi <martin@revosec.ch>
Thu, 30 Jan 2014 13:07:53 +0000 (14:07 +0100)
committerMartin Willi <martin@revosec.ch>
Wed, 4 Jun 2014 14:34:15 +0000 (16:34 +0200)
configure.ac
src/libstrongswan/Makefile.am
src/libstrongswan/plugins/winhttp/Makefile.am [new file with mode: 0644]
src/libstrongswan/plugins/winhttp/winhttp_fetcher.c [new file with mode: 0644]
src/libstrongswan/plugins/winhttp/winhttp_fetcher.h [new file with mode: 0644]
src/libstrongswan/plugins/winhttp/winhttp_plugin.c [new file with mode: 0644]
src/libstrongswan/plugins/winhttp/winhttp_plugin.h [new file with mode: 0644]

index 736f097..ec6ec9b 100644 (file)
@@ -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
index 2602a9e..9a08825 100644 (file)
@@ -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 (file)
index 0000000..f6b00a7
--- /dev/null
@@ -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 (file)
index 0000000..600fa96
--- /dev/null
@@ -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 <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;
+};
+
+/**
+ * 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 (file)
index 0000000..6129eb8
--- /dev/null
@@ -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 <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.
+ */
+
+/**
+ * @defgroup winhttp_fetcher winhttp_fetcher
+ * @{ @ingroup winhttp_p
+ */
+
+#ifndef WINHTTP_FETCHER_H_
+#define WINHTTP_FETCHER_H_
+
+#include <library.h>
+
+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 (file)
index 0000000..8b67ff5
--- /dev/null
@@ -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 <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 "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 (file)
index 0000000..30cd051
--- /dev/null
@@ -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 <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.
+ */
+
+/**
+ * @defgroup winhttp_p winhttp
+ * @ingroup plugins
+ *
+ * @defgroup winhttp_plugin winhttp_plugin
+ * @{ @ingroup winhttp_p
+ */
+
+#ifndef WINHTTP_PLUGIN_H_
+#define WINHTTP_PLUGIN_H_
+
+#include <plugins/plugin.h>
+
+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_ @}*/