Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2023, 2024 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify it under the
6 : terms of the GNU 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 General Public License for more details.
12 :
13 : You should have received a copy of the GNU General Public License along with
14 : TALER; see the file COPYING. If not, see
15 : <http://www.gnu.org/licenses/>
16 : */
17 : /**
18 : * @file lib/exchange_api_get_aml_measures.c
19 : * @brief Implementation of the GET /aml/$OFFICER_PUB/measures request
20 : * @author Christian Grothoff
21 : */
22 : #include "platform.h"
23 : #include <microhttpd.h> /* just for HTTP status codes */
24 : #include <gnunet/gnunet_util_lib.h>
25 : #include <gnunet/gnunet_curl_lib.h>
26 : #include "taler_exchange_service.h"
27 : #include "taler_json_lib.h"
28 : #include "exchange_api_handle.h"
29 : #include "taler_signatures.h"
30 : #include "exchange_api_curl_defaults.h"
31 :
32 :
33 : /**
34 : * Scrap buffer of temporary arrays.
35 : */
36 : struct Scrap
37 : {
38 : /**
39 : * Kept in DLL.
40 : */
41 : struct Scrap *next;
42 :
43 : /**
44 : * Kept in DLL.
45 : */
46 : struct Scrap *prev;
47 :
48 : /**
49 : * Pointer to our allocation.
50 : */
51 : const char **ptr;
52 : };
53 :
54 :
55 : /**
56 : * @brief A GET /aml/$OFFICER_PUB/measures Handle
57 : */
58 : struct TALER_EXCHANGE_AmlGetMeasuresHandle
59 : {
60 :
61 : /**
62 : * The url for this request.
63 : */
64 : char *url;
65 :
66 : /**
67 : * Handle for the request.
68 : */
69 : struct GNUNET_CURL_Job *job;
70 :
71 : /**
72 : * Function to call with the result.
73 : */
74 : TALER_EXCHANGE_AmlMeasuresCallback measures_cb;
75 :
76 : /**
77 : * Closure for @e measures_cb.
78 : */
79 : void *measures_cb_cls;
80 :
81 : /**
82 : * HTTP headers for the job.
83 : */
84 : struct curl_slist *job_headers;
85 :
86 : /**
87 : * Head of scrap list.
88 : */
89 : struct Scrap *scrap_head;
90 :
91 : /**
92 : * Tail of scrap list.
93 : */
94 : struct Scrap *scrap_tail;
95 : };
96 :
97 :
98 : /**
99 : * Parse AML measures.
100 : *
101 : * @param jroots JSON object with measure data
102 : * @param[out] roots where to write the result
103 : * @return #GNUNET_OK on success
104 : */
105 : static enum GNUNET_GenericReturnValue
106 0 : parse_aml_roots (
107 : const json_t *jroots,
108 : struct TALER_EXCHANGE_AvailableAmlMeasures *roots)
109 : {
110 : const json_t *obj;
111 : const char *name;
112 0 : size_t idx = 0;
113 :
114 0 : json_object_foreach ((json_t *) jroots, name, obj)
115 : {
116 0 : struct TALER_EXCHANGE_AvailableAmlMeasures *root
117 0 : = &roots[idx++];
118 : struct GNUNET_JSON_Specification spec[] = {
119 0 : GNUNET_JSON_spec_string ("check_name",
120 : &root->check_name),
121 0 : GNUNET_JSON_spec_string ("prog_name",
122 : &root->prog_name),
123 0 : GNUNET_JSON_spec_mark_optional (
124 : GNUNET_JSON_spec_object_const ("context",
125 : &root->context),
126 : NULL),
127 0 : GNUNET_JSON_spec_end ()
128 : };
129 :
130 0 : if (GNUNET_OK !=
131 0 : GNUNET_JSON_parse (obj,
132 : spec,
133 : NULL,
134 : NULL))
135 : {
136 0 : GNUNET_break_op (0);
137 0 : return GNUNET_SYSERR;
138 : }
139 0 : root->measure_name = name;
140 : }
141 0 : return GNUNET_OK;
142 : }
143 :
144 :
145 : /**
146 : * Create array of length @a len in scrap book.
147 : *
148 : * @param[in,out] lh context for allocations
149 : * @param len length of array
150 : * @return scrap array
151 : */
152 : static const char **
153 0 : make_scrap (
154 : struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh,
155 : unsigned int len)
156 : {
157 0 : struct Scrap *s = GNUNET_new (struct Scrap);
158 :
159 0 : s->ptr = GNUNET_new_array (len,
160 : const char *);
161 0 : GNUNET_CONTAINER_DLL_insert (lh->scrap_head,
162 : lh->scrap_tail,
163 : s);
164 0 : return s->ptr;
165 : }
166 :
167 :
168 : /**
169 : * Free all scrap space.
170 : *
171 : * @param[in,out] lh scrap context
172 : */
173 : static void
174 0 : free_scrap (struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh)
175 : {
176 : struct Scrap *s;
177 :
178 0 : while (NULL != (s = lh->scrap_head))
179 : {
180 0 : GNUNET_CONTAINER_DLL_remove (lh->scrap_head,
181 : lh->scrap_tail,
182 : s);
183 0 : GNUNET_free (s->ptr);
184 0 : GNUNET_free (s);
185 : }
186 0 : }
187 :
188 :
189 : /**
190 : * Convert JSON array of strings to string array.
191 : *
192 : * @param j JSON array to convert
193 : * @param[out] a array to initialize
194 : * @return true on success
195 : */
196 : static bool
197 0 : j_to_a (const json_t *j,
198 : const char **a)
199 : {
200 : const json_t *e;
201 : size_t idx;
202 :
203 0 : json_array_foreach ((json_t *) j, idx, e)
204 : {
205 0 : if (NULL == (a[idx] = json_string_value (e)))
206 0 : return false;
207 : }
208 0 : return true;
209 : }
210 :
211 :
212 : /**
213 : * Parse AML programs.
214 : *
215 : * @param[in,out] lh context for allocations
216 : * @param jprogs JSON object with program data
217 : * @param[out] progs where to write the result
218 : * @return #GNUNET_OK on success
219 : */
220 : static enum GNUNET_GenericReturnValue
221 0 : parse_aml_programs (
222 : struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh,
223 : const json_t *jprogs,
224 : struct TALER_EXCHANGE_AvailableAmlPrograms *progs)
225 : {
226 : const json_t *obj;
227 : const char *name;
228 0 : size_t idx = 0;
229 :
230 0 : json_object_foreach ((json_t *) jprogs, name, obj)
231 : {
232 0 : struct TALER_EXCHANGE_AvailableAmlPrograms *prog
233 0 : = &progs[idx++];
234 : const json_t *jcontext;
235 : const json_t *jinputs;
236 : struct GNUNET_JSON_Specification spec[] = {
237 0 : GNUNET_JSON_spec_string ("description",
238 : &prog->description),
239 0 : GNUNET_JSON_spec_array_const ("context",
240 : &jcontext),
241 0 : GNUNET_JSON_spec_array_const ("inputs",
242 : &jinputs),
243 0 : GNUNET_JSON_spec_end ()
244 : };
245 : unsigned int len;
246 : const char **ptr;
247 :
248 0 : if (GNUNET_OK !=
249 0 : GNUNET_JSON_parse (obj,
250 : spec,
251 : NULL,
252 : NULL))
253 : {
254 0 : GNUNET_break_op (0);
255 0 : return GNUNET_SYSERR;
256 : }
257 0 : prog->prog_name = name;
258 : prog->contexts_length
259 0 : = (unsigned int) json_array_size (jcontext);
260 : prog->inputs_length
261 0 : = (unsigned int) json_array_size (jinputs);
262 0 : len = prog->contexts_length + prog->inputs_length;
263 0 : if ( ((unsigned long long) len) !=
264 0 : (unsigned long long) json_array_size (jcontext)
265 0 : + (unsigned long long) json_array_size (jinputs) )
266 : {
267 0 : GNUNET_break_op (0);
268 0 : return GNUNET_SYSERR;
269 : }
270 0 : ptr = make_scrap (lh,
271 : len);
272 0 : prog->contexts = ptr;
273 0 : if (! j_to_a (jcontext,
274 : prog->contexts))
275 : {
276 0 : GNUNET_break_op (0);
277 0 : return GNUNET_SYSERR;
278 : }
279 0 : prog->inputs = &ptr[prog->contexts_length];
280 0 : if (! j_to_a (jinputs,
281 : prog->inputs))
282 : {
283 0 : GNUNET_break_op (0);
284 0 : return GNUNET_SYSERR;
285 : }
286 : }
287 0 : return GNUNET_OK;
288 : }
289 :
290 :
291 : /**
292 : * Parse AML measures.
293 : *
294 : * @param[in,out] lh context for allocations
295 : * @param jchecks JSON object with measure data
296 : * @param[out] checks where to write the result
297 : * @return #GNUNET_OK on success
298 : */
299 : static enum GNUNET_GenericReturnValue
300 0 : parse_aml_checks (
301 : struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh,
302 : const json_t *jchecks,
303 : struct TALER_EXCHANGE_AvailableKycChecks *checks)
304 : {
305 : const json_t *obj;
306 : const char *name;
307 0 : size_t idx = 0;
308 :
309 0 : json_object_foreach ((json_t *) jchecks, name, obj)
310 : {
311 0 : struct TALER_EXCHANGE_AvailableKycChecks *check
312 0 : = &checks[idx++];
313 : const json_t *jrequires;
314 : const json_t *joutputs;
315 : struct GNUNET_JSON_Specification spec[] = {
316 0 : GNUNET_JSON_spec_string ("description",
317 : &check->description),
318 0 : GNUNET_JSON_spec_mark_optional (
319 : GNUNET_JSON_spec_object_const ("description_i18n",
320 : &check->description_i18n),
321 : NULL),
322 0 : GNUNET_JSON_spec_array_const ("requires",
323 : &jrequires),
324 0 : GNUNET_JSON_spec_array_const ("outputs",
325 : &joutputs),
326 0 : GNUNET_JSON_spec_string ("fallback",
327 : &check->fallback),
328 0 : GNUNET_JSON_spec_end ()
329 : };
330 : unsigned int len;
331 : const char **ptr;
332 :
333 0 : if (GNUNET_OK !=
334 0 : GNUNET_JSON_parse (obj,
335 : spec,
336 : NULL,
337 : NULL))
338 : {
339 0 : GNUNET_break_op (0);
340 0 : return GNUNET_SYSERR;
341 : }
342 0 : check->check_name = name;
343 :
344 : check->requires_length
345 0 : = (unsigned int) json_array_size (jrequires);
346 : check->outputs_length
347 0 : = (unsigned int) json_array_size (joutputs);
348 0 : len = check->requires_length + check->outputs_length;
349 0 : if ( ((unsigned long long) len) !=
350 0 : (unsigned long long) json_array_size (jrequires)
351 0 : + (unsigned long long) json_array_size (joutputs) )
352 : {
353 0 : GNUNET_break_op (0);
354 0 : return GNUNET_SYSERR;
355 : }
356 0 : ptr = make_scrap (lh,
357 : len);
358 0 : check->requires = ptr;
359 0 : if (! j_to_a (jrequires,
360 : check->requires))
361 : {
362 0 : GNUNET_break_op (0);
363 0 : return GNUNET_SYSERR;
364 : }
365 0 : check->outputs = &ptr[check->requires_length];
366 0 : if (! j_to_a (joutputs,
367 : check->outputs))
368 : {
369 0 : GNUNET_break_op (0);
370 0 : return GNUNET_SYSERR;
371 : }
372 : }
373 0 : return GNUNET_OK;
374 : }
375 :
376 :
377 : /**
378 : * Parse the provided decision data from the "200 OK" response.
379 : *
380 : * @param[in,out] lh handle (callback may be zero'ed out)
381 : * @param json json reply with the data for one coin
382 : * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
383 : */
384 : static enum GNUNET_GenericReturnValue
385 0 : parse_measures_ok (struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh,
386 : const json_t *json)
387 : {
388 0 : struct TALER_EXCHANGE_AmlGetMeasuresResponse lr = {
389 : .hr.reply = json,
390 : .hr.http_status = MHD_HTTP_OK
391 : };
392 : const json_t *jroots;
393 : const json_t *jprograms;
394 : const json_t *jchecks;
395 : struct GNUNET_JSON_Specification spec[] = {
396 0 : GNUNET_JSON_spec_object_const ("roots",
397 : &jroots),
398 0 : GNUNET_JSON_spec_object_const ("programs",
399 : &jprograms),
400 0 : GNUNET_JSON_spec_object_const ("checks",
401 : &jchecks),
402 0 : GNUNET_JSON_spec_end ()
403 : };
404 :
405 0 : if (GNUNET_OK !=
406 0 : GNUNET_JSON_parse (json,
407 : spec,
408 : NULL,
409 : NULL))
410 : {
411 0 : GNUNET_break_op (0);
412 0 : return GNUNET_SYSERR;
413 : }
414 : lr.details.ok.roots_length
415 0 : = (unsigned int) json_object_size (jroots);
416 : lr.details.ok.programs_length
417 0 : = (unsigned int) json_object_size (jprograms);
418 : lr.details.ok.checks_length
419 0 : = (unsigned int) json_object_size (jchecks);
420 0 : if ( ( ((size_t) lr.details.ok.roots_length)
421 0 : != json_object_size (jroots)) ||
422 0 : ( ((size_t) lr.details.ok.programs_length)
423 0 : != json_object_size (jprograms)) ||
424 0 : ( ((size_t) lr.details.ok.checks_length)
425 0 : != json_object_size (jchecks)) )
426 : {
427 0 : GNUNET_break_op (0);
428 0 : return GNUNET_SYSERR;
429 : }
430 :
431 0 : {
432 0 : struct TALER_EXCHANGE_AvailableAmlMeasures roots[
433 0 : GNUNET_NZL (lr.details.ok.roots_length)];
434 0 : struct TALER_EXCHANGE_AvailableAmlPrograms progs[
435 0 : GNUNET_NZL (lr.details.ok.programs_length)];
436 0 : struct TALER_EXCHANGE_AvailableKycChecks checks[
437 0 : GNUNET_NZL (lr.details.ok.checks_length)];
438 : enum GNUNET_GenericReturnValue ret;
439 :
440 0 : memset (roots,
441 : 0,
442 : sizeof (roots));
443 0 : memset (progs,
444 : 0,
445 : sizeof (progs));
446 0 : memset (checks,
447 : 0,
448 : sizeof (checks));
449 0 : lr.details.ok.roots = roots;
450 0 : lr.details.ok.programs = progs;
451 0 : lr.details.ok.checks = checks;
452 0 : ret = parse_aml_roots (jroots,
453 : roots);
454 0 : if (GNUNET_OK == ret)
455 0 : ret = parse_aml_programs (lh,
456 : jprograms,
457 : progs);
458 0 : if (GNUNET_OK == ret)
459 0 : ret = parse_aml_checks (lh,
460 : jchecks,
461 : checks);
462 0 : if (GNUNET_OK == ret)
463 : {
464 0 : lh->measures_cb (lh->measures_cb_cls,
465 : &lr);
466 0 : lh->measures_cb = NULL;
467 : }
468 0 : free_scrap (lh);
469 0 : return ret;
470 : }
471 : }
472 :
473 :
474 : /**
475 : * Function called when we're done processing the
476 : * HTTP /aml/$OFFICER_PUB/measures request.
477 : *
478 : * @param cls the `struct TALER_EXCHANGE_AmlGetMeasuresHandle`
479 : * @param response_code HTTP response code, 0 on error
480 : * @param response parsed JSON result, NULL on error
481 : */
482 : static void
483 0 : handle_lookup_finished (void *cls,
484 : long response_code,
485 : const void *response)
486 : {
487 0 : struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh = cls;
488 0 : const json_t *j = response;
489 0 : struct TALER_EXCHANGE_AmlGetMeasuresResponse lr = {
490 : .hr.reply = j,
491 0 : .hr.http_status = (unsigned int) response_code
492 : };
493 :
494 0 : lh->job = NULL;
495 0 : switch (response_code)
496 : {
497 0 : case 0:
498 0 : lr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
499 0 : break;
500 0 : case MHD_HTTP_OK:
501 0 : if (GNUNET_OK !=
502 0 : parse_measures_ok (lh,
503 : j))
504 : {
505 0 : GNUNET_break_op (0);
506 0 : lr.hr.http_status = 0;
507 0 : lr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
508 0 : break;
509 : }
510 0 : GNUNET_assert (NULL == lh->measures_cb);
511 0 : TALER_EXCHANGE_aml_get_measures_cancel (lh);
512 0 : return;
513 0 : case MHD_HTTP_NO_CONTENT:
514 0 : break;
515 0 : case MHD_HTTP_BAD_REQUEST:
516 0 : lr.hr.ec = TALER_JSON_get_error_code (j);
517 0 : lr.hr.hint = TALER_JSON_get_error_hint (j);
518 : /* This should never happen, either us or the exchange is buggy
519 : (or API version conflict); just pass JSON reply to the application */
520 0 : break;
521 0 : case MHD_HTTP_FORBIDDEN:
522 0 : lr.hr.ec = TALER_JSON_get_error_code (j);
523 0 : lr.hr.hint = TALER_JSON_get_error_hint (j);
524 : /* Nothing really to verify, exchange says this coin was not melted; we
525 : should pass the JSON reply to the application */
526 0 : break;
527 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
528 0 : lr.hr.ec = TALER_JSON_get_error_code (j);
529 0 : lr.hr.hint = TALER_JSON_get_error_hint (j);
530 : /* Server had an internal issue; we should retry, but this API
531 : leaves this to the application */
532 0 : break;
533 0 : default:
534 : /* unexpected response code */
535 0 : GNUNET_break_op (0);
536 0 : lr.hr.ec = TALER_JSON_get_error_code (j);
537 0 : lr.hr.hint = TALER_JSON_get_error_hint (j);
538 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
539 : "Unexpected response code %u/%d for get AML measures\n",
540 : (unsigned int) response_code,
541 : (int) lr.hr.ec);
542 0 : break;
543 : }
544 0 : if (NULL != lh->measures_cb)
545 0 : lh->measures_cb (lh->measures_cb_cls,
546 : &lr);
547 0 : TALER_EXCHANGE_aml_get_measures_cancel (lh);
548 : }
549 :
550 :
551 : struct TALER_EXCHANGE_AmlGetMeasuresHandle *
552 0 : TALER_EXCHANGE_aml_get_measures (
553 : struct GNUNET_CURL_Context *ctx,
554 : const char *exchange_url,
555 : const struct TALER_AmlOfficerPrivateKeyP *officer_priv,
556 : TALER_EXCHANGE_AmlMeasuresCallback cb,
557 : void *cb_cls)
558 : {
559 : struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh;
560 : CURL *eh;
561 : struct TALER_AmlOfficerPublicKeyP officer_pub;
562 : struct TALER_AmlOfficerSignatureP officer_sig;
563 : char arg_str[sizeof (struct TALER_AmlOfficerPublicKeyP) * 2
564 : + 32];
565 :
566 0 : GNUNET_CRYPTO_eddsa_key_get_public (&officer_priv->eddsa_priv,
567 : &officer_pub.eddsa_pub);
568 0 : TALER_officer_aml_query_sign (officer_priv,
569 : &officer_sig);
570 : {
571 : char pub_str[sizeof (officer_pub) * 2];
572 : char *end;
573 :
574 0 : end = GNUNET_STRINGS_data_to_string (
575 : &officer_pub,
576 : sizeof (officer_pub),
577 : pub_str,
578 : sizeof (pub_str));
579 0 : *end = '\0';
580 0 : GNUNET_snprintf (arg_str,
581 : sizeof (arg_str),
582 : "aml/%s/measures",
583 : pub_str);
584 : }
585 0 : lh = GNUNET_new (struct TALER_EXCHANGE_AmlGetMeasuresHandle);
586 0 : lh->measures_cb = cb;
587 0 : lh->measures_cb_cls = cb_cls;
588 0 : lh->url = TALER_url_join (exchange_url,
589 : arg_str,
590 : NULL);
591 0 : if (NULL == lh->url)
592 : {
593 0 : GNUNET_free (lh);
594 0 : return NULL;
595 : }
596 0 : eh = TALER_EXCHANGE_curl_easy_get_ (lh->url);
597 0 : if (NULL == eh)
598 : {
599 0 : GNUNET_break (0);
600 0 : GNUNET_free (lh->url);
601 0 : GNUNET_free (lh);
602 0 : return NULL;
603 : }
604 : {
605 : char *hdr;
606 : char sig_str[sizeof (officer_sig) * 2];
607 : char *end;
608 :
609 0 : end = GNUNET_STRINGS_data_to_string (
610 : &officer_sig,
611 : sizeof (officer_sig),
612 : sig_str,
613 : sizeof (sig_str));
614 0 : *end = '\0';
615 :
616 0 : GNUNET_asprintf (&hdr,
617 : "%s: %s",
618 : TALER_AML_OFFICER_SIGNATURE_HEADER,
619 : sig_str);
620 0 : lh->job_headers = curl_slist_append (NULL,
621 : hdr);
622 0 : GNUNET_free (hdr);
623 0 : lh->job_headers = curl_slist_append (lh->job_headers,
624 : "Content-type: application/json");
625 0 : lh->job = GNUNET_CURL_job_add2 (ctx,
626 : eh,
627 0 : lh->job_headers,
628 : &handle_lookup_finished,
629 : lh);
630 : }
631 0 : return lh;
632 : }
633 :
634 :
635 : void
636 0 : TALER_EXCHANGE_aml_get_measures_cancel (
637 : struct TALER_EXCHANGE_AmlGetMeasuresHandle *lh)
638 : {
639 0 : if (NULL != lh->job)
640 : {
641 0 : GNUNET_CURL_job_cancel (lh->job);
642 0 : lh->job = NULL;
643 : }
644 0 : curl_slist_free_all (lh->job_headers);
645 0 : GNUNET_free (lh->url);
646 0 : GNUNET_free (lh);
647 0 : }
648 :
649 :
650 : /* end of exchange_api_get_aml_measures.c */
|