twister

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

commit 8a811ee0dba732be528c3d729318a4a2a9ec2930
parent ee85c023d8eb74bd633efe4d4b6710b7759ea62b
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sat, 20 Jan 2018 14:54:24 +0100

finish response code hacking

Diffstat:
M.gitignore | 9++++-----
Msrc/include/taler_twister_service.h | 2+-
Msrc/twister/Makefile.am | 20++++++++++++++++----
Asrc/twister/taler-twister-service.c | 1460+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/twister/taler-twister.c | 1403-------------------------------------------------------------------------------
Asrc/twister/twister.conf | 23+++++++++++++++++++++++
Asrc/twister/twister.h | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/twister/twister_api.c | 236+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
8 files changed, 1803 insertions(+), 1413 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -1,3 +1,6 @@ +*~ +.deps/ +.libs/ Makefile Makefile.in aclocal.m4 @@ -19,14 +22,10 @@ src/Makefile src/Makefile.in src/include/Makefile src/include/Makefile.in -src/include/platform.h~ src/twister/Makefile src/twister/Makefile.in -src/twister/taler-twister -src/twister/taler-twister.c~ -src/twister/twister.conf~ +src/twister/taler-twister-service stamp-h1 test-driver twister_config.h twister_config.h.in -twister_config.h.in~ diff --git a/src/include/taler_twister_service.h b/src/include/taler_twister_service.h @@ -51,7 +51,7 @@ struct TALER_TWISTER_Handle; * @return handle to use in #TALER_TWISTER_disconnect to disconnect */ struct TALER_TWISTER_Handle * -TALER_TWISTER_connect (const struct TALER_CONFIGURATION_Handle *cfg); +TALER_TWISTER_connect (const struct GNUNET_CONFIGURATION_Handle *cfg); /** diff --git a/src/twister/Makefile.am b/src/twister/Makefile.am @@ -7,13 +7,25 @@ if USE_COVERAGE endif bin_PROGRAMS = \ - taler-twister + taler-twister-service -taler_twister_SOURCES = \ - taler-twister.c -taler_twister_LDADD = \ +taler_twister_service_SOURCES = \ + taler-twister-service.c +taler_twister_service_LDADD = \ $(LIBGCRYPT_LIBS) \ -lmicrohttpd \ -lcurl \ -ljansson \ -lgnunetutil + + +lib_LTLIBRARIES = libtalertwister.la + +libtalertwister_la_SOURCES = \ + twister_api.c twister.h +libtalertwister_la_LIBADD = \ + -lgnunetutil \ + $(XLIB) +libtalertwister_la_LDFLAGS = \ + $(GN_LIB_LDFLAGS) $(WINFLAGS) \ + -version-info 0:0:0 diff --git a/src/twister/taler-twister-service.c b/src/twister/taler-twister-service.c @@ -0,0 +1,1460 @@ +/* + 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> +#include "twister.h" + + +/** + * 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 + + +/** + * 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 ****************** */ + + +/** + * State machine for HTTP requests (per request). + */ +enum RequestState +{ + /** + * Starting state. + */ + REQUEST_STATE_WITH_MHD = 0, + + /** + * We've started receiving upload data from MHD. + */ + REQUEST_STATE_UPLOAD_STARTED, + + /** + * We've finished receiving upload data from MHD. + */ + REQUEST_STATE_UPLOAD_DONE, + + /** + * We've finished uploading data via CURL and can now download. + */ + REQUEST_STATE_DOWNLOAD_STARTED, + + /** + * We've finished receiving download data from cURL. + */ + REQUEST_STATE_DOWNLOAD_DONE + +}; + + +/** + * 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 HttpRequest +{ + + /** + * Client socket read task + */ + struct GNUNET_SCHEDULER_Task * rtask; + + /** + * Client socket write task + */ + struct GNUNET_SCHEDULER_Task * wtask; + + /** + * 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; + + /** + * HTTP response code to give to MHD for the response. + */ + unsigned int response_code; + + /** + * Number of bytes already in the IO buffer. + */ + size_t io_len; + + /** + * Headers from response + */ + struct HttpResponseHeader *header_head; + + /** + * Headers from response + */ + struct HttpResponseHeader *header_tail; + + /** + * Request processing state machine. + */ + enum RequestState state; + +}; + + + +/* *********************** Globals **************************** */ + + +/** + * 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 *mhd_daemon; + +/** + * The task ID + */ +static struct GNUNET_SCHEDULER_Task *httpd_task; + +/** + * Response we return on cURL failures. + */ +static struct MHD_Response *curl_failure_response; + +/** + * Our configuration. + */ +static const struct GNUNET_CONFIGURATION_Handle *cfg; + +/** + * Destination to which HTTP server we forward requests to. + * Of the format "http://servername:PORT" + */ +static char *target_server_base_url; + +/* ************************** Transformations ***************** */ + +/** + * Set to non-zero if we should change the next response code. + * In this case, this is the value we should use. + */ +static unsigned int hack_response_code; + + + +/* ************************* Global helpers ********************* */ + + +/** + * Run MHD now, we have extra data ready for the callback. + */ +static void +run_mhd_now (void); + + +/* ************************* 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 HttpRequest` + * @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 HttpRequest *hr = cls; + size_t bytes_to_copy; + + if ( (REQUEST_STATE_UPLOAD_STARTED == hr->state) || + (REQUEST_STATE_UPLOAD_DONE == hr->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, + hr->io_len); + if ( (0 == bytes_to_copy) && + (REQUEST_STATE_DOWNLOAD_DONE != hr->state) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Pausing MHD download, no data available\n"); + if (NULL != hr->curl) + { + curl_easy_pause (hr->curl, CURLPAUSE_CONT); + curl_download_prepare (); + } + return 0; /* more data later */ + } + if ( (0 == bytes_to_copy) && + (REQUEST_STATE_DOWNLOAD_DONE == hr->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", + (unsigned long) bytes_to_copy, + (unsigned long) hr->io_len); + GNUNET_memcpy (buf, hr->io_buf, bytes_to_copy); + memmove (hr->io_buf, + &hr->io_buf[bytes_to_copy], + hr->io_len - bytes_to_copy); + hr->io_len -= bytes_to_copy; + if (NULL != hr->curl) + curl_easy_pause (hr->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 HttpRequest *` + * @return size of processed bytes + */ +static size_t +curl_check_hdr (void *buffer, size_t size, size_t nmemb, void *cls) +{ + struct HttpRequest *hr = cls; + struct HttpResponseHeader *header; + size_t bytes = size * nmemb; + char *ndup; + const char *hdr_type; + char *hdr_val; + char *tok; + + 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 + char *new_location; + char *new_cookie_hdr; + + new_cookie_hdr = NULL; + if ( (NULL != hr->leho) && + (0 == strcasecmp (hdr_type, + MHD_HTTP_HEADER_SET_COOKIE)) ) + + { + new_cookie_hdr = GNUNET_malloc (strlen (hdr_val) + + strlen (hr->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 (hr->leho)) + { + delta_cdomain = strlen (hr->leho) - strlen (cookie_domain); + if (0 == strcasecmp (cookie_domain, hr->leho + delta_cdomain)) + { + offset += sprintf (new_cookie_hdr + offset, + " domain=%s;", + hr->domain); + continue; + } + } + else if (0 == strcmp (cookie_domain, hr->leho)) + { + offset += sprintf (new_cookie_hdr + offset, + " domain=%s;", + hr->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 != hr->port) + ? "http://%s" + : "https://%s", + hr->leho); + if (0 == strncmp (leho_host, + hdr_val, + strlen (leho_host))) + { + GNUNET_asprintf (&new_location, + "%s%s%s", + (HTTPS_PORT != hr->port) + ? "http://" + : "https://", + hr->domain, + hdr_val + strlen (leho_host)); + hdr_val = new_location; + } + GNUNET_free (leho_host); + } + GNUNET_free_non_null (new_location); + GNUNET_free_non_null (new_cookie_hdr); +#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 (hr->header_head, + hr->header_tail, + header); + } + GNUNET_free (ndup); + return bytes; +} + + +static int +create_mhd_response_from_hr (struct HttpRequest *hr) +{ + long resp_code; + double content_length; + struct HttpResponseHeader *header; + + if (NULL != hr->response) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Response already set!\n"); + return GNUNET_SYSERR; + } + + GNUNET_break (CURLE_OK == + curl_easy_getinfo (hr->curl, + CURLINFO_RESPONSE_CODE, + &resp_code)); + GNUNET_break (CURLE_OK == + curl_easy_getinfo (hr->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); + hr->response_code = resp_code; + hr->response = MHD_create_response_from_callback ((-1 == content_length) ? MHD_SIZE_UNKNOWN : content_length, + IO_BUFFERSIZE, + &mhd_content_cb, + hr, + NULL); + for (header = hr->header_head; NULL != header; header = header->next) + { + GNUNET_break (MHD_YES == + MHD_add_response_header (hr->response, + header->type, + header->value)); + + } +#if 0 + if (NULL != hr->leho) + { + char *cors_hdr; + + GNUNET_asprintf (&cors_hdr, + (HTTPS_PORT == hr->port) + ? "https://%s" + : "http://%s", + hr->leho); + + GNUNET_break (MHD_YES == + MHD_add_response_header (hr->response, + MHD_HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN, + cors_hdr)); + GNUNET_free (cors_hdr); + } +#endif + /* 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 (hr->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 HttpRequest *` + * @return number of bytes handled + */ +static size_t +curl_download_cb (void *ptr, size_t size, size_t nmemb, void* ctx) +{ + struct HttpRequest *hr = ctx; + size_t total = size * nmemb; + + if (NULL == hr->response) + GNUNET_assert (GNUNET_OK == create_mhd_response_from_hr (hr)); + + if ( (REQUEST_STATE_UPLOAD_STARTED == hr->state) || + (REQUEST_STATE_UPLOAD_DONE == hr->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 (hr->io_buf) - hr->io_len < total) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Pausing CURL download, not enough space %lu %lu %lu\n", + (unsigned long) sizeof (hr->io_buf), + (unsigned long) hr->io_len, + (unsigned long) total); + return CURL_WRITEFUNC_PAUSE; /* not enough space */ + } + GNUNET_memcpy (&hr->io_buf[hr->io_len], + ptr, + total); + hr->io_len += total; + if (hr->io_len == total) + run_mhd_now (); + 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 HttpRequest` 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 HttpRequest *hr = cls; + size_t len = size * nmemb; + size_t to_copy; + + if ( (0 == hr->io_len) && + (REQUEST_STATE_UPLOAD_DONE != hr->state) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Pausing CURL UPLOAD, need more data\n"); + return CURL_READFUNC_PAUSE; + } + if ( (0 == hr->io_len) && + (REQUEST_STATE_UPLOAD_DONE == hr->state) ) + { + hr->state = REQUEST_STATE_DOWNLOAD_STARTED; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Completed CURL UPLOAD\n"); + return 0; /* upload finished, can now download */ + } + if ( (REQUEST_STATE_UPLOAD_STARTED != hr->state) && + (REQUEST_STATE_UPLOAD_DONE != hr->state) ) + { + GNUNET_break (0); + return CURL_READFUNC_ABORT; + } + to_copy = GNUNET_MIN (hr->io_len, + len); + GNUNET_memcpy (buf, hr->io_buf, to_copy); + memmove (hr->io_buf, + &hr->io_buf[to_copy], + hr->io_len - to_copy); + hr->io_len -= to_copy; + if (hr->io_len + to_copy == sizeof (hr->io_buf)) + run_mhd_now (); /* 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 HttpRequest *hr; + + 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 **) &hr )); + if (NULL == hr) + { + 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 == hr->response) + GNUNET_assert (GNUNET_OK == create_mhd_response_from_hr (hr)); + hr->state = REQUEST_STATE_DOWNLOAD_DONE; + run_mhd_now (); + 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? */ + hr->state = REQUEST_STATE_DOWNLOAD_DONE; + run_mhd_now (); + break; + } + if (NULL == hr->response) + hr->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, + * substitutions may be applied. + * + * @param cls our `struct HttpRequest` + * @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 HttpRequest *hr = cls; + char *hdr; + +#if 0 + if ( (0 == strcasecmp (MHD_HTTP_HEADER_HOST, key)) && + (NULL != hr->leho) ) + value = hr->leho; +#endif + GNUNET_asprintf (&hdr, + "%s: %s", + key, + value); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Adding HEADER `%s' to HTTP request\n", + hdr); + hr->headers = curl_slist_append (hr->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 HttpRequest *hr = *con_cls; + char *curlurl; + size_t left; + + if (NULL == hr) + { + GNUNET_break (0); + return MHD_NO; + } + //Fresh connection. + if (REQUEST_STATE_WITH_MHD == hr->state) + { + if (NULL == hr->curl) + hr->curl = curl_easy_init (); + if (NULL == hr->curl) + return MHD_queue_response (con, + MHD_HTTP_INTERNAL_SERVER_ERROR, + curl_failure_response); + curl_easy_setopt (hr->curl, + CURLOPT_HEADERFUNCTION, + &curl_check_hdr); + curl_easy_setopt (hr->curl, CURLOPT_HEADERDATA, hr); + curl_easy_setopt (hr->curl, CURLOPT_FOLLOWLOCATION, 0); + curl_easy_setopt (hr->curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); + curl_easy_setopt (hr->curl, CURLOPT_CONNECTTIMEOUT, 600L); + curl_easy_setopt (hr->curl, CURLOPT_TIMEOUT, 600L); + curl_easy_setopt (hr->curl, CURLOPT_NOSIGNAL, 1L); + curl_easy_setopt (hr->curl, CURLOPT_HTTP_CONTENT_DECODING, 0); + curl_easy_setopt (hr->curl, CURLOPT_HTTP_TRANSFER_DECODING, 0); + curl_easy_setopt (hr->curl, CURLOPT_NOSIGNAL, 1L); + curl_easy_setopt (hr->curl, CURLOPT_PRIVATE, hr); + curl_easy_setopt (hr->curl, CURLOPT_VERBOSE, 0); + GNUNET_asprintf (&curlurl, + "%s%s", + target_server_base_url, + hr->url); + curl_easy_setopt (hr->curl, + CURLOPT_URL, + curlurl); + GNUNET_free (curlurl); + if (0 == strcasecmp (meth, MHD_HTTP_METHOD_PUT)) + { + hr->state = REQUEST_STATE_UPLOAD_STARTED; + curl_easy_setopt (hr->curl, CURLOPT_UPLOAD, 1); + curl_easy_setopt (hr->curl, + CURLOPT_WRITEFUNCTION, + &curl_download_cb); + curl_easy_setopt (hr->curl, CURLOPT_WRITEDATA, hr); + curl_easy_setopt (hr->curl, + CURLOPT_READFUNCTION, + &curl_upload_cb); + curl_easy_setopt (hr->curl, CURLOPT_READDATA, hr); + } + else if (0 == strcasecmp (meth, MHD_HTTP_METHOD_POST)) + { + hr->state = REQUEST_STATE_UPLOAD_STARTED; + curl_easy_setopt (hr->curl, CURLOPT_POST, 1L); + curl_easy_setopt (hr->curl, + CURLOPT_WRITEFUNCTION, + &curl_download_cb); + curl_easy_setopt (hr->curl, CURLOPT_WRITEDATA, hr); + curl_easy_setopt (hr->curl, + CURLOPT_READFUNCTION, + &curl_upload_cb); + curl_easy_setopt (hr->curl, CURLOPT_READDATA, hr); + } + else if (0 == strcasecmp (meth, MHD_HTTP_METHOD_HEAD)) + { + hr->state = REQUEST_STATE_DOWNLOAD_STARTED; + curl_easy_setopt (hr->curl, CURLOPT_NOBODY, 1); + } + else if (0 == strcasecmp (meth, MHD_HTTP_METHOD_OPTIONS)) + { + hr->state = REQUEST_STATE_DOWNLOAD_STARTED; + curl_easy_setopt (hr->curl, CURLOPT_CUSTOMREQUEST, "OPTIONS"); + } + else if (0 == strcasecmp (meth, MHD_HTTP_METHOD_GET)) + { + hr->state = REQUEST_STATE_DOWNLOAD_STARTED; + curl_easy_setopt (hr->curl, CURLOPT_HTTPGET, 1); + curl_easy_setopt (hr->curl, + CURLOPT_WRITEFUNCTION, + &curl_download_cb); + curl_easy_setopt (hr->curl, CURLOPT_WRITEDATA, hr); + } + else + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + _("Unsupported HTTP method `%s'\n"), + meth); + curl_easy_cleanup (hr->curl); + hr->curl = NULL; + return MHD_NO; + } + + if (0 == strcasecmp (ver, MHD_HTTP_VERSION_1_0)) + { + curl_easy_setopt (hr->curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); + } + else if (0 == strcasecmp (ver, MHD_HTTP_VERSION_1_1)) + { + curl_easy_setopt (hr->curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); + } + else + { + curl_easy_setopt (hr->curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_NONE); + } + + if (CURLM_OK != curl_multi_add_handle (curl_multi, hr->curl)) + { + GNUNET_break (0); + curl_easy_cleanup (hr->curl); + hr->curl = NULL; + return MHD_NO; + } + MHD_get_connection_values (con, + MHD_HEADER_KIND, + &con_val_iter, + hr); + curl_easy_setopt (hr->curl, + CURLOPT_HTTPHEADER, + hr->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 (hr->curl, CURLOPT_POSTFIELDSIZE, *upload_data_size); + + left = GNUNET_MIN (*upload_data_size, + sizeof (hr->io_buf) - hr->io_len); + GNUNET_memcpy (&hr->io_buf[hr->io_len], + upload_data, + left); + hr->io_len += left; + *upload_data_size -= left; + GNUNET_assert (NULL != hr->curl); + curl_easy_pause (hr->curl, CURLPAUSE_CONT); + return MHD_YES; + } + if (REQUEST_STATE_UPLOAD_STARTED == hr->state) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Finished processing UPLOAD\n"); + hr->state = REQUEST_STATE_UPLOAD_DONE; + } + if (NULL == hr->response) + return MHD_YES; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Queueing response with MHD\n"); + run_mhd_now (); + if (0 != hack_response_code) + { + hr->response_code = hack_response_code; + hack_response_code = 0; /* reset for next request */ + } + return MHD_queue_response (con, + hr->response_code, + hr->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 HttpRequest *` + * @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 HttpRequest *hr = *con_cls; + struct HttpResponseHeader *header; + + if (NULL == hr) + return; + if (MHD_REQUEST_TERMINATED_COMPLETED_OK != toe) + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "MHD encountered error handling request: %d\n", + toe); + if (NULL != hr->curl) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Resetting cURL handle\n"); + curl_multi_remove_handle (curl_multi, + hr->curl); + curl_slist_free_all (hr->headers); + hr->headers = NULL; + curl_easy_reset (hr->curl); + hr->io_len = 0; + } + if ( (NULL != hr->response) && + (curl_failure_response != hr->response) ) + MHD_destroy_response (hr->response); + for (header = hr->header_head; header != NULL; header = hr->header_head) + { + GNUNET_CONTAINER_DLL_remove (hr->header_head, + hr->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", + hr->url); + GNUNET_free (hr->url); + GNUNET_free (hr); + *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 HttpRequest` 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 HttpRequest` that this @a connection is for + */ +static void * +mhd_log_callback (void *cls, + const char *url, + struct MHD_Connection *connection) +{ + struct HttpRequest *hr; + 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; + } + + hr = GNUNET_new (struct HttpRequest); + hr->url = GNUNET_strdup (url); + return hr; +} + + +/** + * Kill the MHD daemon. + */ +static void +kill_httpd (void) +{ + MHD_stop_daemon (mhd_daemon); + mhd_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 (mhd_daemon, + &rs, + &ws, + &es, + &max)) + { + kill_httpd (); + return; + } + haveto = MHD_get_timeout (mhd_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 (mhd_daemon); + schedule_httpd (); +} + + +/** + * 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); +} + + +/* ******************* 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 c configuration + * @param service the initialized service +*/ +static void +run (void *cls, + const struct GNUNET_CONFIGURATION_Handle *c, + struct GNUNET_SERVICE_Handle *service) +{ + unsigned long long port; + + (void) cls; + (void) service; + 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; + } + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (c, + "twister", + "DESTINATION_BASE_URL", + &target_server_base_url)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "twister", + "DESTINATION_BASE_URL"); + GNUNET_SCHEDULER_shutdown (); + return; + } + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (c, + "twister", + "DESTINATION_BASE_URL", + &target_server_base_url)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "twister", + "DESTINATION_BASE_URL"); + GNUNET_SCHEDULER_shutdown (); + return; + } + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_number (c, + "twister", + "HTTP_PORT", + &port)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "twister", + "HTTP_PORT"); + GNUNET_SCHEDULER_shutdown (); + return; + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Proxy listens on port %llu\n", + port); + + /* start MHD daemon for HTTP */ + mhd_daemon = MHD_start_daemon (MHD_USE_DEBUG, + (uint16_t) 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 == mhd_daemon) + { + GNUNET_break (0); + GNUNET_SCHEDULER_shutdown (); + return; + } + GNUNET_SCHEDULER_add_shutdown (&do_shutdown, + NULL); + run_mhd_now (); +} + + + +/** + * Callback called when a client connects to the service. + * + * @param cls closure for the service + * @param c the new client that connected to the service + * @param mq the message queue used to send messages to the client + * @return @a c + */ +static void * +client_connect_cb (void *cls, + struct GNUNET_SERVICE_Client *c, + struct GNUNET_MQ_Handle *mq) +{ + return c; +} + + +/** + * Callback called when a client disconnected from the service + * + * @param cls closure for the service + * @param c the client that disconnected + * @param internal_cls should be equal to @a c + */ +static void +client_disconnect_cb (void *cls, + struct GNUNET_SERVICE_Client *c, + void *internal_cls) +{ + /* intentionally empty */ +} + + +/** + * Comment! + */ +static void +send_acknowledgement (struct GNUNET_SERVICE_Client *c) +{ + struct GNUNET_MQ_Envelope *env; + struct GNUNET_MessageHeader *hdr; + + env = GNUNET_MQ_msg (hdr, + TWISTER_MESSAGE_TYPE_ACKNOWLEDGEMENT); + GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (c), + env); + GNUNET_SERVICE_client_continue (c); +} + + +/** + * Control handler for changing the response code + * + * @param cls message queue for sending replies + * @param src received message + */ +static void +handle_set_response_code (void *cls, + const struct TWISTER_SetResponseCode *src) +{ + struct GNUNET_SERVICE_Client *c = cls; + + hack_response_code = ntohl (src->response_code); + send_acknowledgement (c); +} + + +/** + * Define "main" method using service macro. + */ +GNUNET_SERVICE_MAIN +("twister", + GNUNET_SERVICE_OPTION_NONE, + &run, + &client_connect_cb, + &client_disconnect_cb, + NULL, + GNUNET_MQ_hd_fixed_size (set_response_code, + TWISTER_MESSAGE_TYPE_SET_RESPONSE_CODE, + struct TWISTER_SetResponseCode, + NULL), + GNUNET_MQ_handler_end ()); + + +/* end of taler-twister.c */ diff --git a/src/twister/taler-twister.c b/src/twister/taler-twister.c @@ -1,1403 +0,0 @@ -/* - 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> - - -/** - * 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 - - -/** - * 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 ****************** */ - - -/** - * State machine for HTTP requests (per request). - */ -enum RequestState -{ - /** - * Starting state. - */ - REQUEST_STATE_WITH_MHD = 0, - - /** - * We've started receiving upload data from MHD. - */ - REQUEST_STATE_UPLOAD_STARTED, - - /** - * We've finished receiving upload data from MHD. - */ - REQUEST_STATE_UPLOAD_DONE, - - /** - * We've finished uploading data via CURL and can now download. - */ - REQUEST_STATE_DOWNLOAD_STARTED, - - /** - * We've finished receiving download data from cURL. - */ - REQUEST_STATE_DOWNLOAD_DONE - -}; - - -/** - * 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 HttpRequest -{ - - /** - * Client socket read task - */ - struct GNUNET_SCHEDULER_Task * rtask; - - /** - * Client socket write task - */ - struct GNUNET_SCHEDULER_Task * wtask; - - /** - * 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; - - /** - * HTTP response code to give to MHD for the response. - */ - unsigned int response_code; - - /** - * Number of bytes already in the IO buffer. - */ - size_t io_len; - - /** - * Headers from response - */ - struct HttpResponseHeader *header_head; - - /** - * Headers from response - */ - struct HttpResponseHeader *header_tail; - - /** - * Request processing state machine. - */ - enum RequestState state; - -}; - - - -/* *********************** Globals **************************** */ - - -/** - * 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 *mhd_daemon; - -/** - * The task ID - */ -static struct GNUNET_SCHEDULER_Task *httpd_task; - -/** - * Response we return on cURL failures. - */ -static struct MHD_Response *curl_failure_response; - -/** - * Our configuration. - */ -static const struct GNUNET_CONFIGURATION_Handle *cfg; - -/** - * Destination to which HTTP server we forward requests to. - * Of the format "http://servername:PORT" - */ -static char *target_server_base_url; - - -/* ************************* Global helpers ********************* */ - - -/** - * Run MHD now, we have extra data ready for the callback. - */ -static void -run_mhd_now (void); - - -/* ************************* 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 HttpRequest` - * @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 HttpRequest *hr = cls; - size_t bytes_to_copy; - - if ( (REQUEST_STATE_UPLOAD_STARTED == hr->state) || - (REQUEST_STATE_UPLOAD_DONE == hr->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, - hr->io_len); - if ( (0 == bytes_to_copy) && - (REQUEST_STATE_DOWNLOAD_DONE != hr->state) ) - { - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Pausing MHD download, no data available\n"); - if (NULL != hr->curl) - { - curl_easy_pause (hr->curl, CURLPAUSE_CONT); - curl_download_prepare (); - } - return 0; /* more data later */ - } - if ( (0 == bytes_to_copy) && - (REQUEST_STATE_DOWNLOAD_DONE == hr->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", - (unsigned long) bytes_to_copy, - (unsigned long) hr->io_len); - GNUNET_memcpy (buf, hr->io_buf, bytes_to_copy); - memmove (hr->io_buf, - &hr->io_buf[bytes_to_copy], - hr->io_len - bytes_to_copy); - hr->io_len -= bytes_to_copy; - if (NULL != hr->curl) - curl_easy_pause (hr->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 HttpRequest *` - * @return size of processed bytes - */ -static size_t -curl_check_hdr (void *buffer, size_t size, size_t nmemb, void *cls) -{ - struct HttpRequest *hr = cls; - struct HttpResponseHeader *header; - size_t bytes = size * nmemb; - char *ndup; - const char *hdr_type; - char *hdr_val; - char *tok; - - 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 - char *new_location; - char *new_cookie_hdr; - - new_cookie_hdr = NULL; - if ( (NULL != hr->leho) && - (0 == strcasecmp (hdr_type, - MHD_HTTP_HEADER_SET_COOKIE)) ) - - { - new_cookie_hdr = GNUNET_malloc (strlen (hdr_val) + - strlen (hr->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 (hr->leho)) - { - delta_cdomain = strlen (hr->leho) - strlen (cookie_domain); - if (0 == strcasecmp (cookie_domain, hr->leho + delta_cdomain)) - { - offset += sprintf (new_cookie_hdr + offset, - " domain=%s;", - hr->domain); - continue; - } - } - else if (0 == strcmp (cookie_domain, hr->leho)) - { - offset += sprintf (new_cookie_hdr + offset, - " domain=%s;", - hr->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 != hr->port) - ? "http://%s" - : "https://%s", - hr->leho); - if (0 == strncmp (leho_host, - hdr_val, - strlen (leho_host))) - { - GNUNET_asprintf (&new_location, - "%s%s%s", - (HTTPS_PORT != hr->port) - ? "http://" - : "https://", - hr->domain, - hdr_val + strlen (leho_host)); - hdr_val = new_location; - } - GNUNET_free (leho_host); - } - GNUNET_free_non_null (new_location); - GNUNET_free_non_null (new_cookie_hdr); -#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 (hr->header_head, - hr->header_tail, - header); - } - GNUNET_free (ndup); - return bytes; -} - - -static int -create_mhd_response_from_hr (struct HttpRequest *hr) -{ - long resp_code; - double content_length; - struct HttpResponseHeader *header; - - if (NULL != hr->response) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Response already set!\n"); - return GNUNET_SYSERR; - } - - GNUNET_break (CURLE_OK == - curl_easy_getinfo (hr->curl, - CURLINFO_RESPONSE_CODE, - &resp_code)); - GNUNET_break (CURLE_OK == - curl_easy_getinfo (hr->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); - hr->response_code = resp_code; - hr->response = MHD_create_response_from_callback ((-1 == content_length) ? MHD_SIZE_UNKNOWN : content_length, - IO_BUFFERSIZE, - &mhd_content_cb, - hr, - NULL); - for (header = hr->header_head; NULL != header; header = header->next) - { - GNUNET_break (MHD_YES == - MHD_add_response_header (hr->response, - header->type, - header->value)); - - } -#if 0 - if (NULL != hr->leho) - { - char *cors_hdr; - - GNUNET_asprintf (&cors_hdr, - (HTTPS_PORT == hr->port) - ? "https://%s" - : "http://%s", - hr->leho); - - GNUNET_break (MHD_YES == - MHD_add_response_header (hr->response, - MHD_HTTP_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN, - cors_hdr)); - GNUNET_free (cors_hdr); - } -#endif - /* 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 (hr->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 HttpRequest *` - * @return number of bytes handled - */ -static size_t -curl_download_cb (void *ptr, size_t size, size_t nmemb, void* ctx) -{ - struct HttpRequest *hr = ctx; - size_t total = size * nmemb; - - if (NULL == hr->response) - GNUNET_assert (GNUNET_OK == create_mhd_response_from_hr (hr)); - - if ( (REQUEST_STATE_UPLOAD_STARTED == hr->state) || - (REQUEST_STATE_UPLOAD_DONE == hr->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 (hr->io_buf) - hr->io_len < total) - { - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Pausing CURL download, not enough space %lu %lu %lu\n", - (unsigned long) sizeof (hr->io_buf), - (unsigned long) hr->io_len, - (unsigned long) total); - return CURL_WRITEFUNC_PAUSE; /* not enough space */ - } - GNUNET_memcpy (&hr->io_buf[hr->io_len], - ptr, - total); - hr->io_len += total; - if (hr->io_len == total) - run_mhd_now (); - 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 HttpRequest` 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 HttpRequest *hr = cls; - size_t len = size * nmemb; - size_t to_copy; - - if ( (0 == hr->io_len) && - (REQUEST_STATE_UPLOAD_DONE != hr->state) ) - { - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Pausing CURL UPLOAD, need more data\n"); - return CURL_READFUNC_PAUSE; - } - if ( (0 == hr->io_len) && - (REQUEST_STATE_UPLOAD_DONE == hr->state) ) - { - hr->state = REQUEST_STATE_DOWNLOAD_STARTED; - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Completed CURL UPLOAD\n"); - return 0; /* upload finished, can now download */ - } - if ( (REQUEST_STATE_UPLOAD_STARTED != hr->state) && - (REQUEST_STATE_UPLOAD_DONE != hr->state) ) - { - GNUNET_break (0); - return CURL_READFUNC_ABORT; - } - to_copy = GNUNET_MIN (hr->io_len, - len); - GNUNET_memcpy (buf, hr->io_buf, to_copy); - memmove (hr->io_buf, - &hr->io_buf[to_copy], - hr->io_len - to_copy); - hr->io_len -= to_copy; - if (hr->io_len + to_copy == sizeof (hr->io_buf)) - run_mhd_now (); /* 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 HttpRequest *hr; - - 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 **) &hr )); - if (NULL == hr) - { - 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 == hr->response) - GNUNET_assert (GNUNET_OK == create_mhd_response_from_hr (hr)); - hr->state = REQUEST_STATE_DOWNLOAD_DONE; - run_mhd_now (); - 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? */ - hr->state = REQUEST_STATE_DOWNLOAD_DONE; - run_mhd_now (); - break; - } - if (NULL == hr->response) - hr->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, - * substitutions may be applied. - * - * @param cls our `struct HttpRequest` - * @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 HttpRequest *hr = cls; - char *hdr; - -#if 0 - if ( (0 == strcasecmp (MHD_HTTP_HEADER_HOST, key)) && - (NULL != hr->leho) ) - value = hr->leho; -#endif - GNUNET_asprintf (&hdr, - "%s: %s", - key, - value); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Adding HEADER `%s' to HTTP request\n", - hdr); - hr->headers = curl_slist_append (hr->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 HttpRequest *hr = *con_cls; - char *curlurl; - size_t left; - - if (NULL == hr) - { - GNUNET_break (0); - return MHD_NO; - } - //Fresh connection. - if (REQUEST_STATE_WITH_MHD == hr->state) - { - if (NULL == hr->curl) - hr->curl = curl_easy_init (); - if (NULL == hr->curl) - return MHD_queue_response (con, - MHD_HTTP_INTERNAL_SERVER_ERROR, - curl_failure_response); - curl_easy_setopt (hr->curl, - CURLOPT_HEADERFUNCTION, - &curl_check_hdr); - curl_easy_setopt (hr->curl, CURLOPT_HEADERDATA, hr); - curl_easy_setopt (hr->curl, CURLOPT_FOLLOWLOCATION, 0); - curl_easy_setopt (hr->curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); - curl_easy_setopt (hr->curl, CURLOPT_CONNECTTIMEOUT, 600L); - curl_easy_setopt (hr->curl, CURLOPT_TIMEOUT, 600L); - curl_easy_setopt (hr->curl, CURLOPT_NOSIGNAL, 1L); - curl_easy_setopt (hr->curl, CURLOPT_HTTP_CONTENT_DECODING, 0); - curl_easy_setopt (hr->curl, CURLOPT_HTTP_TRANSFER_DECODING, 0); - curl_easy_setopt (hr->curl, CURLOPT_NOSIGNAL, 1L); - curl_easy_setopt (hr->curl, CURLOPT_PRIVATE, hr); - curl_easy_setopt (hr->curl, CURLOPT_VERBOSE, 0); - GNUNET_asprintf (&curlurl, - "%s%s", - target_server_base_url, - hr->url); - curl_easy_setopt (hr->curl, - CURLOPT_URL, - curlurl); - GNUNET_free (curlurl); - if (0 == strcasecmp (meth, MHD_HTTP_METHOD_PUT)) - { - hr->state = REQUEST_STATE_UPLOAD_STARTED; - curl_easy_setopt (hr->curl, CURLOPT_UPLOAD, 1); - curl_easy_setopt (hr->curl, - CURLOPT_WRITEFUNCTION, - &curl_download_cb); - curl_easy_setopt (hr->curl, CURLOPT_WRITEDATA, hr); - curl_easy_setopt (hr->curl, - CURLOPT_READFUNCTION, - &curl_upload_cb); - curl_easy_setopt (hr->curl, CURLOPT_READDATA, hr); - } - else if (0 == strcasecmp (meth, MHD_HTTP_METHOD_POST)) - { - hr->state = REQUEST_STATE_UPLOAD_STARTED; - curl_easy_setopt (hr->curl, CURLOPT_POST, 1L); - curl_easy_setopt (hr->curl, - CURLOPT_WRITEFUNCTION, - &curl_download_cb); - curl_easy_setopt (hr->curl, CURLOPT_WRITEDATA, hr); - curl_easy_setopt (hr->curl, - CURLOPT_READFUNCTION, - &curl_upload_cb); - curl_easy_setopt (hr->curl, CURLOPT_READDATA, hr); - } - else if (0 == strcasecmp (meth, MHD_HTTP_METHOD_HEAD)) - { - hr->state = REQUEST_STATE_DOWNLOAD_STARTED; - curl_easy_setopt (hr->curl, CURLOPT_NOBODY, 1); - } - else if (0 == strcasecmp (meth, MHD_HTTP_METHOD_OPTIONS)) - { - hr->state = REQUEST_STATE_DOWNLOAD_STARTED; - curl_easy_setopt (hr->curl, CURLOPT_CUSTOMREQUEST, "OPTIONS"); - } - else if (0 == strcasecmp (meth, MHD_HTTP_METHOD_GET)) - { - hr->state = REQUEST_STATE_DOWNLOAD_STARTED; - curl_easy_setopt (hr->curl, CURLOPT_HTTPGET, 1); - curl_easy_setopt (hr->curl, - CURLOPT_WRITEFUNCTION, - &curl_download_cb); - curl_easy_setopt (hr->curl, CURLOPT_WRITEDATA, hr); - } - else - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - _("Unsupported HTTP method `%s'\n"), - meth); - curl_easy_cleanup (hr->curl); - hr->curl = NULL; - return MHD_NO; - } - - if (0 == strcasecmp (ver, MHD_HTTP_VERSION_1_0)) - { - curl_easy_setopt (hr->curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); - } - else if (0 == strcasecmp (ver, MHD_HTTP_VERSION_1_1)) - { - curl_easy_setopt (hr->curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); - } - else - { - curl_easy_setopt (hr->curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_NONE); - } - - if (CURLM_OK != curl_multi_add_handle (curl_multi, hr->curl)) - { - GNUNET_break (0); - curl_easy_cleanup (hr->curl); - hr->curl = NULL; - return MHD_NO; - } - MHD_get_connection_values (con, - MHD_HEADER_KIND, - &con_val_iter, - hr); - curl_easy_setopt (hr->curl, - CURLOPT_HTTPHEADER, - hr->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 (hr->curl, CURLOPT_POSTFIELDSIZE, *upload_data_size); - - left = GNUNET_MIN (*upload_data_size, - sizeof (hr->io_buf) - hr->io_len); - GNUNET_memcpy (&hr->io_buf[hr->io_len], - upload_data, - left); - hr->io_len += left; - *upload_data_size -= left; - GNUNET_assert (NULL != hr->curl); - curl_easy_pause (hr->curl, CURLPAUSE_CONT); - return MHD_YES; - } - if (REQUEST_STATE_UPLOAD_STARTED == hr->state) - { - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Finished processing UPLOAD\n"); - hr->state = REQUEST_STATE_UPLOAD_DONE; - } - if (NULL == hr->response) - return MHD_YES; - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Queueing response with MHD\n"); - run_mhd_now (); - return MHD_queue_response (con, - hr->response_code, - hr->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 HttpRequest *` - * @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 HttpRequest *hr = *con_cls; - struct HttpResponseHeader *header; - - if (NULL == hr) - return; - if (MHD_REQUEST_TERMINATED_COMPLETED_OK != toe) - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "MHD encountered error handling request: %d\n", - toe); - if (NULL != hr->curl) - { - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Resetting cURL handle\n"); - curl_multi_remove_handle (curl_multi, - hr->curl); - curl_slist_free_all (hr->headers); - hr->headers = NULL; - curl_easy_reset (hr->curl); - hr->io_len = 0; - } - if ( (NULL != hr->response) && - (curl_failure_response != hr->response) ) - MHD_destroy_response (hr->response); - for (header = hr->header_head; header != NULL; header = hr->header_head) - { - GNUNET_CONTAINER_DLL_remove (hr->header_head, - hr->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", - hr->url); - GNUNET_free (hr->url); - GNUNET_free (hr); - *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 HttpRequest` 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 HttpRequest` that this @a connection is for - */ -static void * -mhd_log_callback (void *cls, - const char *url, - struct MHD_Connection *connection) -{ - struct HttpRequest *hr; - 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; - } - - hr = GNUNET_new (struct HttpRequest); - hr->url = GNUNET_strdup (url); - return hr; -} - - -/** - * Kill the MHD daemon. - */ -static void -kill_httpd (void) -{ - MHD_stop_daemon (mhd_daemon); - mhd_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 (mhd_daemon, - &rs, - &ws, - &es, - &max)) - { - kill_httpd (); - return; - } - haveto = MHD_get_timeout (mhd_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 (mhd_daemon); - schedule_httpd (); -} - - -/** - * 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); -} - - -/* ******************* 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) -{ - unsigned long long port; - - - 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; - } - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (c, - "twister", - "DESTINATION_BASE_URL", - &target_server_base_url)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "twister", - "DESTINATION_BASE_URL"); - GNUNET_SCHEDULER_shutdown (); - return; - } - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (c, - "twister", - "DESTINATION_BASE_URL", - &target_server_base_url)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "twister", - "DESTINATION_BASE_URL"); - GNUNET_SCHEDULER_shutdown (); - return; - } - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_number (c, - "twister", - "HTTP_PORT", - &port)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "twister", - "HTTP_PORT"); - GNUNET_SCHEDULER_shutdown (); - return; - } - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Proxy listens on port %llu\n", - port); - - /* start MHD daemon for HTTP */ - mhd_daemon = MHD_start_daemon (MHD_USE_DEBUG, - (uint16_t) 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 == mhd_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_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 */ diff --git a/src/twister/twister.conf b/src/twister/twister.conf @@ -0,0 +1,23 @@ +[twister] +# HTTP listen port for twister +HTTP_PORT = 8888 + +# HTTP Destination for twister +# Note: no trailing '/'! +DESTINATION_BASE_URL = http://localhost:8080 + +# Control port for TCP +# PORT = 8889 +HOSTNAME = localhost +ACCEPT_FROM = 127.0.0.1; +ACCEPT_FROM6 = ::1; + +# Control port for UNIX +UNIXPATH = $TALER_RUNTIME_DIR/taler-service-twister.sock +UNIX_MATCH_UID = NO +UNIX_MATCH_GID = YES + +# Launching of twister by ARM +# BINARY = taler-service-twister +# AUTOSTART = NO +# FORCESTART = NO diff --git a/src/twister/twister.h b/src/twister/twister.h @@ -0,0 +1,63 @@ +/* + This file is part of GNUnet. + Copyright (C) 2018 Taler Systems SA + + Twister 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. + + Twister 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 Twister; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +/** + * @author + * @file twister.h + * + * @brief Common type definitions + */ +#ifndef TWISTER_H +#define TWISTER_H + +#include <gnunet/gnunet_common.h> + + +#define TWISTER_MESSAGE_TYPE_ACKNOWLEDGEMENT 1 + +#define TWISTER_MESSAGE_TYPE_SET_RESPONSE_CODE 2 + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * Network size estimate sent from the service + * to clients. Contains the current size estimate + * (or 0 if none has been calculated) and the + * standard deviation of known estimates. + * + */ +struct TWISTER_SetResponseCode +{ + /** + * Type: #TWISTER_MESSAGE_TYPE_SET_RESPONSE_CODE + */ + struct GNUNET_MessageHeader header; + + /** + * The new response code, in big endian. + */ + uint32_t response_code GNUNET_PACKED; + +}; +GNUNET_NETWORK_STRUCT_END + + +#endif diff --git a/src/twister/twister_api.c b/src/twister/twister_api.c @@ -0,0 +1,236 @@ +/* + This file is part of Taler. + Copyright (C) 2009, 2010, 2011, 2016 GNUnet e.V. + Copyright (C) 2018 Taler Systems SA + + Taler 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. + + Taler 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 Taler; see the file COPYING. If not, write to the + Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +/** + * @file twister_api.c + * @brief api to control twister proxy + * @author Christian Grothoff + * @author Marcello Stanisci + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include "taler_twister_service.h" +#include "twister.h" + +#define LOG(kind,...) GNUNET_log_from (kind, "twister-api",__VA_ARGS__) + + + +/** + * Opaque handle for asynchronous operation. + */ +struct TALER_TWISTER_Operation +{ + struct TALER_TWISTER_Operation *next; + struct TALER_TWISTER_Operation *prev; + + struct TALER_TWISTER_Handle *h; + + GNUNET_SCHEDULER_TaskCallback cb; + + void *cb_cls; +}; + + +/** + * Handle for talking with the Twister service. + */ +struct TALER_TWISTER_Handle +{ + /** + * Configuration to use. + */ + const struct GNUNET_CONFIGURATION_Handle *cfg; + + /** + * Message queue (if available). + */ + struct GNUNET_MQ_Handle *mq; + + struct TALER_TWISTER_Operation *op_head; + + struct TALER_TWISTER_Operation *op_tail; + +}; + + +/** + * Generic error handler, called with the appropriate + * error code and the same closure specified at the creation of + * the message queue. + * Not every message queue implementation supports an error handler. + * + * @param cls closure with the `struct TALER_TWISTER_Handle *` + * @param error error code + */ +static void +mq_error_handler (void *cls, + enum GNUNET_MQ_Error error) +{ + struct TALER_TWISTER_Handle *h = cls; + + GNUNET_MQ_destroy (h->mq); + h->mq = NULL; + GNUNET_assert (0); /* FIXME: maybe give test case nicer way to shut down... */ +} + + +/** + * Type of a function to call when we receive a message + * from the service. + * + * @param cls closure + * @param client_msg message received + */ +static void +handle_acknowledgement (void *cls, + const struct GNUNET_MessageHeader *ack) +{ + struct TALER_TWISTER_Handle *h = cls; + struct TALER_TWISTER_Operation *op; + + op = h->op_head; + GNUNET_assert (NULL != op); /* twister very wrong, fail test */ + GNUNET_CONTAINER_DLL_remove (h->op_head, + h->op_tail, + op); + if (NULL != op->cb) + op->cb (op->cb_cls); + GNUNET_free (op); +} + + +/** + * Connect to the twister service. + * + * @param cfg the configuration to use + * @return handle to use + */ +struct TALER_TWISTER_Handle * +TALER_TWISTER_connect (const struct GNUNET_CONFIGURATION_Handle *cfg) +{ + struct TALER_TWISTER_Handle *h; + + h = GNUNET_new (struct TALER_TWISTER_Handle); + h->cfg = cfg; + { + struct GNUNET_MQ_MessageHandler handlers[] = { + GNUNET_MQ_hd_fixed_size (acknowledgement, + TWISTER_MESSAGE_TYPE_ACKNOWLEDGEMENT, + struct GNUNET_MessageHeader, + h), + GNUNET_MQ_handler_end () + }; + + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Connecting to twister service.\n"); + h->mq = GNUNET_CLIENT_connect (h->cfg, + "twister", + handlers, + &mq_error_handler, + h); + } + if (NULL == h->mq) + { + GNUNET_free (h); + return NULL; + } + return h; +} + + +/** + * Disconnect from twister service. + * + * @param h handle to destroy + */ +void +TALER_TWISTER_disconnect (struct TALER_TWISTER_Handle *h) +{ + struct TALER_TWISTER_Operation *op; + + while (NULL != (op = h->op_head)) + { + GNUNET_CONTAINER_DLL_remove (h->op_head, + h->op_tail, + op); + GNUNET_free (op); + } + if (NULL != h->mq) + { + GNUNET_MQ_destroy (h->mq); + h->mq = NULL; + } + GNUNET_free (h); +} + + +/** + * Abort operation. Twister behavior may then include the + * changes requested by the operation, or not! Must be called + * before the operations callback was invoked. + * + * @param op operation to cancel, operation's callback will not be called + */ +void +TALER_TWISTER_cancel (struct TALER_TWISTER_Operation *op) +{ + /* Just don't call the callback anymore */ + op->cb = NULL; +} + + +/** + * Change the next response code to @a new_rc. + * + * @param h twister instance to control + * @param new_rc response code to return from the next response + * @param cb function to call once twister is ready + * @param cb_cls closure for @a cb + * @return operation handle (to possibly abort) + */ +struct TALER_TWISTER_Operation * +TALER_TWISTER_change_response_code (struct TALER_TWISTER_Handle *h, + unsigned int new_rc, + GNUNET_SCHEDULER_TaskCallback cb, + void *cb_cls) +{ + struct TALER_TWISTER_Operation *op; + struct GNUNET_MQ_Envelope *env; + struct TWISTER_SetResponseCode *src; + + op = GNUNET_new (struct TALER_TWISTER_Operation); + op->h = h; + op->cb = cb; + op->cb_cls = cb_cls; + GNUNET_CONTAINER_DLL_insert_tail (h->op_head, + h->op_tail, + op); + env = GNUNET_MQ_msg (src, + TWISTER_MESSAGE_TYPE_SET_RESPONSE_CODE); + src->response_code = htonl ((uint32_t) new_rc); + GNUNET_MQ_send (h->mq, + env); + return op; +} + + +/* end of twister_api.c */