Line data Source code
1 : /*
2 : This file is part of TALER
3 : (C) 2021, 2022 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify
6 : it under the terms of the GNU Affero General Public License as
7 : published by the Free Software Foundation; either version 3,
8 : or (at your option) any later version.
9 :
10 : TALER is distributed in the hope that it will be useful, but
11 : WITHOUT ANY WARRANTY; without even the implied warranty of
12 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 : GNU General Public License for more details.
14 :
15 : You should have received a copy of the GNU General Public
16 : License along with TALER; see the file COPYING. If not,
17 : see <http://www.gnu.org/licenses/>
18 : */
19 :
20 : /**
21 : * @file taler-merchant-httpd_private-post-reserves.c
22 : * @brief implementing POST /reserves request handling
23 : * @author Christian Grothoff
24 : */
25 : #include "platform.h"
26 : #include "taler-merchant-httpd_exchanges.h"
27 : #include "taler-merchant-httpd_private-post-reserves.h"
28 : #include "taler-merchant-httpd_reserves.h"
29 : #include <taler/taler_json_lib.h>
30 :
31 :
32 : /**
33 : * How long to wait before giving up processing with the exchange?
34 : */
35 : #define EXCHANGE_GENERIC_TIMEOUT (GNUNET_TIME_relative_multiply ( \
36 : GNUNET_TIME_UNIT_SECONDS, \
37 : 15))
38 :
39 :
40 : /**
41 : * Information we keep for an individual call to the POST /reserves handler.
42 : */
43 : struct PostReserveContext
44 : {
45 :
46 : /**
47 : * Stored in a DLL.
48 : */
49 : struct PostReserveContext *next;
50 :
51 : /**
52 : * Stored in a DLL.
53 : */
54 : struct PostReserveContext *prev;
55 :
56 : /**
57 : * Array with @e coins_cnt coins we are despositing.
58 : */
59 : struct DepositConfirmation *dc;
60 :
61 : /**
62 : * MHD connection to return to
63 : */
64 : struct MHD_Connection *connection;
65 :
66 : /**
67 : * Details about the client's request.
68 : */
69 : struct TMH_HandlerContext *hc;
70 :
71 : /**
72 : * URL of the exchange.
73 : */
74 : const char *exchange_url;
75 :
76 : /**
77 : * URI of the exchange where the payment needs to be made to.
78 : */
79 : char *payto_uri;
80 :
81 : /**
82 : * Handle for contacting the exchange.
83 : */
84 : struct TMH_EXCHANGES_FindOperation *fo;
85 :
86 : /**
87 : * Task run on timeout.
88 : */
89 : struct GNUNET_SCHEDULER_Task *timeout_task;
90 :
91 : /**
92 : * Initial balance of the reserve.
93 : */
94 : struct TALER_Amount initial_balance;
95 :
96 : /**
97 : * When will the reserve expire.
98 : */
99 : struct GNUNET_TIME_Timestamp reserve_expiration;
100 :
101 : /**
102 : * Which HTTP status should we return?
103 : */
104 : unsigned int http_status;
105 :
106 : /**
107 : * Which error code should we return?
108 : */
109 : enum TALER_ErrorCode ec;
110 :
111 : /**
112 : * Did we suspend @a connection and are thus in
113 : * the #rc_head DLL (#GNUNET_YES). Set to
114 : * #GNUNET_NO if we are not suspended, and to
115 : * #GNUNET_SYSERR if we should close the connection
116 : * without a response due to shutdown.
117 : */
118 : enum GNUNET_GenericReturnValue suspended;
119 : };
120 :
121 :
122 : /**
123 : * Stored in a DLL.
124 : */
125 : static struct PostReserveContext *rc_head;
126 :
127 : /**
128 : * Stored in a DLL.
129 : */
130 : static struct PostReserveContext *rc_tail;
131 :
132 :
133 : /**
134 : * Force all post reserve contexts to be resumed as we are about
135 : * to shut down MHD.
136 : */
137 : void
138 0 : TMH_force_rc_resume ()
139 : {
140 : struct PostReserveContext *rcn;
141 :
142 0 : for (struct PostReserveContext *rc = rc_head;
143 : NULL != rc;
144 0 : rc = rcn)
145 : {
146 0 : rcn = rc->next;
147 0 : if (NULL != rc->timeout_task)
148 : {
149 0 : GNUNET_SCHEDULER_cancel (rc->timeout_task);
150 0 : rc->timeout_task = NULL;
151 : }
152 0 : if (GNUNET_YES == rc->suspended)
153 : {
154 0 : rc->suspended = GNUNET_SYSERR;
155 0 : MHD_resume_connection (rc->connection);
156 0 : GNUNET_CONTAINER_DLL_remove (rc_head,
157 : rc_tail,
158 : rc);
159 : }
160 0 : if (NULL != rc->fo)
161 : {
162 0 : TMH_EXCHANGES_find_exchange_cancel (rc->fo);
163 0 : rc->fo = NULL;
164 : }
165 : }
166 0 : }
167 :
168 :
169 : /**
170 : * Custom cleanup routine for a `struct PostReserveContext`.
171 : *
172 : * @param cls the `struct PostReserveContext` to clean up.
173 : */
174 : static void
175 0 : reserve_context_cleanup (void *cls)
176 : {
177 0 : struct PostReserveContext *rc = cls;
178 :
179 0 : if (NULL != rc->fo)
180 : {
181 0 : TMH_EXCHANGES_find_exchange_cancel (rc->fo);
182 0 : rc->fo = NULL;
183 : }
184 0 : if (NULL != rc->timeout_task)
185 : {
186 0 : GNUNET_SCHEDULER_cancel (rc->timeout_task);
187 0 : rc->timeout_task = NULL;
188 : }
189 0 : GNUNET_assert (GNUNET_YES != rc->suspended);
190 0 : GNUNET_free (rc->payto_uri);
191 0 : GNUNET_free (rc);
192 0 : }
193 :
194 :
195 : /**
196 : * Function called with the result of a #TMH_EXCHANGES_find_exchange()
197 : * operation.
198 : *
199 : * @param cls closure with our `struct PostReserveContext *`
200 : * @param hr HTTP response details
201 : * @param payto_uri URI of the exchange for the wire transfer, NULL on errors
202 : * @param eh handle to the exchange context
203 : * @param wire_fee current applicable wire fee for dealing with @a eh, NULL if not available
204 : * @param exchange_trusted true if this exchange is trusted by config
205 : */
206 : static void
207 0 : handle_exchange (void *cls,
208 : const struct TALER_EXCHANGE_HttpResponse *hr,
209 : struct TALER_EXCHANGE_Handle *eh,
210 : const char *payto_uri,
211 : const struct TALER_Amount *wire_fee,
212 : bool exchange_trusted)
213 : {
214 0 : struct PostReserveContext *rc = cls;
215 : const struct TALER_EXCHANGE_Keys *keys;
216 :
217 0 : rc->fo = NULL;
218 0 : if (NULL != rc->timeout_task)
219 : {
220 0 : GNUNET_SCHEDULER_cancel (rc->timeout_task);
221 0 : rc->timeout_task = NULL;
222 : }
223 0 : rc->suspended = GNUNET_NO;
224 0 : MHD_resume_connection (rc->connection);
225 0 : GNUNET_CONTAINER_DLL_remove (rc_head,
226 : rc_tail,
227 : rc);
228 0 : if (NULL == hr)
229 : {
230 0 : rc->ec = TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT;
231 0 : rc->http_status = MHD_HTTP_GATEWAY_TIMEOUT;
232 0 : TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
233 0 : return;
234 : }
235 0 : if (NULL == eh)
236 : {
237 0 : rc->ec = TALER_EC_MERCHANT_GENERIC_EXCHANGE_CONNECT_FAILURE;
238 0 : rc->http_status = MHD_HTTP_BAD_GATEWAY;
239 0 : TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
240 0 : return;
241 : }
242 0 : keys = TALER_EXCHANGE_get_keys (eh);
243 0 : if (NULL == keys)
244 : {
245 0 : rc->ec = TALER_EC_MERCHANT_GENERIC_EXCHANGE_KEYS_FAILURE;
246 0 : rc->http_status = MHD_HTTP_BAD_GATEWAY;
247 0 : TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
248 0 : return;
249 : }
250 0 : if (NULL == payto_uri)
251 : {
252 0 : rc->ec = TALER_EC_MERCHANT_PRIVATE_POST_RESERVES_UNSUPPORTED_WIRE_METHOD;
253 0 : rc->http_status = MHD_HTTP_CONFLICT;
254 0 : TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
255 0 : return;
256 : }
257 : rc->reserve_expiration
258 0 : = GNUNET_TIME_relative_to_timestamp (keys->reserve_closing_delay);
259 0 : rc->payto_uri = GNUNET_strdup (payto_uri);
260 0 : TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
261 : }
262 :
263 :
264 : /**
265 : * Handle a timeout for the processing of the wire request.
266 : *
267 : * @param cls closure
268 : */
269 : static void
270 0 : handle_exchange_timeout (void *cls)
271 : {
272 0 : struct PostReserveContext *rc = cls;
273 :
274 0 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
275 : "Resuming POST /private/reserves with error after timeout\n");
276 0 : rc->timeout_task = NULL;
277 0 : if (NULL != rc->fo)
278 : {
279 0 : TMH_EXCHANGES_find_exchange_cancel (rc->fo);
280 0 : rc->fo = NULL;
281 : }
282 0 : rc->suspended = GNUNET_NO;
283 0 : MHD_resume_connection (rc->connection);
284 0 : GNUNET_CONTAINER_DLL_remove (rc_head,
285 : rc_tail,
286 : rc);
287 0 : rc->ec = TALER_EC_MERCHANT_GENERIC_EXCHANGE_TIMEOUT;
288 0 : rc->http_status = MHD_HTTP_GATEWAY_TIMEOUT;
289 0 : TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */
290 0 : }
291 :
292 :
293 : MHD_RESULT
294 0 : TMH_private_post_reserves (const struct TMH_RequestHandler *rh,
295 : struct MHD_Connection *connection,
296 : struct TMH_HandlerContext *hc)
297 : {
298 0 : struct PostReserveContext *rc = hc->ctx;
299 0 : struct TMH_MerchantInstance *mi = hc->instance;
300 :
301 0 : GNUNET_assert (NULL != mi);
302 0 : if (NULL == rc)
303 : {
304 : const char *wire_method;
305 :
306 0 : rc = GNUNET_new (struct PostReserveContext);
307 0 : rc->connection = connection;
308 0 : rc->hc = hc;
309 0 : hc->ctx = rc;
310 0 : hc->cc = &reserve_context_cleanup;
311 :
312 : {
313 : enum GNUNET_GenericReturnValue res;
314 : struct GNUNET_JSON_Specification spec[] = {
315 0 : GNUNET_JSON_spec_string ("exchange_url",
316 : &rc->exchange_url),
317 0 : GNUNET_JSON_spec_string ("wire_method",
318 : &wire_method),
319 0 : TALER_JSON_spec_amount ("initial_balance",
320 : TMH_currency,
321 : &rc->initial_balance),
322 0 : GNUNET_JSON_spec_end ()
323 : };
324 0 : res = TALER_MHD_parse_json_data (connection,
325 0 : hc->request_body,
326 : spec);
327 0 : if (GNUNET_OK != res)
328 : return (GNUNET_NO == res)
329 : ? MHD_YES
330 0 : : MHD_NO;
331 : }
332 0 : rc->fo = TMH_EXCHANGES_find_exchange (rc->exchange_url,
333 : wire_method,
334 : GNUNET_NO,
335 : &handle_exchange,
336 : rc);
337 : rc->timeout_task
338 0 : = GNUNET_SCHEDULER_add_delayed (EXCHANGE_GENERIC_TIMEOUT,
339 : &handle_exchange_timeout,
340 : rc);
341 0 : rc->suspended = GNUNET_YES;
342 0 : GNUNET_CONTAINER_DLL_insert (rc_head,
343 : rc_tail,
344 : rc);
345 0 : MHD_suspend_connection (connection);
346 0 : return MHD_YES;
347 : }
348 0 : if (GNUNET_SYSERR == rc->suspended)
349 0 : return MHD_NO; /* we are in shutdown */
350 :
351 0 : GNUNET_assert (GNUNET_NO == rc->suspended);
352 0 : if (NULL == rc->payto_uri)
353 : {
354 0 : return TALER_MHD_reply_with_error (connection,
355 : rc->http_status,
356 : rc->ec,
357 : NULL);
358 : }
359 : {
360 : struct TALER_ReservePublicKeyP reserve_pub;
361 : struct TALER_ReservePrivateKeyP reserve_priv;
362 : enum GNUNET_DB_QueryStatus qs;
363 :
364 0 : GNUNET_CRYPTO_eddsa_key_create (&reserve_priv.eddsa_priv);
365 0 : GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv.eddsa_priv,
366 : &reserve_pub.eddsa_pub);
367 0 : qs = TMH_db->insert_reserve (TMH_db->cls,
368 0 : mi->settings.id,
369 : &reserve_priv,
370 : &reserve_pub,
371 : rc->exchange_url,
372 0 : rc->payto_uri,
373 0 : &rc->initial_balance,
374 : rc->reserve_expiration);
375 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
376 0 : TMH_RESERVES_check (mi->settings.id,
377 : rc->exchange_url,
378 : &reserve_pub,
379 0 : &rc->initial_balance);
380 0 : if (qs < 0)
381 0 : return TALER_MHD_reply_with_error (connection,
382 : MHD_HTTP_INTERNAL_SERVER_ERROR,
383 : TALER_EC_GENERIC_DB_STORE_FAILED,
384 : "reserve");
385 0 : return TALER_MHD_REPLY_JSON_PACK (
386 : connection,
387 : MHD_HTTP_OK,
388 : GNUNET_JSON_pack_data_auto ("reserve_pub",
389 : &reserve_pub),
390 : GNUNET_JSON_pack_string ("payto_uri",
391 : rc->payto_uri));
392 : }
393 : }
394 :
395 :
396 : /* end of taler-merchant-httpd_private-post-reserves.c */
|