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