Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2024, 2025 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify it under the
6 : terms of the GNU Affero 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 <http://www.gnu.org/licenses/>
15 : */
16 : /**
17 : * @file src/backend/taler-merchant-httpd_post-private-donau.c
18 : * @brief implementation of POST /donau
19 : * @author Bohdan Potuzhnyi
20 : * @author Vlada Svirsh
21 : * @author Christian Grothoff
22 : */
23 : #include "platform.h"
24 : #include <jansson.h>
25 : #include "donau/donau_service.h"
26 : #include <taler/taler_json_lib.h>
27 : #include <taler/taler_dbevents.h>
28 : #include "taler/taler_merchant_service.h"
29 : #include "taler-merchant-httpd_post-private-donau.h"
30 : #include "merchant-database/get_exists_donau_instance.h"
31 : #include "merchant-database/insert_donau_instance.h"
32 : #include "merchant-database/event_listen.h"
33 : #include "merchant-database/event_notify.h"
34 : #include "merchant-database/set_instance.h"
35 :
36 :
37 : /**
38 : * Context for the POST /donau request handler.
39 : */
40 : struct PostDonauCtx
41 : {
42 : /**
43 : * Stored in a DLL.
44 : */
45 : struct PostDonauCtx *next;
46 :
47 : /**
48 : * Stored in a DLL.
49 : */
50 : struct PostDonauCtx *prev;
51 :
52 : /**
53 : * Connection to the MHD server
54 : */
55 : struct MHD_Connection *connection;
56 :
57 : /**
58 : * Context of the request handler.
59 : */
60 : struct TMH_HandlerContext *hc;
61 :
62 : /**
63 : * URL of the DONAU service
64 : * to which the charity belongs.
65 : */
66 : const char *donau_url;
67 :
68 : /**
69 : * ID of the charity in the DONAU service.
70 : */
71 : uint64_t charity_id;
72 :
73 : /**
74 : * Handle returned by DONAU_charities_get(); needed to cancel on
75 : * connection abort, etc.
76 : */
77 : struct DONAU_CharityGetHandle *get_handle;
78 :
79 : /**
80 : * Response to return.
81 : */
82 : struct MHD_Response *response;
83 :
84 : /**
85 : * HTTP status for @e response.
86 : */
87 : unsigned int http_status;
88 :
89 : /**
90 : * #GNUNET_YES if we are suspended,
91 : * #GNUNET_NO if not,
92 : * #GNUNET_SYSERR on shutdown
93 : */
94 : enum GNUNET_GenericReturnValue suspended;
95 : };
96 :
97 :
98 : /**
99 : * Head of active pay context DLL.
100 : */
101 : static struct PostDonauCtx *pdc_head;
102 :
103 : /**
104 : * Tail of active pay context DLL.
105 : */
106 : static struct PostDonauCtx *pdc_tail;
107 :
108 :
109 : void
110 0 : TMH_force_post_donau_resume ()
111 : {
112 0 : for (struct PostDonauCtx *pdc = pdc_head;
113 0 : NULL != pdc;
114 0 : pdc = pdc->next)
115 : {
116 0 : if (GNUNET_YES == pdc->suspended)
117 : {
118 0 : pdc->suspended = GNUNET_SYSERR;
119 0 : MHD_resume_connection (pdc->connection);
120 : }
121 : }
122 0 : }
123 :
124 :
125 : /**
126 : * Callback for DONAU_charities_get() to handle the response.
127 : *
128 : * @param cls closure with PostDonauCtx
129 : * @param gcr response from Donau
130 : */
131 : static void
132 2 : donau_charity_get_cb (void *cls,
133 : const struct DONAU_GetCharityResponse *gcr)
134 : {
135 2 : struct PostDonauCtx *pdc = cls;
136 : enum GNUNET_DB_QueryStatus qs;
137 :
138 2 : pdc->get_handle = NULL;
139 2 : pdc->suspended = GNUNET_NO;
140 2 : MHD_resume_connection (pdc->connection);
141 2 : TALER_MHD_daemon_trigger ();
142 2 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
143 : "Processing DONAU charity get response");
144 : /* Anything but 200 => propagate Donau’s response. */
145 2 : if (MHD_HTTP_OK != gcr->hr.http_status)
146 : {
147 0 : pdc->http_status = MHD_HTTP_BAD_GATEWAY;
148 0 : pdc->response = TALER_MHD_MAKE_JSON_PACK (
149 : TALER_MHD_PACK_EC (gcr->hr.ec),
150 : GNUNET_JSON_pack_uint64 ("donau_http_status",
151 : gcr->hr.http_status));
152 0 : return;
153 : }
154 :
155 2 : if (0 !=
156 2 : GNUNET_memcmp (&gcr->details.ok.charity.charity_pub.eddsa_pub,
157 : &pdc->hc->instance->merchant_pub.eddsa_pub))
158 : {
159 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
160 : "Charity key at donau does not match our merchant key\n");
161 0 : pdc->http_status = MHD_HTTP_CONFLICT;
162 0 : pdc->response = TALER_MHD_make_error (
163 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
164 : "charity_pub != merchant_pub");
165 0 : return;
166 : }
167 :
168 2 : qs = TALER_MERCHANTDB_set_instance (
169 : TMH_db,
170 2 : pdc->hc->instance->settings.id);
171 2 : if (0 >= qs)
172 : {
173 0 : GNUNET_break (0);
174 0 : pdc->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
175 0 : pdc->response = TALER_MHD_make_error (
176 : TALER_EC_GENERIC_DB_STORE_FAILED,
177 : "set_instance");
178 0 : return;
179 : }
180 :
181 2 : qs = TALER_MERCHANTDB_insert_donau_instance (TMH_db,
182 : pdc->donau_url,
183 : &gcr->details.ok.charity,
184 : pdc->charity_id);
185 2 : GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
186 : TALER_MERCHANTDB_set_instance (
187 : TMH_db,
188 : NULL));
189 2 : switch (qs)
190 : {
191 0 : case GNUNET_DB_STATUS_HARD_ERROR:
192 0 : GNUNET_break (0);
193 0 : pdc->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
194 0 : pdc->response = TALER_MHD_make_error (
195 : TALER_EC_GENERIC_DB_STORE_FAILED,
196 : "insert_donau_instance");
197 0 : break;
198 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
199 0 : GNUNET_break (0);
200 0 : pdc->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
201 0 : pdc->response = TALER_MHD_make_error (
202 : TALER_EC_GENERIC_DB_STORE_FAILED,
203 : "insert_donau_instance");
204 0 : break;
205 0 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
206 : /* presumably idempotent + concurrent, no need to notify, but still respond */
207 0 : pdc->http_status = MHD_HTTP_NO_CONTENT;
208 0 : pdc->response = MHD_create_response_from_buffer_static (0,
209 : NULL);
210 0 : TALER_MHD_add_global_headers (pdc->response,
211 : false);
212 0 : break;
213 2 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
214 : {
215 2 : struct GNUNET_DB_EventHeaderP es = {
216 2 : .size = htons (sizeof (es)),
217 2 : .type = htons (TALER_DBEVENT_MERCHANT_DONAU_KEYS)
218 : };
219 :
220 2 : TALER_MERCHANTDB_event_notify (TMH_db,
221 : &es,
222 2 : pdc->donau_url,
223 2 : strlen (pdc->donau_url) + 1);
224 2 : pdc->http_status = MHD_HTTP_NO_CONTENT;
225 2 : pdc->response = MHD_create_response_from_buffer_static (0,
226 : NULL);
227 2 : TALER_MHD_add_global_headers (pdc->response,
228 : false);
229 2 : break;
230 : }
231 : }
232 : }
233 :
234 :
235 : /**
236 : * Cleanup function for the PostDonauCtx.
237 : *
238 : * @param cls closure with PostDonauCtx
239 : */
240 : static void
241 4 : post_donau_cleanup (void *cls)
242 : {
243 4 : struct PostDonauCtx *pdc = cls;
244 :
245 4 : if (pdc->get_handle)
246 : {
247 0 : DONAU_charity_get_cancel (pdc->get_handle);
248 0 : pdc->get_handle = NULL;
249 : }
250 4 : GNUNET_CONTAINER_DLL_remove (pdc_head,
251 : pdc_tail,
252 : pdc);
253 4 : GNUNET_free (pdc);
254 4 : }
255 :
256 :
257 : /**
258 : * Handle a POST "/donau" request.
259 : *
260 : * @param rh context of the handler
261 : * @param connection the MHD connection to handle
262 : * @param[in,out] hc context with further information about the request
263 : * @return MHD result code
264 : */
265 : enum MHD_Result
266 6 : TMH_private_post_donau_instance (const struct TMH_RequestHandler *rh,
267 : struct MHD_Connection *connection,
268 : struct TMH_HandlerContext *hc)
269 : {
270 6 : struct PostDonauCtx *pdc = hc->ctx;
271 :
272 6 : if (NULL == pdc)
273 : {
274 : enum GNUNET_DB_QueryStatus qs;
275 :
276 4 : pdc = GNUNET_new (struct PostDonauCtx);
277 4 : pdc->connection = connection;
278 4 : pdc->hc = hc;
279 4 : hc->ctx = pdc;
280 4 : hc->cc = &post_donau_cleanup;
281 4 : GNUNET_CONTAINER_DLL_insert (pdc_head,
282 : pdc_tail,
283 : pdc);
284 : {
285 : struct GNUNET_JSON_Specification spec[] = {
286 4 : TALER_JSON_spec_web_url ("donau_url",
287 : &pdc->donau_url),
288 4 : GNUNET_JSON_spec_uint64 ("charity_id",
289 : &pdc->charity_id),
290 4 : GNUNET_JSON_spec_end ()
291 : };
292 :
293 : enum GNUNET_GenericReturnValue res;
294 :
295 4 : res = TALER_MHD_parse_json_data (connection,
296 4 : hc->request_body,
297 : spec);
298 4 : if (GNUNET_OK != res)
299 : {
300 : /* On a client error the parser already queued a 400 and
301 : returns GNUNET_NO; we must return MHD_YES so MHD sends it,
302 : returning MHD_NO only on GNUNET_SYSERR (drop connection). */
303 0 : GNUNET_break_op (0);
304 : return (GNUNET_NO == res)
305 : ? MHD_YES
306 0 : : MHD_NO;
307 : }
308 : }
309 4 : qs = TALER_MERCHANTDB_get_exists_donau_instance (TMH_db,
310 4 : &hc->instance->merchant_pub,
311 : pdc->donau_url,
312 : pdc->charity_id);
313 4 : switch (qs)
314 : {
315 0 : case GNUNET_DB_STATUS_HARD_ERROR:
316 : case GNUNET_DB_STATUS_SOFT_ERROR:
317 0 : GNUNET_break (0);
318 0 : pdc->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
319 0 : return TALER_MHD_reply_with_error (
320 : connection,
321 : MHD_HTTP_INTERNAL_SERVER_ERROR,
322 : TALER_EC_GENERIC_DB_FETCH_FAILED,
323 : "get_exists_donau_instance");
324 : break;
325 2 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
326 2 : pdc->http_status = MHD_HTTP_NO_CONTENT;
327 2 : pdc->response = MHD_create_response_from_buffer_static (0,
328 : NULL);
329 2 : TALER_MHD_add_global_headers (pdc->response,
330 : false);
331 2 : goto respond;
332 2 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
333 : /* normal case, continue below */
334 2 : break;
335 : }
336 :
337 : {
338 : struct DONAU_CharityPrivateKeyP cp;
339 :
340 : /* Merchant private key IS our charity private key */
341 2 : cp.eddsa_priv = hc->instance->merchant_priv.eddsa_priv;
342 2 : pdc->get_handle =
343 2 : DONAU_charity_get (TMH_curl_ctx,
344 : pdc->donau_url,
345 : pdc->charity_id,
346 : &cp,
347 : &donau_charity_get_cb,
348 : pdc);
349 : }
350 2 : if (NULL == pdc->get_handle)
351 : {
352 0 : GNUNET_break (0);
353 0 : return TALER_MHD_reply_with_error (connection,
354 : MHD_HTTP_SERVICE_UNAVAILABLE,
355 : TALER_EC_GENERIC_ALLOCATION_FAILURE,
356 : "Failed to initiate Donau lookup");
357 : }
358 2 : pdc->suspended = GNUNET_YES;
359 2 : MHD_suspend_connection (connection);
360 2 : return MHD_YES;
361 : }
362 2 : respond:
363 4 : if (NULL != pdc->response)
364 : {
365 : enum MHD_Result res;
366 :
367 4 : GNUNET_break (GNUNET_NO == pdc->suspended);
368 4 : res = MHD_queue_response (pdc->connection,
369 : pdc->http_status,
370 : pdc->response);
371 4 : MHD_destroy_response (pdc->response);
372 4 : return res;
373 : }
374 0 : GNUNET_break (GNUNET_SYSERR == pdc->suspended);
375 0 : return MHD_NO;
376 : }
|