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