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

Generated by: LCOV version 1.16