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