exchange_api_post-reserves-RESERVE_PUB-close.c (12177B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2014-2026 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it under the 6 terms of the GNU 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 15 <http://www.gnu.org/licenses/> 16 */ 17 /** 18 * @file lib/exchange_api_post-reserves-RESERVE_PUB-close.c 19 * @brief Implementation of the POST /reserves/$RESERVE_PUB/close requests 20 * @author Christian Grothoff 21 */ 22 #include "taler/platform.h" 23 #include <jansson.h> 24 #include <microhttpd.h> /* just for HTTP close codes */ 25 #include <gnunet/gnunet_util_lib.h> 26 #include <gnunet/gnunet_json_lib.h> 27 #include <gnunet/gnunet_curl_lib.h> 28 #include "taler/taler_exchange_service.h" 29 #include "taler/taler_json_lib.h" 30 #include "exchange_api_handle.h" 31 #include "taler/taler_signatures.h" 32 #include "exchange_api_curl_defaults.h" 33 34 35 /** 36 * @brief A POST /reserves/$RID/close Handle 37 */ 38 struct TALER_EXCHANGE_PostReservesCloseHandle 39 { 40 41 /** 42 * Reference to the execution context. 43 */ 44 struct GNUNET_CURL_Context *ctx; 45 46 /** 47 * Base URL of the exchange. 48 */ 49 char *base_url; 50 51 /** 52 * The url for this request, set during _start. 53 */ 54 char *url; 55 56 /** 57 * Context for #TEH_curl_easy_post(). Keeps the data that must 58 * persist for Curl to make the upload. 59 */ 60 struct TALER_CURL_PostContext post_ctx; 61 62 /** 63 * Handle for the request. 64 */ 65 struct GNUNET_CURL_Job *job; 66 67 /** 68 * Function to call with the result. 69 */ 70 TALER_EXCHANGE_PostReservesCloseCallback cb; 71 72 /** 73 * Closure for @a cb. 74 */ 75 TALER_EXCHANGE_POST_RESERVES_CLOSE_RESULT_CLOSURE *cb_cls; 76 77 /** 78 * Private key of the reserve. 79 */ 80 struct TALER_ReservePrivateKeyP reserve_priv; 81 82 /** 83 * Public key of the reserve we are querying. 84 */ 85 struct TALER_ReservePublicKeyP reserve_pub; 86 87 /** 88 * Target payto URI to send the reserve balance to (optional). 89 */ 90 struct TALER_FullPayto target_payto_uri; 91 92 /** 93 * When did we make the request. 94 */ 95 struct GNUNET_TIME_Timestamp ts; 96 97 }; 98 99 100 /** 101 * We received an #MHD_HTTP_OK close code. Handle the JSON 102 * response. 103 * 104 * @param prch handle of the request 105 * @param j JSON response 106 * @return #GNUNET_OK on success 107 */ 108 static enum GNUNET_GenericReturnValue 109 handle_reserves_close_ok (struct TALER_EXCHANGE_PostReservesCloseHandle *prch, 110 const json_t *j) 111 { 112 struct TALER_EXCHANGE_PostReservesCloseResponse rs = { 113 .hr.reply = j, 114 .hr.http_status = MHD_HTTP_OK, 115 }; 116 struct GNUNET_JSON_Specification spec[] = { 117 TALER_JSON_spec_amount_any ("wire_amount", 118 &rs.details.ok.wire_amount), 119 GNUNET_JSON_spec_end () 120 }; 121 122 if (GNUNET_OK != 123 GNUNET_JSON_parse (j, 124 spec, 125 NULL, 126 NULL)) 127 { 128 GNUNET_break_op (0); 129 return GNUNET_SYSERR; 130 } 131 prch->cb (prch->cb_cls, 132 &rs); 133 prch->cb = NULL; 134 GNUNET_JSON_parse_free (spec); 135 return GNUNET_OK; 136 } 137 138 139 /** 140 * We received an #MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS close code. Handle the JSON 141 * response. 142 * 143 * @param prch handle of the request 144 * @param j JSON response 145 * @return #GNUNET_OK on success 146 */ 147 static enum GNUNET_GenericReturnValue 148 handle_reserves_close_kyc (struct TALER_EXCHANGE_PostReservesCloseHandle *prch, 149 const json_t *j) 150 { 151 struct TALER_EXCHANGE_PostReservesCloseResponse rs = { 152 .hr.reply = j, 153 .hr.http_status = MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS, 154 }; 155 struct GNUNET_JSON_Specification spec[] = { 156 GNUNET_JSON_spec_fixed_auto ( 157 "h_payto", 158 &rs.details.unavailable_for_legal_reasons.h_payto), 159 GNUNET_JSON_spec_uint64 ( 160 "requirement_row", 161 &rs.details.unavailable_for_legal_reasons.requirement_row), 162 GNUNET_JSON_spec_end () 163 }; 164 165 if (GNUNET_OK != 166 GNUNET_JSON_parse (j, 167 spec, 168 NULL, 169 NULL)) 170 { 171 GNUNET_break_op (0); 172 return GNUNET_SYSERR; 173 } 174 prch->cb (prch->cb_cls, 175 &rs); 176 prch->cb = NULL; 177 GNUNET_JSON_parse_free (spec); 178 return GNUNET_OK; 179 } 180 181 182 /** 183 * Function called when we're done processing the 184 * HTTP /reserves/$RID/close request. 185 * 186 * @param cls the `struct TALER_EXCHANGE_PostReservesCloseHandle` 187 * @param response_code HTTP response code, 0 on error 188 * @param response parsed JSON result, NULL on error 189 */ 190 static void 191 handle_reserves_close_finished (void *cls, 192 long response_code, 193 const void *response) 194 { 195 struct TALER_EXCHANGE_PostReservesCloseHandle *prch = cls; 196 const json_t *j = response; 197 struct TALER_EXCHANGE_PostReservesCloseResponse rs = { 198 .hr.reply = j, 199 .hr.http_status = (unsigned int) response_code 200 }; 201 202 prch->job = NULL; 203 switch (response_code) 204 { 205 case 0: 206 rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 207 break; 208 case MHD_HTTP_OK: 209 if (GNUNET_OK != 210 handle_reserves_close_ok (prch, 211 j)) 212 { 213 GNUNET_break_op (0); 214 rs.hr.http_status = 0; 215 rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 216 } 217 break; 218 case MHD_HTTP_BAD_REQUEST: 219 /* This should never happen, either us or the exchange is buggy 220 (or API version conflict); just pass JSON reply to the application */ 221 GNUNET_break (0); 222 rs.hr.ec = TALER_JSON_get_error_code (j); 223 rs.hr.hint = TALER_JSON_get_error_hint (j); 224 break; 225 case MHD_HTTP_FORBIDDEN: 226 /* This should never happen, either us or the exchange is buggy 227 (or API version conflict); just pass JSON reply to the application */ 228 GNUNET_break (0); 229 rs.hr.ec = TALER_JSON_get_error_code (j); 230 rs.hr.hint = TALER_JSON_get_error_hint (j); 231 break; 232 case MHD_HTTP_NOT_FOUND: 233 /* Nothing really to verify, this should never 234 happen, we should pass the JSON reply to the application */ 235 rs.hr.ec = TALER_JSON_get_error_code (j); 236 rs.hr.hint = TALER_JSON_get_error_hint (j); 237 break; 238 case MHD_HTTP_CONFLICT: 239 /* Insufficient balance to inquire for reserve close */ 240 rs.hr.ec = TALER_JSON_get_error_code (j); 241 rs.hr.hint = TALER_JSON_get_error_hint (j); 242 break; 243 case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: 244 if (GNUNET_OK != 245 handle_reserves_close_kyc (prch, 246 j)) 247 { 248 GNUNET_break_op (0); 249 rs.hr.http_status = 0; 250 rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 251 } 252 break; 253 case MHD_HTTP_INTERNAL_SERVER_ERROR: 254 /* Server had an internal issue; we should retry, but this API 255 leaves this to the application */ 256 rs.hr.ec = TALER_JSON_get_error_code (j); 257 rs.hr.hint = TALER_JSON_get_error_hint (j); 258 break; 259 default: 260 /* unexpected response code */ 261 GNUNET_break_op (0); 262 rs.hr.ec = TALER_JSON_get_error_code (j); 263 rs.hr.hint = TALER_JSON_get_error_hint (j); 264 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 265 "Unexpected response code %u/%d for reserves close\n", 266 (unsigned int) response_code, 267 (int) rs.hr.ec); 268 break; 269 } 270 if (NULL != prch->cb) 271 { 272 prch->cb (prch->cb_cls, 273 &rs); 274 prch->cb = NULL; 275 } 276 TALER_EXCHANGE_post_reserves_close_cancel (prch); 277 } 278 279 280 struct TALER_EXCHANGE_PostReservesCloseHandle * 281 TALER_EXCHANGE_post_reserves_close_create ( 282 struct GNUNET_CURL_Context *ctx, 283 const char *url, 284 const struct TALER_ReservePrivateKeyP *reserve_priv) 285 { 286 struct TALER_EXCHANGE_PostReservesCloseHandle *prch; 287 288 prch = GNUNET_new (struct TALER_EXCHANGE_PostReservesCloseHandle); 289 prch->ctx = ctx; 290 prch->base_url = GNUNET_strdup (url); 291 prch->reserve_priv = *reserve_priv; 292 GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv, 293 &prch->reserve_pub.eddsa_pub); 294 prch->target_payto_uri.full_payto = NULL; 295 return prch; 296 } 297 298 299 enum GNUNET_GenericReturnValue 300 TALER_EXCHANGE_post_reserves_close_set_options_ ( 301 struct TALER_EXCHANGE_PostReservesCloseHandle *prch, 302 unsigned int num_options, 303 const struct TALER_EXCHANGE_PostReservesCloseOptionValue options[]) 304 { 305 for (unsigned int i = 0; i < num_options; i++) 306 { 307 const struct TALER_EXCHANGE_PostReservesCloseOptionValue *opt = &options[i]; 308 309 switch (opt->option) 310 { 311 case TALER_EXCHANGE_POST_RESERVES_CLOSE_OPTION_END: 312 return GNUNET_OK; 313 case TALER_EXCHANGE_POST_RESERVES_CLOSE_OPTION_PAYTO_URI: 314 GNUNET_free (prch->target_payto_uri.full_payto); 315 prch->target_payto_uri.full_payto 316 = GNUNET_strdup (opt->details.payto_uri.full_payto); 317 break; 318 } 319 } 320 return GNUNET_OK; 321 } 322 323 324 enum TALER_ErrorCode 325 TALER_EXCHANGE_post_reserves_close_start ( 326 struct TALER_EXCHANGE_PostReservesCloseHandle *prch, 327 TALER_EXCHANGE_PostReservesCloseCallback cb, 328 TALER_EXCHANGE_POST_RESERVES_CLOSE_RESULT_CLOSURE *cb_cls) 329 { 330 CURL *eh; 331 char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32]; 332 struct TALER_ReserveSignatureP reserve_sig; 333 struct TALER_FullPaytoHashP h_payto; 334 json_t *close_obj; 335 336 prch->cb = cb; 337 prch->cb_cls = cb_cls; 338 prch->ts = GNUNET_TIME_timestamp_get (); 339 { 340 char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2]; 341 char *end; 342 343 end = GNUNET_STRINGS_data_to_string ( 344 &prch->reserve_pub, 345 sizeof (prch->reserve_pub), 346 pub_str, 347 sizeof (pub_str)); 348 *end = '\0'; 349 GNUNET_snprintf (arg_str, 350 sizeof (arg_str), 351 "reserves/%s/close", 352 pub_str); 353 } 354 prch->url = TALER_url_join (prch->base_url, 355 arg_str, 356 NULL); 357 if (NULL == prch->url) 358 return TALER_EC_GENERIC_CONFIGURATION_INVALID; 359 if (NULL != prch->target_payto_uri.full_payto) 360 TALER_full_payto_hash (prch->target_payto_uri, 361 &h_payto); 362 TALER_wallet_reserve_close_sign (prch->ts, 363 (NULL != prch->target_payto_uri.full_payto) 364 ? &h_payto 365 : NULL, 366 &prch->reserve_priv, 367 &reserve_sig); 368 close_obj = GNUNET_JSON_PACK ( 369 GNUNET_JSON_pack_allow_null ( 370 TALER_JSON_pack_full_payto ("payto_uri", 371 prch->target_payto_uri)), 372 GNUNET_JSON_pack_timestamp ("request_timestamp", 373 prch->ts), 374 GNUNET_JSON_pack_data_auto ("reserve_sig", 375 &reserve_sig)); 376 eh = TALER_EXCHANGE_curl_easy_get_ (prch->url); 377 if ( (NULL == eh) || 378 (GNUNET_OK != 379 TALER_curl_easy_post (&prch->post_ctx, 380 eh, 381 close_obj)) ) 382 { 383 GNUNET_break (0); 384 if (NULL != eh) 385 curl_easy_cleanup (eh); 386 json_decref (close_obj); 387 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 388 } 389 json_decref (close_obj); 390 prch->job = GNUNET_CURL_job_add2 (prch->ctx, 391 eh, 392 prch->post_ctx.headers, 393 &handle_reserves_close_finished, 394 prch); 395 if (NULL == prch->job) 396 return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE; 397 return TALER_EC_NONE; 398 } 399 400 401 void 402 TALER_EXCHANGE_post_reserves_close_cancel ( 403 struct TALER_EXCHANGE_PostReservesCloseHandle *prch) 404 { 405 if (NULL != prch->job) 406 { 407 GNUNET_CURL_job_cancel (prch->job); 408 prch->job = NULL; 409 } 410 TALER_curl_easy_post_finished (&prch->post_ctx); 411 GNUNET_free (prch->url); 412 GNUNET_free (prch->base_url); 413 GNUNET_free (prch->target_payto_uri.full_payto); 414 GNUNET_free (prch); 415 } 416 417 418 /* end of exchange_api_post-reserves-RESERVE_PUB-close.c */