/* 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 "twister.h" #include #include #include #include /** * 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're done with the upload 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 { /** * 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; /** * Buffer we use for moving data between MHD and * curl (in both directions). */ char *io_buf; /** * 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; /** * Number of bytes already in the IO buffer. */ size_t io_len; /** * HTTP response code to give to MHD for the response. */ unsigned int response_code; /** * Number of bytes allocated for the IO buffer. */ unsigned int io_size; /** * Request processing state machine. */ enum RequestState state; /** * Did we suspend MHD processing? */ int suspended; /** * Did we pause CURL processing? */ int curl_paused; /** * if GNUNET_YES, then request and response will carry * a compressed body. */ int deflate; /** * Zlib internal state. */ z_stream zstate; }; /* *********************** 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 ***************** */ /** * 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[TWISTER_PATH_LENGTH] = {'\0'}; /** * Will point to a JSON _string_ object * which will get a character flipped. * Only checked against _download_ objects. */ static char flip_path_dl[TWISTER_PATH_LENGTH] = {'\0'}; /** * Will point to a JSON _string_ object * which will get a character flipped. * Only checked against _upload_ objects. */ static char flip_path_ul[TWISTER_PATH_LENGTH] = {'\0'}; /** * Will point to a JSON object to modify. * Only checked against _download_ objects. */ static char modify_path_dl[TWISTER_PATH_LENGTH] = {'\0'}; /** * Will point to a JSON object to modify. * Only checked against _upload_ objects. */ static char modify_path_ul[TWISTER_PATH_LENGTH] = {'\0'}; /** * 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[TWISTER_VALUE_LENGTH]; /** * 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 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'; TALER_LOG_DEBUG ("Raw line: '%s'\n", ndup); 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); if ((0 == strcasecmp (hdr_type, MHD_HTTP_HEADER_CONTENT_ENCODING)) && (NULL != strstr (hdr_val, "deflate"))) { TALER_LOG_INFO ("Compressed response\n"); hr->deflate = GNUNET_YES; } /* Skip "Host:" header as it will be wrong, given that we are man-in-the-middling the connection */ if (0 == strcasecmp (hdr_type, MHD_HTTP_HEADER_HOST)) { GNUNET_free (ndup); return bytes; } /* 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_UPLOAD_STARTED == hr->state) || (REQUEST_STATE_UPLOAD_DONE == 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_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_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_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_UPLOAD_STARTED != hr->state) && (REQUEST_STATE_UPLOAD_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_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; 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_DOWNLOAD_STARTED; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Completed CURL UPLOAD\n"); } 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, 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; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Running curl interaction\n"); 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; 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_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_ACCEPT_ENCODING, key))) { TALER_LOG_INFO ("Compressed request\n"); curl_easy_setopt (hr->curl, CURLOPT_ENCODING, value); return MHD_YES; } if (GNUNET_YES == malform_upload) { if (0 == strcmp ("Content-Length", key)) { GNUNET_asprintf (&new_value, "%lu", malformed_size); value = new_value; malform_upload = GNUNET_NO; } } 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. * * @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_response_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_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 void modify_object (struct MHD_Connection *con, json_t *json, char *path) { char *target; unsigned int ret_modify; json_t *parent; json_t *new_value; json_error_t error; if (GNUNET_OK != walk_response_object (path, &parent, &target, json)) return; /* 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); path[0] = '\0'; GNUNET_free (target); json_decref (new_value); return; 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); if (-1 == ret_modify) TALER_LOG_WARNING ("Could not replace '%s'\n", target); path[0] = '\0'; GNUNET_free (target); json_decref (new_value); return; } /** * Flip a random character to the string pointed to by @a path. * * @param con FIXME deprecated. * @param json the object whose filed will be flipped. * @param flip_path the path to the string-field to flip. */ static void 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_response_object (flip_path, &parent, &target, json)) return; /* here, element is the parent of the element to be deleted. */ int ret_flip = -1; 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)) ret_flip = GNUNET_YES; GNUNET_free (current_value_flip); if (-1 == ret_flip) TALER_LOG_WARNING ("Could not flip '%s'\n", target); flip_path[0] = '\0'; GNUNET_free (target); return; } /** * 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 MHD_YES / MHD_NO depending on successful / failing * response queueing. */ static void delete_object (struct MHD_Connection *con, struct HttpRequest *hr) { char *target; json_t *parent; if (GNUNET_OK != walk_response_object (delete_path, &parent, &target, hr->json)) return; /* 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)); } if (-1 == ret_deletion) TALER_LOG_WARNING ("Could not delete '%s'\n", target); delete_path[0] = '\0'; GNUNET_free (target); return; } /** * Try to compress a response body. * Updates @a buf and @a buf_size. * * @param[in,out] buf pointer to body to compress * @param[in,out] buf_size pointer to initial size of @a buf * @return #MHD_YES if @a buf was compressed */ static int body_compress (void **buf, size_t *buf_size) { Bytef *cbuf; uLongf cbuf_size; int ret; cbuf_size = compressBound (*buf_size); cbuf = malloc (cbuf_size); if (NULL == cbuf) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Could not allocate the compression buffer\n"); return MHD_NO; } ret = compress (cbuf, &cbuf_size, (const Bytef *) *buf, *buf_size); if (Z_OK != ret) { /* compression failed */ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Compression failed\n"); free (cbuf); return MHD_NO; } free (*buf); *buf = (void *) cbuf; *buf_size = (size_t) cbuf_size; 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; 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_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_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; if (GNUNET_YES == hr->curl_paused) { hr->curl_paused = GNUNET_NO; curl_easy_pause (hr->curl, CURLPAUSE_CONT); } return MHD_YES; } /* Upload (*from the client*) finished */ if (REQUEST_STATE_UPLOAD_STARTED == hr->state) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Finished processing UPLOAD\n"); hr->state = REQUEST_STATE_UPLOAD_DONE; } /* 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; } if ('\0' != flip_path_ul[0]) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Going to flip the upload object (%.*s)\n", hr->io_len, hr->io_buf); hr->json = json_loadb (hr->io_buf, hr->io_len, JSON_REJECT_DUPLICATES, NULL); flip_object (con, hr->json, flip_path_ul); /* Existing io_len is enough to accomodate this encoding. */ json_dumpb (hr->json, hr->io_buf, hr->io_len, JSON_COMPACT); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Flipped (?) upload object (%.*s)\n", hr->io_len, hr->io_buf); json_decref (hr->json); } if ('\0' != modify_path_ul[0]) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Going to modify the upload object (%.*s)\n", hr->io_len, hr->io_buf); hr->json = json_loads (hr->io_buf, JSON_REJECT_DUPLICATES, NULL); modify_object (con, hr->json, modify_path_ul); /* Existing io_len is enough to accomodate this encoding. */ json_dumpb (hr->json, hr->io_buf, hr->io_len, JSON_COMPACT); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Modified (?) upload object (%.*s)\n", hr->io_len, hr->io_buf); 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); } if (0 != hr->io_len) curl_easy_setopt (hr->curl, CURLOPT_POSTFIELDSIZE, 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); { char *curlurl; 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); } if (0 == strcasecmp (meth, MHD_HTTP_METHOD_PUT)) { curl_easy_setopt (hr->curl, CURLOPT_UPLOAD, 1L); curl_easy_setopt (hr->curl, CURLOPT_READFUNCTION, &curl_upload_cb); curl_easy_setopt (hr->curl, CURLOPT_READDATA, hr); { const char *us; long upload_size; us = MHD_lookup_connection_value (con, MHD_HEADER_KIND, MHD_HTTP_HEADER_CONTENT_LENGTH); if ( (1 == sscanf (us, "%ld", &upload_size)) && (upload_size >= 0) ) curl_easy_setopt (hr->curl, CURLOPT_INFILESIZE, upload_size); } curl_easy_setopt (hr->curl, CURLOPT_WRITEFUNCTION, &curl_download_cb); curl_easy_setopt (hr->curl, CURLOPT_WRITEDATA, hr); } else if (0 == strcasecmp (meth, MHD_HTTP_METHOD_POST)) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Crafting a CURL POST request\n"); hr->state = REQUEST_STATE_UPLOAD_STARTED; curl_easy_setopt (hr->curl, CURLOPT_POST, 1L); curl_easy_setopt (hr->curl, CURLOPT_VERBOSE, 1L); GNUNET_assert (CURLE_OK == curl_easy_setopt (hr->curl, CURLOPT_READFUNCTION, &curl_upload_cb)); curl_easy_setopt (hr->curl, CURLOPT_READDATA, hr); { const char *us; long upload_size; us = MHD_lookup_connection_value (con, MHD_HEADER_KIND, MHD_HTTP_HEADER_CONTENT_LENGTH); if ( (1 == sscanf (us, "%ld", &upload_size)) && (upload_size >= 0) ) { curl_easy_setopt (hr->curl, CURLOPT_INFILESIZE, upload_size); } } curl_easy_setopt (hr->curl, CURLOPT_WRITEFUNCTION, &curl_download_cb); curl_easy_setopt (hr->curl, CURLOPT_WRITEDATA, hr); } else if (0 == strcasecmp (meth, MHD_HTTP_METHOD_HEAD)) { hr->state = REQUEST_STATE_DOWNLOAD_STARTED; curl_easy_setopt (hr->curl, CURLOPT_NOBODY, 1L); } 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, 1L); 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 (); /* means (?) upload is over. */ if (0 == hr->io_len) hr->state = REQUEST_STATE_DOWNLOAD_STARTED; return MHD_YES; } if (REQUEST_STATE_DOWNLOAD_DONE != hr->state) { MHD_suspend_connection (con); hr->suspended = GNUNET_YES; return MHD_YES; /* wait for curl */ } if (0 != hack_response_code) { hr->response_code = hack_response_code; hack_response_code = 0; /* reset for next request */ } if ('\0' != flip_path_dl[0]) { TALER_LOG_DEBUG ("Will flip path: %s\n", flip_path_dl); flip_object (con, hr->json, flip_path_dl); } if ('\0' != delete_path[0]) { TALER_LOG_DEBUG ("Will delete path: %s\n", delete_path); delete_object (con, hr); } if ('\0' != modify_path_dl[0]) { TALER_LOG_DEBUG ("Will modify path: %s to value %s\n", modify_path_dl, modify_value); modify_object (con, hr->json, modify_path_dl); } if (NULL != hr->json) { TALER_LOG_DEBUG ("Parsing final JSON.\n"); GNUNET_free (hr->io_buf); hr->io_buf = json_dumps (hr->json, JSON_COMPACT); hr->io_len = strlen (hr->io_buf); TALER_LOG_DEBUG ("%.*s\n", hr->io_len, 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; } /** * COMPRESSION STARTS HERE (hr->io_buf/len is data). */ if (GNUNET_YES == hr->deflate) GNUNET_assert (MHD_YES == body_compress ((void **) &hr->io_buf, &hr->io_len)); hr->response = MHD_create_response_from_buffer (hr->io_len, hr->io_buf, MHD_RESPMEM_MUST_COPY); 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->mod_response)) /* Destroy hacked responses. */ MHD_destroy_response (hr->mod_response); 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; } /** * 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) { unsigned long long port; (void) cls; (void) service; cfg = c; delete_path[0] = '\0'; 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_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 | MHD_ALLOW_SUSPEND_RESUME | MHD_USE_DUAL_STACK, (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) { (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 void handle_modify_path_dl (void *cls, const struct TWISTER_ModifyPath *src) { struct GNUNET_SERVICE_Client *c = cls; strcpy (modify_path_dl, src->path); strcpy (modify_value, src->value); send_acknowledgement (c); } /** * 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; strcpy (modify_path_ul, src->path); strcpy (modify_value, src->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; strcpy (flip_path_dl, src->path); send_acknowledgement (c); } /** * 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; strcpy (flip_path_ul, src->path); send_acknowledgement (c); } /** * 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; strcpy (delete_path, src->path); 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_fixed_size (modify_path_ul, TWISTER_MESSAGE_TYPE_MODIFY_PATH_UL, struct TWISTER_ModifyPath, NULL), GNUNET_MQ_hd_fixed_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_fixed_size (delete_path, TWISTER_MESSAGE_TYPE_DELETE_PATH, struct TWISTER_DeletePath, NULL), GNUNET_MQ_hd_fixed_size (flip_path_ul, TWISTER_MESSAGE_TYPE_FLIP_PATH_UL, struct TWISTER_FlipPath, NULL), GNUNET_MQ_hd_fixed_size (flip_path_dl, TWISTER_MESSAGE_TYPE_FLIP_PATH_DL, struct TWISTER_FlipPath, NULL), GNUNET_MQ_handler_end ()); /* end of taler-twister-service.c */