/*
This file is part of TALER
(C) 2014, 2015, 2016 INRIA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero 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
*/
/**
* @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
#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", GNUNET_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);
}