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 : * Function called when we're done processing the
141 : * HTTP /reserves/$RID/close request.
142 : *
143 : * @param cls the `struct TALER_EXCHANGE_PostReservesCloseHandle`
144 : * @param response_code HTTP response code, 0 on error
145 : * @param response parsed JSON result, NULL on error
146 : */
147 : static void
148 4 : handle_reserves_close_finished (void *cls,
149 : long response_code,
150 : const void *response)
151 : {
152 4 : struct TALER_EXCHANGE_PostReservesCloseHandle *prch = cls;
153 4 : const json_t *j = response;
154 4 : struct TALER_EXCHANGE_PostReservesCloseResponse rs = {
155 : .hr.reply = j,
156 4 : .hr.http_status = (unsigned int) response_code
157 : };
158 :
159 4 : prch->job = NULL;
160 4 : switch (response_code)
161 : {
162 0 : case 0:
163 0 : rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
164 0 : break;
165 2 : case MHD_HTTP_OK:
166 2 : if (GNUNET_OK !=
167 2 : handle_reserves_close_ok (prch,
168 : j))
169 : {
170 0 : GNUNET_break_op (0);
171 0 : rs.hr.http_status = 0;
172 0 : rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
173 : }
174 2 : break;
175 0 : case MHD_HTTP_BAD_REQUEST:
176 : /* This should never happen, either us or the exchange is buggy
177 : (or API version conflict); just pass JSON reply to the application */
178 0 : GNUNET_break (0);
179 0 : rs.hr.ec = TALER_JSON_get_error_code (j);
180 0 : rs.hr.hint = TALER_JSON_get_error_hint (j);
181 0 : break;
182 0 : case MHD_HTTP_FORBIDDEN:
183 : /* This should never happen, either us or the exchange is buggy
184 : (or API version conflict); just pass JSON reply to the application */
185 0 : GNUNET_break (0);
186 0 : rs.hr.ec = TALER_JSON_get_error_code (j);
187 0 : rs.hr.hint = TALER_JSON_get_error_hint (j);
188 0 : break;
189 0 : case MHD_HTTP_NOT_FOUND:
190 : /* Nothing really to verify, this should never
191 : happen, we should pass the JSON reply to the application */
192 0 : rs.hr.ec = TALER_JSON_get_error_code (j);
193 0 : rs.hr.hint = TALER_JSON_get_error_hint (j);
194 0 : break;
195 0 : case MHD_HTTP_CONFLICT:
196 : /* Insufficient balance to inquire for reserve close */
197 0 : rs.hr.ec = TALER_JSON_get_error_code (j);
198 0 : rs.hr.hint = TALER_JSON_get_error_hint (j);
199 0 : break;
200 2 : case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
201 2 : rs.hr.ec = TALER_JSON_get_error_code (j);
202 2 : rs.hr.hint = TALER_JSON_get_error_hint (j);
203 2 : if (GNUNET_OK !=
204 2 : TALER_EXCHANGE_parse_451 (&rs.details.unavailable_for_legal_reasons,
205 : j))
206 : {
207 0 : GNUNET_break_op (0);
208 0 : rs.hr.http_status = 0;
209 0 : rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED;
210 : }
211 2 : break;
212 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
213 : /* Server had an internal issue; we should retry, but this API
214 : leaves this to the application */
215 0 : rs.hr.ec = TALER_JSON_get_error_code (j);
216 0 : rs.hr.hint = TALER_JSON_get_error_hint (j);
217 0 : break;
218 0 : default:
219 : /* unexpected response code */
220 0 : GNUNET_break_op (0);
221 0 : rs.hr.ec = TALER_JSON_get_error_code (j);
222 0 : rs.hr.hint = TALER_JSON_get_error_hint (j);
223 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
224 : "Unexpected response code %u/%d for reserves close\n",
225 : (unsigned int) response_code,
226 : (int) rs.hr.ec);
227 0 : break;
228 : }
229 4 : if (NULL != prch->cb)
230 : {
231 2 : prch->cb (prch->cb_cls,
232 : &rs);
233 2 : prch->cb = NULL;
234 : }
235 4 : TALER_EXCHANGE_post_reserves_close_cancel (prch);
236 4 : }
237 :
238 :
239 : struct TALER_EXCHANGE_PostReservesCloseHandle *
240 4 : TALER_EXCHANGE_post_reserves_close_create (
241 : struct GNUNET_CURL_Context *ctx,
242 : const char *url,
243 : const struct TALER_ReservePrivateKeyP *reserve_priv)
244 : {
245 : struct TALER_EXCHANGE_PostReservesCloseHandle *prch;
246 :
247 4 : prch = GNUNET_new (struct TALER_EXCHANGE_PostReservesCloseHandle);
248 4 : prch->ctx = ctx;
249 4 : prch->base_url = GNUNET_strdup (url);
250 4 : prch->reserve_priv = *reserve_priv;
251 4 : GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv,
252 : &prch->reserve_pub.eddsa_pub);
253 4 : prch->target_payto_uri.full_payto = NULL;
254 4 : return prch;
255 : }
256 :
257 :
258 : enum GNUNET_GenericReturnValue
259 4 : TALER_EXCHANGE_post_reserves_close_set_options_ (
260 : struct TALER_EXCHANGE_PostReservesCloseHandle *prch,
261 : unsigned int num_options,
262 : const struct TALER_EXCHANGE_PostReservesCloseOptionValue options[])
263 : {
264 8 : for (unsigned int i = 0; i < num_options; i++)
265 : {
266 8 : const struct TALER_EXCHANGE_PostReservesCloseOptionValue *opt = &options[i];
267 :
268 8 : switch (opt->option)
269 : {
270 4 : case TALER_EXCHANGE_POST_RESERVES_CLOSE_OPTION_END:
271 4 : return GNUNET_OK;
272 4 : case TALER_EXCHANGE_POST_RESERVES_CLOSE_OPTION_PAYTO_URI:
273 4 : GNUNET_free (prch->target_payto_uri.full_payto);
274 : prch->target_payto_uri.full_payto
275 4 : = GNUNET_strdup (opt->details.payto_uri.full_payto);
276 4 : break;
277 : }
278 : }
279 0 : return GNUNET_OK;
280 : }
281 :
282 :
283 : enum TALER_ErrorCode
284 4 : TALER_EXCHANGE_post_reserves_close_start (
285 : struct TALER_EXCHANGE_PostReservesCloseHandle *prch,
286 : TALER_EXCHANGE_PostReservesCloseCallback cb,
287 : TALER_EXCHANGE_POST_RESERVES_CLOSE_RESULT_CLOSURE *cb_cls)
288 : {
289 : CURL *eh;
290 : char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32];
291 : struct TALER_ReserveSignatureP reserve_sig;
292 : struct TALER_FullPaytoHashP h_payto;
293 : json_t *close_obj;
294 :
295 4 : prch->cb = cb;
296 4 : prch->cb_cls = cb_cls;
297 4 : prch->ts = GNUNET_TIME_timestamp_get ();
298 : {
299 : char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
300 : char *end;
301 :
302 4 : end = GNUNET_STRINGS_data_to_string (
303 4 : &prch->reserve_pub,
304 : sizeof (prch->reserve_pub),
305 : pub_str,
306 : sizeof (pub_str));
307 4 : *end = '\0';
308 4 : GNUNET_snprintf (arg_str,
309 : sizeof (arg_str),
310 : "reserves/%s/close",
311 : pub_str);
312 : }
313 4 : prch->url = TALER_url_join (prch->base_url,
314 : arg_str,
315 : NULL);
316 4 : if (NULL == prch->url)
317 0 : return TALER_EC_GENERIC_CONFIGURATION_INVALID;
318 4 : if (NULL != prch->target_payto_uri.full_payto)
319 4 : TALER_full_payto_hash (prch->target_payto_uri,
320 : &h_payto);
321 4 : TALER_wallet_reserve_close_sign (prch->ts,
322 4 : (NULL != prch->target_payto_uri.full_payto)
323 : ? &h_payto
324 : : NULL,
325 4 : &prch->reserve_priv,
326 : &reserve_sig);
327 4 : close_obj = GNUNET_JSON_PACK (
328 : GNUNET_JSON_pack_allow_null (
329 : TALER_JSON_pack_full_payto ("payto_uri",
330 : prch->target_payto_uri)),
331 : GNUNET_JSON_pack_timestamp ("request_timestamp",
332 : prch->ts),
333 : GNUNET_JSON_pack_data_auto ("reserve_sig",
334 : &reserve_sig));
335 4 : eh = TALER_EXCHANGE_curl_easy_get_ (prch->url);
336 8 : if ( (NULL == eh) ||
337 : (GNUNET_OK !=
338 4 : TALER_curl_easy_post (&prch->post_ctx,
339 : eh,
340 : close_obj)) )
341 : {
342 0 : GNUNET_break (0);
343 0 : if (NULL != eh)
344 0 : curl_easy_cleanup (eh);
345 0 : json_decref (close_obj);
346 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
347 : }
348 4 : json_decref (close_obj);
349 8 : prch->job = GNUNET_CURL_job_add2 (prch->ctx,
350 : eh,
351 4 : prch->post_ctx.headers,
352 : &handle_reserves_close_finished,
353 : prch);
354 4 : if (NULL == prch->job)
355 0 : return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
356 4 : return TALER_EC_NONE;
357 : }
358 :
359 :
360 : void
361 4 : TALER_EXCHANGE_post_reserves_close_cancel (
362 : struct TALER_EXCHANGE_PostReservesCloseHandle *prch)
363 : {
364 4 : if (NULL != prch->job)
365 : {
366 0 : GNUNET_CURL_job_cancel (prch->job);
367 0 : prch->job = NULL;
368 : }
369 4 : TALER_curl_easy_post_finished (&prch->post_ctx);
370 4 : GNUNET_free (prch->url);
371 4 : GNUNET_free (prch->base_url);
372 4 : GNUNET_free (prch->target_payto_uri.full_payto);
373 4 : GNUNET_free (prch);
374 4 : }
375 :
376 :
377 : /* end of exchange_api_post-reserves-RESERVE_PUB-close.c */
|