LCOV - code coverage report
Current view: top level - kyclogic - kyclogic_sanctions.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 0 189 0.0 %
Date: 2025-08-30 09:28:00 Functions: 0 8 0.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 "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           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->helper)
     156             :   {
     157           0 :     GNUNET_OS_process_destroy (sr->helper);
     158           0 :     sr->helper = NULL;
     159             :   }
     160           0 :   while (NULL != (ee = sr->ee_tail))
     161             :   {
     162           0 :     GNUNET_CONTAINER_DLL_remove (sr->ee_head,
     163             :                                  sr->ee_tail,
     164             :                                  ee);
     165           0 :     ee->cb (ee->cb_cls,
     166             :             TALER_EC_EXCHANGE_GENERIC_KYC_SANCTION_LIST_CHECK_FAILED,
     167             :             NULL,
     168             :             1.0,
     169             :             0.0);
     170           0 :     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           0 :     GNUNET_free (ee->write_buf);
     174           0 :     GNUNET_free (ee);
     175             :   }
     176           0 : }
     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           0 : process_buffer (struct TALER_KYCLOGIC_SanctionRater *sr)
     187             : {
     188           0 :   const char *buf = sr->read_buf;
     189             :   size_t buf_len;
     190             :   void *end;
     191             : 
     192           0 :   end = memrchr (sr->read_buf,
     193             :                  '\n',
     194             :                  sr->read_pos);
     195           0 :   if ( (NULL == end) &&
     196           0 :        (sr->read_pos < 2048) )
     197           0 :     return true;
     198           0 :   if (NULL == end)
     199             :   {
     200             :     /* line returned by sanction rater way too long */
     201           0 :     GNUNET_break (0);
     202           0 :     return false;
     203             :   }
     204           0 :   end++;
     205           0 :   buf_len = end - sr->read_buf;
     206           0 :   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           0 :     nl = memchr (buf,
     215             :                  '\n',
     216             :                  buf_len);
     217           0 :     if (NULL == nl)
     218             :     {
     219             :       /* no newline in 2048 bytes? not allowed */
     220           0 :       GNUNET_break (0);
     221           0 :       return false;
     222             :     }
     223           0 :     *nl = '\0';
     224           0 :     line_len = nl - buf + 1;
     225           0 :     if (3 !=
     226           0 :         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           0 :       if (2 !=
     234           0 :           sscanf (buf,
     235             :                   "%lf %lf ",
     236             :                   &rating,
     237             :                   &confidence))
     238             :       {
     239           0 :         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     240             :                     "Malformed input line `%s'\n",
     241             :                     buf);
     242           0 :         GNUNET_break (0);
     243           0 :         return false;
     244             :       }
     245           0 :       strcpy (best_match,
     246             :               "<none>");
     247             :     }
     248             :     {
     249           0 :       struct TALER_KYCLOGIC_EvaluationEntry *ee = sr->ee_tail;
     250             : 
     251           0 :       GNUNET_CONTAINER_DLL_remove (sr->ee_head,
     252             :                                    sr->ee_tail,
     253             :                                    ee);
     254           0 :       ee->cb (ee->cb_cls,
     255             :               TALER_EC_NONE,
     256             :               best_match,
     257             :               rating,
     258             :               confidence);
     259           0 :       GNUNET_free (ee->write_buf);
     260           0 :       GNUNET_free (ee);
     261             :     }
     262           0 :     buf += line_len;
     263           0 :     buf_len -= line_len;
     264             :   }
     265           0 :   buf_len = end - sr->read_buf;
     266           0 :   memmove (sr->read_buf,
     267             :            end,
     268           0 :            sr->read_pos - buf_len);
     269           0 :   sr->read_pos -= buf_len;
     270           0 :   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           0 : read_cb (void *cls)
     282             : {
     283           0 :   struct TALER_KYCLOGIC_SanctionRater *sr = cls;
     284             : 
     285           0 :   sr->read_task = NULL;
     286             :   while (1)
     287           0 :   {
     288             :     ssize_t ret;
     289             : 
     290           0 :     if (sr->read_size == sr->read_pos)
     291             :     {
     292             :       /* Grow input buffer */
     293             :       size_t ns;
     294             :       void *tmp;
     295             : 
     296           0 :       ns = GNUNET_MAX (2 * sr->read_size,
     297             :                        1024);
     298           0 :       if (ns > GNUNET_MAX_MALLOC_CHECKED)
     299           0 :         ns = GNUNET_MAX_MALLOC_CHECKED;
     300           0 :       if (sr->read_size == ns)
     301             :       {
     302             :         /* Helper returned more than 40 MB of data! Stop reading! */
     303           0 :         GNUNET_break (0);
     304           0 :         GNUNET_break (GNUNET_OK ==
     305             :                       GNUNET_DISK_file_close (sr->chld_stdin));
     306           0 :         return;
     307             :       }
     308           0 :       tmp = GNUNET_malloc_large (ns);
     309           0 :       if (NULL == tmp)
     310             :       {
     311             :         /* out of memory, also stop reading */
     312           0 :         GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
     313             :                              "malloc");
     314           0 :         GNUNET_break (GNUNET_OK ==
     315             :                       GNUNET_DISK_file_close (sr->chld_stdin));
     316           0 :         return;
     317             :       }
     318           0 :       GNUNET_memcpy (tmp,
     319             :                      sr->read_buf,
     320             :                      sr->read_pos);
     321           0 :       GNUNET_free (sr->read_buf);
     322           0 :       sr->read_buf = tmp;
     323           0 :       sr->read_size = ns;
     324             :     }
     325           0 :     ret = GNUNET_DISK_file_read (sr->chld_stdout,
     326           0 :                                  sr->read_buf + sr->read_pos,
     327           0 :                                  sr->read_size - sr->read_pos);
     328           0 :     if (ret < 0)
     329             :     {
     330           0 :       if ( (EAGAIN != errno) &&
     331           0 :            (EWOULDBLOCK != errno) &&
     332           0 :            (EINTR != errno) )
     333             :       {
     334           0 :         GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
     335             :                              "read");
     336           0 :         return;
     337             :       }
     338             :       /* Continue later */
     339           0 :       break;
     340             :     }
     341           0 :     if (0 == ret)
     342             :     {
     343             :       /* regular end of stream, odd! */
     344           0 :       fail_hard (sr);
     345           0 :       return;
     346             :     }
     347           0 :     GNUNET_assert (sr->read_size >= sr->read_pos + ret);
     348           0 :     sr->read_pos += ret;
     349           0 :     if (! process_buffer (sr))
     350           0 :       return;
     351             :   }
     352             :   sr->read_task
     353           0 :     = GNUNET_SCHEDULER_add_read_file (
     354           0 :         GNUNET_TIME_UNIT_FOREVER_REL,
     355           0 :         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           0 : write_cb (void *cls)
     369             : {
     370           0 :   struct TALER_KYCLOGIC_SanctionRater *sr = cls;
     371           0 :   struct TALER_KYCLOGIC_EvaluationEntry *ee = sr->ee_tail;
     372             :   ssize_t ret;
     373             : 
     374           0 :   sr->write_task = NULL;
     375           0 :   while ( (NULL != ee) &&
     376           0 :           (ee->write_size == ee->write_pos) )
     377           0 :     ee = ee->prev;
     378           0 :   while (NULL != ee)
     379             :   {
     380           0 :     while (ee->write_size > ee->write_pos)
     381             :     {
     382           0 :       ret = GNUNET_DISK_file_write (sr->chld_stdin,
     383           0 :                                     ee->write_buf + ee->write_pos,
     384           0 :                                     ee->write_size - ee->write_pos);
     385           0 :       if (ret < 0)
     386             :       {
     387           0 :         if ( (EAGAIN != errno) &&
     388           0 :              (EINTR != errno) )
     389             :         {
     390           0 :           GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING,
     391             :                                "write");
     392             :           /* helper must have died */
     393           0 :           fail_hard (sr);
     394           0 :           return;
     395             :         }
     396           0 :         break;
     397             :       }
     398           0 :       if (0 == ret)
     399             :       {
     400           0 :         GNUNET_break (0);
     401           0 :         break;
     402             :       }
     403           0 :       GNUNET_assert (ee->write_size >= ee->write_pos + ret);
     404           0 :       ee->write_pos += ret;
     405             :     }
     406           0 :     if ( (ee->write_size > ee->write_pos) &&
     407           0 :          ( (EAGAIN == errno) ||
     408           0 :            (EWOULDBLOCK == errno) ||
     409           0 :            (EINTR == errno) ) )
     410             :     {
     411             :       sr->write_task
     412           0 :         = GNUNET_SCHEDULER_add_write_file (
     413           0 :             GNUNET_TIME_UNIT_FOREVER_REL,
     414           0 :             sr->chld_stdin,
     415             :             &write_cb,
     416             :             sr);
     417           0 :       return;
     418             :     }
     419           0 :     if (ee->write_size == ee->write_pos)
     420             :     {
     421           0 :       GNUNET_free (ee->write_buf);
     422           0 :       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           0 : child_done_cb (void *cls,
     439             :                enum GNUNET_OS_ProcessStatusType type,
     440             :                long unsigned int exit_code)
     441             : {
     442           0 :   struct TALER_KYCLOGIC_SanctionRater *sr = cls;
     443             : 
     444           0 :   sr->cwh = NULL;
     445           0 :   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           0 :   fail_hard (sr);
     451           0 : }
     452             : 
     453             : 
     454             : struct TALER_KYCLOGIC_SanctionRater *
     455           0 : 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           0 :   sr = GNUNET_new (struct TALER_KYCLOGIC_SanctionRater);
     463           0 :   pipe_stdin = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_READ);
     464           0 :   GNUNET_assert (NULL != pipe_stdin);
     465           0 :   pipe_stdout = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_WRITE);
     466           0 :   GNUNET_assert (NULL != pipe_stdout);
     467           0 :   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           0 :   if (NULL == sr->helper)
     474             :   {
     475           0 :     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     476             :                 "Failed to run conversion helper `%s'\n",
     477             :                 binary);
     478           0 :     GNUNET_break (GNUNET_OK ==
     479             :                   GNUNET_DISK_pipe_close (pipe_stdin));
     480           0 :     GNUNET_break (GNUNET_OK ==
     481             :                   GNUNET_DISK_pipe_close (pipe_stdout));
     482           0 :     GNUNET_free (sr);
     483           0 :     return NULL;
     484             :   }
     485           0 :   sr->chld_stdin =
     486           0 :     GNUNET_DISK_pipe_detach_end (pipe_stdin,
     487             :                                  GNUNET_DISK_PIPE_END_WRITE);
     488           0 :   sr->chld_stdout =
     489           0 :     GNUNET_DISK_pipe_detach_end (pipe_stdout,
     490             :                                  GNUNET_DISK_PIPE_END_READ);
     491           0 :   GNUNET_break (GNUNET_OK ==
     492             :                 GNUNET_DISK_pipe_close (pipe_stdin));
     493           0 :   GNUNET_break (GNUNET_OK ==
     494             :                 GNUNET_DISK_pipe_close (pipe_stdout));
     495             : 
     496             :   sr->read_task
     497           0 :     = GNUNET_SCHEDULER_add_read_file (GNUNET_TIME_UNIT_FOREVER_REL,
     498           0 :                                       sr->chld_stdout,
     499             :                                       &read_cb,
     500             :                                       sr);
     501           0 :   sr->cwh = GNUNET_wait_child (sr->helper,
     502             :                                &child_done_cb,
     503             :                                sr);
     504           0 :   return sr;
     505             : }
     506             : 
     507             : 
     508             : struct TALER_KYCLOGIC_EvaluationEntry *
     509           0 : 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           0 :   if (NULL == sr->read_task)
     518           0 :     return NULL;
     519           0 :   ee = GNUNET_new (struct TALER_KYCLOGIC_EvaluationEntry);
     520           0 :   ee->cb = cb;
     521           0 :   ee->cb_cls = cb_cls;
     522           0 :   GNUNET_CONTAINER_DLL_insert (sr->ee_head,
     523             :                                sr->ee_tail,
     524             :                                ee);
     525           0 :   js = json_dumps (attributes,
     526             :                    JSON_COMPACT);
     527           0 :   GNUNET_asprintf (&ee->write_buf,
     528             :                    "%s\n",
     529             :                    js);
     530           0 :   free (js);
     531           0 :   ee->write_size = strlen (ee->write_buf);
     532           0 :   if (NULL == sr->write_task)
     533             :     sr->write_task
     534           0 :       = GNUNET_SCHEDULER_add_write_file (GNUNET_TIME_UNIT_FOREVER_REL,
     535           0 :                                          sr->chld_stdin,
     536             :                                          &write_cb,
     537             :                                          sr);
     538           0 :   return ee;
     539             : }
     540             : 
     541             : 
     542             : void
     543           0 : TALER_KYCLOGIC_sanction_rater_stop (
     544             :   struct TALER_KYCLOGIC_SanctionRater *sr)
     545             : {
     546           0 :   fail_hard (sr);
     547           0 :   if (NULL != sr->cwh)
     548             :   {
     549           0 :     GNUNET_wait_child_cancel (sr->cwh);
     550           0 :     sr->cwh = NULL;
     551             :   }
     552           0 :   if (NULL != sr->write_task)
     553             :   {
     554           0 :     GNUNET_SCHEDULER_cancel (sr->write_task);
     555           0 :     sr->write_task = NULL;
     556             :   }
     557           0 :   if (NULL != sr->chld_stdout)
     558             :   {
     559           0 :     GNUNET_break (GNUNET_OK ==
     560             :                   GNUNET_DISK_file_close (sr->chld_stdout));
     561           0 :     sr->chld_stdout = NULL;
     562             :   }
     563           0 :   GNUNET_free (sr->read_buf);
     564           0 :   GNUNET_free (sr);
     565           0 : }

Generated by: LCOV version 1.16