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