/* This file is part of TALER Copyright (C) 2022-2024 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 */ /** * @file kyclogic_api.c * @brief server-side KYC API * @author Christian Grothoff */ #include "platform.h" #include "taler_json_lib.h" #include "taler_kyclogic_lib.h" /** * Name of the KYC measure that may never be passed. Useful if some * operations/amounts are categorically forbidden. */ #define KYC_MEASURE_IMPOSSIBLE "verboten" /** * Information about a KYC provider. */ struct TALER_KYCLOGIC_KycProvider { /** * Name of the provider. */ char *provider_name; /** * Logic to run for this provider. */ struct TALER_KYCLOGIC_Plugin *logic; /** * Provider-specific details to pass to the @e logic functions. */ struct TALER_KYCLOGIC_ProviderDetails *pd; }; /** * Rule that triggers some measure(s). */ struct TALER_KYCLOGIC_KycRule { /** * Name of the rule (configuration section name). * NULL if not from the configuration. */ char *rule_name; /** * Rule set with custom measures that this KYC rule * is part of. */ const struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs; /** * 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 names of measures to apply on this trigger. */ char **next_measures; /** * Length of the @e next_measures array. */ unsigned int num_measures; /** * Display priority for this rule. */ uint32_t display_priority; /** * What operation type is this rule for? */ enum TALER_KYCLOGIC_KycTriggerEvent trigger; /** * True if all @e next_measures will eventually need to * be satisfied, False if the user has a choice between them. */ bool is_and_combinator; /** * True if this rule and the general nature of the next measures * should be exposed to the client. */ bool exposed; /** * True if any of the measures is 'verboten' and * thus this rule cannot ever be satisfied. */ bool verboten; }; /** * KYC measure that can be taken. */ struct TALER_KYCLOGIC_Measure { /** * Name of the KYC measure. */ char *measure_name; /** * Name of the KYC check. */ char *check_name; /** * Name of the AML program. */ char *prog_name; /** * Context for the check. Can be NULL. */ json_t *context; /** * Can this measure be triggered voluntarily? */ bool voluntary; }; /** * Set of rules that applies to an account. */ struct TALER_KYCLOGIC_LegitimizationRuleSet { /** * When does this rule set expire? */ struct GNUNET_TIME_Timestamp expiration_time; /** * Name of the successor measure after expiration. * NULL to revert to default rules. */ char *successor_measure; /** * Array of the rules. */ struct TALER_KYCLOGIC_KycRule *kyc_rules; /** * Array of custom measures the @e kyc_rules may refer * to. */ struct TALER_KYCLOGIC_Measure *custom_measures; /** * Length of the @e kyc_rules array. */ unsigned int num_kyc_rules; /** * Length of the @e custom_measures array. */ unsigned int num_custom_measures; }; /** * AML programs. */ struct TALER_KYCLOGIC_AmlProgram { /** * Name of the AML program configuration section. */ char *program_name; /** * Name of the AML program (binary) to run. */ char *command; /** * Human-readable description of what this AML helper * program will do. */ char *description; /** * Name of an original measure to take in case the * @e command fails, NULL to fallback to default rules. */ char *fallback; /** * Output of @e command "-r". */ char **required_contexts; /** * Length of the @e required_contexts array. */ unsigned int num_required_contexts; /** * Output of @e command "-a". */ char **required_attributes; /** * Length of the @e required_attributes array. */ unsigned int num_required_attributes; }; /** * 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 configured providers. */ static struct TALER_KYCLOGIC_KycProvider **kyc_providers; /** * Length of the #kyc_providers array. */ static unsigned int num_kyc_providers; /** * 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; /** * Rules that apply if we do not have an AMLA record. */ static struct TALER_KYCLOGIC_LegitimizationRuleSet default_rules; /** * Array of available AML programs. */ static struct TALER_KYCLOGIC_AmlProgram **aml_programs; /** * Length of the #aml_programs array. */ static unsigned int num_aml_programs; /** * Name of our configuration file. */ static char *cfg_filename; struct GNUNET_TIME_Timestamp TALER_KYCLOGIC_rules_get_expiration ( const struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs) { if (NULL == lrs) return GNUNET_TIME_UNIT_FOREVER_TS; return lrs->expiration_time; } const char * TALER_KYCLOGIC_rules_get_successor ( const struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs) { return lrs->successor_measure; } /** * Lookup a KYC check by @a check_name * * @param check_name name to search for * @return NULL if not found */ static struct TALER_KYCLOGIC_KycCheck * find_check (const char *check_name) { for (unsigned int i = 0; icheck_name)) return kyc_check; } GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Check `%s' unknown\n", check_name); return NULL; } /** * Lookup AML program by @a program_name * * @param program_name name to search for * @return NULL if not found */ static struct TALER_KYCLOGIC_AmlProgram * find_program (const char *program_name) { for (unsigned int i = 0; iprogram_name)) return program; } GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "AML program `%s' unknown\n", program_name); return NULL; } /** * Lookup KYC provider by @a provider_name * * @param provider_name name to search for * @return NULL if not found */ static struct TALER_KYCLOGIC_KycProvider * find_provider (const char *provider_name) { for (unsigned int i = 0; iprovider_name)) return provider; } GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "KYC provider `%s' unknown\n", provider_name); return NULL; } /** * Check that @a measure is well-formed and internally * consistent. * * @param measure measure to check * @return true if measure is well-formed */ static bool check_measure (const struct TALER_KYCLOGIC_Measure *measure) { const struct TALER_KYCLOGIC_KycCheck *check; const struct TALER_KYCLOGIC_AmlProgram *program; if (0 == strcasecmp (measure->check_name, "SKIP")) return true; check = find_check (measure->check_name); if (NULL == check) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unknown check `%s' used in measure `%s'\n", measure->check_name, measure->measure_name); return false; } program = find_program (measure->prog_name); if (NULL == program) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unknown program `%s' used in measure `%s'\n", measure->prog_name, measure->measure_name); return false; } for (unsigned int j = 0; jnum_requires; j++) { const char *required_input = check->requires[j]; if (NULL == json_object_get (measure->context, required_input)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Measure `%s' lacks required context `%s' for check `%s'\n", measure->measure_name, required_input, check->check_name); return false; } } for (unsigned int j = 0; jnum_required_contexts; j++) { const char *required_context = program->required_contexts[j]; if (NULL == json_object_get (measure->context, required_context)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Measure `%s' lacks required context `%s' for AML program `%s'\n", measure->measure_name, required_context, program->program_name); return false; } } for (unsigned int j = 0; jnum_required_attributes; j++) { const char *required_attribute = program->required_attributes[j]; bool found = false; for (unsigned int i = 0; inum_outputs; i++) { if (0 == strcasecmp (required_attribute, check->outputs[i])) { found = true; break; } } if (! found) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Check `%s' of measure `%s' does not provide required output `%s' for AML program `%s'\n", check->check_name, measure->measure_name, required_attribute, program->program_name); return false; } } return true; } struct TALER_KYCLOGIC_LegitimizationRuleSet * TALER_KYCLOGIC_rules_parse (const json_t *jlrs) { struct GNUNET_TIME_Timestamp expiration_time; const char *successor_measure = NULL; const json_t *jrules; const json_t *jcustom_measures; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_timestamp ( "expiration_time", &expiration_time), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ( "successor_measure", &successor_measure), NULL), GNUNET_JSON_spec_array_const ("rules", &jrules), GNUNET_JSON_spec_object_const ("custom_measures", &jcustom_measures), GNUNET_JSON_spec_end () }; struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs; const char *err; unsigned int line; if (NULL == jlrs) { GNUNET_break_op (0); return NULL; } if (GNUNET_OK != GNUNET_JSON_parse (jlrs, spec, &err, &line)) { GNUNET_break_op (0); GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Legitimization rules have incorrect input field `%s'\n", err); json_dumpf (jlrs, stderr, JSON_INDENT (2)); return NULL; } lrs = GNUNET_new (struct TALER_KYCLOGIC_LegitimizationRuleSet); lrs->expiration_time = expiration_time; lrs->successor_measure = (NULL == successor_measure) ? NULL : GNUNET_strdup (successor_measure); lrs->num_kyc_rules = (unsigned int) json_array_size (jrules); if (((size_t) lrs->num_kyc_rules) != json_array_size (jrules)) { GNUNET_break (0); goto cleanup; } lrs->num_custom_measures = (unsigned int) json_object_size (jcustom_measures); if (((size_t) lrs->num_custom_measures) != json_object_size (jcustom_measures)) { GNUNET_break (0); goto cleanup; } lrs->kyc_rules = GNUNET_new_array (lrs->num_kyc_rules, struct TALER_KYCLOGIC_KycRule); { const json_t *jrule; size_t off; json_array_foreach ((json_t *) jrules, off, jrule) { struct TALER_KYCLOGIC_KycRule *rule = &lrs->kyc_rules[off]; const json_t *jmeasures; struct GNUNET_JSON_Specification ispec[] = { TALER_JSON_spec_kycte ("operation_type", &rule->trigger), TALER_JSON_spec_amount_any ("threshold", &rule->threshold), GNUNET_JSON_spec_relative_time ("timeframe", &rule->timeframe), GNUNET_JSON_spec_array_const ("measures", &jmeasures), GNUNET_JSON_spec_uint32 ("display_priority", &rule->display_priority), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_bool ("exposed", &rule->exposed), NULL), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_bool ("is_and_combinator", &rule->is_and_combinator), NULL), GNUNET_JSON_spec_end () }; if (GNUNET_OK != GNUNET_JSON_parse (jrule, ispec, NULL, NULL)) { GNUNET_break_op (0); goto cleanup; } GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Parsed KYC rule %u for %d with threshold %s\n", (unsigned int) off, (int) rule->trigger, TALER_amount2s (&rule->threshold)); rule->lrs = lrs; rule->num_measures = json_array_size (jmeasures); rule->next_measures = GNUNET_new_array (rule->num_measures, char *); if (((size_t) rule->num_measures) != json_array_size (jmeasures)) { GNUNET_break (0); goto cleanup; } { size_t j; json_t *jmeasure; json_array_foreach (jmeasures, j, jmeasure) { const char *str; str = json_string_value (jmeasure); if (NULL == str) { GNUNET_break (0); goto cleanup; } rule->next_measures[j] = GNUNET_strdup (str); } } } } lrs->custom_measures = GNUNET_new_array (lrs->num_custom_measures, struct TALER_KYCLOGIC_Measure); { const json_t *jmeasure; const char *measure_name; unsigned int off = 0; json_object_foreach ((json_t *) jcustom_measures, measure_name, jmeasure) { const char *check_name; const char *prog_name; const json_t *context = NULL; bool voluntary = false; struct TALER_KYCLOGIC_Measure *measure = &lrs->custom_measures[off++]; struct GNUNET_JSON_Specification ispec[] = { GNUNET_JSON_spec_string ("check_name", &check_name), GNUNET_JSON_spec_string ("prog_name", &prog_name), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_object_const ("context", &context), NULL), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_bool ("voluntary", &voluntary), NULL), GNUNET_JSON_spec_end () }; if (GNUNET_OK != GNUNET_JSON_parse (jmeasure, ispec, NULL, NULL)) { GNUNET_break_op (0); goto cleanup; } measure->measure_name = GNUNET_strdup (measure_name); measure->check_name = GNUNET_strdup (check_name); measure->prog_name = GNUNET_strdup (prog_name); measure->voluntary = voluntary; if (NULL != context) measure->context = json_incref ((json_t*) context); if (! check_measure (measure)) { GNUNET_break_op (0); goto cleanup; } } } return lrs; cleanup: TALER_KYCLOGIC_rules_free (lrs); return NULL; } void TALER_KYCLOGIC_rules_free (struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs) { if (NULL == lrs) return; for (unsigned int i = 0; inum_kyc_rules; i++) { struct TALER_KYCLOGIC_KycRule *rule = &lrs->kyc_rules[i]; for (unsigned int j = 0; jnum_measures; j++) GNUNET_free (rule->next_measures[j]); GNUNET_free (rule->next_measures); GNUNET_free (rule->rule_name); } for (unsigned int i = 0; inum_custom_measures; i++) { struct TALER_KYCLOGIC_Measure *measure = &lrs->custom_measures[i]; GNUNET_free (measure->measure_name); GNUNET_free (measure->check_name); GNUNET_free (measure->prog_name); json_decref (measure->context); } GNUNET_free (lrs->kyc_rules); GNUNET_free (lrs->custom_measures); GNUNET_free (lrs->successor_measure); GNUNET_free (lrs); } const char * TALER_KYCLOGIC_rule2s ( const struct TALER_KYCLOGIC_KycRule *r) { return r->rule_name; } const char * TALER_KYCLOGIC_status2s (enum TALER_KYCLOGIC_KycStatus status) { switch (status) { case TALER_KYCLOGIC_STATUS_SUCCESS: return "success"; case TALER_KYCLOGIC_STATUS_USER: return "user"; case TALER_KYCLOGIC_STATUS_PROVIDER: return "provider"; case TALER_KYCLOGIC_STATUS_FAILED: return "failed"; case TALER_KYCLOGIC_STATUS_PENDING: return "pending"; case TALER_KYCLOGIC_STATUS_ABORTED: return "aborted"; case TALER_KYCLOGIC_STATUS_USER_PENDING: return "pending with user"; case TALER_KYCLOGIC_STATUS_PROVIDER_PENDING: return "pending at provider"; case TALER_KYCLOGIC_STATUS_USER_ABORTED: return "aborted by user"; case TALER_KYCLOGIC_STATUS_PROVIDER_FAILED: return "failed by provider"; case TALER_KYCLOGIC_STATUS_KEEP: return "keep"; case TALER_KYCLOGIC_STATUS_INTERNAL_ERROR: return "internal error"; } return "unknown status"; } json_t * TALER_KYCLOGIC_rules_to_limits (const json_t *jrules) { if (NULL == jrules) { /* default limits apply */ const struct TALER_KYCLOGIC_KycRule *rules = default_rules.kyc_rules; unsigned int num_rules = default_rules.num_kyc_rules; json_t *jlimits; jlimits = json_array (); GNUNET_assert (NULL != jlimits); for (unsigned int i = 0; iexposed) continue; limit = GNUNET_JSON_PACK ( GNUNET_JSON_pack_bool ("soft_limit", ! rule->verboten), TALER_JSON_pack_kycte ("operation_type", rule->trigger), GNUNET_JSON_pack_time_rel ("timeframe", rule->timeframe), TALER_JSON_pack_amount ("threshold", &rule->threshold) ); GNUNET_assert (0 == json_array_append_new (jlimits, limit)); } return jlimits; } { json_t *limits; json_t *limit; json_t *rule; size_t idx; limits = json_array (); GNUNET_assert (NULL != limits); json_array_foreach ((json_t *) jrules, idx, rule) { struct GNUNET_TIME_Relative timeframe; struct TALER_Amount threshold; bool exposed = false; const json_t *jmeasures; enum TALER_KYCLOGIC_KycTriggerEvent operation_type; struct GNUNET_JSON_Specification spec[] = { TALER_JSON_spec_kycte ("operation_type", &operation_type), GNUNET_JSON_spec_relative_time ("timeframe", &timeframe), TALER_JSON_spec_amount_any ("threshold", &threshold), GNUNET_JSON_spec_array_const ("measures", &jmeasures), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_bool ("exposed", &exposed), NULL), GNUNET_JSON_spec_end () }; bool forbidden = false; size_t i; json_t *jmeasure; if (GNUNET_OK != GNUNET_JSON_parse (jrules, spec, NULL, NULL)) { GNUNET_break_op (0); json_decref (limits); return NULL; } if (! exposed) continue; json_array_foreach (jmeasures, i, jmeasure) { const char *val; val = json_string_value (jmeasure); if (NULL == val) { GNUNET_break_op (0); json_decref (limits); return NULL; } if (0 == strcasecmp (KYC_MEASURE_IMPOSSIBLE, val)) forbidden = true; } limit = GNUNET_JSON_PACK ( TALER_JSON_pack_kycte ( "operation_type", operation_type), GNUNET_JSON_pack_time_rel ( "timeframe", timeframe), TALER_JSON_pack_amount ( "threshold", &threshold), /* optional since v21, defaults to 'false' */ GNUNET_JSON_pack_bool ( "soft_limit", ! forbidden)); GNUNET_assert (0 == json_array_append_new (limits, limit)); } return limits; } } /** * Find measure @a measure_name in @a lrs. * If measure is not found in @a lrs, fall back to * default measures. * * @param lrs rule set to search, can be NULL to only search default measures * @param measure_name name of measure to find * @return NULL if not found, otherwise the measure */ static const struct TALER_KYCLOGIC_Measure * find_measure ( const struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs, const char *measure_name) { if (NULL != lrs) { for (unsigned int i = 0; inum_custom_measures; i++) { const struct TALER_KYCLOGIC_Measure *cm = &lrs->custom_measures[i]; if (0 == strcasecmp (measure_name, cm->measure_name)) return cm; } } if (lrs != &default_rules) { /* Try measures from default rules */ for (unsigned int i = 0; imeasure_name)) return cm; } } GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Measure `%s' not found\n", measure_name); return NULL; } const struct TALER_KYCLOGIC_Measure * TALER_KYCLOGIC_rule_get_instant_measure ( const struct TALER_KYCLOGIC_KycRule *r) { const struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs = r->lrs; if (r->verboten) return NULL; for (unsigned int i = 0; inum_measures; i++) { const char *measure_name = r->next_measures[i]; const struct TALER_KYCLOGIC_Measure *ms; ms = find_measure (lrs, measure_name); if (NULL == ms) { GNUNET_break (0); return NULL; } if (0 == strcasecmp (ms->check_name, "SKIP")) return ms; } return NULL; } json_t * TALER_KYCLOGIC_rule_to_measures ( const struct TALER_KYCLOGIC_KycRule *r) { const struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs = r->lrs; json_t *jmeasures; jmeasures = json_array (); GNUNET_assert (NULL != jmeasures); if (! r->verboten) { for (unsigned int i = 0; inum_measures; i++) { const char *measure_name = r->next_measures[i]; const struct TALER_KYCLOGIC_Measure *ms; json_t *mi; ms = find_measure (lrs, measure_name); if (NULL == ms) { GNUNET_break (0); json_decref (jmeasures); return NULL; } mi = GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("check_name", ms->check_name), GNUNET_JSON_pack_string ("prog_name", ms->prog_name), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_object_incref ("context", ms->context))); GNUNET_assert (0 == json_array_append_new (jmeasures, mi)); } } return GNUNET_JSON_PACK ( GNUNET_JSON_pack_array_steal ("measures", jmeasures), GNUNET_JSON_pack_bool ("is_and_combinator", r->is_and_combinator), GNUNET_JSON_pack_bool ("verboten", r->verboten)); } json_t * TALER_KYCLOGIC_zero_measures ( const struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs) { json_t *zero_measures; const struct TALER_KYCLOGIC_KycRule *rules; unsigned int num_rules; if (NULL == lrs) lrs = &default_rules; rules = lrs->kyc_rules; num_rules = lrs->num_kyc_rules; zero_measures = json_array (); GNUNET_assert (NULL != zero_measures); for (unsigned int i = 0; iexposed) continue; if (rule->verboten) continue; /* see: hard_limits */ if (! TALER_amount_is_zero (&rule->threshold)) continue; for (unsigned int j = 0; jnum_measures; j++) { const struct TALER_KYCLOGIC_Measure *ms; json_t *mi; ms = find_measure (lrs, rule->next_measures[j]); if (NULL == ms) { GNUNET_break (0); json_decref (zero_measures); return NULL; } if (0 == strcasecmp ("verboten", ms->check_name)) continue; /* not a measure to be selected */ mi = GNUNET_JSON_PACK ( TALER_JSON_pack_kycte ("operation_type", rule->trigger), GNUNET_JSON_pack_string ("check_name", ms->check_name), GNUNET_JSON_pack_string ("prog_name", ms->prog_name), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_object_incref ("context", ms->context))); GNUNET_assert (0 == json_array_append_new (zero_measures, mi)); } } return GNUNET_JSON_PACK ( GNUNET_JSON_pack_array_steal ("measures", zero_measures), /* Zero-measures are always OR */ GNUNET_JSON_pack_bool ("is_and_combinator", false), /* OR means verboten measures do not matter */ GNUNET_JSON_pack_bool ("verboten", false)); } /** * Check if @a ms is a voluntary measure, and if so * convert to JSON and append to @a voluntary_measures. * * @param[in,out] voluntary_measures JSON array of MeasureInformation * @param ms a measure to possibly append */ static void append_voluntary_measure ( json_t *voluntary_measures, const struct TALER_KYCLOGIC_Measure *ms) { #if 0 json_t *mj; #endif if (! ms->voluntary) return; if (0 == strcasecmp ("verboten", ms->check_name)) return; /* very strange configuration */ #if 0 /* TODO: support vATTEST-9048 (this API in kyclogic!) */ // NOTE: need to convert ms to "KycRequirementInformation" // *and* in particular generate "id" values that // are then understood to refer to the voluntary measures // by the rest of the API (which is the hard part!) // => need to change the API to encode the // legitimization_outcomes row ID of the lrs from // which the voluntary 'ms' originated, and // then update the kyc-upload/kyc-start endpoints // to recognize the new ID format! mj = GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("check_name", ms->check_name), GNUNET_JSON_pack_string ("prog_name", ms->prog_name), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_object_incref ("context", ms->context))); GNUNET_assert (0 == json_array_append_new (voluntary_measures, mj)); #endif } json_t * TALER_KYCLOGIC_voluntary_measures ( const struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs) { json_t *voluntary_measures; voluntary_measures = json_array (); GNUNET_assert (NULL != voluntary_measures); if (NULL != lrs) { for (unsigned int i = 0; inum_custom_measures; i++) { const struct TALER_KYCLOGIC_Measure *ms = &lrs->custom_measures[i]; append_voluntary_measure (voluntary_measures, ms); } } for (unsigned int i = 0; icheck_name)) { continue; } if (0 == strcasecmp ("SKIP", ms->check_name)) { ret = ms; goto done; } } done: GNUNET_free (nm); return ret; } json_t * TALER_KYCLOGIC_get_measures ( const struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs, const char *measures_spec) { json_t *jmeasures; char *nm; bool verboten = false; bool is_and = false; if ('+' == measures_spec[0]) { nm = GNUNET_strdup (&measures_spec[1]); is_and = true; } else { nm = GNUNET_strdup (measures_spec); } jmeasures = json_array (); GNUNET_assert (NULL != jmeasures); for (const char *tok = strtok (nm, " "); NULL != tok; tok = strtok (NULL, " ")) { const struct TALER_KYCLOGIC_Measure *ms; json_t *mi; if (0 == strcasecmp ("verboten", tok)) { verboten = true; continue; } ms = find_measure (lrs, tok); if (NULL == ms) { GNUNET_break (0); GNUNET_free (nm); json_decref (jmeasures); return NULL; } if (0 == strcasecmp ("verboten", ms->check_name)) { verboten = true; continue; } mi = GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("check_name", ms->check_name), GNUNET_JSON_pack_string ("prog_name", ms->prog_name), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_object_incref ("context", ms->context))); GNUNET_assert (0 == json_array_append_new (jmeasures, mi)); } GNUNET_free (nm); return GNUNET_JSON_PACK ( GNUNET_JSON_pack_array_steal ("measures", jmeasures), GNUNET_JSON_pack_bool ("is_and_combinator", is_and), GNUNET_JSON_pack_bool ("verboten", verboten)); } json_t * TALER_KYCLOGIC_check_to_measures ( const struct TALER_KYCLOGIC_KycCheckContext *kcc) { const struct TALER_KYCLOGIC_KycCheck *check = kcc->check; json_t *jmeasures; json_t *mi; mi = GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("check_name", NULL == check ? "SKIP" : check->check_name), GNUNET_JSON_pack_string ("prog_name", kcc->prog_name), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_object_incref ("context", (json_t *) kcc->context))); jmeasures = json_array (); GNUNET_assert (NULL != jmeasures); GNUNET_assert (0 == json_array_append_new (jmeasures, mi)); return GNUNET_JSON_PACK ( GNUNET_JSON_pack_array_steal ("measures", jmeasures), GNUNET_JSON_pack_bool ("is_and_combinator", true), GNUNET_JSON_pack_bool ("verboten", false)); } uint32_t TALER_KYCLOGIC_rule2priority ( const struct TALER_KYCLOGIC_KycRule *r) { return r->display_priority; } /** * Perform very primitive word splitting of a command. * * @args command command to split * @args extra_args extra arguments to append after the word * @returns NULL-terminated array of words */ static char ** split_words (const char *command, const char **extra_args) { unsigned int i = 0; unsigned int j = 0; unsigned int n = 0; char **res = NULL; /* Result is always NULL-terminated */ GNUNET_array_append (res, n, NULL); /* Split command into words */ while (1) { char *c; /* Skip initial whitespace before word */ while (' ' == command[i]) i++; /* Start of new word */ j = i; /* Scan to end of word */ while ( (0 != command[j]) && (' ' != command[j]) ) j++; /* No new word found */ if (i == j) break; /* Append word to result */ c = GNUNET_malloc (j - i + 1); memcpy (c, &command[i], j - i); c[j - i] = 0; res[n - 1] = c; GNUNET_array_append (res, n, NULL); /* Continue at end of word */ i = j; } /* Append extra args */ if (NULL != extra_args) { for (const char **m = extra_args; *m; m++) { res[n - 1] = GNUNET_strdup (*m); GNUNET_array_append (res, n, NULL); } } return res; } /** * Free arguments allocated with split_words. * * @param args NULL-terminated array of strings to free. */ static void destroy_words (char **args) { if (NULL == args) return; for (char **m = args; *m; m++) { GNUNET_free (*m); *m = NULL; } GNUNET_free (args); } /** * Run @a command with @a argument and return the * respective output from stdout. * * @param command binary to run * @param argument command-line argument to pass * @return NULL if @a command failed */ static char * command_output (const char *command, const char *argument) { char *rval; unsigned int sval; size_t soff; ssize_t ret; int sout[2]; pid_t chld; const char *extra_args[] = { argument, "-c", cfg_filename, NULL, }; if (0 != pipe (sout)) { GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "pipe"); return NULL; } chld = fork (); if (-1 == chld) { GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "fork"); return NULL; } if (0 == chld) { char **argv; argv = split_words (command, extra_args); GNUNET_break (0 == close (sout[0])); GNUNET_break (0 == close (STDOUT_FILENO)); GNUNET_assert (STDOUT_FILENO == dup2 (sout[1], STDOUT_FILENO)); GNUNET_break (0 == close (sout[1])); execvp (argv[0], argv); destroy_words (argv); GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "exec", command); exit (EXIT_FAILURE); } GNUNET_break (0 == close (sout[1])); sval = 1024; rval = GNUNET_malloc (sval); soff = 0; while (0 < (ret = read (sout[0], rval + soff, sval - soff)) ) { soff += ret; if (soff == sval) { GNUNET_array_grow (rval, sval, sval * 2); } } GNUNET_break (0 == close (sout[0])); { int wstatus; GNUNET_break (chld == waitpid (chld, &wstatus, 0)); if ( (! WIFEXITED (wstatus)) || (0 != WEXITSTATUS (wstatus)) ) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Command `%s' %s failed with status %d\n", command, argument, wstatus); GNUNET_array_grow (rval, sval, 0); return NULL; } } GNUNET_array_grow (rval, sval, soff + 1); rval[soff] = '\0'; return rval; } /** * Convert check type @a ctype_s into @a ctype. * * @param ctype_s check type as a string * @param[out] ctype set to check type as enum * @return #GNUNET_OK on success */ static enum GNUNET_GenericReturnValue check_type_from_string ( const char *ctype_s, enum TALER_KYCLOGIC_CheckType *ctype) { struct { const char *in; enum TALER_KYCLOGIC_CheckType out; } map [] = { { "INFO", TALER_KYCLOGIC_CT_INFO }, { "LINK", TALER_KYCLOGIC_CT_LINK }, { "FORM", TALER_KYCLOGIC_CT_FORM }, { NULL, 0 } }; for (unsigned int i = 0; NULL != map[i].in; i++) if (0 == strcasecmp (map[i].in, ctype_s)) { *ctype = map[i].out; return GNUNET_OK; } GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Invalid check type `%s'\n", ctype_s); return GNUNET_SYSERR; } enum GNUNET_GenericReturnValue TALER_KYCLOGIC_kyc_trigger_from_string ( const char *trigger_s, enum TALER_KYCLOGIC_KycTriggerEvent *trigger) { /* NOTE: if you change this, also change the code in src/json/json_helper.c! */ 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 }, { "CLOSE", TALER_KYCLOGIC_KYC_TRIGGER_RESERVE_CLOSE }, { "AGGREGATE", TALER_KYCLOGIC_KYC_TRIGGER_AGGREGATE }, { "TRANSACTION", TALER_KYCLOGIC_KYC_TRIGGER_TRANSACTION }, { "REFUND", TALER_KYCLOGIC_KYC_TRIGGER_REFUND }, { 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; } json_t * TALER_KYCLOGIC_get_wallet_thresholds (void) { json_t *ret; ret = json_array (); GNUNET_assert (NULL != ret); for (unsigned int i = 0; itrigger) continue; GNUNET_assert ( 0 == json_array_append_new ( ret, TALER_JSON_from_amount ( &rule->threshold))); } return ret; } /** * 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); for (unsigned int i = 0; ilibrary_name)) { GNUNET_free (lib_name); return kyc_logics[i]; } plugin = GNUNET_PLUGIN_load (lib_name, (void *) cfg); if (NULL == plugin) { GNUNET_free (lib_name); return NULL; } plugin->library_name = lib_name; plugin->name = GNUNET_strdup (name); GNUNET_array_append (kyc_logics, num_kyc_logics, plugin); return plugin; } /** * 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) { char *logic; struct TALER_KYCLOGIC_Plugin *lp; struct TALER_KYCLOGIC_ProviderDetails *pd; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Parsing KYC provider %s\n", section); 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_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, section, "LOGIC", "logic plugin could not be loaded"); GNUNET_free (logic); return GNUNET_SYSERR; } GNUNET_free (logic); pd = lp->load_configuration (lp->cls, section); if (NULL == pd) return GNUNET_SYSERR; { struct TALER_KYCLOGIC_KycProvider *kp; kp = GNUNET_new (struct TALER_KYCLOGIC_KycProvider); kp->provider_name = GNUNET_strdup (§ion[strlen ("kyc-provider-")]); kp->logic = lp; kp->pd = pd; GNUNET_array_append (kyc_providers, num_kyc_providers, kp); } return GNUNET_OK; } /** * Tokenize @a input along @a token * and build an array of the tokens. * * @param[in,out] input the input to tokenize; clobbered * @param sep separator between tokens to separate @a input on * @param[out] p_strs where to put array of tokens * @param[out] num_strs set to length of @a p_strs array */ static void add_tokens (char *input, const char *sep, char ***p_strs, unsigned int *num_strs) { char *sptr; char **rstr = NULL; unsigned int num_rstr = 0; for (char *tok = strtok_r (input, sep, &sptr); NULL != tok; tok = strtok_r (NULL, sep, &sptr)) { GNUNET_array_append (rstr, num_rstr, GNUNET_strdup (tok)); } *p_strs = rstr; *num_strs = num_rstr; } /** * Closure for the handle_XXX_section functions * that parse configuration sections matching certain * prefixes. */ 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_provider_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)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Setup failed in configuration section `%s'\n", section); sc->result = false; } return; } } /** * Parse configuration @a cfg in section @a section for * the specification of a KYC check. * * @param cfg configuration to parse * @param section configuration section to parse * @return #GNUNET_OK on success */ static enum GNUNET_GenericReturnValue add_check (const struct GNUNET_CONFIGURATION_Handle *cfg, const char *section) { enum TALER_KYCLOGIC_CheckType ct; char *description = NULL; json_t *description_i18n = NULL; char *requires = NULL; char *outputs = NULL; char *fallback = NULL; if (0 == strcasecmp (§ion[strlen ("kyc-check-")], "SKIP")) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "The kyc-check-skip section must not exist, 'skip' is reserved name for a built-in check\n"); return GNUNET_SYSERR; } GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Parsing KYC check %s\n", section); { char *type_s; if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, section, "TYPE", &type_s)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, section, "TYPE"); return GNUNET_SYSERR; } if (GNUNET_OK != check_type_from_string (type_s, &ct)) { GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, section, "TYPE", "valid check type required"); GNUNET_free (type_s); return GNUNET_SYSERR; } GNUNET_free (type_s); } if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, section, "DESCRIPTION", &description)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, section, "DESCRIPTION"); goto fail; } { char *tmp; if (GNUNET_OK == GNUNET_CONFIGURATION_get_value_string (cfg, section, "DESCRIPTION_I18N", &tmp)) { json_error_t err; description_i18n = json_loads (tmp, JSON_REJECT_DUPLICATES, &err); GNUNET_free (tmp); if (NULL == description_i18n) { GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, section, "DESCRIPTION_I18N", err.text); goto fail; } if (! TALER_JSON_check_i18n (description_i18n) ) { GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, section, "DESCRIPTION_I18N", "JSON with internationalization map required"); goto fail; } } } if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, section, "REQUIRES", &requires)) { /* no requirements is OK */ requires = GNUNET_strdup (""); } if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, section, "OUTPUTS", &outputs)) { /* no outputs is OK */ outputs = GNUNET_strdup (""); } if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, section, "FALLBACK", &fallback)) { /* FIXME: Allow NULL to fall back to default rules? */ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, section, "FALLBACK"); goto fail; } { struct TALER_KYCLOGIC_KycCheck *kc; kc = GNUNET_new (struct TALER_KYCLOGIC_KycCheck); switch (ct) { case TALER_KYCLOGIC_CT_INFO: /* nothing to do */ break; case TALER_KYCLOGIC_CT_FORM: { char *form_name; if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, section, "FORM_NAME", &form_name)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, section, "FORM_NAME"); GNUNET_free (requires); GNUNET_free (outputs); GNUNET_free (kc); return GNUNET_SYSERR; } kc->details.form.name = form_name; } break; case TALER_KYCLOGIC_CT_LINK: { char *provider_id; if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, section, "PROVIDER_ID", &provider_id)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, section, "PROVIDER_ID"); GNUNET_free (requires); GNUNET_free (outputs); GNUNET_free (kc); return GNUNET_SYSERR; } kc->details.link.provider = find_provider (provider_id); if (NULL == kc->details.link.provider) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unknown KYC provider `%s' used in check `%s'\n", provider_id, §ion[strlen ("kyc-check-")]); GNUNET_free (provider_id); GNUNET_free (requires); GNUNET_free (outputs); GNUNET_free (kc); return GNUNET_SYSERR; } GNUNET_free (provider_id); } break; } kc->check_name = GNUNET_strdup (§ion[strlen ("kyc-check-")]); kc->description = description; kc->description_i18n = description_i18n; kc->fallback = fallback; kc->type = ct; add_tokens (requires, ";", &kc->requires, &kc->num_requires); GNUNET_free (requires); add_tokens (outputs, " ", &kc->outputs, &kc->num_outputs); GNUNET_free (outputs); GNUNET_array_append (kyc_checks, num_kyc_checks, kc); } return GNUNET_OK; fail: GNUNET_free (description); json_decref (description_i18n); GNUNET_free (requires); GNUNET_free (outputs); GNUNET_free (fallback); return GNUNET_SYSERR; } /** * Function to iterate over configuration sections. * * @param cls a `struct SectionContext *` * @param section name of the section */ static void handle_check_section (void *cls, const char *section) { struct SectionContext *sc = cls; if (0 == strncasecmp (section, "kyc-check-", strlen ("kyc-check-"))) { if (GNUNET_OK != add_check (sc->cfg, section)) sc->result = false; return; } } /** * Parse configuration @a cfg in section @a section for * the specification of a KYC rule. * * @param cfg configuration to parse * @param section configuration section to parse * @return #GNUNET_OK on success */ static enum GNUNET_GenericReturnValue add_rule (const struct GNUNET_CONFIGURATION_Handle *cfg, const char *section) { struct TALER_Amount threshold; struct GNUNET_TIME_Relative timeframe; enum TALER_KYCLOGIC_KycTriggerEvent ot; char *measures; bool exposed; bool is_and; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Parsing KYC rule from %s\n", section); if (GNUNET_YES != GNUNET_CONFIGURATION_get_value_yesno (cfg, section, "ENABLED")) return GNUNET_OK; 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; } exposed = (GNUNET_YES == GNUNET_CONFIGURATION_get_value_yesno (cfg, section, "EXPOSED")); { enum GNUNET_GenericReturnValue r; r = GNUNET_CONFIGURATION_get_value_yesno (cfg, section, "IS_AND_COMBINATOR"); if (GNUNET_SYSERR == r) { GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, section, "IS_AND_COMBINATOR", "YES or NO required"); return GNUNET_SYSERR; } is_and = (GNUNET_YES == r); } { char *ot_s; 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, "NEXT_MEASURES", &measures)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, section, "NEXT_MEASURES"); return GNUNET_SYSERR; } GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Adding KYC rule %s for trigger %d with threshold %s\n", section, (int) ot, TALER_amount2s (&threshold)); { struct TALER_KYCLOGIC_KycRule kt = { .lrs = &default_rules, .rule_name = GNUNET_strdup (§ion[strlen ("kyc-rule-")]), .timeframe = timeframe, .threshold = threshold, .trigger = ot, .is_and_combinator = is_and, .exposed = exposed, .display_priority = 0, .verboten = false }; add_tokens (measures, " ", &kt.next_measures, &kt.num_measures); for (unsigned int i=0; icfg, section)) sc->result = false; return; } } /** * Parse configuration @a cfg in section @a section for * the specification of an AML program. * * @param cfg configuration to parse * @param section configuration section to parse * @return #GNUNET_OK on success */ static enum GNUNET_GenericReturnValue add_program (const struct GNUNET_CONFIGURATION_Handle *cfg, const char *section) { char *command = NULL; char *description = NULL; char *fallback = NULL; char *required_contexts = NULL; char *required_attributes = NULL; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Parsing KYC program %s\n", section); if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, section, "COMMAND", &command)) { GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, section, "COMMAND", "command required"); goto fail; } if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, section, "DESCRIPTION", &description)) { GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, section, "DESCRIPTION", "description required"); goto fail; } if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, section, "FALLBACK", &fallback)) { /* FIXME: Allow NULL to fall back to default rules? */ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, section, "FALLBACK", "fallback measure name required"); goto fail; } required_contexts = command_output (command, "-r"); if (NULL == required_contexts) { GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, section, "COMMAND", "output for -r invalid"); goto fail; } required_attributes = command_output (command, "-a"); if (NULL == required_attributes) { GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, section, "COMMAND", "output for -a invalid"); goto fail; } { struct TALER_KYCLOGIC_AmlProgram *ap; ap = GNUNET_new (struct TALER_KYCLOGIC_AmlProgram); ap->program_name = GNUNET_strdup (§ion[strlen ("aml-program-")]); ap->command = command; ap->description = description; ap->fallback = fallback; add_tokens (required_contexts, "\n", &ap->required_contexts, &ap->num_required_contexts); GNUNET_free (required_contexts); add_tokens (required_attributes, "\n", &ap->required_attributes, &ap->num_required_attributes); GNUNET_free (required_attributes); GNUNET_array_append (aml_programs, num_aml_programs, ap); } return GNUNET_OK; fail: GNUNET_free (command); GNUNET_free (description); GNUNET_free (required_contexts); GNUNET_free (required_attributes); GNUNET_free (fallback); return GNUNET_SYSERR; } /** * Function to iterate over configuration sections. * * @param cls a `struct SectionContext *` * @param section name of the section */ static void handle_program_section (void *cls, const char *section) { struct SectionContext *sc = cls; if (0 == strncasecmp (section, "aml-program-", strlen ("aml-program-"))) { if (GNUNET_OK != add_program (sc->cfg, section)) sc->result = false; return; } } /** * Parse configuration @a cfg in section @a section for * the specification of a KYC measure. * * @param cfg configuration to parse * @param section configuration section to parse * @return #GNUNET_OK on success */ static enum GNUNET_GenericReturnValue add_measure (const struct GNUNET_CONFIGURATION_Handle *cfg, const char *section) { bool voluntary; char *check_name = NULL; char *context_str = NULL; char *program = NULL; json_t *context; json_error_t err; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Parsing KYC measure %s\n", section); if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, section, "CHECK_NAME", &check_name)) { check_name = GNUNET_strdup ("SKIP"); } if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, section, "PROGRAM", &program)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, section, "PROGRAM"); goto fail; } voluntary = (GNUNET_YES == GNUNET_CONFIGURATION_get_value_yesno (cfg, section, "VOLUNTARY")); if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, section, "CONTEXT", &context_str)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, section, "CONTEXT"); goto fail; } context = json_loads (context_str, JSON_REJECT_DUPLICATES, &err); GNUNET_free (context_str); if (NULL == context) { GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, section, "CONTEXT", err.text); goto fail; } { struct TALER_KYCLOGIC_Measure m; m.measure_name = GNUNET_strdup (§ion[strlen ("kyc-measure-")]); m.check_name = check_name; m.prog_name = program; m.context = context; m.voluntary = voluntary; GNUNET_array_append (default_rules.custom_measures, default_rules.num_custom_measures, m); } return GNUNET_OK; fail: GNUNET_free (check_name); GNUNET_free (program); GNUNET_free (context_str); return GNUNET_SYSERR; } /** * Function to iterate over configuration sections. * * @param cls a `struct SectionContext *` * @param section name of the section */ static void handle_measure_section (void *cls, const char *section) { struct SectionContext *sc = cls; if (0 == strncasecmp (section, "kyc-measure-", strlen ("kyc-measure-"))) { if (GNUNET_OK != add_measure (sc->cfg, section)) sc->result = false; return; } } /** * Comparator for qsort. Compares two rules * by timeframe to sort rules 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_KycRule *r1 = (struct TALER_KYCLOGIC_KycRule *) p1; struct TALER_KYCLOGIC_KycRule *r2 = (struct TALER_KYCLOGIC_KycRule *) p2; if (GNUNET_TIME_relative_cmp (r1->timeframe, <, r2->timeframe)) return -1; if (GNUNET_TIME_relative_cmp (r1->timeframe, >, r2->timeframe)) return 1; return 0; } enum GNUNET_GenericReturnValue TALER_KYCLOGIC_kyc_init ( const struct GNUNET_CONFIGURATION_Handle *cfg, const char *cfg_fn) { struct SectionContext sc = { .cfg = cfg, .result = true }; cfg_filename = GNUNET_strdup (cfg_fn); GNUNET_CONFIGURATION_iterate_sections (cfg, &handle_provider_section, &sc); GNUNET_CONFIGURATION_iterate_sections (cfg, &handle_check_section, &sc); GNUNET_CONFIGURATION_iterate_sections (cfg, &handle_rule_section, &sc); GNUNET_CONFIGURATION_iterate_sections (cfg, &handle_program_section, &sc); GNUNET_CONFIGURATION_iterate_sections (cfg, &handle_measure_section, &sc); if (! sc.result) { TALER_KYCLOGIC_kyc_done (); return GNUNET_SYSERR; } if (0 != default_rules.num_kyc_rules) qsort (default_rules.kyc_rules, default_rules.num_kyc_rules, sizeof (struct TALER_KYCLOGIC_KycRule), &sort_by_timeframe); for (unsigned int i=0; inum_measures; j++) { const char *measure_name = rule->next_measures[j]; const struct TALER_KYCLOGIC_Measure *m; if (0 == strcmp ("verboten", measure_name)) continue; m = find_measure (&default_rules, measure_name); if (NULL == m) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unknown measure `%s' used in rule `%s'\n", measure_name, rule->rule_name); return GNUNET_SYSERR; } } } for (unsigned int i=0; ifallback) continue; /* default */ m = find_measure (&default_rules, program->fallback); if (NULL == m) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unknown fallback measure `%s' used in program `%s'\n", program->fallback, program->program_name); return GNUNET_SYSERR; } } for (unsigned int i = 0; ifallback) { const struct TALER_KYCLOGIC_Measure *measure; measure = find_measure (&default_rules, kyc_check->fallback); if (NULL == measure) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unknown fallback measure `%s' used in check `%s'\n", kyc_check->fallback, kyc_check->check_name); return GNUNET_SYSERR; } } } for (unsigned int i=0; inum_measures; j++) GNUNET_free (kt->next_measures[j]); GNUNET_array_grow (kt->next_measures, kt->num_measures, 0); GNUNET_free (kt->rule_name); } GNUNET_array_grow (default_rules.kyc_rules, default_rules.num_kyc_rules, 0); for (unsigned int i = 0; ilogic->unload_configuration (kp->pd); GNUNET_free (kp->provider_name); GNUNET_free (kp); } GNUNET_array_grow (kyc_providers, num_kyc_providers, 0); for (unsigned int i = 0; ilibrary_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; icheck_name); GNUNET_free (kc->description); json_decref (kc->description_i18n); for (unsigned int j = 0; jnum_requires; j++) GNUNET_free (kc->requires[j]); GNUNET_array_grow (kc->requires, kc->num_requires, 0); GNUNET_free (kc->fallback); for (unsigned int j = 0; jnum_outputs; j++) GNUNET_free (kc->outputs[j]); GNUNET_array_grow (kc->outputs, kc->num_outputs, 0); switch (kc->type) { case TALER_KYCLOGIC_CT_INFO: break; case TALER_KYCLOGIC_CT_FORM: GNUNET_free (kc->details.form.name); break; case TALER_KYCLOGIC_CT_LINK: break; } GNUNET_free (kc); } GNUNET_array_grow (kyc_checks, num_kyc_checks, 0); for (unsigned int i = 0; iprogram_name); GNUNET_free (ap->command); GNUNET_free (ap->description); GNUNET_free (ap->fallback); for (unsigned int j = 0; jnum_required_contexts; j++) GNUNET_free (ap->required_contexts[j]); GNUNET_array_grow (ap->required_contexts, ap->num_required_contexts, 0); for (unsigned int j = 0; jnum_required_attributes; j++) GNUNET_free (ap->required_attributes[j]); GNUNET_array_grow (ap->required_attributes, ap->num_required_attributes, 0); GNUNET_free (ap); } GNUNET_array_grow (aml_programs, num_aml_programs, 0); GNUNET_free (cfg_filename); } void TALER_KYCLOGIC_provider_to_logic ( const struct TALER_KYCLOGIC_KycProvider *provider, struct TALER_KYCLOGIC_Plugin **plugin, struct TALER_KYCLOGIC_ProviderDetails **pd, const char **provider_name) { *plugin = provider->logic; *pd = provider->pd; *provider_name = provider->provider_name; } enum GNUNET_GenericReturnValue TALER_KYCLOGIC_get_original_measure ( const char *measure_name, struct TALER_KYCLOGIC_KycCheckContext *kcc) { const struct TALER_KYCLOGIC_Measure *measure; measure = find_measure (&default_rules, measure_name); if (NULL == measure) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Default measure `%s' unknown\n", measure_name); return GNUNET_SYSERR; } if (0 == strcasecmp (measure->check_name, "SKIP")) { kcc->check = NULL; kcc->prog_name = measure->prog_name; kcc->context = measure->context; return GNUNET_OK; } for (unsigned int i = 0; icheck_name, kyc_checks[i]->check_name)) { kcc->check = kyc_checks[i]; kcc->prog_name = measure->prog_name; kcc->context = measure->context; return GNUNET_OK; } GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Check `%s' unknown (but required by measure %s)\n", measure->check_name, measure_name); return GNUNET_SYSERR; } enum GNUNET_GenericReturnValue TALER_KYCLOGIC_requirements_to_check ( const struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs, const struct TALER_KYCLOGIC_KycRule *kyc_rule, const char *measure_name, struct TALER_KYCLOGIC_KycCheckContext *kcc) { bool found = false; const struct TALER_KYCLOGIC_Measure *measure = NULL; if (NULL == lrs) lrs = &default_rules; if (NULL == measure_name) { GNUNET_break (0); return GNUNET_SYSERR; } if (NULL != kyc_rule) { for (unsigned int i = 0; inum_measures; i++) { if (0 != strcasecmp (measure_name, kyc_rule->next_measures[i])) continue; found = true; break; } if (! found) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Measure `%s' not allowed for rule `%s'\n", measure_name, kyc_rule->rule_name); return GNUNET_SYSERR; } if (kyc_rule->verboten) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Rule says operation is categorically is verboten, cannot take measures\n"); return GNUNET_SYSERR; } } measure = find_measure (lrs, measure_name); if (NULL == measure) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Measure `%s' unknown (but allowed by rule `%s')\n", measure_name, NULL != kyc_rule ? kyc_rule->rule_name : ""); return GNUNET_SYSERR; } if (0 == strcasecmp (measure->check_name, "SKIP")) { kcc->check = NULL; kcc->prog_name = measure->prog_name; kcc->context = measure->context; return GNUNET_OK; } for (unsigned int i = 0; icheck_name, kyc_checks[i]->check_name)) { kcc->check = kyc_checks[i]; kcc->prog_name = measure->prog_name; kcc->context = measure->context; return GNUNET_OK; } GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Check `%s' unknown (but required by measure %s)\n", measure->check_name, measure_name); return GNUNET_SYSERR; } enum GNUNET_GenericReturnValue TALER_KYCLOGIC_lookup_logic ( const char *name, struct TALER_KYCLOGIC_Plugin **plugin, struct TALER_KYCLOGIC_ProviderDetails **pd, const char **provider_name) { for (unsigned int i = 0; iprovider_name)) continue; *plugin = kp->logic; *pd = kp->pd; *provider_name = kp->provider_name; return GNUNET_OK; } for (unsigned int i = 0; iname, name)) continue; *plugin = logic; *pd = NULL; *provider_name = NULL; return GNUNET_OK; } GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Provider `%s' unknown\n", name); return GNUNET_SYSERR; } void TALER_KYCLOGIC_kyc_get_details ( const char *logic_name, TALER_KYCLOGIC_DetailsCallback cb, void *cb_cls) { for (unsigned int i = 0; ilogic->name, logic_name)) continue; if (GNUNET_OK != cb (cb_cls, kp->pd, kp->logic->cls)) return; } } /** * Closure for check_amount(). */ struct KycTestContext { /** * Rule set we apply. */ const struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs; /** * Events we care about. */ enum TALER_KYCLOGIC_KycTriggerEvent event; /** * Total amount encountered so far, invalid if zero. */ struct TALER_Amount sum; /** * Set to the triggered rule. */ const struct TALER_KYCLOGIC_KycRule *triggered_rule; }; /** * Function called on each @a amount that was found to * be relevant for a KYC check. Evaluates the given * @a amount and @a date against all the applicable * rules in the legitimization rule set. * * @param cls our `struct KycTestContext *` * @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 check_amount ( void *cls, const struct TALER_Amount *amount, struct GNUNET_TIME_Absolute date) { struct KycTestContext *ktc = cls; struct GNUNET_TIME_Relative dur; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "KYC checking transaction amount %s from %s against %u rules\n", TALER_amount2s (amount), GNUNET_TIME_absolute2s (date), ktc->lrs->num_kyc_rules); dur = GNUNET_TIME_absolute_get_duration (date); if (GNUNET_OK != TALER_amount_is_valid (&ktc->sum)) ktc->sum = *amount; else GNUNET_assert (0 <= TALER_amount_add (&ktc->sum, &ktc->sum, amount)); for (unsigned int i=0; ilrs->num_kyc_rules; i++) { const struct TALER_KYCLOGIC_KycRule *rule = &ktc->lrs->kyc_rules[i]; if (ktc->event != rule->trigger) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Wrong event type (%d) for rule %u (%d)\n", (int) ktc->event, i, (int) rule->trigger); continue; /* wrong trigger event type */ } if (GNUNET_TIME_relative_cmp (dur, >, rule->timeframe)) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Out of time range for rule %u\n", i); continue; /* out of time range for rule */ } if (-1 == TALER_amount_cmp (&ktc->sum, &rule->threshold)) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Below threshold of %s for rule %u\n", TALER_amount2s (&rule->threshold), i); continue; /* sum < threshold */ } if ( (NULL != ktc->triggered_rule) && (1 == TALER_amount_cmp (&ktc->triggered_rule->threshold, &rule->threshold)) ) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Higher than threshold of already triggered rule\n"); continue; /* threshold of triggered_rule > rule */ } GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Remembering rule %s as triggered\n", rule->rule_name); ktc->triggered_rule = rule; } return GNUNET_OK; } enum GNUNET_DB_QueryStatus TALER_KYCLOGIC_kyc_test_required ( enum TALER_KYCLOGIC_KycTriggerEvent event, const struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs, TALER_KYCLOGIC_KycAmountIterator ai, void *ai_cls, const struct TALER_KYCLOGIC_KycRule **triggered_rule, struct TALER_Amount *next_threshold) { struct GNUNET_TIME_Relative range = GNUNET_TIME_UNIT_ZERO; enum GNUNET_DB_QueryStatus qs; bool have_threshold = false; memset (next_threshold, 0, sizeof (struct TALER_Amount)); if (NULL == lrs) lrs = &default_rules; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Testing %u KYC rules for trigger %d\n", lrs->num_kyc_rules, event); for (unsigned int i=0; inum_kyc_rules; i++) { const struct TALER_KYCLOGIC_KycRule *rule = &lrs->kyc_rules[i]; if (event != rule->trigger) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Rule %u is for a different trigger (%d/%d)\n", i, (int) event, (int) rule->trigger); continue; } if (have_threshold) { GNUNET_assert (GNUNET_OK == TALER_amount_max (next_threshold, next_threshold, &rule->threshold)); } else { *next_threshold = rule->threshold; have_threshold = true; } GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Matched rule %u with timeframe %s and threshold %s\n", i, GNUNET_TIME_relative2s (rule->timeframe, true), TALER_amount2s (&rule->threshold)); range = GNUNET_TIME_relative_max (range, rule->timeframe); } if (! have_threshold) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "No rules apply\n"); *triggered_rule = NULL; return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; } { struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get (); struct KycTestContext ktc = { .lrs = lrs, .event = event }; qs = ai (ai_cls, GNUNET_TIME_absolute_subtract (now, range), &check_amount, &ktc); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Triggered rule is %s\n", (NULL == ktc.triggered_rule) ? "NONE" : ktc.triggered_rule->rule_name); *triggered_rule = ktc.triggered_rule; } return qs; } json_t * TALER_KYCLOGIC_measure_to_requirement ( const char *check_name, const char *prog_name, const json_t *context, const struct TALER_AccountAccessTokenP *access_token, size_t offset, uint64_t legitimization_measure_row_id) { struct TALER_KYCLOGIC_KycCheck *kc; json_t *kri; struct TALER_KycMeasureAuthorizationHash shv; char *ids; char *xids; kc = find_check (check_name); if (NULL == kc) { GNUNET_break (0); return NULL; } GNUNET_assert (offset <= UINT32_MAX); TALER_kyc_measure_authorization_hash (access_token, legitimization_measure_row_id, (uint32_t) offset, &shv); switch (kc->type) { case TALER_KYCLOGIC_CT_INFO: return GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("form", "INFO"), GNUNET_JSON_pack_string ("description", kc->description), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_object_incref ("description_i18n", (json_t *) kc->description_i18n))); case TALER_KYCLOGIC_CT_FORM: GNUNET_assert (offset <= UINT_MAX); ids = GNUNET_STRINGS_data_to_string_alloc (&shv, sizeof (shv)); GNUNET_asprintf (&xids, "%s-%u-%llu", ids, (unsigned int) offset, (unsigned long long) legitimization_measure_row_id); GNUNET_free (ids); kri = GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("form", kc->details.form.name), GNUNET_JSON_pack_string ("id", xids), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_object_incref ("context", (json_t *) context)), GNUNET_JSON_pack_string ("description", kc->description), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_object_incref ("description_i18n", (json_t *) kc->description_i18n))); GNUNET_free (xids); return kri; case TALER_KYCLOGIC_CT_LINK: GNUNET_assert (offset <= UINT_MAX); ids = GNUNET_STRINGS_data_to_string_alloc (&shv, sizeof (shv)); GNUNET_asprintf (&xids, "%s-%u-%llu", ids, (unsigned int) offset, (unsigned long long) legitimization_measure_row_id); GNUNET_free (ids); kri = GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("form", "LINK"), GNUNET_JSON_pack_string ("id", xids), GNUNET_JSON_pack_string ("description", kc->description), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_object_incref ("description_i18n", (json_t *) kc->description_i18n))); GNUNET_free (xids); return kri; } GNUNET_break (0); /* invalid type */ return NULL; } void TALER_KYCLOGIC_get_measure_configuration ( json_t **proots, json_t **pprograms, json_t **pchecks) { json_t *roots; json_t *programs; json_t *checks; roots = json_object (); for (unsigned int i = 0; icheck_name), GNUNET_JSON_pack_string ("prog_name", m->prog_name), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_object_incref ("context", m->context))); GNUNET_assert (0 == json_object_set_new (roots, m->measure_name, jm)); } programs = json_object (); for (unsigned int i = 0; inum_required_contexts; j++) { const char *rc = ap->required_contexts[j]; GNUNET_assert (0 == json_array_append_new (ctx, json_string (rc))); } inp = json_array (); GNUNET_assert (NULL != inp); for (unsigned int j = 0; jnum_required_attributes; j++) { const char *ra = ap->required_attributes[j]; GNUNET_assert (0 == json_array_append_new (inp, json_string (ra))); } jp = GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("description", ap->description), GNUNET_JSON_pack_array_steal ("context", ctx), GNUNET_JSON_pack_array_steal ("inputs", inp)); GNUNET_assert (0 == json_object_set_new (programs, ap->program_name, jp)); } checks = json_object (); for (unsigned int i = 0; inum_requires; j++) { const char *ra = ck->requires[j]; GNUNET_assert (0 == json_array_append_new (requires, json_string (ra))); } outputs = json_array (); GNUNET_assert (NULL != outputs); for (unsigned int j = 0; jnum_outputs; j++) { const char *out = ck->outputs[j]; GNUNET_assert (0 == json_array_append_new (outputs, json_string (out))); } jc = GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("description", ck->description), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_object_incref ("description", ck->description_i18n)), GNUNET_JSON_pack_array_steal ("requires", requires), GNUNET_JSON_pack_array_steal ("outputs", outputs), GNUNET_JSON_pack_string ("fallback", ck->fallback)); GNUNET_assert (0 == json_object_set_new (checks, ck->check_name, jc)); } *proots = roots; *pprograms = programs; *pchecks = checks; } enum TALER_ErrorCode TALER_KYCLOGIC_select_measure ( const json_t *jmeasures, size_t measure_index, const char **check_name, const char **prog_name, const json_t **context) { const json_t *jmeasure_arr; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_array_const ("measures", &jmeasure_arr), GNUNET_JSON_spec_end () }; const json_t *jmeasure; struct GNUNET_JSON_Specification ispec[] = { GNUNET_JSON_spec_string ("check_name", check_name), GNUNET_JSON_spec_string ("prog_name", prog_name), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_object_const ("context", context), NULL), GNUNET_JSON_spec_end () }; *check_name = NULL; *prog_name = NULL; *context = NULL; if (GNUNET_OK != GNUNET_JSON_parse (jmeasures, spec, NULL, NULL)) { GNUNET_break (0); return TALER_EC_EXCHANGE_KYC_MEASURES_MALFORMED; } if (measure_index >= json_array_size (jmeasure_arr)) { GNUNET_break_op (0); return TALER_EC_EXCHANGE_KYC_MEASURE_INDEX_INVALID; } jmeasure = json_array_get (jmeasure_arr, measure_index); if (GNUNET_OK != GNUNET_JSON_parse (jmeasure, ispec, NULL, NULL)) { GNUNET_break (0); return TALER_EC_EXCHANGE_KYC_MEASURES_MALFORMED; } return TALER_EC_NONE; } enum TALER_ErrorCode TALER_KYCLOGIC_check_form ( const json_t *jmeasures, size_t measure_index, const json_t *form_data, const char **error_message) { const char *check_name; const char *prog_name; const json_t *context; struct TALER_KYCLOGIC_KycCheck *kc; struct TALER_KYCLOGIC_AmlProgram *prog; *error_message = NULL; if (TALER_EC_NONE != TALER_KYCLOGIC_select_measure (jmeasures, measure_index, &check_name, &prog_name, &context)) { GNUNET_break_op (0); return TALER_EC_EXCHANGE_KYC_MEASURE_INDEX_INVALID; } kc = find_check (check_name); if (NULL == kc) { GNUNET_break (0); *error_message = check_name; return TALER_EC_EXCHANGE_KYC_GENERIC_CHECK_GONE; } if (TALER_KYCLOGIC_CT_FORM != kc->type) { GNUNET_break_op (0); return TALER_EC_EXCHANGE_KYC_NOT_A_FORM; } prog = find_program (prog_name); if (NULL == prog) { GNUNET_break (0); *error_message = prog_name; return TALER_EC_EXCHANGE_KYC_GENERIC_AML_PROGRAM_GONE; } for (unsigned int i = 0; inum_required_attributes; i++) { const char *rattr = prog->required_attributes[i]; if (NULL == json_object_get (form_data, rattr)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Form data lacks required attribute `%s' for AML program %s\n", rattr, prog_name); *error_message = rattr; return TALER_EC_EXCHANGE_KYC_AML_FORM_INCOMPLETE; } } return TALER_EC_NONE; } const struct TALER_KYCLOGIC_KycProvider * TALER_KYCLOGIC_check_to_provider (const char *check_name) { struct TALER_KYCLOGIC_KycCheck *kc; if (NULL == check_name) return NULL; if (0 == strcasecmp (check_name, "SKIP")) return NULL; kc = find_check (check_name); switch (kc->type) { case TALER_KYCLOGIC_CT_FORM: case TALER_KYCLOGIC_CT_INFO: return NULL; case TALER_KYCLOGIC_CT_LINK: break; } return kc->details.link.provider; } struct TALER_KYCLOGIC_AmlProgramRunnerHandle { /** * Function to call back with the result. */ TALER_KYCLOGIC_AmlProgramResultCallback aprc; /** * Closure for @e aprc. */ void *aprc_cls; /** * Handle to an external process. */ struct TALER_JSON_ExternalConversion *proc; /** * AML program to turn. */ const struct TALER_KYCLOGIC_AmlProgram *program; /** * Task to return @e apr result asynchronously. */ struct GNUNET_SCHEDULER_Task *async_cb; /** * Result returned to the client. */ struct TALER_KYCLOGIC_AmlProgramResult apr; }; /** * Function that that receives a JSON @a result from * the AML program. * * @param cls closure of type `struct TALER_KYCLOGIC_AmlProgramRunnerHandle` * @param status_type how did the process die * @param code termination status code from the process, * non-zero if AML checks are required next * @param result some JSON result, NULL if we failed to get an JSON output */ static void handle_aml_output ( void *cls, enum GNUNET_OS_ProcessStatusType status_type, unsigned long code, const json_t *result) { struct TALER_KYCLOGIC_AmlProgramRunnerHandle *aprh = cls; const char *fallback_measure = aprh->program->fallback; struct TALER_KYCLOGIC_AmlProgramResult *apr = &aprh->apr; const char **evs = NULL; aprh->proc = NULL; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "AML program output is:\n"); json_dumpf (result, stderr, JSON_INDENT (2)); memset (apr, 0, sizeof (*apr)); if ( (GNUNET_OS_PROCESS_EXITED != status_type) || (0 != code) ) { apr->status = TALER_KYCLOGIC_AMLR_FAILURE; apr->details.failure.fallback_measure = fallback_measure; apr->details.failure.error_message = "AML program returned non-zero exit code"; apr->details.failure.ec = TALER_EC_EXCHANGE_KYC_AML_PROGRAM_FAILURE; goto ready; } { const json_t *jevents = NULL; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_bool ( "to_investigate", &apr->details.success.to_investigate), NULL), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_object_const ( "properties", &apr->details.success.account_properties), NULL), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_array_const ( "events", &jevents), NULL), GNUNET_JSON_spec_object_const ( "new_rules", &apr->details.success.new_rules), GNUNET_JSON_spec_end () }; const char *err; unsigned int line; if (GNUNET_OK != GNUNET_JSON_parse (result, spec, &err, &line)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "AML program output is malformed at `%s'\n", err); json_dumpf (result, stderr, JSON_INDENT (2)); apr->status = TALER_KYCLOGIC_AMLR_FAILURE; apr->details.failure.fallback_measure = fallback_measure; apr->details.failure.error_message = err; apr->details.failure.ec = TALER_EC_EXCHANGE_KYC_AML_PROGRAM_MALFORMED_RESULT; goto ready; } apr->details.success.num_events = json_array_size (jevents); GNUNET_assert (((size_t) apr->details.success.num_events) == json_array_size (jevents)); evs = GNUNET_new_array ( apr->details.success.num_events, const char *); for (unsigned int i = 0; idetails.success.num_events; i++) { evs[i] = json_string_value ( json_array_get (jevents, i)); if (NULL == evs[i]) { apr->status = TALER_KYCLOGIC_AMLR_FAILURE; apr->details.failure.fallback_measure = fallback_measure; apr->details.failure.error_message = "events"; apr->details.failure.ec = TALER_EC_EXCHANGE_KYC_AML_PROGRAM_MALFORMED_RESULT; goto ready; } } apr->status = TALER_KYCLOGIC_AMLR_SUCCESS; apr->details.success.events = evs; { /* check new_rules */ struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs; lrs = TALER_KYCLOGIC_rules_parse ( apr->details.success.new_rules); if (NULL == lrs) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "AML program output is malformed at `%s'\n", "new_rules"); apr->status = TALER_KYCLOGIC_AMLR_FAILURE; apr->details.failure.fallback_measure = fallback_measure; apr->details.failure.error_message = "new_rules"; apr->details.failure.ec = TALER_EC_EXCHANGE_KYC_AML_PROGRAM_MALFORMED_RESULT; goto ready; } TALER_KYCLOGIC_rules_free (lrs); } } ready: aprh->aprc (aprh->aprc_cls, &aprh->apr); GNUNET_free (evs); TALER_KYCLOGIC_run_aml_program_cancel (aprh); } /** * Helper function to asynchronously return * the result. * * @param[in] cls a `struct TALER_KYCLOGIC_AmlProgramRunnerHandle` to return results for */ static void async_return_task (void *cls) { struct TALER_KYCLOGIC_AmlProgramRunnerHandle *aprh = cls; aprh->async_cb = NULL; aprh->aprc (aprh->aprc_cls, &aprh->apr); TALER_KYCLOGIC_run_aml_program_cancel (aprh); } struct TALER_KYCLOGIC_AmlProgramRunnerHandle * TALER_KYCLOGIC_run_aml_program ( const json_t *attributes, const json_t *aml_history, const json_t *kyc_history, const json_t *jmeasures, unsigned int measure_index, TALER_KYCLOGIC_AmlProgramResultCallback aprc, void *aprc_cls) { const json_t *context; const char *check_name; const char *prog_name; { enum TALER_ErrorCode ec; ec = TALER_KYCLOGIC_select_measure (jmeasures, measure_index, &check_name, &prog_name, &context); if (TALER_EC_NONE != ec) { GNUNET_break (0); return NULL; } } return TALER_KYCLOGIC_run_aml_program2 (prog_name, attributes, aml_history, kyc_history, context, aprc, aprc_cls); } struct TALER_KYCLOGIC_AmlProgramRunnerHandle * TALER_KYCLOGIC_run_aml_program2 ( const char *prog_name, const json_t *attributes, const json_t *aml_history, const json_t *kyc_history, const json_t *context, TALER_KYCLOGIC_AmlProgramResultCallback aprc, void *aprc_cls) { struct TALER_KYCLOGIC_AmlProgramRunnerHandle *aprh; struct TALER_KYCLOGIC_AmlProgram *prog; prog = find_program (prog_name); if (NULL == prog) { GNUNET_break (0); return NULL; } aprh = GNUNET_new (struct TALER_KYCLOGIC_AmlProgramRunnerHandle); aprh->aprc = aprc; aprh->aprc_cls = aprc_cls; aprh->program = prog; for (unsigned int i = 0; inum_required_attributes; i++) { const char *rattr = prog->required_attributes[i]; if (NULL == json_object_get (attributes, rattr)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "KYC attributes lack required attribute `%s' for AML program %s\n", rattr, prog->program_name); json_dumpf (attributes, stderr, JSON_INDENT (2)); aprh->apr.status = TALER_KYCLOGIC_AMLR_FAILURE; aprh->apr.details.failure.fallback_measure = prog->fallback; aprh->apr.details.failure.error_message = rattr; aprh->apr.details.failure.ec = TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_INCOMPLETE_REPLY; aprh->async_cb = GNUNET_SCHEDULER_add_now (&async_return_task, aprh); return aprh; } } for (unsigned int i = 0; inum_required_contexts; i++) { const char *rctx = prog->required_contexts[i]; if (NULL == json_object_get (context, rctx)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Context lacks required field `%s' for AML program %s\n", rctx, prog->program_name); json_dumpf (context, stderr, JSON_INDENT (2)); aprh->apr.status = TALER_KYCLOGIC_AMLR_FAILURE; aprh->apr.details.failure.fallback_measure = prog->fallback; aprh->apr.details.failure.error_message = rctx; aprh->apr.details.failure.ec = TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_INCOMPLETE_CONTEXT; aprh->async_cb = GNUNET_SCHEDULER_add_now (&async_return_task, aprh); return aprh; } } { json_t *input; const char *extra_args[] = { "-c", cfg_filename, NULL, }; char **args; input = GNUNET_JSON_PACK ( GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_object_incref ("context", (json_t *) context)), GNUNET_JSON_pack_object_incref ("attributes", (json_t *) attributes), GNUNET_JSON_pack_array_incref ("aml_history", (json_t *) aml_history), GNUNET_JSON_pack_array_incref ("kyc_history", (json_t *) kyc_history) ); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Running AML program %s\n", prog->command); args = split_words (prog->command, extra_args); GNUNET_assert (NULL != args); GNUNET_assert (NULL != args[0]); json_dumpf (input, stderr, JSON_INDENT (2)); aprh->proc = TALER_JSON_external_conversion_start ( input, &handle_aml_output, aprh, args[0], (const char **) args); destroy_words (args); json_decref (input); } return aprh; } struct TALER_KYCLOGIC_AmlProgramRunnerHandle * TALER_KYCLOGIC_run_aml_program3 ( const struct TALER_KYCLOGIC_Measure *measure, const json_t *attributes, const json_t *aml_history, const json_t *kyc_history, TALER_KYCLOGIC_AmlProgramResultCallback aprc, void *aprc_cls) { return TALER_KYCLOGIC_run_aml_program2 ( measure->prog_name, attributes, aml_history, kyc_history, measure->context, aprc, aprc_cls); } void TALER_KYCLOGIC_run_aml_program_cancel ( struct TALER_KYCLOGIC_AmlProgramRunnerHandle *aprh) { if (NULL != aprh->proc) { TALER_JSON_external_conversion_stop (aprh->proc); aprh->proc = NULL; } if (NULL != aprh->async_cb) { GNUNET_SCHEDULER_cancel (aprh->async_cb); aprh->async_cb = NULL; } GNUNET_free (aprh); } json_t * TALER_KYCLOGIC_get_hard_limits () { const struct TALER_KYCLOGIC_KycRule *rules = default_rules.kyc_rules; unsigned int num_rules = default_rules.num_kyc_rules; json_t *hard_limits; hard_limits = json_array (); GNUNET_assert (NULL != hard_limits); for (unsigned int i = 0; iverboten) continue; if (! rule->exposed) continue; hard_limit = GNUNET_JSON_PACK ( TALER_JSON_pack_kycte ("operation_type", rule->trigger), GNUNET_JSON_pack_time_rel ("timeframe", rule->timeframe), TALER_JSON_pack_amount ("threshold", &rule->threshold) ); GNUNET_assert (0 == json_array_append_new (hard_limits, hard_limit)); } return hard_limits; } json_t * TALER_KYCLOGIC_get_zero_limits () { const struct TALER_KYCLOGIC_KycRule *rules = default_rules.kyc_rules; unsigned int num_rules = default_rules.num_kyc_rules; json_t *zero_limits; zero_limits = json_array (); GNUNET_assert (NULL != zero_limits); for (unsigned int i = 0; iexposed) continue; if (rule->verboten) continue; /* see: hard_limits */ if (! TALER_amount_is_zero (&rule->threshold)) continue; zero_limit = GNUNET_JSON_PACK ( TALER_JSON_pack_kycte ("operation_type", rule->trigger)); GNUNET_assert (0 == json_array_append_new (zero_limits, zero_limit)); } return zero_limits; } /* end of kyclogic_api.c */