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