commit 8a811ee0dba732be528c3d729318a4a2a9ec2930
parent ee85c023d8eb74bd633efe4d4b6710b7759ea62b
Author: Christian Grothoff <christian@grothoff.org>
Date: Sat, 20 Jan 2018 14:54:24 +0100
finish response code hacking
Diffstat:
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 */