exchange

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

kyclogic_sanctions.c (14977B)


      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 kyclogic_sanctions.c
     18  * @brief wrapper around sanction list evaluator
     19  * @author Christian Grothoff
     20  */
     21 #include "platform.h"
     22 #include "taler/taler_json_lib.h"
     23 #include "taler/taler_kyclogic_lib.h"
     24 
     25 
     26 /**
     27  * Entry in the ordered list of pending evaluations.
     28  */
     29 struct TALER_KYCLOGIC_EvaluationEntry
     30 {
     31   /**
     32    * Kept in a DLL.
     33    */
     34   struct TALER_KYCLOGIC_EvaluationEntry *prev;
     35 
     36   /**
     37    * Kept in a DLL.
     38    */
     39   struct TALER_KYCLOGIC_EvaluationEntry *next;
     40 
     41   /**
     42    * Callback to call with the result.
     43    */
     44   TALER_KYCLOGIC_SanctionResultCallback cb;
     45 
     46   /**
     47    * Closure for @e cb.
     48    */
     49   void *cb_cls;
     50 
     51   /**
     52    * Buffer with data we need to send to the helper.
     53    */
     54   char *write_buf;
     55 
     56   /**
     57    * Total length of @e write_buf.
     58    */
     59   size_t write_size;
     60 
     61   /**
     62    * Current write position in @e write_buf.
     63    */
     64   size_t write_pos;
     65 
     66 };
     67 
     68 
     69 /**
     70  * Handle to a sanction list evaluation helper process.
     71  */
     72 struct TALER_KYCLOGIC_SanctionRater
     73 {
     74 
     75   /**
     76    * Kept in a DLL.
     77    */
     78   struct TALER_KYCLOGIC_EvaluationEntry *ee_head;
     79 
     80   /**
     81    * Kept in a DLL.
     82    */
     83   struct TALER_KYCLOGIC_EvaluationEntry *ee_tail;
     84 
     85   /**
     86    * Handle to the helper process.
     87    */
     88   struct GNUNET_Process *helper;
     89 
     90   /**
     91    * Pipe for the stdin of the @e helper.
     92    */
     93   struct GNUNET_DISK_FileHandle *chld_stdin;
     94 
     95   /**
     96    * Pipe for the stdout of the @e helper.
     97    */
     98   struct GNUNET_DISK_FileHandle *chld_stdout;
     99 
    100   /**
    101    * Handle to wait on the child to terminate.
    102    */
    103   struct GNUNET_ChildWaitHandle *cwh;
    104 
    105   /**
    106    * Task to read JSON output from the child.
    107    */
    108   struct GNUNET_SCHEDULER_Task *read_task;
    109 
    110   /**
    111    * Task to send JSON input to the child.
    112    */
    113   struct GNUNET_SCHEDULER_Task *write_task;
    114 
    115   /**
    116    * Buffer for reading data from the helper.
    117    */
    118   void *read_buf;
    119 
    120   /**
    121    * Current size of @a read_buf.
    122    */
    123   size_t read_size;
    124 
    125   /**
    126    * Current offset in @a read_buf.
    127    */
    128   size_t read_pos;
    129 
    130 };
    131 
    132 
    133 /**
    134  * We encountered a hard error (or explicit stop) of @a sr.
    135  * Shut down processing (but do not yet free @a sr).
    136  *
    137  * @param[in,out] sr sanction rater to fail
    138  */
    139 static void
    140 fail_hard (struct TALER_KYCLOGIC_SanctionRater *sr)
    141 {
    142   struct TALER_KYCLOGIC_EvaluationEntry *ee;
    143 
    144   if (NULL != sr->chld_stdin)
    145   {
    146     GNUNET_break (GNUNET_OK ==
    147                   GNUNET_DISK_file_close (sr->chld_stdin));
    148     sr->chld_stdin = NULL;
    149   }
    150   if (NULL != sr->read_task)
    151   {
    152     GNUNET_SCHEDULER_cancel (sr->read_task);
    153     sr->read_task = NULL;
    154   }
    155   if (NULL != sr->write_task)
    156   {
    157     GNUNET_SCHEDULER_cancel (sr->write_task);
    158     sr->write_task = NULL;
    159   }
    160   if (NULL != sr->helper)
    161   {
    162     GNUNET_process_destroy (sr->helper);
    163     sr->helper = NULL;
    164   }
    165   while (NULL != (ee = sr->ee_tail))
    166   {
    167     GNUNET_CONTAINER_DLL_remove (sr->ee_head,
    168                                  sr->ee_tail,
    169                                  ee);
    170     ee->cb (ee->cb_cls,
    171             TALER_EC_EXCHANGE_GENERIC_KYC_SANCTION_LIST_CHECK_FAILED,
    172             NULL,
    173             1.0,
    174             0.0);
    175     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    176                 "Failed to send %u bytes to child\n",
    177                 (unsigned int) (ee->write_size - ee->write_pos));
    178     GNUNET_free (ee->write_buf);
    179     GNUNET_free (ee);
    180   }
    181 }
    182 
    183 
    184 /**
    185  * Parse data from input buffer.
    186  *
    187  * @param[in,out] sr sanction rater to process data from
    188  * @return true if everything is fine, false on failure
    189  */
    190 static bool
    191 process_buffer (struct TALER_KYCLOGIC_SanctionRater *sr)
    192 {
    193   char *buf = sr->read_buf;
    194   size_t buf_len;
    195   const void *end;
    196 
    197   end = memrchr (sr->read_buf,
    198                  '\n',
    199                  sr->read_pos);
    200   if ( (NULL == end) &&
    201        (sr->read_pos < 2048) )
    202     return true;
    203   if (NULL == end)
    204   {
    205     /* line returned by sanction rater way too long */
    206     GNUNET_break (0);
    207     return false;
    208   }
    209   end++;
    210   buf_len = end - sr->read_buf;
    211   while (0 != buf_len)
    212   {
    213     char *nl;
    214     double rating;
    215     double confidence;
    216     char best_match[1024];
    217     size_t line_len;
    218 
    219     nl = memchr (buf,
    220                  '\n',
    221                  buf_len);
    222     if (NULL == nl)
    223     {
    224       /* no newline in 2048 bytes? not allowed */
    225       GNUNET_break (0);
    226       return false;
    227     }
    228     *nl = '\0';
    229     line_len = nl - buf + 1;
    230     if (3 !=
    231         sscanf (buf,
    232                 "%lf %lf %1023s",
    233                 &rating,
    234                 &confidence,
    235                 best_match))
    236     {
    237       /* maybe best_match is empty because literally nothing matched */
    238       if (2 !=
    239           sscanf (buf,
    240                   "%lf %lf ",
    241                   &rating,
    242                   &confidence))
    243       {
    244         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    245                     "Malformed input line `%s'\n",
    246                     buf);
    247         GNUNET_break (0);
    248         return false;
    249       }
    250       strcpy (best_match,
    251               "<none>");
    252     }
    253 
    254     {
    255       struct TALER_KYCLOGIC_EvaluationEntry *ee = sr->ee_tail;
    256 
    257       /* This assertion would fail if the helper outputs more lines
    258          than we sent it queries, pointing to some kind of
    259          desynchronization. Failing hard as we cannot really handle
    260          this case nicely. */
    261       GNUNET_assert (NULL != ee);
    262       GNUNET_CONTAINER_DLL_remove (sr->ee_head,
    263                                    sr->ee_tail,
    264                                    ee);
    265       ee->cb (ee->cb_cls,
    266               TALER_EC_NONE,
    267               best_match,
    268               rating,
    269               confidence);
    270       GNUNET_free (ee->write_buf);
    271       GNUNET_free (ee);
    272     }
    273     buf += line_len;
    274     buf_len -= line_len;
    275   }
    276   buf_len = end - sr->read_buf;
    277   memmove (sr->read_buf,
    278            end,
    279            sr->read_pos - buf_len);
    280   sr->read_pos -= buf_len;
    281   return true;
    282 }
    283 
    284 
    285 /**
    286  * Function called when we can read more data from
    287  * the child process.
    288  *
    289  * @param cls our `struct TALER_KYCLOGIC_SanctionRater *`
    290  */
    291 static void
    292 read_cb (void *cls)
    293 {
    294   struct TALER_KYCLOGIC_SanctionRater *sr = cls;
    295 
    296   sr->read_task = NULL;
    297   while (1)
    298   {
    299     ssize_t ret;
    300 
    301     if (sr->read_size == sr->read_pos)
    302     {
    303       /* Grow input buffer */
    304       size_t ns;
    305       void *tmp;
    306 
    307       ns = GNUNET_MAX (2 * sr->read_size,
    308                        1024);
    309       if (ns > GNUNET_MAX_MALLOC_CHECKED)
    310         ns = GNUNET_MAX_MALLOC_CHECKED;
    311       if (sr->read_size == ns)
    312       {
    313         /* Helper returned more than 40 MB of data! Stop reading! */
    314         GNUNET_break (0);
    315         GNUNET_break (GNUNET_OK ==
    316                       GNUNET_DISK_file_close (sr->chld_stdin));
    317         return;
    318       }
    319       tmp = GNUNET_malloc_large (ns);
    320       if (NULL == tmp)
    321       {
    322         /* out of memory, also stop reading */
    323         GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
    324                              "malloc");
    325         GNUNET_break (GNUNET_OK ==
    326                       GNUNET_DISK_file_close (sr->chld_stdin));
    327         return;
    328       }
    329       GNUNET_memcpy (tmp,
    330                      sr->read_buf,
    331                      sr->read_pos);
    332       GNUNET_free (sr->read_buf);
    333       sr->read_buf = tmp;
    334       sr->read_size = ns;
    335     }
    336     ret = GNUNET_DISK_file_read (sr->chld_stdout,
    337                                  sr->read_buf + sr->read_pos,
    338                                  sr->read_size - sr->read_pos);
    339     if (ret < 0)
    340     {
    341       if ( (EAGAIN != errno) &&
    342            (EWOULDBLOCK != errno) &&
    343            (EINTR != errno) )
    344       {
    345         GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
    346                              "read");
    347         return;
    348       }
    349       /* Continue later */
    350       break;
    351     }
    352     if (0 == ret)
    353     {
    354       /* regular end of stream, odd! */
    355       fail_hard (sr);
    356       return;
    357     }
    358     GNUNET_assert (sr->read_size >= sr->read_pos + ret);
    359     sr->read_pos += ret;
    360     if (! process_buffer (sr))
    361       return;
    362   }
    363   sr->read_task
    364     = GNUNET_SCHEDULER_add_read_file (
    365         GNUNET_TIME_UNIT_FOREVER_REL,
    366         sr->chld_stdout,
    367         &read_cb,
    368         sr);
    369 }
    370 
    371 
    372 /**
    373  * Function called when we can write more data to
    374  * the child process.
    375  *
    376  * @param cls our `struct SanctionRater *`
    377  */
    378 static void
    379 write_cb (void *cls)
    380 {
    381   struct TALER_KYCLOGIC_SanctionRater *sr = cls;
    382   struct TALER_KYCLOGIC_EvaluationEntry *ee = sr->ee_tail;
    383   ssize_t ret;
    384 
    385   sr->write_task = NULL;
    386   while ( (NULL != ee) &&
    387           (ee->write_size == ee->write_pos) )
    388     ee = ee->prev;
    389   while (NULL != ee)
    390   {
    391     while (ee->write_size > ee->write_pos)
    392     {
    393       ret = GNUNET_DISK_file_write (sr->chld_stdin,
    394                                     ee->write_buf + ee->write_pos,
    395                                     ee->write_size - ee->write_pos);
    396       if (ret < 0)
    397       {
    398         if ( (EAGAIN != errno) &&
    399              (EINTR != errno) )
    400         {
    401           GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
    402                                "write");
    403           /* helper must have died */
    404           fail_hard (sr);
    405           return;
    406         }
    407         break;
    408       }
    409       if (0 == ret)
    410       {
    411         GNUNET_break (0);
    412         break;
    413       }
    414       GNUNET_assert (ee->write_size >= ee->write_pos + ret);
    415       ee->write_pos += ret;
    416     }
    417     if ( (ee->write_size > ee->write_pos) &&
    418          ( (EAGAIN == errno) ||
    419            (EWOULDBLOCK == errno) ||
    420            (EINTR == errno) ) )
    421     {
    422       sr->write_task
    423         = GNUNET_SCHEDULER_add_write_file (
    424             GNUNET_TIME_UNIT_FOREVER_REL,
    425             sr->chld_stdin,
    426             &write_cb,
    427             sr);
    428       return;
    429     }
    430     if (ee->write_size == ee->write_pos)
    431     {
    432       GNUNET_free (ee->write_buf);
    433       ee = ee->prev;
    434     }
    435   } /* while (NULL != ee) */
    436 }
    437 
    438 
    439 /**
    440  * Defines a GNUNET_ChildCompletedCallback which is sent back
    441  * upon death or completion of a child process.
    442  *
    443  * @param cls handle for the callback
    444  * @param type type of the process
    445  * @param exit_code status code of the process
    446  *
    447  */
    448 static void
    449 child_done_cb (void *cls,
    450                enum GNUNET_OS_ProcessStatusType type,
    451                long unsigned int exit_code)
    452 {
    453   struct TALER_KYCLOGIC_SanctionRater *sr = cls;
    454 
    455   sr->cwh = NULL;
    456   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    457               "Conversion helper exited with status %d and code %llu after outputting %llu bytes of data\n",
    458               (int) type,
    459               (unsigned long long) exit_code,
    460               (unsigned long long) sr->read_pos);
    461   fail_hard (sr);
    462 }
    463 
    464 
    465 struct TALER_KYCLOGIC_SanctionRater *
    466 TALER_KYCLOGIC_sanction_rater_start (const char *binary,
    467                                      char *const*argv)
    468 {
    469   struct TALER_KYCLOGIC_SanctionRater *sr;
    470   struct GNUNET_DISK_PipeHandle *pipe_stdin;
    471   struct GNUNET_DISK_PipeHandle *pipe_stdout;
    472 
    473   sr = GNUNET_new (struct TALER_KYCLOGIC_SanctionRater);
    474   pipe_stdin = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_READ);
    475   GNUNET_assert (NULL != pipe_stdin);
    476   pipe_stdout = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_WRITE);
    477   GNUNET_assert (NULL != pipe_stdout);
    478   sr->helper = GNUNET_process_create (GNUNET_OS_INHERIT_STD_ERR);
    479   GNUNET_assert (GNUNET_OK ==
    480                  GNUNET_process_set_options (
    481                    sr->helper,
    482                    GNUNET_process_option_inherit_rpipe (pipe_stdin,
    483                                                         STDIN_FILENO),
    484                    GNUNET_process_option_inherit_wpipe (pipe_stdout,
    485                                                         STDOUT_FILENO)));
    486   if (GNUNET_OK !=
    487       GNUNET_process_run_command_argv (sr->helper,
    488                                        binary,
    489                                        (const char **) argv))
    490   {
    491     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    492                 "Failed to run conversion helper `%s'\n",
    493                 binary);
    494     GNUNET_process_destroy (sr->helper);
    495     GNUNET_break (GNUNET_OK ==
    496                   GNUNET_DISK_pipe_close (pipe_stdin));
    497     GNUNET_break (GNUNET_OK ==
    498                   GNUNET_DISK_pipe_close (pipe_stdout));
    499     GNUNET_free (sr);
    500     return NULL;
    501   }
    502   sr->chld_stdin =
    503     GNUNET_DISK_pipe_detach_end (pipe_stdin,
    504                                  GNUNET_DISK_PIPE_END_WRITE);
    505   sr->chld_stdout =
    506     GNUNET_DISK_pipe_detach_end (pipe_stdout,
    507                                  GNUNET_DISK_PIPE_END_READ);
    508   GNUNET_break (GNUNET_OK ==
    509                 GNUNET_DISK_pipe_close (pipe_stdin));
    510   GNUNET_break (GNUNET_OK ==
    511                 GNUNET_DISK_pipe_close (pipe_stdout));
    512 
    513   sr->read_task
    514     = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
    515                                       sr->chld_stdout,
    516                                       &read_cb,
    517                                       sr);
    518   sr->cwh = GNUNET_wait_child (sr->helper,
    519                                &child_done_cb,
    520                                sr);
    521   return sr;
    522 }
    523 
    524 
    525 struct TALER_KYCLOGIC_EvaluationEntry *
    526 TALER_KYCLOGIC_sanction_rater_eval (struct TALER_KYCLOGIC_SanctionRater *sr,
    527                                     const json_t *attributes,
    528                                     TALER_KYCLOGIC_SanctionResultCallback cb,
    529                                     void *cb_cls)
    530 {
    531   struct TALER_KYCLOGIC_EvaluationEntry *ee;
    532   char *js;
    533 
    534   if (NULL == sr->read_task)
    535     return NULL;
    536   ee = GNUNET_new (struct TALER_KYCLOGIC_EvaluationEntry);
    537   ee->cb = cb;
    538   ee->cb_cls = cb_cls;
    539   GNUNET_CONTAINER_DLL_insert (sr->ee_head,
    540                                sr->ee_tail,
    541                                ee);
    542   js = json_dumps (attributes,
    543                    JSON_COMPACT);
    544   GNUNET_asprintf (&ee->write_buf,
    545                    "%s\n",
    546                    js);
    547   free (js);
    548   ee->write_size = strlen (ee->write_buf);
    549   if (NULL == sr->write_task)
    550     sr->write_task
    551       = GNUNET_SCHEDULER_add_write_file (GNUNET_TIME_UNIT_FOREVER_REL,
    552                                          sr->chld_stdin,
    553                                          &write_cb,
    554                                          sr);
    555   return ee;
    556 }
    557 
    558 
    559 void
    560 TALER_KYCLOGIC_sanction_rater_stop (
    561   struct TALER_KYCLOGIC_SanctionRater *sr)
    562 {
    563   fail_hard (sr);
    564   if (NULL != sr->cwh)
    565   {
    566     GNUNET_wait_child_cancel (sr->cwh);
    567     sr->cwh = NULL;
    568   }
    569   if (NULL != sr->write_task)
    570   {
    571     GNUNET_SCHEDULER_cancel (sr->write_task);
    572     sr->write_task = NULL;
    573   }
    574   if (NULL != sr->chld_stdout)
    575   {
    576     GNUNET_break (GNUNET_OK ==
    577                   GNUNET_DISK_file_close (sr->chld_stdout));
    578     sr->chld_stdout = NULL;
    579   }
    580   GNUNET_free (sr->read_buf);
    581   GNUNET_free (sr);
    582 }