diff options
Diffstat (limited to 'src/backend/taler-merchant-httpd_exchanges.c')
-rw-r--r-- | src/backend/taler-merchant-httpd_exchanges.c | 540 |
1 files changed, 540 insertions, 0 deletions
diff --git a/src/backend/taler-merchant-httpd_exchanges.c b/src/backend/taler-merchant-httpd_exchanges.c new file mode 100644 index 00000000..59e97495 --- /dev/null +++ b/src/backend/taler-merchant-httpd_exchanges.c @@ -0,0 +1,540 @@ +/* + This file is part of TALER + (C) 2014, 2015 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file backend/taler-merchant-httpd_exchanges.c + * @brief logic this HTTPD keeps for each exchange we interact with + * @author Marcello Stanisci + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler-merchant-httpd_exchanges.h" + + +/** + * How often do we retry fetching /keys? + */ +#define KEYS_RETRY_FREQ GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 60) + + +/** + * Exchange + */ +struct Exchange; + + +/** + * Information we keep for a pending #MMH_EXCHANGES_find_exchange() operation. + */ +struct TMH_EXCHANGES_FindOperation +{ + + /** + * Kept in a DLL. + */ + struct TMH_EXCHANGES_FindOperation *next; + + /** + * Kept in a DLL. + */ + struct TMH_EXCHANGES_FindOperation *prev; + + /** + * Function to call with the result. + */ + TMH_EXCHANGES_FindContinuation fc; + + /** + * Closure for @e fc. + */ + void *fc_cls; + + /** + * Exchange we wait for the /keys for. + */ + struct Exchange *my_exchange; + + /** + * Task scheduled to asynchrnously return the result. + */ + struct GNUNET_SCHEDULER_Task *at; + +}; + + +/** + * Exchange + */ +struct Exchange +{ + + /** + * Kept in a DLL. + */ + struct Exchange *next; + + /** + * Kept in a DLL. + */ + struct Exchange *prev; + + /** + * Head of FOs pending for this exchange. + */ + struct TMH_EXCHANGES_FindOperation *fo_head; + + /** + * Tail of FOs pending for this exchange. + */ + struct TMH_EXCHANGES_FindOperation *fo_tail; + + /** + * (base) URI of the exchange. + */ + char *uri; + + /** + * A connection to this exchange + */ + struct TALER_EXCHANGE_Handle *conn; + + /** + * Master public key, guaranteed to be set ONLY for + * trusted exchanges. + */ + struct TALER_MasterPublicKeyP master_pub; + + /** + * At what time should we try to fetch /keys again? + */ + struct GNUNET_TIME_Absolute retry_time; + + /** + * Flag which indicates whether some HTTP transfer between + * this merchant and the exchange is still ongoing + */ + int pending; + + /** + * #GNUNET_YES if this exchange is from our configuration and + * explicitly trusted, #GNUNET_NO if we need to check each + * key to be sure it is trusted. + */ + int trusted; + +}; + + +/** + * Context for all exchange operations (useful to the event loop) + */ +static struct TALER_EXCHANGE_Context *ctx; + +/** + * Task we use to drive the interaction with this exchange. + */ +static struct GNUNET_SCHEDULER_Task *poller_task; + +/** + * Head of exchanges we know about. + */ +static struct Exchange *exchange_head; + +/** + * Tail of exchanges we know about. + */ +static struct Exchange *exchange_tail; + +/** + * List of our trusted exchanges for inclusion in contracts. + */ +json_t *trusted_exchanges; + + +/** + * Function called with information about who is auditing + * a particular exchange and what key the exchange is using. + * + * @param cls closure, will be `struct Exchange` so that + * when this function gets called, it will change the flag 'pending' + * to 'false'. Note: 'keys' is automatically saved inside the exchange's + * handle, which is contained inside 'struct Exchange', when + * this callback is called. Thus, once 'pending' turns 'false', + * it is safe to call 'TALER_EXCHANGE_get_keys()' on the exchange's handle, + * in order to get the "good" keys. + * @param keys information about the various keys used + * by the exchange + */ +static void +keys_mgmt_cb (void *cls, + const struct TALER_EXCHANGE_Keys *keys) +{ + struct Exchange *exchange = cls; + struct TMH_EXCHANGES_FindOperation *fo; + + if (NULL != keys) + { + exchange->pending = GNUNET_NO; + } + else + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Failed to fetch /keys from `%s'\n", + exchange->uri); + TALER_EXCHANGE_disconnect (exchange->conn); + exchange->conn = NULL; + exchange->pending = GNUNET_SYSERR; /* failed hard */ + exchange->retry_time = GNUNET_TIME_relative_to_absolute (KEYS_RETRY_FREQ); + } + while (NULL != (fo = exchange->fo_head)) + { + GNUNET_CONTAINER_DLL_remove (exchange->fo_head, + exchange->fo_tail, + fo); + fo->fc (fo->fc_cls, + (NULL != keys) ? exchange->conn : NULL, + exchange->trusted); + GNUNET_free (fo); + } +} + + +/** + * Task that runs the exchange's event loop using the GNUnet scheduler. + * + * @param cls a `struct Exchange *` + * @param tc scheduler context (unused) + */ +static void +context_task (void *cls, + const struct GNUNET_SCHEDULER_TaskContext *tc) +{ + long timeout; + int max_fd; + fd_set read_fd_set; + fd_set write_fd_set; + fd_set except_fd_set; + struct GNUNET_NETWORK_FDSet *rs; + struct GNUNET_NETWORK_FDSet *ws; + struct GNUNET_TIME_Relative delay; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "In exchange context polling task\n"); + + poller_task = NULL; + TALER_EXCHANGE_perform (ctx); + max_fd = -1; + timeout = -1; + FD_ZERO (&read_fd_set); + FD_ZERO (&write_fd_set); + FD_ZERO (&except_fd_set); + TALER_EXCHANGE_get_select_info (ctx, + &read_fd_set, + &write_fd_set, + &except_fd_set, + &max_fd, + &timeout); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "In exchange context polling task, max_fd=%d, timeout=%ld\n", + max_fd, timeout); + if (timeout >= 0) + delay = + GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, + timeout); + else + delay = GNUNET_TIME_UNIT_FOREVER_REL; + rs = GNUNET_NETWORK_fdset_create (); + GNUNET_NETWORK_fdset_copy_native (rs, + &read_fd_set, + max_fd + 1); + ws = GNUNET_NETWORK_fdset_create (); + GNUNET_NETWORK_fdset_copy_native (ws, + &write_fd_set, + max_fd + 1); + poller_task = + GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_DEFAULT, + delay, + rs, + ws, + &context_task, + NULL); + GNUNET_NETWORK_fdset_destroy (rs); + GNUNET_NETWORK_fdset_destroy (ws); +} + + +/** + * Task to return find operation result asynchronously to caller. + * + * @param cls a `struct TMH_EXCHANGES_FindOperation` + * @param tc unused + */ +static void +return_result (void *cls, + const struct GNUNET_SCHEDULER_TaskContext *tc) +{ + struct TMH_EXCHANGES_FindOperation *fo = cls; + struct Exchange *exchange = fo->my_exchange; + + fo->at = NULL; + GNUNET_CONTAINER_DLL_remove (exchange->fo_head, + exchange->fo_tail, + fo); + fo->fc (fo->fc_cls, + (GNUNET_SYSERR == exchange->pending) ? NULL : exchange->conn, + exchange->trusted); + GNUNET_free (fo); + GNUNET_SCHEDULER_cancel (poller_task); + GNUNET_SCHEDULER_add_now (&context_task, + NULL); +} + + +/** + * Find a exchange that matches @a chosen_exchange. If we cannot connect + * to the exchange, or if it is not acceptable, @a fc is called with + * NULL for the exchange. + * + * @param chosen_exchange URI of the exchange we would like to talk to + * @param fc function to call with the handles for the exchange + * @param fc_cls closure for @a fc + * @return NULL on error + */ +struct TMH_EXCHANGES_FindOperation * +TMH_EXCHANGES_find_exchange (const char *chosen_exchange, + TMH_EXCHANGES_FindContinuation fc, + void *fc_cls) +{ + struct Exchange *exchange; + struct TMH_EXCHANGES_FindOperation *fo; + + if (NULL == ctx) + { + GNUNET_break (0); + return NULL; + } + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Trying to find chosen exchange `%s'\n", + chosen_exchange); + + /* Check if the exchange is known */ + for (exchange = exchange_head; NULL != exchange; exchange = exchange->next) + /* test it by checking public key --- FIXME: hostname or public key!? + Should probably be URI, not hostname anyway! */ + if (0 == strcmp (exchange->uri, + chosen_exchange)) + break; + if (NULL == exchange) + { + /* This is a new exchange */ + exchange = GNUNET_new (struct Exchange); + exchange->uri = GNUNET_strdup (chosen_exchange); + exchange->pending = GNUNET_YES; + GNUNET_CONTAINER_DLL_insert (exchange_head, + exchange_tail, + exchange); + } + + /* check if we should resume this exchange */ + if ( (GNUNET_SYSERR == exchange->pending) && + (0 == GNUNET_TIME_absolute_get_remaining (exchange->retry_time).rel_value_us) ) + exchange->pending = GNUNET_YES; + + + fo = GNUNET_new (struct TMH_EXCHANGES_FindOperation); + fo->fc = fc; + fo->fc_cls = fc_cls; + fo->my_exchange = exchange; + GNUNET_CONTAINER_DLL_insert (exchange->fo_head, + exchange->fo_tail, + fo); + + if (GNUNET_NO == exchange->pending) + { + /* We are not currently waiting for a reply, immediately + return result */ + fo->at = GNUNET_SCHEDULER_add_now (&return_result, + fo); + return fo; + } + + /* If new or resumed, retry fetching /keys */ + if ( (NULL == exchange->conn) && + (GNUNET_YES == exchange->pending) ) + { + exchange->conn = TALER_EXCHANGE_connect (ctx, + exchange->uri, + &keys_mgmt_cb, + exchange, + TALER_EXCHANGE_OPTION_END); + GNUNET_break (NULL != exchange->conn); + } + return fo; +} + + +/** + * Abort pending find operation. + * + * @param fo handle to operation to abort + */ +void +TMH_EXCHANGES_find_exchange_cancel (struct TMH_EXCHANGES_FindOperation *fo) +{ + struct Exchange *exchange = fo->my_exchange; + + if (NULL != fo->at) + { + GNUNET_SCHEDULER_cancel (fo->at); + fo->at = NULL; + } + GNUNET_CONTAINER_DLL_remove (exchange->fo_head, + exchange->fo_tail, + fo); + GNUNET_free (fo); +} + + +/** + * Function called on each configuration section. Finds sections + * about exchanges and parses the entries. + * + * @param cls closure, with a `const struct GNUNET_CONFIGURATION_Handle *` + * @param section name of the section + */ +static void +parse_exchanges (void *cls, + const char *section) +{ + const struct GNUNET_CONFIGURATION_Handle *cfg = cls; + char *uri; + char *mks; + struct Exchange *exchange; + + if (0 != strncasecmp (section, + "exchange-", + strlen ("exchange-"))) + return; + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + section, + "URI", + &uri)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + section, + "URI"); + return; + } + exchange = GNUNET_new (struct Exchange); + exchange->uri = uri; + if (GNUNET_OK == + GNUNET_CONFIGURATION_get_value_string (cfg, + section, + "MASTER_KEY", + &mks)) + { + if (GNUNET_OK == + GNUNET_CRYPTO_eddsa_public_key_from_string (mks, + strlen (mks), + &exchange->master_pub.eddsa_pub)) + { + exchange->trusted = GNUNET_YES; + } + else + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + section, + "MASTER_KEY", + _("ill-formed key")); + } + GNUNET_free (mks); + } + GNUNET_CONTAINER_DLL_insert (exchange_head, + exchange_tail, + exchange); + exchange->pending = GNUNET_YES; + exchange->conn = TALER_EXCHANGE_connect (ctx, + exchange->uri, + &keys_mgmt_cb, + exchange, + TALER_EXCHANGE_OPTION_END); + GNUNET_break (NULL != exchange->conn); +} + + +/** + * Parses "trusted" exchanges listed in the configuration. + * + * @param cfg the configuration + * @return #GNUNET_OK on success; #GNUNET_SYSERR upon error in + * parsing. + */ +int +TMH_EXCHANGES_init (const struct GNUNET_CONFIGURATION_Handle *cfg) +{ + struct Exchange *exchange; + json_t *j_exchange; + + ctx = TALER_EXCHANGE_init (); + if (NULL == ctx) + return GNUNET_SYSERR; + GNUNET_CONFIGURATION_iterate_sections (cfg, + &parse_exchanges, + (void *) cfg); + /* build JSON with list of trusted exchanges */ + trusted_exchanges = json_array (); + for (exchange = exchange_head; NULL != exchange; exchange = exchange->next) + { + if (GNUNET_YES != exchange->trusted) + continue; + j_exchange = json_pack ("{s:s, s:o}", + "url", exchange->uri, + "master_pub", TALER_json_from_data (&exchange->master_pub, + sizeof (struct TALER_MasterPublicKeyP))); + json_array_append_new (trusted_exchanges, + j_exchange); + } + poller_task = GNUNET_SCHEDULER_add_now (&context_task, + NULL); + return GNUNET_OK; +} + + +/** + * Function called to shutdown the exchanges subsystem. + */ +void +TMH_EXCHANGES_done () +{ + struct Exchange *exchange; + + while (NULL != (exchange = exchange_head)) + { + GNUNET_CONTAINER_DLL_remove (exchange_head, + exchange_tail, + exchange); + if (NULL != exchange->conn) + TALER_EXCHANGE_disconnect (exchange->conn); + GNUNET_free (exchange->uri); + GNUNET_free (exchange); + } + if (NULL != poller_task) + { + GNUNET_SCHEDULER_cancel (poller_task); + poller_task = NULL; + } + TALER_EXCHANGE_fini (ctx); +} |