/* 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-service.c * @brief HTTP proxy that acts as a man in the * middle making changes to requests or responses */ #include "platform.h" #include #if HAVE_CURL_CURL_H #include #elif HAVE_GNURL_CURL_H #include #endif #include #include #include "twister.h" #include #include #include #include /** * To be removed upon the next release. */ #ifndef TALER_LOG_INFO #define TALER_LOG_INFO(...) \ GNUNET_log (GNUNET_ERROR_TYPE_INFO, __VA_ARGS__) #endif #define REQUEST_BUFFER_MAX (1024*1024) #define UNIX_BACKLOG 500 /** * 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_CLIENT_UPLOAD_STARTED, /** * Wa have started uploading data to the proxied service. */ REQUEST_STATE_PROXY_UPLOAD_STARTED, /** * We're done with the upload from MHD. */ REQUEST_STATE_CLIENT_UPLOAD_DONE, /** * We're done uploading data to the proxied service. */ REQUEST_STATE_PROXY_UPLOAD_DONE, /** * We've finished uploading data via CURL and can now download. */ REQUEST_STATE_PROXY_DOWNLOAD_STARTED, /** * We've finished receiving download data from cURL. */ REQUEST_STATE_PROXY_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 { /** * Kept in DLL. */ struct HttpRequest *prev; /** * Kept in DLL. */ struct HttpRequest *next; /** * MHD request that triggered us. */ struct MHD_Connection *con; /** * Client socket read task */ struct GNUNET_SCHEDULER_Task *rtask; /** * Client socket write task */ struct GNUNET_SCHEDULER_Task *wtask; /** * Hold the response obtained by modifying the original one. */ struct MHD_Response *mod_response; /** * MHD response object for this request. */ struct MHD_Response *response; /** * The URL to fetch */ char *url; /** * JSON we use to parse payloads (in both directions). */ json_t *json; /** * Handle to cURL */ CURL *curl; /** * HTTP request headers for the curl request. */ struct curl_slist *headers; /** * Headers from response */ struct HttpResponseHeader *header_head; /** * Headers from response */ struct HttpResponseHeader *header_tail; /** * Buffer we use for moving data between MHD and * curl (in both directions). */ char *io_buf; /** * Number of bytes already in the IO buffer. */ size_t io_len; /** * Number of bytes allocated for the IO buffer. */ unsigned int io_size; /** * HTTP response code to give to MHD for the response. */ unsigned int response_code; /** * Request processing state machine. */ enum RequestState state; /** * Did we suspend MHD processing? */ int suspended; /** * Did we pause CURL processing? */ int curl_paused; }; /* *********************** Globals **************************** */ /** * The cURL download task (curl multi API). */ static struct GNUNET_SCHEDULER_Task *curl_download_task; /** * DLL of active HTTP requests. */ static struct HttpRequest *hr_head; /** * DLL of active HTTP requests. */ static struct HttpRequest *hr_tail; /** * 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 ***************** */ /** * Chaos probability (in percent); this value is taken from * the config and stays valid for all the Twister's lifetime. */ static long long unsigned int chaos_rate = 0; /** * Set to non-zero if we should change the next response code. * In this case, this is the value to use. */ static unsigned int hack_response_code; /** * Will point to a JSON object to delete. Only cares about * _download_ objects. */ static char *delete_path = NULL; /** * Will point to a JSON _string_ object * which will get a character flipped. * Only checked against _download_ objects. */ static char *flip_path_dl = NULL; /** * Will point to a JSON _string_ object * which will get a character flipped. * Only checked against _upload_ objects. */ static char *flip_path_ul = NULL; /** * Will point to a JSON object to modify. * Only checked against _download_ objects. */ static char *modify_path_dl = NULL; /** * Will point to a JSON object to modify. * Only checked against _upload_ objects. */ static char *modify_path_ul = NULL; /** * If true, will randomly truncate the request body * to upload to the proxied service. */ static unsigned int malform_upload = GNUNET_NO; /** * If true, will randomly truncate the response body * before returning to the client. */ static unsigned int malform = GNUNET_NO; /** * New value to give the modified field. * Both for upload and download mods. */ static char *modify_value = NULL; /** * Size of the malformed body to be uploaded to the * proxied service. */ static size_t malformed_size; /* ********************* Global helpers ****************** */ /** * Run MHD now, we have extra data ready for the callback. */ static void run_mhd_now (void); /* *************** HTTP handling with cURL ***************** */ /** * Transform _one_ CURL header (gotten from the request) into * MHD format and put it into the response headers list; 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; /* Raw line is not guaranteed to be null-terminated. */ ndup = GNUNET_malloc (bytes + 1); memcpy (ndup, buffer, bytes); ndup[bytes] = '\0'; 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++; /* MHD does not allow certain characters in values, * remove those, plus those could alter strings matching. */ 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'; TALER_LOG_DEBUG ("Parsed line: '%s: %s'\n", hdr_type, hdr_val); /* Skip "Content-length:" header as it will be wrong, given that we are man-in-the-middling the connection */ if (0 == strcasecmp (hdr_type, MHD_HTTP_HEADER_CONTENT_LENGTH)) { GNUNET_free (ndup); return bytes; } if (0 != strlen (hdr_val)) /* Rely in MHD to set those */ { header = GNUNET_new (struct HttpResponseHeader); header->type = GNUNET_strdup (hdr_type); header->value = GNUNET_strdup (hdr_val); GNUNET_CONTAINER_DLL_insert (hr->header_head, hr->header_tail, header); } GNUNET_free (ndup); return bytes; } /** * Create the MHD response with CURL's as starting base; * mainly set the response code and parses the response into * JSON, if it is such. * * @param hr pointer to where to store the new data. Despite * its name, the struct contains response data as well. * @return #GNUNET_OK if it succeeds. */ static int create_mhd_response_from_hr (struct HttpRequest *hr) { long resp_code; json_error_t error; 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_log (GNUNET_ERROR_TYPE_DEBUG, "Creating MHD response with code %u\n", (unsigned int) resp_code); hr->response_code = resp_code; /* Note, will be NULL if io_buf does not represent * a JSON value. */ hr->json = json_loadb (hr->io_buf, hr->io_len, JSON_DECODE_ANY, &error); GNUNET_assert (GNUNET_YES == hr->suspended); MHD_resume_connection (hr->con); hr->suspended = GNUNET_NO; 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; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Curl download proceeding\n"); if (REQUEST_STATE_PROXY_UPLOAD_STARTED == hr->state) { /* Web server started with response before we finished the upload. In this case, current libcurl decides to NOT complete the upload, so we should jump in the state machine to process the download, dropping the rest of the upload. This should only really happen with uploads without "Expect: 100 Continue" and Web servers responding with an error (i.e. upload not allowed) */ hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Stopping %u byte upload: we are already downloading...\n", (unsigned int) hr->io_len); hr->io_len = 0; } if (REQUEST_STATE_PROXY_DOWNLOAD_STARTED != hr->state) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Download callback goes to sleep\n"); hr->curl_paused = GNUNET_YES; return CURL_WRITEFUNC_PAUSE; } GNUNET_assert (REQUEST_STATE_PROXY_DOWNLOAD_STARTED == hr->state); if (hr->io_size - hr->io_len < total) { GNUNET_assert (total + hr->io_size >= total); GNUNET_assert (hr->io_size * 2 + 1024 > hr->io_size); GNUNET_array_grow (hr->io_buf, hr->io_size, GNUNET_MAX (total + hr->io_len, hr->io_size * 2 + 1024)); } GNUNET_memcpy (&hr->io_buf[hr->io_len], ptr, total); hr->io_len += total; return total; } /** * Ask cURL for the select() sets and schedule cURL operations. */ static void curl_download_prepare (void); /** * cURL callback for uploaded (PUT/POST) data. * Copies from our `io_buf` to make it available to cURL. * * @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; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Upload cb is working...\n"); if ( (REQUEST_STATE_PROXY_DOWNLOAD_STARTED == hr->state) || (REQUEST_STATE_PROXY_DOWNLOAD_DONE == hr->state) ) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Upload cb aborts: we are already downloading...\n"); return CURL_READFUNC_ABORT; } if ( (0 == hr->io_len) && (REQUEST_STATE_PROXY_UPLOAD_STARTED == hr->state) ) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Pausing CURL UPLOAD, need more data\n"); return CURL_READFUNC_PAUSE; } /** * We got rescheduled because the download callback was asleep. * FIXME: can this block be eliminated and the unpausing being * moved in the last block where we return zero as well? */ if ( (0 == hr->io_len) && (REQUEST_STATE_PROXY_DOWNLOAD_STARTED == hr->state) ) { if (GNUNET_YES == hr->curl_paused) { hr->curl_paused = GNUNET_NO; curl_easy_pause (hr->curl, CURLPAUSE_CONT); } curl_download_prepare (); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Completed CURL UPLOAD\n"); return 0; /* upload finished, can now download */ } to_copy = GNUNET_MIN (hr->io_len, len); GNUNET_memcpy (buf, hr->io_buf, to_copy); /* shift remaining data back to the beginning of the buffer. */ memmove (hr->io_buf, &hr->io_buf[to_copy], hr->io_len - to_copy); hr->io_len -= to_copy; if (0 == hr->io_len) { hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Completed CURL UPLOAD\n"); } return to_copy; } /* ************** helper functions ************* */ /** * Extract the hostname from a complete URL. * * @param url full fledged URL * * @return pointer to the 0-terminated hostname, to be freed * by the caller. */ static char * build_host_header (const char *url) { #define MARKER "://" char *header; char *end; char *hostname; char *dup = GNUNET_strdup (url); hostname = strstr (dup, MARKER); hostname+=3; end = strchrnul (hostname, '/'); *end = '\0'; GNUNET_asprintf (&header, "Host: %s", hostname); GNUNET_free (dup); return header; } /* ************** 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, usually NULL. */ static void curl_task_download (void *cls) { int running; int msgnum; struct CURLMsg *msg; CURLMcode mret; struct HttpRequest *hr; (void) cls; 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_PROXY_DOWNLOAD_DONE; if (GNUNET_YES == hr->suspended) { MHD_resume_connection (hr->con); hr->suspended = GNUNET_NO; } 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_PROXY_DOWNLOAD_DONE; if (GNUNET_YES == hr->suspended) { MHD_resume_connection (hr->con); hr->suspended = GNUNET_NO; } run_mhd_now (); break; } if (NULL == hr->response) hr->response = curl_failure_response; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Curl request for `%s' finished (got the response)\n", hr->url); run_mhd_now (); 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 ***************** */ /** * "Filter" function that translates MHD request headers to * cURL's. * * @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; char *new_value = NULL; (void) kind; if (0 == strcmp (MHD_HTTP_HEADER_HOST, key)) { /* We don't take the host header as given in the request. * We'll instead put the proxied service's hostname in it*/ return MHD_YES; } if ((0 == strcmp (MHD_HTTP_HEADER_ACCEPT_ENCODING, key)) || (0 == strcmp (MHD_HTTP_HEADER_CONTENT_ENCODING, key))) { TALER_LOG_INFO ("Do not re-compress request and/or do not" " ask for compressed responses\n"); return MHD_YES; } if ((0 == strcmp (MHD_HTTP_HEADER_CONTENT_LENGTH, key))) { TALER_LOG_INFO ("Do not re-set Content-Length for request (CURLOPT_POSTFIELDSIZE did)\n"); return MHD_YES; } 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); GNUNET_free_non_null (new_value); return MHD_YES; } /** * Walk a JSON object preparing its modification. Name to * be changed in 'walk_object'. * * @param path the path pointing to a object to modify. * @param[out] parent will point to the parent of the targeted * object. This parent will be the "handle" to pass * to the jansson modification function. * @param[out] target last token of the path. E.g. given a x.y.z, * will point to 'z'. This value will also be passed * to the jansson modification function. * To be freed by the caller. * @param json the object to be walked over. * @return #GNUNET_OK if @a path was valid. */ static unsigned int walk_object (const char *path, json_t **parent, char **target, json_t *json) { json_t *element; json_t *cur; char *token; char *last_token; char *path_dup; GNUNET_assert (NULL != json); GNUNET_asprintf (&path_dup, ".%s", /* Make sure path starts with dot. */ path); last_token = strrchr (path_dup, '.') + 1; /* Give first nondelim char. */ token = strtok (path_dup, "."); if (NULL == (element = json)) { TALER_LOG_ERROR ("Attempting to walk a non JSON response!\n"); return GNUNET_SYSERR; } while (last_token != token) { TALER_LOG_DEBUG ("token/last_token: %s@%p / %s@%p\n", token, token, last_token, last_token); if (NULL == token) return GNUNET_SYSERR; // path was ".", refuse to process it. if (NULL != (cur = json_object_get (element, token))) { element = cur; token = strtok (NULL, "."); continue; } if (NULL != (cur = json_array_get (element, (unsigned int) strtoul (token, NULL, 10)))) { element = cur; token = strtok (NULL, "."); continue; } TALER_LOG_WARNING ("Path token '%s' not found\n", token); GNUNET_free (path_dup); return GNUNET_NO; } if ( (NULL == json_object_get (element, last_token) ) && /* NOTE: if token is bad but converts to either 0, or * ULONG max AND the array has such a index, then this * test won't detect any error. Likewise, the method for * deleting/modifying the response will operate on that * same random array element. */ (NULL == json_array_get (element, (unsigned int) strtoul (token, NULL, 10))) ) { TALER_LOG_WARNING ("(Last) path token '%s' not found\n", last_token); GNUNET_free (path_dup); return GNUNET_NO; } *target = GNUNET_strdup (last_token); *parent = element; GNUNET_free (path_dup); return GNUNET_OK; } /** * Modify a JSON object. NOTE, the new value to set is * taken from the global value `modify_value'. * * @param con HTTP connection handle. * FIXME: deprecated, to be removed. * @param json the JSON object to modify. * @param path the path to the field to modify. */ static int modify_object (struct MHD_Connection *con, json_t *json, char *path) { char *target; int ret_modify; json_t *parent; json_t *new_value; json_error_t error; if (GNUNET_OK != walk_object (path, &parent, &target, json)) { TALER_LOG_INFO ("Path (%s) was not found on this object\n", path); return GNUNET_NO; } /* At this point, the parent and the target are pointed to. */ if (0 == strcmp ("true", modify_value)) { TALER_LOG_DEBUG ("New value parsed as boolean true\n"); new_value = json_true (); goto perform_modbody; } if (NULL != (new_value = json_loads (modify_value, JSON_REJECT_DUPLICATES | JSON_DISABLE_EOF_CHECK, &error))) { TALER_LOG_DEBUG ("New value parsed as object/array\n"); goto perform_modbody; } if (NULL != (new_value = json_string (modify_value))) { TALER_LOG_DEBUG ("New value parsed as string\n"); goto perform_modbody; } TALER_LOG_ERROR ("Unvalid new value given: %s\n", modify_value); GNUNET_free (target); json_decref (new_value); return GNUNET_SYSERR; perform_modbody: ret_modify = -1; if (json_is_object (parent)) ret_modify = json_object_set (parent, target, new_value); if (json_is_array (parent)) ret_modify = json_array_set_new (parent, (unsigned int) strtoul (target, NULL, 10), new_value); GNUNET_free (target); json_decref (new_value); if (-1 == ret_modify) { TALER_LOG_WARNING ("Could not replace '%s'\n", target); return GNUNET_SYSERR; } return GNUNET_OK; } /** * Flip a random character to the string pointed to by @a path. * * @param con FIXME deprecated. * @param json the object whose field will be flipped. * @param flip_path the path to the string-field to flip. * @return GNUNET_OK when the path was found, and flipped. */ static int flip_object (struct MHD_Connection *con, json_t *json, char *flip_path) { char *target; json_t *parent; #define CROCKFORD_MAX_INDEX 31 char crockford_chars[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'X', 'Y', 'Z'}; // index: 0-31 if (GNUNET_OK != walk_object (flip_path, &parent, &target, json)) { /** * Not an error, as the user can "batch" * requests until the right object gets in the way. */ TALER_LOG_INFO ("Path (%s) was not found on this object\n", flip_path); return GNUNET_NO; } /* here, element is the parent of the element to be deleted. */ json_t *child = NULL; const char *current_value; char *current_value_flip; uint32_t crockford_index; uint32_t flip_index; if (json_is_object (parent)) child = json_object_get (parent, target); if (json_is_array (parent)) child = json_array_get (parent, (unsigned int) strtoul (target, NULL, 10)); /* json walker is such that at this point the * child's parent is always a object or array. */ GNUNET_assert (NULL != child); current_value = json_string_value (child); current_value_flip = GNUNET_strdup (current_value); flip_index = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, strlen (current_value_flip)); do{ crockford_index = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_STRONG, CROCKFORD_MAX_INDEX + 1); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Try flipping with Crockford index: %u\n", crockford_index); } while (current_value_flip[flip_index] == crockford_chars[crockford_index]); current_value_flip[flip_index] = crockford_chars[crockford_index]; TALER_LOG_INFO ("Flipping %s to %s\n", current_value, current_value_flip); if (0 != json_string_set (child, (const char *) current_value_flip)) { TALER_LOG_WARNING ("Could not flip '%s'\n", target); GNUNET_free (current_value_flip); GNUNET_free (target); return GNUNET_SYSERR; } GNUNET_free (current_value_flip); GNUNET_free (target); return GNUNET_OK; } /** * Delete object within the proxied response. * Always queues a response; only deletes the object if it is * found within the response, otherwise return it verbatim (but * will look for it into the next response). Will flush the * operation once the wanted object has been found. * * @param con FIXME deprecated. * @param hr contains the object whose field will be deleted. * @return GNUNET_OK when the path was found, and deleted. */ static int delete_object (struct MHD_Connection *con, struct HttpRequest *hr) { char *target; json_t *parent; if (GNUNET_OK != walk_object (delete_path, &parent, &target, hr->json)) { TALER_LOG_INFO ("Path (%s) was not found on this object\n", delete_path); return GNUNET_NO; } /* here, element is the parent of the element to be deleted. */ int ret_deletion = -1; if (json_is_object (parent)) ret_deletion = json_object_del (parent, target); if (json_is_array (parent)) { ret_deletion = json_array_remove (parent, (unsigned int) strtoul (target, NULL, 10)); } GNUNET_free (target); if (-1 == ret_deletion) { TALER_LOG_WARNING ("Could not delete '%s'\n", target); return GNUNET_SYSERR; } return GNUNET_OK; } /** * Decompress data. * * @param request contains input data to inflate * @return result code indicating the status of the operation */ static int inflate_data (struct HttpRequest *request) { z_stream z; char *tmp; size_t tmp_size; int ret; memset (&z, 0, sizeof (z)); z.next_in = (Bytef *) request->io_buf; z.avail_in = request->io_len; tmp_size = GNUNET_MIN (REQUEST_BUFFER_MAX, request->io_len * 4); tmp = GNUNET_malloc (tmp_size); z.next_out = (Bytef *) tmp; z.avail_out = tmp_size; ret = inflateInit (&z); switch (ret) { case Z_MEM_ERROR: GNUNET_break (0); return GNUNET_JSON_PR_OUT_OF_MEMORY; case Z_STREAM_ERROR: GNUNET_break_op (0); return GNUNET_JSON_PR_JSON_INVALID; case Z_OK: break; } while (1) { ret = inflate (&z, 0); switch (ret) { case Z_MEM_ERROR: GNUNET_break (0); GNUNET_break (Z_OK == inflateEnd (&z)); GNUNET_free (tmp); return GNUNET_JSON_PR_OUT_OF_MEMORY; case Z_DATA_ERROR: GNUNET_break (0); GNUNET_break (Z_OK == inflateEnd (&z)); GNUNET_free (tmp); return GNUNET_JSON_PR_JSON_INVALID; case Z_NEED_DICT: GNUNET_break (0); GNUNET_break (Z_OK == inflateEnd (&z)); GNUNET_free (tmp); return GNUNET_JSON_PR_JSON_INVALID; case Z_OK: if ((0 < z.avail_out) && (0 == z.avail_in)) { /* truncated input stream */ GNUNET_break (0); GNUNET_break (Z_OK == inflateEnd (&z)); GNUNET_free (tmp); return GNUNET_JSON_PR_JSON_INVALID; } if (0 < z.avail_out) continue; /* just call it again */ /* output buffer full, can we grow it? */ if (tmp_size == REQUEST_BUFFER_MAX) { /* already at max */ GNUNET_break (0); GNUNET_break (Z_OK == inflateEnd (&z)); GNUNET_free (tmp); return GNUNET_JSON_PR_OUT_OF_MEMORY; } if (tmp_size * 2 < tmp_size) tmp_size = REQUEST_BUFFER_MAX; else tmp_size = GNUNET_MIN (REQUEST_BUFFER_MAX, tmp_size * 2); tmp = GNUNET_realloc (tmp, tmp_size); z.next_out = (Bytef *) &tmp[z.total_out]; continue; case Z_STREAM_END: /* decompression successful, make 'tmp' the new 'data' */ GNUNET_free (request->io_buf); request->io_buf = tmp; request->io_size = tmp_size; request->io_len = z.total_out; GNUNET_break (Z_OK == inflateEnd (&z)); return GNUNET_JSON_PR_SUCCESS; /* at least for now */ } } /* while (1) */ } /** * Create the response object according to the "chaos rate". * If this latter strikes, then the response will be "503 Service * Unavailable" with a empty body (overriding every other mod that * the user might have given.) * * @param request the HTTP object representing the current state. */ static void create_response_with_chaos_rate (struct HttpRequest *request) { uint64_t random; void *resp_buf; size_t resp_len; random = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK, 100); TALER_LOG_INFO ("p: %llu, random: %llu\n", (unsigned long long) chaos_rate, (unsigned long long) random); if (random < chaos_rate) { /* p won */ TALER_LOG_INFO ("Chaos probability won the case.\n"); resp_buf = "Service unavailable"; resp_len = strlen (resp_buf); request->response_code = MHD_HTTP_SERVICE_UNAVAILABLE; } else { resp_len = request->io_len; resp_buf = request->io_buf; } request->response = MHD_create_response_from_buffer (resp_len, resp_buf, MHD_RESPMEM_MUST_COPY); } /** * 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; struct HttpResponseHeader *header; (void) cls; (void) url; if (NULL == hr) { GNUNET_break (0); return MHD_NO; } if (REQUEST_STATE_WITH_MHD == hr->state) { hr->state = REQUEST_STATE_CLIENT_UPLOAD_STARTED; /* TODO: hacks for 100 continue suppression would go here! */ return MHD_YES; } /* continuing to process request */ if (0 != *upload_data_size) { GNUNET_assert (REQUEST_STATE_CLIENT_UPLOAD_STARTED == hr->state); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Processing %u bytes UPLOAD\n", (unsigned int) *upload_data_size); /* Grow the buffer if remaining space isn't enough. */ if (hr->io_size - hr->io_len < *upload_data_size) { /* How can this assertion be false? */ GNUNET_assert (hr->io_size * 2 + 1024 > hr->io_size); /* This asserts that upload_data_size > 0, ? */ GNUNET_assert (*upload_data_size + hr->io_len > hr->io_len); GNUNET_array_grow (hr->io_buf, hr->io_size, GNUNET_MAX (hr->io_size * 2 + 1024, *upload_data_size + hr->io_len)); } /* Finally copy upload data. */ GNUNET_memcpy (&hr->io_buf[hr->io_len], upload_data, *upload_data_size); hr->io_len += *upload_data_size; *upload_data_size = 0; return MHD_YES; } /* Upload (*from the client*) finished or just a without-body * request. */ if (REQUEST_STATE_CLIENT_UPLOAD_STARTED == hr->state) { hr->state = REQUEST_STATE_CLIENT_UPLOAD_DONE; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Finished processing UPLOAD\n"); if (0 != hr->io_len) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Attempting to decompress\n"); const char *ce; ce = MHD_lookup_connection_value (con, MHD_HEADER_KIND, MHD_HTTP_HEADER_CONTENT_ENCODING); if ((NULL != ce) && (0 == strcmp ("deflate", ce))) GNUNET_assert (Z_OK == inflate_data (hr)); ce = MHD_lookup_connection_value (con, MHD_HEADER_KIND, MHD_HTTP_HEADER_CONTENT_TYPE); if ((NULL != ce) && (0 == strcmp ("application/json", ce))) { json_error_t error; hr->json = json_loadb (hr->io_buf, hr->io_len, JSON_DECODE_ANY, &error); if (NULL == hr->json) { TALER_LOG_ERROR ("Could not parse JSON from client: %s (%s)\n", error.text, error.source); /* Quick and dirty. */ return MHD_NO; } } } } /* generate curl request to the proxied service. */ if (NULL == hr->curl) { /* Malform request body. Note, this flag will be * cleared only after having set the right malformed * body lenght in the request headers. */ if (GNUNET_YES == malform_upload) { TALER_LOG_DEBUG ("Will (badly) truncate the request.\n"); malformed_size = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, hr->io_len); hr->io_len = malformed_size; malform_upload = GNUNET_NO; } if (NULL != flip_path_ul) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Will flip path in request: %s\n", flip_path_ul); if (GNUNET_OK == flip_object (con, hr->json, flip_path_ul)) { GNUNET_free (flip_path_ul); flip_path_ul = NULL; } } if (NULL != modify_path_ul) { int ret; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Will try to modify: %s\n", flip_path_ul); ret = modify_object (con, hr->json, modify_path_ul); if ((GNUNET_OK == ret) || (GNUNET_SYSERR == ret)) { GNUNET_free (modify_path_ul); GNUNET_free (modify_value); modify_path_ul = NULL; modify_value = NULL; } } /* Existing io_len is enough to accomodate this encoding. */ json_dumpb (hr->json, hr->io_buf, hr->io_len, JSON_COMPACT); json_decref (hr->json); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Generating curl request\n"); hr->curl = curl_easy_init (); if (NULL == hr->curl) { TALER_LOG_ERROR ("Could not init the curl handle\n"); return MHD_queue_response (con, MHD_HTTP_INTERNAL_SERVER_ERROR, curl_failure_response); } /* No need to check whether we're POSTing or PUTting. * If not needed, one of the following values will be * ignored.*/ curl_easy_setopt (hr->curl, CURLOPT_POSTFIELDSIZE, hr->io_len); curl_easy_setopt (hr->curl, CURLOPT_INFILESIZE, hr->io_len); 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_CONNECTTIMEOUT, 600L); curl_easy_setopt (hr->curl, CURLOPT_TIMEOUT, 600L); curl_easy_setopt (hr->curl, CURLOPT_NOSIGNAL, 1L); curl_easy_setopt (hr->curl, CURLOPT_PRIVATE, hr); curl_easy_setopt (hr->curl, CURLOPT_VERBOSE, 0); curl_easy_setopt (hr->curl, CURLOPT_READFUNCTION, &curl_upload_cb); curl_easy_setopt (hr->curl, CURLOPT_READDATA, hr); curl_easy_setopt (hr->curl, CURLOPT_WRITEFUNCTION, &curl_download_cb); curl_easy_setopt (hr->curl, CURLOPT_WRITEDATA, hr); { char *curlurl; char *host_hdr; GNUNET_asprintf (&curlurl, "%s%s", target_server_base_url, hr->url); curl_easy_setopt (hr->curl, CURLOPT_URL, curlurl); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Forwarding request to: %s\n", curlurl); GNUNET_free (curlurl); host_hdr = build_host_header (target_server_base_url); TALER_LOG_DEBUG ("Faking the host header, %s\n", host_hdr); curl_slist_append (hr->headers, host_hdr); GNUNET_free (host_hdr); } if (0 == strcasecmp (meth, MHD_HTTP_METHOD_PUT)) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Crafting a CURL PUT request\n"); curl_easy_setopt (hr->curl, CURLOPT_PUT, 1L); hr->state = REQUEST_STATE_PROXY_UPLOAD_STARTED; } else if (0 == strcasecmp (meth, MHD_HTTP_METHOD_POST)) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Crafting a CURL POST request\n"); curl_easy_setopt (hr->curl, CURLOPT_POST, 1L); curl_easy_setopt (hr->curl, CURLOPT_VERBOSE, 1L); hr->state = REQUEST_STATE_PROXY_UPLOAD_STARTED; } else if (0 == strcasecmp (meth, MHD_HTTP_METHOD_HEAD)) { hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED; curl_easy_setopt (hr->curl, CURLOPT_NOBODY, 1L); } else if (0 == strcasecmp (meth, MHD_HTTP_METHOD_OPTIONS)) { hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED; curl_easy_setopt (hr->curl, CURLOPT_CUSTOMREQUEST, "OPTIONS"); } else if (0 == strcasecmp (meth, MHD_HTTP_METHOD_GET)) { hr->state = REQUEST_STATE_PROXY_DOWNLOAD_STARTED; curl_easy_setopt (hr->curl, CURLOPT_HTTPGET, 1L); } 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; } if (REQUEST_STATE_PROXY_DOWNLOAD_DONE != hr->state) { MHD_suspend_connection (con); hr->suspended = GNUNET_YES; return MHD_YES; /* wait for curl */ } GNUNET_assert (REQUEST_STATE_PROXY_DOWNLOAD_DONE == hr->state); if (0 != hack_response_code) { hr->response_code = hack_response_code; hack_response_code = 0; /* reset for next request */ } if (NULL != flip_path_dl) { TALER_LOG_DEBUG ("Will flip path" " in response: %s\n", flip_path_dl); if (GNUNET_OK == flip_object (con, hr->json, flip_path_dl)) { GNUNET_free (flip_path_dl); flip_path_dl = NULL; } } if (NULL != delete_path) { TALER_LOG_DEBUG ("Will delete path: %s\n", delete_path); if (GNUNET_OK == delete_object (con, hr)) { GNUNET_free (delete_path); delete_path = NULL; } } if (NULL != modify_path_dl) { int ret; TALER_LOG_DEBUG ("Will modify path: %s to value %s\n", modify_path_dl, modify_value); ret = modify_object (con, hr->json, modify_path_dl); if ((GNUNET_OK == ret) || (GNUNET_SYSERR == ret)) { GNUNET_free (modify_path_dl); GNUNET_free (modify_value); modify_path_dl = NULL; modify_value = NULL; } } if (NULL != hr->json) { GNUNET_free (hr->io_buf); hr->io_buf = json_dumps (hr->json, JSON_COMPACT); hr->io_len = strlen (hr->io_buf); json_decref (hr->json); } if (GNUNET_YES == malform) { size_t fake_len; TALER_LOG_DEBUG ("Will (badly) truncate the response.\n"); fake_len = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, hr->io_len); hr->io_len = fake_len; malform = GNUNET_NO; } create_response_with_chaos_rate (hr); for (header = hr->header_head; NULL != header; header = header->next) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Adding MHD response header %s->%s\n", header->type, header->value); GNUNET_break (MHD_YES == MHD_add_response_header (hr->response, header->type, header->value)); } 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 *` (set by `create_response()`) * @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; (void) cls; (void) connection; 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_cleanup (hr->curl); hr->curl = NULL; hr->io_len = 0; } if ( (NULL != hr->response) && (curl_failure_response != hr->response) ) /* Destroy non-error responses... (?) */ 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, "Proxying of '%s' completely done\n", hr->url); GNUNET_free (hr->url); GNUNET_free_non_null (hr->io_buf); GNUNET_CONTAINER_DLL_remove (hr_head, hr_tail, hr); 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; (void) cls; 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->con = connection; hr->url = GNUNET_strdup (url); GNUNET_CONTAINER_DLL_insert (hr_head, hr_tail, hr); 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) { (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) { (void) cls; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Shutting down...\n"); /* MHD requires resuming before destroying the daemons */ for (struct HttpRequest *hr = hr_head; NULL != hr; hr = hr->next) { if (hr->suspended) { hr->suspended = GNUNET_NO; MHD_resume_connection (hr->con); } } 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; } GNUNET_free (target_server_base_url); target_server_base_url = NULL; } /** * Connect to a unix domain socket. * * @param path the IPC path * @param mode the IPC path mode * @return the file descriptor of the connection. */ static int open_unix_path (const char *path, mode_t mode) { struct GNUNET_NETWORK_Handle *nh; struct sockaddr_un *un; int fd; if (sizeof (un->sun_path) <= strlen (path)) { fprintf (stderr, "path `%s' too long\n", path); return -1; } GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Creating listen socket '%s' with mode %o\n", path, mode); if (GNUNET_OK != GNUNET_DISK_directory_create_for_file (path)) { GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "mkdir", path); } un = GNUNET_new (struct sockaddr_un); un->sun_family = AF_UNIX; strncpy (un->sun_path, path, sizeof (un->sun_path) - 1); GNUNET_NETWORK_unix_precheck (un); if (NULL == (nh = GNUNET_NETWORK_socket_create (AF_UNIX, SOCK_STREAM, 0))) { fprintf (stderr, "create failed for AF_UNIX\n"); GNUNET_free (un); return -1; } if (GNUNET_OK != GNUNET_NETWORK_socket_bind (nh, (void *) un, sizeof (struct sockaddr_un))) { fprintf (stderr, "bind failed for AF_UNIX\n"); GNUNET_free (un); GNUNET_NETWORK_socket_close (nh); return -1; } GNUNET_free (un); if (GNUNET_OK != GNUNET_NETWORK_socket_listen (nh, UNIX_BACKLOG)) { fprintf (stderr, "listen failed for AF_UNIX\n"); GNUNET_NETWORK_socket_close (nh); return -1; } if (0 != chmod (path, mode)) { fprintf (stderr, "chmod failed: %s\n", strerror (errno)); GNUNET_NETWORK_socket_close (nh); return -1; } GNUNET_log (GNUNET_ERROR_TYPE_INFO, "set socket '%s' to mode %o\n", path, mode); fd = GNUNET_NETWORK_get_fd (nh); GNUNET_NETWORK_socket_free_memory_only_ (nh); return fd; } /** * Crawl the configuration file and extracts the serving * method, TCP vs IPC, and the respective details (port/path/mode) * * @param cfg configuration handle. * @param port[out] port number to use * @param path[out] unix path for IPC. * @param mode[out] mode string for @a path. * @return GNUNET_SYSERR if the parsing didn't succeed. */ static int parse_serving_mean (const struct GNUNET_CONFIGURATION_Handle *cfg, uint16_t *port, char **path, mode_t *mode) { const char *serve; const char *choices[] = {"tcp", "unix", NULL}; char *modestring; unsigned long long port_ull; if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_choice (cfg, "twister", "SERVE", choices, &serve)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, "twister", "SERVE"); return GNUNET_SYSERR; } if (0 == strcmp ("tcp", serve)) { *path = NULL; if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_number (cfg, "twister", "HTTP_PORT", &port_ull)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, "twister", "HTTP_PORT"); return GNUNET_SYSERR; } *port = (uint16_t) port_ull; return GNUNET_OK; } /* serving via unix */ if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_filename (cfg, "twister", "SERVE_UNIXPATH", path)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, "twister", "SERVE_UNIXPATH"); return GNUNET_SYSERR; } if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, "twister", "SERVE_UNIXMODE", &modestring)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, "twister", "SERVE_UNIXMODE"); return GNUNET_SYSERR; } errno = 0; *mode = (mode_t) strtoul (modestring, NULL, 8); if (0 != errno) { GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, "twister", "SERVE_UNIXMODE", "must be octal number"); GNUNET_free (modestring); return GNUNET_SYSERR; } GNUNET_free (modestring); return GNUNET_OK; } /** * Main function that will be run. Main tasks are (1) init. the * curl infrastructure (curl_global_init() / curl_multi_init()), * then fetch the HTTP port where its Web service should listen at, * and finally start MHD on that port. * * @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) { uint16_t port; int fh = -1; char *serve_unixpath; mode_t serve_unixmode; (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; } /* No need to check return value. If given, we take, * otherwise it stays zero. */ if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_number (c, "twister", "CHAOS_RATE", &chaos_rate)) chaos_rate = 0; if (100 < chaos_rate) { GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, "twister", "CHAOS_RATE", "must be below 100"); 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_SYSERR == parse_serving_mean (c, &port, &serve_unixpath, &serve_unixmode)) { GNUNET_break (0); GNUNET_SCHEDULER_shutdown (); return; } if (NULL != serve_unixpath) { /* Connect the 'fh' socket. */ fh = open_unix_path (serve_unixpath, serve_unixmode); GNUNET_assert (-1 != fh); } /* start MHD daemon for HTTP */ mhd_daemon = MHD_start_daemon (MHD_USE_DEBUG | MHD_ALLOW_SUSPEND_RESUME | MHD_USE_DUAL_STACK, (-1 == fh) ? (uint16_t) port : 0, 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_LISTEN_SOCKET, fh, 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) { (void) cls; (void) 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 */ (void) cls; (void) c; (void) internal_cls; } /** * Send confirmation that the operation was handled. * * @param c handle to the client waiting for confirmation. */ 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 malforming responses. * * @param cls message queue for sending replies * @param src received message */ static void handle_malform (void *cls, const struct TWISTER_Malform *src) { struct GNUNET_SERVICE_Client *c = cls; TALER_LOG_DEBUG ("Flagging response malformation\n"); malform = GNUNET_YES; send_acknowledgement (c); } /** * Control handler for malforming requests. * * @param cls message queue for sending replies * @param src received message */ static void handle_malform_upload (void *cls, const struct TWISTER_Malform *src) { struct GNUNET_SERVICE_Client *c = cls; TALER_LOG_DEBUG ("Flagging request malformation\n"); malform_upload = GNUNET_YES; send_acknowledgement (c); } /** * Control handler for deleting JSON response objects * * @param cls message queue for sending replies * @param src received message */ static int check_modify_path_dl (void *cls, const struct TWISTER_ModifyPath *src) { return GNUNET_OK; } /** * Control handler for deleting JSON response objects * * @param cls message queue for sending replies * @param src received message */ static void handle_modify_path_dl (void *cls, const struct TWISTER_ModifyPath *src) { struct GNUNET_SERVICE_Client *c = cls; uint16_t tailsize; char *payload_path; char *payload_value; tailsize = ntohs (src->header.size) - sizeof (*src); if (tailsize != GNUNET_STRINGS_buffer_tokenize ((const char *) &src[1], tailsize, 2, &payload_path, &payload_value)) { GNUNET_break_op (0); GNUNET_SERVICE_client_drop (c); return; } modify_path_dl = GNUNET_strdup (payload_path); modify_value = GNUNET_strdup (payload_value); send_acknowledgement (c); } /** * Control handler for deleting JSON response objects * * @param cls message queue for sending replies * @param src received message */ static int check_modify_path_ul (void *cls, const struct TWISTER_ModifyPath *src) { return GNUNET_OK; } /** * Control handler for deleting JSON request objects; * (means request to the proxied services) * * @param cls message queue for sending replies * @param src received message */ static void handle_modify_path_ul (void *cls, const struct TWISTER_ModifyPath *src) { struct GNUNET_SERVICE_Client *c = cls; uint16_t tailsize; char *payload_path; char *payload_value; tailsize = ntohs (src->header.size) - sizeof (*src); if (tailsize != GNUNET_STRINGS_buffer_tokenize ((const char *) &src[1], tailsize, 2, &payload_path, &payload_value)) { GNUNET_break_op (0); GNUNET_SERVICE_client_drop (c); return; } modify_path_dl = GNUNET_strdup (payload_path); modify_value = GNUNET_strdup (payload_value); send_acknowledgement (c); } /** * Control handler for flipping JSON strings into response objects * * @param cls message queue for sending replies * @param src received message */ static void handle_flip_path_dl (void *cls, const struct TWISTER_FlipPath *src) { struct GNUNET_SERVICE_Client *c = cls; uint16_t tailsize; char *payload; tailsize = ntohs (src->header.size) - sizeof (*src); if (tailsize != GNUNET_STRINGS_buffer_tokenize ((const char *) &src[1], tailsize, 1, &payload)) { GNUNET_break_op (0); GNUNET_SERVICE_client_drop (c); return; } flip_path_dl = GNUNET_strdup (payload); send_acknowledgement (c); } /** * Control handler for flipping JSON strings into request objects * * @param cls message queue for sending replies * @param src received message */ static int check_flip_path_ul (void *cls, const struct TWISTER_FlipPath *src) { return GNUNET_OK; } /** * Control handler for flipping JSON strings into request objects * * @param cls message queue for sending replies * @param src received message */ static int check_flip_path_dl (void *cls, const struct TWISTER_FlipPath *src) { return GNUNET_OK; } /** * Control handler for flipping JSON strings into request objects * * @param cls message queue for sending replies * @param src received message */ static void handle_flip_path_ul (void *cls, const struct TWISTER_FlipPath *src) { struct GNUNET_SERVICE_Client *c = cls; uint16_t tailsize; char *payload; tailsize = ntohs (src->header.size) - sizeof (*src); if (tailsize != GNUNET_STRINGS_buffer_tokenize ((const char *) &src[1], tailsize, 1, &payload)) { GNUNET_break_op (0); GNUNET_SERVICE_client_drop (c); return; } flip_path_ul = GNUNET_strdup (payload); send_acknowledgement (c); } /** * Control handler for deleting JSON fields from response objects * * @param cls message queue for sending replies * @param src received message */ static int check_delete_path (void *cls, const struct TWISTER_DeletePath *src) { return GNUNET_OK; } /** * Control handler for deleting JSON fields from response objects * * @param cls message queue for sending replies * @param src received message */ static void handle_delete_path (void *cls, const struct TWISTER_DeletePath *src) { struct GNUNET_SERVICE_Client *c = cls; uint16_t tailsize; char *payload; tailsize = ntohs (src->header.size) - sizeof (*src); if (tailsize != GNUNET_STRINGS_buffer_tokenize ((const char *) &src[1], tailsize, 1, &payload)) { GNUNET_break_op (0); GNUNET_SERVICE_client_drop (c); return; } delete_path = GNUNET_strdup (payload); send_acknowledgement (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_hd_var_size (modify_path_ul, TWISTER_MESSAGE_TYPE_MODIFY_PATH_UL, struct TWISTER_ModifyPath, NULL), GNUNET_MQ_hd_var_size (modify_path_dl, TWISTER_MESSAGE_TYPE_MODIFY_PATH_DL, struct TWISTER_ModifyPath, NULL), GNUNET_MQ_hd_fixed_size (malform_upload, TWISTER_MESSAGE_TYPE_MALFORM_UPLOAD, struct TWISTER_Malform, NULL), GNUNET_MQ_hd_fixed_size (malform, TWISTER_MESSAGE_TYPE_MALFORM, struct TWISTER_Malform, NULL), GNUNET_MQ_hd_var_size (delete_path, TWISTER_MESSAGE_TYPE_DELETE_PATH, struct TWISTER_DeletePath, NULL), GNUNET_MQ_hd_var_size (flip_path_ul, TWISTER_MESSAGE_TYPE_FLIP_PATH_UL, struct TWISTER_FlipPath, NULL), GNUNET_MQ_hd_var_size (flip_path_dl, TWISTER_MESSAGE_TYPE_FLIP_PATH_DL, struct TWISTER_FlipPath, NULL), GNUNET_MQ_handler_end ()); /* end of taler-twister-service.c */