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