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 : }
|