Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2014-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-deposits-H_WIRE-MERCHANT_PUB-H_CONTRACT_TERMS-COIN_PUB.c
19 : * @brief Implementation of the GET /deposits/... request
20 : * @author Christian Grothoff
21 : */
22 : #include <jansson.h>
23 : #include <microhttpd.h> /* just for HTTP status codes */
24 : #include <gnunet/gnunet_util_lib.h>
25 : #include <gnunet/gnunet_json_lib.h>
26 : #include <gnunet/gnunet_curl_lib.h>
27 : #include "taler/taler_json_lib.h"
28 : #include "exchange_api_handle.h"
29 : #include "taler/taler_signatures.h"
30 : #include "exchange_api_curl_defaults.h"
31 :
32 :
33 : /**
34 : * @brief A GET /deposits/... Handle
35 : */
36 : struct TALER_EXCHANGE_GetDepositsHandle
37 : {
38 :
39 : /**
40 : * Base URL of the exchange.
41 : */
42 : char *base_url;
43 :
44 : /**
45 : * The url for this request.
46 : */
47 : char *url;
48 :
49 : /**
50 : * The keys of this request handle will use.
51 : */
52 : struct TALER_EXCHANGE_Keys *keys;
53 :
54 : /**
55 : * Handle for the request.
56 : */
57 : struct GNUNET_CURL_Job *job;
58 :
59 : /**
60 : * Function to call with the result.
61 : */
62 : TALER_EXCHANGE_GetDepositsCallback cb;
63 :
64 : /**
65 : * Closure for @e cb.
66 : */
67 : TALER_EXCHANGE_GET_DEPOSITS_RESULT_CLOSURE *cb_cls;
68 :
69 : /**
70 : * CURL context to use.
71 : */
72 : struct GNUNET_CURL_Context *ctx;
73 :
74 : /**
75 : * Private key of the merchant.
76 : */
77 : struct TALER_MerchantPrivateKeyP merchant_priv;
78 :
79 : /**
80 : * Hash over the wiring information of the merchant.
81 : */
82 : struct TALER_MerchantWireHashP h_wire;
83 :
84 : /**
85 : * Hash over the contract for which this deposit is made.
86 : */
87 : struct TALER_PrivateContractHashP h_contract_terms;
88 :
89 : /**
90 : * The coin's public key.
91 : */
92 : struct TALER_CoinSpendPublicKeyP coin_pub;
93 :
94 : /**
95 : * Options set for this request.
96 : */
97 : struct
98 : {
99 : /**
100 : * How long to wait for the wire transfer, enabling long polling.
101 : * Default: zero (no long polling).
102 : */
103 : struct GNUNET_TIME_Relative timeout;
104 :
105 : /**
106 : * Long-poll target type. Defaults to #TALER_DGLPT_NONE (0).
107 : */
108 : enum TALER_DepositGetLongPollTarget lpt;
109 : } options;
110 :
111 : };
112 :
113 :
114 : /**
115 : * Function called when we're done processing the
116 : * HTTP GET /deposits/... request.
117 : *
118 : * @param cls the `struct TALER_EXCHANGE_GetDepositsHandle`
119 : * @param response_code HTTP response code, 0 on error
120 : * @param response parsed JSON result, NULL on error
121 : */
122 : static void
123 8 : handle_deposit_wtid_finished (void *cls,
124 : long response_code,
125 : const void *response)
126 : {
127 8 : struct TALER_EXCHANGE_GetDepositsHandle *gdh = cls;
128 8 : const json_t *j = response;
129 8 : struct TALER_EXCHANGE_GetDepositsResponse dr = {
130 : .hr.reply = j,
131 8 : .hr.http_status = (unsigned int) response_code
132 : };
133 :
134 8 : gdh->job = NULL;
135 8 : switch (response_code)
136 : {
137 0 : case 0:
138 0 : dr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
139 0 : break;
140 2 : case MHD_HTTP_OK:
141 : {
142 : struct GNUNET_JSON_Specification spec[] = {
143 2 : GNUNET_JSON_spec_fixed_auto (
144 : "wtid",
145 : &dr.details.ok.wtid),
146 2 : GNUNET_JSON_spec_timestamp (
147 : "execution_time",
148 : &dr.details.ok.execution_time),
149 2 : TALER_JSON_spec_amount_any (
150 : "coin_contribution",
151 : &dr.details.ok.coin_contribution),
152 2 : GNUNET_JSON_spec_fixed_auto (
153 : "exchange_sig",
154 : &dr.details.ok.exchange_sig),
155 2 : GNUNET_JSON_spec_fixed_auto (
156 : "exchange_pub",
157 : &dr.details.ok.exchange_pub),
158 2 : GNUNET_JSON_spec_end ()
159 : };
160 :
161 2 : GNUNET_assert (NULL != gdh->keys);
162 2 : if (GNUNET_OK !=
163 2 : GNUNET_JSON_parse (j,
164 : spec,
165 : NULL, NULL))
166 : {
167 0 : GNUNET_break_op (0);
168 0 : dr.hr.http_status = 0;
169 0 : dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
170 0 : break;
171 : }
172 2 : if (GNUNET_OK !=
173 2 : TALER_EXCHANGE_test_signing_key (gdh->keys,
174 : &dr.details.ok.exchange_pub))
175 : {
176 0 : GNUNET_break_op (0);
177 0 : dr.hr.http_status = 0;
178 0 : dr.hr.ec = TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_SIGNATURE_BY_EXCHANGE;
179 0 : break;
180 : }
181 2 : if (GNUNET_OK !=
182 2 : TALER_exchange_online_confirm_wire_verify (
183 2 : &gdh->h_wire,
184 2 : &gdh->h_contract_terms,
185 : &dr.details.ok.wtid,
186 2 : &gdh->coin_pub,
187 : dr.details.ok.execution_time,
188 : &dr.details.ok.coin_contribution,
189 : &dr.details.ok.exchange_pub,
190 : &dr.details.ok.exchange_sig))
191 : {
192 0 : GNUNET_break_op (0);
193 0 : dr.hr.http_status = 0;
194 0 : dr.hr.ec = TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_SIGNATURE_BY_EXCHANGE;
195 0 : break;
196 : }
197 2 : gdh->cb (gdh->cb_cls,
198 : &dr);
199 2 : gdh->cb = NULL;
200 2 : TALER_EXCHANGE_get_deposits_cancel (gdh);
201 2 : return;
202 : }
203 4 : case MHD_HTTP_ACCEPTED:
204 : {
205 : /* Transaction known, but not executed yet */
206 4 : bool no_legi = false;
207 : struct GNUNET_JSON_Specification spec[] = {
208 4 : GNUNET_JSON_spec_timestamp (
209 : "execution_time",
210 : &dr.details.accepted.execution_time),
211 4 : GNUNET_JSON_spec_mark_optional (
212 : GNUNET_JSON_spec_fixed_auto (
213 : "account_pub",
214 : &dr.details.accepted.account_pub),
215 : NULL),
216 4 : GNUNET_JSON_spec_mark_optional (
217 : GNUNET_JSON_spec_uint64 (
218 : "requirement_row",
219 : &dr.details.accepted.requirement_row),
220 : &no_legi),
221 4 : GNUNET_JSON_spec_bool (
222 : "kyc_ok",
223 : &dr.details.accepted.kyc_ok),
224 4 : GNUNET_JSON_spec_end ()
225 : };
226 :
227 4 : if (GNUNET_OK !=
228 4 : GNUNET_JSON_parse (j,
229 : spec,
230 : NULL, NULL))
231 : {
232 0 : GNUNET_break_op (0);
233 0 : dr.hr.http_status = 0;
234 0 : dr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
235 0 : break;
236 : }
237 4 : if (no_legi)
238 3 : dr.details.accepted.requirement_row = 0;
239 4 : gdh->cb (gdh->cb_cls,
240 : &dr);
241 4 : gdh->cb = NULL;
242 4 : TALER_EXCHANGE_get_deposits_cancel (gdh);
243 4 : return;
244 : }
245 0 : case MHD_HTTP_BAD_REQUEST:
246 0 : dr.hr.ec = TALER_JSON_get_error_code (j);
247 0 : dr.hr.hint = TALER_JSON_get_error_hint (j);
248 : /* This should never happen, either us or the exchange is buggy
249 : (or API version conflict); just pass JSON reply to the application */
250 0 : break;
251 0 : case MHD_HTTP_FORBIDDEN:
252 0 : dr.hr.ec = TALER_JSON_get_error_code (j);
253 0 : dr.hr.hint = TALER_JSON_get_error_hint (j);
254 : /* Nothing really to verify, exchange says one of the signatures is
255 : invalid; as we checked them, this should never happen, we
256 : should pass the JSON reply to the application */
257 0 : break;
258 2 : case MHD_HTTP_NOT_FOUND:
259 2 : dr.hr.ec = TALER_JSON_get_error_code (j);
260 2 : dr.hr.hint = TALER_JSON_get_error_hint (j);
261 : /* Exchange does not know about transaction;
262 : we should pass the reply to the application */
263 2 : break;
264 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
265 0 : dr.hr.ec = TALER_JSON_get_error_code (j);
266 0 : dr.hr.hint = TALER_JSON_get_error_hint (j);
267 : /* Server had an internal issue; we should retry, but this API
268 : leaves this to the application */
269 0 : break;
270 0 : default:
271 : /* unexpected response code */
272 0 : dr.hr.ec = TALER_JSON_get_error_code (j);
273 0 : dr.hr.hint = TALER_JSON_get_error_hint (j);
274 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
275 : "Unexpected response code %u/%d for exchange GET deposits\n",
276 : (unsigned int) response_code,
277 : (int) dr.hr.ec);
278 0 : GNUNET_break_op (0);
279 0 : break;
280 : }
281 2 : if (NULL != gdh->cb)
282 2 : gdh->cb (gdh->cb_cls,
283 : &dr);
284 2 : TALER_EXCHANGE_get_deposits_cancel (gdh);
285 : }
286 :
287 :
288 : struct TALER_EXCHANGE_GetDepositsHandle *
289 8 : TALER_EXCHANGE_get_deposits_create (
290 : struct GNUNET_CURL_Context *ctx,
291 : const char *url,
292 : struct TALER_EXCHANGE_Keys *keys,
293 : const struct TALER_MerchantPrivateKeyP *merchant_priv,
294 : const struct TALER_MerchantWireHashP *h_wire,
295 : const struct TALER_PrivateContractHashP *h_contract_terms,
296 : const struct TALER_CoinSpendPublicKeyP *coin_pub)
297 : {
298 : struct TALER_EXCHANGE_GetDepositsHandle *gdh;
299 : struct TALER_MerchantPublicKeyP merchant;
300 :
301 8 : gdh = GNUNET_new (struct TALER_EXCHANGE_GetDepositsHandle);
302 8 : gdh->ctx = ctx;
303 8 : gdh->base_url = GNUNET_strdup (url);
304 8 : gdh->keys = TALER_EXCHANGE_keys_incref (keys);
305 8 : gdh->merchant_priv = *merchant_priv;
306 8 : gdh->h_wire = *h_wire;
307 8 : gdh->h_contract_terms = *h_contract_terms;
308 8 : GNUNET_CRYPTO_eddsa_key_get_public (&merchant_priv->eddsa_priv,
309 : &merchant.eddsa_pub);
310 8 : gdh->coin_pub = *coin_pub;
311 8 : return gdh;
312 : }
313 :
314 :
315 : enum GNUNET_GenericReturnValue
316 0 : TALER_EXCHANGE_get_deposits_set_options_ (
317 : struct TALER_EXCHANGE_GetDepositsHandle *gdh,
318 : unsigned int num_options,
319 : const struct TALER_EXCHANGE_GetDepositsOptionValue *options)
320 : {
321 0 : for (unsigned int i = 0; i < num_options; i++)
322 : {
323 0 : const struct TALER_EXCHANGE_GetDepositsOptionValue *opt = &options[i];
324 :
325 0 : switch (opt->option)
326 : {
327 0 : case TALER_EXCHANGE_GET_DEPOSITS_OPTION_END:
328 0 : return GNUNET_OK;
329 0 : case TALER_EXCHANGE_GET_DEPOSITS_OPTION_TIMEOUT:
330 0 : gdh->options.timeout = opt->details.timeout;
331 0 : break;
332 0 : case TALER_EXCHANGE_GET_DEPOSITS_OPTION_LONG_POLL_TARGET:
333 0 : gdh->options.lpt = opt->details.lpt;
334 0 : break;
335 : }
336 : }
337 0 : return GNUNET_OK;
338 : }
339 :
340 :
341 : enum TALER_ErrorCode
342 8 : TALER_EXCHANGE_get_deposits_start (
343 : struct TALER_EXCHANGE_GetDepositsHandle *gdh,
344 : TALER_EXCHANGE_GetDepositsCallback cb,
345 : TALER_EXCHANGE_GET_DEPOSITS_RESULT_CLOSURE *cb_cls)
346 : {
347 : struct TALER_MerchantPublicKeyP merchant;
348 : struct TALER_MerchantSignatureP merchant_sig;
349 : unsigned int tms;
350 : CURL *eh;
351 :
352 8 : if (NULL != gdh->job)
353 : {
354 0 : GNUNET_break (0);
355 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
356 : }
357 8 : gdh->cb = cb;
358 8 : gdh->cb_cls = cb_cls;
359 16 : tms = (unsigned int) gdh->options.timeout.rel_value_us
360 8 : / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us;
361 8 : GNUNET_CRYPTO_eddsa_key_get_public (&gdh->merchant_priv.eddsa_priv,
362 : &merchant.eddsa_pub);
363 8 : TALER_merchant_deposit_sign (&gdh->h_contract_terms,
364 8 : &gdh->h_wire,
365 8 : &gdh->coin_pub,
366 8 : &gdh->merchant_priv,
367 : &merchant_sig);
368 : {
369 : char cpub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2];
370 : char mpub_str[sizeof (struct TALER_MerchantPublicKeyP) * 2];
371 : char msig_str[sizeof (struct TALER_MerchantSignatureP) * 2];
372 : char chash_str[sizeof (struct TALER_PrivateContractHashP) * 2];
373 : char whash_str[sizeof (struct TALER_MerchantWireHashP) * 2];
374 : char arg_str[sizeof (whash_str)
375 : + sizeof (mpub_str)
376 : + sizeof (chash_str)
377 : + sizeof (cpub_str) + 48];
378 : char timeout_str[24];
379 : char lpt_str[12];
380 : char *end;
381 :
382 8 : end = GNUNET_STRINGS_data_to_string (&gdh->h_wire,
383 : sizeof (gdh->h_wire),
384 : whash_str,
385 : sizeof (whash_str));
386 8 : *end = '\0';
387 8 : end = GNUNET_STRINGS_data_to_string (&merchant,
388 : sizeof (merchant),
389 : mpub_str,
390 : sizeof (mpub_str));
391 8 : *end = '\0';
392 8 : end = GNUNET_STRINGS_data_to_string (&gdh->h_contract_terms,
393 : sizeof (gdh->h_contract_terms),
394 : chash_str,
395 : sizeof (chash_str));
396 8 : *end = '\0';
397 8 : end = GNUNET_STRINGS_data_to_string (&gdh->coin_pub,
398 : sizeof (gdh->coin_pub),
399 : cpub_str,
400 : sizeof (cpub_str));
401 8 : *end = '\0';
402 8 : end = GNUNET_STRINGS_data_to_string (&merchant_sig,
403 : sizeof (merchant_sig),
404 : msig_str,
405 : sizeof (msig_str));
406 8 : *end = '\0';
407 8 : if (0 == tms)
408 : {
409 8 : timeout_str[0] = '\0';
410 : }
411 : else
412 : {
413 0 : GNUNET_snprintf (
414 : timeout_str,
415 : sizeof (timeout_str),
416 : "%u",
417 : tms);
418 : }
419 8 : if (TALER_DGLPT_OK == gdh->options.lpt)
420 : {
421 0 : lpt_str[0] = '\0';
422 : }
423 : else
424 : {
425 8 : GNUNET_snprintf (
426 : lpt_str,
427 : sizeof (lpt_str),
428 : "%u",
429 8 : (unsigned int) gdh->options.lpt);
430 : }
431 :
432 8 : GNUNET_snprintf (arg_str,
433 : sizeof (arg_str),
434 : "deposits/%s/%s/%s/%s",
435 : whash_str,
436 : mpub_str,
437 : chash_str,
438 : cpub_str);
439 8 : gdh->url = TALER_url_join (
440 8 : gdh->base_url,
441 : arg_str,
442 : "merchant_sig", msig_str,
443 : "timeout_ms", (0 == tms) ? NULL : timeout_str,
444 8 : "lpt", (TALER_DGLPT_NONE == gdh->options.lpt) ? NULL : lpt_str,
445 : NULL);
446 : }
447 8 : if (NULL == gdh->url)
448 : {
449 0 : GNUNET_break (0);
450 0 : return TALER_EC_GENERIC_CONFIGURATION_INVALID;
451 : }
452 8 : eh = TALER_EXCHANGE_curl_easy_get_ (gdh->url);
453 8 : if (NULL == eh)
454 : {
455 0 : GNUNET_break (0);
456 0 : return TALER_EC_GENERIC_CONFIGURATION_INVALID;
457 : }
458 8 : if (0 != tms)
459 : {
460 0 : GNUNET_break (CURLE_OK ==
461 : curl_easy_setopt (eh,
462 : CURLOPT_TIMEOUT_MS,
463 : (long) (tms + 100L)));
464 : }
465 8 : gdh->job = GNUNET_CURL_job_add (gdh->ctx,
466 : eh,
467 : &handle_deposit_wtid_finished,
468 : gdh);
469 8 : if (NULL == gdh->job)
470 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
471 8 : return TALER_EC_NONE;
472 : }
473 :
474 :
475 : void
476 8 : TALER_EXCHANGE_get_deposits_cancel (
477 : struct TALER_EXCHANGE_GetDepositsHandle *gdh)
478 : {
479 8 : if (NULL != gdh->job)
480 : {
481 0 : GNUNET_CURL_job_cancel (gdh->job);
482 0 : gdh->job = NULL;
483 : }
484 8 : GNUNET_free (gdh->url);
485 8 : GNUNET_free (gdh->base_url);
486 8 : TALER_EXCHANGE_keys_decref (gdh->keys);
487 8 : GNUNET_free (gdh);
488 8 : }
489 :
490 :
491 : /* end of exchange_api_get-deposits-H_WIRE-MERCHANT_PUB-H_CONTRACT_TERMS-COIN_PUB.c */
|