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_post-reserves-RESERVE_PUB-close.c
19 : * @brief Implementation of the POST /reserves/$RESERVE_PUB/close requests
20 : * @author Christian Grothoff
21 : */
22 : #include "taler/platform.h"
23 : #include <jansson.h>
24 : #include <microhttpd.h> /* just for HTTP close 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_exchange_service.h"
29 : #include "taler/taler_json_lib.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 POST /reserves/$RID/close Handle
37 : */
38 : struct TALER_EXCHANGE_PostReservesCloseHandle
39 : {
40 :
41 : /**
42 : * Reference to the execution context.
43 : */
44 : struct GNUNET_CURL_Context *ctx;
45 :
46 : /**
47 : * Base URL of the exchange.
48 : */
49 : char *base_url;
50 :
51 : /**
52 : * The url for this request, set during _start.
53 : */
54 : char *url;
55 :
56 : /**
57 : * Context for #TEH_curl_easy_post(). Keeps the data that must
58 : * persist for Curl to make the upload.
59 : */
60 : struct TALER_CURL_PostContext post_ctx;
61 :
62 : /**
63 : * Handle for the request.
64 : */
65 : struct GNUNET_CURL_Job *job;
66 :
67 : /**
68 : * Function to call with the result.
69 : */
70 : TALER_EXCHANGE_PostReservesCloseCallback cb;
71 :
72 : /**
73 : * Closure for @a cb.
74 : */
75 : TALER_EXCHANGE_POST_RESERVES_CLOSE_RESULT_CLOSURE *cb_cls;
76 :
77 : /**
78 : * Private key of the reserve.
79 : */
80 : struct TALER_ReservePrivateKeyP reserve_priv;
81 :
82 : /**
83 : * Public key of the reserve we are querying.
84 : */
85 : struct TALER_ReservePublicKeyP reserve_pub;
86 :
87 : /**
88 : * Target payto URI to send the reserve balance to (optional).
89 : */
90 : struct TALER_FullPayto target_payto_uri;
91 :
92 : /**
93 : * When did we make the request.
94 : */
95 : struct GNUNET_TIME_Timestamp ts;
96 :
97 : };
98 :
99 :
100 : /**
101 : * We received an #MHD_HTTP_OK close code. Handle the JSON
102 : * response.
103 : *
104 : * @param prch handle of the request
105 : * @param j JSON response
106 : * @return #GNUNET_OK on success
107 : */
108 : static enum GNUNET_GenericReturnValue
109 2 : handle_reserves_close_ok (struct TALER_EXCHANGE_PostReservesCloseHandle *prch,
110 : const json_t *j)
111 : {
112 2 : struct TALER_EXCHANGE_PostReservesCloseResponse rs = {
113 : .hr.reply = j,
114 : .hr.http_status = MHD_HTTP_OK,
115 : };
116 : struct GNUNET_JSON_Specification spec[] = {
117 2 : TALER_JSON_spec_amount_any ("wire_amount",
118 : &rs.details.ok.wire_amount),
119 2 : GNUNET_JSON_spec_end ()
120 : };
121 :
122 2 : if (GNUNET_OK !=
123 2 : GNUNET_JSON_parse (j,
124 : spec,
125 : NULL,
126 : NULL))
127 : {
128 0 : GNUNET_break_op (0);
129 0 : return GNUNET_SYSERR;
130 : }
131 2 : prch->cb (prch->cb_cls,
132 : &rs);
133 2 : prch->cb = NULL;
134 2 : GNUNET_JSON_parse_free (spec);
135 2 : return GNUNET_OK;
136 : }
137 :
138 :
139 : /**
140 : * We received an #MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS close code. Handle the JSON
141 : * response.
142 : *
143 : * @param prch handle of the request
144 : * @param j JSON response
145 : * @return #GNUNET_OK on success
146 : */
147 : static enum GNUNET_GenericReturnValue
148 2 : handle_reserves_close_kyc (struct TALER_EXCHANGE_PostReservesCloseHandle *prch,
149 : const json_t *j)
150 : {
151 2 : struct TALER_EXCHANGE_PostReservesCloseResponse rs = {
152 : .hr.reply = j,
153 : .hr.http_status = MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS,
154 : };
155 : struct GNUNET_JSON_Specification spec[] = {
156 2 : GNUNET_JSON_spec_fixed_auto (
157 : "h_payto",
158 : &rs.details.unavailable_for_legal_reasons.h_payto),
159 2 : GNUNET_JSON_spec_uint64 (
160 : "requirement_row",
161 : &rs.details.unavailable_for_legal_reasons.requirement_row),
162 2 : GNUNET_JSON_spec_end ()
163 : };
164 :
165 2 : if (GNUNET_OK !=
166 2 : GNUNET_JSON_parse (j,
167 : spec,
168 : NULL,
169 : NULL))
170 : {
171 0 : GNUNET_break_op (0);
172 0 : return GNUNET_SYSERR;
173 : }
174 2 : prch->cb (prch->cb_cls,
175 : &rs);
176 2 : prch->cb = NULL;
177 2 : GNUNET_JSON_parse_free (spec);
178 2 : return GNUNET_OK;
179 : }
180 :
181 :
182 : /**
183 : * Function called when we're done processing the
184 : * HTTP /reserves/$RID/close request.
185 : *
186 : * @param cls the `struct TALER_EXCHANGE_PostReservesCloseHandle`
187 : * @param response_code HTTP response code, 0 on error
188 : * @param response parsed JSON result, NULL on error
189 : */
190 : static void
191 4 : handle_reserves_close_finished (void *cls,
192 : long response_code,
193 : const void *response)
194 : {
195 4 : struct TALER_EXCHANGE_PostReservesCloseHandle *prch = cls;
196 4 : const json_t *j = response;
197 4 : struct TALER_EXCHANGE_PostReservesCloseResponse rs = {
198 : .hr.reply = j,
199 4 : .hr.http_status = (unsigned int) response_code
200 : };
201 :
202 4 : prch->job = NULL;
203 4 : switch (response_code)
204 : {
205 0 : case 0:
206 0 : rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
207 0 : break;
208 2 : case MHD_HTTP_OK:
209 2 : if (GNUNET_OK !=
210 2 : handle_reserves_close_ok (prch,
211 : j))
212 : {
213 0 : GNUNET_break_op (0);
214 0 : rs.hr.http_status = 0;
215 0 : rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
216 : }
217 2 : break;
218 0 : case MHD_HTTP_BAD_REQUEST:
219 : /* This should never happen, either us or the exchange is buggy
220 : (or API version conflict); just pass JSON reply to the application */
221 0 : GNUNET_break (0);
222 0 : rs.hr.ec = TALER_JSON_get_error_code (j);
223 0 : rs.hr.hint = TALER_JSON_get_error_hint (j);
224 0 : break;
225 0 : case MHD_HTTP_FORBIDDEN:
226 : /* This should never happen, either us or the exchange is buggy
227 : (or API version conflict); just pass JSON reply to the application */
228 0 : GNUNET_break (0);
229 0 : rs.hr.ec = TALER_JSON_get_error_code (j);
230 0 : rs.hr.hint = TALER_JSON_get_error_hint (j);
231 0 : break;
232 0 : case MHD_HTTP_NOT_FOUND:
233 : /* Nothing really to verify, this should never
234 : happen, we should pass the JSON reply to the application */
235 0 : rs.hr.ec = TALER_JSON_get_error_code (j);
236 0 : rs.hr.hint = TALER_JSON_get_error_hint (j);
237 0 : break;
238 0 : case MHD_HTTP_CONFLICT:
239 : /* Insufficient balance to inquire for reserve close */
240 0 : rs.hr.ec = TALER_JSON_get_error_code (j);
241 0 : rs.hr.hint = TALER_JSON_get_error_hint (j);
242 0 : break;
243 2 : case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
244 2 : if (GNUNET_OK !=
245 2 : handle_reserves_close_kyc (prch,
246 : j))
247 : {
248 0 : GNUNET_break_op (0);
249 0 : rs.hr.http_status = 0;
250 0 : rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
251 : }
252 2 : break;
253 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
254 : /* Server had an internal issue; we should retry, but this API
255 : leaves this to the application */
256 0 : rs.hr.ec = TALER_JSON_get_error_code (j);
257 0 : rs.hr.hint = TALER_JSON_get_error_hint (j);
258 0 : break;
259 0 : default:
260 : /* unexpected response code */
261 0 : GNUNET_break_op (0);
262 0 : rs.hr.ec = TALER_JSON_get_error_code (j);
263 0 : rs.hr.hint = TALER_JSON_get_error_hint (j);
264 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
265 : "Unexpected response code %u/%d for reserves close\n",
266 : (unsigned int) response_code,
267 : (int) rs.hr.ec);
268 0 : break;
269 : }
270 4 : if (NULL != prch->cb)
271 : {
272 0 : prch->cb (prch->cb_cls,
273 : &rs);
274 0 : prch->cb = NULL;
275 : }
276 4 : TALER_EXCHANGE_post_reserves_close_cancel (prch);
277 4 : }
278 :
279 :
280 : struct TALER_EXCHANGE_PostReservesCloseHandle *
281 4 : TALER_EXCHANGE_post_reserves_close_create (
282 : struct GNUNET_CURL_Context *ctx,
283 : const char *url,
284 : const struct TALER_ReservePrivateKeyP *reserve_priv)
285 : {
286 : struct TALER_EXCHANGE_PostReservesCloseHandle *prch;
287 :
288 4 : prch = GNUNET_new (struct TALER_EXCHANGE_PostReservesCloseHandle);
289 4 : prch->ctx = ctx;
290 4 : prch->base_url = GNUNET_strdup (url);
291 4 : prch->reserve_priv = *reserve_priv;
292 4 : GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
293 : &prch->reserve_pub.eddsa_pub);
294 4 : prch->target_payto_uri.full_payto = NULL;
295 4 : return prch;
296 : }
297 :
298 :
299 : enum GNUNET_GenericReturnValue
300 4 : TALER_EXCHANGE_post_reserves_close_set_options_ (
301 : struct TALER_EXCHANGE_PostReservesCloseHandle *prch,
302 : unsigned int num_options,
303 : const struct TALER_EXCHANGE_PostReservesCloseOptionValue options[])
304 : {
305 8 : for (unsigned int i = 0; i < num_options; i++)
306 : {
307 8 : const struct TALER_EXCHANGE_PostReservesCloseOptionValue *opt = &options[i];
308 :
309 8 : switch (opt->option)
310 : {
311 4 : case TALER_EXCHANGE_POST_RESERVES_CLOSE_OPTION_END:
312 4 : return GNUNET_OK;
313 4 : case TALER_EXCHANGE_POST_RESERVES_CLOSE_OPTION_PAYTO_URI:
314 4 : GNUNET_free (prch->target_payto_uri.full_payto);
315 : prch->target_payto_uri.full_payto
316 4 : = GNUNET_strdup (opt->details.payto_uri.full_payto);
317 4 : break;
318 : }
319 : }
320 0 : return GNUNET_OK;
321 : }
322 :
323 :
324 : enum TALER_ErrorCode
325 4 : TALER_EXCHANGE_post_reserves_close_start (
326 : struct TALER_EXCHANGE_PostReservesCloseHandle *prch,
327 : TALER_EXCHANGE_PostReservesCloseCallback cb,
328 : TALER_EXCHANGE_POST_RESERVES_CLOSE_RESULT_CLOSURE *cb_cls)
329 : {
330 : CURL *eh;
331 : char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32];
332 : struct TALER_ReserveSignatureP reserve_sig;
333 : struct TALER_FullPaytoHashP h_payto;
334 : json_t *close_obj;
335 :
336 4 : prch->cb = cb;
337 4 : prch->cb_cls = cb_cls;
338 4 : prch->ts = GNUNET_TIME_timestamp_get ();
339 : {
340 : char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
341 : char *end;
342 :
343 4 : end = GNUNET_STRINGS_data_to_string (
344 4 : &prch->reserve_pub,
345 : sizeof (prch->reserve_pub),
346 : pub_str,
347 : sizeof (pub_str));
348 4 : *end = '\0';
349 4 : GNUNET_snprintf (arg_str,
350 : sizeof (arg_str),
351 : "reserves/%s/close",
352 : pub_str);
353 : }
354 4 : prch->url = TALER_url_join (prch->base_url,
355 : arg_str,
356 : NULL);
357 4 : if (NULL == prch->url)
358 0 : return TALER_EC_GENERIC_CONFIGURATION_INVALID;
359 4 : if (NULL != prch->target_payto_uri.full_payto)
360 4 : TALER_full_payto_hash (prch->target_payto_uri,
361 : &h_payto);
362 4 : TALER_wallet_reserve_close_sign (prch->ts,
363 4 : (NULL != prch->target_payto_uri.full_payto)
364 : ? &h_payto
365 : : NULL,
366 4 : &prch->reserve_priv,
367 : &reserve_sig);
368 4 : close_obj = GNUNET_JSON_PACK (
369 : GNUNET_JSON_pack_allow_null (
370 : TALER_JSON_pack_full_payto ("payto_uri",
371 : prch->target_payto_uri)),
372 : GNUNET_JSON_pack_timestamp ("request_timestamp",
373 : prch->ts),
374 : GNUNET_JSON_pack_data_auto ("reserve_sig",
375 : &reserve_sig));
376 4 : eh = TALER_EXCHANGE_curl_easy_get_ (prch->url);
377 8 : if ( (NULL == eh) ||
378 : (GNUNET_OK !=
379 4 : TALER_curl_easy_post (&prch->post_ctx,
380 : eh,
381 : close_obj)) )
382 : {
383 0 : GNUNET_break (0);
384 0 : if (NULL != eh)
385 0 : curl_easy_cleanup (eh);
386 0 : json_decref (close_obj);
387 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
388 : }
389 4 : json_decref (close_obj);
390 8 : prch->job = GNUNET_CURL_job_add2 (prch->ctx,
391 : eh,
392 4 : prch->post_ctx.headers,
393 : &handle_reserves_close_finished,
394 : prch);
395 4 : if (NULL == prch->job)
396 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
397 4 : return TALER_EC_NONE;
398 : }
399 :
400 :
401 : void
402 4 : TALER_EXCHANGE_post_reserves_close_cancel (
403 : struct TALER_EXCHANGE_PostReservesCloseHandle *prch)
404 : {
405 4 : if (NULL != prch->job)
406 : {
407 0 : GNUNET_CURL_job_cancel (prch->job);
408 0 : prch->job = NULL;
409 : }
410 4 : TALER_curl_easy_post_finished (&prch->post_ctx);
411 4 : GNUNET_free (prch->url);
412 4 : GNUNET_free (prch->base_url);
413 4 : GNUNET_free (prch->target_payto_uri.full_payto);
414 4 : GNUNET_free (prch);
415 4 : }
416 :
417 :
418 : /* end of exchange_api_post-reserves-RESERVE_PUB-close.c */
|