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