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_random_block (&wp->request_uid,
100 : sizeof 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 ( (0 == d_len) ||
273 55 : (0 == u_len) ||
274 55 : (sizeof (*wp) + d_len + u_len > buf_size) ||
275 55 : (d_len > buf_size) ||
276 55 : (u_len > buf_size) ||
277 55 : (d_len + u_len > buf_size) )
278 : {
279 0 : GNUNET_break (0);
280 0 : return NULL;
281 : }
282 55 : x_len = buf_size - (sizeof (*wp) + d_len + u_len);
283 55 : destination_account_uri = (const char *) &wp[1];
284 55 : exchange_base_url = destination_account_uri + d_len;
285 55 : if ( ('\0' != destination_account_uri[d_len - 1]) ||
286 55 : ('\0' != exchange_base_url[u_len - 1]) )
287 : {
288 0 : GNUNET_break (0);
289 0 : return NULL;
290 : }
291 55 : if (0 != x_len)
292 : {
293 0 : extra_metadata = destination_account_uri + d_len + u_len;
294 0 : if ('\0' != extra_metadata[x_len - 1])
295 : {
296 0 : GNUNET_break (0);
297 0 : return NULL;
298 : }
299 : }
300 : else
301 : {
302 55 : extra_metadata = NULL;
303 : }
304 :
305 55 : if (NULL == auth->wire_gateway_url)
306 : {
307 0 : GNUNET_break (0);
308 0 : return NULL;
309 : }
310 55 : TALER_amount_ntoh (&amount,
311 : &wp->amount);
312 55 : th = GNUNET_new (struct TALER_BANK_TransferHandle);
313 55 : th->cb = cc;
314 55 : th->cb_cls = cc_cls;
315 55 : th->request_url = TALER_url_join (auth->wire_gateway_url,
316 : "transfer",
317 : NULL);
318 55 : if (NULL == th->request_url)
319 : {
320 0 : GNUNET_free (th);
321 0 : GNUNET_break (0);
322 0 : return NULL;
323 : }
324 55 : transfer_obj = GNUNET_JSON_PACK (
325 : GNUNET_JSON_pack_data_auto ("request_uid",
326 : &wp->request_uid),
327 : TALER_JSON_pack_amount ("amount",
328 : &amount),
329 : GNUNET_JSON_pack_string ("exchange_base_url",
330 : exchange_base_url),
331 : GNUNET_JSON_pack_allow_null (
332 : GNUNET_JSON_pack_string ("metadata",
333 : extra_metadata)),
334 : GNUNET_JSON_pack_data_auto ("wtid",
335 : &wp->wtid),
336 : GNUNET_JSON_pack_string ("credit_account",
337 : destination_account_uri));
338 55 : eh = curl_easy_init ();
339 110 : if ( (NULL == eh) ||
340 : (GNUNET_OK !=
341 55 : TALER_BANK_setup_auth_ (eh,
342 55 : auth)) ||
343 : (CURLE_OK !=
344 55 : curl_easy_setopt (eh,
345 : CURLOPT_URL,
346 55 : th->request_url)) ||
347 : (GNUNET_OK !=
348 55 : TALER_curl_easy_post (&th->post_ctx,
349 : eh,
350 : transfer_obj)) )
351 : {
352 0 : GNUNET_break (0);
353 0 : TALER_BANK_transfer_cancel (th);
354 0 : if (NULL != eh)
355 0 : curl_easy_cleanup (eh);
356 0 : json_decref (transfer_obj);
357 0 : return NULL;
358 : }
359 55 : json_decref (transfer_obj);
360 110 : th->job = GNUNET_CURL_job_add2 (ctx,
361 : eh,
362 55 : th->post_ctx.headers,
363 : &handle_transfer_finished,
364 : th);
365 55 : if (NULL == th->job)
366 : {
367 0 : GNUNET_break (0);
368 0 : curl_easy_cleanup (eh);
369 0 : TALER_BANK_transfer_cancel (th);
370 0 : return NULL;
371 : }
372 55 : return th;
373 : }
374 :
375 :
376 : void
377 55 : TALER_BANK_transfer_cancel (struct TALER_BANK_TransferHandle *th)
378 : {
379 55 : if (NULL != th->job)
380 : {
381 0 : GNUNET_CURL_job_cancel (th->job);
382 0 : th->job = NULL;
383 : }
384 55 : TALER_curl_easy_post_finished (&th->post_ctx);
385 55 : GNUNET_free (th->request_url);
386 55 : GNUNET_free (th);
387 55 : }
388 :
389 :
390 : /* end of bank_api_transfer.c */
|