exchange/src/kyclogic/kyclogic_api.c

1312 lines
34 KiB
C
Raw Normal View History

2022-08-01 18:09:06 +02:00
/*
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/>
*/
/**
2022-08-08 15:22:45 +02:00
* @file kyclogic_api.c
* @brief server-side KYC API
2022-08-01 18:09:06 +02:00
* @author Christian Grothoff
*/
#include "platform.h"
2022-08-08 15:22:45 +02:00
#include "taler_kyclogic_lib.h"
2022-08-01 18:09:06 +02:00
/**
* Information about a KYC provider.
*/
2022-08-08 15:22:45 +02:00
struct TALER_KYCLOGIC_KycProvider;
2022-08-01 18:09:06 +02:00
/**
* Abstract representation of a KYC check.
*/
2022-08-08 15:22:45 +02:00
struct TALER_KYCLOGIC_KycCheck
2022-08-01 18:09:06 +02:00
{
/**
* Human-readable name given to the KYC check.
*/
char *name;
/**
* Array of @e num_providers providers that offer this type of KYC check.
*/
2022-08-08 15:22:45 +02:00
struct TALER_KYCLOGIC_KycProvider **providers;
2022-08-01 18:09:06 +02:00
/**
* Length of the @e providers array.
*/
unsigned int num_providers;
};
2022-08-08 15:22:45 +02:00
struct TALER_KYCLOGIC_KycProvider
2022-08-01 18:09:06 +02:00
{
/**
* Name of the provider (configuration section name).
*/
const char *provider_section_name;
/**
* Array of @e num_checks checks performed by this provider.
*/
2022-08-08 15:22:45 +02:00
struct TALER_KYCLOGIC_KycCheck **provided_checks;
2022-08-01 18:09:06 +02:00
/**
* Logic to run for this provider.
*/
struct TALER_KYCLOGIC_Plugin *logic;
2022-08-01 18:09:06 +02:00
/**
* @e provider_section_name specific details to
* pass to the @e logic functions.
*/
struct TALER_KYCLOGIC_ProviderDetails *pd;
2022-08-01 18:09:06 +02:00
2022-08-04 11:36:05 +02:00
/**
* Cost of running this provider's KYC.
*/
unsigned long long cost;
2022-08-01 18:09:06 +02:00
/**
* Length of the @e checks array.
*/
unsigned int num_checks;
/**
* Type of user this provider supports.
*/
2022-08-08 15:22:45 +02:00
enum TALER_KYCLOGIC_KycUserType user_type;
2022-08-01 18:09:06 +02:00
};
/**
* Condition that triggers a need to perform KYC.
*/
2022-08-08 15:22:45 +02:00
struct TALER_KYCLOGIC_KycTrigger
2022-08-01 18:09:06 +02:00
{
/**
* 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;
2022-08-01 18:09:06 +02:00
/**
* Array of @e num_checks checks to apply on this trigger.
*/
2022-08-08 15:22:45 +02:00
struct TALER_KYCLOGIC_KycCheck **required_checks;
2022-08-01 18:09:06 +02:00
/**
* Length of the @e checks array.
*/
unsigned int num_checks;
/**
* What event is this trigger for?
*/
2022-08-08 15:22:45 +02:00
enum TALER_KYCLOGIC_KycTriggerEvent trigger;
2022-08-01 18:09:06 +02:00
};
/**
* Array of @e num_kyc_logics KYC logic plugins we have loaded.
*/
static struct TALER_KYCLOGIC_Plugin **kyc_logics;
2022-08-01 18:09:06 +02:00
/**
* Length of the #kyc_logics array.
*/
static unsigned int num_kyc_logics;
2022-08-01 18:09:06 +02:00
/**
* Array of @e num_kyc_checks known types of
* KYC checks.
*/
2022-08-08 15:22:45 +02:00
static struct TALER_KYCLOGIC_KycCheck **kyc_checks;
2022-08-01 18:09:06 +02:00
/**
* Length of the #kyc_checks array.
*/
static unsigned int num_kyc_checks;
/**
* Array of configured triggers.
*/
2022-08-08 15:22:45 +02:00
static struct TALER_KYCLOGIC_KycTrigger **kyc_triggers;
2022-08-01 18:09:06 +02:00
/**
* Length of the #kyc_triggers array.
*/
static unsigned int num_kyc_triggers;
/**
* Array of configured providers.
*/
2022-08-08 15:22:45 +02:00
static struct TALER_KYCLOGIC_KycProvider **kyc_providers;
2022-08-01 18:09:06 +02:00
/**
* Length of the #kyc_providers array.
*/
static unsigned int num_kyc_providers;
enum GNUNET_GenericReturnValue
2022-08-08 15:22:45 +02:00
TALER_KYCLOGIC_kyc_trigger_from_string (const char *trigger_s,
enum TALER_KYCLOGIC_KycTriggerEvent *
trigger)
2022-08-01 18:09:06 +02:00
{
struct
{
const char *in;
2022-08-08 15:22:45 +02:00
enum TALER_KYCLOGIC_KycTriggerEvent out;
} map [] = {
2022-08-08 15:22:45 +02:00
{ "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);
2022-08-01 18:09:06 +02:00
return GNUNET_SYSERR;
}
const char *
2022-08-08 15:22:45 +02:00
TALER_KYCLOGIC_kyc_trigger2s (enum TALER_KYCLOGIC_KycTriggerEvent trigger)
2022-08-01 18:09:06 +02:00
{
switch (trigger)
{
2022-08-08 15:22:45 +02:00
case TALER_KYCLOGIC_KYC_TRIGGER_WITHDRAW:
return "withdraw";
2022-08-08 15:22:45 +02:00
case TALER_KYCLOGIC_KYC_TRIGGER_DEPOSIT:
return "deposit";
2022-08-08 15:22:45 +02:00
case TALER_KYCLOGIC_KYC_TRIGGER_P2P_RECEIVE:
return "merge";
2022-08-08 15:22:45 +02:00
case TALER_KYCLOGIC_KYC_TRIGGER_WALLET_BALANCE:
return "balance";
}
2022-08-01 18:09:06 +02:00
GNUNET_break (0);
return NULL;
}
enum GNUNET_GenericReturnValue
2022-08-08 15:22:45 +02:00
TALER_KYCLOGIC_kyc_user_type_from_string (const char *ut_s,
enum TALER_KYCLOGIC_KycUserType *ut)
2022-08-01 18:09:06 +02:00
{
struct
{
const char *in;
2022-08-19 09:31:51 +02:00
enum TALER_KYCLOGIC_KycUserType out;
} map [] = {
2022-08-08 15:22:45 +02:00
{ "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);
2022-08-01 18:09:06 +02:00
return GNUNET_SYSERR;
}
const char *
2022-08-08 15:22:45 +02:00
TALER_KYCLOGIC_kyc_user_type2s (enum TALER_KYCLOGIC_KycUserType ut)
{
switch (ut)
{
2022-08-08 15:22:45 +02:00
case TALER_KYCLOGIC_KYC_UT_INDIVIDUAL:
return "individual";
2022-08-08 15:22:45 +02:00
case TALER_KYCLOGIC_KYC_UT_BUSINESS:
return "business";
}
GNUNET_break (0);
return NULL;
}
/**
* Load KYC logic plugin.
*
2022-08-08 15:22:45 +02:00
* @param cfg configuration to use
* @param name name of the plugin
* @return NULL on error
*/
static struct TALER_KYCLOGIC_Plugin *
2022-08-08 15:22:45 +02:00
load_logic (const struct GNUNET_CONFIGURATION_Handle *cfg,
const char *name)
2022-08-01 18:09:06 +02:00
{
char *lib_name;
struct TALER_KYCLOGIC_Plugin *plugin;
GNUNET_asprintf (&lib_name,
"libtaler_plugin_kyclogic_%s",
name);
for (unsigned int i = 0; i<num_kyc_logics; i++)
if (0 == strcmp (lib_name,
kyc_logics[i]->library_name))
{
GNUNET_free (lib_name);
return kyc_logics[i];
}
plugin = GNUNET_PLUGIN_load (lib_name,
2022-08-08 15:22:45 +02:00
(void *) cfg);
2022-08-15 13:48:13 +02:00
if (NULL == plugin)
{
GNUNET_free (lib_name);
2022-08-15 13:48:13 +02:00
return NULL;
}
plugin->library_name = lib_name;
plugin->name = GNUNET_strdup (name);
GNUNET_array_append (kyc_logics,
num_kyc_logics,
plugin);
return plugin;
2022-08-01 18:09:06 +02:00
}
/**
* 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
*/
2022-08-08 15:22:45 +02:00
static struct TALER_KYCLOGIC_KycCheck *
add_check (const char *check)
{
2022-08-08 15:22:45 +02:00
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];
2022-08-08 15:22:45 +02:00
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,
2022-08-08 15:22:45 +02:00
struct TALER_KYCLOGIC_KycCheck ***p_checks,
unsigned int *num_p_checks)
{
char *sptr;
2022-08-08 15:22:45 +02:00
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))
{
2022-08-08 15:22:45 +02:00
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.
*
2022-08-08 15:22:45 +02:00
* @param cfg configuration to parse
* @param section name of the section to analyze
* @return #GNUNET_OK on success
*/
static enum GNUNET_GenericReturnValue
2022-08-08 15:22:45 +02:00
add_provider (const struct GNUNET_CONFIGURATION_Handle *cfg,
const char *section)
{
unsigned long long cost;
char *logic;
char *ut_s;
2022-08-08 15:22:45 +02:00
enum TALER_KYCLOGIC_KycUserType ut;
char *checks;
struct TALER_KYCLOGIC_Plugin *lp;
if (GNUNET_OK !=
2022-08-08 15:22:45 +02:00
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 !=
2022-08-08 15:22:45 +02:00
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 !=
2022-08-08 15:22:45 +02:00
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 !=
2022-08-08 15:22:45 +02:00
GNUNET_CONFIGURATION_get_value_string (cfg,
section,
"LOGIC",
&logic))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
section,
"LOGIC");
return GNUNET_SYSERR;
}
2022-08-08 15:22:45 +02:00
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 !=
2022-08-08 15:22:45 +02:00
GNUNET_CONFIGURATION_get_value_string (cfg,
section,
"PROVIDED_CHECKS",
&checks))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
section,
"PROVIDED_CHECKS");
return GNUNET_SYSERR;
}
{
2022-08-08 15:22:45 +02:00
struct TALER_KYCLOGIC_KycProvider *kp;
2022-08-08 15:22:45 +02:00
kp = GNUNET_new (struct TALER_KYCLOGIC_KycProvider);
kp->provider_section_name = section;
kp->user_type = ut;
kp->logic = lp;
2022-08-04 11:36:05 +02:00
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++)
{
2022-08-08 15:22:45 +02:00
struct TALER_KYCLOGIC_KycCheck *kc = kp->provided_checks[i];
GNUNET_array_append (kc->providers,
kc->num_providers,
kp);
}
}
return GNUNET_OK;
}
/**
* Parse configuration @a cfg in section @a section for
* the specification of a KYC trigger.
*
* @param cfg configuration to parse
* @param section configuration section to parse
* @return #GNUNET_OK on success
*/
static enum GNUNET_GenericReturnValue
2022-08-08 15:22:45 +02:00
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;
2022-08-08 15:22:45 +02:00
enum TALER_KYCLOGIC_KycTriggerEvent ot;
if (GNUNET_OK !=
2022-08-08 15:22:45 +02:00
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 !=
2022-08-08 15:22:45 +02:00
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 !=
2022-08-08 15:22:45 +02:00
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 !=
2022-08-08 15:22:45 +02:00
GNUNET_CONFIGURATION_get_value_time (cfg,
section,
"TIMEFRAME",
&timeframe))
{
2022-08-08 15:22:45 +02:00
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 !=
2022-08-08 15:22:45 +02:00
GNUNET_CONFIGURATION_get_value_string (cfg,
section,
"REQUIRED_CHECKS",
&checks))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
section,
"REQUIRED_CHECKS");
return GNUNET_SYSERR;
}
{
2022-08-08 15:22:45 +02:00
struct TALER_KYCLOGIC_KycTrigger *kt;
2022-08-08 15:22:45 +02:00
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;
}
2022-08-08 15:22:45 +02:00
/**
* 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.
*
2022-08-08 15:22:45 +02:00
* @param cls a `struct SectionContext *`
* @param section name of the section
*/
static void
handle_section (void *cls,
const char *section)
{
2022-08-08 15:22:45 +02:00
struct SectionContext *sc = cls;
if (0 == strncasecmp (section,
"kyc-provider-",
strlen ("kyc-provider-")))
{
if (GNUNET_OK !=
2022-08-08 15:22:45 +02:00
add_provider (sc->cfg,
section))
sc->result = false;
return;
}
if (0 == strncasecmp (section,
"kyc-legitimization-",
strlen ("kyc-legitimization-")))
{
if (GNUNET_OK !=
2022-08-08 15:22:45 +02:00
add_trigger (sc->cfg,
section))
sc->result = false;
return;
}
}
2022-08-04 11:36:05 +02:00
/**
* 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)
2022-08-04 11:36:05 +02:00
{
2022-08-08 15:22:45 +02:00
struct TALER_KYCLOGIC_KycTrigger **t1 = (struct
TALER_KYCLOGIC_KycTrigger **) p1;
struct TALER_KYCLOGIC_KycTrigger **t2 = (struct
TALER_KYCLOGIC_KycTrigger **) p2;
2022-08-04 11:36:05 +02:00
if (GNUNET_TIME_relative_cmp ((*t1)->timeframe,
<,
(*t2)->timeframe))
return -1;
if (GNUNET_TIME_relative_cmp ((*t1)->timeframe,
>,
(*t2)->timeframe))
return 1;
return 0;
}
2022-08-01 18:09:06 +02:00
enum GNUNET_GenericReturnValue
2022-08-08 15:22:45 +02:00
TALER_KYCLOGIC_kyc_init (const struct GNUNET_CONFIGURATION_Handle *cfg)
2022-08-01 18:09:06 +02:00
{
2022-08-08 15:22:45 +02:00
struct SectionContext sc = {
.cfg = cfg,
.result = true
};
2022-08-08 15:22:45 +02:00
GNUNET_CONFIGURATION_iterate_sections (cfg,
&handle_section,
2022-08-08 15:22:45 +02:00
&sc);
if (! sc.result)
{
2022-08-08 15:22:45 +02:00
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);
2022-08-08 15:22:45 +02:00
TALER_KYCLOGIC_kyc_done ();
return GNUNET_SYSERR;
}
2022-08-04 11:36:05 +02:00
qsort (kyc_triggers,
num_kyc_triggers,
2022-08-08 15:22:45 +02:00
sizeof (struct TALER_KYCLOGIC_KycTrigger *),
2022-08-04 11:36:05 +02:00
&sort_by_timeframe);
2022-08-01 18:09:06 +02:00
return GNUNET_OK;
}
void
2022-08-08 15:22:45 +02:00
TALER_KYCLOGIC_kyc_done (void)
2022-08-01 18:09:06 +02:00
{
for (unsigned int i = 0; i<num_kyc_triggers; i++)
{
2022-08-08 15:22:45 +02:00
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++)
{
2022-08-08 15:22:45 +02:00
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_free (lp->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++)
{
2022-08-08 15:22:45 +02:00
struct TALER_KYCLOGIC_KycCheck *kc = kyc_checks[i];
2022-08-17 14:36:16 +02:00
GNUNET_array_grow (kc->providers,
kc->num_providers,
0);
GNUNET_free (kc->name);
GNUNET_free (kc);
}
GNUNET_array_grow (kyc_checks,
num_kyc_checks,
0);
2022-08-01 18:09:06 +02:00
}
2022-08-04 11:36:05 +02:00
/**
* Closure for the #eval_trigger().
*/
struct ThresholdTestContext
{
/**
* Total amount so far.
*/
struct TALER_Amount total;
/**
* Trigger event to evaluate triggers of.
*/
2022-08-08 15:22:45 +02:00
enum TALER_KYCLOGIC_KycTriggerEvent event;
2022-08-04 11:36:05 +02:00
/**
* 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.
*/
2022-08-08 15:22:45 +02:00
struct TALER_KYCLOGIC_KycCheck **needed;
2022-08-04 11:36:05 +02:00
/**
* Pointer to number of entries used in @a needed.
*/
unsigned int *needed_cnt;
2022-08-08 15:22:45 +02:00
/**
* Has @e total been initialized yet?
*/
bool have_total;
2022-08-04 11:36:05 +02:00
};
/**
* 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;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"KYC check with new amount %s\n",
TALER_amount2s (amount));
2022-08-04 11:36:05 +02:00
duration = GNUNET_TIME_absolute_get_duration (date);
2022-08-08 15:22:45 +02:00
if (ttc->have_total)
2022-08-04 11:36:05 +02:00
{
2022-08-08 15:22:45 +02:00
if (0 >
TALER_amount_add (&ttc->total,
&ttc->total,
amount))
{
GNUNET_break (0);
return GNUNET_SYSERR;
}
}
else
{
ttc->total = *amount;
ttc->have_total = true;
2022-08-04 11:36:05 +02:00
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"KYC check: new total is %s\n",
TALER_amount2s (&ttc->total));
2022-08-04 11:36:05 +02:00
for (unsigned int i = ttc->start; i<num_kyc_triggers; i++)
{
2022-08-08 15:22:45 +02:00
const struct TALER_KYCLOGIC_KycTrigger *kt = kyc_triggers[i];
2022-08-04 11:36:05 +02:00
if (ttc->event != kt->trigger)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"KYC check #%u: trigger type does not match\n",
i);
2022-08-04 11:36:05 +02:00
continue;
}
duration = GNUNET_TIME_relative_max (duration,
kt->timeframe);
2022-08-04 11:36:05 +02:00
if (GNUNET_TIME_relative_cmp (kt->timeframe,
>,
duration))
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"KYC check #%u: amount is beyond time limit\n",
i);
2022-08-04 11:36:05 +02:00
if (bump)
ttc->start = i;
return GNUNET_OK;
2022-08-04 11:36:05 +02:00
}
if (-1 ==
TALER_amount_cmp (&ttc->total,
&kt->threshold))
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
2022-08-11 23:53:25 +02:00
"KYC check #%u: amount is below threshold\n",
i);
2022-08-04 11:36:05 +02:00
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++)
{
2022-08-08 15:22:45 +02:00
struct TALER_KYCLOGIC_KycCheck *rc = kt->required_checks[j];
2022-08-04 11:36:05 +02:00
bool found = false;
for (unsigned int k = 0; k<*ttc->needed_cnt; k++)
if (ttc->needed[k] == rc)
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"KYC rule #%u already listed\n",
j);
2022-08-04 11:36:05 +02:00
found = true;
break;
}
if (! found)
{
ttc->needed[*ttc->needed_cnt] = rc;
(*ttc->needed_cnt)++;
}
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"KYC check #%u (%s) is applicable, %u checks needed so far\n",
i,
ttc->needed[(*ttc->needed_cnt) - 1]->name,
*ttc->needed_cnt);
2022-08-04 11:36:05 +02:00
}
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.
*/
2022-08-08 15:22:45 +02:00
struct TALER_KYCLOGIC_KycCheck **needed;
2022-08-04 11:36:05 +02:00
/**
* 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++)
{
2022-08-08 15:22:45 +02:00
const struct TALER_KYCLOGIC_KycProvider *kp = kyc_providers[i];
2022-08-04 11:36:05 +02:00
if (0 != strcasecmp (provider_name,
kp->provider_section_name))
continue;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Provider `%s' satisfied\n",
provider_name);
2022-08-04 11:36:05 +02:00
for (unsigned int j = 0; j<kp->num_checks; j++)
{
2022-08-08 15:22:45 +02:00
const struct TALER_KYCLOGIC_KycCheck *kc = kp->provided_checks[j];
2022-08-04 11:36:05 +02:00
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Provider satisfies check `%s'\n",
kc->name);
2022-08-04 11:36:05 +02:00
for (unsigned int k = 0; k<*rc->needed_cnt; k++)
if (kc == rc->needed[k])
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Removing check `%s' from list\n",
kc->name);
2022-08-04 11:36:05 +02:00
rc->needed[k] = rc->needed[*rc->needed_cnt - 1];
(*rc->needed_cnt)--;
if (0 == *rc->needed_cnt)
return; /* for sure finished */
break;
}
}
break;
}
}
2022-08-01 18:09:06 +02:00
const char *
2022-08-08 15:22:45 +02:00
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)
2022-08-01 18:09:06 +02:00
{
2022-08-08 15:22:45 +02:00
struct TALER_KYCLOGIC_KycCheck *needed[num_kyc_checks];
2022-08-04 11:36:05 +02:00
unsigned int needed_cnt = 0;
2022-08-20 21:29:29 +02:00
char *ret;
2022-08-04 11:36:05 +02:00
struct GNUNET_TIME_Relative timeframe;
timeframe = GNUNET_TIME_UNIT_ZERO;
for (unsigned int i = 0; i<num_kyc_triggers; i++)
{
2022-08-08 15:22:45 +02:00
const struct TALER_KYCLOGIC_KycTrigger *kt = kyc_triggers[i];
2022-08-04 11:36:05 +02:00
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);
}
2022-08-20 21:29:29 +02:00
if (0 == needed_cnt)
return 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. */
qs = ki (ki_cls,
h_payto,
&remove_satisfied,
&rc);
GNUNET_break (qs >= 0); // FIXME: handle DB failure more nicely?
}
2022-08-04 11:36:05 +02:00
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. */
2022-08-08 15:22:45 +02:00
qs = ki (ki_cls,
h_payto,
&remove_satisfied,
2022-08-08 15:22:45 +02:00
&rc);
2022-08-04 11:36:05 +02:00
GNUNET_break (qs >= 0); // FIXME: handle DB failure more nicely?
}
if (0 == needed_cnt)
return NULL;
2022-08-20 21:29:29 +02:00
ret = NULL;
for (unsigned int k = 0; k<needed_cnt; k++)
{
const struct TALER_KYCLOGIC_KycCheck *kc = needed[k];
if (NULL == ret)
{
ret = GNUNET_strdup (kc->name);
}
else /* append */
{
char *tmp = ret;
GNUNET_asprintf (&ret,
"%s %s",
tmp,
kc->name);
GNUNET_free (tmp);
}
}
return ret;
}
void
TALER_KYCLOGIC_kyc_get_details (
const char *logic_name,
TALER_KYCLOGIC_DetailsCallback cb,
void *cb_cls)
{
for (unsigned int i = 0; i<num_kyc_providers; i++)
{
struct TALER_KYCLOGIC_KycProvider *kp = kyc_providers[i];
if (0 !=
strcmp (kp->logic->name,
logic_name))
continue;
if (GNUNET_OK !=
cb (cb_cls,
kp->pd,
kp->logic->cls))
return;
}
}
bool
TALER_KYCLOGIC_check_satisfied (const char *requirements,
const struct TALER_PaytoHashP *h_payto,
TALER_KYCLOGIC_KycSatisfiedIterator ki,
void *ki_cls)
{
struct TALER_KYCLOGIC_KycCheck *needed[num_kyc_checks];
unsigned int needed_cnt = 0;
if (NULL == requirements)
return true;
{
char *req = GNUNET_strdup (requirements);
for (const char *tok = strtok (req, " ");
NULL != tok;
tok = strtok (NULL, " "))
needed[needed_cnt++] = add_check (tok);
GNUNET_free (req);
}
{
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. */
qs = ki (ki_cls,
h_payto,
&remove_satisfied,
&rc);
GNUNET_break (qs >= 0); // FIXME: handle DB failure more nicely?
}
return (0 == needed_cnt);
}
enum GNUNET_GenericReturnValue
TALER_KYCLOGIC_requirements_to_logic (const char *requirements,
enum TALER_KYCLOGIC_KycUserType ut,
struct TALER_KYCLOGIC_Plugin **plugin,
struct TALER_KYCLOGIC_ProviderDetails **pd,
const char **configuration_section)
{
struct TALER_KYCLOGIC_KycCheck *needed[num_kyc_checks];
unsigned int needed_cnt = 0;
unsigned long long min_cost = ULLONG_MAX;
2022-08-20 21:29:29 +02:00
unsigned int max_checks = 0;
const struct TALER_KYCLOGIC_KycProvider *kp_best = NULL;
2022-08-22 22:45:41 +02:00
// FIXME: use 'ut' to filter providers!
2022-08-20 21:29:29 +02:00
if (NULL == requirements)
return GNUNET_NO;
{
char *req = GNUNET_strdup (requirements);
for (const char *tok = strtok (req, " ");
NULL != tok;
tok = strtok (NULL, " "))
needed[needed_cnt++] = add_check (tok);
GNUNET_free (req);
}
2022-08-04 11:36:05 +02:00
/* Count maximum number of remaining checks covered by any
provider */
for (unsigned int i = 0; i<num_kyc_providers; i++)
{
2022-08-08 15:22:45 +02:00
const struct TALER_KYCLOGIC_KycProvider *kp = kyc_providers[i];
2022-08-04 11:36:05 +02:00
unsigned int matched = 0;
for (unsigned int j = 0; j<kp->num_checks; j++)
{
2022-08-08 15:22:45 +02:00
const struct TALER_KYCLOGIC_KycCheck *kc = kp->provided_checks[j];
2022-08-04 11:36:05 +02:00
for (unsigned int k = 0; k<needed_cnt; k++)
if (kc == needed[k])
{
matched++;
break;
}
}
max_checks = GNUNET_MAX (max_checks,
matched);
}
2022-08-20 21:29:29 +02:00
if (0 == max_checks)
return GNUNET_SYSERR;
2022-08-04 11:36:05 +02:00
/* Find min-cost provider covering max_checks. */
for (unsigned int i = 0; i<num_kyc_providers; i++)
{
2022-08-08 15:22:45 +02:00
const struct TALER_KYCLOGIC_KycProvider *kp = kyc_providers[i];
2022-08-04 11:36:05 +02:00
unsigned int matched = 0;
for (unsigned int j = 0; j<kp->num_checks; j++)
{
2022-08-08 15:22:45 +02:00
const struct TALER_KYCLOGIC_KycCheck *kc = kp->provided_checks[j];
2022-08-04 11:36:05 +02:00
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;
}
}
2022-08-20 21:29:29 +02:00
*plugin = kp_best->logic;
*pd = kp_best->pd;
*configuration_section = kp_best->provider_section_name;
return GNUNET_OK;
}
2022-08-01 18:09:06 +02:00
enum GNUNET_GenericReturnValue
2022-08-20 21:29:29 +02:00
TALER_KYCLOGIC_lookup_logic (const char *name,
struct TALER_KYCLOGIC_Plugin **plugin,
struct TALER_KYCLOGIC_ProviderDetails **pd,
const char **provider_section)
2022-08-01 18:09:06 +02:00
{
for (unsigned int i = 0; i<num_kyc_providers; i++)
{
2022-08-08 15:22:45 +02:00
struct TALER_KYCLOGIC_KycProvider *kp = kyc_providers[i];
if (0 !=
2022-08-20 21:29:29 +02:00
strcasecmp (name,
kp->provider_section_name))
continue;
*plugin = kp->logic;
*pd = kp->pd;
2022-08-20 21:29:29 +02:00
*provider_section = kp->provider_section_name;
return GNUNET_OK;
}
for (unsigned int i = 0; i<num_kyc_logics; i++)
{
struct TALER_KYCLOGIC_Plugin *logic = kyc_logics[i];
if (0 !=
strcasecmp (logic->name,
2022-08-20 21:29:29 +02:00
name))
continue;
*plugin = logic;
*pd = NULL;
2022-08-20 21:29:29 +02:00
*provider_section = NULL;
return GNUNET_OK;
}
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Provider `%s' unknown\n",
2022-08-20 21:29:29 +02:00
name);
2022-08-01 18:09:06 +02:00
return GNUNET_SYSERR;
}
void
TALER_KYCLOGIC_kyc_iterate_thresholds (
enum TALER_KYCLOGIC_KycTriggerEvent event,
TALER_KYCLOGIC_KycThresholdIterator it,
void *it_cls)
{
for (unsigned int i = 0; i<num_kyc_triggers; i++)
{
const struct TALER_KYCLOGIC_KycTrigger *kt = kyc_triggers[i];
if (event != kt->trigger)
continue;
it (it_cls,
&kt->threshold);
}
}
/* end of taler-exchange-httpd_kyc.c */