twister

HTTP fault injector for testing
Log | Files | Refs | README | LICENSE

commit 1234fa22095d521c88cf5f8bc4f4abdb09366732
parent 9da6fd1d3fc46224482f19ca970988c4e00e163b
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sat, 20 Jan 2018 12:24:08 +0100

initial import of GNS proxy, minus Socks5 and TLS support

Diffstat:
Asrc/twister/taler-twister.c | 1512+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 1512 insertions(+), 0 deletions(-)

diff --git a/src/twister/taler-twister.c b/src/twister/taler-twister.c @@ -0,0 +1,1512 @@ +/* + This file is part of GNUnet. + Copyright (C) 2012-2014 GNUnet e.V. + Copyright (C) 2018 Taler Systems SA + + GNUnet 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 3, or (at your + option) any later version. + + GNUnet 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. + + You should have received a copy of the GNU General Public License + along with GNUnet; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ +/** + * @author Martin Schanzenbach + * @author Christian Grothoff + * @author Marcello Stanisci + * @file src/twister/taler-twister.c + * @brief HTTP proxy that acts as a man in the middle making changes to + * requests or responses + */ +#include "platform.h" +#include <microhttpd.h> +#if HAVE_CURL_CURL_H +#include <curl/curl.h> +#elif HAVE_GNURL_CURL_H +#include <gnurl/curl.h> +#endif +#include <gnunet/gnunet_util_lib.h> + + +/** + * Default Socks5 listen port. + */ +#define GNUNET_GNS_PROXY_PORT 7777 + +/** + * Maximum supported length for a URI. + * Should die. @deprecated + */ +#define MAX_HTTP_URI_LENGTH 2048 + +/** + * Size of the buffer for the data upload / download. Must be + * enough for curl, thus CURL_MAX_WRITE_SIZE is needed here (16k). + */ +#define IO_BUFFERSIZE CURL_MAX_WRITE_SIZE + +/** + * Size of the read/write buffers for Socks. Uses + * 256 bytes for the hostname (at most), plus a few + * bytes overhead for the messages. + */ +#define SOCKS_BUFFERSIZE (256 + 32) + +/** + * Port for plaintext HTTP. + */ +#define HTTP_PORT 80 + +/** + * After how long do we clean up unused MHD SSL/TLS instances? + */ +#define MHD_CACHE_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 5) + +/** + * After how long do we clean up Socks5 handles that failed to show any activity + * with their respective MHD instance? + */ +#define HTTP_HANDSHAKE_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 15) + + +/** + * Log curl error. + * + * @param level log level + * @param fun name of curl_easy-function that gave the error + * @param rc return code from curl + */ +#define LOG_CURL_EASY(level,fun,rc) GNUNET_log(level, _("%s failed at %s:%d: `%s'\n"), fun, __FILE__, __LINE__, curl_easy_strerror (rc)) + + +/* *********************** Datastructures for HTTP handling ****************** */ + + +/** + * A header list + */ +struct HttpResponseHeader +{ + /** + * DLL + */ + struct HttpResponseHeader *next; + + /** + * DLL + */ + struct HttpResponseHeader *prev; + + /** + * Header type + */ + char *type; + + /** + * Header value + */ + char *value; +}; + + +/** + * A structure for socks requests + */ +struct Socks5Request +{ + + /** + * DLL. + */ + struct Socks5Request *next; + + /** + * DLL. + */ + struct Socks5Request *prev; + + /** + * Client socket read task + */ + struct GNUNET_SCHEDULER_Task * rtask; + + /** + * Client socket write task + */ + struct GNUNET_SCHEDULER_Task * wtask; + + /** + * Read buffer + */ + char rbuf[SOCKS_BUFFERSIZE]; + + /** + * Write buffer + */ + char wbuf[SOCKS_BUFFERSIZE]; + + /** + * Buffer we use for moving data between MHD and curl (in both directions). + */ + char io_buf[IO_BUFFERSIZE]; + + /** + * MHD response object for this request. + */ + struct MHD_Response *response; + + /** + * The URL to fetch + */ + char *url; + + /** + * Handle to cURL + */ + CURL *curl; + + /** + * HTTP request headers for the curl request. + */ + struct curl_slist *headers; + + /** + * DNS->IP mappings resolved through GNS + */ + struct curl_slist *hosts; + + /** + * HTTP response code to give to MHD for the response. + */ + unsigned int response_code; + + /** + * Number of bytes already in read buffer + */ + size_t rbuf_len; + + /** + * Number of bytes already in write buffer + */ + size_t wbuf_len; + + /** + * Number of bytes already in the IO buffer. + */ + size_t io_len; + + /** + * Once known, what's the target address for the connection? + */ + struct sockaddr_storage destination_address; + + /** + * Headers from response + */ + struct HttpResponseHeader *header_head; + + /** + * Headers from response + */ + struct HttpResponseHeader *header_tail; + +}; + + + +/* *********************** Globals **************************** */ + + +/** + * The port the proxy is running on (default 7777) + */ +static unsigned long long port = GNUNET_GNS_PROXY_PORT; + +/** + * The cURL download task (curl multi API). + */ +static struct GNUNET_SCHEDULER_Task * curl_download_task; + +/** + * The cURL multi handle + */ +static CURLM *curl_multi; + +/** + * The daemon handle + */ +static struct MHD_Daemon *daemon; + +/** + * The task ID + */ +static struct GNUNET_SCHEDULER_Task *httpd_task; + +/** + * DLL of active socks requests. + */ +static struct Socks5Request *s5r_head; + +/** + * DLL of active socks requests. + */ +static struct Socks5Request *s5r_tail; + +/** + * Response we return on cURL failures. + */ +static struct MHD_Response *curl_failure_response; + +/** + * Our configuration. + */ +static const struct GNUNET_CONFIGURATION_Handle *cfg; + + +/* ************************* Global helpers ********************* */ + + +/** + * Run MHD now, we have extra data ready for the callback. + * + * @param hd the daemon to run now. + */ +static void +run_mhd_now (struct MhdHttpList *hd); + + +/* ************************* HTTP handling with cURL *********************** */ + +static void +curl_download_prepare (); + +/** + * Callback for MHD response generation. This function is called from + * MHD whenever MHD expects to get data back. Copies data from the + * io_buf, if available. + * + * @param cls closure with our `struct Socks5Request` + * @param pos in buffer + * @param buf where to copy data + * @param max available space in @a buf + * @return number of bytes written to @a buf + */ +static ssize_t +mhd_content_cb (void *cls, + uint64_t pos, + char* buf, + size_t max) +{ + struct Socks5Request *s5r = cls; + size_t bytes_to_copy; + + if ( (SOCKS5_SOCKET_UPLOAD_STARTED == s5r->state) || + (SOCKS5_SOCKET_UPLOAD_DONE == s5r->state) ) + { + /* we're still not done with the upload, do not yet + start the download, the IO buffer is still full + with upload data. */ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Pausing MHD download, not yet ready for download\n"); + return 0; /* not yet ready for data download */ + } + bytes_to_copy = GNUNET_MIN (max, + s5r->io_len); + if ( (0 == bytes_to_copy) && + (SOCKS5_SOCKET_DOWNLOAD_DONE != s5r->state) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Pausing MHD download, no data available\n"); + if (NULL != s5r->curl) + { + curl_easy_pause (s5r->curl, CURLPAUSE_CONT); + curl_download_prepare (); + } + return 0; /* more data later */ + } + if ( (0 == bytes_to_copy) && + (SOCKS5_SOCKET_DOWNLOAD_DONE == s5r->state) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Completed MHD download\n"); + return MHD_CONTENT_READER_END_OF_STREAM; + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Writing %lu/%lu bytes\n", bytes_to_copy, s5r->io_len); + GNUNET_memcpy (buf, s5r->io_buf, bytes_to_copy); + memmove (s5r->io_buf, + &s5r->io_buf[bytes_to_copy], + s5r->io_len - bytes_to_copy); + s5r->io_len -= bytes_to_copy; + if (NULL != s5r->curl) + curl_easy_pause (s5r->curl, CURLPAUSE_CONT); + return bytes_to_copy; +} + + +/** + * We're getting an HTTP response header from cURL. Convert it to the + * MHD response headers. Mostly copies the headers, but makes special + * adjustments based on control requests. + * + * @param buffer curl buffer with a single line of header data; not 0-terminated! + * @param size curl blocksize + * @param nmemb curl blocknumber + * @param cls our `struct Socks5Request *` + * @return size of processed bytes + */ +static size_t +curl_check_hdr (void *buffer, size_t size, size_t nmemb, void *cls) +{ + struct Socks5Request *s5r = cls; + struct HttpResponseHeader *header; + size_t bytes = size * nmemb; + char *ndup; + const char *hdr_type; + const char *cookie_domain; + char *hdr_val; + char *new_cookie_hdr; + char *new_location; + size_t offset; + size_t delta_cdomain; + int domain_matched; + char *tok; + + /* first, check SSL certificate */ + if ((GNUNET_YES != s5r->ssl_checked) && + (HTTPS_PORT == s5r->port)) + { + if (GNUNET_OK != check_ssl_certificate (s5r)) + return 0; + } + + ndup = GNUNET_strndup (buffer, bytes); + hdr_type = strtok (ndup, ":"); + if (NULL == hdr_type) + { + GNUNET_free (ndup); + return bytes; + } + hdr_val = strtok (NULL, ""); + if (NULL == hdr_val) + { + GNUNET_free (ndup); + return bytes; + } + if (' ' == *hdr_val) + hdr_val++; + + /* custom logic for certain header types */ +#if 0 + new_cookie_hdr = NULL; + if ( (NULL != s5r->leho) && + (0 == strcasecmp (hdr_type, + MHD_HTTP_HEADER_SET_COOKIE)) ) + + { + new_cookie_hdr = GNUNET_malloc (strlen (hdr_val) + + strlen (s5r->domain) + 1); + offset = 0; + domain_matched = GNUNET_NO; /* make sure we match domain at most once */ + for (tok = strtok (hdr_val, ";"); NULL != tok; tok = strtok (NULL, ";")) + { + if ( (0 == strncasecmp (tok, " domain", strlen (" domain"))) && + (GNUNET_NO == domain_matched) ) + { + domain_matched = GNUNET_YES; + cookie_domain = tok + strlen (" domain") + 1; + if (strlen (cookie_domain) < strlen (s5r->leho)) + { + delta_cdomain = strlen (s5r->leho) - strlen (cookie_domain); + if (0 == strcasecmp (cookie_domain, s5r->leho + delta_cdomain)) + { + offset += sprintf (new_cookie_hdr + offset, + " domain=%s;", + s5r->domain); + continue; + } + } + else if (0 == strcmp (cookie_domain, s5r->leho)) + { + offset += sprintf (new_cookie_hdr + offset, + " domain=%s;", + s5r->domain); + continue; + } + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + _("Cookie domain `%s' supplied by server is invalid\n"), + tok); + } + GNUNET_memcpy (new_cookie_hdr + offset, tok, strlen (tok)); + offset += strlen (tok); + new_cookie_hdr[offset++] = ';'; + } + hdr_val = new_cookie_hdr; + } + + new_location = NULL; + if (0 == strcasecmp (MHD_HTTP_HEADER_LOCATION, hdr_type)) + { + char *leho_host; + + GNUNET_asprintf (&leho_host, + (HTTPS_PORT != s5r->port) + ? "http://%s" + : "https://%s", + s5r->leho); + if (0 == strncmp (leho_host, + hdr_val, + strlen (leho_host))) + { + GNUNET_asprintf (&new_location, + "%s%s%s", + (HTTPS_PORT != s5r->port) + ? "http://" + : "https://", + s5r->domain, + hdr_val + strlen (leho_host)); + hdr_val = new_location; + } + GNUNET_free (leho_host); + } +#endif + /* MHD does not allow certain characters in values, remove those */ + if (NULL != (tok = strchr (hdr_val, '\n'))) + *tok = '\0'; + if (NULL != (tok = strchr (hdr_val, '\r'))) + *tok = '\0'; + if (NULL != (tok = strchr (hdr_val, '\t'))) + *tok = '\0'; + if (0 != strlen (hdr_val)) /* Rely in MHD to set those */ + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Adding header %s: %s to MHD response\n", + hdr_type, + hdr_val); + header = GNUNET_new (struct HttpResponseHeader); + header->type = GNUNET_strndup (hdr_type, strlen (hdr_type)); + header->value = GNUNET_strndup (hdr_val, strlen (hdr_val)); + GNUNET_CONTAINER_DLL_insert (s5r->header_head, + s5r->header_tail, + header); + } + GNUNET_free (ndup); + GNUNET_free_non_null (new_cookie_hdr); + GNUNET_free_non_null (new_location); + return bytes; +} + + +static int +create_mhd_response_from_s5r (struct Socks5Request *s5r) +{ + long resp_code; + double content_length; + struct HttpResponseHeader *header; + + if (NULL != s5r->response) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Response already set!\n"); + return GNUNET_SYSERR; + } + + GNUNET_break (CURLE_OK == + curl_easy_getinfo (s5r->curl, + CURLINFO_RESPONSE_CODE, + &resp_code)); + GNUNET_break (CURLE_OK == + curl_easy_getinfo (s5r->curl, + CURLINFO_CONTENT_LENGTH_DOWNLOAD, + &content_length)); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Creating MHD response with code %d and size %d\n", + (int) resp_code, (int) content_length); + s5r->response_code = resp_code; + s5r->response = MHD_create_response_from_callback ((-1 == content_length) ? MHD_SIZE_UNKNOWN : content_length, + IO_BUFFERSIZE, + &mhd_content_cb, + s5r, + NULL); + for (header = s5r->header_head; NULL != header; header = header->next) + { + GNUNET_break (MHD_YES == + MHD_add_response_header (s5r->response, + header->type, + header->value)); + + } + if (NULL != s5r->leho) + { + char *cors_hdr; + + GNUNET_asprintf (&cors_hdr, + (HTTPS_PORT == s5r->port) + ? "https://%s" + : "http://%s", + s5r->leho); + + GNUNET_break (MHD_YES == + MHD_add_response_header (s5r->response, + MHD_HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN, + cors_hdr)); + GNUNET_free (cors_hdr); + } + /* force connection to be closed after each request, as we + do not support HTTP pipelining (yet, FIXME!) */ + /*GNUNET_break (MHD_YES == + MHD_add_response_header (s5r->response, + MHD_HTTP_HEADER_CONNECTION, + "close"));*/ + return GNUNET_OK; +} + +/** + * Handle response payload data from cURL. Copies it into our `io_buf` to make + * it available to MHD. + * + * @param ptr pointer to the data + * @param size number of blocks of data + * @param nmemb blocksize + * @param ctx our `struct Socks5Request *` + * @return number of bytes handled + */ +static size_t +curl_download_cb (void *ptr, size_t size, size_t nmemb, void* ctx) +{ + struct Socks5Request *s5r = ctx; + size_t total = size * nmemb; + + if (NULL == s5r->response) + GNUNET_assert (GNUNET_OK == create_mhd_response_from_s5r (s5r)); + + if ( (SOCKS5_SOCKET_UPLOAD_STARTED == s5r->state) || + (SOCKS5_SOCKET_UPLOAD_DONE == s5r->state) ) + { + /* we're still not done with the upload, do not yet + start the download, the IO buffer is still full + with upload data. */ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Pausing CURL download, waiting for UPLOAD to finish\n"); + return CURL_WRITEFUNC_PAUSE; /* not yet ready for data download */ + } + if (sizeof (s5r->io_buf) - s5r->io_len < total) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Pausing CURL download, not enough space %lu %lu %lu\n", sizeof (s5r->io_buf), + s5r->io_len, total); + return CURL_WRITEFUNC_PAUSE; /* not enough space */ + } + GNUNET_memcpy (&s5r->io_buf[s5r->io_len], + ptr, + total); + s5r->io_len += total; + if (s5r->io_len == total) + run_mhd_now (s5r->hd); + return total; +} + + +/** + * cURL callback for uploaded (PUT/POST) data. Copies it into our `io_buf` + * to make it available to MHD. + * + * @param buf where to write the data + * @param size number of bytes per member + * @param nmemb number of members available in @a buf + * @param cls our `struct Socks5Request` that generated the data + * @return number of bytes copied to @a buf + */ +static size_t +curl_upload_cb (void *buf, size_t size, size_t nmemb, void *cls) +{ + struct Socks5Request *s5r = cls; + size_t len = size * nmemb; + size_t to_copy; + + if ( (0 == s5r->io_len) && + (SOCKS5_SOCKET_UPLOAD_DONE != s5r->state) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Pausing CURL UPLOAD, need more data\n"); + return CURL_READFUNC_PAUSE; + } + if ( (0 == s5r->io_len) && + (SOCKS5_SOCKET_UPLOAD_DONE == s5r->state) ) + { + s5r->state = SOCKS5_SOCKET_DOWNLOAD_STARTED; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Completed CURL UPLOAD\n"); + return 0; /* upload finished, can now download */ + } + if ( (SOCKS5_SOCKET_UPLOAD_STARTED != s5r->state) && + (SOCKS5_SOCKET_UPLOAD_DONE != s5r->state) ) + { + GNUNET_break (0); + return CURL_READFUNC_ABORT; + } + to_copy = GNUNET_MIN (s5r->io_len, + len); + GNUNET_memcpy (buf, s5r->io_buf, to_copy); + memmove (s5r->io_buf, + &s5r->io_buf[to_copy], + s5r->io_len - to_copy); + s5r->io_len -= to_copy; + if (s5r->io_len + to_copy == sizeof (s5r->io_buf)) + run_mhd_now (s5r->hd); /* got more space for upload now */ + return to_copy; +} + + +/* ************************** main loop of cURL interaction ****************** */ + + +/** + * Task that is run when we are ready to receive more data + * from curl + * + * @param cls closure + */ +static void +curl_task_download (void *cls); + + +/** + * Ask cURL for the select() sets and schedule cURL operations. + */ +static void +curl_download_prepare () +{ + CURLMcode mret; + fd_set rs; + fd_set ws; + fd_set es; + int max; + struct GNUNET_NETWORK_FDSet *grs; + struct GNUNET_NETWORK_FDSet *gws; + long to; + struct GNUNET_TIME_Relative rtime; + + if (NULL != curl_download_task) + { + GNUNET_SCHEDULER_cancel (curl_download_task); + curl_download_task = NULL; + } + max = -1; + FD_ZERO (&rs); + FD_ZERO (&ws); + FD_ZERO (&es); + if (CURLM_OK != (mret = curl_multi_fdset (curl_multi, &rs, &ws, &es, &max))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "%s failed at %s:%d: `%s'\n", + "curl_multi_fdset", __FILE__, __LINE__, + curl_multi_strerror (mret)); + return; + } + to = -1; + GNUNET_break (CURLM_OK == curl_multi_timeout (curl_multi, &to)); + if (-1 == to) + rtime = GNUNET_TIME_UNIT_FOREVER_REL; + else + rtime = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, to); + if (-1 != max) + { + grs = GNUNET_NETWORK_fdset_create (); + gws = GNUNET_NETWORK_fdset_create (); + GNUNET_NETWORK_fdset_copy_native (grs, &rs, max + 1); + GNUNET_NETWORK_fdset_copy_native (gws, &ws, max + 1); + curl_download_task = GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_DEFAULT, + rtime, + grs, gws, + &curl_task_download, curl_multi); + GNUNET_NETWORK_fdset_destroy (gws); + GNUNET_NETWORK_fdset_destroy (grs); + } + else + { + curl_download_task = GNUNET_SCHEDULER_add_delayed (rtime, + &curl_task_download, + curl_multi); + } +} + + +/** + * Task that is run when we are ready to receive more data from curl. + * + * @param cls closure, NULL + */ +static void +curl_task_download (void *cls) +{ + int running; + int msgnum; + struct CURLMsg *msg; + CURLMcode mret; + struct Socks5Request *s5r; + + curl_download_task = NULL; + do + { + running = 0; + mret = curl_multi_perform (curl_multi, &running); + while (NULL != (msg = curl_multi_info_read (curl_multi, &msgnum))) + { + GNUNET_break (CURLE_OK == + curl_easy_getinfo (msg->easy_handle, + CURLINFO_PRIVATE, + (char **) &s5r )); + if (NULL == s5r) + { + GNUNET_break (0); + continue; + } + switch (msg->msg) + { + case CURLMSG_NONE: + /* documentation says this is not used */ + GNUNET_break (0); + break; + case CURLMSG_DONE: + switch (msg->data.result) + { + case CURLE_OK: + case CURLE_GOT_NOTHING: + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "CURL download completed.\n"); + if (NULL == s5r->response) + GNUNET_assert (GNUNET_OK == create_mhd_response_from_s5r (s5r)); + s5r->state = SOCKS5_SOCKET_DOWNLOAD_DONE; + run_mhd_now (s5r->hd); + break; + default: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Download curl failed: %s\n", + curl_easy_strerror (msg->data.result)); + /* FIXME: indicate error somehow? close MHD connection badly as well? */ + s5r->state = SOCKS5_SOCKET_DOWNLOAD_DONE; + run_mhd_now (s5r->hd); + break; + } + if (NULL == s5r->response) + s5r->response = curl_failure_response; + break; + case CURLMSG_LAST: + /* documentation says this is not used */ + GNUNET_break (0); + break; + default: + /* unexpected status code */ + GNUNET_break (0); + break; + } + }; + } while (mret == CURLM_CALL_MULTI_PERFORM); + if (CURLM_OK != mret) + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "%s failed at %s:%d: `%s'\n", + "curl_multi_perform", __FILE__, __LINE__, + curl_multi_strerror (mret)); + if (0 == running) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Suspending cURL multi loop, no more events pending\n"); + return; /* nothing more in progress */ + } + curl_download_prepare (); +} + + +/* ********************************* MHD response generation ******************* */ + + +/** + * Read HTTP request header field from the request. Copies the fields + * over to the 'headers' that will be given to curl. However, 'Host' + * is substituted with the LEHO if present. We also change the + * 'Connection' header value to "close" as the proxy does not support + * pipelining. + * + * @param cls our `struct Socks5Request` + * @param kind value kind + * @param key field key + * @param value field value + * @return MHD_YES to continue to iterate + */ +static int +con_val_iter (void *cls, + enum MHD_ValueKind kind, + const char *key, + const char *value) +{ + struct Socks5Request *s5r = cls; + char *hdr; + + if ( (0 == strcasecmp (MHD_HTTP_HEADER_HOST, key)) && + (NULL != s5r->leho) ) + value = s5r->leho; + if (0 == strcasecmp (MHD_HTTP_HEADER_CONTENT_LENGTH, key)) + return MHD_YES; + if (0 == strcasecmp (MHD_HTTP_HEADER_ACCEPT_ENCODING, key)) + return MHD_YES; + GNUNET_asprintf (&hdr, + "%s: %s", + key, + value); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Adding HEADER `%s' to HTTP request\n", + hdr); + s5r->headers = curl_slist_append (s5r->headers, + hdr); + GNUNET_free (hdr); + return MHD_YES; +} + + +/** + * Main MHD callback for handling requests. + * + * @param cls unused + * @param con MHD connection handle + * @param url the url in the request + * @param meth the HTTP method used ("GET", "PUT", etc.) + * @param ver the HTTP version string (i.e. "HTTP/1.1") + * @param upload_data the data being uploaded (excluding HEADERS, + * for a POST that fits into memory and that is encoded + * with a supported encoding, the POST data will NOT be + * given in upload_data and is instead available as + * part of MHD_get_connection_values; very large POST + * data *will* be made available incrementally in + * upload_data) + * @param upload_data_size set initially to the size of the + * @a upload_data provided; the method must update this + * value to the number of bytes NOT processed; + * @param con_cls pointer to location where we store the 'struct Request' + * @return MHD_YES if the connection was handled successfully, + * MHD_NO if the socket must be closed due to a serious + * error while handling the request + */ +static int +create_response (void *cls, + struct MHD_Connection *con, + const char *url, + const char *meth, + const char *ver, + const char *upload_data, + size_t *upload_data_size, + void **con_cls) +{ + struct Socks5Request *s5r = *con_cls; + char *curlurl; + char *curl_hosts; + char ipstring[INET6_ADDRSTRLEN]; + char ipaddr[INET6_ADDRSTRLEN + 2]; + const struct sockaddr *sa; + const struct sockaddr_in *s4; + const struct sockaddr_in6 *s6; + uint16_t port; + size_t left; + + if (NULL == s5r) + { + GNUNET_break (0); + return MHD_NO; + } + //Fresh connection. + if (SOCKS5_SOCKET_WITH_MHD == s5r->state) + { + /* first time here, initialize curl handle */ + sa = (const struct sockaddr *) &s5r->destination_address; + switch (sa->sa_family) + { + case AF_INET: + s4 = (const struct sockaddr_in *) &s5r->destination_address; + if (NULL == inet_ntop (AF_INET, + &s4->sin_addr, + ipstring, + sizeof (ipstring))) + { + GNUNET_break (0); + return MHD_NO; + } + GNUNET_snprintf (ipaddr, + sizeof (ipaddr), + "%s", + ipstring); + port = ntohs (s4->sin_port); + break; + case AF_INET6: + s6 = (const struct sockaddr_in6 *) &s5r->destination_address; + if (NULL == inet_ntop (AF_INET6, + &s6->sin6_addr, + ipstring, + sizeof (ipstring))) + { + GNUNET_break (0); + return MHD_NO; + } + GNUNET_snprintf (ipaddr, + sizeof (ipaddr), + "%s", + ipstring); + port = ntohs (s6->sin6_port); + break; + default: + GNUNET_break (0); + return MHD_NO; + } + if (NULL == s5r->curl) + s5r->curl = curl_easy_init (); + if (NULL == s5r->curl) + return MHD_queue_response (con, + MHD_HTTP_INTERNAL_SERVER_ERROR, + curl_failure_response); + curl_easy_setopt (s5r->curl, CURLOPT_HEADERFUNCTION, &curl_check_hdr); + curl_easy_setopt (s5r->curl, CURLOPT_HEADERDATA, s5r); + curl_easy_setopt (s5r->curl, CURLOPT_FOLLOWLOCATION, 0); + curl_easy_setopt (s5r->curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); + curl_easy_setopt (s5r->curl, CURLOPT_CONNECTTIMEOUT, 600L); + curl_easy_setopt (s5r->curl, CURLOPT_TIMEOUT, 600L); + curl_easy_setopt (s5r->curl, CURLOPT_NOSIGNAL, 1L); + curl_easy_setopt (s5r->curl, CURLOPT_HTTP_CONTENT_DECODING, 0); + curl_easy_setopt (s5r->curl, CURLOPT_HTTP_TRANSFER_DECODING, 0); + curl_easy_setopt (s5r->curl, CURLOPT_NOSIGNAL, 1L); + curl_easy_setopt (s5r->curl, CURLOPT_PRIVATE, s5r); + curl_easy_setopt (s5r->curl, CURLOPT_VERBOSE, 0); + /** + * Pre-populate cache to resolve Hostname. + * This is necessary as the DNS name in the CURLOPT_URL is used + * for SNI http://de.wikipedia.org/wiki/Server_Name_Indication + */ + if (NULL != s5r->leho) + { + GNUNET_asprintf (&curl_hosts, + "%s:%d:%s", + s5r->leho, + port, + ipaddr); + s5r->hosts = curl_slist_append(NULL, curl_hosts); + curl_easy_setopt(s5r->curl, CURLOPT_RESOLVE, s5r->hosts); + GNUNET_free (curl_hosts); + } + GNUNET_asprintf (&curlurl, + (HTTPS_PORT != s5r->port) + ? "http://%s:%d%s" + : "https://%s:%d%s", + (NULL != s5r->leho) + ? s5r->leho + : ipaddr, + port, + s5r->url); + curl_easy_setopt (s5r->curl, + CURLOPT_URL, + curlurl); + GNUNET_free (curlurl); + if (0 == strcasecmp (meth, MHD_HTTP_METHOD_PUT)) + { + s5r->state = SOCKS5_SOCKET_UPLOAD_STARTED; + curl_easy_setopt (s5r->curl, CURLOPT_UPLOAD, 1); + curl_easy_setopt (s5r->curl, CURLOPT_WRITEFUNCTION, &curl_download_cb); + curl_easy_setopt (s5r->curl, CURLOPT_WRITEDATA, s5r); + curl_easy_setopt (s5r->curl, CURLOPT_READFUNCTION, &curl_upload_cb); + curl_easy_setopt (s5r->curl, CURLOPT_READDATA, s5r); + } + else if (0 == strcasecmp (meth, MHD_HTTP_METHOD_POST)) + { + s5r->state = SOCKS5_SOCKET_UPLOAD_STARTED; + curl_easy_setopt (s5r->curl, CURLOPT_POST, 1L); + curl_easy_setopt (s5r->curl, CURLOPT_WRITEFUNCTION, &curl_download_cb); + curl_easy_setopt (s5r->curl, CURLOPT_WRITEDATA, s5r); + curl_easy_setopt (s5r->curl, CURLOPT_READFUNCTION, &curl_upload_cb); + curl_easy_setopt (s5r->curl, CURLOPT_READDATA, s5r); + } + else if (0 == strcasecmp (meth, MHD_HTTP_METHOD_HEAD)) + { + s5r->state = SOCKS5_SOCKET_DOWNLOAD_STARTED; + curl_easy_setopt (s5r->curl, CURLOPT_NOBODY, 1); + } + else if (0 == strcasecmp (meth, MHD_HTTP_METHOD_OPTIONS)) + { + s5r->state = SOCKS5_SOCKET_DOWNLOAD_STARTED; + curl_easy_setopt (s5r->curl, CURLOPT_CUSTOMREQUEST, "OPTIONS"); + } + else if (0 == strcasecmp (meth, MHD_HTTP_METHOD_GET)) + { + s5r->state = SOCKS5_SOCKET_DOWNLOAD_STARTED; + curl_easy_setopt (s5r->curl, CURLOPT_HTTPGET, 1); + curl_easy_setopt (s5r->curl, CURLOPT_WRITEFUNCTION, &curl_download_cb); + curl_easy_setopt (s5r->curl, CURLOPT_WRITEDATA, s5r); + } + else + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + _("Unsupported HTTP method `%s'\n"), + meth); + curl_easy_cleanup (s5r->curl); + s5r->curl = NULL; + return MHD_NO; + } + + if (0 == strcasecmp (ver, MHD_HTTP_VERSION_1_0)) + { + curl_easy_setopt (s5r->curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); + } + else if (0 == strcasecmp (ver, MHD_HTTP_VERSION_1_1)) + { + curl_easy_setopt (s5r->curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + } + else + { + curl_easy_setopt (s5r->curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_NONE); + } + + if (HTTPS_PORT == s5r->port) + { + curl_easy_setopt (s5r->curl, CURLOPT_USE_SSL, CURLUSESSL_ALL); + if (NULL != s5r->dane_data) + curl_easy_setopt (s5r->curl, CURLOPT_SSL_VERIFYPEER, 0L); + else + curl_easy_setopt (s5r->curl, CURLOPT_SSL_VERIFYPEER, 1L); + /* Disable cURL checking the hostname, as we will check ourselves + as only we have the domain name or the LEHO or the DANE record */ + curl_easy_setopt (s5r->curl, CURLOPT_SSL_VERIFYHOST, 0L); + } + else + { + curl_easy_setopt (s5r->curl, CURLOPT_USE_SSL, CURLUSESSL_NONE); + } + + if (CURLM_OK != curl_multi_add_handle (curl_multi, s5r->curl)) + { + GNUNET_break (0); + curl_easy_cleanup (s5r->curl); + s5r->curl = NULL; + return MHD_NO; + } + MHD_get_connection_values (con, + MHD_HEADER_KIND, + &con_val_iter, s5r); + curl_easy_setopt (s5r->curl, CURLOPT_HTTPHEADER, s5r->headers); + curl_download_prepare (); + return MHD_YES; + } + + /* continuing to process request */ + if (0 != *upload_data_size) + { + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Processing %u bytes UPLOAD\n", + (unsigned int) *upload_data_size); + + /* FIXME: This must be set or a header with Transfer-Encoding: chunked. Else + * upload callback is not called! + */ + curl_easy_setopt (s5r->curl, CURLOPT_POSTFIELDSIZE, *upload_data_size); + + left = GNUNET_MIN (*upload_data_size, + sizeof (s5r->io_buf) - s5r->io_len); + GNUNET_memcpy (&s5r->io_buf[s5r->io_len], + upload_data, + left); + s5r->io_len += left; + *upload_data_size -= left; + GNUNET_assert (NULL != s5r->curl); + curl_easy_pause (s5r->curl, CURLPAUSE_CONT); + return MHD_YES; + } + if (SOCKS5_SOCKET_UPLOAD_STARTED == s5r->state) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Finished processing UPLOAD\n"); + s5r->state = SOCKS5_SOCKET_UPLOAD_DONE; + } + if (NULL == s5r->response) + return MHD_YES; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Queueing response with MHD\n"); + run_mhd_now (s5r->hd); + return MHD_queue_response (con, + s5r->response_code, + s5r->response); +} + + +/* ******************** MHD HTTP setup and event loop ******************** */ + + +/** + * Function called when MHD decides that we are done with a request. + * + * @param cls NULL + * @param connection connection handle + * @param con_cls value as set by the last call to + * the MHD_AccessHandlerCallback, should be our `struct Socks5Request *` + * @param toe reason for request termination (ignored) + */ +static void +mhd_completed_cb (void *cls, + struct MHD_Connection *connection, + void **con_cls, + enum MHD_RequestTerminationCode toe) +{ + struct Socks5Request *s5r = *con_cls; + struct HttpResponseHeader *header; + + if (NULL == s5r) + return; + if (MHD_REQUEST_TERMINATED_COMPLETED_OK != toe) + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "MHD encountered error handling request: %d\n", + toe); + if (NULL != s5r->curl) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Resetting cURL handle\n"); + curl_multi_remove_handle (curl_multi, + s5r->curl); + curl_slist_free_all (s5r->headers); + s5r->headers = NULL; + curl_easy_reset (s5r->curl); + s5r->rbuf_len = 0; + s5r->wbuf_len = 0; + s5r->io_len = 0; + } + if ( (NULL != s5r->response) && + (curl_failure_response != s5r->response) ) + MHD_destroy_response (s5r->response); + for (header = s5r->header_head; header != NULL; header = s5r->header_head) + { + GNUNET_CONTAINER_DLL_remove (s5r->header_head, + s5r->header_tail, + header); + GNUNET_free (header->type); + GNUNET_free (header->value); + GNUNET_free (header); + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Finished request for %s\n", + s5r->url); + GNUNET_free (s5r->url); + GNUNET_free (s5r); + *con_cls = NULL; +} + + +/** + * Function called when MHD first processes an incoming connection. + * Gives us the respective URI information. + * + * We use this to associate the `struct MHD_Connection` with our + * internal `struct Socks5Request` data structure (by checking + * for matching sockets). + * + * @param cls the HTTP server handle (a `struct MhdHttpList`) + * @param url the URL that is being requested + * @param connection MHD connection object for the request + * @return the `struct Socks5Request` that this @a connection is for + */ +static void * +mhd_log_callback (void *cls, + const char *url, + struct MHD_Connection *connection) +{ + struct Socks5Request *s5r; + const union MHD_ConnectionInfo *ci; + + ci = MHD_get_connection_info (connection, + MHD_CONNECTION_INFO_SOCKET_CONTEXT); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Processing %s\n", + url); + if (NULL == ci) + { + GNUNET_break (0); + return NULL; + } + + s5r = GNUNET_new (struct Socks5Request); + s5r->url = GNUNET_strdup (url); + return s5r; +} + + +/** + * Kill the MHD daemon. + */ +static void +kill_httpd (void) +{ + MHD_stop_daemon (daemon); + daemon = NULL; + if (NULL != httpd_task) + { + GNUNET_SCHEDULER_cancel (httpd_task); + httpd_task = NULL; + } +} + + +/** + * Task run whenever HTTP server operations are pending. + * + * @param cls the `struct MhdHttpList *` of the daemon that is being run + */ +static void +do_httpd (void *cls); + + +/** + * Schedule MHD. This function should be called initially when an + * MHD is first getting its client socket, and will then automatically + * always be called later whenever there is work to be done. + */ +static void +schedule_httpd (void) +{ + fd_set rs; + fd_set ws; + fd_set es; + struct GNUNET_NETWORK_FDSet *wrs; + struct GNUNET_NETWORK_FDSet *wws; + int max; + int haveto; + MHD_UNSIGNED_LONG_LONG timeout; + struct GNUNET_TIME_Relative tv; + + FD_ZERO (&rs); + FD_ZERO (&ws); + FD_ZERO (&es); + max = -1; + if (MHD_YES != + MHD_get_fdset (daemon, + &rs, + &ws, + &es, + &max)) + { + kill_httpd (NULL); + return; + } + haveto = MHD_get_timeout (daemon, + &timeout); + if (MHD_YES == haveto) + tv.rel_value_us = (uint64_t) timeout * 1000LL; + else + tv = GNUNET_TIME_UNIT_FOREVER_REL; + if (-1 != max) + { + wrs = GNUNET_NETWORK_fdset_create (); + wws = GNUNET_NETWORK_fdset_create (); + GNUNET_NETWORK_fdset_copy_native (wrs, + &rs, + max + 1); + GNUNET_NETWORK_fdset_copy_native (wws, + &ws, + max + 1); + } + else + { + wrs = NULL; + wws = NULL; + } + if (NULL != httpd_task) + { + GNUNET_SCHEDULER_cancel (httpd_task); + httpd_task = NULL; + } + httpd_task = + GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_DEFAULT, + tv, + wrs, + wws, + &do_httpd, + NULL); + if (NULL != wrs) + GNUNET_NETWORK_fdset_destroy (wrs); + if (NULL != wws) + GNUNET_NETWORK_fdset_destroy (wws); +} + + +/** + * Task run whenever HTTP server operations are pending. + * + * @param cls NULL + */ +static void +do_httpd (void *cls) +{ + httpd_task = NULL; + MHD_run (daemon); + schedule_httpd (NULL); +} + + +/** + * Run MHD now, we have extra data ready for the callback. + */ +static void +run_mhd_now (void) +{ + if (NULL != httpd_task) + GNUNET_SCHEDULER_cancel (httpd_task); + httpd_task = GNUNET_SCHEDULER_add_now (&do_httpd, + NULL); +} + + + +/** + * Function called by MHD with errors, suppresses them all. + * + * @param cls closure + * @param fm format string (`printf()`-style) + * @param ap arguments to @a fm + */ +static void +mhd_error_log_callback (void *cls, + const char *fm, + va_list ap) +{ + /* do nothing */ +} + + +/* ******************* General / main code ********************* */ + + +/** + * Task run on shutdown + * + * @param cls closure + */ +static void +do_shutdown (void *cls) +{ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Shutting down...\n"); + kill_httpd (); + if (NULL != curl_multi) + { + curl_multi_cleanup (curl_multi); + curl_multi = NULL; + } + if (NULL != curl_download_task) + { + GNUNET_SCHEDULER_cancel (curl_download_task); + curl_download_task = NULL; + } +} + + +/** + * Main function that will be run + * + * @param cls closure + * @param args remaining command-line arguments + * @param cfgfile name of the configuration file used (for saving, can be NULL!) + * @param c configuration + */ +static void +run (void *cls, + char *const *args, + const char *cfgfile, + const struct GNUNET_CONFIGURATION_Handle *c) +{ + cfg = c; + if (0 != curl_global_init (CURL_GLOBAL_WIN32)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "cURL global init failed!\n"); + GNUNET_SCHEDULER_shutdown (); + return; + } + if (NULL == (curl_multi = curl_multi_init ())) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to create cURL multi handle!\n"); + return; + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Proxy listens on port %llu\n", + port); + + /* start MHD daemon for HTTP */ + daemon = MHD_start_daemon (MHD_USE_DEBUG, + port, + NULL, NULL, + &create_response, NULL, + MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 16, + MHD_OPTION_NOTIFY_COMPLETED, &mhd_completed_cb, NULL, + MHD_OPTION_URI_LOG_CALLBACK, &mhd_log_callback, NULL, + MHD_OPTION_END); + if (NULL == daemon) + { + GNUNET_break (0); + GNUNET_SCHEDULER_shutdown (); + return; + } + GNUNET_SCHEDULER_add_shutdown (&do_shutdown, + NULL); + run_mhd_now (); +} + + +/** + * The main function for gnunet-gns-proxy. + * + * @param argc number of arguments from the command line + * @param argv command line arguments + * @return 0 ok, 1 on error + */ +int +main (int argc, char *const *argv) +{ + struct GNUNET_GETOPT_CommandLineOption options[] = { + GNUNET_GETOPT_option_ulong ('p', + "port", + NULL, + gettext_noop ("listen on specified port (default: 7777)"), + &port), + GNUNET_GETOPT_OPTION_END + }; + static const char* page = + "<html><head><title>taler-twister</title>" + "</head><body>cURL fail</body></html>"; + int ret; + + if (GNUNET_OK != + GNUNET_STRINGS_get_utf8_args (argc, argv, + &argc, &argv)) + return 2; + GNUNET_log_setup ("taler-twister", + "WARNING", + NULL); + curl_failure_response + = MHD_create_response_from_buffer (strlen (page), + (void *) page, + MHD_RESPMEM_PERSISTENT); + + ret = + (GNUNET_OK == + GNUNET_PROGRAM_run (argc, argv, + "taler-twister", + _("Taler Man-in-the-Middle HTTP proxy"), + options, + &run, NULL)) ? 0 : 1; + MHD_destroy_response (curl_failure_response); + GNUNET_free_non_null ((char *) argv); + return ret; +} + +/* end of taler-twister.c */