Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2015--2023, 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 bank-lib/bank_api_transfer.c
19 : * @brief Implementation of the /transfer/ requests of the bank's HTTP API
20 : * @author Christian Grothoff
21 : */
22 : #include "taler/platform.h"
23 : #include "bank_api_common.h"
24 : #include <microhttpd.h> /* just for HTTP status codes */
25 : #include "taler/taler_signatures.h"
26 : #include "taler/taler_curl_lib.h"
27 : #include "taler/taler_bank_service.h"
28 :
29 :
30 : GNUNET_NETWORK_STRUCT_BEGIN
31 :
32 : /**
33 : * Data structure serialized in the prepare stage.
34 : */
35 : struct WirePackP
36 : {
37 : /**
38 : * Random unique identifier for the request.
39 : */
40 : struct GNUNET_HashCode request_uid;
41 :
42 : /**
43 : * Amount to be transferred.
44 : */
45 : struct TALER_AmountNBO amount;
46 :
47 : /**
48 : * Wire transfer identifier to use.
49 : */
50 : struct TALER_WireTransferIdentifierRawP wtid;
51 :
52 : /**
53 : * Length of the payto:// URL of the target account,
54 : * including 0-terminator, in network byte order.
55 : */
56 : uint32_t account_len GNUNET_PACKED;
57 :
58 : /**
59 : * Length of the exchange's base URL,
60 : * including 0-terminator, in network byte order.
61 : */
62 : uint32_t exchange_url_len GNUNET_PACKED;
63 :
64 : };
65 :
66 : GNUNET_NETWORK_STRUCT_END
67 :
68 :
69 : void
70 61 : TALER_BANK_prepare_transfer (
71 : const struct TALER_FullPayto destination_account_payto_uri,
72 : const struct TALER_Amount *amount,
73 : const char *exchange_base_url,
74 : const struct TALER_WireTransferIdentifierRawP *wtid,
75 : const char *extra_wire_transfer_subject,
76 : void **buf,
77 : size_t *buf_size)
78 : {
79 61 : const char *payto = destination_account_payto_uri.full_payto;
80 : struct WirePackP *wp;
81 61 : size_t d_len = strlen (payto) + 1;
82 61 : size_t u_len = strlen (exchange_base_url) + 1;
83 61 : size_t x_len = (NULL == extra_wire_transfer_subject)
84 : ? 0
85 61 : : strlen (extra_wire_transfer_subject) + 1;
86 : char *end;
87 :
88 61 : if ( (d_len >= (size_t) GNUNET_MAX_MALLOC_CHECKED) ||
89 61 : (u_len >= (size_t) GNUNET_MAX_MALLOC_CHECKED) ||
90 61 : (x_len >= (size_t) GNUNET_MAX_MALLOC_CHECKED) ||
91 61 : (d_len + u_len + x_len + sizeof (*wp) >= GNUNET_MAX_MALLOC_CHECKED) )
92 : {
93 0 : GNUNET_break (0); /* that's some long URL... */
94 0 : *buf = NULL;
95 0 : *buf_size = 0;
96 0 : return;
97 : }
98 61 : *buf_size = sizeof (*wp) + d_len + u_len + x_len;
99 61 : wp = GNUNET_malloc (*buf_size);
100 61 : GNUNET_CRYPTO_hash_create_random (GNUNET_CRYPTO_QUALITY_NONCE,
101 : &wp->request_uid);
102 61 : TALER_amount_hton (&wp->amount,
103 : amount);
104 61 : wp->wtid = *wtid;
105 61 : wp->account_len = htonl ((uint32_t) d_len);
106 61 : wp->exchange_url_len = htonl ((uint32_t) u_len);
107 61 : end = (char *) &wp[1];
108 61 : GNUNET_memcpy (end,
109 : payto,
110 : d_len);
111 61 : GNUNET_memcpy (end + d_len,
112 : exchange_base_url,
113 : u_len);
114 61 : GNUNET_memcpy (end + d_len + u_len,
115 : extra_wire_transfer_subject,
116 : x_len);
117 61 : *buf = (char *) wp;
118 : }
119 :
120 :
121 : /**
122 : * @brief Handle for an active wire transfer.
123 : */
124 : struct TALER_BANK_TransferHandle
125 : {
126 :
127 : /**
128 : * The url for this request.
129 : */
130 : char *request_url;
131 :
132 : /**
133 : * POST context.
134 : */
135 : struct TALER_CURL_PostContext post_ctx;
136 :
137 : /**
138 : * Handle for the request.
139 : */
140 : struct GNUNET_CURL_Job *job;
141 :
142 : /**
143 : * Function to call with the result.
144 : */
145 : TALER_BANK_TransferCallback cb;
146 :
147 : /**
148 : * Closure for @a cb.
149 : */
150 : void *cb_cls;
151 :
152 : };
153 :
154 :
155 : /**
156 : * Function called when we're done processing the
157 : * HTTP /transfer request.
158 : *
159 : * @param cls the `struct TALER_BANK_TransferHandle`
160 : * @param response_code HTTP response code, 0 on error
161 : * @param response parsed JSON result, NULL on error
162 : */
163 : static void
164 55 : handle_transfer_finished (void *cls,
165 : long response_code,
166 : const void *response)
167 : {
168 55 : struct TALER_BANK_TransferHandle *th = cls;
169 55 : const json_t *j = response;
170 55 : struct TALER_BANK_TransferResponse tr = {
171 : .http_status = response_code,
172 : .response = j
173 : };
174 :
175 55 : th->job = NULL;
176 55 : switch (response_code)
177 : {
178 0 : case 0:
179 0 : tr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
180 0 : break;
181 55 : case MHD_HTTP_OK:
182 : {
183 : struct GNUNET_JSON_Specification spec[] = {
184 55 : GNUNET_JSON_spec_uint64 ("row_id",
185 : &tr.details.ok.row_id),
186 55 : GNUNET_JSON_spec_timestamp ("timestamp",
187 : &tr.details.ok.timestamp),
188 55 : GNUNET_JSON_spec_end ()
189 : };
190 :
191 55 : if (GNUNET_OK !=
192 55 : GNUNET_JSON_parse (j,
193 : spec,
194 : NULL, NULL))
195 : {
196 0 : GNUNET_break_op (0);
197 0 : tr.http_status = 0;
198 0 : tr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
199 0 : break;
200 : }
201 : }
202 55 : break;
203 0 : case MHD_HTTP_BAD_REQUEST:
204 : /* This should never happen, either us or the bank is buggy
205 : (or API version conflict); just pass JSON reply to the application */
206 0 : GNUNET_break_op (0);
207 0 : tr.ec = TALER_JSON_get_error_code (j);
208 0 : break;
209 0 : case MHD_HTTP_UNAUTHORIZED:
210 : /* Nothing really to verify, bank says our credentials are
211 : invalid. We should pass the JSON reply to the application. */
212 0 : tr.ec = TALER_JSON_get_error_code (j);
213 0 : break;
214 0 : case MHD_HTTP_NOT_FOUND:
215 : /* Nothing really to verify, endpoint wrong -- could be user unknown */
216 0 : tr.ec = TALER_JSON_get_error_code (j);
217 0 : break;
218 0 : case MHD_HTTP_CONFLICT:
219 : /* Nothing really to verify. Server says we used the same transfer request
220 : UID before, but with different details. Should not happen if the user
221 : properly used #TALER_BANK_prepare_transfer() and our PRNG is not
222 : broken... */
223 0 : tr.ec = TALER_JSON_get_error_code (j);
224 0 : break;
225 0 : case MHD_HTTP_INTERNAL_SERVER_ERROR:
226 : /* Server had an internal issue; we should retry, but this API
227 : leaves this to the application */
228 0 : tr.ec = TALER_JSON_get_error_code (j);
229 0 : break;
230 0 : default:
231 : /* unexpected response code */
232 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
233 : "Unexpected response code %u\n",
234 : (unsigned int) response_code);
235 0 : GNUNET_break (0);
236 0 : tr.ec = TALER_JSON_get_error_code (j);
237 0 : break;
238 : }
239 55 : th->cb (th->cb_cls,
240 : &tr);
241 55 : TALER_BANK_transfer_cancel (th);
242 55 : }
243 :
244 :
245 : struct TALER_BANK_TransferHandle *
246 55 : TALER_BANK_transfer (
247 : struct GNUNET_CURL_Context *ctx,
248 : const struct TALER_BANK_AuthenticationData *auth,
249 : const void *buf,
250 : size_t buf_size,
251 : TALER_BANK_TransferCallback cc,
252 : void *cc_cls)
253 : {
254 : struct TALER_BANK_TransferHandle *th;
255 : json_t *transfer_obj;
256 : CURL *eh;
257 55 : const struct WirePackP *wp = buf;
258 : uint32_t d_len;
259 : uint32_t u_len;
260 : uint32_t x_len;
261 : const char *destination_account_uri;
262 : const char *exchange_base_url;
263 : const char *extra_metadata;
264 : struct TALER_Amount amount;
265 :
266 55 : if (sizeof (*wp) > buf_size)
267 : {
268 0 : GNUNET_break (0);
269 0 : return NULL;
270 : }
271 55 : d_len = ntohl (wp->account_len);
272 55 : u_len = ntohl (wp->exchange_url_len);
273 55 : if ( (sizeof (*wp) + d_len + u_len > buf_size) ||
274 55 : (d_len > buf_size) ||
275 55 : (u_len > buf_size) ||
276 55 : (d_len + u_len > buf_size) )
277 : {
278 0 : GNUNET_break (0);
279 0 : return NULL;
280 : }
281 55 : x_len = buf_size - (sizeof (*wp) + d_len + u_len);
282 55 : destination_account_uri = (const char *) &wp[1];
283 55 : exchange_base_url = destination_account_uri + d_len;
284 55 : if ( ('\0' != destination_account_uri[d_len - 1]) ||
285 55 : ('\0' != exchange_base_url[u_len - 1]) )
286 : {
287 0 : GNUNET_break (0);
288 0 : return NULL;
289 : }
290 55 : if (0 != x_len)
291 : {
292 0 : extra_metadata = destination_account_uri + d_len + u_len;
293 0 : if ('\0' != extra_metadata[x_len - 1])
294 : {
295 0 : GNUNET_break (0);
296 0 : return NULL;
297 : }
298 : }
299 : else
300 : {
301 55 : extra_metadata = NULL;
302 : }
303 :
304 55 : if (NULL == auth->wire_gateway_url)
305 : {
306 0 : GNUNET_break (0);
307 0 : return NULL;
308 : }
309 55 : TALER_amount_ntoh (&amount,
310 : &wp->amount);
311 55 : th = GNUNET_new (struct TALER_BANK_TransferHandle);
312 55 : th->cb = cc;
313 55 : th->cb_cls = cc_cls;
314 55 : th->request_url = TALER_url_join (auth->wire_gateway_url,
315 : "transfer",
316 : NULL);
317 55 : if (NULL == th->request_url)
318 : {
319 0 : GNUNET_free (th);
320 0 : GNUNET_break (0);
321 0 : return NULL;
322 : }
323 55 : transfer_obj = GNUNET_JSON_PACK (
324 : GNUNET_JSON_pack_data_auto ("request_uid",
325 : &wp->request_uid),
326 : TALER_JSON_pack_amount ("amount",
327 : &amount),
328 : GNUNET_JSON_pack_string ("exchange_base_url",
329 : exchange_base_url),
330 : GNUNET_JSON_pack_allow_null (
331 : GNUNET_JSON_pack_string ("metadata",
332 : extra_metadata)),
333 : GNUNET_JSON_pack_data_auto ("wtid",
334 : &wp->wtid),
335 : GNUNET_JSON_pack_string ("credit_account",
336 : destination_account_uri));
337 55 : if (NULL == transfer_obj)
338 : {
339 0 : GNUNET_break (0);
340 0 : return NULL;
341 : }
342 55 : eh = curl_easy_init ();
343 110 : if ( (NULL == eh) ||
344 : (GNUNET_OK !=
345 55 : TALER_BANK_setup_auth_ (eh,
346 55 : auth)) ||
347 : (CURLE_OK !=
348 55 : curl_easy_setopt (eh,
349 : CURLOPT_URL,
350 55 : th->request_url)) ||
351 : (GNUNET_OK !=
352 55 : TALER_curl_easy_post (&th->post_ctx,
353 : eh,
354 : transfer_obj)) )
355 : {
356 0 : GNUNET_break (0);
357 0 : TALER_BANK_transfer_cancel (th);
358 0 : if (NULL != eh)
359 0 : curl_easy_cleanup (eh);
360 0 : json_decref (transfer_obj);
361 0 : return NULL;
362 : }
363 55 : json_decref (transfer_obj);
364 110 : th->job = GNUNET_CURL_job_add2 (ctx,
365 : eh,
366 55 : th->post_ctx.headers,
367 : &handle_transfer_finished,
368 : th);
369 55 : return th;
370 : }
371 :
372 :
373 : void
374 55 : TALER_BANK_transfer_cancel (struct TALER_BANK_TransferHandle *th)
375 : {
376 55 : if (NULL != th->job)
377 : {
378 0 : GNUNET_CURL_job_cancel (th->job);
379 0 : th->job = NULL;
380 : }
381 55 : TALER_curl_easy_post_finished (&th->post_ctx);
382 55 : GNUNET_free (th->request_url);
383 55 : GNUNET_free (th);
384 55 : }
385 :
386 :
387 : /* end of bank_api_transfer.c */
|