Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2023, 2024, 2026 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-OFFICER_PUB-decisions.c
19 : * @brief Implementation of the /aml/$OFFICER_PUB/decisions request
20 : * @author Christian Grothoff
21 : */
22 : #include <microhttpd.h> /* just for HTTP status codes */
23 : #include <gnunet/gnunet_util_lib.h>
24 : #include <gnunet/gnunet_curl_lib.h>
25 : #include "taler/taler_json_lib.h"
26 : #include "taler/exchange/get-aml-OFFICER_PUB-decisions.h"
27 : #include "exchange_api_handle.h"
28 : #include "taler/taler_signatures.h"
29 : #include "exchange_api_curl_defaults.h"
30 :
31 : #define DEBUG 0
32 :
33 : /**
34 : * @brief A GET /aml/$OFFICER_PUB/decisions Handle
35 : */
36 : struct TALER_EXCHANGE_GetAmlDecisionsHandle
37 : {
38 :
39 : /**
40 : * The base URL of the exchange.
41 : */
42 : char *base_url;
43 :
44 : /**
45 : * The full URL for this request, set during _start.
46 : */
47 : char *url;
48 :
49 : /**
50 : * Handle for the request.
51 : */
52 : struct GNUNET_CURL_Job *job;
53 :
54 : /**
55 : * Function to call with the result.
56 : */
57 : TALER_EXCHANGE_GetAmlDecisionsCallback cb;
58 :
59 : /**
60 : * Closure for @e cb.
61 : */
62 : TALER_EXCHANGE_GET_AML_DECISIONS_RESULT_CLOSURE *cb_cls;
63 :
64 : /**
65 : * Reference to the execution context.
66 : */
67 : struct GNUNET_CURL_Context *ctx;
68 :
69 : /**
70 : * Public key of the AML officer.
71 : */
72 : struct TALER_AmlOfficerPublicKeyP officer_pub;
73 :
74 : /**
75 : * Private key of the AML officer (for signing).
76 : */
77 : struct TALER_AmlOfficerPrivateKeyP officer_priv;
78 :
79 : /**
80 : * Signature of the AML officer.
81 : */
82 : struct TALER_AmlOfficerSignatureP officer_sig;
83 :
84 : /**
85 : * Options for the request.
86 : */
87 : struct
88 : {
89 : /**
90 : * Limit on number of results (-20 by default).
91 : */
92 : int64_t limit;
93 :
94 : /**
95 : * Row offset threshold (INT64_MAX by default).
96 : */
97 : uint64_t offset;
98 :
99 : /**
100 : * Optional account filter; NULL if not set.
101 : */
102 : const struct TALER_NormalizedPaytoHashP *h_payto;
103 :
104 : /**
105 : * Filter for active decisions (YNA_ALL by default).
106 : */
107 : enum TALER_EXCHANGE_YesNoAll active;
108 :
109 : /**
110 : * Filter for investigation status (YNA_ALL by default).
111 : */
112 : enum TALER_EXCHANGE_YesNoAll investigation;
113 : } options;
114 :
115 : /**
116 : * Flat array of all KYC rules across all decisions (allocated during parse).
117 : */
118 : struct TALER_EXCHANGE_GetAmlDecisionsKycRule *all_rules;
119 :
120 : /**
121 : * Flat array of all measure string pointers across all rules (allocated during parse).
122 : */
123 : const char **all_mp;
124 :
125 : };
126 :
127 :
128 : /**
129 : * Parse the limits/rules object.
130 : *
131 : * @param[in,out] adgh handle (used for allocation tracking)
132 : * @param jlimits JSON object with legitimization rule set data
133 : * @param[out] limits where to write the parsed rule set
134 : * @param[in,out] rule_off current offset into adgh->all_rules (advanced)
135 : * @param[in,out] mp_off current offset into adgh->all_mp (advanced)
136 : * @return #GNUNET_OK on success
137 : */
138 : static enum GNUNET_GenericReturnValue
139 2 : parse_limits (
140 : struct TALER_EXCHANGE_GetAmlDecisionsHandle *adgh,
141 : const json_t *jlimits,
142 : struct TALER_EXCHANGE_GetAmlDecisionsLegitimizationRuleSet *limits,
143 : size_t *rule_off,
144 : size_t *mp_off)
145 : {
146 : const json_t *jrules;
147 : const json_t *jcustom_measures;
148 : struct GNUNET_JSON_Specification spec[] = {
149 2 : GNUNET_JSON_spec_timestamp ("expiration_time",
150 : &limits->expiration_time),
151 2 : GNUNET_JSON_spec_mark_optional (
152 : GNUNET_JSON_spec_string ("successor_measure",
153 : &limits->successor_measure),
154 : NULL),
155 2 : GNUNET_JSON_spec_array_const ("rules",
156 : &jrules),
157 2 : GNUNET_JSON_spec_mark_optional (
158 : GNUNET_JSON_spec_object_const ("custom_measures",
159 : &jcustom_measures),
160 : NULL),
161 2 : GNUNET_JSON_spec_end ()
162 : };
163 :
164 2 : if (GNUNET_OK !=
165 2 : GNUNET_JSON_parse (jlimits,
166 : spec,
167 : NULL,
168 : NULL))
169 : {
170 0 : GNUNET_break_op (0);
171 0 : return GNUNET_SYSERR;
172 : }
173 2 : limits->custom_measures = jcustom_measures;
174 :
175 : {
176 2 : size_t rule_count = json_array_size (jrules);
177 2 : size_t rule_start = *rule_off;
178 :
179 2 : limits->rules = &adgh->all_rules[rule_start];
180 2 : limits->rules_length = rule_count;
181 :
182 : {
183 : const json_t *jrule;
184 : size_t ridx;
185 :
186 9 : json_array_foreach ((json_t *) jrules, ridx, jrule)
187 : {
188 7 : struct TALER_EXCHANGE_GetAmlDecisionsKycRule *r
189 7 : = &adgh->all_rules[*rule_off];
190 : const json_t *jsmeasures;
191 : struct GNUNET_JSON_Specification rspec[] = {
192 7 : TALER_JSON_spec_kycte ("operation_type",
193 : &r->operation_type),
194 7 : GNUNET_JSON_spec_mark_optional (
195 : GNUNET_JSON_spec_string ("rule_name",
196 : &r->rule_name),
197 : NULL),
198 7 : TALER_JSON_spec_amount_any ("threshold",
199 : &r->threshold),
200 7 : GNUNET_JSON_spec_relative_time ("timeframe",
201 : &r->timeframe),
202 7 : GNUNET_JSON_spec_array_const ("measures",
203 : &jsmeasures),
204 7 : GNUNET_JSON_spec_mark_optional (
205 : GNUNET_JSON_spec_bool ("exposed",
206 : &r->exposed),
207 : NULL),
208 7 : GNUNET_JSON_spec_mark_optional (
209 : GNUNET_JSON_spec_bool ("is_and_combinator",
210 : &r->is_and_combinator),
211 : NULL),
212 7 : GNUNET_JSON_spec_int64 ("display_priority",
213 : &r->display_priority),
214 7 : GNUNET_JSON_spec_end ()
215 : };
216 :
217 7 : if (GNUNET_OK !=
218 7 : GNUNET_JSON_parse (jrule,
219 : rspec,
220 : NULL,
221 : NULL))
222 : {
223 0 : GNUNET_break_op (0);
224 0 : return GNUNET_SYSERR;
225 : }
226 :
227 : {
228 7 : size_t mlen = json_array_size (jsmeasures);
229 7 : size_t mp_start = *mp_off;
230 :
231 7 : r->measures = &adgh->all_mp[mp_start];
232 7 : r->measures_length = mlen;
233 :
234 : {
235 : size_t midx;
236 : const json_t *jm;
237 :
238 13 : json_array_foreach (jsmeasures, midx, jm)
239 : {
240 6 : const char *sval = json_string_value (jm);
241 :
242 6 : if (NULL == sval)
243 : {
244 0 : GNUNET_break_op (0);
245 0 : return GNUNET_SYSERR;
246 : }
247 6 : adgh->all_mp[*mp_off] = sval;
248 6 : (*mp_off)++;
249 : }
250 : }
251 : }
252 :
253 7 : (*rule_off)++;
254 : }
255 : }
256 : }
257 :
258 2 : return GNUNET_OK;
259 : }
260 :
261 :
262 : /**
263 : * Parse AML decision records.
264 : *
265 : * @param[in,out] adgh handle (for allocations)
266 : * @param jrecords JSON array of decision records
267 : * @param records_ar_length length of @a records_ar
268 : * @param[out] records_ar caller-allocated array to fill
269 : * @return #GNUNET_OK on success
270 : */
271 : static enum GNUNET_GenericReturnValue
272 2 : parse_aml_decisions (
273 : struct TALER_EXCHANGE_GetAmlDecisionsHandle *adgh,
274 : const json_t *jrecords,
275 : size_t records_ar_length,
276 : struct TALER_EXCHANGE_GetAmlDecisionsDecision *records_ar)
277 : {
278 2 : size_t rule_off = 0;
279 2 : size_t mp_off = 0;
280 : const json_t *obj;
281 : size_t idx;
282 :
283 4 : json_array_foreach ((json_t *) jrecords, idx, obj)
284 : {
285 2 : struct TALER_EXCHANGE_GetAmlDecisionsDecision *decision = &records_ar[idx];
286 : const json_t *jlimits;
287 : struct GNUNET_JSON_Specification spec[] = {
288 2 : GNUNET_JSON_spec_fixed_auto ("h_payto",
289 : &decision->h_payto),
290 2 : GNUNET_JSON_spec_mark_optional (
291 : GNUNET_JSON_spec_string ("full_payto",
292 : &decision->full_payto),
293 : NULL),
294 2 : GNUNET_JSON_spec_mark_optional (
295 : GNUNET_JSON_spec_bool ("is_wallet",
296 : &decision->is_wallet),
297 : NULL),
298 2 : GNUNET_JSON_spec_uint64 ("rowid",
299 : &decision->rowid),
300 2 : GNUNET_JSON_spec_mark_optional (
301 : GNUNET_JSON_spec_string ("justification",
302 : &decision->justification),
303 : NULL),
304 2 : GNUNET_JSON_spec_mark_optional (
305 : GNUNET_JSON_spec_string ("new_measures",
306 : &decision->new_measures),
307 : NULL),
308 2 : GNUNET_JSON_spec_mark_optional (
309 2 : GNUNET_JSON_spec_fixed_auto ("decider_pub",
310 : &decision->decider_pub),
311 : NULL),
312 2 : GNUNET_JSON_spec_mark_optional (
313 : GNUNET_JSON_spec_string ("decider_name",
314 : &decision->decider_name),
315 : NULL),
316 2 : GNUNET_JSON_spec_mark_optional (
317 : GNUNET_JSON_spec_uint64 ("kyc_attributes_rowid",
318 : &decision->kyc_attributes_rowid),
319 : NULL),
320 2 : GNUNET_JSON_spec_timestamp ("decision_time",
321 : &decision->decision_time),
322 2 : GNUNET_JSON_spec_mark_optional (
323 : GNUNET_JSON_spec_object_const ("properties",
324 : &decision->properties),
325 : NULL),
326 2 : GNUNET_JSON_spec_object_const ("limits",
327 : &jlimits),
328 2 : GNUNET_JSON_spec_bool ("to_investigate",
329 : &decision->to_investigate),
330 2 : GNUNET_JSON_spec_bool ("is_active",
331 : &decision->is_active),
332 2 : GNUNET_JSON_spec_end ()
333 : };
334 :
335 2 : GNUNET_assert (idx < records_ar_length);
336 2 : if (GNUNET_OK !=
337 2 : GNUNET_JSON_parse (obj,
338 : spec,
339 : NULL,
340 : NULL))
341 : {
342 0 : GNUNET_break_op (0);
343 0 : return GNUNET_SYSERR;
344 : }
345 :
346 2 : if (GNUNET_OK !=
347 2 : parse_limits (adgh,
348 : jlimits,
349 : &decision->limits,
350 : &rule_off,
351 : &mp_off))
352 : {
353 0 : GNUNET_break_op (0);
354 0 : return GNUNET_SYSERR;
355 : }
356 : }
357 2 : return GNUNET_OK;
358 : }
359 :
360 :
361 : /**
362 : * Parse the provided decision data from the "200 OK" response.
363 : *
364 : * @param[in,out] adgh handle (callback may be zero'ed out)
365 : * @param json json reply with the data
366 : * @return #GNUNET_OK on success, #GNUNET_SYSERR on error
367 : */
368 : static enum GNUNET_GenericReturnValue
369 2 : parse_get_aml_decisions_ok (struct TALER_EXCHANGE_GetAmlDecisionsHandle *adgh,
370 : const json_t *json)
371 : {
372 2 : struct TALER_EXCHANGE_GetAmlDecisionsResponse lr = {
373 : .hr.reply = json,
374 : .hr.http_status = MHD_HTTP_OK
375 : };
376 : const json_t *jrecords;
377 : struct GNUNET_JSON_Specification spec[] = {
378 2 : GNUNET_JSON_spec_array_const ("records",
379 : &jrecords),
380 2 : GNUNET_JSON_spec_end ()
381 : };
382 :
383 2 : if (GNUNET_OK !=
384 2 : GNUNET_JSON_parse (json,
385 : spec,
386 : NULL,
387 : NULL))
388 : {
389 0 : GNUNET_break_op (0);
390 0 : return GNUNET_SYSERR;
391 : }
392 :
393 2 : lr.details.ok.records_length = json_array_size (jrecords);
394 2 : if (lr.details.ok.records_length >
395 : GNUNET_MAX_MALLOC_CHECKED
396 : / sizeof (struct TALER_EXCHANGE_GetAmlDecisionsDecision))
397 : {
398 0 : GNUNET_break_op (0);
399 0 : return GNUNET_SYSERR;
400 : }
401 :
402 : /* First pass: count total rules and measures across all records */
403 : {
404 2 : size_t total_rules = 0;
405 2 : size_t total_measures = 0;
406 : const json_t *obj;
407 : size_t idx;
408 :
409 4 : json_array_foreach ((json_t *) jrecords, idx, obj)
410 : {
411 2 : const json_t *jlimits = json_object_get (obj, "limits");
412 : const json_t *jrules;
413 :
414 2 : if (NULL == jlimits)
415 0 : continue;
416 2 : jrules = json_object_get (jlimits, "rules");
417 2 : if (NULL == jrules)
418 0 : continue;
419 2 : total_rules += json_array_size (jrules);
420 :
421 : {
422 : const json_t *jrule;
423 : size_t ridx;
424 :
425 9 : json_array_foreach ((json_t *) jrules, ridx, jrule)
426 : {
427 7 : const json_t *jmeasures = json_object_get (jrule, "measures");
428 :
429 7 : if (NULL != jmeasures)
430 7 : total_measures += json_array_size (jmeasures);
431 : }
432 : }
433 : }
434 :
435 2 : adgh->all_rules = GNUNET_new_array (
436 : GNUNET_NZL (total_rules),
437 : struct TALER_EXCHANGE_GetAmlDecisionsKycRule);
438 2 : adgh->all_mp = GNUNET_new_array (
439 : GNUNET_NZL (total_measures),
440 : const char *);
441 : }
442 :
443 : {
444 : struct TALER_EXCHANGE_GetAmlDecisionsDecision *records;
445 : enum GNUNET_GenericReturnValue ret;
446 :
447 2 : records = GNUNET_new_array (
448 : GNUNET_NZL (lr.details.ok.records_length),
449 : struct TALER_EXCHANGE_GetAmlDecisionsDecision);
450 2 : lr.details.ok.records = records;
451 2 : ret = parse_aml_decisions (adgh,
452 : jrecords,
453 : lr.details.ok.records_length,
454 : records);
455 2 : if (GNUNET_OK == ret)
456 : {
457 2 : adgh->cb (adgh->cb_cls,
458 : &lr);
459 2 : adgh->cb = NULL;
460 : }
461 2 : GNUNET_free (records);
462 2 : GNUNET_free (adgh->all_rules);
463 2 : adgh->all_rules = NULL;
464 2 : GNUNET_free (adgh->all_mp);
465 2 : adgh->all_mp = NULL;
466 2 : return ret;
467 : }
468 : }
469 :
470 :
471 : /**
472 : * Function called when we're done processing the
473 : * HTTP /aml/$OFFICER_PUB/decisions request.
474 : *
475 : * @param cls the `struct TALER_EXCHANGE_GetAmlDecisionsHandle`
476 : * @param response_code HTTP response code, 0 on error
477 : * @param response parsed JSON result, NULL on error
478 : */
479 : static void
480 4 : handle_get_aml_decisions_finished (void *cls,
481 : long response_code,
482 : const void *response)
483 : {
484 4 : struct TALER_EXCHANGE_GetAmlDecisionsHandle *adgh = cls;
485 4 : const json_t *j = response;
486 4 : struct TALER_EXCHANGE_GetAmlDecisionsResponse lr = {
487 : .hr.reply = j,
488 4 : .hr.http_status = (unsigned int) response_code
489 : };
490 :
491 4 : adgh->job = NULL;
492 4 : switch (response_code)
493 : {
494 0 : case 0:
495 0 : lr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
496 0 : break;
497 2 : case MHD_HTTP_OK:
498 2 : if (GNUNET_OK !=
499 2 : parse_get_aml_decisions_ok (adgh,
500 : j))
501 : {
502 0 : GNUNET_break_op (0);
503 0 : lr.hr.http_status = 0;
504 0 : lr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
505 0 : break;
506 : }
507 2 : GNUNET_assert (NULL == adgh->cb);
508 2 : TALER_EXCHANGE_get_aml_decisions_cancel (adgh);
509 2 : return;
510 1 : case MHD_HTTP_NO_CONTENT:
511 1 : break;
512 0 : case MHD_HTTP_BAD_REQUEST:
513 : #if DEBUG
514 : json_dumpf (j,
515 : stderr,
516 : JSON_INDENT (2));
517 : #endif
518 0 : lr.hr.ec = TALER_JSON_get_error_code (j);
519 0 : lr.hr.hint = TALER_JSON_get_error_hint (j);
520 0 : break;
521 1 : case MHD_HTTP_FORBIDDEN:
522 1 : lr.hr.ec = TALER_JSON_get_error_code (j);
523 1 : lr.hr.hint = TALER_JSON_get_error_hint (j);
524 1 : break;
525 0 : case MHD_HTTP_NOT_FOUND:
526 0 : lr.hr.ec = TALER_JSON_get_error_code (j);
527 0 : lr.hr.hint = TALER_JSON_get_error_hint (j);
528 0 : break;
529 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
530 0 : lr.hr.ec = TALER_JSON_get_error_code (j);
531 0 : lr.hr.hint = TALER_JSON_get_error_hint (j);
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 decisions\n",
540 : (unsigned int) response_code,
541 : (int) lr.hr.ec);
542 0 : break;
543 : }
544 2 : if (NULL != adgh->cb)
545 2 : adgh->cb (adgh->cb_cls,
546 : &lr);
547 2 : TALER_EXCHANGE_get_aml_decisions_cancel (adgh);
548 : }
549 :
550 :
551 : struct TALER_EXCHANGE_GetAmlDecisionsHandle *
552 4 : TALER_EXCHANGE_get_aml_decisions_create (
553 : struct GNUNET_CURL_Context *ctx,
554 : const char *url,
555 : const struct TALER_AmlOfficerPrivateKeyP *officer_priv)
556 : {
557 : struct TALER_EXCHANGE_GetAmlDecisionsHandle *adgh;
558 :
559 4 : adgh = GNUNET_new (struct TALER_EXCHANGE_GetAmlDecisionsHandle);
560 4 : adgh->ctx = ctx;
561 4 : adgh->base_url = GNUNET_strdup (url);
562 4 : adgh->officer_priv = *officer_priv;
563 4 : GNUNET_CRYPTO_eddsa_key_get_public (&officer_priv->eddsa_priv,
564 : &adgh->officer_pub.eddsa_pub);
565 4 : adgh->options.limit = -20;
566 4 : adgh->options.offset = INT64_MAX;
567 4 : adgh->options.active = TALER_EXCHANGE_YNA_ALL;
568 4 : adgh->options.investigation = TALER_EXCHANGE_YNA_ALL;
569 4 : return adgh;
570 : }
571 :
572 :
573 : enum GNUNET_GenericReturnValue
574 7 : TALER_EXCHANGE_get_aml_decisions_set_options_ (
575 : struct TALER_EXCHANGE_GetAmlDecisionsHandle *adgh,
576 : unsigned int num_options,
577 : const struct TALER_EXCHANGE_GetAmlDecisionsOptionValue options[])
578 : {
579 18 : for (unsigned int i = 0; i < num_options; i++)
580 : {
581 18 : const struct TALER_EXCHANGE_GetAmlDecisionsOptionValue *opt = &options[i];
582 :
583 18 : switch (opt->option)
584 : {
585 7 : case TALER_EXCHANGE_GET_AML_DECISIONS_OPTION_END:
586 7 : return GNUNET_OK;
587 4 : case TALER_EXCHANGE_GET_AML_DECISIONS_OPTION_LIMIT:
588 4 : adgh->options.limit = opt->details.limit;
589 4 : break;
590 4 : case TALER_EXCHANGE_GET_AML_DECISIONS_OPTION_OFFSET:
591 4 : if (opt->details.offset > INT64_MAX)
592 : {
593 0 : GNUNET_break (0);
594 0 : return GNUNET_NO;
595 : }
596 4 : adgh->options.offset = opt->details.offset;
597 4 : break;
598 3 : case TALER_EXCHANGE_GET_AML_DECISIONS_OPTION_H_PAYTO:
599 3 : adgh->options.h_payto = opt->details.h_payto;
600 3 : break;
601 0 : case TALER_EXCHANGE_GET_AML_DECISIONS_OPTION_ACTIVE:
602 0 : adgh->options.active = opt->details.active;
603 0 : break;
604 0 : case TALER_EXCHANGE_GET_AML_DECISIONS_OPTION_INVESTIGATION:
605 0 : adgh->options.investigation = opt->details.investigation;
606 0 : break;
607 0 : default:
608 0 : GNUNET_break (0);
609 0 : return GNUNET_SYSERR;
610 : }
611 : }
612 0 : return GNUNET_OK;
613 : }
614 :
615 :
616 : enum TALER_ErrorCode
617 4 : TALER_EXCHANGE_get_aml_decisions_start (
618 : struct TALER_EXCHANGE_GetAmlDecisionsHandle *adgh,
619 : TALER_EXCHANGE_GetAmlDecisionsCallback cb,
620 : TALER_EXCHANGE_GET_AML_DECISIONS_RESULT_CLOSURE *cb_cls)
621 : {
622 : CURL *eh;
623 : char arg_str[sizeof (struct TALER_AmlOfficerPublicKeyP) * 2 + 32];
624 4 : struct curl_slist *job_headers = NULL;
625 :
626 4 : adgh->cb = cb;
627 4 : adgh->cb_cls = cb_cls;
628 :
629 : /* Build AML officer signature */
630 4 : TALER_officer_aml_query_sign (&adgh->officer_priv,
631 : &adgh->officer_sig);
632 :
633 : /* Build the path component: aml/{officer_pub}/decisions */
634 : {
635 : char pub_str[sizeof (adgh->officer_pub) * 2];
636 : char *end;
637 :
638 4 : end = GNUNET_STRINGS_data_to_string (
639 4 : &adgh->officer_pub,
640 : sizeof (adgh->officer_pub),
641 : pub_str,
642 : sizeof (pub_str));
643 4 : *end = '\0';
644 4 : GNUNET_snprintf (arg_str,
645 : sizeof (arg_str),
646 : "aml/%s/decisions",
647 : pub_str);
648 : }
649 :
650 : /* Build URL with optional query parameters */
651 : {
652 : char limit_s[24];
653 : char offset_s[24];
654 : char payto_s[sizeof (*adgh->options.h_payto) * 2 + 1];
655 4 : int64_t limit = adgh->options.limit;
656 4 : uint64_t offset = adgh->options.offset;
657 4 : bool omit_limit = (-20 == limit);
658 4 : bool omit_offset = ( ( (limit < 0) && ((uint64_t) INT64_MAX == offset) ) ||
659 0 : ( (limit > 0) && (0 == offset) ) );
660 :
661 4 : GNUNET_snprintf (limit_s,
662 : sizeof (limit_s),
663 : "%lld",
664 : (long long) limit);
665 4 : GNUNET_snprintf (offset_s,
666 : sizeof (offset_s),
667 : "%llu",
668 : (unsigned long long) offset);
669 :
670 4 : if (NULL != adgh->options.h_payto)
671 : {
672 : char *end;
673 :
674 3 : end = GNUNET_STRINGS_data_to_string (
675 3 : adgh->options.h_payto,
676 : sizeof (*adgh->options.h_payto),
677 : payto_s,
678 : sizeof (payto_s) - 1);
679 3 : *end = '\0';
680 : }
681 :
682 12 : adgh->url = TALER_url_join (
683 4 : adgh->base_url,
684 : arg_str,
685 : "limit",
686 : omit_limit ? NULL : limit_s,
687 : "offset",
688 : omit_offset ? NULL : offset_s,
689 : "h_payto",
690 4 : (NULL != adgh->options.h_payto) ? payto_s : NULL,
691 : "active",
692 4 : (TALER_EXCHANGE_YNA_ALL != adgh->options.active)
693 0 : ? TALER_yna_to_string (adgh->options.active)
694 : : NULL,
695 : "investigation",
696 4 : (TALER_EXCHANGE_YNA_ALL != adgh->options.investigation)
697 0 : ? TALER_yna_to_string (adgh->options.investigation)
698 : : NULL,
699 : NULL);
700 : }
701 :
702 4 : if (NULL == adgh->url)
703 0 : return TALER_EC_GENERIC_CONFIGURATION_INVALID;
704 :
705 4 : eh = TALER_EXCHANGE_curl_easy_get_ (adgh->url);
706 4 : if (NULL == eh)
707 : {
708 0 : GNUNET_break (0);
709 0 : GNUNET_free (adgh->url);
710 0 : adgh->url = NULL;
711 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
712 : }
713 :
714 : /* Build job headers with AML officer signature */
715 : {
716 : char *hdr;
717 : char sig_str[sizeof (adgh->officer_sig) * 2];
718 : char *end;
719 :
720 4 : end = GNUNET_STRINGS_data_to_string (
721 4 : &adgh->officer_sig,
722 : sizeof (adgh->officer_sig),
723 : sig_str,
724 : sizeof (sig_str));
725 4 : *end = '\0';
726 :
727 4 : GNUNET_asprintf (&hdr,
728 : "%s: %s",
729 : TALER_AML_OFFICER_SIGNATURE_HEADER,
730 : sig_str);
731 4 : job_headers = curl_slist_append (NULL,
732 : hdr);
733 4 : GNUNET_free (hdr);
734 : }
735 :
736 4 : adgh->job = GNUNET_CURL_job_add2 (adgh->ctx,
737 : eh,
738 : job_headers,
739 : &handle_get_aml_decisions_finished,
740 : adgh);
741 4 : curl_slist_free_all (job_headers);
742 :
743 4 : if (NULL == adgh->job)
744 : {
745 0 : GNUNET_free (adgh->url);
746 0 : adgh->url = NULL;
747 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
748 : }
749 4 : return TALER_EC_NONE;
750 : }
751 :
752 :
753 : void
754 4 : TALER_EXCHANGE_get_aml_decisions_cancel (
755 : struct TALER_EXCHANGE_GetAmlDecisionsHandle *adgh)
756 : {
757 4 : if (NULL != adgh->job)
758 : {
759 0 : GNUNET_CURL_job_cancel (adgh->job);
760 0 : adgh->job = NULL;
761 : }
762 4 : GNUNET_free (adgh->all_rules);
763 4 : GNUNET_free (adgh->all_mp);
764 4 : GNUNET_free (adgh->url);
765 4 : GNUNET_free (adgh->base_url);
766 4 : GNUNET_free (adgh);
767 4 : }
768 :
769 :
770 : /* end of exchange_api_get-aml-OFFICER_PUB-decisions.c */
|