exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

taler-exchange-sanctionscheck.c (26899B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2025 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU Affero General Public License as published by the Free Software
      7   Foundation; either version 3, or (at your option) any later version.
      8 
      9   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
     10   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11   A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details.
     12 
     13   You should have received a copy of the GNU Affero General Public License along with
     14   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 /**
     17  * @file taler-exchange-sanctionscheck.c
     18  * @brief Process that checks all existing customers against a sanctions list
     19  * @author Christian Grothoff
     20  */
     21 #include "platform.h"
     22 #include <gnunet/gnunet_util_lib.h>
     23 #include <jansson.h>
     24 #include <pthread.h>
     25 #include <microhttpd.h>
     26 #include "taler/taler_dbevents.h"
     27 #include "exchangedb_lib.h"
     28 #include "taler/taler_json_lib.h"
     29 #include "taler/taler_kyclogic_lib.h"
     30 #include <math.h>
     31 #include "exchange-database/release_revolving_shard.h"
     32 #include "exchange-database/commit.h"
     33 #include "exchange-database/preflight.h"
     34 #include "exchange-database/insert_sanction_list_hit.h"
     35 #include "exchange-database/start.h"
     36 #include "exchange-database/rollback.h"
     37 #include "exchange-database/iterate_all_kyc_attributes.h"
     38 #include "exchange-database/event_listen.h"
     39 #include "exchange-database/event_listen_cancel.h"
     40 #include "exchange-database/start.h"
     41 
     42 /**
     43  * Account we are currently checking.
     44  */
     45 struct Account
     46 {
     47   /**
     48    * Kept in a DLL.
     49    */
     50   struct Account *next;
     51 
     52   /**
     53    * Kept in a DLL.
     54    */
     55   struct Account *prev;
     56 
     57   /**
     58    * Original properties of the account.
     59    */
     60   json_t *properties;
     61 
     62   /**
     63    * Evaluation entry with the sanction list checker.
     64    */
     65   struct TALER_KYCLOGIC_EvaluationEntry *ee;
     66 
     67   /**
     68    * Row of the attributes in the kyc_attributes table.
     69    */
     70   uint64_t row_id;
     71 
     72   /**
     73    * Hash of the normalized payto:// URI of the account.
     74    */
     75   struct TALER_NormalizedPaytoHashP h_payto;
     76 
     77 };
     78 
     79 
     80 /**
     81  * The exchange's configuration (global)
     82  */
     83 static const struct GNUNET_CONFIGURATION_Handle *cfg;
     84 
     85 /**
     86  * Our DB plugin.
     87  */
     88 static struct TALER_EXCHANGEDB_PostgresContext *pg;
     89 
     90 /**
     91  * Helper process for the actual rating.
     92  */
     93 static struct TALER_KYCLOGIC_SanctionRater *sr;
     94 
     95 /**
     96  * Value to return from main(). 0 on success, non-zero on
     97  * on serious errors.
     98  */
     99 static int global_ret;
    100 
    101 /**
    102  * Key used to encrypt KYC attribute data in our database.
    103  */
    104 static struct TALER_AttributeEncryptionKeyP attribute_key;
    105 
    106 /**
    107  * Account rules to set if we have a good match against the
    108  * sanction list and should freeze an account immediately.
    109  */
    110 static json_t *freeze_rules;
    111 
    112 /**
    113  * Head of DLL of accounts we are currently checking.
    114  */
    115 static struct Account *acc_head;
    116 
    117 /**
    118  * Tail of DLL of accounts we are currently checking.
    119  */
    120 static struct Account *acc_tail;
    121 
    122 /**
    123  * Minimum row ID to process records from.  This is the value we persist,
    124  * and it is only advanced once the transaction that evaluated the
    125  * respective rows was committed.
    126  */
    127 static uint64_t min_row_id;
    128 
    129 /**
    130  * Highest row ID evaluated in the current transaction.  Folded into
    131  * #min_row_id when that transaction commits.
    132  */
    133 static uint64_t max_row_id;
    134 
    135 /**
    136  * File descriptor with the name of the file where we track our
    137  * progress.
    138  */
    139 static int min_row_fd = -1;
    140 
    141 /**
    142  * '-t' command line flag. Disables background processing mode.
    143  */
    144 static int testmode;
    145 
    146 /**
    147  * '-r' command line flag. Restarts analysis from scratch (for
    148  * fresh sanction list).
    149  */
    150 static int reset;
    151 
    152 /**
    153  * '-n' command line flag. Do not actually run, only reset.
    154  */
    155 static int norun;
    156 
    157 /**
    158  * Handler to learn about updated KYC attributes.
    159  */
    160 static struct GNUNET_DB_EventHandler *eh;
    161 
    162 /**
    163  * Set to true if we should restart immediately after
    164  * finishing the current transaction.
    165  */
    166 static bool restart_now;
    167 
    168 /**
    169  * Set to true while we are in a database transaction iterating
    170  * over KYC attributes.
    171  */
    172 static bool in_transaction;
    173 
    174 /**
    175  * Match quality needed for instantly freezing an account.
    176  */
    177 static float freeze_rating_limit = 0.9;
    178 
    179 /**
    180  * Match confidence needed for instantly freezing an account.
    181  */
    182 static float freeze_confidence_limit = 0.9;
    183 
    184 /**
    185  * Threshold on rating/confidence that must be exceeded to begin an
    186  * investigation (when the account is not automatically frozen).  A low
    187  * confidence relative to the rating raises the ratio, which is intentional:
    188  * the less sure the automated match is, the more a human should look.
    189  */
    190 static float investigation_limit = 0.9;
    191 
    192 /**
    193  * Minimum match confidence for an investigation.  This gates the ratio test
    194  * above so that near-zero-confidence noise (e.g. a fuzzy match against an
    195  * attribute that is not a name at all) does not, via its large ratio, flag
    196  * every account.  Optional; defaults to 0 to preserve the historic behaviour
    197  * of having no confidence floor.
    198  */
    199 static float investigation_confidence_limit = 0.0;
    200 
    201 /**
    202  * Write @a min_row_id to @a min_row_fd.
    203  */
    204 static void
    205 sync_row (void)
    206 {
    207   uint64_t r = GNUNET_htonll (min_row_id);
    208   ssize_t rval;
    209 
    210   GNUNET_assert (-1 != min_row_fd);
    211   GNUNET_break (0 == lseek (min_row_fd,
    212                             0,
    213                             SEEK_SET));
    214   rval = write (min_row_fd,
    215                 &r,
    216                 sizeof (r));
    217   if (rval != (ssize_t) sizeof (r))
    218   {
    219     GNUNET_break (-1 == rval);
    220     GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
    221                          "write");
    222   }
    223   GNUNET_break (0 ==
    224                 fsync (min_row_fd));
    225 }
    226 
    227 
    228 /**
    229  * We're being aborted with CTRL-C (or SIGTERM). Shut down.
    230  *
    231  * @param cls closure
    232  */
    233 static void
    234 shutdown_task (void *cls)
    235 {
    236   struct Account *acc;
    237 
    238   (void) cls;
    239   if (-1 != min_row_fd)
    240   {
    241     sync_row ();
    242     GNUNET_break (0 == close (min_row_fd));
    243     min_row_fd = -1;
    244   }
    245   while (NULL != (acc = acc_head))
    246   {
    247     GNUNET_CONTAINER_DLL_remove (acc_head,
    248                                  acc_tail,
    249                                  acc);
    250     json_decref (acc->properties);
    251     GNUNET_free (acc);
    252   }
    253   if (NULL != eh)
    254   {
    255     TALER_EXCHANGEDB_event_listen_cancel (pg,
    256                                           eh);
    257     eh = NULL;
    258   }
    259   if (NULL != sr)
    260   {
    261     TALER_KYCLOGIC_sanction_rater_stop (sr);
    262     sr = NULL;
    263   }
    264   TALER_EXCHANGEDB_disconnect (pg);
    265   pg = NULL;
    266   cfg = NULL;
    267 }
    268 
    269 
    270 /**
    271  * Convert double value @a d to numeric score.
    272  *
    273  * @param d score from [0,1]
    274  * @return value from 0 to 1 billion proportional to @a d
    275  */
    276 static uint64_t
    277 double_to_billion (double d)
    278 {
    279   double r;
    280 
    281   GNUNET_break (d >= 0);
    282   GNUNET_break (d <= 1.0);
    283   r = round (d * 1000000000);
    284   return (uint64_t) r;
    285 }
    286 
    287 
    288 /**
    289  * Start the actual database transaction.
    290  */
    291 static void
    292 begin_transaction (void);
    293 
    294 
    295 /**
    296  * Function called with the result of a sanction evaluation.
    297  *
    298  * @param cls closure
    299  * @param ec error code, #TALER_EC_NONE on success
    300  * @param best_match identifies the sanction list entry with the best match
    301  * @param rating likelihood of the match, from 0 (none) to 1 (perfect)
    302  * @param confidence confidence in the evaluation, from 0 (none) to 1 (perfect)
    303  */
    304 static void
    305 sanction_cb (void *cls,
    306              enum TALER_ErrorCode ec,
    307              const char *best_match,
    308              double rating,
    309              double confidence)
    310 {
    311   struct Account *acc = cls;
    312   bool freeze = false;
    313   bool investigate = false;
    314 
    315   if (TALER_EC_NONE != ec)
    316   {
    317     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    318                 "Error %s (%d) when analyzing record\n",
    319                 TALER_ErrorCode_get_hint (ec),
    320                 (int) ec);
    321     TALER_EXCHANGEDB_rollback (pg);
    322     global_ret = 1;
    323     GNUNET_SCHEDULER_shutdown ();
    324     return;
    325   }
    326   if ( (rating > (double) freeze_rating_limit) &&
    327        (confidence > (double) freeze_confidence_limit) )
    328   {
    329     freeze = true;
    330     investigate = true;
    331   }
    332   else if ( (rating > (double) investigation_limit * confidence) &&
    333             (confidence > (double) investigation_confidence_limit) )
    334   {
    335     investigate = true;
    336   }
    337   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    338               "Best match is %f/%f at `%s' will %s\n",
    339               rating,
    340               confidence,
    341               best_match,
    342               freeze || investigate
    343               ? (freeze
    344                  ? "freeze"
    345                  : "investigate")
    346               : "do nothing");
    347   if (freeze || investigate)
    348   {
    349     static const char *freeze_event[] = {
    350       "sanction-list-hit-account-frozen",
    351     };
    352     static const char *partial_match_event[] = {
    353       "sanction-list-hit-partial-account-investigated",
    354     };
    355     enum GNUNET_DB_QueryStatus qs;
    356     json_t *properties;
    357     const char **events;
    358     json_t *new_rules;
    359 
    360     new_rules = freeze ? freeze_rules : NULL;
    361     events = freeze ? freeze_event : partial_match_event;
    362     properties = GNUNET_JSON_PACK (
    363       GNUNET_JSON_pack_string ("SANCTION_LIST_BEST_MATCH",
    364                                best_match),
    365       GNUNET_JSON_pack_uint64 ("SANCTION_LIST_RATING",
    366                                double_to_billion (rating)),
    367       GNUNET_JSON_pack_uint64 ("SANCTION_LIST_CONFIDENCE",
    368                                double_to_billion (confidence)),
    369       GNUNET_JSON_pack_string ("AML_INVESTIGATION_STATE",
    370                                "INVESTIGATION_PENDING"),
    371       GNUNET_JSON_pack_string ("AML_INVESTIGATION_TIGGER",
    372                                "SANCTION_LIST_MATCH"));
    373     if (NULL != acc->properties)
    374       GNUNET_assert (0 ==
    375                      json_object_update_missing (properties,
    376                                                  acc->properties));
    377     qs = TALER_EXCHANGEDB_insert_sanction_list_hit (pg,
    378                                                     &acc->h_payto,
    379                                                     investigate,
    380                                                     new_rules,
    381                                                     properties,
    382                                                     1,
    383                                                     events);
    384     json_decref (properties);
    385     if (qs < 0)
    386     {
    387       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    388                   "Failed to insert sanction list evaluation result\n");
    389       global_ret = EXIT_FAILURE;
    390       GNUNET_SCHEDULER_shutdown ();
    391       return;
    392     }
    393   }
    394   GNUNET_CONTAINER_DLL_remove (acc_head,
    395                                acc_tail,
    396                                acc);
    397   json_decref (acc->properties);
    398   /* Evaluations complete out of order, so keep the highest row we did. */
    399   max_row_id = GNUNET_MAX (max_row_id,
    400                            acc->row_id);
    401   GNUNET_free (acc);
    402   if (NULL != acc_head)
    403     return; /* more work */
    404   {
    405     enum GNUNET_DB_QueryStatus qs;
    406 
    407     qs = TALER_EXCHANGEDB_commit (pg);
    408     if (qs < 0)
    409     {
    410       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    411                   "Failed to commit DB transaction\n");
    412       global_ret = EXIT_NOTCONFIGURED;
    413       GNUNET_SCHEDULER_shutdown ();
    414       return;
    415     }
    416   }
    417   in_transaction = false;
    418   /* Our writes are durable now, so the resume point may move. */
    419   min_row_id = max_row_id;
    420   sync_row ();
    421   if (restart_now)
    422   {
    423     begin_transaction ();
    424     return;
    425   }
    426   if (testmode)
    427     GNUNET_SCHEDULER_shutdown ();
    428 }
    429 
    430 
    431 /**
    432  * Function called on each account.
    433  *
    434  * @param cls closure
    435  * @param row_id row of the attributes in the database
    436  * @param h_payto account for which the attribute data is stored
    437  * @param provider_name provider that must be checked
    438  * @param collection_time when was the data collected
    439  * @param expiration_time when does the data expire
    440  * @param properties properties that were set for @a h_payto
    441  * @param enc_attributes_size number of bytes in @a enc_attributes
    442  * @param enc_attributes encrypted attribute data
    443  * @return true to continue to iterate
    444  */
    445 static bool
    446 account_cb (void *cls,
    447             uint64_t row_id,
    448             const struct TALER_NormalizedPaytoHashP *h_payto,
    449             const char *provider_name,
    450             struct GNUNET_TIME_Timestamp collection_time,
    451             struct GNUNET_TIME_Timestamp expiration_time,
    452             const json_t *properties,
    453             size_t enc_attributes_size,
    454             const void *enc_attributes)
    455 {
    456   json_t *attributes;
    457   struct Account *acc;
    458 
    459   (void) cls;
    460   if (json_boolean_value (json_object_get (properties,
    461                                            "SANCTION_LIST_SUPPRESS")))
    462   {
    463     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    464                 "Skipping %llu as suppressed by staff as false-positive\n",
    465                 (unsigned long long) row_id);
    466     return true;
    467   }
    468   attributes = TALER_CRYPTO_kyc_attributes_decrypt (&attribute_key,
    469                                                     enc_attributes,
    470                                                     enc_attributes_size);
    471   if (NULL == attributes)
    472   {
    473     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    474                 "Failed to decrypt attributes at row #%llu\n",
    475                 (unsigned long long) row_id);
    476     return true;
    477   }
    478   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    479               "Found KYC data %llu\n",
    480               (unsigned long long) row_id);
    481 #if DEBUG
    482   json_dumpf (attributes,
    483               stderr,
    484               JSON_INDENT (2));
    485 #endif
    486   acc = GNUNET_new (struct Account);
    487   acc->row_id = row_id;
    488   acc->h_payto = *h_payto;
    489   acc->properties = json_incref ((json_t *) properties);
    490   acc->ee = TALER_KYCLOGIC_sanction_rater_eval (sr,
    491                                                 attributes,
    492                                                 &sanction_cb,
    493                                                 acc);
    494   if (NULL == acc->ee)
    495   {
    496     json_decref (acc->properties);
    497     GNUNET_free (acc);
    498     return false;
    499   }
    500   GNUNET_CONTAINER_DLL_insert (acc_head,
    501                                acc_tail,
    502                                acc);
    503   return true;
    504 }
    505 
    506 
    507 /**
    508  * Initialize JSON rules for freezing an account.
    509  *
    510  * @return true on success
    511  */
    512 static bool
    513 init_freeze (void)
    514 {
    515   char *currency;
    516   struct TALER_Amount zero;
    517   json_t *rules;
    518   json_t *verboten;
    519 
    520   if (GNUNET_OK !=
    521       TALER_config_get_currency (cfg,
    522                                  "exchange",
    523                                  &currency))
    524   {
    525     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    526                                "exchange",
    527                                "currency");
    528     return false;
    529   }
    530   GNUNET_assert (GNUNET_OK ==
    531                  TALER_amount_set_zero (currency,
    532                                         &zero));
    533   verboten = json_array ();
    534   GNUNET_assert (NULL != verboten);
    535   GNUNET_assert (0 ==
    536                  json_array_append_new (verboten,
    537                                         json_string ("verboten")));
    538   rules = json_array ();
    539   GNUNET_assert (NULL != rules);
    540   for (enum TALER_KYCLOGIC_KycTriggerEvent et =
    541          TALER_KYCLOGIC_KYC_TRIGGER_WITHDRAW;
    542        et <= TALER_KYCLOGIC_KYC_TRIGGER_REFUND;
    543        et++)
    544   {
    545     json_t *rule;
    546 
    547     rule = GNUNET_JSON_PACK (
    548       TALER_JSON_pack_kycte ("operation_type",
    549                              et),
    550       TALER_JSON_pack_amount ("threshold",
    551                               &zero),
    552       GNUNET_JSON_pack_time_rel ("timeframe",
    553                                  GNUNET_TIME_UNIT_YEARS),
    554       GNUNET_JSON_pack_array_incref ("measures",
    555                                      verboten),
    556       GNUNET_JSON_pack_uint64 ("display_priority",
    557                                1),
    558       GNUNET_JSON_pack_bool ("exposed",
    559                              false),
    560       GNUNET_JSON_pack_bool ("is_and_combinator",
    561                              false));
    562     GNUNET_assert (0 ==
    563                    json_array_append_new (rules,
    564                                           rule));
    565   }
    566   json_decref (verboten);
    567   freeze_rules =
    568     GNUNET_JSON_PACK (
    569       GNUNET_JSON_pack_timestamp ("expiration_time",
    570                                   GNUNET_TIME_UNIT_FOREVER_TS),
    571       GNUNET_JSON_pack_array_steal ("rules",
    572                                     rules));
    573   return true;
    574 }
    575 
    576 
    577 static void
    578 begin_transaction ()
    579 {
    580   enum GNUNET_DB_QueryStatus qs;
    581 
    582   restart_now = false;
    583   max_row_id = min_row_id;
    584   GNUNET_assert (! in_transaction);
    585   if (GNUNET_OK !=
    586       TALER_EXCHANGEDB_start (pg,
    587                               "sanctionscheck"))
    588   {
    589     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    590                 "Failed to begin DB transaction\n");
    591     global_ret = EXIT_NOTCONFIGURED;
    592     GNUNET_SCHEDULER_shutdown ();
    593     return;
    594   }
    595   in_transaction = true;
    596   /* FIXME-10063: we may want to eventually limit the number of
    597      records we process in a single transaction. */
    598   qs = TALER_EXCHANGEDB_iterate_all_kyc_attributes (pg,
    599                                                     min_row_id,
    600                                                     &account_cb,
    601                                                     NULL);
    602   if (qs < 0)
    603   {
    604     global_ret = EXIT_FAILURE;
    605     GNUNET_break (0);
    606     GNUNET_SCHEDULER_shutdown ();
    607     return;
    608   }
    609   if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    610   {
    611     TALER_EXCHANGEDB_rollback (pg);
    612     if (testmode)
    613       GNUNET_SCHEDULER_shutdown ();
    614     return;
    615   }
    616   if (NULL == acc_head)
    617     in_transaction = false;
    618   if ( (NULL == acc_head) &&
    619        (testmode) )
    620   {
    621     /* no work, not incremental, we are done */
    622     GNUNET_SCHEDULER_shutdown ();
    623   }
    624 }
    625 
    626 
    627 /**
    628  * Function called on new KYC attributes being available in Postgres.
    629  *
    630  * @param cls closure
    631  * @param extra additional event data provided
    632  * @param extra_size number of bytes in @a extra
    633  */
    634 static void
    635 db_event_cb (void *cls,
    636              const void *extra,
    637              size_t extra_size)
    638 {
    639   GNUNET_break (NULL == cls);
    640   (void) extra;
    641   (void) extra_size;
    642   if (in_transaction)
    643     restart_now = true;
    644   else
    645     begin_transaction ();
    646 }
    647 
    648 
    649 /**
    650  * First task.
    651  *
    652  * @param cls closure, NULL
    653  * @param args remaining command-line arguments
    654  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
    655  * @param c configuration
    656  */
    657 static void
    658 run (void *cls,
    659      char *const *args,
    660      const char *cfgfile,
    661      const struct GNUNET_CONFIGURATION_Handle *c)
    662 {
    663   (void) cls;
    664   (void) cfgfile;
    665   cfg = c;
    666   if (GNUNET_OK !=
    667       GNUNET_CONFIGURATION_get_value_float (cfg,
    668                                             "exchange-sanctionscheck",
    669                                             "FREEZE_RATING_LIMIT",
    670                                             &freeze_rating_limit))
    671   {
    672     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    673                                "exchange-sanctionscheck",
    674                                "FREEZE_RATING_LIMIT");
    675     global_ret = EXIT_NOTCONFIGURED;
    676     return;
    677   }
    678   if (GNUNET_OK !=
    679       GNUNET_CONFIGURATION_get_value_float (cfg,
    680                                             "exchange-sanctionscheck",
    681                                             "FREEZE_CONFIDENCE_LIMIT",
    682                                             &freeze_confidence_limit))
    683   {
    684     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    685                                "exchange-sanctionscheck",
    686                                "FREEZE_CONFIDENCE_LIMIT");
    687     global_ret = EXIT_NOTCONFIGURED;
    688     return;
    689   }
    690   if (GNUNET_OK !=
    691       GNUNET_CONFIGURATION_get_value_float (cfg,
    692                                             "exchange-sanctionscheck",
    693                                             "INVESTIGATION_LIMIT",
    694                                             &investigation_limit))
    695   {
    696     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    697                                "exchange-sanctionscheck",
    698                                "INVESTIGATION_LIMIT");
    699     global_ret = EXIT_NOTCONFIGURED;
    700     return;
    701   }
    702   if (GNUNET_OK !=
    703       GNUNET_CONFIGURATION_get_value_float (cfg,
    704                                             "exchange-sanctionscheck",
    705                                             "INVESTIGATION_CONFIDENCE_LIMIT",
    706                                             &investigation_confidence_limit))
    707   {
    708     /* Optional; absence restores the historic no-floor behaviour.  It gates
    709        every investigation, so say which value is in force. */
    710     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    711                 "INVESTIGATION_CONFIDENCE_LIMIT not configured, using %f\n",
    712                 (double) investigation_confidence_limit);
    713   }
    714   if (! init_freeze ())
    715     return;
    716   {
    717     char *attr_enc_key_str;
    718 
    719     if (GNUNET_OK !=
    720         GNUNET_CONFIGURATION_get_value_string (cfg,
    721                                                "exchange",
    722                                                "ATTRIBUTE_ENCRYPTION_KEY",
    723                                                &attr_enc_key_str))
    724     {
    725       GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    726                                  "exchange",
    727                                  "ATTRIBUTE_ENCRYPTION_KEY");
    728       global_ret = EXIT_NOTCONFIGURED;
    729       return;
    730     }
    731     GNUNET_CRYPTO_hash (attr_enc_key_str,
    732                         strlen (attr_enc_key_str),
    733                         &attribute_key.hash);
    734     GNUNET_free (attr_enc_key_str);
    735   }
    736   if (NULL ==
    737       (pg = TALER_EXCHANGEDB_connect (cfg)))
    738   {
    739     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    740                 "Failed to initialize DB subsystem\n");
    741     global_ret = EXIT_NOTCONFIGURED;
    742     return;
    743   }
    744   GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
    745                                  cls);
    746   {
    747     char *rater;
    748     char **sargv = NULL;
    749     unsigned int sargc = 0;
    750 
    751     if (GNUNET_OK !=
    752         GNUNET_CONFIGURATION_get_value_string (cfg,
    753                                                "exchange-sanctionscheck",
    754                                                "RATER_COMMAND",
    755                                                &rater))
    756     {
    757       GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    758                                  "exchange-sanctionscheck",
    759                                  "RATER_COMMAND");
    760       global_ret = EXIT_NOTCONFIGURED;
    761       return;
    762     }
    763     for (const char *tok = strtok (rater,
    764                                    " ");
    765          NULL != tok;
    766          tok = strtok (NULL,
    767                        " "))
    768     {
    769       char *arg = GNUNET_strdup (tok);
    770 
    771       GNUNET_array_append (sargv,
    772                            sargc,
    773                            arg);
    774     }
    775     GNUNET_free (rater);
    776     GNUNET_array_append (sargv,
    777                          sargc,
    778                          NULL);
    779     sr = TALER_KYCLOGIC_sanction_rater_start (sargv[0],
    780                                               sargv);
    781     for (unsigned int i = 0; i<sargc; i++)
    782       GNUNET_free (sargv[i]);
    783     GNUNET_array_grow (sargv,
    784                        sargc,
    785                        0);
    786     if (NULL == sr)
    787     {
    788       global_ret = EXIT_INVALIDARGUMENT;
    789       GNUNET_SCHEDULER_shutdown ();
    790       return;
    791     }
    792   }
    793   {
    794     char *min_row_fn;
    795     uint64_t r;
    796 
    797     if (GNUNET_OK !=
    798         GNUNET_CONFIGURATION_get_value_filename (cfg,
    799                                                  "exchange-sanctionscheck",
    800                                                  "MIN_ROW_FILENAME",
    801                                                  &min_row_fn))
    802     {
    803       GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    804                                  "exchange-sanctionscheck",
    805                                  "MIN_ROW_FILENAME");
    806       global_ret = EXIT_NOTCONFIGURED;
    807       GNUNET_SCHEDULER_shutdown ();
    808       return;
    809     }
    810     if (reset &&
    811         (0 != unlink (min_row_fn)) &&
    812         (ENOENT != errno) )
    813     {
    814       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
    815                                 "unlink",
    816                                 min_row_fn);
    817       GNUNET_free (min_row_fn);
    818       global_ret = EXIT_NOPERMISSION;
    819       GNUNET_SCHEDULER_shutdown ();
    820       return;
    821     }
    822     if (GNUNET_OK !=
    823         GNUNET_DISK_directory_create_for_file (min_row_fn))
    824     {
    825       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
    826                                 "open",
    827                                 min_row_fn);
    828       GNUNET_free (min_row_fn);
    829       global_ret = EXIT_NOPERMISSION;
    830       GNUNET_SCHEDULER_shutdown ();
    831       return;
    832     }
    833     min_row_fd = open (min_row_fn,
    834                        O_CREAT | O_RDWR,
    835                        S_IRUSR | S_IWUSR);
    836     if (-1 == min_row_fd)
    837     {
    838       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
    839                                 "open",
    840                                 min_row_fn);
    841       GNUNET_free (min_row_fn);
    842       global_ret = EXIT_NOTCONFIGURED;
    843       GNUNET_SCHEDULER_shutdown ();
    844       return;
    845     }
    846     if (sizeof (r) !=
    847         read (min_row_fd,
    848               &r,
    849               sizeof (r)))
    850     {
    851       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    852                   "Could not read starting row from `%s', will start from 0\n",
    853                   min_row_fn);
    854       min_row_id = 0;
    855     }
    856     else
    857     {
    858       min_row_id = GNUNET_ntohll (r);
    859     }
    860     GNUNET_free (min_row_fn);
    861   }
    862   if (norun)
    863   {
    864     GNUNET_SCHEDULER_shutdown ();
    865     return;
    866   }
    867   if (! testmode)
    868   {
    869     struct GNUNET_DB_EventHeaderP hdr = {
    870       .size = htons (sizeof (hdr)),
    871       .type = htons (TALER_DBEVENT_EXCHANGE_NEW_KYC_ATTRIBUTES),
    872     };
    873 
    874     eh = TALER_EXCHANGEDB_event_listen (
    875       pg,
    876       GNUNET_TIME_UNIT_FOREVER_REL,
    877       &hdr,
    878       &db_event_cb,
    879       NULL);
    880   }
    881   begin_transaction ();
    882 }
    883 
    884 
    885 /**
    886  * The main function of taler-exchange-sanctionscheck
    887  *
    888  * @param argc number of arguments from the command line
    889  * @param argv command line arguments
    890  * @return 0 ok, non-zero on error
    891  */
    892 int
    893 main (int argc,
    894       char *const *argv)
    895 {
    896   struct GNUNET_GETOPT_CommandLineOption options[] = {
    897     GNUNET_GETOPT_option_version (VERSION),
    898     GNUNET_GETOPT_option_flag ('n',
    899                                "norun",
    900                                "do not actually start a scan (to be used to only reset without starting a scan)",
    901                                &norun),
    902     GNUNET_GETOPT_option_flag ('r',
    903                                "reset",
    904                                "rescan all records (to be used when the sanction list was updated)",
    905                                &reset),
    906     GNUNET_GETOPT_option_flag ('t',
    907                                "test",
    908                                "run in test mode and exit when idle",
    909                                &testmode),
    910     GNUNET_GETOPT_OPTION_END
    911   };
    912   enum GNUNET_GenericReturnValue ret;
    913 
    914   ret = GNUNET_PROGRAM_run (
    915     TALER_EXCHANGE_project_data (),
    916     argc, argv,
    917     "taler-exchange-sanctionscheck -- HELPER [HELPER ARGS]",
    918     gettext_noop (
    919       "process that checks all existing customer accounts against a sanctions list"),
    920     options,
    921     &run, NULL);
    922   json_decref (freeze_rules);
    923   freeze_rules = NULL;
    924   if (GNUNET_SYSERR == ret)
    925     return EXIT_INVALIDARGUMENT;
    926   if (GNUNET_NO == ret)
    927     return EXIT_SUCCESS;
    928   return global_ret;
    929 }
    930 
    931 
    932 /* end of taler-exchange-sanctionscheck.c */