taler-merchant-httpd_post-private-donau.c (10674B)
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/check_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 TMH_force_post_donau_resume () 111 { 112 for (struct PostDonauCtx *pdc = pdc_head; 113 NULL != pdc; 114 pdc = pdc->next) 115 { 116 if (GNUNET_YES == pdc->suspended) 117 { 118 pdc->suspended = GNUNET_SYSERR; 119 MHD_resume_connection (pdc->connection); 120 } 121 } 122 } 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 donau_charity_get_cb (void *cls, 133 const struct DONAU_GetCharityResponse *gcr) 134 { 135 struct PostDonauCtx *pdc = cls; 136 enum GNUNET_DB_QueryStatus qs; 137 138 pdc->get_handle = NULL; 139 pdc->suspended = GNUNET_NO; 140 MHD_resume_connection (pdc->connection); 141 TALER_MHD_daemon_trigger (); 142 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 143 "Processing DONAU charity get response"); 144 /* Anything but 200 => propagate Donau’s response. */ 145 if (MHD_HTTP_OK != gcr->hr.http_status) 146 { 147 pdc->http_status = MHD_HTTP_BAD_GATEWAY; 148 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 return; 153 } 154 155 if (0 != 156 GNUNET_memcmp (&gcr->details.ok.charity.charity_pub.eddsa_pub, 157 &pdc->hc->instance->merchant_pub.eddsa_pub)) 158 { 159 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 160 "Charity key at donau does not match our merchant key\n"); 161 pdc->http_status = MHD_HTTP_CONFLICT; 162 pdc->response = TALER_MHD_make_error ( 163 TALER_EC_GENERIC_PARAMETER_MALFORMED, 164 "charity_pub != merchant_pub"); 165 return; 166 } 167 168 qs = TALER_MERCHANTDB_set_instance ( 169 TMH_db, 170 pdc->hc->instance->settings.id); 171 if (0 >= qs) 172 { 173 GNUNET_break (0); 174 pdc->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR; 175 pdc->response = TALER_MHD_make_error ( 176 TALER_EC_GENERIC_DB_STORE_FAILED, 177 "set_instance"); 178 return; 179 } 180 181 qs = TALER_MERCHANTDB_insert_donau_instance (TMH_db, 182 pdc->donau_url, 183 &gcr->details.ok.charity, 184 pdc->charity_id); 185 GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 186 TALER_MERCHANTDB_set_instance ( 187 TMH_db, 188 NULL)); 189 switch (qs) 190 { 191 case GNUNET_DB_STATUS_HARD_ERROR: 192 GNUNET_break (0); 193 pdc->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR; 194 pdc->response = TALER_MHD_make_error ( 195 TALER_EC_GENERIC_DB_STORE_FAILED, 196 "insert_donau_instance"); 197 break; 198 case GNUNET_DB_STATUS_SOFT_ERROR: 199 GNUNET_break (0); 200 pdc->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR; 201 pdc->response = TALER_MHD_make_error ( 202 TALER_EC_GENERIC_DB_STORE_FAILED, 203 "insert_donau_instance"); 204 break; 205 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 206 /* presumably idempotent + concurrent, no need to notify, but still respond */ 207 pdc->http_status = MHD_HTTP_NO_CONTENT; 208 pdc->response = MHD_create_response_from_buffer_static (0, 209 NULL); 210 TALER_MHD_add_global_headers (pdc->response, 211 false); 212 break; 213 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 214 { 215 struct GNUNET_DB_EventHeaderP es = { 216 .size = htons (sizeof (es)), 217 .type = htons (TALER_DBEVENT_MERCHANT_DONAU_KEYS) 218 }; 219 220 TALER_MERCHANTDB_event_notify (TMH_db, 221 &es, 222 pdc->donau_url, 223 strlen (pdc->donau_url) + 1); 224 pdc->http_status = MHD_HTTP_NO_CONTENT; 225 pdc->response = MHD_create_response_from_buffer_static (0, 226 NULL); 227 TALER_MHD_add_global_headers (pdc->response, 228 false); 229 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 post_donau_cleanup (void *cls) 242 { 243 struct PostDonauCtx *pdc = cls; 244 245 if (pdc->get_handle) 246 { 247 DONAU_charity_get_cancel (pdc->get_handle); 248 pdc->get_handle = NULL; 249 } 250 GNUNET_CONTAINER_DLL_remove (pdc_head, 251 pdc_tail, 252 pdc); 253 GNUNET_free (pdc); 254 } 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 TMH_private_post_donau_instance (const struct TMH_RequestHandler *rh, 267 struct MHD_Connection *connection, 268 struct TMH_HandlerContext *hc) 269 { 270 struct PostDonauCtx *pdc = hc->ctx; 271 272 if (NULL == pdc) 273 { 274 enum GNUNET_DB_QueryStatus qs; 275 276 pdc = GNUNET_new (struct PostDonauCtx); 277 pdc->connection = connection; 278 pdc->hc = hc; 279 hc->ctx = pdc; 280 hc->cc = &post_donau_cleanup; 281 GNUNET_CONTAINER_DLL_insert (pdc_head, 282 pdc_tail, 283 pdc); 284 { 285 struct GNUNET_JSON_Specification spec[] = { 286 GNUNET_JSON_spec_string ("donau_url", 287 &pdc->donau_url), 288 GNUNET_JSON_spec_uint64 ("charity_id", 289 &pdc->charity_id), 290 GNUNET_JSON_spec_end () 291 }; 292 293 if (GNUNET_OK != 294 TALER_MHD_parse_json_data (connection, 295 hc->request_body, 296 spec)) 297 { 298 GNUNET_break_op (0); 299 return MHD_NO; 300 } 301 } 302 qs = TALER_MERCHANTDB_check_donau_instance (TMH_db, 303 &hc->instance->merchant_pub, 304 pdc->donau_url, 305 pdc->charity_id); 306 switch (qs) 307 { 308 case GNUNET_DB_STATUS_HARD_ERROR: 309 case GNUNET_DB_STATUS_SOFT_ERROR: 310 GNUNET_break (0); 311 pdc->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR; 312 return TALER_MHD_reply_with_error ( 313 connection, 314 MHD_HTTP_INTERNAL_SERVER_ERROR, 315 TALER_EC_GENERIC_DB_FETCH_FAILED, 316 "check_donau_instance"); 317 break; 318 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 319 pdc->http_status = MHD_HTTP_NO_CONTENT; 320 pdc->response = MHD_create_response_from_buffer_static (0, 321 NULL); 322 TALER_MHD_add_global_headers (pdc->response, 323 false); 324 goto respond; 325 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 326 /* normal case, continue below */ 327 break; 328 } 329 330 { 331 struct DONAU_CharityPrivateKeyP cp; 332 333 /* Merchant private key IS our charity private key */ 334 cp.eddsa_priv = hc->instance->merchant_priv.eddsa_priv; 335 pdc->get_handle = 336 DONAU_charity_get (TMH_curl_ctx, 337 pdc->donau_url, 338 pdc->charity_id, 339 &cp, 340 &donau_charity_get_cb, 341 pdc); 342 } 343 if (NULL == pdc->get_handle) 344 { 345 GNUNET_break (0); 346 GNUNET_free (pdc); 347 return TALER_MHD_reply_with_error (connection, 348 MHD_HTTP_SERVICE_UNAVAILABLE, 349 TALER_EC_GENERIC_ALLOCATION_FAILURE, 350 "Failed to initiate Donau lookup"); 351 } 352 pdc->suspended = GNUNET_YES; 353 MHD_suspend_connection (connection); 354 return MHD_YES; 355 } 356 respond: 357 if (NULL != pdc->response) 358 { 359 enum MHD_Result res; 360 361 GNUNET_break (GNUNET_NO == pdc->suspended); 362 res = MHD_queue_response (pdc->connection, 363 pdc->http_status, 364 pdc->response); 365 MHD_destroy_response (pdc->response); 366 return res; 367 } 368 GNUNET_break (GNUNET_SYSERR == pdc->suspended); 369 return MHD_NO; 370 }