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 (14912B)


      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         fail_hard (sr);
    316         return;
    317       }
    318       tmp = GNUNET_malloc_large (ns);
    319       if (NULL == tmp)
    320       {
    321         /* out of memory, also stop reading */
    322         GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
    323                              "malloc");
    324         fail_hard (sr);
    325         return;
    326       }
    327       GNUNET_memcpy (tmp,
    328                      sr->read_buf,
    329                      sr->read_pos);
    330       GNUNET_free (sr->read_buf);
    331       sr->read_buf = tmp;
    332       sr->read_size = ns;
    333     }
    334     ret = GNUNET_DISK_file_read (sr->chld_stdout,
    335                                  sr->read_buf + sr->read_pos,
    336                                  sr->read_size - sr->read_pos);
    337     if (ret < 0)
    338     {
    339       if ( (EAGAIN != errno) &&
    340            (EWOULDBLOCK != errno) &&
    341            (EINTR != errno) )
    342       {
    343         GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
    344                              "read");
    345         fail_hard (sr);
    346         return;
    347       }
    348       /* Continue later */
    349       break;
    350     }
    351     if (0 == ret)
    352     {
    353       /* regular end of stream, odd! */
    354       fail_hard (sr);
    355       return;
    356     }
    357     GNUNET_assert (sr->read_size >= sr->read_pos + ret);
    358     sr->read_pos += ret;
    359     if (! process_buffer (sr))
    360     {
    361       fail_hard (sr);
    362       return;
    363     }
    364   }
    365   sr->read_task
    366     = GNUNET_SCHEDULER_add_read_file (
    367         GNUNET_TIME_UNIT_FOREVER_REL,
    368         sr->chld_stdout,
    369         &read_cb,
    370         sr);
    371 }
    372 
    373 
    374 /**
    375  * Function called when we can write more data to
    376  * the child process.
    377  *
    378  * @param cls our `struct SanctionRater *`
    379  */
    380 static void
    381 write_cb (void *cls)
    382 {
    383   struct TALER_KYCLOGIC_SanctionRater *sr = cls;
    384   struct TALER_KYCLOGIC_EvaluationEntry *ee = sr->ee_tail;
    385   ssize_t ret;
    386 
    387   sr->write_task = NULL;
    388   while ( (NULL != ee) &&
    389           (ee->write_size == ee->write_pos) )
    390     ee = ee->prev;
    391   while (NULL != ee)
    392   {
    393     bool retry = false;
    394 
    395     while (ee->write_size > ee->write_pos)
    396     {
    397       ret = GNUNET_DISK_file_write (sr->chld_stdin,
    398                                     ee->write_buf + ee->write_pos,
    399                                     ee->write_size - ee->write_pos);
    400       if (ret < 0)
    401       {
    402         if ( (EAGAIN != errno) &&
    403              (EWOULDBLOCK != errno) &&
    404              (EINTR != errno) )
    405         {
    406           GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
    407                                "write");
    408           /* helper must have died */
    409           fail_hard (sr);
    410           return;
    411         }
    412         /* continue later */
    413         retry = true;
    414         break;
    415       }
    416       if (0 == ret)
    417       {
    418         /* should be impossible on a pipe */
    419         GNUNET_break (0);
    420         fail_hard (sr);
    421         return;
    422       }
    423       GNUNET_assert (ee->write_size >= ee->write_pos + ret);
    424       ee->write_pos += ret;
    425     }
    426     if (retry)
    427     {
    428       sr->write_task
    429         = GNUNET_SCHEDULER_add_write_file (
    430             GNUNET_TIME_UNIT_FOREVER_REL,
    431             sr->chld_stdin,
    432             &write_cb,
    433             sr);
    434       return;
    435     }
    436     GNUNET_free (ee->write_buf);
    437     ee = ee->prev;
    438   } /* while (NULL != ee) */
    439 }
    440 
    441 
    442 /**
    443  * Defines a GNUNET_ChildCompletedCallback which is sent back
    444  * upon death or completion of a child process.
    445  *
    446  * @param cls handle for the callback
    447  * @param type type of the process
    448  * @param exit_code status code of the process
    449  *
    450  */
    451 static void
    452 child_done_cb (void *cls,
    453                enum GNUNET_OS_ProcessStatusType type,
    454                long unsigned int exit_code)
    455 {
    456   struct TALER_KYCLOGIC_SanctionRater *sr = cls;
    457 
    458   sr->cwh = NULL;
    459   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    460               "Conversion helper exited with status %d and code %llu after outputting %llu bytes of data\n",
    461               (int) type,
    462               (unsigned long long) exit_code,
    463               (unsigned long long) sr->read_pos);
    464   fail_hard (sr);
    465 }
    466 
    467 
    468 struct TALER_KYCLOGIC_SanctionRater *
    469 TALER_KYCLOGIC_sanction_rater_start (const char *binary,
    470                                      char *const*argv)
    471 {
    472   struct TALER_KYCLOGIC_SanctionRater *sr;
    473   struct GNUNET_DISK_PipeHandle *pipe_stdin;
    474   struct GNUNET_DISK_PipeHandle *pipe_stdout;
    475 
    476   sr = GNUNET_new (struct TALER_KYCLOGIC_SanctionRater);
    477   pipe_stdin = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_READ);
    478   GNUNET_assert (NULL != pipe_stdin);
    479   pipe_stdout = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_WRITE);
    480   GNUNET_assert (NULL != pipe_stdout);
    481   sr->helper = GNUNET_process_create (GNUNET_OS_INHERIT_STD_ERR);
    482   GNUNET_assert (GNUNET_OK ==
    483                  GNUNET_process_set_options (
    484                    sr->helper,
    485                    GNUNET_process_option_inherit_rpipe (pipe_stdin,
    486                                                         STDIN_FILENO),
    487                    GNUNET_process_option_inherit_wpipe (pipe_stdout,
    488                                                         STDOUT_FILENO)));
    489   if (GNUNET_OK !=
    490       GNUNET_process_run_command_argv (sr->helper,
    491                                        binary,
    492                                        (const char **) argv))
    493   {
    494     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    495                 "Failed to run conversion helper `%s'\n",
    496                 binary);
    497     GNUNET_process_destroy (sr->helper);
    498     GNUNET_break (GNUNET_OK ==
    499                   GNUNET_DISK_pipe_close (pipe_stdin));
    500     GNUNET_break (GNUNET_OK ==
    501                   GNUNET_DISK_pipe_close (pipe_stdout));
    502     GNUNET_free (sr);
    503     return NULL;
    504   }
    505   sr->chld_stdin =
    506     GNUNET_DISK_pipe_detach_end (pipe_stdin,
    507                                  GNUNET_DISK_PIPE_END_WRITE);
    508   sr->chld_stdout =
    509     GNUNET_DISK_pipe_detach_end (pipe_stdout,
    510                                  GNUNET_DISK_PIPE_END_READ);
    511   GNUNET_break (GNUNET_OK ==
    512                 GNUNET_DISK_pipe_close (pipe_stdin));
    513   GNUNET_break (GNUNET_OK ==
    514                 GNUNET_DISK_pipe_close (pipe_stdout));
    515 
    516   sr->read_task
    517     = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
    518                                       sr->chld_stdout,
    519                                       &read_cb,
    520                                       sr);
    521   sr->cwh = GNUNET_wait_child (sr->helper,
    522                                &child_done_cb,
    523                                sr);
    524   return sr;
    525 }
    526 
    527 
    528 struct TALER_KYCLOGIC_EvaluationEntry *
    529 TALER_KYCLOGIC_sanction_rater_eval (struct TALER_KYCLOGIC_SanctionRater *sr,
    530                                     const json_t *attributes,
    531                                     TALER_KYCLOGIC_SanctionResultCallback cb,
    532                                     void *cb_cls)
    533 {
    534   struct TALER_KYCLOGIC_EvaluationEntry *ee;
    535   char *js;
    536 
    537   if (NULL == sr->read_task)
    538   {
    539     GNUNET_break (0);
    540     return NULL;
    541   }
    542   ee = GNUNET_new (struct TALER_KYCLOGIC_EvaluationEntry);
    543   ee->cb = cb;
    544   ee->cb_cls = cb_cls;
    545   GNUNET_CONTAINER_DLL_insert (sr->ee_head,
    546                                sr->ee_tail,
    547                                ee);
    548   js = json_dumps (attributes,
    549                    JSON_COMPACT);
    550   GNUNET_asprintf (&ee->write_buf,
    551                    "%s\n",
    552                    js);
    553   free (js);
    554   ee->write_size = strlen (ee->write_buf);
    555   if (NULL == sr->write_task)
    556     sr->write_task
    557       = GNUNET_SCHEDULER_add_write_file (GNUNET_TIME_UNIT_FOREVER_REL,
    558                                          sr->chld_stdin,
    559                                          &write_cb,
    560                                          sr);
    561   return ee;
    562 }
    563 
    564 
    565 void
    566 TALER_KYCLOGIC_sanction_rater_stop (
    567   struct TALER_KYCLOGIC_SanctionRater *sr)
    568 {
    569   fail_hard (sr);
    570   if (NULL != sr->cwh)
    571   {
    572     GNUNET_wait_child_cancel (sr->cwh);
    573     sr->cwh = NULL;
    574   }
    575   if (NULL != sr->write_task)
    576   {
    577     GNUNET_SCHEDULER_cancel (sr->write_task);
    578     sr->write_task = NULL;
    579   }
    580   if (NULL != sr->chld_stdout)
    581   {
    582     GNUNET_break (GNUNET_OK ==
    583                   GNUNET_DISK_file_close (sr->chld_stdout));
    584     sr->chld_stdout = NULL;
    585   }
    586   GNUNET_free (sr->read_buf);
    587   GNUNET_free (sr);
    588 }