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