commit ff48ada7d5ba026a39c0e8ddefd54b80b813425b
parent d4fb0695ceb2f1c1ceb0ef1fc6fdded668396d4d
Author: Christian Grothoff <christian@grothoff.org>
Date: Mon, 8 Aug 2022 15:22:45 +0200
move kyclogic into libtalerkyclogic
Diffstat:
10 files changed, 1647 insertions(+), 1233 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -163,3 +163,4 @@ po/remove-potcdate.sed
src/include/taler_dbevents.h
src/bank-lib/taler-exchange-wire-gateway-client
src/exchange/taler-exchange-drain
+src/kyclogic/taler-exchange-kyc-tester
diff --git a/src/exchange/Makefile.am b/src/exchange/Makefile.am
@@ -132,7 +132,6 @@ taler_exchange_httpd_SOURCES = \
taler-exchange-httpd_deposits_get.c taler-exchange-httpd_deposits_get.h \
taler-exchange-httpd_extensions.c taler-exchange-httpd_extensions.h \
taler-exchange-httpd_keys.c taler-exchange-httpd_keys.h \
- taler-exchange-httpd_kyc.c taler-exchange-httpd_kyc.h \
taler-exchange-httpd_kyc-check.c taler-exchange-httpd_kyc-check.h \
taler-exchange-httpd_kyc-proof.c taler-exchange-httpd_kyc-proof.h \
taler-exchange-httpd_kyc-wallet.c taler-exchange-httpd_kyc-wallet.h \
@@ -177,6 +176,7 @@ taler_exchange_httpd_LDADD = \
$(top_builddir)/src/mhd/libtalermhd.la \
$(top_builddir)/src/json/libtalerjson.la \
$(top_builddir)/src/exchangedb/libtalerexchangedb.la \
+ $(top_builddir)/src/kyclogic/libtalerkyclogic.la \
$(top_builddir)/src/util/libtalerutil.la \
$(top_builddir)/src/extensions/libtalerextensions.la \
-lmicrohttpd \
diff --git a/src/exchange/taler-exchange-httpd_kyc-webhook.c b/src/exchange/taler-exchange-httpd_kyc-webhook.c
@@ -26,7 +26,7 @@
#include <pthread.h>
#include "taler_json_lib.h"
#include "taler_mhd_lib.h"
-#include "taler-exchange-httpd_kyc.h"
+#include "taler_kyclogic_lib.h"
#include "taler-exchange-httpd_kyc-webhook.h"
#include "taler-exchange-httpd_responses.h"
@@ -263,9 +263,9 @@ handler_kyc_webhook_generic (
rc->rh_cleaner = &clean_kwh;
if (GNUNET_OK !=
- TEH_kyc_get_logic (kwh->logic,
- &kwh->plugin,
- &kwh->pd))
+ TALER_KYCLOGIC_kyc_get_logic (kwh->logic,
+ &kwh->plugin,
+ &kwh->pd))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"KYC logic `%s' unknown (check KYC provider configuration)\n",
diff --git a/src/exchange/taler-exchange-httpd_kyc.c b/src/exchange/taler-exchange-httpd_kyc.c
@@ -1,1019 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2022 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 Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License along with
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
-*/
-/**
- * @file taler-exchange-httpd_kyc.c
- * @brief KYC API for the exchange
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include "taler-exchange-httpd.h"
-#include "taler-exchange-httpd_kyc.h"
-#include "taler_exchangedb_plugin.h"
-
-/**
- * Information about a KYC provider.
- */
-struct TEH_KycProvider;
-
-
-/**
- * Abstract representation of a KYC check.
- */
-struct TEH_KycCheck
-{
- /**
- * Human-readable name given to the KYC check.
- */
- char *name;
-
- /**
- * Array of @e num_providers providers that offer this type of KYC check.
- */
- struct TEH_KycProvider **providers;
-
- /**
- * Length of the @e providers array.
- */
- unsigned int num_providers;
-
-};
-
-
-struct TEH_KycProvider
-{
- /**
- * Name of the provider (configuration section name).
- */
- const char *provider_section_name;
-
- /**
- * Array of @e num_checks checks performed by this provider.
- */
- struct TEH_KycCheck **provided_checks;
-
- /**
- * Logic to run for this provider.
- */
- struct TALER_KYCLOGIC_Plugin *logic;
-
- /**
- * @e provider_section_name specific details to
- * pass to the @e logic functions.
- */
- struct TALER_KYCLOGIC_ProviderDetails *pd;
-
- /**
- * Cost of running this provider's KYC.
- */
- unsigned long long cost;
-
- /**
- * Length of the @e checks array.
- */
- unsigned int num_checks;
-
- /**
- * Type of user this provider supports.
- */
- enum TEH_KycUserType user_type;
-};
-
-
-/**
- * Condition that triggers a need to perform KYC.
- */
-struct TEH_KycTrigger
-{
-
- /**
- * Timeframe to consider for computing the amount
- * to compare against the @e limit. Zero for the
- * wallet balance trigger (as not applicable).
- */
- struct GNUNET_TIME_Relative timeframe;
-
- /**
- * Maximum amount that can be transacted until
- * the rule triggers.
- */
- struct TALER_Amount threshold;
-
- /**
- * Array of @e num_checks checks to apply on this trigger.
- */
- struct TEH_KycCheck **required_checks;
-
- /**
- * Length of the @e checks array.
- */
- unsigned int num_checks;
-
- /**
- * What event is this trigger for?
- */
- enum TEH_KycTriggerEvent trigger;
-
-};
-
-
-/**
- * Array of @e num_kyc_logics KYC logic plugins we have loaded.
- */
-static struct TALER_KYCLOGIC_Plugin **kyc_logics;
-
-/**
- * Length of the #kyc_logics array.
- */
-static unsigned int num_kyc_logics;
-
-/**
- * Array of @e num_kyc_checks known types of
- * KYC checks.
- */
-static struct TEH_KycCheck **kyc_checks;
-
-/**
- * Length of the #kyc_checks array.
- */
-static unsigned int num_kyc_checks;
-
-/**
- * Array of configured triggers.
- */
-static struct TEH_KycTrigger **kyc_triggers;
-
-/**
- * Length of the #kyc_triggers array.
- */
-static unsigned int num_kyc_triggers;
-
-/**
- * Array of configured providers.
- */
-static struct TEH_KycProvider **kyc_providers;
-
-/**
- * Length of the #kyc_providers array.
- */
-static unsigned int num_kyc_providers;
-
-
-enum GNUNET_GenericReturnValue
-TEH_kyc_trigger_from_string (const char *trigger_s,
- enum TEH_KycTriggerEvent *trigger)
-{
- struct
- {
- const char *in;
- enum TEH_KycTriggerEvent out;
- } map [] = {
- { "withdraw", TEH_KYC_TRIGGER_WITHDRAW },
- { "deposit", TEH_KYC_TRIGGER_DEPOSIT },
- { "merge", TEH_KYC_TRIGGER_P2P_RECEIVE },
- { "balance", TEH_KYC_TRIGGER_WALLET_BALANCE },
- { NULL, 0 }
- };
-
- for (unsigned int i = 0; NULL != map[i].in; i++)
- if (0 == strcasecmp (map[i].in,
- trigger_s))
- {
- *trigger = map[i].out;
- return GNUNET_OK;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Invalid KYC trigger `%s'\n",
- trigger_s);
- return GNUNET_SYSERR;
-}
-
-
-const char *
-TEH_kyc_trigger2s (enum TEH_KycTriggerEvent trigger)
-{
- switch (trigger)
- {
- case TEH_KYC_TRIGGER_WITHDRAW:
- return "withdraw";
- case TEH_KYC_TRIGGER_DEPOSIT:
- return "deposit";
- case TEH_KYC_TRIGGER_P2P_RECEIVE:
- return "merge";
- case TEH_KYC_TRIGGER_WALLET_BALANCE:
- return "balance";
- }
- GNUNET_break (0);
- return NULL;
-}
-
-
-enum GNUNET_GenericReturnValue
-TEH_kyc_user_type_from_string (const char *ut_s,
- enum TEH_KycUserType *ut)
-{
- struct
- {
- const char *in;
- enum TEH_KycTriggerEvent out;
- } map [] = {
- { "individual", TEH_KYC_UT_INDIVIDUAL },
- { "business", TEH_KYC_UT_BUSINESS },
- { NULL, 0 }
- };
-
- for (unsigned int i = 0; NULL != map[i].in; i++)
- if (0 == strcasecmp (map[i].in,
- ut_s))
- {
- *ut = map[i].out;
- return GNUNET_OK;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Invalid user type `%s'\n",
- ut_s);
- return GNUNET_SYSERR;
-}
-
-
-const char *
-TEH_kyc_user_type2s (enum TEH_KycUserType ut)
-{
- switch (ut)
- {
- case TEH_KYC_UT_INDIVIDUAL:
- return "individual";
- case TEH_KYC_UT_BUSINESS:
- return "business";
- }
- GNUNET_break (0);
- return NULL;
-}
-
-
-/**
- * Load KYC logic plugin.
- *
- * @param name name of the plugin
- * @return NULL on error
- */
-static struct TALER_KYCLOGIC_Plugin *
-load_logic (const char *name)
-{
- char *lib_name;
- struct TALER_KYCLOGIC_Plugin *plugin;
-
- GNUNET_asprintf (&lib_name,
- "libtaler_plugin_kyclogic_%s",
- name);
- plugin = GNUNET_PLUGIN_load (lib_name,
- (void *) TEH_cfg);
- if (NULL != plugin)
- plugin->library_name = lib_name;
- else
- GNUNET_free (lib_name);
- return plugin;
-}
-
-
-/**
- * Add check type to global array of checks.
- * First checks if the type already exists, otherwise
- * adds a new one.
- *
- * @param check name of the check
- * @return pointer into the global list
- */
-static struct TEH_KycCheck *
-add_check (const char *check)
-{
- struct TEH_KycCheck *kc;
-
- for (unsigned int i = 0; i<num_kyc_checks; i++)
- if (0 == strcasecmp (check,
- kyc_checks[i]->name))
- return kyc_checks[i];
- kc = GNUNET_new (struct TEH_KycCheck);
- kc->name = GNUNET_strdup (check);
- GNUNET_array_append (kyc_checks,
- num_kyc_checks,
- kc);
- return kc;
-}
-
-
-/**
- * Parse list of checks from @a checks and build an
- * array of aliases into the global checks array
- * in @a provided_checks.
- *
- * @param[in,out] checks list of checks; clobbered
- * @param[out] p_checks where to put array of aliases
- * @param[out] num_p_checks set to length of @a p_checks array
- */
-static void
-add_checks (char *checks,
- struct TEH_KycCheck ***p_checks,
- unsigned int *num_p_checks)
-{
- char *sptr;
- struct TEH_KycCheck **rchecks = NULL;
- unsigned int num_rchecks = 0;
-
- for (char *tok = strtok_r (checks, " ", &sptr);
- NULL != tok;
- tok = strtok_r (NULL, " ", &sptr))
- {
- struct TEH_KycCheck *kc;
-
- kc = add_check (tok);
- GNUNET_array_append (rchecks,
- num_rchecks,
- kc);
- }
- *p_checks = rchecks;
- *num_p_checks = num_rchecks;
-}
-
-
-/**
- * Parse configuration of a KYC provider.
- *
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-add_provider (const char *section)
-{
- unsigned long long cost;
- char *logic;
- char *ut_s;
- enum TEH_KycUserType ut;
- char *checks;
- struct TALER_KYCLOGIC_Plugin *lp;
-
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_number (TEH_cfg,
- section,
- "COST",
- &cost))
- {
- GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
- section,
- "COST",
- "number required");
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_string (TEH_cfg,
- section,
- "USER_TYPE",
- &ut_s))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- section,
- "USER_TYPE");
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TEH_kyc_user_type_from_string (ut_s,
- &ut))
- {
- GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
- section,
- "USER_TYPE",
- "valid user type required");
- GNUNET_free (ut_s);
- return GNUNET_SYSERR;
- }
- GNUNET_free (ut_s);
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_string (TEH_cfg,
- section,
- "LOGIC",
- &logic))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- section,
- "LOGIC");
- return GNUNET_SYSERR;
- }
- lp = load_logic (logic);
- if (NULL == lp)
- {
- GNUNET_free (logic);
- GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
- section,
- "LOGIC",
- "logic plugin could not be loaded");
- return GNUNET_SYSERR;
- }
- GNUNET_free (logic);
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_string (TEH_cfg,
- section,
- "PROVIDED_CHECKS",
- &checks))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- section,
- "PROVIDED_CHECKS");
- return GNUNET_SYSERR;
- }
- {
- struct TEH_KycProvider *kp;
-
- kp = GNUNET_new (struct TEH_KycProvider);
- kp->provider_section_name = section;
- kp->user_type = ut;
- kp->logic = lp;
- kp->cost = cost;
- add_checks (checks,
- &kp->provided_checks,
- &kp->num_checks);
- GNUNET_free (checks);
- kp->pd = lp->load_configuration (lp->cls,
- section);
- if (NULL == kp->pd)
- {
- GNUNET_free (kp);
- return GNUNET_SYSERR;
- }
- GNUNET_array_append (kyc_providers,
- num_kyc_providers,
- kp);
- for (unsigned int i = 0; i<kp->num_checks; i++)
- {
- struct TEH_KycCheck *kc = kp->provided_checks[i];
-
- GNUNET_array_append (kc->providers,
- kc->num_providers,
- kp);
- }
- }
- return GNUNET_OK;
-}
-
-
-static enum GNUNET_GenericReturnValue
-add_trigger (const char *section)
-{
- char *ot_s;
- struct TALER_Amount threshold;
- struct GNUNET_TIME_Relative timeframe;
- char *checks;
- enum TEH_KycTriggerEvent ot;
-
- if (GNUNET_OK !=
- TALER_config_get_amount (TEH_cfg,
- section,
- "THRESHOLD",
- &threshold))
- {
- GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
- section,
- "THRESHOLD",
- "amount required");
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_string (TEH_cfg,
- section,
- "OPERATION_TYPE",
- &ot_s))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- section,
- "OPERATION_TYPE");
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TEH_kyc_trigger_from_string (ot_s,
- &ot))
- {
- GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
- section,
- "OPERATION_TYPE",
- "valid trigger type required");
- GNUNET_free (ot_s);
- return GNUNET_SYSERR;
- }
- GNUNET_free (ot_s);
-
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_time (TEH_cfg,
- section,
- "TIMEFRAME",
- &timeframe))
- {
- if (TEH_KYC_TRIGGER_WALLET_BALANCE == ot)
- {
- timeframe = GNUNET_TIME_UNIT_ZERO;
- }
- else
- {
- GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
- section,
- "TIMEFRAME",
- "duration required");
- return GNUNET_SYSERR;
- }
- }
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_string (TEH_cfg,
- section,
- "REQUIRED_CHECKS",
- &checks))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- section,
- "REQUIRED_CHECKS");
- return GNUNET_SYSERR;
- }
-
- {
- struct TEH_KycTrigger *kt;
-
- kt = GNUNET_new (struct TEH_KycTrigger);
- kt->timeframe = timeframe;
- kt->threshold = threshold;
- kt->trigger = ot;
- add_checks (checks,
- &kt->required_checks,
- &kt->num_checks);
- GNUNET_free (checks);
- GNUNET_array_append (kyc_triggers,
- num_kyc_triggers,
- kt);
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Function to iterate over configuration sections.
- *
- * @param cls closure, `boolean *`, set to false on failure
- * @param section name of the section
- */
-static void
-handle_section (void *cls,
- const char *section)
-{
- bool *ok = cls;
-
- if (0 == strncasecmp (section,
- "kyc-provider-",
- strlen ("kyc-provider-")))
- {
- if (GNUNET_OK !=
- add_provider (section))
- *ok = false;
- return;
- }
- if (0 == strncasecmp (section,
- "kyc-legitimization-",
- strlen ("kyc-legitimization-")))
- {
- if (GNUNET_OK !=
- add_trigger (section))
- *ok = false;
- return;
- }
-}
-
-
-/**
- * Comparator for qsort. Compares two triggers
- * by timeframe to sort triggers by time.
- *
- * @param p1 first trigger to compare
- * @param p2 second trigger to compare
- * @return -1 if p1 < p2, 0 if p1==p2, 1 if p1 > p2.
- */
-static int
-sort_by_timeframe (const void *p1,
- const void *p2)
-{
- struct TEH_KycTrigger **t1 = (struct TEH_KycTrigger **) p1;
- struct TEH_KycTrigger **t2 = (struct TEH_KycTrigger **) p2;
-
- if (GNUNET_TIME_relative_cmp ((*t1)->timeframe,
- <,
- (*t2)->timeframe))
- return -1;
- if (GNUNET_TIME_relative_cmp ((*t1)->timeframe,
- >,
- (*t2)->timeframe))
- return 1;
- return 0;
-}
-
-
-enum GNUNET_GenericReturnValue
-TEH_kyc_init (void)
-{
- bool ok = true;
-
- GNUNET_CONFIGURATION_iterate_sections (TEH_cfg,
- &handle_section,
- &ok);
- if (! ok)
- {
- TEH_kyc_done ();
- return GNUNET_SYSERR;
- }
-
- /* sanity check: ensure at least one provider exists
- for any trigger and indidivual or business. */
- for (unsigned int i = 0; i<num_kyc_checks; i++)
- if (0 == kyc_checks[i]->num_providers)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "No provider available for required KYC check `%s'\n",
- kyc_checks[i]->name);
- TEH_kyc_done ();
- return GNUNET_SYSERR;
- }
- qsort (kyc_triggers,
- num_kyc_triggers,
- sizeof (struct TEH_KycTrigger *),
- &sort_by_timeframe);
- return GNUNET_OK;
-}
-
-
-void
-TEH_kyc_done (void)
-{
- for (unsigned int i = 0; i<num_kyc_triggers; i++)
- {
- struct TEH_KycTrigger *kt = kyc_triggers[i];
-
- GNUNET_array_grow (kt->required_checks,
- kt->num_checks,
- 0);
- GNUNET_free (kt);
- }
- GNUNET_array_grow (kyc_triggers,
- num_kyc_triggers,
- 0);
- for (unsigned int i = 0; i<num_kyc_providers; i++)
- {
- struct TEH_KycProvider *kp = kyc_providers[i];
-
- kp->logic->unload_configuration (kp->pd);
- GNUNET_array_grow (kp->provided_checks,
- kp->num_checks,
- 0);
- GNUNET_free (kp);
- }
- GNUNET_array_grow (kyc_providers,
- num_kyc_providers,
- 0);
- for (unsigned int i = 0; i<num_kyc_logics; i++)
- {
- struct TALER_KYCLOGIC_Plugin *lp = kyc_logics[i];
- char *lib_name = lp->library_name;
-
- GNUNET_assert (NULL == GNUNET_PLUGIN_unload (lib_name,
- lp));
- GNUNET_free (lib_name);
- }
- GNUNET_array_grow (kyc_logics,
- num_kyc_logics,
- 0);
- for (unsigned int i = 0; i<num_kyc_checks; i++)
- {
- struct TEH_KycCheck *kc = kyc_checks[i];
-
- GNUNET_free (kc->name);
- GNUNET_free (kc);
- }
- GNUNET_array_grow (kyc_checks,
- num_kyc_checks,
- 0);
-}
-
-
-/**
- * Closure for the #eval_trigger().
- */
-struct ThresholdTestContext
-{
- /**
- * Total amount so far.
- */
- struct TALER_Amount total;
-
- /**
- * Trigger event to evaluate triggers of.
- */
- enum TEH_KycTriggerEvent event;
-
- /**
- * Offset in the triggers array where we need to start
- * checking for triggers. All trigges below this
- * offset were already hit.
- */
- unsigned int start;
-
- /**
- * Array of checks needed so far.
- */
- struct TEH_KycCheck **needed;
-
- /**
- * Pointer to number of entries used in @a needed.
- */
- unsigned int *needed_cnt;
-
-};
-
-
-/**
- * Function called on each @a amount that was found to
- * be relevant for a KYC check.
- *
- * @param cls closure to allow the KYC module to
- * total up amounts and evaluate rules
- * @param amount encountered transaction amount
- * @param date when was the amount encountered
- * @return #GNUNET_OK to continue to iterate,
- * #GNUNET_NO to abort iteration
- * #GNUNET_SYSERR on internal error (also abort itaration)
- */
-static enum GNUNET_GenericReturnValue
-eval_trigger (void *cls,
- const struct TALER_Amount *amount,
- struct GNUNET_TIME_Absolute date)
-{
- struct ThresholdTestContext *ttc = cls;
- struct GNUNET_TIME_Relative duration;
- bool bump = true;
-
- duration = GNUNET_TIME_absolute_get_duration (date);
- if (0 >
- TALER_amount_add (&ttc->total,
- &ttc->total,
- amount))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- for (unsigned int i = ttc->start; i<num_kyc_triggers; i++)
- {
- const struct TEH_KycTrigger *kt = kyc_triggers[i];
-
- if (ttc->event != kt->trigger)
- continue;
- duration = GNUNET_TIME_relative_max (duration,
- kt->timeframe);
- if (GNUNET_TIME_relative_cmp (kt->timeframe,
- >,
- duration))
- {
- if (bump)
- ttc->start = i;
- return GNUNET_OK;
- }
- if (-1 ==
- TALER_amount_cmp (&ttc->total,
- &kt->threshold))
- {
- if (bump)
- ttc->start = i;
- bump = false;
- continue; /* amount too low to trigger */
- }
- /* add check to list of required checks, unless
- already present... */
- for (unsigned int j = 0; j<kt->num_checks; j++)
- {
- struct TEH_KycCheck *rc = kt->required_checks[j];
- bool found = false;
-
- for (unsigned int k = 0; k<*ttc->needed_cnt; k++)
- if (ttc->needed[k] == rc)
- {
- found = true;
- break;
- }
- if (! found)
- {
- ttc->needed[*ttc->needed_cnt] = rc;
- (*ttc->needed_cnt)++;
- }
- }
- }
- if (bump)
- return GNUNET_NO; /* we hit all possible triggers! */
- return GNUNET_OK;
-}
-
-
-/**
- * Closure for the #remove_satisfied().
- */
-struct RemoveContext
-{
-
- /**
- * Array of checks needed so far.
- */
- struct TEH_KycCheck **needed;
-
- /**
- * Pointer to number of entries used in @a needed.
- */
- unsigned int *needed_cnt;
-
-};
-
-
-/**
- * Remove all checks satisfied by @a provider_name from
- * our list of checks.
- *
- * @param cls a `struct RemoveContext`
- * @param provider_name section name of provider that was already run previously
- */
-static void
-remove_satisfied (void *cls,
- const char *provider_name)
-{
- struct RemoveContext *rc = cls;
-
- for (unsigned int i = 0; i<num_kyc_providers; i++)
- {
- const struct TEH_KycProvider *kp = kyc_providers[i];
-
- if (0 != strcasecmp (provider_name,
- kp->provider_section_name))
- continue;
- for (unsigned int j = 0; j<kp->num_checks; j++)
- {
- const struct TEH_KycCheck *kc = kp->provided_checks[j];
-
- for (unsigned int k = 0; k<*rc->needed_cnt; k++)
- if (kc == rc->needed[k])
- {
- rc->needed[k] = rc->needed[*rc->needed_cnt - 1];
- (*rc->needed_cnt)--;
- if (0 == *rc->needed_cnt)
- return; /* for sure finished */
- break;
- }
- }
- break;
- }
-}
-
-
-const char *
-TEH_kyc_test_required (enum TEH_KycTriggerEvent event,
- const struct TALER_PaytoHashP *h_payto,
- TEH_KycAmountIterator ai,
- void *ai_cls)
-{
- struct TEH_KycCheck *needed[num_kyc_checks];
- unsigned int needed_cnt = 0;
- struct GNUNET_TIME_Relative timeframe;
- unsigned long long min_cost = ULONG_LONG_MAX;
- unsigned int max_checks = 0;
- const struct TEH_KycProvider *kp_best = NULL;
-
- timeframe = GNUNET_TIME_UNIT_ZERO;
- for (unsigned int i = 0; i<num_kyc_triggers; i++)
- {
- const struct TEH_KycTrigger *kt = kyc_triggers[i];
-
- if (event != kt->trigger)
- continue;
- timeframe = GNUNET_TIME_relative_max (timeframe,
- kt->timeframe);
- }
- {
- struct GNUNET_TIME_Absolute now;
- struct ThresholdTestContext ttc = {
- .event = event,
- .needed = needed,
- .needed_cnt = &needed_cnt
- };
-
- TALER_amount_set_zero (TEH_currency,
- &ttc.total);
- now = GNUNET_TIME_absolute_get ();
- ai (ai_cls,
- GNUNET_TIME_absolute_subtract (now,
- timeframe),
- &eval_trigger,
- &ttc);
- }
- if (0 == needed_cnt)
- return NULL;
- {
- struct RemoveContext rc = {
- .needed = needed,
- .needed_cnt = &needed_cnt
- };
- enum GNUNET_DB_QueryStatus qs;
-
- /* Check what provider checks are already satisfied for h_payto (with
- database), remove those from the 'needed' array. */
- GNUNET_break (0);
- qs = TEH_plugin->select_satisfied_kyc_processes (TEH_plugin->cls,
- h_payto,
- &remove_satisfied,
- &rc);
- GNUNET_break (qs >= 0); // FIXME: handle DB failure more nicely?
- }
-
- /* Count maximum number of remaining checks covered by any
- provider */
- for (unsigned int i = 0; i<num_kyc_providers; i++)
- {
- const struct TEH_KycProvider *kp = kyc_providers[i];
- unsigned int matched = 0;
-
- for (unsigned int j = 0; j<kp->num_checks; j++)
- {
- const struct TEH_KycCheck *kc = kp->provided_checks[j];
-
- for (unsigned int k = 0; k<needed_cnt; k++)
- if (kc == needed[k])
- {
- matched++;
- break;
- }
- }
- max_checks = GNUNET_MAX (max_checks,
- matched);
- }
-
- /* Find min-cost provider covering max_checks. */
- for (unsigned int i = 0; i<num_kyc_providers; i++)
- {
- const struct TEH_KycProvider *kp = kyc_providers[i];
- unsigned int matched = 0;
-
- for (unsigned int j = 0; j<kp->num_checks; j++)
- {
- const struct TEH_KycCheck *kc = kp->provided_checks[j];
-
- for (unsigned int k = 0; k<needed_cnt; k++)
- if (kc == needed[k])
- {
- matched++;
- break;
- }
- }
- if ( (max_checks == matched) &&
- (kp->cost < min_cost) )
- {
- min_cost = kp->cost;
- kp_best = kp;
- }
- }
-
- GNUNET_assert (NULL != kp_best);
- return kp_best->provider_section_name;
-}
-
-
-enum GNUNET_GenericReturnValue
-TEH_kyc_get_logic (const char *provider_section_name,
- struct TALER_KYCLOGIC_Plugin **plugin,
- struct TALER_KYCLOGIC_ProviderDetails **pd)
-{
- for (unsigned int i = 0; i<num_kyc_providers; i++)
- {
- struct TEH_KycProvider *kp = kyc_providers[i];
-
- if (0 !=
- strcasecmp (provider_section_name,
- kp->provider_section_name))
- continue;
- *plugin = kp->logic;
- *pd = kp->pd;
- return GNUNET_OK;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Provider `%s' unknown\n",
- provider_section_name);
- return GNUNET_SYSERR;
-}
-
-
-/* end of taler-exchange-httpd_kyc.c */
diff --git a/src/exchange/taler-exchange-httpd_kyc.h b/src/exchange/taler-exchange-httpd_kyc.h
@@ -1,202 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2022 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 Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License along with
- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
-*/
-/**
- * @file taler-exchange-httpd_kyc.h
- * @brief KYC API for the exchange
- * @author Christian Grothoff
- */
-#ifndef TALER_EXCHANGE_HTTPD_KYC_H
-#define TALER_EXCHANGE_HTTPD_KYC_H
-
-#include <microhttpd.h>
-#include "taler_exchangedb_plugin.h"
-#include "taler_kyclogic_plugin.h"
-
-
-/**
- * Enumeration for our KYC user types.
- */
-enum TEH_KycUserType
-{
- /**
- * KYC rule is for an individual.
- */
- TEH_KYC_UT_INDIVIDUAL = 0,
-
- /**
- * KYC rule is for a business.
- */
- TEH_KYC_UT_BUSINESS = 1
-};
-
-
-/**
- * Enumeration of possible events that may trigger
- * KYC requirements.
- */
-enum TEH_KycTriggerEvent
-{
-
- /**
- * Customer withdraws coins.
- */
- TEH_KYC_TRIGGER_WITHDRAW = 0,
-
- /**
- * Merchant deposits coins.
- */
- TEH_KYC_TRIGGER_DEPOSIT = 1,
-
- /**
- * Wallet receives P2P payment.
- */
- TEH_KYC_TRIGGER_P2P_RECEIVE = 2,
-
- /**
- * Wallet balance exceeds threshold.
- */
- TEH_KYC_TRIGGER_WALLET_BALANCE = 3
-
-};
-
-
-/**
- * Parse KYC trigger string value from a string
- * into enumeration value.
- *
- * @param trigger_s string to parse
- * @param[out] trigger set to the value found
- * @return #GNUNET_OK on success, #GNUNET_NO if option
- * does not exist, #GNUNET_SYSERR if option is
- * malformed
- */
-enum GNUNET_GenericReturnValue
-TEH_kyc_trigger_from_string (const char *trigger_s,
- enum TEH_KycTriggerEvent *trigger);
-
-
-/**
- * Convert KYC trigger value to human-readable string.
- *
- * @param trigger value to convert
- * @return human-readable representation of the @a trigger
- */
-const char *
-TEH_kyc_trigger2s (enum TEH_KycTriggerEvent trigger);
-
-
-/**
- * Parse user type string into enumeration value.
- *
- * @param ut_s string to parse
- * @param[out] ut set to the value found
- * @return #GNUNET_OK on success, #GNUNET_NO if option
- * does not exist, #GNUNET_SYSERR if option is
- * malformed
- */
-enum GNUNET_GenericReturnValue
-TEH_kyc_user_type_from_string (const char *ut_s,
- enum TEH_KycUserType *ut);
-
-
-/**
- * Convert KYC user type to human-readable string.
- *
- * @param ut value to convert
- * @return human-readable representation of the @a ut
- */
-const char *
-TEH_kyc_user_type2s (enum TEH_KycUserType ut);
-
-
-/**
- * Initialize KYC subsystem. Loads the KYC
- * configuration.
- *
- * @return #GNUNET_OK on success
- */
-enum GNUNET_GenericReturnValue
-TEH_kyc_init (void);
-
-
-/**
- * Shut down the KYC subsystem.
- */
-void
-TEH_kyc_done (void);
-
-
-/**
- * Function called to iterate over KYC-relevant
- * transaction amounts for a particular time range.
- * Called within a database transaction, so must
- * not start a new one.
- *
- * @param cls closure, identifies the event type and
- * account to iterate over events for
- * @param limit maximum time-range for which events
- * should be fetched (timestamp in the past)
- * @param cb function to call on each event found,
- * events must be returned in reverse chronological
- * order
- * @param cb_cls closure for @a cb
- */
-typedef void
-(*TEH_KycAmountIterator)(void *cls,
- struct GNUNET_TIME_Absolute limit,
- TALER_EXCHANGEDB_KycAmountCallback cb,
- void *cb_cls);
-
-
-/**
- * Check if KYC is provided for a particular operation. Returns the best
- * provider (configuration section name) that could perform the required
- * check.
- *
- * Called within a database transaction, so must
- * not start a new one.
- *
- * @param event what type of operation is triggering the
- * test if KYC is required
- * @param h_payto account the event is about
- * @param ai callback offered to inquire about historic
- * amounts involved in this type of operation
- * at the given account
- * @param ai_cls closure for @a ai
- * @return NULL if no check is needed
- */
-const char *
-TEH_kyc_test_required (enum TEH_KycTriggerEvent event,
- const struct TALER_PaytoHashP *h_payto,
- TEH_KycAmountIterator ai,
- void *ai_cls);
-
-
-/**
- * Obtain the provider logic for a given @a provider_section_name.
- *
- * @param provider_section_name identifies a KYC provider process
- * @param[out] plugin set to the KYC logic API
- * @param[out] pd set to the specific operation context
- * @return #GNUNET_OK on success
- */
-enum GNUNET_GenericReturnValue
-TEH_kyc_get_logic (const char *provider_section_name,
- struct TALER_KYCLOGIC_Plugin **plugin,
- struct TALER_KYCLOGIC_ProviderDetails **pd);
-
-
-#endif
diff --git a/src/include/Makefile.am b/src/include/Makefile.am
@@ -17,6 +17,7 @@ talerinclude_HEADERS = \
taler_exchangedb_plugin.h \
taler_extensions.h \
taler_fakebank_lib.h \
+ taler_kyclogic_lib.h \
taler_kyclogic_plugin.h \
taler_json_lib.h \
taler_testing_lib.h \
diff --git a/src/include/taler_kyclogic_lib.h b/src/include/taler_kyclogic_lib.h
@@ -0,0 +1,226 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 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 Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file taler_kyclogic_lib.h
+ * @brief server-side KYC API
+ * @author Christian Grothoff
+ */
+#ifndef TALER_KYCLOGIC_LIB_H
+#define TALER_KYCLOGIC_LIB_H
+
+#include <microhttpd.h>
+#include <taler_exchangedb_plugin.h>
+#include <taler_kyclogic_plugin.h>
+
+
+/**
+ * Enumeration for our KYC user types.
+ */
+enum TALER_KYCLOGIC_KycUserType
+{
+ /**
+ * KYC rule is for an individual.
+ */
+ TALER_KYCLOGIC_KYC_UT_INDIVIDUAL = 0,
+
+ /**
+ * KYC rule is for a business.
+ */
+ TALER_KYCLOGIC_KYC_UT_BUSINESS = 1
+};
+
+
+/**
+ * Enumeration of possible events that may trigger
+ * KYC requirements.
+ */
+enum TALER_KYCLOGIC_KycTriggerEvent
+{
+
+ /**
+ * Customer withdraws coins.
+ */
+ TALER_KYCLOGIC_KYC_TRIGGER_WITHDRAW = 0,
+
+ /**
+ * Merchant deposits coins.
+ */
+ TALER_KYCLOGIC_KYC_TRIGGER_DEPOSIT = 1,
+
+ /**
+ * Wallet receives P2P payment.
+ */
+ TALER_KYCLOGIC_KYC_TRIGGER_P2P_RECEIVE = 2,
+
+ /**
+ * Wallet balance exceeds threshold.
+ */
+ TALER_KYCLOGIC_KYC_TRIGGER_WALLET_BALANCE = 3
+
+};
+
+
+/**
+ * Parse KYC trigger string value from a string
+ * into enumeration value.
+ *
+ * @param trigger_s string to parse
+ * @param[out] trigger set to the value found
+ * @return #GNUNET_OK on success, #GNUNET_NO if option
+ * does not exist, #GNUNET_SYSERR if option is
+ * malformed
+ */
+enum GNUNET_GenericReturnValue
+TALER_KYCLOGIC_kyc_trigger_from_string (
+ const char *trigger_s,
+ enum TALER_KYCLOGIC_KycTriggerEvent *trigger);
+
+
+/**
+ * Convert KYC trigger value to human-readable string.
+ *
+ * @param trigger value to convert
+ * @return human-readable representation of the @a trigger
+ */
+const char *
+TALER_KYCLOGIC_kyc_trigger2s (enum TALER_KYCLOGIC_KycTriggerEvent trigger);
+
+
+/**
+ * Parse user type string into enumeration value.
+ *
+ * @param ut_s string to parse
+ * @param[out] ut set to the value found
+ * @return #GNUNET_OK on success, #GNUNET_NO if option
+ * does not exist, #GNUNET_SYSERR if option is
+ * malformed
+ */
+enum GNUNET_GenericReturnValue
+TALER_KYCLOGIC_kyc_user_type_from_string (const char *ut_s,
+ enum TALER_KYCLOGIC_KycUserType *ut);
+
+
+/**
+ * Convert KYC user type to human-readable string.
+ *
+ * @param ut value to convert
+ * @return human-readable representation of the @a ut
+ */
+const char *
+TALER_KYCLOGIC_kyc_user_type2s (enum TALER_KYCLOGIC_KycUserType ut);
+
+
+/**
+ * Initialize KYC subsystem. Loads the KYC configuration.
+ *
+ * @param cfg configuration to parse
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TALER_KYCLOGIC_kyc_init (const struct GNUNET_CONFIGURATION_Handle *cfg);
+
+
+/**
+ * Shut down the KYC subsystem.
+ */
+void
+TALER_KYCLOGIC_kyc_done (void);
+
+
+/**
+ * Function called to iterate over KYC-relevant
+ * transaction amounts for a particular time range.
+ * Called within a database transaction, so must
+ * not start a new one.
+ *
+ * @param cls closure, identifies the event type and
+ * account to iterate over events for
+ * @param limit maximum time-range for which events
+ * should be fetched (timestamp in the past)
+ * @param cb function to call on each event found,
+ * events must be returned in reverse chronological
+ * order
+ * @param cb_cls closure for @a cb
+ */
+typedef void
+(*TALER_KYCLOGIC_KycAmountIterator)(void *cls,
+ struct GNUNET_TIME_Absolute limit,
+ TALER_EXCHANGEDB_KycAmountCallback cb,
+ void *cb_cls);
+
+
+/**
+ * Call us on KYC processes satisfied for the given
+ * account. Must match the ``select_satisfied_kyc_processes`` of the exchange database plugin.
+ *
+ * @param cls the @e cls of this struct with the plugin-specific state
+ * @param h_payto account identifier
+ * @param spc function to call for each satisfied KYC process
+ * @param spc_cls closure for @a spc
+ * @return transaction status code
+ */
+typedef enum GNUNET_DB_QueryStatus
+(*TALER_KYCLOGIC_KycSatisfiedIterator)(
+ void *cls,
+ const struct TALER_PaytoHashP *h_payto,
+ TALER_EXCHANGEDB_SatisfiedProviderCallback spc,
+ void *spc_cls);
+
+
+/**
+ * Check if KYC is provided for a particular operation. Returns the best
+ * provider (configuration section name) that could perform the required
+ * check.
+ *
+ * Called within a database transaction, so must
+ * not start a new one.
+ *
+ * @param event what type of operation is triggering the
+ * test if KYC is required
+ * @param h_payto account the event is about
+ * @param ki callback that returns list of already
+ * satisfied KYC checks, implemented by ``select_satisfied_kyc_processes`` of the exchangedb
+ * @param ki_cls closure for @a ki
+ * @param ai callback offered to inquire about historic
+ * amounts involved in this type of operation
+ * at the given account
+ * @param ai_cls closure for @a ai
+ * @return NULL if no check is needed
+ */
+const char *
+TALER_KYCLOGIC_kyc_test_required (enum TALER_KYCLOGIC_KycTriggerEvent event,
+ const struct TALER_PaytoHashP *h_payto,
+ TALER_KYCLOGIC_KycSatisfiedIterator ki,
+ void *ki_cls,
+ TALER_KYCLOGIC_KycAmountIterator ai,
+ void *ai_cls);
+
+
+/**
+ * Obtain the provider logic for a given @a provider_section_name.
+ *
+ * @param provider_section_name identifies a KYC provider process
+ * @param[out] plugin set to the KYC logic API
+ * @param[out] pd set to the specific operation context
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TALER_KYCLOGIC_kyc_get_logic (const char *provider_section_name,
+ struct TALER_KYCLOGIC_Plugin **plugin,
+ struct TALER_KYCLOGIC_ProviderDetails **pd);
+
+
+#endif
diff --git a/src/kyclogic/Makefile.am b/src/kyclogic/Makefile.am
@@ -16,6 +16,17 @@ EXTRA_DIST = \
kyclogic.conf \
kyclogic-oauth2.conf
+lib_LTLIBRARIES = \
+ libtalerkyclogic.la
+
+libtalerkyclogic_la_SOURCES = \
+ kyclogic_api.c
+libtalerkyclogic_la_LIBADD = \
+ $(top_builddir)/src/util/libtalerutil.la \
+ -lgnunetutil \
+ $(XLIB)
+
+
bin_PROGRAMS = \
taler-exchange-kyc-tester
@@ -23,6 +34,7 @@ taler_exchange_kyc_tester_SOURCES = \
taler-exchange-kyc-tester.c
taler_exchange_kyc_tester_LDADD = \
$(LIBGCRYPT_LIBS) \
+ libtalerkyclogic.la \
$(top_builddir)/src/mhd/libtalermhd.la \
$(top_builddir)/src/json/libtalerjson.la \
$(top_builddir)/src/util/libtalerutil.la \
diff --git a/src/kyclogic/kyclogic_api.c b/src/kyclogic/kyclogic_api.c
@@ -0,0 +1,1062 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 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 Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file kyclogic_api.c
+ * @brief server-side KYC API
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_kyclogic_lib.h"
+
+/**
+ * Information about a KYC provider.
+ */
+struct TALER_KYCLOGIC_KycProvider;
+
+
+/**
+ * Abstract representation of a KYC check.
+ */
+struct TALER_KYCLOGIC_KycCheck
+{
+ /**
+ * Human-readable name given to the KYC check.
+ */
+ char *name;
+
+ /**
+ * Array of @e num_providers providers that offer this type of KYC check.
+ */
+ struct TALER_KYCLOGIC_KycProvider **providers;
+
+ /**
+ * Length of the @e providers array.
+ */
+ unsigned int num_providers;
+
+};
+
+
+struct TALER_KYCLOGIC_KycProvider
+{
+ /**
+ * Name of the provider (configuration section name).
+ */
+ const char *provider_section_name;
+
+ /**
+ * Array of @e num_checks checks performed by this provider.
+ */
+ struct TALER_KYCLOGIC_KycCheck **provided_checks;
+
+ /**
+ * Logic to run for this provider.
+ */
+ struct TALER_KYCLOGIC_Plugin *logic;
+
+ /**
+ * @e provider_section_name specific details to
+ * pass to the @e logic functions.
+ */
+ struct TALER_KYCLOGIC_ProviderDetails *pd;
+
+ /**
+ * Cost of running this provider's KYC.
+ */
+ unsigned long long cost;
+
+ /**
+ * Length of the @e checks array.
+ */
+ unsigned int num_checks;
+
+ /**
+ * Type of user this provider supports.
+ */
+ enum TALER_KYCLOGIC_KycUserType user_type;
+};
+
+
+/**
+ * Condition that triggers a need to perform KYC.
+ */
+struct TALER_KYCLOGIC_KycTrigger
+{
+
+ /**
+ * Timeframe to consider for computing the amount
+ * to compare against the @e limit. Zero for the
+ * wallet balance trigger (as not applicable).
+ */
+ struct GNUNET_TIME_Relative timeframe;
+
+ /**
+ * Maximum amount that can be transacted until
+ * the rule triggers.
+ */
+ struct TALER_Amount threshold;
+
+ /**
+ * Array of @e num_checks checks to apply on this trigger.
+ */
+ struct TALER_KYCLOGIC_KycCheck **required_checks;
+
+ /**
+ * Length of the @e checks array.
+ */
+ unsigned int num_checks;
+
+ /**
+ * What event is this trigger for?
+ */
+ enum TALER_KYCLOGIC_KycTriggerEvent trigger;
+
+};
+
+
+/**
+ * Array of @e num_kyc_logics KYC logic plugins we have loaded.
+ */
+static struct TALER_KYCLOGIC_Plugin **kyc_logics;
+
+/**
+ * Length of the #kyc_logics array.
+ */
+static unsigned int num_kyc_logics;
+
+/**
+ * Array of @e num_kyc_checks known types of
+ * KYC checks.
+ */
+static struct TALER_KYCLOGIC_KycCheck **kyc_checks;
+
+/**
+ * Length of the #kyc_checks array.
+ */
+static unsigned int num_kyc_checks;
+
+/**
+ * Array of configured triggers.
+ */
+static struct TALER_KYCLOGIC_KycTrigger **kyc_triggers;
+
+/**
+ * Length of the #kyc_triggers array.
+ */
+static unsigned int num_kyc_triggers;
+
+/**
+ * Array of configured providers.
+ */
+static struct TALER_KYCLOGIC_KycProvider **kyc_providers;
+
+/**
+ * Length of the #kyc_providers array.
+ */
+static unsigned int num_kyc_providers;
+
+
+enum GNUNET_GenericReturnValue
+TALER_KYCLOGIC_kyc_trigger_from_string (const char *trigger_s,
+ enum TALER_KYCLOGIC_KycTriggerEvent *
+ trigger)
+{
+ struct
+ {
+ const char *in;
+ enum TALER_KYCLOGIC_KycTriggerEvent out;
+ } map [] = {
+ { "withdraw", TALER_KYCLOGIC_KYC_TRIGGER_WITHDRAW },
+ { "deposit", TALER_KYCLOGIC_KYC_TRIGGER_DEPOSIT },
+ { "merge", TALER_KYCLOGIC_KYC_TRIGGER_P2P_RECEIVE },
+ { "balance", TALER_KYCLOGIC_KYC_TRIGGER_WALLET_BALANCE },
+ { NULL, 0 }
+ };
+
+ for (unsigned int i = 0; NULL != map[i].in; i++)
+ if (0 == strcasecmp (map[i].in,
+ trigger_s))
+ {
+ *trigger = map[i].out;
+ return GNUNET_OK;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid KYC trigger `%s'\n",
+ trigger_s);
+ return GNUNET_SYSERR;
+}
+
+
+const char *
+TALER_KYCLOGIC_kyc_trigger2s (enum TALER_KYCLOGIC_KycTriggerEvent trigger)
+{
+ switch (trigger)
+ {
+ case TALER_KYCLOGIC_KYC_TRIGGER_WITHDRAW:
+ return "withdraw";
+ case TALER_KYCLOGIC_KYC_TRIGGER_DEPOSIT:
+ return "deposit";
+ case TALER_KYCLOGIC_KYC_TRIGGER_P2P_RECEIVE:
+ return "merge";
+ case TALER_KYCLOGIC_KYC_TRIGGER_WALLET_BALANCE:
+ return "balance";
+ }
+ GNUNET_break (0);
+ return NULL;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_KYCLOGIC_kyc_user_type_from_string (const char *ut_s,
+ enum TALER_KYCLOGIC_KycUserType *ut)
+{
+ struct
+ {
+ const char *in;
+ enum TALER_KYCLOGIC_KycTriggerEvent out;
+ } map [] = {
+ { "individual", TALER_KYCLOGIC_KYC_UT_INDIVIDUAL },
+ { "business", TALER_KYCLOGIC_KYC_UT_BUSINESS },
+ { NULL, 0 }
+ };
+
+ for (unsigned int i = 0; NULL != map[i].in; i++)
+ if (0 == strcasecmp (map[i].in,
+ ut_s))
+ {
+ *ut = map[i].out;
+ return GNUNET_OK;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Invalid user type `%s'\n",
+ ut_s);
+ return GNUNET_SYSERR;
+}
+
+
+const char *
+TALER_KYCLOGIC_kyc_user_type2s (enum TALER_KYCLOGIC_KycUserType ut)
+{
+ switch (ut)
+ {
+ case TALER_KYCLOGIC_KYC_UT_INDIVIDUAL:
+ return "individual";
+ case TALER_KYCLOGIC_KYC_UT_BUSINESS:
+ return "business";
+ }
+ GNUNET_break (0);
+ return NULL;
+}
+
+
+/**
+ * Load KYC logic plugin.
+ *
+ * @param cfg configuration to use
+ * @param name name of the plugin
+ * @return NULL on error
+ */
+static struct TALER_KYCLOGIC_Plugin *
+load_logic (const struct GNUNET_CONFIGURATION_Handle *cfg,
+ const char *name)
+{
+ char *lib_name;
+ struct TALER_KYCLOGIC_Plugin *plugin;
+
+ GNUNET_asprintf (&lib_name,
+ "libtaler_plugin_kyclogic_%s",
+ name);
+ plugin = GNUNET_PLUGIN_load (lib_name,
+ (void *) cfg);
+ if (NULL != plugin)
+ plugin->library_name = lib_name;
+ else
+ GNUNET_free (lib_name);
+ return plugin;
+}
+
+
+/**
+ * Add check type to global array of checks.
+ * First checks if the type already exists, otherwise
+ * adds a new one.
+ *
+ * @param check name of the check
+ * @return pointer into the global list
+ */
+static struct TALER_KYCLOGIC_KycCheck *
+add_check (const char *check)
+{
+ struct TALER_KYCLOGIC_KycCheck *kc;
+
+ for (unsigned int i = 0; i<num_kyc_checks; i++)
+ if (0 == strcasecmp (check,
+ kyc_checks[i]->name))
+ return kyc_checks[i];
+ kc = GNUNET_new (struct TALER_KYCLOGIC_KycCheck);
+ kc->name = GNUNET_strdup (check);
+ GNUNET_array_append (kyc_checks,
+ num_kyc_checks,
+ kc);
+ return kc;
+}
+
+
+/**
+ * Parse list of checks from @a checks and build an
+ * array of aliases into the global checks array
+ * in @a provided_checks.
+ *
+ * @param[in,out] checks list of checks; clobbered
+ * @param[out] p_checks where to put array of aliases
+ * @param[out] num_p_checks set to length of @a p_checks array
+ */
+static void
+add_checks (char *checks,
+ struct TALER_KYCLOGIC_KycCheck ***p_checks,
+ unsigned int *num_p_checks)
+{
+ char *sptr;
+ struct TALER_KYCLOGIC_KycCheck **rchecks = NULL;
+ unsigned int num_rchecks = 0;
+
+ for (char *tok = strtok_r (checks, " ", &sptr);
+ NULL != tok;
+ tok = strtok_r (NULL, " ", &sptr))
+ {
+ struct TALER_KYCLOGIC_KycCheck *kc;
+
+ kc = add_check (tok);
+ GNUNET_array_append (rchecks,
+ num_rchecks,
+ kc);
+ }
+ *p_checks = rchecks;
+ *num_p_checks = num_rchecks;
+}
+
+
+/**
+ * Parse configuration of a KYC provider.
+ *
+ * @param cfg configuration to parse
+ * @param section name of the section to analyze
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+add_provider (const struct GNUNET_CONFIGURATION_Handle *cfg,
+ const char *section)
+{
+ unsigned long long cost;
+ char *logic;
+ char *ut_s;
+ enum TALER_KYCLOGIC_KycUserType ut;
+ char *checks;
+ struct TALER_KYCLOGIC_Plugin *lp;
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_number (cfg,
+ section,
+ "COST",
+ &cost))
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "COST",
+ "number required");
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ section,
+ "USER_TYPE",
+ &ut_s))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "USER_TYPE");
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_KYCLOGIC_kyc_user_type_from_string (ut_s,
+ &ut))
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "USER_TYPE",
+ "valid user type required");
+ GNUNET_free (ut_s);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_free (ut_s);
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ section,
+ "LOGIC",
+ &logic))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "LOGIC");
+ return GNUNET_SYSERR;
+ }
+ lp = load_logic (cfg,
+ logic);
+ if (NULL == lp)
+ {
+ GNUNET_free (logic);
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "LOGIC",
+ "logic plugin could not be loaded");
+ return GNUNET_SYSERR;
+ }
+ GNUNET_free (logic);
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ section,
+ "PROVIDED_CHECKS",
+ &checks))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "PROVIDED_CHECKS");
+ return GNUNET_SYSERR;
+ }
+ {
+ struct TALER_KYCLOGIC_KycProvider *kp;
+
+ kp = GNUNET_new (struct TALER_KYCLOGIC_KycProvider);
+ kp->provider_section_name = section;
+ kp->user_type = ut;
+ kp->logic = lp;
+ kp->cost = cost;
+ add_checks (checks,
+ &kp->provided_checks,
+ &kp->num_checks);
+ GNUNET_free (checks);
+ kp->pd = lp->load_configuration (lp->cls,
+ section);
+ if (NULL == kp->pd)
+ {
+ GNUNET_free (kp);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_array_append (kyc_providers,
+ num_kyc_providers,
+ kp);
+ for (unsigned int i = 0; i<kp->num_checks; i++)
+ {
+ struct TALER_KYCLOGIC_KycCheck *kc = kp->provided_checks[i];
+
+ GNUNET_array_append (kc->providers,
+ kc->num_providers,
+ kp);
+ }
+ }
+ return GNUNET_OK;
+}
+
+
+static enum GNUNET_GenericReturnValue
+add_trigger (const struct GNUNET_CONFIGURATION_Handle *cfg,
+ const char *section)
+{
+ char *ot_s;
+ struct TALER_Amount threshold;
+ struct GNUNET_TIME_Relative timeframe;
+ char *checks;
+ enum TALER_KYCLOGIC_KycTriggerEvent ot;
+
+ if (GNUNET_OK !=
+ TALER_config_get_amount (cfg,
+ section,
+ "THRESHOLD",
+ &threshold))
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "THRESHOLD",
+ "amount required");
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ section,
+ "OPERATION_TYPE",
+ &ot_s))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "OPERATION_TYPE");
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_KYCLOGIC_kyc_trigger_from_string (ot_s,
+ &ot))
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "OPERATION_TYPE",
+ "valid trigger type required");
+ GNUNET_free (ot_s);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_free (ot_s);
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_time (cfg,
+ section,
+ "TIMEFRAME",
+ &timeframe))
+ {
+ if (TALER_KYCLOGIC_KYC_TRIGGER_WALLET_BALANCE == ot)
+ {
+ timeframe = GNUNET_TIME_UNIT_ZERO;
+ }
+ else
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "TIMEFRAME",
+ "duration required");
+ return GNUNET_SYSERR;
+ }
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ section,
+ "REQUIRED_CHECKS",
+ &checks))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "REQUIRED_CHECKS");
+ return GNUNET_SYSERR;
+ }
+
+ {
+ struct TALER_KYCLOGIC_KycTrigger *kt;
+
+ kt = GNUNET_new (struct TALER_KYCLOGIC_KycTrigger);
+ kt->timeframe = timeframe;
+ kt->threshold = threshold;
+ kt->trigger = ot;
+ add_checks (checks,
+ &kt->required_checks,
+ &kt->num_checks);
+ GNUNET_free (checks);
+ GNUNET_array_append (kyc_triggers,
+ num_kyc_triggers,
+ kt);
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Closure for #handle_section().
+ */
+struct SectionContext
+{
+ /**
+ * Configuration to handle.
+ */
+ const struct GNUNET_CONFIGURATION_Handle *cfg;
+
+ /**
+ * Result to return, set to false on failures.
+ */
+ bool result;
+};
+
+
+/**
+ * Function to iterate over configuration sections.
+ *
+ * @param cls a `struct SectionContext *`
+ * @param section name of the section
+ */
+static void
+handle_section (void *cls,
+ const char *section)
+{
+ struct SectionContext *sc = cls;
+
+ if (0 == strncasecmp (section,
+ "kyc-provider-",
+ strlen ("kyc-provider-")))
+ {
+ if (GNUNET_OK !=
+ add_provider (sc->cfg,
+ section))
+ sc->result = false;
+ return;
+ }
+ if (0 == strncasecmp (section,
+ "kyc-legitimization-",
+ strlen ("kyc-legitimization-")))
+ {
+ if (GNUNET_OK !=
+ add_trigger (sc->cfg,
+ section))
+ sc->result = false;
+ return;
+ }
+}
+
+
+/**
+ * Comparator for qsort. Compares two triggers
+ * by timeframe to sort triggers by time.
+ *
+ * @param p1 first trigger to compare
+ * @param p2 second trigger to compare
+ * @return -1 if p1 < p2, 0 if p1==p2, 1 if p1 > p2.
+ */
+static int
+sort_by_timeframe (const void *p1,
+ const void *p2)
+{
+ struct TALER_KYCLOGIC_KycTrigger **t1 = (struct
+ TALER_KYCLOGIC_KycTrigger **) p1;
+ struct TALER_KYCLOGIC_KycTrigger **t2 = (struct
+ TALER_KYCLOGIC_KycTrigger **) p2;
+
+ if (GNUNET_TIME_relative_cmp ((*t1)->timeframe,
+ <,
+ (*t2)->timeframe))
+ return -1;
+ if (GNUNET_TIME_relative_cmp ((*t1)->timeframe,
+ >,
+ (*t2)->timeframe))
+ return 1;
+ return 0;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_KYCLOGIC_kyc_init (const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+ struct SectionContext sc = {
+ .cfg = cfg,
+ .result = true
+ };
+
+ GNUNET_CONFIGURATION_iterate_sections (cfg,
+ &handle_section,
+ &sc);
+ if (! sc.result)
+ {
+ TALER_KYCLOGIC_kyc_done ();
+ return GNUNET_SYSERR;
+ }
+
+ /* sanity check: ensure at least one provider exists
+ for any trigger and indidivual or business. */
+ for (unsigned int i = 0; i<num_kyc_checks; i++)
+ if (0 == kyc_checks[i]->num_providers)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "No provider available for required KYC check `%s'\n",
+ kyc_checks[i]->name);
+ TALER_KYCLOGIC_kyc_done ();
+ return GNUNET_SYSERR;
+ }
+ qsort (kyc_triggers,
+ num_kyc_triggers,
+ sizeof (struct TALER_KYCLOGIC_KycTrigger *),
+ &sort_by_timeframe);
+ return GNUNET_OK;
+}
+
+
+void
+TALER_KYCLOGIC_kyc_done (void)
+{
+ for (unsigned int i = 0; i<num_kyc_triggers; i++)
+ {
+ struct TALER_KYCLOGIC_KycTrigger *kt = kyc_triggers[i];
+
+ GNUNET_array_grow (kt->required_checks,
+ kt->num_checks,
+ 0);
+ GNUNET_free (kt);
+ }
+ GNUNET_array_grow (kyc_triggers,
+ num_kyc_triggers,
+ 0);
+ for (unsigned int i = 0; i<num_kyc_providers; i++)
+ {
+ struct TALER_KYCLOGIC_KycProvider *kp = kyc_providers[i];
+
+ kp->logic->unload_configuration (kp->pd);
+ GNUNET_array_grow (kp->provided_checks,
+ kp->num_checks,
+ 0);
+ GNUNET_free (kp);
+ }
+ GNUNET_array_grow (kyc_providers,
+ num_kyc_providers,
+ 0);
+ for (unsigned int i = 0; i<num_kyc_logics; i++)
+ {
+ struct TALER_KYCLOGIC_Plugin *lp = kyc_logics[i];
+ char *lib_name = lp->library_name;
+
+ GNUNET_assert (NULL == GNUNET_PLUGIN_unload (lib_name,
+ lp));
+ GNUNET_free (lib_name);
+ }
+ GNUNET_array_grow (kyc_logics,
+ num_kyc_logics,
+ 0);
+ for (unsigned int i = 0; i<num_kyc_checks; i++)
+ {
+ struct TALER_KYCLOGIC_KycCheck *kc = kyc_checks[i];
+
+ GNUNET_free (kc->name);
+ GNUNET_free (kc);
+ }
+ GNUNET_array_grow (kyc_checks,
+ num_kyc_checks,
+ 0);
+}
+
+
+/**
+ * Closure for the #eval_trigger().
+ */
+struct ThresholdTestContext
+{
+ /**
+ * Total amount so far.
+ */
+ struct TALER_Amount total;
+
+ /**
+ * Trigger event to evaluate triggers of.
+ */
+ enum TALER_KYCLOGIC_KycTriggerEvent event;
+
+ /**
+ * Offset in the triggers array where we need to start
+ * checking for triggers. All trigges below this
+ * offset were already hit.
+ */
+ unsigned int start;
+
+ /**
+ * Array of checks needed so far.
+ */
+ struct TALER_KYCLOGIC_KycCheck **needed;
+
+ /**
+ * Pointer to number of entries used in @a needed.
+ */
+ unsigned int *needed_cnt;
+
+ /**
+ * Has @e total been initialized yet?
+ */
+ bool have_total;
+};
+
+
+/**
+ * Function called on each @a amount that was found to
+ * be relevant for a KYC check.
+ *
+ * @param cls closure to allow the KYC module to
+ * total up amounts and evaluate rules
+ * @param amount encountered transaction amount
+ * @param date when was the amount encountered
+ * @return #GNUNET_OK to continue to iterate,
+ * #GNUNET_NO to abort iteration
+ * #GNUNET_SYSERR on internal error (also abort itaration)
+ */
+static enum GNUNET_GenericReturnValue
+eval_trigger (void *cls,
+ const struct TALER_Amount *amount,
+ struct GNUNET_TIME_Absolute date)
+{
+ struct ThresholdTestContext *ttc = cls;
+ struct GNUNET_TIME_Relative duration;
+ bool bump = true;
+
+ duration = GNUNET_TIME_absolute_get_duration (date);
+ if (ttc->have_total)
+ {
+ if (0 >
+ TALER_amount_add (&ttc->total,
+ &ttc->total,
+ amount))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ else
+ {
+ ttc->total = *amount;
+ }
+ for (unsigned int i = ttc->start; i<num_kyc_triggers; i++)
+ {
+ const struct TALER_KYCLOGIC_KycTrigger *kt = kyc_triggers[i];
+
+ if (ttc->event != kt->trigger)
+ continue;
+ duration = GNUNET_TIME_relative_max (duration,
+ kt->timeframe);
+ if (GNUNET_TIME_relative_cmp (kt->timeframe,
+ >,
+ duration))
+ {
+ if (bump)
+ ttc->start = i;
+ return GNUNET_OK;
+ }
+ if (-1 ==
+ TALER_amount_cmp (&ttc->total,
+ &kt->threshold))
+ {
+ if (bump)
+ ttc->start = i;
+ bump = false;
+ continue; /* amount too low to trigger */
+ }
+ /* add check to list of required checks, unless
+ already present... */
+ for (unsigned int j = 0; j<kt->num_checks; j++)
+ {
+ struct TALER_KYCLOGIC_KycCheck *rc = kt->required_checks[j];
+ bool found = false;
+
+ for (unsigned int k = 0; k<*ttc->needed_cnt; k++)
+ if (ttc->needed[k] == rc)
+ {
+ found = true;
+ break;
+ }
+ if (! found)
+ {
+ ttc->needed[*ttc->needed_cnt] = rc;
+ (*ttc->needed_cnt)++;
+ }
+ }
+ }
+ if (bump)
+ return GNUNET_NO; /* we hit all possible triggers! */
+ return GNUNET_OK;
+}
+
+
+/**
+ * Closure for the #remove_satisfied().
+ */
+struct RemoveContext
+{
+
+ /**
+ * Array of checks needed so far.
+ */
+ struct TALER_KYCLOGIC_KycCheck **needed;
+
+ /**
+ * Pointer to number of entries used in @a needed.
+ */
+ unsigned int *needed_cnt;
+
+};
+
+
+/**
+ * Remove all checks satisfied by @a provider_name from
+ * our list of checks.
+ *
+ * @param cls a `struct RemoveContext`
+ * @param provider_name section name of provider that was already run previously
+ */
+static void
+remove_satisfied (void *cls,
+ const char *provider_name)
+{
+ struct RemoveContext *rc = cls;
+
+ for (unsigned int i = 0; i<num_kyc_providers; i++)
+ {
+ const struct TALER_KYCLOGIC_KycProvider *kp = kyc_providers[i];
+
+ if (0 != strcasecmp (provider_name,
+ kp->provider_section_name))
+ continue;
+ for (unsigned int j = 0; j<kp->num_checks; j++)
+ {
+ const struct TALER_KYCLOGIC_KycCheck *kc = kp->provided_checks[j];
+
+ for (unsigned int k = 0; k<*rc->needed_cnt; k++)
+ if (kc == rc->needed[k])
+ {
+ rc->needed[k] = rc->needed[*rc->needed_cnt - 1];
+ (*rc->needed_cnt)--;
+ if (0 == *rc->needed_cnt)
+ return; /* for sure finished */
+ break;
+ }
+ }
+ break;
+ }
+}
+
+
+const char *
+TALER_KYCLOGIC_kyc_test_required (enum TALER_KYCLOGIC_KycTriggerEvent event,
+ const struct TALER_PaytoHashP *h_payto,
+ TALER_KYCLOGIC_KycSatisfiedIterator ki,
+ void *ki_cls,
+ TALER_KYCLOGIC_KycAmountIterator ai,
+ void *ai_cls)
+{
+ struct TALER_KYCLOGIC_KycCheck *needed[num_kyc_checks];
+ unsigned int needed_cnt = 0;
+ struct GNUNET_TIME_Relative timeframe;
+ unsigned long long min_cost = ULONG_LONG_MAX;
+ unsigned int max_checks = 0;
+ const struct TALER_KYCLOGIC_KycProvider *kp_best = NULL;
+
+ timeframe = GNUNET_TIME_UNIT_ZERO;
+ for (unsigned int i = 0; i<num_kyc_triggers; i++)
+ {
+ const struct TALER_KYCLOGIC_KycTrigger *kt = kyc_triggers[i];
+
+ if (event != kt->trigger)
+ continue;
+ timeframe = GNUNET_TIME_relative_max (timeframe,
+ kt->timeframe);
+ }
+ {
+ struct GNUNET_TIME_Absolute now;
+ struct ThresholdTestContext ttc = {
+ .event = event,
+ .needed = needed,
+ .needed_cnt = &needed_cnt
+ };
+
+ now = GNUNET_TIME_absolute_get ();
+ ai (ai_cls,
+ GNUNET_TIME_absolute_subtract (now,
+ timeframe),
+ &eval_trigger,
+ &ttc);
+ }
+ if (0 == needed_cnt)
+ return NULL;
+ {
+ struct RemoveContext rc = {
+ .needed = needed,
+ .needed_cnt = &needed_cnt
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ /* Check what provider checks are already satisfied for h_payto (with
+ database), remove those from the 'needed' array. */
+ GNUNET_break (0);
+ // FIXME: do via callback!
+ qs = ki (ki_cls,
+ h_payto,
+ &
+ remove_satisfied,
+ &rc);
+ GNUNET_break (qs >= 0); // FIXME: handle DB failure more nicely?
+ }
+
+ /* Count maximum number of remaining checks covered by any
+ provider */
+ for (unsigned int i = 0; i<num_kyc_providers; i++)
+ {
+ const struct TALER_KYCLOGIC_KycProvider *kp = kyc_providers[i];
+ unsigned int matched = 0;
+
+ for (unsigned int j = 0; j<kp->num_checks; j++)
+ {
+ const struct TALER_KYCLOGIC_KycCheck *kc = kp->provided_checks[j];
+
+ for (unsigned int k = 0; k<needed_cnt; k++)
+ if (kc == needed[k])
+ {
+ matched++;
+ break;
+ }
+ }
+ max_checks = GNUNET_MAX (max_checks,
+ matched);
+ }
+
+ /* Find min-cost provider covering max_checks. */
+ for (unsigned int i = 0; i<num_kyc_providers; i++)
+ {
+ const struct TALER_KYCLOGIC_KycProvider *kp = kyc_providers[i];
+ unsigned int matched = 0;
+
+ for (unsigned int j = 0; j<kp->num_checks; j++)
+ {
+ const struct TALER_KYCLOGIC_KycCheck *kc = kp->provided_checks[j];
+
+ for (unsigned int k = 0; k<needed_cnt; k++)
+ if (kc == needed[k])
+ {
+ matched++;
+ break;
+ }
+ }
+ if ( (max_checks == matched) &&
+ (kp->cost < min_cost) )
+ {
+ min_cost = kp->cost;
+ kp_best = kp;
+ }
+ }
+
+ GNUNET_assert (NULL != kp_best);
+ return kp_best->provider_section_name;
+}
+
+
+enum GNUNET_GenericReturnValue
+TALER_KYCLOGIC_kyc_get_logic (const char *provider_section_name,
+ struct TALER_KYCLOGIC_Plugin **plugin,
+ struct TALER_KYCLOGIC_ProviderDetails **pd)
+{
+ for (unsigned int i = 0; i<num_kyc_providers; i++)
+ {
+ struct TALER_KYCLOGIC_KycProvider *kp = kyc_providers[i];
+
+ if (0 !=
+ strcasecmp (provider_section_name,
+ kp->provider_section_name))
+ continue;
+ *plugin = kp->logic;
+ *pd = kp->pd;
+ return GNUNET_OK;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Provider `%s' unknown\n",
+ provider_section_name);
+ return GNUNET_SYSERR;
+}
+
+
+/* end of taler-exchange-httpd_kyc.c */
diff --git a/src/kyclogic/taler-exchange-kyc-tester.c b/src/kyclogic/taler-exchange-kyc-tester.c
@@ -28,6 +28,7 @@
#include "taler_mhd_lib.h"
#include "taler_json_lib.h"
#include "taler_crypto_lib.h"
+#include "taler_kyclogic_lib.h"
#include "taler_kyclogic_plugin.h"
#include <gnunet/gnunet_mhd_compat.h>
@@ -221,6 +222,328 @@ r404 (struct MHD_Connection *connection,
/**
+ * Context for the webhook.
+ */
+struct KycWebhookContext
+{
+
+ /**
+ * Kept in a DLL while suspended.
+ */
+ struct KycWebhookContext *next;
+
+ /**
+ * Kept in a DLL while suspended.
+ */
+ struct KycWebhookContext *prev;
+
+ /**
+ * Details about the connection we are processing.
+ */
+ struct TEKT_RequestContext *rc;
+
+ /**
+ * Plugin responsible for the webhook.
+ */
+ struct TALER_KYCLOGIC_Plugin *plugin;
+
+ /**
+ * Configuration for the specific action.
+ */
+ struct TALER_KYCLOGIC_ProviderDetails *pd;
+
+ /**
+ * Webhook activity.
+ */
+ struct TALER_KYCLOGIC_WebhookHandle *wh;
+
+ /**
+ * HTTP response to return.
+ */
+ struct MHD_Response *response;
+
+ /**
+ * Logic the request is for. Name of the configuration
+ * section defining the KYC logic.
+ */
+ char *logic;
+
+ /**
+ * HTTP response code to return.
+ */
+ unsigned int response_code;
+
+ /**
+ * #GNUNET_YES if we are suspended,
+ * #GNUNET_NO if not.
+ * #GNUNET_SYSERR if we had some error.
+ */
+ enum GNUNET_GenericReturnValue suspended;
+
+};
+
+
+/**
+ * Contexts are kept in a DLL while suspended.
+ */
+static struct KycWebhookContext *kwh_head;
+
+/**
+ * Contexts are kept in a DLL while suspended.
+ */
+static struct KycWebhookContext *kwh_tail;
+
+
+/**
+ * Resume processing the @a kwh request.
+ *
+ * @param kwh request to resume
+ */
+static void
+kwh_resume (struct KycWebhookContext *kwh)
+{
+ GNUNET_assert (GNUNET_YES == kwh->suspended);
+ kwh->suspended = GNUNET_NO;
+ GNUNET_CONTAINER_DLL_remove (kwh_head,
+ kwh_tail,
+ kwh);
+ MHD_resume_connection (kwh->rc->connection);
+ TALER_MHD_daemon_trigger ();
+}
+
+
+static void
+kyc_webhook_cleanup (void)
+{
+ struct KycWebhookContext *kwh;
+
+ while (NULL != (kwh = kwh_head))
+ {
+ if (NULL != kwh->wh)
+ {
+ kwh->plugin->webhook_cancel (kwh->wh);
+ kwh->wh = NULL;
+ }
+ kwh_resume (kwh);
+ }
+}
+
+
+/**
+ * Function called with the result of a webhook
+ * operation.
+ *
+ * Note that the "decref" for the @a response
+ * will be done by the plugin.
+ *
+ * @param cls closure
+ * @param legi_row legitimization request the webhook was about
+ * @param account_id account the webhook was about
+ * @param provider_user_id set to user ID at the provider, or NULL if not supported or unknown
+ * @param provider_legitimization_id set to legitimization process ID at the provider, or NULL if not supported or unknown
+ * @param status KYC status
+ * @param expiration until when is the KYC check valid
+ * @param http_status HTTP status code of @a response
+ * @param[in] response to return to the HTTP client
+ */
+static void
+webhook_finished_cb (
+ void *cls,
+ uint64_t legi_row,
+ const struct TALER_PaytoHashP *account_id,
+ const char *provider_user_id,
+ const char *provider_legitimization_id,
+ enum TALER_KYCLOGIC_KycStatus status,
+ struct GNUNET_TIME_Absolute expiration,
+ unsigned int http_status,
+ struct MHD_Response *response)
+{
+ struct KycWebhookContext *kwh = cls;
+
+ kwh->wh = NULL;
+ switch (status)
+ {
+ case TALER_KYCLOGIC_STATUS_SUCCESS:
+ /* _successfully_ resumed case */
+ GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE,
+ "KYC successful for user `%s' (legi: %s)\n",
+ provider_user_id,
+ provider_legitimization_id);
+ break;
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "KYC status of %s/%s (Row #%llu) is %d\n",
+ provider_user_id,
+ provider_legitimization_id,
+ (unsigned long long) legi_row,
+ status);
+ break;
+ }
+ kwh->response = response;
+ kwh->response_code = http_status;
+ kwh_resume (kwh);
+}
+
+
+/**
+ * Function called to clean up a context.
+ *
+ * @param rc request context
+ */
+static void
+clean_kwh (struct TEKT_RequestContext *rc)
+{
+ struct KycWebhookContext *kwh = rc->rh_ctx;
+
+ if (NULL != kwh->wh)
+ {
+ kwh->plugin->webhook_cancel (kwh->wh);
+ kwh->wh = NULL;
+ }
+ if (NULL != kwh->response)
+ {
+ MHD_destroy_response (kwh->response);
+ kwh->response = NULL;
+ }
+ GNUNET_free (kwh->logic);
+ GNUNET_free (kwh);
+}
+
+
+/**
+ * Function the plugin can use to lookup an
+ * @a h_payto by @a provider_legitimization_id.
+ *
+ * @param cls closure, NULL
+ * @param provider_section
+ * @param provider_legitimization_id legi to look up
+ * @param[out] h_payto where to write the result
+ * @return database transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+kyc_provider_account_lookup (
+ void *cls,
+ const char *provider_section,
+ const char *provider_legitimization_id,
+ struct TALER_PaytoHashP *h_payto)
+{
+ // FIXME: pass value to use for h_payto via command-line?
+ memset (h_payto,
+ 42,
+ sizeof (*h_payto));
+ return 1; // FIXME...
+}
+
+
+/**
+ * Handle a (GET or POST) "/kyc-webhook" request.
+ *
+ * @param rc request to handle
+ * @param method HTTP request method used by the client
+ * @param root uploaded JSON body (can be NULL)
+ * @param args one argument with the payment_target_uuid
+ * @return MHD result code
+ */
+static MHD_RESULT
+handler_kyc_webhook_generic (
+ struct TEKT_RequestContext *rc,
+ const char *method,
+ const json_t *root,
+ const char *const args[])
+{
+ struct KycWebhookContext *kwh = rc->rh_ctx;
+
+ if (NULL == kwh)
+ { /* first time */
+ kwh = GNUNET_new (struct KycWebhookContext);
+ kwh->logic = GNUNET_strdup (args[0]);
+ kwh->rc = rc;
+ rc->rh_ctx = kwh;
+ rc->rh_cleaner = &clean_kwh;
+
+ if (GNUNET_OK !=
+ TALER_KYCLOGIC_kyc_get_logic (kwh->logic,
+ &kwh->plugin,
+ &kwh->pd))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "KYC logic `%s' unknown (check KYC provider configuration)\n",
+ kwh->logic);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_KYC_WEBHOOK_LOGIC_UNKNOWN,
+ "$LOGIC");
+ }
+ kwh->wh = kwh->plugin->webhook (kwh->plugin->cls,
+ kwh->pd,
+ &kyc_provider_account_lookup,
+ NULL,
+ method,
+ &args[1],
+ rc->connection,
+ root,
+ &webhook_finished_cb,
+ kwh);
+ if (NULL == kwh->wh)
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "failed to run webhook logic");
+ }
+ kwh->suspended = GNUNET_YES;
+ GNUNET_CONTAINER_DLL_insert (kwh_head,
+ kwh_tail,
+ kwh);
+ MHD_suspend_connection (rc->connection);
+ return MHD_YES;
+ }
+
+ if (NULL != kwh->response)
+ {
+ /* handle _failed_ resumed cases */
+ return MHD_queue_response (rc->connection,
+ kwh->response_code,
+ kwh->response);
+ }
+
+ /* We resumed, but got no response? This should
+ not happen. */
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "resumed without response");
+}
+
+
+static MHD_RESULT
+handler_kyc_webhook_get (
+ struct TEKT_RequestContext *rc,
+ const char *const args[])
+{
+ return handler_kyc_webhook_generic (rc,
+ MHD_HTTP_METHOD_GET,
+ NULL,
+ args);
+}
+
+
+static MHD_RESULT
+handler_kyc_webhook_post (
+ struct TEKT_RequestContext *rc,
+ const json_t *root,
+ const char *const args[])
+{
+ return handler_kyc_webhook_generic (rc,
+ MHD_HTTP_METHOD_POST,
+ root,
+ args);
+}
+
+
+/**
* Function called whenever MHD is done with a request. If the
* request was a POST, we may have stored a `struct Buffer *` in the
* @a con_cls that might still need to be cleaned up. Call the
@@ -295,7 +618,6 @@ proceed_with_handler (struct TEKT_RequestContext *rc,
size_t *upload_data_size)
{
const struct TEKT_RequestHandler *rh = rc->rh;
- // FIXME: handle '-1 == rh->nargs'!!!
const char *args[rh->nargs + 2];
size_t ulen = strlen (url) + 1;
json_t *root = NULL;
@@ -442,19 +764,21 @@ handle_mhd_request (void *cls,
.handler.post = &TEKT_handler_kyc_wallet,
.nargs = 0
},
+#endif
{
.url = "kyc-webhook",
.method = MHD_HTTP_METHOD_POST,
- .handler.post = &TEKT_handler_kyc_webhook_post,
- .nargs = -1
+ .handler.post = &handler_kyc_webhook_post,
+ .nargs = 128,
+ .nargs_is_upper_bound = true
},
{
.url = "kyc-webhook",
.method = MHD_HTTP_METHOD_GET,
- .handler.post = &TEKT_handler_kyc_webhook_get,
- .nargs = -1
+ .handler.get = &handler_kyc_webhook_get,
+ .nargs = 128,
+ .nargs_is_upper_bound = true
},
-#endif
/* mark end of list */
{
.url = NULL
@@ -670,6 +994,8 @@ do_shutdown (void *cls)
struct MHD_Daemon *mhd;
(void) cls;
+ kyc_webhook_cleanup ();
+ TALER_KYCLOGIC_kyc_done ();
mhd = TALER_MHD_daemon_stop ();
if (NULL != mhd)
MHD_stop_daemon (mhd);
@@ -708,11 +1034,18 @@ run (void *cls,
(void ) cfgfile;
TALER_MHD_setup (TALER_MHD_GO_NONE);
TEKT_cfg = config;
-
+ if (GNUNET_OK !=
+ TALER_KYCLOGIC_kyc_init (config))
+ {
+ global_ret = EXIT_NOTCONFIGURED;
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
if (GNUNET_OK !=
exchange_serve_process_config ())
{
global_ret = EXIT_NOTCONFIGURED;
+ TALER_KYCLOGIC_kyc_done ();
GNUNET_SCHEDULER_shutdown ();
return;
}