diff options
author | Christian Grothoff <christian@grothoff.org> | 2020-04-17 14:07:06 +0200 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2020-04-17 14:07:24 +0200 |
commit | 0b8e550d09635f762033626c8c12b7b4a0d0faf7 (patch) | |
tree | 986a09d537a5dfde4b65fabd018c0f74d2778457 /src/backend | |
parent | 0a327ceebd3126d4adf69916e92702fe3c7a22e2 (diff) | |
download | merchant-0b8e550d09635f762033626c8c12b7b4a0d0faf7.tar.gz merchant-0b8e550d09635f762033626c8c12b7b4a0d0faf7.tar.bz2 merchant-0b8e550d09635f762033626c8c12b7b4a0d0faf7.zip |
starting v1 protocol dispatching logic
Diffstat (limited to 'src/backend')
-rw-r--r-- | src/backend/Makefile.am | 6 | ||||
-rw-r--r-- | src/backend/legacy.c | 509 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd.c | 1335 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd.h | 308 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd_config.c | 95 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd_config.h | 12 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd_exchanges.c | 28 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd_mhd.c | 38 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd_mhd.h | 26 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd_private-get-instances.c | 160 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd_transfers-get.h | 49 |
11 files changed, 1214 insertions, 1352 deletions
diff --git a/src/backend/Makefile.am b/src/backend/Makefile.am index 082c35f9..f47cf088 100644 --- a/src/backend/Makefile.am +++ b/src/backend/Makefile.am @@ -21,10 +21,12 @@ taler_merchant_httpd_SOURCES = \ taler-merchant-httpd.c taler-merchant-httpd.h \ taler-merchant-httpd_auditors.c taler-merchant-httpd_auditors.h \ taler-merchant-httpd_config.c taler-merchant-httpd_config.h \ - taler-merchant-httpd_check-payment.c taler-merchant-httpd_check-payment.h \ taler-merchant-httpd_exchanges.c taler-merchant-httpd_exchanges.h \ + taler-merchant-httpd_mhd.c taler-merchant-httpd_mhd.h + +DEAD = \ + taler-merchant-httpd_check-payment.c taler-merchant-httpd_check-payment.h \ taler-merchant-httpd_history.c taler-merchant-httpd_history.h \ - taler-merchant-httpd_mhd.c taler-merchant-httpd_mhd.h \ taler-merchant-httpd_order.c taler-merchant-httpd_order.h \ taler-merchant-httpd_pay.c taler-merchant-httpd_pay.h \ taler-merchant-httpd_poll-payment.c taler-merchant-httpd_poll-payment.h \ diff --git a/src/backend/legacy.c b/src/backend/legacy.c new file mode 100644 index 00000000..c105ecb3 --- /dev/null +++ b/src/backend/legacy.c @@ -0,0 +1,509 @@ +/** + * Create a taler://pay/ URI for the given @a con and @a order_id + * and @a session_id and @a instance_id. + * + * @param con HTTP connection + * @param order_id the order id + * @param session_id session, may be NULL + * @param instance_id instance, may be "default" + * @return corresponding taler://pay/ URI, or NULL on missing "host" + */ +char * +TMH_make_taler_pay_uri (struct MHD_Connection *con, + const char *order_id, + const char *session_id, + const char *instance_id) +{ + const char *host; + const char *forwarded_host; + const char *uri_path; + const char *uri_instance_id; + const char *query; + char *result; + + host = MHD_lookup_connection_value (con, + MHD_HEADER_KIND, + "Host"); + forwarded_host = MHD_lookup_connection_value (con, + MHD_HEADER_KIND, + "X-Forwarded-Host"); + + uri_path = MHD_lookup_connection_value (con, + MHD_HEADER_KIND, + "X-Forwarded-Prefix"); + if (NULL == uri_path) + uri_path = "-"; + if (NULL != forwarded_host) + host = forwarded_host; + if (0 == strcmp (instance_id, + "default")) + uri_instance_id = "-"; + else + uri_instance_id = instance_id; + if (NULL == host) + { + /* Should never happen, at least the host header should be defined */ + GNUNET_break (0); + return NULL; + } + + if (GNUNET_YES == TALER_mhd_is_https (con)) + query = ""; + else + query = "?insecure=1"; + GNUNET_assert (NULL != order_id); + GNUNET_assert (0 < GNUNET_asprintf (&result, + "taler://pay/%s/%s/%s/%s%s%s%s", + host, + uri_path, + uri_instance_id, + order_id, + (NULL == session_id) ? "" : "/", + (NULL == session_id) ? "" : session_id, + query)); + return result; +} + + +/** + * Closure for the #wireformat_iterator_cb(). + */ +struct WireFormatIteratorContext +{ + /** + * The global iteration context. + */ + struct IterateInstancesCls *iic; + + /** + * The merchant instance we are currently building. + */ + struct MerchantInstance *mi; + + /** + * Set to #GNUNET_YES if the default instance was found. + */ + int default_instance; +}; + + +/** + * Callback that looks for 'merchant-account-*' sections, + * and populates our wire method according to the data + * + * @param cls closure with a `struct WireFormatIteratorContext *` + * @section section name this callback gets + */ +static void +wireformat_iterator_cb (void *cls, + const char *section) +{ + struct WireFormatIteratorContext *wfic = cls; + struct MerchantInstance *mi = wfic->mi; + struct IterateInstancesCls *iic = wfic->iic; + char *instance_option; + struct WireMethod *wm; + char *payto; + char *fn; + json_t *j; + struct GNUNET_HashCode jh_wire; + char *wire_file_mode; + + if (0 != strncasecmp (section, + "merchant-account-", + strlen ("merchant-account-"))) + return; + GNUNET_asprintf (&instance_option, + "HONOR_%s", + mi->id); + if (GNUNET_YES != + GNUNET_CONFIGURATION_get_value_yesno (cfg, + section, + instance_option)) + { + GNUNET_free (instance_option); + return; + } + GNUNET_free (instance_option); + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + section, + "PAYTO_URI", + &payto)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + section, + "PAYTO_URI"); + iic->ret = GNUNET_SYSERR; + return; + } + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_filename (cfg, + section, + "WIRE_RESPONSE", + &fn)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + section, + "WIRE_RESPONSE"); + GNUNET_free (payto); + iic->ret = GNUNET_SYSERR; + return; + } + + /* Try loading existing JSON from file */ + if (GNUNET_YES == + GNUNET_DISK_file_test (fn)) + { + json_error_t err; + char *url; + + if (NULL == + (j = json_load_file (fn, + JSON_REJECT_DUPLICATES, + &err))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to load JSON from `%s': %s at %d:%d\n", + fn, + err.text, + err.line, + err.column); + GNUNET_free (fn); + GNUNET_free (payto); + iic->ret = GNUNET_SYSERR; + return; + } + url = TALER_JSON_wire_to_payto (j); + if (NULL == url) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "URL missing in `%s', disabling account `%s'\n", + fn, + section); + GNUNET_free (fn); + GNUNET_free (payto); + iic->ret = GNUNET_SYSERR; + return; + } + if (0 != strcasecmp (url, + payto)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "URL `%s' does not match configuration `%s', disabling account `%s'\n", + url, + payto, + section); + GNUNET_free (fn); + GNUNET_free (payto); + GNUNET_free (url); + iic->ret = GNUNET_SYSERR; + return; + } + GNUNET_free (url); + } + else /* need to generate JSON */ + { + struct GNUNET_HashCode salt; + char *salt_str; + + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, + &salt, + sizeof (salt)); + salt_str = GNUNET_STRINGS_data_to_string_alloc (&salt, + sizeof (salt)); + j = json_pack ("{s:s, s:s}", + "payto_uri", payto, + "salt", salt_str); + GNUNET_free (salt_str); + + /* Make sure every path component exists. */ + if (GNUNET_OK != GNUNET_DISK_directory_create_for_file (fn)) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, + "mkdir", + fn); + GNUNET_free (fn); + GNUNET_free (payto); + json_decref (j); + iic->ret = GNUNET_SYSERR; + return; + } + + if (0 != json_dump_file (j, + fn, + JSON_COMPACT | JSON_SORT_KEYS)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to write hashed wire details to `%s'\n", + fn); + GNUNET_free (fn); + GNUNET_free (payto); + json_decref (j); + iic->ret = GNUNET_SYSERR; + return; + } + + if (GNUNET_OK == + GNUNET_CONFIGURATION_get_value_string (cfg, + section, + "WIRE_FILE_MODE", + &wire_file_mode)) + { + errno = 0; + mode_t mode = (mode_t) strtoul (wire_file_mode, + NULL, + 8); + if (0 != errno) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + section, + "WIRE_FILE_MODE", + "Must be octal number\n"); + iic->ret = GNUNET_SYSERR; + GNUNET_free (fn); + return; + } + if (0 != chmod (fn, mode)) + { + TALER_LOG_ERROR ("chmod failed on %s\n", fn); + iic->ret = GNUNET_SYSERR; + GNUNET_free (fn); + return; + } + } + } + + GNUNET_free (fn); + + if (GNUNET_OK != + TALER_JSON_merchant_wire_signature_hash (j, + &jh_wire)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to hash wire input\n"); + GNUNET_free (fn); + GNUNET_free (payto); + json_decref (j); + iic->ret = GNUNET_SYSERR; + return; + } + + wm = GNUNET_new (struct WireMethod); + wm->wire_method = TALER_payto_get_method (payto); + GNUNET_free (payto); + GNUNET_asprintf (&instance_option, + "ACTIVE_%s", + mi->id); + wm->active = GNUNET_CONFIGURATION_get_value_yesno (cfg, + section, + instance_option); + GNUNET_free (instance_option); + if (GNUNET_YES == wm->active) + GNUNET_CONTAINER_DLL_insert (mi->wm_head, + mi->wm_tail, + wm); + else + GNUNET_CONTAINER_DLL_insert_tail (mi->wm_head, + mi->wm_tail, + wm); + wm->j_wire = j; + wm->h_wire = jh_wire; +} + + +/** + * Callback that looks for 'instance-*' sections, + * and populates accordingly each instance's data + * + * @param cls closure of type `struct IterateInstancesCls` + * @section section name this callback gets + */ +static void +instances_iterator_cb (void *cls, + const char *section) +{ + struct IterateInstancesCls *iic = cls; + char *token; + struct MerchantInstance *mi; + /* used as hashmap keys */ + struct GNUNET_HashCode h_pk; + struct GNUNET_HashCode h_id; + + if (0 != strncasecmp (section, + "instance-", + strlen ("instance-"))) + return; + /** Get id **/ + token = strrchr (section, '-'); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Extracted token: %s\n", + token + 1); + mi = GNUNET_new (struct MerchantInstance); + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + section, + "NAME", + &mi->name)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + section, + "NAME"); + GNUNET_free (mi); + iic->ret = GNUNET_SYSERR; + return; + } + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_filename (cfg, + section, + "KEYFILE", + &mi->keyfile)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + section, + "KEYFILE"); + GNUNET_free (mi->name); + GNUNET_free (mi); + iic->ret = GNUNET_SYSERR; + return; + } + if (GNUNET_OK == + GNUNET_CONFIGURATION_get_value_string (cfg, + section, + "TIP_EXCHANGE", + &mi->tip_exchange)) + { + char *tip_reserves; + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_filename (cfg, + section, + "TIP_RESERVE_PRIV_FILENAME", + &tip_reserves)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + section, + "TIP_RESERVE_PRIV_FILENAME"); + GNUNET_free (mi->keyfile); + GNUNET_free (mi->name); + GNUNET_free (mi); + iic->ret = GNUNET_SYSERR; + return; + } + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_key_from_file (tip_reserves, + GNUNET_NO, + &mi->tip_reserve.eddsa_priv)) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + section, + "TIP_RESERVE_PRIV_FILENAME", + "Failed to read private key"); + GNUNET_free (tip_reserves); + GNUNET_free (mi->keyfile); + GNUNET_free (mi->name); + GNUNET_free (mi); + iic->ret = GNUNET_SYSERR; + return; + } + GNUNET_free (tip_reserves); + } + + if (GNUNET_SYSERR == + GNUNET_CRYPTO_eddsa_key_from_file (mi->keyfile, + GNUNET_YES, + &mi->privkey.eddsa_priv)) + { + GNUNET_break (0); + GNUNET_free (mi->keyfile); + GNUNET_free (mi->name); + GNUNET_free (mi); + iic->ret = GNUNET_SYSERR; + return; + } + GNUNET_CRYPTO_eddsa_key_get_public (&mi->privkey.eddsa_priv, + &mi->pubkey.eddsa_pub); + + mi->id = GNUNET_strdup (token + 1); + if (0 == strcasecmp ("default", + mi->id)) + iic->default_instance = GNUNET_YES; + + GNUNET_CRYPTO_hash (mi->id, + strlen (mi->id), + &h_id); + if (GNUNET_OK != + GNUNET_CONTAINER_multihashmap_put (by_id_map, + &h_id, + mi, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to put an entry into the 'by_id' hashmap\n"); + iic->ret = GNUNET_SYSERR; + GNUNET_free (mi->keyfile); + GNUNET_free (mi->name); + GNUNET_free (mi); + return; + } + GNUNET_CRYPTO_hash (&mi->pubkey.eddsa_pub, + sizeof (struct GNUNET_CRYPTO_EddsaPublicKey), + &h_pk); + + + /* Initialize wireformats */ + { + struct WireFormatIteratorContext wfic = { + .iic = iic, + .mi = mi + }; + + GNUNET_CONFIGURATION_iterate_sections (cfg, + &wireformat_iterator_cb, + &wfic); + } + if (NULL == mi->wm_head) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to load wire formats for instance `%s'\n", + mi->id); + iic->ret = GNUNET_SYSERR; + } + +} + + +/** + * Iterate over each merchant instance, in order to populate + * each instance's own data + * + * @return #GNUNET_OK if successful, #GNUNET_SYSERR upon errors + * (for example, if no "default" instance is defined) + */ +static int +iterate_instances (void) +{ + struct IterateInstancesCls iic; + + iic.default_instance = GNUNET_NO; + iic.ret = GNUNET_OK; + GNUNET_CONFIGURATION_iterate_sections (cfg, + &instances_iterator_cb, + &iic); + + if (GNUNET_NO == iic.default_instance) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "No default merchant instance found\n"); + return GNUNET_SYSERR; + } + if (GNUNET_OK != iic.ret) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "At least one instance was not successfully parsed\n"); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} diff --git a/src/backend/taler-merchant-httpd.c b/src/backend/taler-merchant-httpd.c index 86b36306..60979d2e 100644 --- a/src/backend/taler-merchant-httpd.c +++ b/src/backend/taler-merchant-httpd.c @@ -1,6 +1,6 @@ /* This file is part of TALER - (C) 2014-2018 Taler Systems SA + (C) 2014-2020 Taler Systems SA 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 @@ -32,23 +32,8 @@ #include "taler_merchantdb_lib.h" #include "taler-merchant-httpd.h" #include "taler-merchant-httpd_auditors.h" -#include "taler-merchant-httpd_check-payment.h" #include "taler-merchant-httpd_exchanges.h" -#include "taler-merchant-httpd_history.h" #include "taler-merchant-httpd_mhd.h" -#include "taler-merchant-httpd_order.h" -#include "taler-merchant-httpd_pay.h" -#include "taler-merchant-httpd_poll-payment.h" -#include "taler-merchant-httpd_proposal.h" -#include "taler-merchant-httpd_refund.h" -#include "taler-merchant-httpd_refund_increase.h" -#include "taler-merchant-httpd_refund_lookup.h" -#include "taler-merchant-httpd_track-transaction.h" -#include "taler-merchant-httpd_track-transfer.h" -#include "taler-merchant-httpd_tip-authorize.h" -#include "taler-merchant-httpd_tip-pickup.h" -#include "taler-merchant-httpd_tip-query.h" -#include "taler-merchant-httpd_tip-reserve-helper.h" #include "taler-merchant-httpd_config.h" /** @@ -58,45 +43,26 @@ /** - * Used by the iterator of the various merchant's instances given - * in configuration + * Which currency do we use? */ -struct IterateInstancesCls -{ - - /** - * Current index in the global array of #MerchantInstance - * types. Used by the callback in order to know which index - * is associated to the element being processed. - */ - unsigned int current_index; - - /** - * Flag indicating whether config contains a default instance - */ - unsigned int default_instance; +char *TMH_currency; - /** - * Tells if the parsing encountered any error. We need this - * field since the iterator must return void - */ - unsigned int ret; -}; +/** + * Inform the auditor for all deposit confirmations (global option) + */ +int TMH_force_audit; +/** + * Connection handle to the our database + */ +struct TALER_MERCHANTDB_Plugin *TMH_db; /** * Hashmap pointing at merchant instances by 'id'. An 'id' is * just a string that identifies a merchant instance. When a frontend * needs to specify an instance to the backend, it does so by 'id' */ -struct GNUNET_CONTAINER_MultiHashMap *by_id_map; - -/** - * Hashmap pointing at merchant instances by public key. This map - * is mainly used to check whether there is more than one instance - * using the same key - */ -struct GNUNET_CONTAINER_MultiHashMap *by_kpub_map; +static struct GNUNET_CONTAINER_MultiHashMap *by_id_map; /** * The port we are running on @@ -104,55 +70,9 @@ struct GNUNET_CONTAINER_MultiHashMap *by_kpub_map; static uint16_t port; /** - * This value tells the exchange by which date this merchant would like - * to receive the funds for a deposited payment - */ -struct GNUNET_TIME_Relative default_wire_transfer_delay; - -/** - * Locations from the configuration. Mapping from - * label to location data. - */ -json_t *default_locations; - -/** - * If the frontend does NOT specify a payment deadline, how long should - * offers we make be valid by default? - */ -struct GNUNET_TIME_Relative default_pay_deadline; - -/** - * Default maximum wire fee to assume, unless stated differently in the proposal - * already. - */ -struct TALER_Amount default_max_wire_fee; - -/** - * Default max deposit fee that the merchant is willing to - * pay; if deposit costs more, then the customer will cover - * the difference. - */ -struct TALER_Amount default_max_deposit_fee; - -/** - * Default factor for wire fee amortization. - */ -unsigned long long default_wire_fee_amortization; - -/** * Should a "Connection: close" header be added to each HTTP response? */ -static int TMH_merchant_connection_close; - -/** - * Which currency do we use? - */ -char *TMH_currency; - -/** - * Inform the auditor for all deposit confirmations (global option) - */ -int TMH_force_audit; +static int merchant_connection_close; /** * Task running the HTTP server. @@ -165,11 +85,6 @@ static struct GNUNET_SCHEDULER_Task *mhd_task; static int result; /** - * Connection handle to the our database - */ -struct TALER_MERCHANTDB_Plugin *db; - -/** * The MHD Daemon */ static struct MHD_Daemon *mhd; @@ -178,39 +93,39 @@ static struct MHD_Daemon *mhd; * MIN-Heap of suspended connections to resume when the timeout expires, * ordered by timeout. Values are of type `struct MHD_Connection` */ -struct GNUNET_CONTAINER_Heap *resume_timeout_heap; +static struct GNUNET_CONTAINER_Heap *resume_timeout_heap; /** * Hash map from H(order_id,merchant_pub) to `struct MHD_Connection` * entries to resume when a payment is made for the given order. */ -struct GNUNET_CONTAINER_MultiHashMap *payment_trigger_map; +static struct GNUNET_CONTAINER_MultiHashMap *payment_trigger_map; /** * Task responsible for timeouts in the #resume_timeout_heap. */ -struct GNUNET_SCHEDULER_Task *resume_timeout_task; +static struct GNUNET_SCHEDULER_Task *resume_timeout_task; /** * Our configuration. */ -static struct GNUNET_CONFIGURATION_Handle *cfg; +static const struct GNUNET_CONFIGURATION_Handle *cfg; /** - * Callback that frees all the elements in the hashmap + * Callback that frees all the instances in the hashmap * * @param cls closure, NULL * @param key current key - * @param value a `struct MerchantInstance` + * @param value a `struct TMH_MerchantInstance` */ static int -hashmap_free (void *cls, - const struct GNUNET_HashCode *key, - void *value) +instance_free (void *cls, + const struct GNUNET_HashCode *key, + void *value) { - struct MerchantInstance *mi = value; - struct WireMethod *wm; + struct TMH_MerchantInstance *mi = value; + struct TMH_WireMethod *wm; (void) cls; (void) key; @@ -266,10 +181,10 @@ payment_trigger_free (void *cls, * @param mpub an instance public key * @param key[out] set to the hash map key to use */ -void -TMH_compute_pay_key (const char *order_id, - const struct TALER_MerchantPublicKeyP *mpub, - struct GNUNET_HashCode *key) +static void +compute_pay_key (const char *order_id, + const struct TALER_MerchantPublicKeyP *mpub, + struct GNUNET_HashCode *key) { size_t olen = strlen (order_id); char buf[sizeof (*mpub) + olen]; @@ -287,7 +202,6 @@ TMH_compute_pay_key (const char *order_id, "Pay key for %s is %s\n", order_id, GNUNET_h2s (key)); - } @@ -332,14 +246,21 @@ do_resume (void *cls) /** * Suspend connection from @a sc until payment has been received. * + * @param order_id the order that we are waiting on + * @param mi the merchant instance we are waiting on * @param sc connection to suspend * @param min_refund refund amount we are waiting on to be exceeded before resuming, * NULL if we are not waiting for refunds */ void -TMH_long_poll_suspend (struct TMH_SuspendedConnection *sc, +TMH_long_poll_suspend (const char *order_id, + const struct TMH_MerchantInstance *mi, + struct TMH_SuspendedConnection *sc, const struct TALER_Amount *min_refund) { + compute_pay_key (order_id, + &mi->pubkey, + &sc->key); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Suspending operation on key %s\n", GNUNET_h2s (&sc->key)); @@ -350,7 +271,7 @@ TMH_long_poll_suspend (struct TMH_SuspendedConnection *sc, GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE)); if (NULL != min_refund) { - sc->awaiting_refund = GNUNET_YES; + sc->awaiting_refund = true; sc->refund_expected = *min_refund; } sc->hn = GNUNET_CONTAINER_heap_insert (resume_timeout_heap, @@ -385,7 +306,7 @@ resume_operation (void *cls, const struct TALER_Amount *have_refund = cls; struct TMH_SuspendedConnection *sc = value; - if ( (GNUNET_YES == sc->awaiting_refund) && + if ( (sc->awaiting_refund) && ( (NULL == have_refund) || (1 != TALER_amount_cmp (have_refund, &sc->refund_expected)) ) ) @@ -415,20 +336,20 @@ resume_operation (void *cls, * Find out if we have any clients long-polling for @a order_id to be * confirmed at merchant @a mpub, and if so, tell them to resume. * - * @param order_id the order that was paid - * @param mpub the merchant's public key of the instance where the payment happened + * @param order_id the order that was paid or refunded + * @param mi the merchant instance where the payment or refund happened * @param have_refund refunded amount, NULL if there was no refund */ void TMH_long_poll_resume (const char *order_id, - const struct TALER_MerchantPublicKeyP *mpub, + const struct TMH_MerchantInstance *mi, const struct TALER_Amount *have_refund) { struct GNUNET_HashCode key; - TMH_compute_pay_key (order_id, - mpub, - &key); + compute_pay_key (order_id, + &mi->pubkey, + &key); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Resuming operations suspended pending payment on key %s\n", GNUNET_h2s (&key)); @@ -443,73 +364,6 @@ TMH_long_poll_resume (const char *order_id, /** - * Create a taler://pay/ URI for the given @a con and @a order_id - * and @a session_id and @a instance_id. - * - * @param con HTTP connection - * @param order_id the order id - * @param session_id session, may be NULL - * @param instance_id instance, may be "default" - * @return corresponding taler://pay/ URI, or NULL on missing "host" - */ -char * -TMH_make_taler_pay_uri (struct MHD_Connection *con, - const char *order_id, - const char *session_id, - const char *instance_id) -{ - const char *host; - const char *forwarded_host; - const char *uri_path; - const char *uri_instance_id; - const char *query; - char *result; - - host = MHD_lookup_connection_value (con, - MHD_HEADER_KIND, - "Host"); - forwarded_host = MHD_lookup_connection_value (con, - MHD_HEADER_KIND, - "X-Forwarded-Host"); - - uri_path = MHD_lookup_connection_value (con, - MHD_HEADER_KIND, - "X-Forwarded-Prefix"); - if (NULL == uri_path) - uri_path = "-"; - if (NULL != forwarded_host) - host = forwarded_host; - if (0 == strcmp (instance_id, - "default")) - uri_instance_id = "-"; - else - uri_instance_id = instance_id; - if (NULL == host) - { - /* Should never happen, at least the host header should be defined */ - GNUNET_break (0); - return NULL; - } - - if (GNUNET_YES == TALER_mhd_is_https (con)) - query = ""; - else - query = "?insecure=1"; - GNUNET_assert (NULL != order_id); - GNUNET_assert (0 < GNUNET_asprintf (&result, - "taler://pay/%s/%s/%s/%s%s%s%s", - host, - uri_path, - uri_instance_id, - order_id, - (NULL == session_id) ? "" : "/", - (NULL == session_id) ? "" : session_id, - query)); - return result; -} - - -/** * Shutdown task (magically invoked when the application is being * quit) * @@ -521,10 +375,12 @@ do_shutdown (void *cls) struct TMH_SuspendedConnection *sc; (void) cls; - MH_force_pc_resume (); - MH_force_trh_resume (); - MH_force_refund_resume (); - MH_force_tip_pickup_resume (); +#if 0 + TMH_force_pc_resume (); + TMH_force_trh_resume (); + TMH_force_refund_resume (); + TMH_force_tip_pickup_resume (); +#endif if (NULL != mhd_task) { GNUNET_SCHEDULER_cancel (mhd_task); @@ -556,10 +412,10 @@ do_shutdown (void *cls) MHD_stop_daemon (mhd); mhd = NULL; } - if (NULL != db) + if (NULL != TMH_db) { - TALER_MERCHANTDB_plugin_unload (db); - db = NULL; + TALER_MERCHANTDB_plugin_unload (TMH_db); + TMH_db = NULL; } TMH_EXCHANGES_done (); TMH_AUDITORS_done (); @@ -574,16 +430,11 @@ do_shutdown (void *cls) if (NULL != by_id_map) { GNUNET_CONTAINER_multihashmap_iterate (by_id_map, - &hashmap_free, + &instance_free, NULL); GNUNET_CONTAINER_multihashmap_destroy (by_id_map); by_id_map = NULL; } - if (NULL != by_kpub_map) - { - GNUNET_CONTAINER_multihashmap_destroy (by_kpub_map); - by_kpub_map = NULL; - } } @@ -607,15 +458,21 @@ handle_mhd_completion_callback (void *cls, void **con_cls, enum MHD_RequestTerminationCode toe) { - struct TM_HandlerContext *hc = *con_cls; + struct TMH_HandlerContext *hc = *con_cls; if (NULL == hc) return; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Finished handling request for `%s' with status %d\n", - hc->rh->url, + hc->url, (int) toe); - hc->cc (hc); + if (NULL != hc->cc) + hc->cc (hc->ctx); + TALER_MHD_parse_post_cleanup_callback (hc->json_parse_context); + GNUNET_free_non_null (hc->infix); + if (NULL != hc->json) + json_decref (hc->json); + GNUNET_free (hc); *con_cls = NULL; } @@ -625,7 +482,6 @@ handle_mhd_completion_callback (void *cls, * starts the task waiting for them. */ static struct GNUNET_SCHEDULER_Task * - prepare_daemon (void); @@ -729,504 +585,18 @@ prepare_daemon (void) /** - * Callback that looks for 'merchant-location-*' sections, - * and populates @a default_locations. - * - * @param cls closure - * @section section name this callback gets - */ -static void -locations_iterator_cb (void *cls, - const char *section) -{ - static const char *keys[] = { - "country", - "city", - "state", - "region", - "province", - "zip_code", - "street", - "street_number", - NULL, - }; - const char *prefix = "merchant-location-"; - const char *substr = strstr (section, prefix); - const char *locname; - json_t *loc; - - (void) cls; - if ( (NULL == substr) || (substr != section) ) - return; - locname = section + strlen (prefix); - if (0 == strlen (locname)) - return; - GNUNET_assert (json_is_object (default_locations)); - - loc = json_object (); - json_object_set_new (default_locations, - locname, - loc); - for (unsigned int pos = 0; NULL != keys[pos]; pos++) - { - char *val; - - if (GNUNET_OK == - GNUNET_CONFIGURATION_get_value_string (cfg, - section, - keys[pos], - &val)) - { - json_object_set_new (loc, - keys[pos], - json_string (val)); - GNUNET_free (val); - } - } -} - - -/** - * Closure for the #wireformat_iterator_cb(). - */ -struct WireFormatIteratorContext -{ - /** - * The global iteration context. - */ - struct IterateInstancesCls *iic; - - /** - * The merchant instance we are currently building. - */ - struct MerchantInstance *mi; - - /** - * Set to #GNUNET_YES if the default instance was found. - */ - int default_instance; -}; - - -/** - * Callback that looks for 'merchant-account-*' sections, - * and populates our wire method according to the data - * - * @param cls closure with a `struct WireFormatIteratorContext *` - * @section section name this callback gets - */ -static void -wireformat_iterator_cb (void *cls, - const char *section) -{ - struct WireFormatIteratorContext *wfic = cls; - struct MerchantInstance *mi = wfic->mi; - struct IterateInstancesCls *iic = wfic->iic; - char *instance_option; - struct WireMethod *wm; - char *payto; - char *fn; - json_t *j; - struct GNUNET_HashCode jh_wire; - char *wire_file_mode; - - if (0 != strncasecmp (section, - "merchant-account-", - strlen ("merchant-account-"))) - return; - GNUNET_asprintf (&instance_option, - "HONOR_%s", - mi->id); - if (GNUNET_YES != - GNUNET_CONFIGURATION_get_value_yesno (cfg, - section, - instance_option)) - { - GNUNET_free (instance_option); - return; - } - GNUNET_free (instance_option); - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (cfg, - section, - "PAYTO_URI", - &payto)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - section, - "PAYTO_URI"); - iic->ret = GNUNET_SYSERR; - return; - } - - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_filename (cfg, - section, - "WIRE_RESPONSE", - &fn)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - section, - "WIRE_RESPONSE"); - GNUNET_free (payto); - iic->ret = GNUNET_SYSERR; - return; - } - - /* Try loading existing JSON from file */ - if (GNUNET_YES == - GNUNET_DISK_file_test (fn)) - { - json_error_t err; - char *url; - - if (NULL == - (j = json_load_file (fn, - JSON_REJECT_DUPLICATES, - &err))) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Failed to load JSON from `%s': %s at %d:%d\n", - fn, - err.text, - err.line, - err.column); - GNUNET_free (fn); - GNUNET_free (payto); - iic->ret = GNUNET_SYSERR; - return; - } - url = TALER_JSON_wire_to_payto (j); - if (NULL == url) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "URL missing in `%s', disabling account `%s'\n", - fn, - section); - GNUNET_free (fn); - GNUNET_free (payto); - iic->ret = GNUNET_SYSERR; - return; - } - if (0 != strcasecmp (url, - payto)) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "URL `%s' does not match configuration `%s', disabling account `%s'\n", - url, - payto, - section); - GNUNET_free (fn); - GNUNET_free (payto); - GNUNET_free (url); - iic->ret = GNUNET_SYSERR; - return; - } - GNUNET_free (url); - } - else /* need to generate JSON */ - { - struct GNUNET_HashCode salt; - char *salt_str; - - GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, - &salt, - sizeof (salt)); - salt_str = GNUNET_STRINGS_data_to_string_alloc (&salt, - sizeof (salt)); - j = json_pack ("{s:s, s:s}", - "payto_uri", payto, - "salt", salt_str); - GNUNET_free (salt_str); - - /* Make sure every path component exists. */ - if (GNUNET_OK != GNUNET_DISK_directory_create_for_file (fn)) - { - GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, - "mkdir", - fn); - GNUNET_free (fn); - GNUNET_free (payto); - json_decref (j); - iic->ret = GNUNET_SYSERR; - return; - } - - if (0 != json_dump_file (j, - fn, - JSON_COMPACT | JSON_SORT_KEYS)) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Failed to write hashed wire details to `%s'\n", - fn); - GNUNET_free (fn); - GNUNET_free (payto); - json_decref (j); - iic->ret = GNUNET_SYSERR; - return; - } - - if (GNUNET_OK == - GNUNET_CONFIGURATION_get_value_string (cfg, - section, - "WIRE_FILE_MODE", - &wire_file_mode)) - { - errno = 0; - mode_t mode = (mode_t) strtoul (wire_file_mode, - NULL, - 8); - if (0 != errno) - { - GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, - section, - "WIRE_FILE_MODE", - "Must be octal number\n"); - iic->ret = GNUNET_SYSERR; - GNUNET_free (fn); - return; - } - if (0 != chmod (fn, mode)) - { - TALER_LOG_ERROR ("chmod failed on %s\n", fn); - iic->ret = GNUNET_SYSERR; - GNUNET_free (fn); - return; - } - } - } - - GNUNET_free (fn); - - if (GNUNET_OK != - TALER_JSON_merchant_wire_signature_hash (j, - &jh_wire)) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Failed to hash wire input\n"); - GNUNET_free (fn); - GNUNET_free (payto); - json_decref (j); - iic->ret = GNUNET_SYSERR; - return; - } - - wm = GNUNET_new (struct WireMethod); - wm->wire_method = TALER_payto_get_method (payto); - GNUNET_free (payto); - GNUNET_asprintf (&instance_option, - "ACTIVE_%s", - mi->id); - wm->active = GNUNET_CONFIGURATION_get_value_yesno (cfg, - section, - instance_option); - GNUNET_free (instance_option); - if (GNUNET_YES == wm->active) - GNUNET_CONTAINER_DLL_insert (mi->wm_head, - mi->wm_tail, - wm); - else - GNUNET_CONTAINER_DLL_insert_tail (mi->wm_head, - mi->wm_tail, - wm); - wm->j_wire = j; - wm->h_wire = jh_wire; -} - - -/** - * Callback that looks for 'instance-*' sections, - * and populates accordingly each instance's data - * - * @param cls closure of type `struct IterateInstancesCls` - * @section section name this callback gets - */ -static void -instances_iterator_cb (void *cls, - const char *section) -{ - struct IterateInstancesCls *iic = cls; - char *token; - struct MerchantInstance *mi; - /* used as hashmap keys */ - struct GNUNET_HashCode h_pk; - struct GNUNET_HashCode h_id; - - if (0 != strncasecmp (section, - "instance-", - strlen ("instance-"))) - return; - /** Get id **/ - token = strrchr (section, '-'); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Extracted token: %s\n", - token + 1); - mi = GNUNET_new (struct MerchantInstance); - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (cfg, - section, - "NAME", - &mi->name)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - section, - "NAME"); - GNUNET_free (mi); - iic->ret = GNUNET_SYSERR; - return; - } - - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_filename (cfg, - section, - "KEYFILE", - &mi->keyfile)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - section, - "KEYFILE"); - GNUNET_free (mi->name); - GNUNET_free (mi); - iic->ret = GNUNET_SYSERR; - return; - } - if (GNUNET_OK == - GNUNET_CONFIGURATION_get_value_string (cfg, - section, - "TIP_EXCHANGE", - &mi->tip_exchange)) - { - char *tip_reserves; - - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_filename (cfg, - section, - "TIP_RESERVE_PRIV_FILENAME", - &tip_reserves)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - section, - "TIP_RESERVE_PRIV_FILENAME"); - GNUNET_free (mi->keyfile); - GNUNET_free (mi->name); - GNUNET_free (mi); - iic->ret = GNUNET_SYSERR; - return; - } - if (GNUNET_OK != - GNUNET_CRYPTO_eddsa_key_from_file (tip_reserves, - GNUNET_NO, - &mi->tip_reserve.eddsa_priv)) - { - GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, - section, - "TIP_RESERVE_PRIV_FILENAME", - "Failed to read private key"); - GNUNET_free (tip_reserves); - GNUNET_free (mi->keyfile); - GNUNET_free (mi->name); - GNUNET_free (mi); - iic->ret = GNUNET_SYSERR; - return; - } - GNUNET_free (tip_reserves); - } - - if (GNUNET_SYSERR == - GNUNET_CRYPTO_eddsa_key_from_file (mi->keyfile, - GNUNET_YES, - &mi->privkey.eddsa_priv)) - { - GNUNET_break (0); - GNUNET_free (mi->keyfile); - GNUNET_free (mi->name); - GNUNET_free (mi); - iic->ret = GNUNET_SYSERR; - return; - } - GNUNET_CRYPTO_eddsa_key_get_public (&mi->privkey.eddsa_priv, - &mi->pubkey.eddsa_pub); - - mi->id = GNUNET_strdup (token + 1); - if (0 == strcasecmp ("default", - mi->id)) - iic->default_instance = GNUNET_YES; - - GNUNET_CRYPTO_hash (mi->id, - strlen (mi->id), - &h_id); - if (GNUNET_OK != - GNUNET_CONTAINER_multihashmap_put (by_id_map, - &h_id, - mi, - GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Failed to put an entry into the 'by_id' hashmap\n"); - iic->ret = GNUNET_SYSERR; - GNUNET_free (mi->keyfile); - GNUNET_free (mi->name); - GNUNET_free (mi); - return; - } - GNUNET_CRYPTO_hash (&mi->pubkey.eddsa_pub, - sizeof (struct GNUNET_CRYPTO_EddsaPublicKey), - &h_pk); - if (GNUNET_OK != - GNUNET_CONTAINER_multihashmap_put (by_kpub_map, - &h_pk, - mi, - GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Failed to put an entry into the 'by_kpub_map' hashmap\n"); - GNUNET_assert (GNUNET_OK == - GNUNET_CONTAINER_multihashmap_remove (by_id_map, - &h_id, - mi)); - iic->ret = GNUNET_SYSERR; - GNUNET_free (mi->keyfile); - GNUNET_free (mi->name); - GNUNET_free (mi); - return; - } - - /* Initialize wireformats */ - { - struct WireFormatIteratorContext wfic = { - .iic = iic, - .mi = mi - }; - - GNUNET_CONFIGURATION_iterate_sections (cfg, - &wireformat_iterator_cb, - &wfic); - } - if (NULL == mi->wm_head) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Failed to load wire formats for instance `%s'\n", - mi->id); - iic->ret = GNUNET_SYSERR; - } - -} - - -/** * Lookup a merchant instance by its instance ID. * * @param instance_id identifier of the instance to resolve * @return NULL if that instance is unknown to us */ -static struct MerchantInstance * +static struct TMH_MerchantInstance * lookup_instance (const char *instance_id) { struct GNUNET_HashCode h_instance; if (NULL == instance_id) instance_id = "default"; - GNUNET_CRYPTO_hash (instance_id, strlen (instance_id), &h_instance); @@ -1242,57 +612,6 @@ lookup_instance (const char *instance_id) /** - * Iterate over locations in config in order to populate - * the location data. - * - * @return #GNUNET_OK if successful, #GNUNET_SYSERR upon errors - */ -static void -iterate_locations (void) -{ - GNUNET_assert (NULL == default_locations); - default_locations = json_object (); - GNUNET_CONFIGURATION_iterate_sections (cfg, - &locations_iterator_cb, - NULL); -} - - -/** - * Iterate over each merchant instance, in order to populate - * each instance's own data - * - * @return #GNUNET_OK if successful, #GNUNET_SYSERR upon errors - * (for example, if no "default" instance is defined) - */ -static int -iterate_instances (void) -{ - struct IterateInstancesCls iic; - - iic.default_instance = GNUNET_NO; - iic.ret = GNUNET_OK; - GNUNET_CONFIGURATION_iterate_sections (cfg, - &instances_iterator_cb, - &iic); - - if (GNUNET_NO == iic.default_instance) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "No default merchant instance found\n"); - return GNUNET_SYSERR; - } - if (GNUNET_OK != iic.ret) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "At least one instance was not successfully parsed\n"); - return GNUNET_SYSERR; - } - return GNUNET_OK; -} - - -/** * A client has requested the given url using the given method * (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT, * #MHD_HTTP_METHOD_DELETE, #MHD_HTTP_METHOD_POST, etc). The callback @@ -1341,158 +660,143 @@ url_handler (void *cls, size_t *upload_data_size, void **con_cls) { - static struct TMH_RequestHandler handlers[] = { - /* Landing page, tell humans to go away. */ - { "/", MHD_HTTP_METHOD_GET, "text/plain", - "Hello, I'm a merchant's Taler backend. This HTTP server is not for humans.\n", - 0, - &TMH_MHD_handler_static_response, MHD_HTTP_OK }, - { "/agpl", MHD_HTTP_METHOD_GET, "text/plain", - NULL, 0, - &TMH_MHD_handler_agpl_redirect, MHD_HTTP_FOUND }, - { "/track/transfer", MHD_HTTP_METHOD_GET, "application/json", - NULL, 0, - &MH_handler_track_transfer, MHD_HTTP_OK}, - { "/track/transfer", NULL, "text/plain", - "Only GET is allowed", 0, - &TMH_MHD_handler_static_response, MHD_HTTP_OK}, - { "/track/transaction", MHD_HTTP_METHOD_GET, "application/json", - NULL, 0, - &MH_handler_track_transaction, MHD_HTTP_OK}, - { "/track/transaction", NULL, "text/plain", - "Only GET is allowed", 0, - &TMH_MHD_handler_static_response, MHD_HTTP_OK}, - { "/history", MHD_HTTP_METHOD_GET, "text/plain", - "Only GET is allowed", 0, - &MH_handler_history, MHD_HTTP_OK}, - { "/order", MHD_HTTP_METHOD_POST, "application/json", - NULL, 0, - &MH_handler_order_post, MHD_HTTP_OK }, - { "/refund", MHD_HTTP_METHOD_POST, "application/json", - NULL, 0, - &MH_handler_refund_increase, MHD_HTTP_OK}, - { "/tip-authorize", MHD_HTTP_METHOD_POST, "text/plain", - NULL, 0, - &MH_handler_tip_authorize, MHD_HTTP_OK}, - { "/tip-query", MHD_HTTP_METHOD_GET, "text/plain", - NULL, 0, - &MH_handler_tip_query, MHD_HTTP_OK}, - { "/check-payment", MHD_HTTP_METHOD_GET, "text/plain", - NULL, 0, - &MH_handler_check_payment, MHD_HTTP_OK}, - { "/config", MHD_HTTP_METHOD_GET, "text/plain", - NULL, 0, - &MH_handler_config, MHD_HTTP_OK}, - {NULL, NULL, NULL, NULL, 0, 0 } + static struct TMH_RequestHandler private_handlers[] = { + { + .url_prefix = "/", + .method = MHD_HTTP_METHOD_GET, + .mime_type = "text/plain", + .skip_instance = true, + .data = "This is a GNU Taler merchant backend. See https://taler.net/.\n", + .data_size = strlen ( + "This is a GNU Taler merchant backend. See https://taler.net/.\n"), + .handler = &TMH_MHD_handler_static_response, + .response_code = MHD_HTTP_OK + }, + { + .url_prefix = "/agpl", + .method = MHD_HTTP_METHOD_GET, + .skip_instance = true, + .handler = &TMH_MHD_handler_agpl_redirect + }, + { + NULL + } }; static struct TMH_RequestHandler public_handlers[] = { - { "/pay", MHD_HTTP_METHOD_POST, "application/json", - NULL, 0, - &MH_handler_pay, MHD_HTTP_OK }, - { "/proposal", MHD_HTTP_METHOD_GET, "text/plain", - NULL, 0, - &MH_handler_proposal_lookup, MHD_HTTP_OK }, - { "/tip-pickup", MHD_HTTP_METHOD_POST, "text/plain", - NULL, 0, - &MH_handler_tip_pickup, MHD_HTTP_OK }, - { "/refund", MHD_HTTP_METHOD_GET, "text/plain", - NULL, 0, - &MH_handler_refund_lookup, MHD_HTTP_OK }, - { "/tip-pickup", MHD_HTTP_METHOD_GET, "text/plain", - NULL, 0, - &MH_handler_tip_pickup_get, MHD_HTTP_OK }, - { "/poll-payment", MHD_HTTP_METHOD_GET, "text/plain", - NULL, 0, - &MH_handler_poll_payment, MHD_HTTP_OK}, - { "/config", MHD_HTTP_METHOD_GET, "text/plain", - NULL, 0, - &MH_handler_config, MHD_HTTP_OK}, - {NULL, NULL, NULL, NULL, 0, 0 } + { + .url_prefix = "/", + .method = MHD_HTTP_METHOD_GET, + .mime_type = "text/plain", + .skip_instance = true, + .data = "This is a GNU Taler merchant backend. See https://taler.net/.\n", + .data_size = strlen ( + "This is a GNU Taler merchant backend. See https://taler.net/.\n"), + .handler = &TMH_MHD_handler_static_response, + .response_code = MHD_HTTP_OK + }, + { + .url_prefix = "/agpl", + .method = MHD_HTTP_METHOD_GET, + .skip_instance = true, + .handler = &TMH_MHD_handler_agpl_redirect + }, + { + .url_prefix = "/config", + .method = MHD_HTTP_METHOD_GET, + .skip_instance = true, + .handler = &MH_handler_config + }, + { + NULL + } }; static struct TMH_RequestHandler h404 = { - "", NULL, "text/html", - "<html><title>404: not found</title><body>404: not found</body></html>", 0, - &TMH_MHD_handler_static_response, MHD_HTTP_NOT_FOUND + .mime_type = "text/html", + .data = "<html><title>404: not found</title>" + "<body>404: not found</body></html>", + .data_size = strlen ("<html><title>404: not found</title>" + "<body>404: not found</body></html>"), + .handler = &TMH_MHD_handler_static_response, + .response_code = MHD_HTTP_NOT_FOUND }; - - struct TM_HandlerContext *hc = *con_cls; - struct GNUNET_AsyncScopeId aid; - const char *correlation_id = NULL; - struct MerchantInstance *instance; - const char *effective_url; - /* Is a publicly facing endpoint being requested? */ - int is_public; - /* Matching URL found, but maybe method doesn't match */ - int url_found = GNUNET_NO; - MHD_RESULT ret; - struct TMH_RequestHandler *selected_handler = NULL; + struct TMH_HandlerContext *hc = *con_cls; + struct TMH_RequestHandler *handlers; (void) cls; (void) version; - if (NULL == hc) + if (NULL != hc) { - GNUNET_async_scope_fresh (&aid); - /* We only read the correlation ID on the first callback for every client */ + GNUNET_assert (NULL != hc->rh); + GNUNET_SCHEDULER_begin_async_scope (&hc->async_scope_id); + if ( (hc->is_post) && + (NULL == hc->json) ) + { + int res; + + res = TALER_MHD_parse_post_json (connection, + &hc->json_parse_context, + upload_data, + upload_data_size, + &hc->json); + if (GNUNET_SYSERR == res) + return MHD_NO; + /* A error response was already generated */ + if ( (GNUNET_NO == res) || + /* or, need more data to accomplish parsing */ + (NULL == hc->json) ) + return MHD_YES; + } + return hc->rh->handler (hc->rh, + connection, + hc); + } + hc = GNUNET_new (struct TMH_HandlerContext); + *con_cls = hc; + GNUNET_async_scope_fresh (&hc->async_scope_id); + GNUNET_SCHEDULER_begin_async_scope (&hc->async_scope_id); + hc->url = url; + { + const char *correlation_id; + correlation_id = MHD_lookup_connection_value (connection, MHD_HEADER_KIND, "Taler-Correlation-Id"); - if ((NULL != correlation_id) && - (GNUNET_YES != GNUNET_CURL_is_valid_scope_id (correlation_id))) + if ( (NULL != correlation_id) && + (GNUNET_YES != GNUNET_CURL_is_valid_scope_id (correlation_id)) ) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "illegal incoming correlation ID\n"); correlation_id = NULL; } - } - else - { - aid = hc->async_scope_id; - } - - GNUNET_SCHEDULER_begin_async_scope (&aid); - - if (NULL != correlation_id) - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Handling request for (%s) URL '%s', correlation_id=%s\n", - method, - url, - correlation_id); - else - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Handling request (%s) for URL '%s'\n", - method, - url); - - effective_url = url; - - { - const char *public_prefix = "/public/"; - - if (0 == strncmp (effective_url, - public_prefix, - strlen (public_prefix))) - { - is_public = GNUNET_YES; - effective_url = effective_url + strlen (public_prefix) - 1; - } + if (NULL != correlation_id) + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Handling request for (%s) URL '%s', correlation_id=%s\n", + method, + url, + correlation_id); else - { - is_public = GNUNET_NO; - } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Handling request (%s) for URL '%s'\n", + method, + url); } + if (0 == strcasecmp (method, + MHD_HTTP_METHOD_HEAD)) + method = MHD_HTTP_METHOD_GET; /* MHD will deal with the rest */ + /* Find out the merchant backend instance for the request. * If there is an instance, remove the instance specification * from the beginning of the request URL. */ { const char *instance_prefix = "/instances/"; - if (0 == strncmp (effective_url, + if (0 == strncmp (url, instance_prefix, strlen (instance_prefix))) { /* url starts with "/instances/" */ - const char *istart = effective_url + strlen (instance_prefix); + const char *istart = url + strlen (instance_prefix); const char *slash = strchr (istart, '/'); char *instance_id; @@ -1500,114 +804,149 @@ url_handler (void *cls, { return TMH_MHD_handler_static_response (&h404, connection, - con_cls, - upload_data, - upload_data_size, - NULL); + hc); } instance_id = GNUNET_strndup (istart, slash - istart); - instance = lookup_instance (instance_id); + hc->instance = lookup_instance (instance_id); GNUNET_free (instance_id); - effective_url = slash; + url = slash; } else { - instance = lookup_instance (NULL); + /* use 'default' */ + hc->instance = lookup_instance (NULL); } } - if (NULL == instance) - return TALER_MHD_reply_json_pack (connection, - MHD_HTTP_NOT_FOUND, - "{s:I, s:s}", - "code", - (json_int_t) TALER_EC_INSTANCE_UNKNOWN, - "error", - "merchant instance unknown"); - if (GNUNET_NO == is_public) { - for (unsigned int i = 0; NULL != handlers[i].url; i++) - { - struct TMH_RequestHandler *rh = &handlers[i]; + const char *private_prefix = "/private/"; - if ( (0 != strcasecmp (effective_url, rh->url)) ) - continue; - url_found = GNUNET_YES; - if (0 == strcasecmp (method, - MHD_HTTP_METHOD_OPTIONS)) - { - return TALER_MHD_reply_cors_preflight (connection); - } - if ( (rh->method != NULL) && - (0 != strcasecmp (method, rh->method)) ) - continue; - selected_handler = rh; - break; + if (0 == strncmp (url, + private_prefix, + strlen (private_prefix))) + { + handlers = private_handlers; + url += strlen (private_prefix) - 1; + } + else + { + handlers = public_handlers; } } + if (strcmp (url, + "")) + url = "/"; /* code below does not like empty string */ - if (NULL == selected_handler) { - for (unsigned int i = 0; NULL != public_handlers[i].url; i++) + /* Matching URL found, but maybe method doesn't match */ + size_t prefix_strlen; /* i.e. 8 for "/orders/", or 7 for "/config" */ + const char *infix_url = NULL; /* i.e. "$ORDER_ID", no '/'-es */ + size_t infix_strlen = 0; /* number of characters in infix_url */ + const char *suffix_url = NULL; /* i.e. "/refund", includes '/' at the beginning */ + size_t suffix_strlen = 0; /* number of characters in suffix_url */ + { - struct TMH_RequestHandler *rh = &public_handlers[i]; + const char *slash; - if ( (0 != strcasecmp (effective_url, rh->url)) ) - continue; - url_found = GNUNET_YES; - if (0 == strcasecmp (method, - MHD_HTTP_METHOD_OPTIONS)) + slash = strchr (&url[1], '/'); + if (NULL == slash) { - return TALER_MHD_reply_cors_preflight (connection); + prefix_strlen = strlen (url); + } + else + { + prefix_strlen = slash - url + 1; /* includes both '/'-es if present! */ + infix_url = slash + 1; + slash = strchr (&infix_url[1], '/'); + if (NULL == slash) + { + infix_strlen = strlen (infix_url); + } + else + { + infix_strlen = slash - infix_url; + suffix_url = slash; + suffix_strlen = strlen (suffix_url); + } + hc->infix = GNUNET_strndup (infix_url, + infix_strlen); } - if ( (rh->method != NULL) && (0 != strcasecmp (method, rh->method)) ) - continue; - selected_handler = rh; - break; } - } - if (NULL == selected_handler) - { - if (GNUNET_YES == url_found) { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "invalid request: method '%s' for '%s' not allowed\n", - method, - url); - return TALER_MHD_reply_json_pack (connection, - MHD_HTTP_METHOD_NOT_ALLOWED, - "{s:s}", - "error", - "method not allowed"); + bool url_found = false; + + for (unsigned int i = 0; NULL != handlers[i].url_prefix; i++) + { + struct TMH_RequestHandler *rh = &handlers[i]; + + if ( (prefix_strlen != strlen (rh->url_prefix)) || + (0 != memcmp (url, + rh->url_prefix, + prefix_strlen)) ) + continue; + if ( (NULL == infix_url) + ^ (GNUNET_NO == rh->have_id_segment) ) + continue; /* infix existence missmatch */ + if ( (NULL == suffix_url) + ^ (NULL != rh->url_suffix) ) + continue; /* suffix existence missmatch */ + if ( (NULL != suffix_url) && + ( (suffix_strlen != strlen (rh->url_suffix)) || + (0 != memcmp (suffix_url, + rh->url_suffix, + suffix_strlen)) ) ) + continue; /* suffix content missmatch */ + url_found = true; + if (0 == strcasecmp (method, + MHD_HTTP_METHOD_OPTIONS)) + { + return TALER_MHD_reply_cors_preflight (connection); + } + if ( (rh->method != NULL) && + (0 != strcasecmp (method, rh->method)) ) + continue; + hc->rh = rh; + break; + } + if ( (NULL == hc->rh) && + (url_found) ) + return TALER_MHD_reply_json_pack (connection, + MHD_HTTP_METHOD_NOT_ALLOWED, + "{s:s}", + "error", + "method not allowed"); + if (NULL == hc->rh) + return TMH_MHD_handler_static_response (&h404, + connection, + hc); } - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "invalid request: URL '%s' not handled\n", - url); - return TMH_MHD_handler_static_response (&h404, - connection, - con_cls, - upload_data, - upload_data_size, - instance); } - - ret = selected_handler->handler (selected_handler, - connection, - con_cls, - upload_data, - upload_data_size, - instance); - hc = *con_cls; - if (NULL != hc) + /* At this point, we must have found a handler */ + GNUNET_assert (NULL != hc->rh); + if ( (NULL == hc->instance) && + (GNUNET_YES != hc->rh->skip_instance) ) + return TALER_MHD_reply_json_pack (connection, + MHD_HTTP_NOT_FOUND, + "{s:I, s:s}", + "code", + (json_int_t) TALER_EC_INSTANCE_UNKNOWN, + "error", + "merchant instance unknown"); + hc->is_post = (0 == strcasecmp (method, + MHD_HTTP_METHOD_POST)); + if (hc->is_post) { - hc->rh = selected_handler; - /* Store the async context ID, so we can restore it if - * we get another callback for this request. */ - hc->async_scope_id = aid; + /* FIXME: Maybe check for maximum upload size here + and refuse if it is too big? */ + + GNUNET_break (NULL == hc->json); /* can't have it already */ + return MHD_YES; /* proceed with upload */ } - return ret; + return hc->rh->handler (hc->rh, + connection, + hc); } @@ -1632,10 +971,11 @@ run (void *cls, (void) cls; (void) args; (void) cfgfile; + cfg = config; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Starting taler-merchant-httpd\n"); go = TALER_MHD_GO_NONE; - if (TMH_merchant_connection_close) + if (merchant_connection_close) go |= TALER_MHD_GO_FORCE_CONNECTION_CLOSE; TALER_MHD_setup (go); @@ -1643,14 +983,14 @@ run (void *cls, GNUNET_SCHEDULER_add_shutdown (&do_shutdown, NULL); if (GNUNET_OK != - TALER_config_get_currency (config, + TALER_config_get_currency (cfg, &TMH_currency)) { GNUNET_SCHEDULER_shutdown (); return; } if (GNUNET_YES == - GNUNET_CONFIGURATION_get_value_yesno (config, + GNUNET_CONFIGURATION_get_value_yesno (cfg, "merchant", "FORCE_AUDIT")) TMH_force_audit = GNUNET_YES; @@ -1666,7 +1006,6 @@ run (void *cls, GNUNET_SCHEDULER_shutdown (); return; } - if (NULL == (by_id_map = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_NO))) @@ -1674,97 +1013,14 @@ run (void *cls, GNUNET_SCHEDULER_shutdown (); return; } - if (NULL == - (by_kpub_map = GNUNET_CONTAINER_multihashmap_create (1, - GNUNET_NO))) - { - GNUNET_SCHEDULER_shutdown (); - return; - } - - if (GNUNET_SYSERR == - GNUNET_CONFIGURATION_get_value_time (config, - "merchant", - "WIRE_TRANSFER_DELAY", - &default_wire_transfer_delay)) + (TMH_db = TALER_MERCHANTDB_plugin_load (cfg))) { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "merchant", - "WIRE_TRANSFER_DELAY"); GNUNET_SCHEDULER_shutdown (); return; } - if (GNUNET_SYSERR == - GNUNET_CONFIGURATION_get_value_time (config, - "merchant", - "DEFAULT_PAY_DEADLINE", - &default_pay_deadline)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "merchant", - "DEFAULT_PAY_DEADLINE"); - GNUNET_SCHEDULER_shutdown (); - return; - } - - if (GNUNET_OK != - TALER_config_get_amount (config, - "merchant", - "DEFAULT_MAX_WIRE_FEE", - &default_max_wire_fee)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "merchant", - "DEFAULT_MAX_WIRE_FEE"); - GNUNET_SCHEDULER_shutdown (); - return; - } - - if (GNUNET_OK != - TALER_config_get_amount (config, - "merchant", - "DEFAULT_MAX_DEPOSIT_FEE", - &default_max_deposit_fee)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "merchant", - "DEFAULT_MAX_DEPOSIT_FEE"); - GNUNET_SCHEDULER_shutdown (); - return; - } - - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_number (config, - "merchant", - "DEFAULT_WIRE_FEE_AMORTIZATION", - &default_wire_fee_amortization)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "merchant", - "DEFAULT_WIRE_FEE_AMORTIZATION"); - GNUNET_SCHEDULER_shutdown (); - return; - } - - cfg = GNUNET_CONFIGURATION_dup (config); - if (GNUNET_OK != - iterate_instances ()) - { - GNUNET_SCHEDULER_shutdown (); - return; - } - iterate_locations (); - - if (NULL == - (db = TALER_MERCHANTDB_plugin_load (cfg))) - { - GNUNET_SCHEDULER_shutdown (); - return; - } - - fh = TALER_MHD_bind (config, + fh = TALER_MHD_bind (cfg, "merchant", &port); if ( (0 == port) && @@ -1805,7 +1061,7 @@ run (void *cls, * * @param argc number of arguments from the command line * @param argv command line arguments - * @return 0 ok, 1 on error + * @return 0 ok, non-zero on error */ int main (int argc, @@ -1815,12 +1071,11 @@ main (int argc, GNUNET_GETOPT_option_flag ('C', "connection-close", "force HTTP connections to be closed after each request", - &TMH_merchant_connection_close), + &merchant_connection_close), GNUNET_GETOPT_option_timetravel ('T', "timetravel"), GNUNET_GETOPT_OPTION_END }; - if (GNUNET_OK != GNUNET_PROGRAM_run (argc, argv, "taler-merchant-httpd", diff --git a/src/backend/taler-merchant-httpd.h b/src/backend/taler-merchant-httpd.h index 28a0f2ea..b2e1a599 100644 --- a/src/backend/taler-merchant-httpd.h +++ b/src/backend/taler-merchant-httpd.h @@ -39,17 +39,17 @@ /** * Supported wire method. Kept in a DLL. */ -struct WireMethod +struct TMH_WireMethod { /** * Next entry in DLL. */ - struct WireMethod *next; + struct TMH_WireMethod *next; /** * Previous entry in DLL. */ - struct WireMethod *prev; + struct TMH_WireMethod *prev; /** * Which wire method / payment target identifier is @e j_wire using? @@ -69,7 +69,7 @@ struct WireMethod /** * Is this wire method active (should it be included in new contracts)? */ - int active; + bool active; }; @@ -79,7 +79,7 @@ struct WireMethod * backend can account for several merchants, as used to do in donation * shops */ -struct MerchantInstance +struct TMH_MerchantInstance { /** @@ -102,12 +102,12 @@ struct MerchantInstance /** * Next entry in DLL. */ - struct WireMethod *wm_head; + struct TMH_WireMethod *wm_head; /** * Previous entry in DLL. */ - struct WireMethod *wm_tail; + struct TMH_WireMethod *wm_tail; /** * Merchant's private key @@ -120,12 +120,50 @@ struct MerchantInstance struct TALER_MerchantPublicKeyP pubkey; /** + * Default maximum wire fee to assume, unless stated differently in the proposal + * already. + */ + struct TALER_Amount default_max_wire_fee; + + /** + * Default max deposit fee that the merchant is willing to + * pay; if deposit costs more, then the customer will cover + * the difference. + */ + struct TALER_Amount default_max_deposit_fee; + + /** + * Default factor for wire fee amortization. + */ + unsigned long long default_wire_fee_amortization; + + /** + * If the frontend does NOT specify an execution date, how long should + * we tell the exchange to wait to aggregate transactions before + * executing the wire transfer? This delay is added to the current + * time when we generate the advisory execution time for the exchange. + */ + struct GNUNET_TIME_Relative default_wire_transfer_delay; + + /** + * If the frontend does NOT specify a payment deadline, how long should + * offers we make be valid by default? + */ + struct GNUNET_TIME_Relative default_pay_deadline; + + /** * Exchange this instance uses for tipping, NULL if tipping * is not supported. */ char *tip_exchange; /** + * Locations from the configuration. Mapping from + * label to location data. + */ + json_t *default_locations; + + /** * What is the private key of the reserve used for signing tips by this exchange? * Only valid if @e tip_exchange is non-null. */ @@ -135,14 +173,56 @@ struct MerchantInstance /** * @brief Struct describing an URL and the handler for it. + * + * The overall URL is always @e url_prefix, optionally followed by the + * id_segment, which is optionally followed by the @e url_suffix. It is NOT + * allowed for the @e url_prefix to be directly followed by the @e url_suffix. + * A @e url_suffix SHOULD only be used with a @e method of #MHD_HTTP_METHOD_POST. + */ +struct TMH_RequestHandler; + +/** + * This information is stored in the "connection_cls" of MHD for + * every request that we process. + * Individual handlers can evaluate tis memebers and + * are allowed to update @e cc and @e ctx to store and clean up + * handler-specific data. + */ +struct TMH_HandlerContext; + + +/** + * @brief Struct describing an URL and the handler for it. + * + * The overall URL is always @e url_prefix, optionally followed by the + * id_segment, which is optionally followed by the @e url_suffix. It is NOT + * allowed for the @e url_prefix to be directly followed by the @e url_suffix. + * A @e url_suffix SHOULD only be used with a @e method of #MHD_HTTP_METHOD_POST. */ struct TMH_RequestHandler { /** - * URL the handler is for. + * URL prefix the handler is for. */ - const char *url; + const char *url_prefix; + + /** + * Does this request include an identifier segment + * (product_id, reserve_pub, order_id, tip_id) in the + * second segment? + */ + bool have_id_segment; + + /** + * Does this request handler expect an instance? + */ + bool skip_instance; + + /** + * URL suffix the handler is for. + */ + const char *url_suffix; /** * Method the handler is for, NULL for "all". @@ -155,36 +235,30 @@ struct TMH_RequestHandler const char *mime_type; /** - * Raw data for the @e handler + * Raw data for the @e handler (can be NULL). */ const void *data; /** - * Number of bytes in @e data, 0 for 0-terminated. + * Number of bytes in @e data. */ size_t data_size; /** - * Function to call to handle the request. + * Handler to be called for this URL/METHOD combination. * * @param rh this struct - * @param mime_type the @e mime_type for the reply (hint, can be NULL) * @param connection the MHD connection to handle - * @param[in,out] connection_cls the connection's closure (can be updated) - * @param upload_data upload data - * @param[in,out] upload_data_size number of bytes (left) in @a upload_data - * @param mi merchant backend instance, never NULL + * @param[in,out] hc context with further information about the request * @return MHD result code */ - MHD_RESULT (*handler)(struct TMH_RequestHandler *rh, - struct MHD_Connection *connection, - void **connection_cls, - const char *upload_data, - size_t *upload_data_size, - struct MerchantInstance *mi); + MHD_RESULT + (*handler)(const struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + struct TMH_HandlerContext *hc); /** - * Default response code. + * Default response code to use. */ unsigned int response_code; }; @@ -196,33 +270,39 @@ struct TMH_RequestHandler * member. This struct contains a single callback, which will be * invoked to clean up the memory when the contection is completed. */ -struct TM_HandlerContext; +struct TMH_HandlerContext; /** * Signature of a function used to clean up the context * we keep in the "connection_cls" of MHD when handling * a request. * - * @param hc header of the context to clean up. + * @param ctxt the context to clean up. */ typedef void -(*TM_ContextCleanup)(struct TM_HandlerContext *hc); +(*TMH_ContextCleanup)(void *ctx); /** - * Each MHD response handler that sets the "connection_cls" to a - * non-NULL value must use a struct that has this struct as its first - * member. This struct contains a single callback, which will be - * invoked to clean up the memory when the connection is completed. + * This information is stored in the "connection_cls" of MHD for + * every request that we process. + * Individual handlers can evaluate tis memebers and + * are allowed to update @e cc and @e ctx to store and clean up + * handler-specific data. */ -struct TM_HandlerContext +struct TMH_HandlerContext { /** * Function to execute the handler-specific cleanup of the - * (typically larger) context. + * (request-specific) context in @e ctx. + */ + TMH_ContextCleanup cc; + + /** + * Client-specific context we keep. Passed to @e cc. */ - TM_ContextCleanup cc; + void *ctx; /** * Which request handler is handling this request? @@ -230,9 +310,40 @@ struct TM_HandlerContext const struct TMH_RequestHandler *rh; /** + * Which instance is handling this request? + */ + struct TMH_MerchantInstance *instance; + + /** * Asynchronous request context id. */ struct GNUNET_AsyncScopeId async_scope_id; + + /** + * Our original URL, for logging. + */ + const char *url; + + /** + * Infix part of @a url. + */ + char *infix; + + /** + * JSON body that was uploaded, NULL if @e is_post is false. + */ + json_t *json; + + /** + * Placeholder for #TALER_MHD_parse_post_json() to keep its internal state. + * Used when we parse the POSTed data. + */ + void *json_parse_context; + + /** + * Set to true if this is an #MHD_HTTP_METHOD_POST request. + */ + bool is_post; }; @@ -247,12 +358,14 @@ struct TMH_SuspendedConnection struct MHD_Connection *con; /** - * Associated heap node. + * Associated heap node. Used internally by #TMH_long_poll_suspend() + * and TMH_long_poll_resume(). */ struct GNUNET_CONTAINER_HeapNode *hn; /** - * Key of this entry in the #payment_trigger_map + * Key of this entry in the #payment_trigger_map. Used internally by + * #TMH_long_poll_suspend() and TMH_long_poll_resume(). */ struct GNUNET_HashCode key; @@ -270,53 +383,12 @@ struct TMH_SuspendedConnection /** * #GNUNET_YES if we are waiting for a refund. */ - int awaiting_refund; + bool awaiting_refund; }; /** - * Locations from the configuration. Mapping from - * label to location data. - */ -extern json_t *default_locations; - -/** - * Default maximum wire fee to assume, unless stated differently in the proposal - * already. - */ -extern struct TALER_Amount default_max_wire_fee; - -/** - * Default max deposit fee that the merchant is willing to - * pay; if deposit costs more, then the customer will cover - * the difference. - */ -extern struct TALER_Amount default_max_deposit_fee; - -/** - * Default factor for wire fee amortization. - */ -extern unsigned long long default_wire_fee_amortization; - -/** - * MIN-Heap of suspended connections to resume when the timeout expires, - * ordered by timeout. Values are of type `struct TMH_SuspendedConnection` - */ -extern struct GNUNET_CONTAINER_Heap *resume_timeout_heap; - -/** - * Task responsible for timeouts in the #resume_timeout_heap. - */ -extern struct GNUNET_SCHEDULER_Task *resume_timeout_task; - -/** - * Hash map from H(order_id,merchant_pub) to `struct TMH_SuspendedConnection` - * entries to resume when a payment is made for the given order. - */ -extern struct GNUNET_CONTAINER_MultiHashMap *payment_trigger_map; - -/** * Which currency do we use? */ extern char *TMH_currency; @@ -327,45 +399,10 @@ extern char *TMH_currency; extern int TMH_force_audit; /** - * Hash of our wire format details as given in #j_wire. - */ -extern struct GNUNET_HashCode h_wire; - -/** - * Our private key, corresponds to #pubkey. - */ -extern struct TALER_MerchantPrivateKeyP privkey; - -/** - * Our public key, corresponds to #privkey. - */ -extern struct TALER_MerchantPublicKeyP pubkey; - -/** - * Hashmap pointing at merchant instances by 'id'. An 'id' is - * just a string that identifies a merchant instance. When a frontend - * needs to specify an instance to the backend, it does so by 'id' - */ -extern struct GNUNET_CONTAINER_MultiHashMap *by_id_map; - -/** * Handle to the database backend. */ -extern struct TALER_MERCHANTDB_Plugin *db; - -/** - * If the frontend does NOT specify an execution date, how long should - * we tell the exchange to wait to aggregate transactions before - * executing the wire transfer? This delay is added to the current - * time when we generate the advisory execution time for the exchange. - */ -extern struct GNUNET_TIME_Relative default_wire_transfer_delay; +extern struct TALER_MERCHANTDB_Plugin *TMH_db; -/** - * If the frontend does NOT specify a payment deadline, how long should - * offers we make be valid by default? - */ -extern struct GNUNET_TIME_Relative default_pay_deadline; /** * Kick MHD to run now, to be called after MHD_resume_connection(). @@ -378,28 +415,18 @@ TMH_trigger_daemon (void); /** - * Compute @a key to use for @a order_id and @a mpub in our - * #payment_trigger_map. - * - * @param order_id an order ID - * @param mpub an instance public key - * @param key[out] set to the hash map key to use - */ -void -TMH_compute_pay_key (const char *order_id, - const struct TALER_MerchantPublicKeyP *mpub, - struct GNUNET_HashCode *key); - - -/** * Suspend connection from @a sc until payment has been received. * + * @param order_id the order that we are waiting on + * @param mi the merchant instance we are waiting on * @param sc connection to suspend * @param min_refund refund amount we are waiting on to be exceeded before resuming, * NULL if we are not waiting for refunds */ void -TMH_long_poll_suspend (struct TMH_SuspendedConnection *sc, +TMH_long_poll_suspend (const char *order_id, + const struct TMH_MerchantInstance *mi, + struct TMH_SuspendedConnection *sc, const struct TALER_Amount *min_refund); @@ -407,31 +434,14 @@ TMH_long_poll_suspend (struct TMH_SuspendedConnection *sc, * Find out if we have any clients long-polling for @a order_id to be * confirmed at merchant @a mpub, and if so, tell them to resume. * - * @param order_id the order that was paid - * @param mpub the merchant's public key of the instance where the payment happened + * @param order_id the order that was paid or refunded + * @param mi the merchant instance where the payment or refund happened * @param refund_amount refunded amount, if the trigger was a refund, otherwise NULL */ void TMH_long_poll_resume (const char *order_id, - const struct TALER_MerchantPublicKeyP *mpub, + const struct TMH_MerchantInstance *mi, const struct TALER_Amount *refund_amount); -/** - * Create a taler://pay/ URI for the given @a con and @a order_id - * and @a session_id and @a instance_id. - * - * @param con HTTP connection - * @param order_id the order id - * @param session_id session, may be NULL - * @param instance_id instance, may be "default" - * @return corresponding taler://pay/ URI, or NULL on missing "host" - */ -char * -TMH_make_taler_pay_uri (struct MHD_Connection *con, - const char *order_id, - const char *session_id, - const char *instance_id); - - #endif diff --git a/src/backend/taler-merchant-httpd_config.c b/src/backend/taler-merchant-httpd_config.c index d6f6c922..c4fed585 100644 --- a/src/backend/taler-merchant-httpd_config.c +++ b/src/backend/taler-merchant-httpd_config.c @@ -25,8 +25,6 @@ #include "taler-merchant-httpd.h" #include "taler-merchant-httpd_mhd.h" #include "taler-merchant-httpd_exchanges.h" -#include "taler-merchant-httpd_tip-query.h" -#include "taler-merchant-httpd_tip-reserve-helper.h" /** @@ -47,111 +45,28 @@ #define MERCHANT_PROTOCOL_VERSION "0:0:0" -static int -add_instance (void *cls, - const struct GNUNET_HashCode *key, - void *value) -{ - json_t *ja = cls; - struct MerchantInstance *mi = value; - char *url; - json_t *pta; - - /* Compile array of all unique wire methods supported by this - instance */ - pta = json_array (); - GNUNET_assert (NULL != pta); - for (struct WireMethod *wm = mi->wm_head; - NULL != wm; - wm = wm->next) - { - int duplicate = GNUNET_NO; - - if (! wm->active) - break; - /* Yes, O(n^2), but really how many bank accounts can an - instance realistically have for this to matter? */ - for (struct WireMethod *pm = mi->wm_head; - pm != wm; - pm = pm->next) - if (0 == strcasecmp (pm->wire_method, - wm->wire_method)) - { - duplicate = GNUNET_YES; - break; - } - if (duplicate) - continue; - GNUNET_assert (0 == - json_array_append_new (pta, - json_string (wm->wire_method))); - } - GNUNET_asprintf (&url, - "/%s/", - mi->id); - GNUNET_assert (0 == - json_array_append_new ( - ja, - json_pack ( - (NULL != mi->tip_exchange) - ? "{s:s, s:s, s:o, s:o, s:s}" - : "{s:s, s:s, s:o, s:o}", - "name", - mi->name, - "backend_base_url", - url, - "merchant_pub", - GNUNET_JSON_from_data_auto (&mi->pubkey), - "payment_targets", - pta, - /* optional: */ - "tipping_exchange_baseurl", - mi->tip_exchange))); - GNUNET_free (url); - return GNUNET_OK; -} - - /** * Handle a "/config" request. * * @param rh context of the handler * @param connection the MHD connection to handle - * @param[in,out] connection_cls the connection's closure (can be updated) - * @param upload_data upload data - * @param[in,out] upload_data_size number of bytes (left) in @a upload_data - * @param mi merchant backend instance, never NULL + * @param[in,out] hc handler context (can be updated) * @return MHD result code */ MHD_RESULT MH_handler_config (struct TMH_RequestHandler *rh, struct MHD_Connection *connection, - void **connection_cls, - const char *upload_data, - size_t *upload_data_size, - struct MerchantInstance *mi) + struct TMH_HandlerContext *hc) { static struct MHD_Response *response; (void) rh; - (void) connection_cls; - (void) upload_data; - (void) upload_data_size; - (void) mi; + (void) hc; if (NULL == response) { - json_t *ia; - - ia = json_array (); - GNUNET_assert (NULL != ia); - GNUNET_CONTAINER_multihashmap_iterate (by_id_map, - &add_instance, - ia); - response = TALER_MHD_make_json_pack ("{s:s, s:s, s:o}", + response = TALER_MHD_make_json_pack ("{s:s, s:s }", "currency", TMH_currency, - "version", MERCHANT_PROTOCOL_VERSION, - "instances", ia); - + "version", MERCHANT_PROTOCOL_VERSION); } return MHD_queue_response (connection, MHD_HTTP_OK, diff --git a/src/backend/taler-merchant-httpd_config.h b/src/backend/taler-merchant-httpd_config.h index 1a5dfd68..8d21e47c 100644 --- a/src/backend/taler-merchant-httpd_config.h +++ b/src/backend/taler-merchant-httpd_config.h @@ -28,18 +28,12 @@ * * @param rh context of the handler * @param connection the MHD connection to handle - * @param[in,out] connection_cls the connection's closure (can be updated) - * @param upload_data upload data - * @param[in,out] upload_data_size number of bytes (left) in @a upload_data - * @param mi merchant backend instance, never NULL + * @param[in,out] hc handler context (can be updated) * @return MHD result code */ MHD_RESULT -MH_handler_config (struct TMH_RequestHandler *rh, +MH_handler_config (const struct TMH_RequestHandler *rh, struct MHD_Connection *connection, - void **connection_cls, - const char *upload_data, - size_t *upload_data_size, - struct MerchantInstance *mi); + struct TMH_HandlerContext *hc); #endif diff --git a/src/backend/taler-merchant-httpd_exchanges.c b/src/backend/taler-merchant-httpd_exchanges.c index 190f5db0..11f845f1 100644 --- a/src/backend/taler-merchant-httpd_exchanges.c +++ b/src/backend/taler-merchant-httpd_exchanges.c @@ -380,10 +380,10 @@ process_wire_fees (struct Exchange *exchange, wire_method, GNUNET_STRINGS_absolute_time_to_string (af->start_date), TALER_amount2s (&af->wire_fee)); - db->preflight (db->cls); + TMH_db->preflight (TMH_db->cls); if (GNUNET_OK != - db->start (db->cls, - "store wire fee")) + TMH_db->start (TMH_db->cls, + "store wire fee")) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to start database transaction to store exchange wire fees (will try to continue anyway)!\n"); @@ -391,21 +391,21 @@ process_wire_fees (struct Exchange *exchange, fees = fees->next; continue; } - qs = db->store_wire_fee_by_exchange (db->cls, - master_pub, - &h_wire_method, - &af->wire_fee, - &af->closing_fee, - af->start_date, - af->end_date, - &af->master_sig); + qs = TMH_db->store_wire_fee_by_exchange (TMH_db->cls, + master_pub, + &h_wire_method, + &af->wire_fee, + &af->closing_fee, + af->start_date, + af->end_date, + &af->master_sig); if (0 > qs) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to persist exchange wire fees in merchant DB (will try to continue anyway)!\n"); GNUNET_free (af); fees = fees->next; - db->rollback (db->cls); + TMH_db->rollback (TMH_db->cls); continue; } if (0 == qs) @@ -413,12 +413,12 @@ process_wire_fees (struct Exchange *exchange, /* Entry was already in DB, fine, continue as if we had succeeded */ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Fees already in DB, rolling back transaction attempt!\n"); - db->rollback (db->cls); + TMH_db->rollback (TMH_db->cls); } if (0 < qs) { /* Inserted into DB, make sure transaction completes */ - qs = db->commit (db->cls); + qs = TMH_db->commit (TMH_db->cls); if (0 > qs) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, diff --git a/src/backend/taler-merchant-httpd_mhd.c b/src/backend/taler-merchant-httpd_mhd.c index 08608604..79f40714 100644 --- a/src/backend/taler-merchant-httpd_mhd.c +++ b/src/backend/taler-merchant-httpd_mhd.c @@ -33,27 +33,15 @@ * * @param rh context of the handler * @param connection the MHD connection to handle - * @param[in,out] connection_cls the connection's closure (can be updated) - * @param upload_data upload data - * @param[in,out] upload_data_size number of bytes (left) in @a upload_data - * @param mi merchant backend instance, could be NULL in this specific case! + * @param[in,out] hc handler context (can be updated) * @return MHD result code */ MHD_RESULT -TMH_MHD_handler_static_response (struct TMH_RequestHandler *rh, +TMH_MHD_handler_static_response (const struct TMH_RequestHandler *rh, struct MHD_Connection *connection, - void **connection_cls, - const char *upload_data, - size_t *upload_data_size, - struct MerchantInstance *instance) + struct TMH_HandlerContext *hc) { - (void) instance; - (void) connection_cls; - (void) upload_data; - (void) upload_data_size; - (void) instance; - if (0 == rh->data_size) - rh->data_size = strlen ((const char *) rh->data); + (void) hc; return TALER_MHD_reply_static (connection, rh->response_code, rh->mime_type, @@ -68,24 +56,16 @@ TMH_MHD_handler_static_response (struct TMH_RequestHandler *rh, * * @param rh context of the handler * @param connection the MHD connection to handle - * @param[in,out] connection_cls the connection's closure (can be updated) - * @param upload_data upload data - * @param[in,out] upload_data_size number of bytes (left) in @a upload_data - * @param mi merchant backend instance, never NULL (but unused) + * @param[in,out] hc handler context (can be updated) * @return MHD result code */ MHD_RESULT -TMH_MHD_handler_agpl_redirect (struct TMH_RequestHandler *rh, +TMH_MHD_handler_agpl_redirect (const struct TMH_RequestHandler *rh, struct MHD_Connection *connection, - void **connection_cls, - const char *upload_data, - size_t *upload_data_size, - struct MerchantInstance *mi) + struct TMH_HandlerContext *hc) { - (void) mi; - (void) connection_cls; - (void) upload_data; - (void) upload_data_size; + (void) rh; + (void) hc; return TALER_MHD_reply_agpl (connection, "http://www.git.taler.net/?p=merchant.git"); } diff --git a/src/backend/taler-merchant-httpd_mhd.h b/src/backend/taler-merchant-httpd_mhd.h index 8fc78a21..cbf83add 100644 --- a/src/backend/taler-merchant-httpd_mhd.h +++ b/src/backend/taler-merchant-httpd_mhd.h @@ -34,19 +34,13 @@ * * @param rh context of the handler * @param connection the MHD connection to handle - * @param[in,out] connection_cls the connection's closure (can be updated) - * @param upload_data upload data - * @param[in,out] upload_data_size number of bytes (left) in @a upload_data - * @param mi merchant backend instance, NULL is allowed in this case! + * @param[in,out] hc handler context (can be updated) * @return MHD result code */ MHD_RESULT -TMH_MHD_handler_static_response (struct TMH_RequestHandler *rh, +TMH_MHD_handler_static_response (const struct TMH_RequestHandler *rh, struct MHD_Connection *connection, - void **connection_cls, - const char *upload_data, - size_t *upload_data_size, - struct MerchantInstance *mi); + struct TMH_HandlerContext *hc); /** @@ -55,19 +49,13 @@ TMH_MHD_handler_static_response (struct TMH_RequestHandler *rh, * * @param rh context of the handler * @param connection the MHD connection to handle - * @param[in,out] connection_cls the connection's closure (can be updated) - * @param upload_data upload data - * @param[in,out] upload_data_size number of bytes (left) in @a upload_data - * @param mi merchant backend instance, never NULL + * @param[in,out] hc handler context (can be updated) * @return MHD result code */ MHD_RESULT -TMH_MHD_handler_agpl_redirect (struct TMH_RequestHandler *rh, +TMH_MHD_handler_agpl_redirect (const struct TMH_RequestHandler *rh, struct MHD_Connection *connection, - void **connection_cls, - const char *upload_data, - size_t *upload_data_size, - struct MerchantInstance *mi); + struct TMH_HandlerContext *hc); /** @@ -111,7 +99,7 @@ TMH_MHD_handler_send_json_pack_error (struct TMH_RequestHandler *rh, void **connection_cls, const char *upload_data, size_t *upload_data_size, - struct MerchantInstance *mi); + struct TMH_MerchantInstance *mi); #endif diff --git a/src/backend/taler-merchant-httpd_private-get-instances.c b/src/backend/taler-merchant-httpd_private-get-instances.c new file mode 100644 index 00000000..8a08ab14 --- /dev/null +++ b/src/backend/taler-merchant-httpd_private-get-instances.c @@ -0,0 +1,160 @@ +/* + This file is part of TALER + (C) 2019, 2020 Taler Systems SA + + 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, see <http://www.gnu.org/licenses/> +*/ +/** + * @file backend/taler-merchant-httpd_config.c + * @brief implement API for querying configuration data of the backend + * @author Florian Dold + */ +#include "platform.h" +#include <jansson.h> +#include <taler/taler_util.h> +#include <taler/taler_json_lib.h> +#include "taler-merchant-httpd.h" +#include "taler-merchant-httpd_mhd.h" +#include "taler-merchant-httpd_exchanges.h" + + +/** + * Taler protocol version in the format CURRENT:REVISION:AGE + * as used by GNU libtool. See + * https://www.gnu.org/software/libtool/manual/html_node/Libtool-versioning.html + * + * Please be very careful when updating and follow + * https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html#Updating-version-info + * precisely. Note that this version has NOTHING to do with the + * release version, and the format is NOT the same that semantic + * versioning uses either. + * + * When changing this version, you likely want to also update + * #MERCHANT_PROTOCOL_CURRENT and #MERCHANT_PROTOCOL_AGE in + * merchant_api_config.c! + */ +#define MERCHANT_PROTOCOL_VERSION "0:0:0" + + +static int +add_instance (void *cls, + const struct GNUNET_HashCode *key, + void *value) +{ + json_t *ja = cls; + struct MerchantInstance *mi = value; + char *url; + json_t *pta; + + /* Compile array of all unique wire methods supported by this + instance */ + pta = json_array (); + GNUNET_assert (NULL != pta); + for (struct WireMethod *wm = mi->wm_head; + NULL != wm; + wm = wm->next) + { + int duplicate = GNUNET_NO; + + if (! wm->active) + break; + /* Yes, O(n^2), but really how many bank accounts can an + instance realistically have for this to matter? */ + for (struct WireMethod *pm = mi->wm_head; + pm != wm; + pm = pm->next) + if (0 == strcasecmp (pm->wire_method, + wm->wire_method)) + { + duplicate = GNUNET_YES; + break; + } + if (duplicate) + continue; + GNUNET_assert (0 == + json_array_append_new (pta, + json_string (wm->wire_method))); + } + GNUNET_asprintf (&url, + "/%s/", + mi->id); + GNUNET_assert (0 == + json_array_append_new ( + ja, + json_pack ( + (NULL != mi->tip_exchange) + ? "{s:s, s:s, s:o, s:o, s:s}" + : "{s:s, s:s, s:o, s:o}", + "name", + mi->name, + "backend_base_url", + url, + "merchant_pub", + GNUNET_JSON_from_data_auto (&mi->pubkey), + "payment_targets", + pta, + /* optional: */ + "tipping_exchange_baseurl", + mi->tip_exchange))); + GNUNET_free (url); + return GNUNET_OK; +} + + +/** + * Handle a "/config" request. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[in,out] upload_data_size number of bytes (left) in @a upload_data + * @param mi merchant backend instance, never NULL + * @return MHD result code + */ +MHD_RESULT +MH_handler_config (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size, + struct MerchantInstance *mi) +{ + static struct MHD_Response *response; + + (void) rh; + (void) connection_cls; + (void) upload_data; + (void) upload_data_size; + (void) mi; + if (NULL == response) + { + json_t *ia; + + ia = json_array (); + GNUNET_assert (NULL != ia); + GNUNET_CONTAINER_multihashmap_iterate (by_id_map, + &add_instance, + ia); + response = TALER_MHD_make_json_pack ("{s:s, s:s, s:o}", + "currency", TMH_currency, + "version", MERCHANT_PROTOCOL_VERSION, + "instances", ia); + + } + return MHD_queue_response (connection, + MHD_HTTP_OK, + response); +} + + +/* end of taler-merchant-httpd_config.c */ diff --git a/src/backend/taler-merchant-httpd_transfers-get.h b/src/backend/taler-merchant-httpd_transfers-get.h new file mode 100644 index 00000000..0463295e --- /dev/null +++ b/src/backend/taler-merchant-httpd_transfers-get.h @@ -0,0 +1,49 @@ +/* + This file is part of TALER + (C) 2014-2020 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file backend/taler-merchant-httpd_track-transfer.h + * @brief headers for /track/transfer handler + * @author Christian Grothoff + * @author Marcello Stanisci + */ +#ifndef TALER_MERCHANT_HTTPD_TRACK_TRANSFER_H +#define TALER_MERCHANT_HTTPD_TRACK_TRANSFER_H +#include <microhttpd.h> +#include "taler-merchant-httpd.h" + +/** + * Manages a /track/transfer call, thus it calls the /wire/transfer + * offered by the exchange in order to return the set of transfers + * (of coins) associated with a given wire transfer + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[in,out] upload_data_size number of bytes (left) in @a upload_data + * @param mi merchant backend instance, never NULL + * @return MHD result code + */ +MHD_RESULT +MH_handler_track_transfer (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size, + struct MerchantInstance *mi); + + +#endif |