/* This file is part of GNU Taler (C) 2021-2024 Taler Systems SA GNU 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. GNU 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 */ /** * @file taler-merchant-httpd_private-get-instances-ID-kyc.c * @brief implementing GET /instances/$ID/kyc request handling * @author Christian Grothoff */ #include "platform.h" #include "taler-merchant-httpd_private-get-instances-ID-kyc.h" #include "taler-merchant-httpd_helper.h" #include "taler-merchant-httpd_exchanges.h" #include #include #include /** * We do not re-check an acceptable KYC status for * a month, as usually a KYC never expires. */ #define STALE_KYC_TIMEOUT GNUNET_TIME_UNIT_MONTHS /** * How long should clients cache a KYC failure response? */ #define EXPIRATION_KYC_FAILURE GNUNET_TIME_relative_multiply ( \ GNUNET_TIME_UNIT_MINUTES, 5) /** * How long should clients cache a KYC success response? */ #define EXPIRATION_KYC_SUCCESS GNUNET_TIME_relative_multiply ( \ GNUNET_TIME_UNIT_HOURS, 1) /** * Information we keep per /kyc request. */ struct KycContext; /** * Structure for tracking requests to the exchange's * ``/kyc-check`` API. */ struct ExchangeKycRequest { /** * Kept in a DLL. */ struct ExchangeKycRequest *next; /** * Kept in a DLL. */ struct ExchangeKycRequest *prev; /** * Find operation where we connect to the respective exchange. */ struct TMH_EXCHANGES_KeysOperation *fo; /** * JSON array of payto-URIs with KYC auth wire transfer * instructions. Provided if @e auth_ok is false and * @e kyc_auth_conflict is false. */ json_t *pkaa; /** * The keys of the exchange. */ struct TALER_EXCHANGE_Keys *keys; /** * KYC request this exchange request is made for. */ struct KycContext *kc; /** * JSON array of AccountLimits that apply, NULL if * unknown (and likely defaults apply). */ json_t *jlimits; /** * Our account's payto URI. */ struct TALER_FullPayto payto_uri; /** * Base URL of the exchange. */ char *exchange_url; /** * Hash of the wire account (with salt) we are checking. */ struct TALER_MerchantWireHashP h_wire; /** * Current access token for the KYC SPA. Only set * if @e auth_ok is true. */ struct TALER_AccountAccessTokenP access_token; /** * Timestamp when we last got a reply from the exchange. */ struct GNUNET_TIME_Timestamp last_check; /** * Last HTTP status code obtained via /kyc-check from * the exchange. */ unsigned int last_http_status; /** * Last Taler error code returned from /kyc-check. */ enum TALER_ErrorCode last_ec; /** * True if this account * cannot work at this exchange because KYC auth is * impossible. */ bool kyc_auth_conflict; /** * We could not get /keys from the exchange. */ bool no_keys; /** * True if @e access_token is available. */ bool auth_ok; /** * True if we believe no KYC is currently required * for this account at this exchange. */ bool kyc_ok; /** * True if the exchange exposed to us that the account * is currently under AML review. */ bool in_aml_review; }; /** * Information we keep per /kyc request. */ struct KycContext { /** * Stored in a DLL. */ struct KycContext *next; /** * Stored in a DLL. */ struct KycContext *prev; /** * Connection we are handling. */ struct MHD_Connection *connection; /** * Instance we are serving. */ struct TMH_MerchantInstance *mi; /** * Our handler context. */ struct TMH_HandlerContext *hc; /** * Response to return, NULL if we don't have one yet. */ struct MHD_Response *response; /** * JSON array where we are building up the array with * pending KYC operations. */ json_t *kycs_data; /** * Head of DLL of requests we are making to an * exchange to inquire about the latest KYC status. */ struct ExchangeKycRequest *exchange_pending_head; /** * Tail of DLL of requests we are making to an * exchange to inquire about the latest KYC status. */ struct ExchangeKycRequest *exchange_pending_tail; /** * Set to the exchange URL, or NULL to not filter by * exchange. */ const char *exchange_url; /** * Notification handler from database on changes * to the KYC status. */ struct GNUNET_DB_EventHandler *eh; /** * Set to the h_wire of the merchant account if * @a have_h_wire is true, used to filter by account. */ struct TALER_MerchantWireHashP h_wire; /** * How long are we willing to wait for the exchange(s)? */ struct GNUNET_TIME_Absolute timeout; /** * HTTP status code to use for the reply, i.e 200 for "OK". * Special value UINT_MAX is used to indicate hard errors * (no reply, return #MHD_NO). */ unsigned int response_code; /** * #GNUNET_NO if the @e connection was not suspended, * #GNUNET_YES if the @e connection was suspended, * #GNUNET_SYSERR if @e connection was resumed to as * part of #MH_force_pc_resume during shutdown. */ enum GNUNET_GenericReturnValue suspended; /** * What state are we long-polling for? */ enum TALER_EXCHANGE_KycLongPollTarget lpt; /** * True if @e h_wire was given. */ bool have_h_wire; /** * We're still waiting on the exchange to determine * the KYC status of our deposit(s). */ bool return_immediately; }; /** * Head of DLL. */ static struct KycContext *kc_head; /** * Tail of DLL. */ static struct KycContext *kc_tail; void TMH_force_kyc_resume () { for (struct KycContext *kc = kc_head; NULL != kc; kc = kc->next) { if (GNUNET_YES == kc->suspended) { kc->suspended = GNUNET_SYSERR; MHD_resume_connection (kc->connection); } } } /** * Custom cleanup routine for a `struct KycContext`. * * @param cls the `struct KycContext` to clean up. */ static void kyc_context_cleanup (void *cls) { struct KycContext *kc = cls; struct ExchangeKycRequest *ekr; while (NULL != (ekr = kc->exchange_pending_head)) { GNUNET_CONTAINER_DLL_remove (kc->exchange_pending_head, kc->exchange_pending_tail, ekr); if (NULL != ekr->fo) { TMH_EXCHANGES_keys4exchange_cancel (ekr->fo); ekr->fo = NULL; } json_decref (ekr->pkaa); json_decref (ekr->jlimits); if (NULL != ekr->keys) TALER_EXCHANGE_keys_decref (ekr->keys); GNUNET_free (ekr->exchange_url); GNUNET_free (ekr->payto_uri.full_payto); GNUNET_free (ekr); } if (NULL != kc->eh) { TMH_db->event_listen_cancel (kc->eh); kc->eh = NULL; } if (NULL != kc->response) { MHD_destroy_response (kc->response); kc->response = NULL; } GNUNET_CONTAINER_DLL_remove (kc_head, kc_tail, kc); json_decref (kc->kycs_data); GNUNET_free (kc); } /** * Resume the given KYC context and send the final response. Stores the * response in the @a kc and signals MHD to resume the connection. Also * ensures MHD runs immediately. * * @param kc KYC context */ static void resume_kyc_with_response (struct KycContext *kc) { char dat[128]; kc->response_code = MHD_HTTP_OK; kc->response = TALER_MHD_MAKE_JSON_PACK ( GNUNET_JSON_pack_array_incref ("kyc_data", kc->kycs_data)); /* KYC failed, cache briefly */ TALER_MHD_get_date_string (GNUNET_TIME_relative_to_absolute ( EXPIRATION_KYC_FAILURE), dat); GNUNET_break (MHD_YES == MHD_add_response_header (kc->response, MHD_HTTP_HEADER_EXPIRES, dat)); GNUNET_break (MHD_YES == MHD_add_response_header (kc->response, MHD_HTTP_HEADER_CACHE_CONTROL, "max-age=300")); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Resuming /kyc handling as exchange interaction is done (%u)\n", MHD_HTTP_OK); if (GNUNET_YES == kc->suspended) { kc->suspended = GNUNET_NO; MHD_resume_connection (kc->connection); TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */ } } /** * Handle a DB event about an update relevant * for the processing of the kyc request. * * @param cls our `struct KycContext` * @param extra additional event data provided * @param extra_size number of bytes in @a extra */ static void kyc_change_cb (void *cls, const void *extra, size_t extra_size) { struct KycContext *kc = cls; GNUNET_assert (GNUNET_YES == kc->suspended); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Resuming KYC with gateway timeout\n"); kc->suspended = GNUNET_NO; MHD_resume_connection (kc->connection); TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */ } /** * Pack the given @a limit into the JSON @a limits array. * * @param limit account limit to pack * @param[in,out] limits JSON array to extend */ static void pack_limit (const struct TALER_EXCHANGE_AccountLimit *limit, json_t *limits) { json_t *jl; jl = GNUNET_JSON_PACK ( TALER_JSON_pack_kycte ("operation_type", limit->operation_type), GNUNET_JSON_pack_time_rel ("timeframe", limit->timeframe), TALER_JSON_pack_amount ("threshold", &limit->threshold), GNUNET_JSON_pack_bool ("soft_limit", limit->soft_limit) ); GNUNET_assert (0 == json_array_append_new (limits, jl)); } /** * Return JSON array with AccountLimit objects giving * the current limits for this exchange. * * @param[in,out] ekr overall request context */ static json_t * get_exchange_limits ( struct ExchangeKycRequest *ekr) { const struct TALER_EXCHANGE_Keys *keys = ekr->keys; json_t *limits; if (NULL != ekr->jlimits) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Returning custom KYC limits\n"); return json_incref (ekr->jlimits); } if (NULL == keys) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "No keys, thus no default KYC limits known\n"); return NULL; } GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Returning default KYC limits (%u/%u)\n", keys->hard_limits_length, keys->zero_limits_length); limits = json_array (); GNUNET_assert (NULL != limits); for (unsigned int i = 0; ihard_limits_length; i++) { const struct TALER_EXCHANGE_AccountLimit *limit = &keys->hard_limits[i]; pack_limit (limit, limits); } for (unsigned int i = 0; izero_limits_length; i++) { const struct TALER_EXCHANGE_ZeroLimitedOperation *zlimit = &keys->zero_limits[i]; json_t *jl; struct TALER_Amount zero; GNUNET_assert (GNUNET_OK == TALER_amount_set_zero (keys->currency, &zero)); jl = GNUNET_JSON_PACK ( TALER_JSON_pack_kycte ("operation_type", zlimit->operation_type), GNUNET_JSON_pack_time_rel ("timeframe", GNUNET_TIME_UNIT_ZERO), TALER_JSON_pack_amount ("threshold", &zero), GNUNET_JSON_pack_bool ("soft_limit", true) ); GNUNET_assert (0 == json_array_append_new (limits, jl)); } return limits; } /** * Maps @a ekr to a status code for clients to interpret the * overall result. * * @param ekr request summary * @return status of the KYC state as a string */ static const char * map_to_status (const struct ExchangeKycRequest *ekr) { if (ekr->no_keys) { return "no-exchange-keys"; } if (ekr->kyc_ok) { return "ready"; } if (! ekr->auth_ok) { if (ekr->kyc_auth_conflict) return "kyc-wire-impossible"; return "kyc-wire-required"; } if (ekr->in_aml_review) return "awaiting-aml-review"; switch (ekr->last_http_status) { case 0: return "exchange-unreachable"; case MHD_HTTP_OK: /* then we should have kyc_ok */ GNUNET_break (0); return NULL; case MHD_HTTP_ACCEPTED: /* Then KYC is really what is needed */ return "kyc-required"; case MHD_HTTP_NO_CONTENT: /* then we should have had kyc_ok! */ GNUNET_break (0); return NULL; case MHD_HTTP_FORBIDDEN: /* then we should have had ! auth_ok */ GNUNET_break (0); return NULL; case MHD_HTTP_NOT_FOUND: /* then we should have had ! auth_ok */ GNUNET_break (0); return NULL; case MHD_HTTP_CONFLICT: /* then we should have had ! auth_ok */ GNUNET_break (0); return NULL; case MHD_HTTP_INTERNAL_SERVER_ERROR: return "exchange-internal-error"; case MHD_HTTP_GATEWAY_TIMEOUT: return "exchange-gateway-timeout"; default: GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Exchange responded with unexpected HTTP status %u to /kyc-check request!\n", ekr->last_http_status); break; } return "exchange-status-invalid"; } /** * Take data from @a ekr to expand our response. * * @param ekr exchange we are done inspecting */ static void ekr_expand_response (struct ExchangeKycRequest *ekr) { const char *status; status = map_to_status (ekr); if (NULL == status) { GNUNET_break (0); status = "logic-bug"; } GNUNET_assert ( 0 == json_array_append_new ( ekr->kc->kycs_data, GNUNET_JSON_PACK ( TALER_JSON_pack_full_payto ( "payto_uri", ekr->payto_uri), GNUNET_JSON_pack_data_auto ( "h_wire", &ekr->h_wire), GNUNET_JSON_pack_string ( "status", status), GNUNET_JSON_pack_string ( "exchange_url", ekr->exchange_url), GNUNET_JSON_pack_bool ("no_keys", ekr->no_keys), GNUNET_JSON_pack_bool ("auth_conflict", ekr->kyc_auth_conflict), GNUNET_JSON_pack_uint64 ("exchange_http_status", ekr->last_http_status), (TALER_EC_NONE == ekr->last_ec) ? GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_string ( "dummy", NULL)) : GNUNET_JSON_pack_uint64 ("exchange_code", ekr->last_ec), ekr->auth_ok ? GNUNET_JSON_pack_data_auto ( "access_token", &ekr->access_token) : GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_string ( "dummy", NULL)), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_array_steal ( "limits", get_exchange_limits (ekr))), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_array_incref ("payto_kycauths", ekr->pkaa)) ))); } /** * We are done with the KYC request @a ekr. Remove it from the work list and * check if we are done overall. * * @param[in] ekr key request that is done (and will be freed) */ static void ekr_finished (struct ExchangeKycRequest *ekr) { struct KycContext *kc = ekr->kc; ekr_expand_response (ekr); GNUNET_CONTAINER_DLL_remove (kc->exchange_pending_head, kc->exchange_pending_tail, ekr); json_decref (ekr->jlimits); json_decref (ekr->pkaa); if (NULL != ekr->keys) TALER_EXCHANGE_keys_decref (ekr->keys); GNUNET_free (ekr->exchange_url); GNUNET_free (ekr->payto_uri.full_payto); GNUNET_free (ekr); if (NULL != kc->exchange_pending_head) return; /* wait for more */ if ( (! kc->return_immediately) && (! GNUNET_TIME_absolute_is_past (kc->timeout)) ) { if (GNUNET_NO == kc->suspended) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Suspending: long poll target %d not reached\n", kc->lpt); MHD_suspend_connection (kc->connection); kc->suspended = GNUNET_YES; } else { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Remaining suspended: long poll target %d not reached\n", kc->lpt); } return; } /* All exchange requests done, create final big response from cumulated replies */ resume_kyc_with_response (kc); } /** * Figure out which exchange accounts from @a keys could * be used for a KYC auth wire transfer from the account * that @a ekr is checking. Will set the "pkaa" array * in @a ekr. * * @param[in,out] ekr request we are processing */ static void determine_eligible_accounts ( struct ExchangeKycRequest *ekr) { struct KycContext *kc = ekr->kc; const struct TALER_EXCHANGE_Keys *keys = ekr->keys; struct TALER_Amount kyc_amount; char *merchant_pub_str; struct TALER_NormalizedPayto np; ekr->pkaa = json_array (); GNUNET_assert (NULL != ekr->pkaa); { const struct TALER_EXCHANGE_GlobalFee *gf; gf = TALER_EXCHANGE_get_global_fee (keys, GNUNET_TIME_timestamp_get ()); if (NULL == gf) { GNUNET_assert (GNUNET_OK == TALER_amount_set_zero (keys->currency, &kyc_amount)); } else { /* FIXME: history fee should be globally renamed to KYC fee... */ kyc_amount = gf->fees.history; } } merchant_pub_str = GNUNET_STRINGS_data_to_string_alloc ( &kc->mi->merchant_pub, sizeof (kc->mi->merchant_pub)); /* For all accounts of the exchange */ np = TALER_payto_normalize (ekr->payto_uri); for (unsigned int i = 0; iaccounts_len; i++) { const struct TALER_EXCHANGE_WireAccount *account = &keys->accounts[i]; /* KYC auth transfers are never supported with conversion */ if (NULL != account->conversion_url) continue; /* filter by source account by credit_restrictions */ if (GNUNET_YES != TALER_EXCHANGE_test_account_allowed (account, true, /* credit */ np)) continue; /* exchange account is allowed, add it */ { const char *exchange_account_payto = account->fpayto_uri.full_payto; char *payto_kycauth; if (TALER_amount_is_zero (&kyc_amount)) GNUNET_asprintf (&payto_kycauth, "%s%cmessage=KYC:%s", exchange_account_payto, (NULL == strchr (exchange_account_payto, '?')) ? '?' : '&', merchant_pub_str); else GNUNET_asprintf (&payto_kycauth, "%s%camount=%s&message=KYC:%s", exchange_account_payto, (NULL == strchr (exchange_account_payto, '?')) ? '?' : '&', TALER_amount2s (&kyc_amount), merchant_pub_str); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Found account %s where KYC auth is possible\n", payto_kycauth); GNUNET_assert (0 == json_array_append_new (ekr->pkaa, json_string (payto_kycauth))); GNUNET_free (payto_kycauth); } } GNUNET_free (np.normalized_payto); GNUNET_free (merchant_pub_str); } /** * Function called with the result of a #TMH_EXCHANGES_keys4exchange() * operation. Runs the KYC check against the exchange. * * @param cls closure with our `struct ExchangeKycRequest *` * @param keys keys of the exchange context * @param exchange representation of the exchange */ static void kyc_with_exchange (void *cls, struct TALER_EXCHANGE_Keys *keys, struct TMH_Exchange *exchange) { struct ExchangeKycRequest *ekr = cls; (void) exchange; ekr->fo = NULL; if (NULL == keys) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Failed to download `%skeys`\n", ekr->exchange_url); ekr->no_keys = true; ekr_finished (ekr); return; } ekr->keys = TALER_EXCHANGE_keys_incref (keys); if (! ekr->auth_ok) { determine_eligible_accounts (ekr); if (0 == json_array_size (ekr->pkaa)) { /* No KYC auth wire transfers are possible to this exchange from our merchant bank account, so we cannot use this account with this exchange if it has any KYC requirements! */ GNUNET_log (GNUNET_ERROR_TYPE_INFO, "KYC auth to `%s' impossible for merchant account `%s'\n", ekr->exchange_url, ekr->payto_uri.full_payto); ekr->kyc_auth_conflict = true; } } ekr_finished (ekr); } /** * Function called from account_kyc_get_status() with KYC status information * for this merchant. * * @param cls our `struct KycContext *` * @param h_wire hash of the wire account * @param payto_uri payto:// URI of the merchant's bank account * @param exchange_url base URL of the exchange for which this is a status * @param last_check when did we last get an update on our KYC status from the exchange * @param kyc_ok true if we satisfied the KYC requirements * @param access_token access token for the KYC SPA, NULL if we cannot access it yet (need KYC auth wire transfer) * @param last_http_status last HTTP status from /kyc-check * @param last_ec last Taler error code from /kyc-check * @param in_aml_review true if the account is pending review * @param jlimits JSON array of applicable AccountLimits, or NULL if unknown (like defaults apply) */ static void kyc_status_cb ( void *cls, const struct TALER_MerchantWireHashP *h_wire, struct TALER_FullPayto payto_uri, const char *exchange_url, struct GNUNET_TIME_Timestamp last_check, bool kyc_ok, const struct TALER_AccountAccessTokenP *access_token, unsigned int last_http_status, enum TALER_ErrorCode last_ec, bool in_aml_review, const json_t *jlimits) { struct KycContext *kc = cls; struct ExchangeKycRequest *ekr; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "KYC status for `%s' at `%s' is %u/%s/%s/%s\n", payto_uri.full_payto, exchange_url, last_http_status, kyc_ok ? "KYC OK" : "KYC NEEDED", in_aml_review ? "IN AML REVIEW" : "NO AML REVIEW", NULL == jlimits ? "DEFAULT LIMITS" : "CUSTOM LIMITS"); switch (kc->lpt) { case TALER_EXCHANGE_KLPT_NONE: break; case TALER_EXCHANGE_KLPT_KYC_AUTH_TRANSFER: if (NULL != access_token) kc->return_immediately = true; break; case TALER_EXCHANGE_KLPT_INVESTIGATION_DONE: if (! in_aml_review) kc->return_immediately = true; break; case TALER_EXCHANGE_KLPT_KYC_OK: if (kyc_ok) kc->return_immediately = true; break; } ekr = GNUNET_new (struct ExchangeKycRequest); GNUNET_CONTAINER_DLL_insert (kc->exchange_pending_head, kc->exchange_pending_tail, ekr); ekr->last_http_status = last_http_status; ekr->last_ec = last_ec; if (NULL != jlimits) ekr->jlimits = json_incref ((json_t *) jlimits); ekr->h_wire = *h_wire; ekr->exchange_url = GNUNET_strdup (exchange_url); ekr->payto_uri.full_payto = GNUNET_strdup (payto_uri.full_payto); ekr->last_check = last_check; ekr->kyc_ok = kyc_ok; ekr->kc = kc; ekr->in_aml_review = in_aml_review; ekr->auth_ok = (NULL != access_token); if ( (! ekr->auth_ok) || (NULL == ekr->jlimits) ) { /* Figure out wire transfer instructions */ if (GNUNET_NO == kc->suspended) { MHD_suspend_connection (kc->connection); kc->suspended = GNUNET_YES; } ekr->fo = TMH_EXCHANGES_keys4exchange ( exchange_url, false, &kyc_with_exchange, ekr); if (NULL == ekr->fo) { GNUNET_break (0); ekr_finished (ekr); return; } return; } ekr->access_token = *access_token; ekr_finished (ekr); } /** * Check the KYC status of an instance. * * @param mi instance to check KYC status of * @param connection the MHD connection to handle * @param[in,out] hc context with further information about the request * @return MHD result code */ static MHD_RESULT get_instances_ID_kyc ( struct TMH_MerchantInstance *mi, struct MHD_Connection *connection, struct TMH_HandlerContext *hc) { struct KycContext *kc = hc->ctx; if (NULL == kc) { kc = GNUNET_new (struct KycContext); kc->mi = mi; hc->ctx = kc; hc->cc = &kyc_context_cleanup; GNUNET_CONTAINER_DLL_insert (kc_head, kc_tail, kc); kc->connection = connection; kc->hc = hc; kc->kycs_data = json_array (); GNUNET_assert (NULL != kc->kycs_data); TALER_MHD_parse_request_timeout (connection, &kc->timeout); { uint64_t num = 0; int val; TALER_MHD_parse_request_number (connection, "lpt", &num); val = (int) num; if ( (val < 0) || (val > TALER_EXCHANGE_KLPT_MAX) ) { /* Protocol violation, but we can be graceful and just ignore the long polling! */ GNUNET_break_op (0); val = TALER_EXCHANGE_KLPT_NONE; } kc->lpt = (enum TALER_EXCHANGE_KycLongPollTarget) val; } kc->return_immediately = (TALER_EXCHANGE_KLPT_NONE == kc->lpt); /* process 'exchange_url' argument */ kc->exchange_url = MHD_lookup_connection_value ( connection, MHD_GET_ARGUMENT_KIND, "exchange_url"); if ( (NULL != kc->exchange_url) && ( (! TALER_url_valid_charset (kc->exchange_url)) || (! TALER_is_web_url (kc->exchange_url)) ) ) { GNUNET_break_op (0); return TALER_MHD_reply_with_error ( connection, MHD_HTTP_BAD_REQUEST, TALER_EC_GENERIC_PARAMETER_MALFORMED, "exchange_url must be a valid HTTP(s) URL"); } TALER_MHD_parse_request_arg_auto (connection, "h_wire", &kc->h_wire, kc->have_h_wire); if ( (TALER_EXCHANGE_KLPT_NONE != kc->lpt) && (! GNUNET_TIME_absolute_is_past (kc->timeout)) ) { if (kc->have_h_wire) { struct TALER_MERCHANTDB_MerchantKycStatusChangeEventP ev = { .header.size = htons (sizeof (ev)), .header.type = htons ( TALER_DBEVENT_MERCHANT_EXCHANGE_KYC_STATUS_CHANGED ), .h_wire = kc->h_wire }; kc->eh = TMH_db->event_listen ( TMH_db->cls, &ev.header, GNUNET_TIME_absolute_get_remaining (kc->timeout), &kyc_change_cb, kc); } else { struct GNUNET_DB_EventHeaderP hdr = { .size = htons (sizeof (hdr)), .type = htons (TALER_DBEVENT_MERCHANT_KYC_STATUS_CHANGED) }; kc->eh = TMH_db->event_listen ( TMH_db->cls, &hdr, GNUNET_TIME_absolute_get_remaining (kc->timeout), &kyc_change_cb, kc); } } /* end register LISTEN hooks */ } /* end 1st time initialization */ if (GNUNET_SYSERR == kc->suspended) return MHD_NO; /* during shutdown, we don't generate any more replies */ GNUNET_assert (GNUNET_NO == kc->suspended); if (NULL != kc->response) return MHD_queue_response (connection, kc->response_code, kc->response); /* Check our database */ { enum GNUNET_DB_QueryStatus qs; GNUNET_break (0 == json_array_clear (kc->kycs_data)); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Checking KYC status for %s (%d/%s)\n", mi->settings.id, kc->have_h_wire, kc->exchange_url); qs = TMH_db->account_kyc_get_status ( TMH_db->cls, mi->settings.id, kc->have_h_wire ? &kc->h_wire : NULL, kc->exchange_url, &kyc_status_cb, kc); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "account_kyc_get_status returned %d records\n", (int) qs); if (qs < 0) { /* Database error */ GNUNET_break (0); if (GNUNET_YES == kc->suspended) { /* must have suspended before DB error, resume! */ MHD_resume_connection (connection); kc->suspended = GNUNET_NO; } return TALER_MHD_reply_with_ec ( connection, TALER_EC_GENERIC_DB_FETCH_FAILED, "account_kyc_get_status"); } if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) { /* no matching accounts, could not have suspended */ GNUNET_assert (GNUNET_NO == kc->suspended); return TALER_MHD_reply_static (connection, MHD_HTTP_NO_CONTENT, NULL, NULL, 0); } } if (GNUNET_YES == kc->suspended) return MHD_YES; /* Should have generated a response */ GNUNET_break (NULL != kc->response); return MHD_queue_response (connection, kc->response_code, kc->response); } MHD_RESULT TMH_private_get_instances_ID_kyc ( const struct TMH_RequestHandler *rh, struct MHD_Connection *connection, struct TMH_HandlerContext *hc) { struct TMH_MerchantInstance *mi = hc->instance; (void) rh; return get_instances_ID_kyc (mi, connection, hc); } MHD_RESULT TMH_private_get_instances_default_ID_kyc ( const struct TMH_RequestHandler *rh, struct MHD_Connection *connection, struct TMH_HandlerContext *hc) { struct TMH_MerchantInstance *mi; (void) rh; mi = TMH_lookup_instance (hc->infix); if (NULL == mi) { return TALER_MHD_reply_with_error ( connection, MHD_HTTP_NOT_FOUND, TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN, hc->infix); } return get_instances_ID_kyc (mi, connection, hc); } /* end of taler-merchant-httpd_private-get-instances-ID-kyc.c */