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


      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 "taler/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_OS_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->helper)
    156   {
    157     GNUNET_OS_process_destroy (sr->helper);
    158     sr->helper = NULL;
    159   }
    160   while (NULL != (ee = sr->ee_tail))
    161   {
    162     GNUNET_CONTAINER_DLL_remove (sr->ee_head,
    163                                  sr->ee_tail,
    164                                  ee);
    165     ee->cb (ee->cb_cls,
    166             TALER_EC_EXCHANGE_GENERIC_KYC_SANCTION_LIST_CHECK_FAILED,
    167             NULL,
    168             1.0,
    169             0.0);
    170     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    171                 "Failed to send %u bytes to child\n",
    172                 (unsigned int) (ee->write_size - ee->write_pos));
    173     GNUNET_free (ee->write_buf);
    174     GNUNET_free (ee);
    175   }
    176 }
    177 
    178 
    179 /**
    180  * Parse data from input buffer.
    181  *
    182  * @param[in,out] sr sanction rater to process data from
    183  * @return true if everything is fine, false on failure
    184  */
    185 static bool
    186 process_buffer (struct TALER_KYCLOGIC_SanctionRater *sr)
    187 {
    188   const char *buf = sr->read_buf;
    189   size_t buf_len;
    190   void *end;
    191 
    192   end = memrchr (sr->read_buf,
    193                  '\n',
    194                  sr->read_pos);
    195   if ( (NULL == end) &&
    196        (sr->read_pos < 2048) )
    197     return true;
    198   if (NULL == end)
    199   {
    200     /* line returned by sanction rater way too long */
    201     GNUNET_break (0);
    202     return false;
    203   }
    204   end++;
    205   buf_len = end - sr->read_buf;
    206   while (0 != buf_len)
    207   {
    208     char *nl;
    209     double rating;
    210     double confidence;
    211     char best_match[1024];
    212     size_t line_len;
    213 
    214     nl = memchr (buf,
    215                  '\n',
    216                  buf_len);
    217     if (NULL == nl)
    218     {
    219       /* no newline in 2048 bytes? not allowed */
    220       GNUNET_break (0);
    221       return false;
    222     }
    223     *nl = '\0';
    224     line_len = nl - buf + 1;
    225     if (3 !=
    226         sscanf (buf,
    227                 "%lf %lf %1023s",
    228                 &rating,
    229                 &confidence,
    230                 best_match))
    231     {
    232       /* maybe best_match is empty because literally nothing matched */
    233       if (2 !=
    234           sscanf (buf,
    235                   "%lf %lf ",
    236                   &rating,
    237                   &confidence))
    238       {
    239         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    240                     "Malformed input line `%s'\n",
    241                     buf);
    242         GNUNET_break (0);
    243         return false;
    244       }
    245       strcpy (best_match,
    246               "<none>");
    247     }
    248     {
    249       struct TALER_KYCLOGIC_EvaluationEntry *ee = sr->ee_tail;
    250 
    251       GNUNET_CONTAINER_DLL_remove (sr->ee_head,
    252                                    sr->ee_tail,
    253                                    ee);
    254       ee->cb (ee->cb_cls,
    255               TALER_EC_NONE,
    256               best_match,
    257               rating,
    258               confidence);
    259       GNUNET_free (ee->write_buf);
    260       GNUNET_free (ee);
    261     }
    262     buf += line_len;
    263     buf_len -= line_len;
    264   }
    265   buf_len = end - sr->read_buf;
    266   memmove (sr->read_buf,
    267            end,
    268            sr->read_pos - buf_len);
    269   sr->read_pos -= buf_len;
    270   return true;
    271 }
    272 
    273 
    274 /**
    275  * Function called when we can read more data from
    276  * the child process.
    277  *
    278  * @param cls our `struct TALER_KYCLOGIC_SanctionRater *`
    279  */
    280 static void
    281 read_cb (void *cls)
    282 {
    283   struct TALER_KYCLOGIC_SanctionRater *sr = cls;
    284 
    285   sr->read_task = NULL;
    286   while (1)
    287   {
    288     ssize_t ret;
    289 
    290     if (sr->read_size == sr->read_pos)
    291     {
    292       /* Grow input buffer */
    293       size_t ns;
    294       void *tmp;
    295 
    296       ns = GNUNET_MAX (2 * sr->read_size,
    297                        1024);
    298       if (ns > GNUNET_MAX_MALLOC_CHECKED)
    299         ns = GNUNET_MAX_MALLOC_CHECKED;
    300       if (sr->read_size == ns)
    301       {
    302         /* Helper returned more than 40 MB of data! Stop reading! */
    303         GNUNET_break (0);
    304         GNUNET_break (GNUNET_OK ==
    305                       GNUNET_DISK_file_close (sr->chld_stdin));
    306         return;
    307       }
    308       tmp = GNUNET_malloc_large (ns);
    309       if (NULL == tmp)
    310       {
    311         /* out of memory, also stop reading */
    312         GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
    313                              "malloc");
    314         GNUNET_break (GNUNET_OK ==
    315                       GNUNET_DISK_file_close (sr->chld_stdin));
    316         return;
    317       }
    318       GNUNET_memcpy (tmp,
    319                      sr->read_buf,
    320                      sr->read_pos);
    321       GNUNET_free (sr->read_buf);
    322       sr->read_buf = tmp;
    323       sr->read_size = ns;
    324     }
    325     ret = GNUNET_DISK_file_read (sr->chld_stdout,
    326                                  sr->read_buf + sr->read_pos,
    327                                  sr->read_size - sr->read_pos);
    328     if (ret < 0)
    329     {
    330       if ( (EAGAIN != errno) &&
    331            (EWOULDBLOCK != errno) &&
    332            (EINTR != errno) )
    333       {
    334         GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
    335                              "read");
    336         return;
    337       }
    338       /* Continue later */
    339       break;
    340     }
    341     if (0 == ret)
    342     {
    343       /* regular end of stream, odd! */
    344       fail_hard (sr);
    345       return;
    346     }
    347     GNUNET_assert (sr->read_size >= sr->read_pos + ret);
    348     sr->read_pos += ret;
    349     if (! process_buffer (sr))
    350       return;
    351   }
    352   sr->read_task
    353     = GNUNET_SCHEDULER_add_read_file (
    354         GNUNET_TIME_UNIT_FOREVER_REL,
    355         sr->chld_stdout,
    356         &read_cb,
    357         sr);
    358 }
    359 
    360 
    361 /**
    362  * Function called when we can write more data to
    363  * the child process.
    364  *
    365  * @param cls our `struct SanctionRater *`
    366  */
    367 static void
    368 write_cb (void *cls)
    369 {
    370   struct TALER_KYCLOGIC_SanctionRater *sr = cls;
    371   struct TALER_KYCLOGIC_EvaluationEntry *ee = sr->ee_tail;
    372   ssize_t ret;
    373 
    374   sr->write_task = NULL;
    375   while ( (NULL != ee) &&
    376           (ee->write_size == ee->write_pos) )
    377     ee = ee->prev;
    378   while (NULL != ee)
    379   {
    380     while (ee->write_size > ee->write_pos)
    381     {
    382       ret = GNUNET_DISK_file_write (sr->chld_stdin,
    383                                     ee->write_buf + ee->write_pos,
    384                                     ee->write_size - ee->write_pos);
    385       if (ret < 0)
    386       {
    387         if ( (EAGAIN != errno) &&
    388              (EINTR != errno) )
    389         {
    390           GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
    391                                "write");
    392           /* helper must have died */
    393           fail_hard (sr);
    394           return;
    395         }
    396         break;
    397       }
    398       if (0 == ret)
    399       {
    400         GNUNET_break (0);
    401         break;
    402       }
    403       GNUNET_assert (ee->write_size >= ee->write_pos + ret);
    404       ee->write_pos += ret;
    405     }
    406     if ( (ee->write_size > ee->write_pos) &&
    407          ( (EAGAIN == errno) ||
    408            (EWOULDBLOCK == errno) ||
    409            (EINTR == errno) ) )
    410     {
    411       sr->write_task
    412         = GNUNET_SCHEDULER_add_write_file (
    413             GNUNET_TIME_UNIT_FOREVER_REL,
    414             sr->chld_stdin,
    415             &write_cb,
    416             sr);
    417       return;
    418     }
    419     if (ee->write_size == ee->write_pos)
    420     {
    421       GNUNET_free (ee->write_buf);
    422       ee = ee->prev;
    423     }
    424   } /* while (NULL != ee) */
    425 }
    426 
    427 
    428 /**
    429  * Defines a GNUNET_ChildCompletedCallback which is sent back
    430  * upon death or completion of a child process.
    431  *
    432  * @param cls handle for the callback
    433  * @param type type of the process
    434  * @param exit_code status code of the process
    435  *
    436  */
    437 static void
    438 child_done_cb (void *cls,
    439                enum GNUNET_OS_ProcessStatusType type,
    440                long unsigned int exit_code)
    441 {
    442   struct TALER_KYCLOGIC_SanctionRater *sr = cls;
    443 
    444   sr->cwh = NULL;
    445   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    446               "Conversion helper exited with status %d and code %llu after outputting %llu bytes of data\n",
    447               (int) type,
    448               (unsigned long long) exit_code,
    449               (unsigned long long) sr->read_pos);
    450   fail_hard (sr);
    451 }
    452 
    453 
    454 struct TALER_KYCLOGIC_SanctionRater *
    455 TALER_KYCLOGIC_sanction_rater_start (const char *binary,
    456                                      char *const*argv)
    457 {
    458   struct TALER_KYCLOGIC_SanctionRater *sr;
    459   struct GNUNET_DISK_PipeHandle *pipe_stdin;
    460   struct GNUNET_DISK_PipeHandle *pipe_stdout;
    461 
    462   sr = GNUNET_new (struct TALER_KYCLOGIC_SanctionRater);
    463   pipe_stdin = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_READ);
    464   GNUNET_assert (NULL != pipe_stdin);
    465   pipe_stdout = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_WRITE);
    466   GNUNET_assert (NULL != pipe_stdout);
    467   sr->helper = GNUNET_OS_start_process_vap (GNUNET_OS_INHERIT_STD_ERR,
    468                                             pipe_stdin,
    469                                             pipe_stdout,
    470                                             NULL,
    471                                             binary,
    472                                             (char *const *) argv);
    473   if (NULL == sr->helper)
    474   {
    475     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    476                 "Failed to run conversion helper `%s'\n",
    477                 binary);
    478     GNUNET_break (GNUNET_OK ==
    479                   GNUNET_DISK_pipe_close (pipe_stdin));
    480     GNUNET_break (GNUNET_OK ==
    481                   GNUNET_DISK_pipe_close (pipe_stdout));
    482     GNUNET_free (sr);
    483     return NULL;
    484   }
    485   sr->chld_stdin =
    486     GNUNET_DISK_pipe_detach_end (pipe_stdin,
    487                                  GNUNET_DISK_PIPE_END_WRITE);
    488   sr->chld_stdout =
    489     GNUNET_DISK_pipe_detach_end (pipe_stdout,
    490                                  GNUNET_DISK_PIPE_END_READ);
    491   GNUNET_break (GNUNET_OK ==
    492                 GNUNET_DISK_pipe_close (pipe_stdin));
    493   GNUNET_break (GNUNET_OK ==
    494                 GNUNET_DISK_pipe_close (pipe_stdout));
    495 
    496   sr->read_task
    497     = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
    498                                       sr->chld_stdout,
    499                                       &read_cb,
    500                                       sr);
    501   sr->cwh = GNUNET_wait_child (sr->helper,
    502                                &child_done_cb,
    503                                sr);
    504   return sr;
    505 }
    506 
    507 
    508 struct TALER_KYCLOGIC_EvaluationEntry *
    509 TALER_KYCLOGIC_sanction_rater_eval (struct TALER_KYCLOGIC_SanctionRater *sr,
    510                                     const json_t *attributes,
    511                                     TALER_KYCLOGIC_SanctionResultCallback cb,
    512                                     void *cb_cls)
    513 {
    514   struct TALER_KYCLOGIC_EvaluationEntry *ee;
    515   char *js;
    516 
    517   if (NULL == sr->read_task)
    518     return NULL;
    519   ee = GNUNET_new (struct TALER_KYCLOGIC_EvaluationEntry);
    520   ee->cb = cb;
    521   ee->cb_cls = cb_cls;
    522   GNUNET_CONTAINER_DLL_insert (sr->ee_head,
    523                                sr->ee_tail,
    524                                ee);
    525   js = json_dumps (attributes,
    526                    JSON_COMPACT);
    527   GNUNET_asprintf (&ee->write_buf,
    528                    "%s\n",
    529                    js);
    530   free (js);
    531   ee->write_size = strlen (ee->write_buf);
    532   if (NULL == sr->write_task)
    533     sr->write_task
    534       = GNUNET_SCHEDULER_add_write_file (GNUNET_TIME_UNIT_FOREVER_REL,
    535                                          sr->chld_stdin,
    536                                          &write_cb,
    537                                          sr);
    538   return ee;
    539 }
    540 
    541 
    542 void
    543 TALER_KYCLOGIC_sanction_rater_stop (
    544   struct TALER_KYCLOGIC_SanctionRater *sr)
    545 {
    546   fail_hard (sr);
    547   if (NULL != sr->cwh)
    548   {
    549     GNUNET_wait_child_cancel (sr->cwh);
    550     sr->cwh = NULL;
    551   }
    552   if (NULL != sr->write_task)
    553   {
    554     GNUNET_SCHEDULER_cancel (sr->write_task);
    555     sr->write_task = NULL;
    556   }
    557   if (NULL != sr->chld_stdout)
    558   {
    559     GNUNET_break (GNUNET_OK ==
    560                   GNUNET_DISK_file_close (sr->chld_stdout));
    561     sr->chld_stdout = NULL;
    562   }
    563   GNUNET_free (sr->read_buf);
    564   GNUNET_free (sr);
    565 }