LCOV - code coverage report
Current view: top level - kyclogic - kyclogic_sanctions.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 0.0 % 197 0
Test Date: 2026-09-09 15:11:34 Functions: 0.0 % 8 0

            Line data    Source code
       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            0 : fail_hard (struct TALER_KYCLOGIC_SanctionRater *sr)
     141              : {
     142              :   struct TALER_KYCLOGIC_EvaluationEntry *ee;
     143              : 
     144            0 :   if (NULL != sr->chld_stdin)
     145              :   {
     146            0 :     GNUNET_break (GNUNET_OK ==
     147              :                   GNUNET_DISK_file_close (sr->chld_stdin));
     148            0 :     sr->chld_stdin = NULL;
     149              :   }
     150            0 :   if (NULL != sr->read_task)
     151              :   {
     152            0 :     GNUNET_SCHEDULER_cancel (sr->read_task);
     153            0 :     sr->read_task = NULL;
     154              :   }
     155            0 :   if (NULL != sr->write_task)
     156              :   {
     157            0 :     GNUNET_SCHEDULER_cancel (sr->write_task);
     158            0 :     sr->write_task = NULL;
     159              :   }
     160            0 :   if (NULL != sr->helper)
     161              :   {
     162            0 :     GNUNET_process_destroy (sr->helper);
     163            0 :     sr->helper = NULL;
     164              :   }
     165            0 :   while (NULL != (ee = sr->ee_tail))
     166              :   {
     167            0 :     GNUNET_CONTAINER_DLL_remove (sr->ee_head,
     168              :                                  sr->ee_tail,
     169              :                                  ee);
     170            0 :     ee->cb (ee->cb_cls,
     171              :             TALER_EC_EXCHANGE_GENERIC_KYC_SANCTION_LIST_CHECK_FAILED,
     172              :             NULL,
     173              :             1.0,
     174              :             0.0);
     175            0 :     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            0 :     GNUNET_free (ee->write_buf);
     179            0 :     GNUNET_free (ee);
     180              :   }
     181            0 : }
     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            0 : process_buffer (struct TALER_KYCLOGIC_SanctionRater *sr)
     192              : {
     193            0 :   char *buf = sr->read_buf;
     194              :   size_t buf_len;
     195              :   const void *end;
     196              : 
     197            0 :   end = memrchr (sr->read_buf,
     198              :                  '\n',
     199              :                  sr->read_pos);
     200            0 :   if ( (NULL == end) &&
     201            0 :        (sr->read_pos < 2048) )
     202            0 :     return true;
     203            0 :   if (NULL == end)
     204              :   {
     205              :     /* line returned by sanction rater way too long */
     206            0 :     GNUNET_break (0);
     207            0 :     return false;
     208              :   }
     209            0 :   end++;
     210            0 :   buf_len = end - sr->read_buf;
     211            0 :   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            0 :     nl = memchr (buf,
     220              :                  '\n',
     221              :                  buf_len);
     222            0 :     if (NULL == nl)
     223              :     {
     224              :       /* no newline in 2048 bytes? not allowed */
     225            0 :       GNUNET_break (0);
     226            0 :       return false;
     227              :     }
     228            0 :     *nl = '\0';
     229            0 :     line_len = nl - buf + 1;
     230            0 :     if (3 !=
     231            0 :         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            0 :       if (2 !=
     239            0 :           sscanf (buf,
     240              :                   "%lf %lf ",
     241              :                   &rating,
     242              :                   &confidence))
     243              :       {
     244            0 :         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     245              :                     "Malformed input line `%s'\n",
     246              :                     buf);
     247            0 :         GNUNET_break (0);
     248            0 :         return false;
     249              :       }
     250            0 :       strcpy (best_match,
     251              :               "<none>");
     252              :     }
     253              : 
     254              :     {
     255            0 :       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            0 :       GNUNET_assert (NULL != ee);
     262            0 :       GNUNET_CONTAINER_DLL_remove (sr->ee_head,
     263              :                                    sr->ee_tail,
     264              :                                    ee);
     265            0 :       ee->cb (ee->cb_cls,
     266              :               TALER_EC_NONE,
     267              :               best_match,
     268              :               rating,
     269              :               confidence);
     270            0 :       GNUNET_free (ee->write_buf);
     271            0 :       GNUNET_free (ee);
     272              :     }
     273            0 :     buf += line_len;
     274            0 :     buf_len -= line_len;
     275              :   }
     276            0 :   buf_len = end - sr->read_buf;
     277            0 :   memmove (sr->read_buf,
     278              :            end,
     279            0 :            sr->read_pos - buf_len);
     280            0 :   sr->read_pos -= buf_len;
     281            0 :   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            0 : read_cb (void *cls)
     293              : {
     294            0 :   struct TALER_KYCLOGIC_SanctionRater *sr = cls;
     295              : 
     296            0 :   sr->read_task = NULL;
     297              :   while (1)
     298            0 :   {
     299              :     ssize_t ret;
     300              : 
     301            0 :     if (sr->read_size == sr->read_pos)
     302              :     {
     303              :       /* Grow input buffer */
     304              :       size_t ns;
     305              :       void *tmp;
     306              : 
     307            0 :       ns = GNUNET_MAX (2 * sr->read_size,
     308              :                        1024);
     309            0 :       if (ns > GNUNET_MAX_MALLOC_CHECKED)
     310            0 :         ns = GNUNET_MAX_MALLOC_CHECKED;
     311            0 :       if (sr->read_size == ns)
     312              :       {
     313              :         /* Helper returned more than 40 MB of data! Stop reading! */
     314            0 :         GNUNET_break (0);
     315            0 :         GNUNET_break (GNUNET_OK ==
     316              :                       GNUNET_DISK_file_close (sr->chld_stdin));
     317            0 :         return;
     318              :       }
     319            0 :       tmp = GNUNET_malloc_large (ns);
     320            0 :       if (NULL == tmp)
     321              :       {
     322              :         /* out of memory, also stop reading */
     323            0 :         GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
     324              :                              "malloc");
     325            0 :         GNUNET_break (GNUNET_OK ==
     326              :                       GNUNET_DISK_file_close (sr->chld_stdin));
     327            0 :         return;
     328              :       }
     329            0 :       GNUNET_memcpy (tmp,
     330              :                      sr->read_buf,
     331              :                      sr->read_pos);
     332            0 :       GNUNET_free (sr->read_buf);
     333            0 :       sr->read_buf = tmp;
     334            0 :       sr->read_size = ns;
     335              :     }
     336            0 :     ret = GNUNET_DISK_file_read (sr->chld_stdout,
     337            0 :                                  sr->read_buf + sr->read_pos,
     338            0 :                                  sr->read_size - sr->read_pos);
     339            0 :     if (ret < 0)
     340              :     {
     341            0 :       if ( (EAGAIN != errno) &&
     342            0 :            (EWOULDBLOCK != errno) &&
     343            0 :            (EINTR != errno) )
     344              :       {
     345            0 :         GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
     346              :                              "read");
     347            0 :         return;
     348              :       }
     349              :       /* Continue later */
     350            0 :       break;
     351              :     }
     352            0 :     if (0 == ret)
     353              :     {
     354              :       /* regular end of stream, odd! */
     355            0 :       fail_hard (sr);
     356            0 :       return;
     357              :     }
     358            0 :     GNUNET_assert (sr->read_size >= sr->read_pos + ret);
     359            0 :     sr->read_pos += ret;
     360            0 :     if (! process_buffer (sr))
     361            0 :       return;
     362              :   }
     363              :   sr->read_task
     364            0 :     = GNUNET_SCHEDULER_add_read_file (
     365            0 :         GNUNET_TIME_UNIT_FOREVER_REL,
     366            0 :         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            0 : write_cb (void *cls)
     380              : {
     381            0 :   struct TALER_KYCLOGIC_SanctionRater *sr = cls;
     382            0 :   struct TALER_KYCLOGIC_EvaluationEntry *ee = sr->ee_tail;
     383              :   ssize_t ret;
     384              : 
     385            0 :   sr->write_task = NULL;
     386            0 :   while ( (NULL != ee) &&
     387            0 :           (ee->write_size == ee->write_pos) )
     388            0 :     ee = ee->prev;
     389            0 :   while (NULL != ee)
     390              :   {
     391            0 :     while (ee->write_size > ee->write_pos)
     392              :     {
     393            0 :       ret = GNUNET_DISK_file_write (sr->chld_stdin,
     394            0 :                                     ee->write_buf + ee->write_pos,
     395            0 :                                     ee->write_size - ee->write_pos);
     396            0 :       if (ret < 0)
     397              :       {
     398            0 :         if ( (EAGAIN != errno) &&
     399            0 :              (EINTR != errno) )
     400              :         {
     401            0 :           GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
     402              :                                "write");
     403              :           /* helper must have died */
     404            0 :           fail_hard (sr);
     405            0 :           return;
     406              :         }
     407            0 :         break;
     408              :       }
     409            0 :       if (0 == ret)
     410              :       {
     411            0 :         GNUNET_break (0);
     412            0 :         break;
     413              :       }
     414            0 :       GNUNET_assert (ee->write_size >= ee->write_pos + ret);
     415            0 :       ee->write_pos += ret;
     416              :     }
     417            0 :     if ( (ee->write_size > ee->write_pos) &&
     418            0 :          ( (EAGAIN == errno) ||
     419            0 :            (EWOULDBLOCK == errno) ||
     420            0 :            (EINTR == errno) ) )
     421              :     {
     422              :       sr->write_task
     423            0 :         = GNUNET_SCHEDULER_add_write_file (
     424            0 :             GNUNET_TIME_UNIT_FOREVER_REL,
     425            0 :             sr->chld_stdin,
     426              :             &write_cb,
     427              :             sr);
     428            0 :       return;
     429              :     }
     430            0 :     if (ee->write_size == ee->write_pos)
     431              :     {
     432            0 :       GNUNET_free (ee->write_buf);
     433            0 :       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            0 : child_done_cb (void *cls,
     450              :                enum GNUNET_OS_ProcessStatusType type,
     451              :                long unsigned int exit_code)
     452              : {
     453            0 :   struct TALER_KYCLOGIC_SanctionRater *sr = cls;
     454              : 
     455            0 :   sr->cwh = NULL;
     456            0 :   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            0 :   fail_hard (sr);
     462            0 : }
     463              : 
     464              : 
     465              : struct TALER_KYCLOGIC_SanctionRater *
     466            0 : 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            0 :   sr = GNUNET_new (struct TALER_KYCLOGIC_SanctionRater);
     474            0 :   pipe_stdin = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_READ);
     475            0 :   GNUNET_assert (NULL != pipe_stdin);
     476            0 :   pipe_stdout = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_WRITE);
     477            0 :   GNUNET_assert (NULL != pipe_stdout);
     478            0 :   sr->helper = GNUNET_process_create (GNUNET_OS_INHERIT_STD_ERR);
     479            0 :   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            0 :   if (GNUNET_OK !=
     487            0 :       GNUNET_process_run_command_argv (sr->helper,
     488              :                                        binary,
     489              :                                        (const char **) argv))
     490              :   {
     491            0 :     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     492              :                 "Failed to run conversion helper `%s'\n",
     493              :                 binary);
     494            0 :     GNUNET_process_destroy (sr->helper);
     495            0 :     GNUNET_break (GNUNET_OK ==
     496              :                   GNUNET_DISK_pipe_close (pipe_stdin));
     497            0 :     GNUNET_break (GNUNET_OK ==
     498              :                   GNUNET_DISK_pipe_close (pipe_stdout));
     499            0 :     GNUNET_free (sr);
     500            0 :     return NULL;
     501              :   }
     502            0 :   sr->chld_stdin =
     503            0 :     GNUNET_DISK_pipe_detach_end (pipe_stdin,
     504              :                                  GNUNET_DISK_PIPE_END_WRITE);
     505            0 :   sr->chld_stdout =
     506            0 :     GNUNET_DISK_pipe_detach_end (pipe_stdout,
     507              :                                  GNUNET_DISK_PIPE_END_READ);
     508            0 :   GNUNET_break (GNUNET_OK ==
     509              :                 GNUNET_DISK_pipe_close (pipe_stdin));
     510            0 :   GNUNET_break (GNUNET_OK ==
     511              :                 GNUNET_DISK_pipe_close (pipe_stdout));
     512              : 
     513              :   sr->read_task
     514            0 :     = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
     515            0 :                                       sr->chld_stdout,
     516              :                                       &read_cb,
     517              :                                       sr);
     518            0 :   sr->cwh = GNUNET_wait_child (sr->helper,
     519              :                                &child_done_cb,
     520              :                                sr);
     521            0 :   return sr;
     522              : }
     523              : 
     524              : 
     525              : struct TALER_KYCLOGIC_EvaluationEntry *
     526            0 : 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            0 :   if (NULL == sr->read_task)
     535              :   {
     536            0 :     GNUNET_break (0);
     537            0 :     return NULL;
     538              :   }
     539            0 :   ee = GNUNET_new (struct TALER_KYCLOGIC_EvaluationEntry);
     540            0 :   ee->cb = cb;
     541            0 :   ee->cb_cls = cb_cls;
     542            0 :   GNUNET_CONTAINER_DLL_insert (sr->ee_head,
     543              :                                sr->ee_tail,
     544              :                                ee);
     545            0 :   js = json_dumps (attributes,
     546              :                    JSON_COMPACT);
     547            0 :   GNUNET_asprintf (&ee->write_buf,
     548              :                    "%s\n",
     549              :                    js);
     550            0 :   free (js);
     551            0 :   ee->write_size = strlen (ee->write_buf);
     552            0 :   if (NULL == sr->write_task)
     553              :     sr->write_task
     554            0 :       = GNUNET_SCHEDULER_add_write_file (GNUNET_TIME_UNIT_FOREVER_REL,
     555            0 :                                          sr->chld_stdin,
     556              :                                          &write_cb,
     557              :                                          sr);
     558            0 :   return ee;
     559              : }
     560              : 
     561              : 
     562              : void
     563            0 : TALER_KYCLOGIC_sanction_rater_stop (
     564              :   struct TALER_KYCLOGIC_SanctionRater *sr)
     565              : {
     566            0 :   fail_hard (sr);
     567            0 :   if (NULL != sr->cwh)
     568              :   {
     569            0 :     GNUNET_wait_child_cancel (sr->cwh);
     570            0 :     sr->cwh = NULL;
     571              :   }
     572            0 :   if (NULL != sr->write_task)
     573              :   {
     574            0 :     GNUNET_SCHEDULER_cancel (sr->write_task);
     575            0 :     sr->write_task = NULL;
     576              :   }
     577            0 :   if (NULL != sr->chld_stdout)
     578              :   {
     579            0 :     GNUNET_break (GNUNET_OK ==
     580              :                   GNUNET_DISK_file_close (sr->chld_stdout));
     581            0 :     sr->chld_stdout = NULL;
     582              :   }
     583            0 :   GNUNET_free (sr->read_buf);
     584            0 :   GNUNET_free (sr);
     585            0 : }
        

Generated by: LCOV version 2.0-1