exchange_api_reserves_close.c (10473B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2014-2023 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_reserves_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 /reserves/$RID/close Handle 37 */ 38 struct TALER_EXCHANGE_ReservesCloseHandle 39 { 40 41 /** 42 * The url for this request. 43 */ 44 char *url; 45 46 /** 47 * Handle for the request. 48 */ 49 struct GNUNET_CURL_Job *job; 50 51 /** 52 * Context for #TEH_curl_easy_post(). Keeps the data that must 53 * persist for Curl to make the upload. 54 */ 55 struct TALER_CURL_PostContext post_ctx; 56 57 /** 58 * Function to call with the result. 59 */ 60 TALER_EXCHANGE_ReservesCloseCallback cb; 61 62 /** 63 * Closure for @a cb. 64 */ 65 void *cb_cls; 66 67 /** 68 * Public key of the reserve we are querying. 69 */ 70 struct TALER_ReservePublicKeyP reserve_pub; 71 72 /** 73 * Our signature. 74 */ 75 struct TALER_ReserveSignatureP reserve_sig; 76 77 /** 78 * When did we make the request. 79 */ 80 struct GNUNET_TIME_Timestamp ts; 81 82 }; 83 84 85 /** 86 * We received an #MHD_HTTP_OK close code. Handle the JSON 87 * response. 88 * 89 * @param rch handle of the request 90 * @param j JSON response 91 * @return #GNUNET_OK on success 92 */ 93 static enum GNUNET_GenericReturnValue 94 handle_reserves_close_ok (struct TALER_EXCHANGE_ReservesCloseHandle *rch, 95 const json_t *j) 96 { 97 struct TALER_EXCHANGE_ReserveCloseResult rs = { 98 .hr.reply = j, 99 .hr.http_status = MHD_HTTP_OK, 100 }; 101 struct GNUNET_JSON_Specification spec[] = { 102 TALER_JSON_spec_amount_any ("wire_amount", 103 &rs.details.ok.wire_amount), 104 GNUNET_JSON_spec_end () 105 }; 106 107 if (GNUNET_OK != 108 GNUNET_JSON_parse (j, 109 spec, 110 NULL, 111 NULL)) 112 { 113 GNUNET_break_op (0); 114 return GNUNET_SYSERR; 115 } 116 rch->cb (rch->cb_cls, 117 &rs); 118 rch->cb = NULL; 119 GNUNET_JSON_parse_free (spec); 120 return GNUNET_OK; 121 } 122 123 124 /** 125 * We received an #MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS close code. Handle the JSON 126 * response. 127 * 128 * @param rch handle of the request 129 * @param j JSON response 130 * @return #GNUNET_OK on success 131 */ 132 static enum GNUNET_GenericReturnValue 133 handle_reserves_close_kyc (struct TALER_EXCHANGE_ReservesCloseHandle *rch, 134 const json_t *j) 135 { 136 struct TALER_EXCHANGE_ReserveCloseResult rs = { 137 .hr.reply = j, 138 .hr.http_status = MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS, 139 }; 140 struct GNUNET_JSON_Specification spec[] = { 141 GNUNET_JSON_spec_fixed_auto ( 142 "h_payto", 143 &rs.details.unavailable_for_legal_reasons.h_payto), 144 GNUNET_JSON_spec_uint64 ( 145 "requirement_row", 146 &rs.details.unavailable_for_legal_reasons.requirement_row), 147 GNUNET_JSON_spec_end () 148 }; 149 150 if (GNUNET_OK != 151 GNUNET_JSON_parse (j, 152 spec, 153 NULL, 154 NULL)) 155 { 156 GNUNET_break_op (0); 157 return GNUNET_SYSERR; 158 } 159 rch->cb (rch->cb_cls, 160 &rs); 161 rch->cb = NULL; 162 GNUNET_JSON_parse_free (spec); 163 return GNUNET_OK; 164 } 165 166 167 /** 168 * Function called when we're done processing the 169 * HTTP /reserves/$RID/close request. 170 * 171 * @param cls the `struct TALER_EXCHANGE_ReservesCloseHandle` 172 * @param response_code HTTP response code, 0 on error 173 * @param response parsed JSON result, NULL on error 174 */ 175 static void 176 handle_reserves_close_finished (void *cls, 177 long response_code, 178 const void *response) 179 { 180 struct TALER_EXCHANGE_ReservesCloseHandle *rch = cls; 181 const json_t *j = response; 182 struct TALER_EXCHANGE_ReserveCloseResult rs = { 183 .hr.reply = j, 184 .hr.http_status = (unsigned int) response_code 185 }; 186 187 rch->job = NULL; 188 switch (response_code) 189 { 190 case 0: 191 rs.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 192 break; 193 case MHD_HTTP_OK: 194 if (GNUNET_OK != 195 handle_reserves_close_ok (rch, 196 j)) 197 { 198 GNUNET_break_op (0); 199 rs.hr.http_status = 0; 200 rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 201 } 202 break; 203 case MHD_HTTP_BAD_REQUEST: 204 /* This should never happen, either us or the exchange is buggy 205 (or API version conflict); just pass JSON reply to the application */ 206 GNUNET_break (0); 207 rs.hr.ec = TALER_JSON_get_error_code (j); 208 rs.hr.hint = TALER_JSON_get_error_hint (j); 209 break; 210 case MHD_HTTP_FORBIDDEN: 211 /* This should never happen, either us or the exchange is buggy 212 (or API version conflict); just pass JSON reply to the application */ 213 GNUNET_break (0); 214 rs.hr.ec = TALER_JSON_get_error_code (j); 215 rs.hr.hint = TALER_JSON_get_error_hint (j); 216 break; 217 case MHD_HTTP_NOT_FOUND: 218 /* Nothing really to verify, this should never 219 happen, we should pass the JSON reply to the application */ 220 rs.hr.ec = TALER_JSON_get_error_code (j); 221 rs.hr.hint = TALER_JSON_get_error_hint (j); 222 break; 223 case MHD_HTTP_CONFLICT: 224 /* Insufficient balance to inquire for reserve close */ 225 rs.hr.ec = TALER_JSON_get_error_code (j); 226 rs.hr.hint = TALER_JSON_get_error_hint (j); 227 break; 228 case MHD_HTTP_UNAVAILABLE_FOR_LEGAL_REASONS: 229 if (GNUNET_OK != 230 handle_reserves_close_kyc (rch, 231 j)) 232 { 233 GNUNET_break_op (0); 234 rs.hr.http_status = 0; 235 rs.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 236 } 237 break; 238 case MHD_HTTP_INTERNAL_SERVER_ERROR: 239 /* Server had an internal issue; we should retry, but this API 240 leaves this to the application */ 241 rs.hr.ec = TALER_JSON_get_error_code (j); 242 rs.hr.hint = TALER_JSON_get_error_hint (j); 243 break; 244 default: 245 /* unexpected response code */ 246 GNUNET_break_op (0); 247 rs.hr.ec = TALER_JSON_get_error_code (j); 248 rs.hr.hint = TALER_JSON_get_error_hint (j); 249 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 250 "Unexpected response code %u/%d for reserves close\n", 251 (unsigned int) response_code, 252 (int) rs.hr.ec); 253 break; 254 } 255 if (NULL != rch->cb) 256 { 257 rch->cb (rch->cb_cls, 258 &rs); 259 rch->cb = NULL; 260 } 261 TALER_EXCHANGE_reserves_close_cancel (rch); 262 } 263 264 265 struct TALER_EXCHANGE_ReservesCloseHandle * 266 TALER_EXCHANGE_reserves_close ( 267 struct GNUNET_CURL_Context *ctx, 268 const char *url, 269 const struct TALER_ReservePrivateKeyP *reserve_priv, 270 const struct TALER_FullPayto target_payto_uri, 271 TALER_EXCHANGE_ReservesCloseCallback cb, 272 void *cb_cls) 273 { 274 struct TALER_EXCHANGE_ReservesCloseHandle *rch; 275 CURL *eh; 276 char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32]; 277 struct TALER_FullPaytoHashP h_payto; 278 279 rch = GNUNET_new (struct TALER_EXCHANGE_ReservesCloseHandle); 280 rch->cb = cb; 281 rch->cb_cls = cb_cls; 282 rch->ts = GNUNET_TIME_timestamp_get (); 283 GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv->eddsa_priv, 284 &rch->reserve_pub.eddsa_pub); 285 { 286 char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2]; 287 char *end; 288 289 end = GNUNET_STRINGS_data_to_string ( 290 &rch->reserve_pub, 291 sizeof (rch->reserve_pub), 292 pub_str, 293 sizeof (pub_str)); 294 *end = '\0'; 295 GNUNET_snprintf (arg_str, 296 sizeof (arg_str), 297 "reserves/%s/close", 298 pub_str); 299 } 300 rch->url = TALER_url_join (url, 301 arg_str, 302 NULL); 303 if (NULL == rch->url) 304 { 305 GNUNET_free (rch); 306 return NULL; 307 } 308 eh = TALER_EXCHANGE_curl_easy_get_ (rch->url); 309 if (NULL == eh) 310 { 311 GNUNET_break (0); 312 GNUNET_free (rch->url); 313 GNUNET_free (rch); 314 return NULL; 315 } 316 if (NULL != target_payto_uri.full_payto) 317 TALER_full_payto_hash (target_payto_uri, 318 &h_payto); 319 TALER_wallet_reserve_close_sign (rch->ts, 320 (NULL != target_payto_uri.full_payto) 321 ? &h_payto 322 : NULL, 323 reserve_priv, 324 &rch->reserve_sig); 325 { 326 json_t *close_obj = GNUNET_JSON_PACK ( 327 GNUNET_JSON_pack_allow_null ( 328 TALER_JSON_pack_full_payto ("payto_uri", 329 target_payto_uri)), 330 GNUNET_JSON_pack_timestamp ("request_timestamp", 331 rch->ts), 332 GNUNET_JSON_pack_data_auto ("reserve_sig", 333 &rch->reserve_sig)); 334 335 if (GNUNET_OK != 336 TALER_curl_easy_post (&rch->post_ctx, 337 eh, 338 close_obj)) 339 { 340 GNUNET_break (0); 341 curl_easy_cleanup (eh); 342 json_decref (close_obj); 343 GNUNET_free (rch->url); 344 GNUNET_free (rch); 345 return NULL; 346 } 347 json_decref (close_obj); 348 } 349 rch->job = GNUNET_CURL_job_add2 (ctx, 350 eh, 351 rch->post_ctx.headers, 352 &handle_reserves_close_finished, 353 rch); 354 return rch; 355 } 356 357 358 void 359 TALER_EXCHANGE_reserves_close_cancel ( 360 struct TALER_EXCHANGE_ReservesCloseHandle *rch) 361 { 362 if (NULL != rch->job) 363 { 364 GNUNET_CURL_job_cancel (rch->job); 365 rch->job = NULL; 366 } 367 TALER_curl_easy_post_finished (&rch->post_ctx); 368 GNUNET_free (rch->url); 369 GNUNET_free (rch); 370 } 371 372 373 /* end of exchange_api_reserves_close.c */