Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2014-2022 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_wire.c
19 : * @brief Implementation of the /wire request of the exchange's HTTP API
20 : * @author Christian Grothoff
21 : */
22 : #include "platform.h"
23 : #include <jansson.h>
24 : #include <microhttpd.h> /* just for HTTP status codes */
25 : #include <gnunet/gnunet_util_lib.h>
26 : #include <gnunet/gnunet_curl_lib.h>
27 : #include "taler_exchange_service.h"
28 : #include "taler_json_lib.h"
29 : #include "taler_signatures.h"
30 : #include "exchange_api_handle.h"
31 : #include "exchange_api_curl_defaults.h"
32 :
33 :
34 : /**
35 : * @brief A Wire Handle
36 : */
37 : struct TALER_EXCHANGE_WireHandle
38 : {
39 :
40 : /**
41 : * The connection to exchange this request handle will use
42 : */
43 : struct TALER_EXCHANGE_Handle *exchange;
44 :
45 : /**
46 : * The url for this request.
47 : */
48 : char *url;
49 :
50 : /**
51 : * Handle for the request.
52 : */
53 : struct GNUNET_CURL_Job *job;
54 :
55 : /**
56 : * Function to call with the result.
57 : */
58 : TALER_EXCHANGE_WireCallback cb;
59 :
60 : /**
61 : * Closure for @a cb.
62 : */
63 : void *cb_cls;
64 :
65 : };
66 :
67 :
68 : /**
69 : * List of wire fees by method.
70 : */
71 : struct FeeMap
72 : {
73 : /**
74 : * Next entry in list.
75 : */
76 : struct FeeMap *next;
77 :
78 : /**
79 : * Wire method this fee structure is for.
80 : */
81 : char *method;
82 :
83 : /**
84 : * Array of wire fees, also linked list, but allocated
85 : * only once.
86 : */
87 : struct TALER_EXCHANGE_WireAggregateFees *fee_list;
88 : };
89 :
90 :
91 : /**
92 : * Frees @a fm.
93 : *
94 : * @param fm memory to release
95 : */
96 : static void
97 0 : free_fees (struct FeeMap *fm)
98 : {
99 0 : while (NULL != fm)
100 : {
101 0 : struct FeeMap *fe = fm->next;
102 :
103 0 : GNUNET_free (fm->fee_list);
104 0 : GNUNET_free (fm->method);
105 0 : GNUNET_free (fm);
106 0 : fm = fe;
107 : }
108 0 : }
109 :
110 :
111 : /**
112 : * Parse wire @a fees and return map.
113 : *
114 : * @param fees json AggregateTransferFee to parse
115 : * @return NULL on error
116 : */
117 : static struct FeeMap *
118 0 : parse_fees (json_t *fees)
119 : {
120 0 : struct FeeMap *fm = NULL;
121 : const char *key;
122 : json_t *fee_array;
123 :
124 0 : json_object_foreach (fees, key, fee_array) {
125 0 : struct FeeMap *fe = GNUNET_new (struct FeeMap);
126 : unsigned int len;
127 : unsigned int idx;
128 : json_t *fee;
129 :
130 0 : if (0 == (len = json_array_size (fee_array)))
131 : {
132 0 : GNUNET_free (fe);
133 0 : continue; /* skip */
134 : }
135 0 : fe->method = GNUNET_strdup (key);
136 0 : fe->next = fm;
137 0 : fe->fee_list = GNUNET_new_array (len,
138 : struct TALER_EXCHANGE_WireAggregateFees);
139 0 : fm = fe;
140 0 : json_array_foreach (fee_array, idx, fee)
141 : {
142 0 : struct TALER_EXCHANGE_WireAggregateFees *wa = &fe->fee_list[idx];
143 : struct GNUNET_JSON_Specification spec[] = {
144 0 : GNUNET_JSON_spec_fixed_auto ("sig",
145 : &wa->master_sig),
146 0 : TALER_JSON_spec_amount_any ("wire_fee",
147 : &wa->fees.wire),
148 0 : TALER_JSON_spec_amount_any ("wad_fee",
149 : &wa->fees.wad),
150 0 : TALER_JSON_spec_amount_any ("closing_fee",
151 : &wa->fees.closing),
152 0 : GNUNET_JSON_spec_timestamp ("start_date",
153 : &wa->start_date),
154 0 : GNUNET_JSON_spec_timestamp ("end_date",
155 : &wa->end_date),
156 0 : GNUNET_JSON_spec_end ()
157 : };
158 :
159 0 : if (GNUNET_OK !=
160 0 : GNUNET_JSON_parse (fee,
161 : spec,
162 : NULL,
163 : NULL))
164 : {
165 0 : GNUNET_break_op (0);
166 0 : free_fees (fm);
167 0 : return NULL;
168 : }
169 0 : if (idx + 1 < len)
170 0 : wa->next = &fe->fee_list[idx + 1];
171 : else
172 0 : wa->next = NULL;
173 : }
174 : }
175 0 : return fm;
176 : }
177 :
178 :
179 : /**
180 : * Find fee by @a method.
181 : *
182 : * @param fm map to look in
183 : * @param method key to look for
184 : * @return NULL if fee is not specified in @a fm
185 : */
186 : static const struct TALER_EXCHANGE_WireAggregateFees *
187 0 : lookup_fee (const struct FeeMap *fm,
188 : const char *method)
189 : {
190 0 : for (; NULL != fm; fm = fm->next)
191 0 : if (0 == strcasecmp (fm->method,
192 : method))
193 0 : return fm->fee_list;
194 0 : return NULL;
195 : }
196 :
197 :
198 : /**
199 : * Function called when we're done processing the
200 : * HTTP /wire request.
201 : *
202 : * @param cls the `struct TALER_EXCHANGE_WireHandle`
203 : * @param response_code HTTP response code, 0 on error
204 : * @param response parsed JSON result, NULL on error
205 : */
206 : static void
207 0 : handle_wire_finished (void *cls,
208 : long response_code,
209 : const void *response)
210 : {
211 0 : struct TALER_EXCHANGE_WireHandle *wh = cls;
212 0 : const json_t *j = response;
213 0 : struct TALER_EXCHANGE_HttpResponse hr = {
214 : .reply = j,
215 0 : .http_status = (unsigned int) response_code
216 : };
217 :
218 0 : TALER_LOG_DEBUG ("Checking raw /wire response\n");
219 0 : wh->job = NULL;
220 0 : switch (response_code)
221 : {
222 0 : case 0:
223 0 : hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
224 0 : wh->exchange->wire_error_count++;
225 0 : break;
226 0 : case MHD_HTTP_OK:
227 : {
228 : json_t *accounts;
229 : json_t *fees;
230 : unsigned int num_accounts;
231 : struct FeeMap *fm;
232 : const struct TALER_EXCHANGE_Keys *key_state;
233 : struct GNUNET_JSON_Specification spec[] = {
234 0 : GNUNET_JSON_spec_json ("accounts",
235 : &accounts),
236 0 : GNUNET_JSON_spec_json ("fees",
237 : &fees),
238 0 : GNUNET_JSON_spec_end ()
239 : };
240 :
241 0 : wh->exchange->wire_error_count = 0;
242 :
243 0 : if (GNUNET_OK !=
244 0 : GNUNET_JSON_parse (j,
245 : spec,
246 : NULL, NULL))
247 : {
248 : /* bogus reply */
249 0 : GNUNET_break_op (0);
250 0 : hr.http_status = 0;
251 0 : hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
252 0 : break;
253 : }
254 0 : if (0 == (num_accounts = json_array_size (accounts)))
255 : {
256 : /* bogus reply */
257 0 : GNUNET_break_op (0);
258 0 : GNUNET_JSON_parse_free (spec);
259 0 : hr.http_status = 0;
260 0 : hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
261 0 : break;
262 : }
263 0 : if (NULL == (fm = parse_fees (fees)))
264 : {
265 : /* bogus reply */
266 0 : GNUNET_break_op (0);
267 0 : GNUNET_JSON_parse_free (spec);
268 0 : hr.http_status = 0;
269 0 : hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
270 0 : break;
271 : }
272 :
273 0 : key_state = TALER_EXCHANGE_get_keys (wh->exchange);
274 : /* parse accounts */
275 0 : {
276 0 : struct TALER_EXCHANGE_WireAccount was[num_accounts];
277 :
278 0 : for (unsigned int i = 0; i<num_accounts; i++)
279 : {
280 0 : struct TALER_EXCHANGE_WireAccount *wa = &was[i];
281 : json_t *account;
282 : struct GNUNET_JSON_Specification spec_account[] = {
283 0 : GNUNET_JSON_spec_string ("payto_uri",
284 : &wa->payto_uri),
285 0 : GNUNET_JSON_spec_fixed_auto ("master_sig",
286 : &wa->master_sig),
287 0 : GNUNET_JSON_spec_end ()
288 : };
289 : char *method;
290 :
291 0 : account = json_array_get (accounts,
292 : i);
293 0 : if (GNUNET_OK !=
294 0 : TALER_JSON_exchange_wire_signature_check (account,
295 : &key_state->master_pub))
296 : {
297 : /* bogus reply */
298 0 : GNUNET_break_op (0);
299 0 : hr.http_status = 0;
300 0 : hr.ec = TALER_EC_EXCHANGE_WIRE_SIGNATURE_INVALID;
301 0 : break;
302 : }
303 0 : if (GNUNET_OK !=
304 0 : GNUNET_JSON_parse (account,
305 : spec_account,
306 : NULL, NULL))
307 : {
308 : /* bogus reply */
309 0 : GNUNET_break_op (0);
310 0 : hr.http_status = 0;
311 0 : hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
312 0 : break;
313 : }
314 0 : if (NULL == (method = TALER_payto_get_method (wa->payto_uri)))
315 : {
316 : /* bogus reply */
317 0 : GNUNET_break_op (0);
318 0 : hr.http_status = 0;
319 0 : hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
320 0 : break;
321 : }
322 0 : if (NULL == (wa->fees = lookup_fee (fm,
323 : method)))
324 : {
325 : /* bogus reply */
326 0 : GNUNET_break_op (0);
327 0 : hr.http_status = 0;
328 0 : hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
329 0 : GNUNET_free (method);
330 0 : break;
331 : }
332 0 : GNUNET_free (method);
333 : } /* end 'for all accounts */
334 0 : if ( (0 != response_code) &&
335 0 : (NULL != wh->cb) )
336 : {
337 0 : wh->cb (wh->cb_cls,
338 : &hr,
339 : num_accounts,
340 : was);
341 0 : wh->cb = NULL;
342 : }
343 : } /* end of 'parse accounts */
344 0 : free_fees (fm);
345 0 : GNUNET_JSON_parse_free (spec);
346 : } /* end of MHD_HTTP_OK */
347 0 : break;
348 0 : case MHD_HTTP_BAD_REQUEST:
349 : /* This should never happen, either us or the exchange is buggy
350 : (or API version conflict); just pass JSON reply to the application */
351 0 : hr.ec = TALER_JSON_get_error_code (j);
352 0 : hr.hint = TALER_JSON_get_error_hint (j);
353 0 : break;
354 0 : case MHD_HTTP_NOT_FOUND:
355 : /* Nothing really to verify, this should never
356 : happen, we should pass the JSON reply to the application */
357 0 : hr.ec = TALER_JSON_get_error_code (j);
358 0 : hr.hint = TALER_JSON_get_error_hint (j);
359 0 : break;
360 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
361 : /* Server had an internal issue; we should retry, but this API
362 : leaves this to the application */
363 0 : hr.ec = TALER_JSON_get_error_code (j);
364 0 : hr.hint = TALER_JSON_get_error_hint (j);
365 0 : break;
366 0 : default:
367 : /* unexpected response code */
368 0 : if (MHD_HTTP_GATEWAY_TIMEOUT == response_code)
369 0 : wh->exchange->wire_error_count++;
370 0 : GNUNET_break_op (0);
371 0 : hr.ec = TALER_JSON_get_error_code (j);
372 0 : hr.hint = TALER_JSON_get_error_hint (j);
373 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
374 : "Unexpected response code %u/%d for exchange wire\n",
375 : (unsigned int) response_code,
376 : (int) hr.ec);
377 0 : break;
378 : }
379 0 : if (NULL != wh->cb)
380 0 : wh->cb (wh->cb_cls,
381 : &hr,
382 : 0,
383 : NULL);
384 0 : TALER_EXCHANGE_wire_cancel (wh);
385 0 : }
386 :
387 :
388 : /**
389 : * Compute the network timeout for the next request to /wire.
390 : *
391 : * @param exchange the exchange handle
392 : * @returns the timeout in seconds (for use by CURL)
393 : */
394 : static long
395 0 : get_wire_timeout_seconds (struct TALER_EXCHANGE_Handle *exchange)
396 : {
397 0 : return GNUNET_MIN (60,
398 : 5 + (1L << exchange->wire_error_count));
399 : }
400 :
401 :
402 : /**
403 : * Obtain information about a exchange's wire instructions.
404 : * A exchange may provide wire instructions for creating
405 : * a reserve. The wire instructions also indicate
406 : * which wire formats merchants may use with the exchange.
407 : * This API is typically used by a wallet for wiring
408 : * funds, and possibly by a merchant to determine
409 : * supported wire formats.
410 : *
411 : * Note that while we return the (main) response verbatim to the
412 : * caller for further processing, we do already verify that the
413 : * response is well-formed (i.e. that signatures included in the
414 : * response are all valid). If the exchange's reply is not well-formed,
415 : * we return an HTTP status code of zero to @a cb.
416 : *
417 : * @param exchange the exchange handle; the exchange must be ready to operate
418 : * @param wire_cb the callback to call when a reply for this request is available
419 : * @param wire_cb_cls closure for the above callback
420 : * @return a handle for this request
421 : */
422 : struct TALER_EXCHANGE_WireHandle *
423 0 : TALER_EXCHANGE_wire (struct TALER_EXCHANGE_Handle *exchange,
424 : TALER_EXCHANGE_WireCallback wire_cb,
425 : void *wire_cb_cls)
426 : {
427 : struct TALER_EXCHANGE_WireHandle *wh;
428 : struct GNUNET_CURL_Context *ctx;
429 : CURL *eh;
430 :
431 0 : if (GNUNET_YES !=
432 0 : TEAH_handle_is_ready (exchange))
433 : {
434 0 : GNUNET_break (0);
435 0 : return NULL;
436 : }
437 0 : wh = GNUNET_new (struct TALER_EXCHANGE_WireHandle);
438 0 : wh->exchange = exchange;
439 0 : wh->cb = wire_cb;
440 0 : wh->cb_cls = wire_cb_cls;
441 0 : wh->url = TEAH_path_to_url (exchange,
442 : "/wire");
443 0 : if (NULL == wh->url)
444 : {
445 0 : GNUNET_free (wh);
446 0 : return NULL;
447 : }
448 0 : eh = TALER_EXCHANGE_curl_easy_get_ (wh->url);
449 0 : if (NULL == eh)
450 : {
451 0 : GNUNET_break (0);
452 0 : GNUNET_free (wh->url);
453 0 : GNUNET_free (wh);
454 0 : return NULL;
455 : }
456 0 : GNUNET_break (CURLE_OK ==
457 : curl_easy_setopt (eh,
458 : CURLOPT_TIMEOUT,
459 : get_wire_timeout_seconds (wh->exchange)));
460 0 : ctx = TEAH_handle_to_context (exchange);
461 0 : wh->job = GNUNET_CURL_job_add_with_ct_json (ctx,
462 : eh,
463 : &handle_wire_finished,
464 : wh);
465 0 : return wh;
466 : }
467 :
468 :
469 : /**
470 : * Cancel a wire information request. This function cannot be used
471 : * on a request handle if a response is already served for it.
472 : *
473 : * @param wh the wire information request handle
474 : */
475 : void
476 0 : TALER_EXCHANGE_wire_cancel (struct TALER_EXCHANGE_WireHandle *wh)
477 : {
478 0 : if (NULL != wh->job)
479 : {
480 0 : GNUNET_CURL_job_cancel (wh->job);
481 0 : wh->job = NULL;
482 : }
483 0 : GNUNET_free (wh->url);
484 0 : GNUNET_free (wh);
485 0 : }
486 :
487 :
488 : /* end of exchange_api_wire.c */
|