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