anastasis_api_truth_challenge.c (13087B)
1 /* 2 This file is part of Anastasis 3 Copyright (C) 2020, 2021, 2022 Anastasis SARL 4 5 Anastasis 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 Anastasis 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 Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/> 15 */ 16 /** 17 * @file restclient/anastasis_api_truth_challenge.c 18 * @brief Implementation of the POST /truth/$TID/challenge request on the client-side 19 * @author Christian Grothoff 20 * @author Dennis Neufeld 21 * @author Dominik Meister 22 */ 23 #include "platform.h" 24 #include <curl/curl.h> 25 #include <jansson.h> 26 #include <microhttpd.h> /* just for HTTP status codes */ 27 #include "anastasis_service.h" 28 #include "anastasis_api_curl_defaults.h" 29 #include <taler/taler_curl_lib.h> 30 #include <taler/taler_json_lib.h> 31 #include <taler/taler_merchant_service.h> 32 33 34 /** 35 * @brief A Contract Operation Handle 36 */ 37 struct ANASTASIS_TruthChallengeOperation 38 { 39 /** 40 * The url for this request. 41 */ 42 char *url; 43 44 /** 45 * Handle for the request. 46 */ 47 struct GNUNET_CURL_Job *job; 48 49 /** 50 * Function to call with the result. 51 */ 52 ANASTASIS_TruthChallengeCallback cb; 53 54 /** 55 * Closure for @a cb. 56 */ 57 void *cb_cls; 58 59 /** 60 * Context for #TEH_curl_easy_post(). Keeps the data that must 61 * persist for Curl to make the upload. 62 */ 63 struct TALER_CURL_PostContext ctx; 64 65 /** 66 * Payment URI we received from the service, or NULL. 67 */ 68 char *pay_uri; 69 70 /** 71 * Content type of the body. 72 */ 73 char *content_type; 74 }; 75 76 77 void 78 ANASTASIS_truth_challenge_cancel ( 79 struct ANASTASIS_TruthChallengeOperation *tco) 80 { 81 if (NULL != tco->job) 82 { 83 GNUNET_CURL_job_cancel (tco->job); 84 tco->job = NULL; 85 } 86 GNUNET_free (tco->pay_uri); 87 GNUNET_free (tco->url); 88 GNUNET_free (tco->content_type); 89 TALER_curl_easy_post_finished (&tco->ctx); 90 GNUNET_free (tco); 91 } 92 93 94 /** 95 * Process POST /truth/$TID/challenge response 96 * 97 * @param cls our `struct ANASTASIS_TruthChallengeOperation *` 98 * @param response_code the HTTP status 99 * @param response parsed JSON result, NULL one rrro 100 */ 101 static void 102 handle_truth_challenge_finished (void *cls, 103 long response_code, 104 const void *response) 105 { 106 struct ANASTASIS_TruthChallengeOperation *tco = cls; 107 const json_t *j = response; 108 struct ANASTASIS_TruthChallengeDetails tcd = { 109 .http_status = response_code, 110 .response = j 111 }; 112 113 tco->job = NULL; 114 switch (response_code) 115 { 116 case 0: 117 /* Hard error */ 118 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 119 "Backend didn't even return from POST /truth/$TID/challenge\n"); 120 tcd.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 121 break; 122 case MHD_HTTP_OK: 123 { 124 const char *ct; 125 const char *tan_hint = NULL; 126 const char *filename = NULL; 127 const json_t *wire_details = NULL; 128 struct GNUNET_JSON_Specification spec[] = { 129 GNUNET_JSON_spec_string ( 130 "challenge_type", 131 &ct), 132 GNUNET_JSON_spec_mark_optional ( 133 GNUNET_JSON_spec_string ("tan_address_hint", 134 &tan_hint), 135 NULL), 136 GNUNET_JSON_spec_mark_optional ( 137 GNUNET_JSON_spec_string ("filename", 138 &filename), 139 NULL), 140 GNUNET_JSON_spec_mark_optional ( 141 GNUNET_JSON_spec_object_const ("wire_details", 142 &wire_details), 143 NULL), 144 GNUNET_JSON_spec_end () 145 }; 146 147 if (GNUNET_OK != 148 GNUNET_JSON_parse (j, 149 spec, 150 NULL, NULL)) 151 { 152 GNUNET_break_op (0); 153 tcd.http_status = 0; 154 tcd.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 155 break; 156 } 157 if (0 == strcmp (ct, 158 "TAN_SENT")) 159 { 160 tcd.details.success.cs = ANASTASIS_CS_TAN_SENT; 161 tcd.details.success.details.tan_address_hint = tan_hint; 162 break; 163 } 164 if (0 == strcmp (ct, 165 "TAN_ALREADY_SENT")) 166 { 167 tcd.details.success.cs = ANASTASIS_CS_TAN_ALREADY_SENT; 168 break; 169 } 170 if ( (0 == strcmp (ct, 171 "FILE_WRITTEN")) && 172 (NULL != filename) ) 173 { 174 tcd.details.success.cs = ANASTASIS_CS_FILE_WRITTEN; 175 tcd.details.success.details.challenge_filename = filename; 176 break; 177 } 178 if ( (0 == strcmp (ct, 179 "IBAN_WIRE")) && 180 (NULL != wire_details) ) 181 { 182 struct GNUNET_JSON_Specification ispec[] = { 183 GNUNET_JSON_spec_string ( 184 "credit_iban", 185 &tcd.details.success.details.wire_funds.target_iban), 186 GNUNET_JSON_spec_uint64 ( 187 "answer_code", 188 &tcd.details.success.details.wire_funds.answer_code), 189 GNUNET_JSON_spec_string ( 190 "business_name", 191 &tcd.details.success.details.wire_funds.target_business_name), 192 GNUNET_JSON_spec_string ( 193 "wire_transfer_subject", 194 &tcd.details.success.details.wire_funds.wire_transfer_subject), 195 TALER_JSON_spec_amount_any ("challenge_amount", 196 &tcd.details.success.details.wire_funds. 197 amount), 198 GNUNET_JSON_spec_end () 199 }; 200 201 if (GNUNET_OK != 202 GNUNET_JSON_parse (wire_details, 203 ispec, 204 NULL, NULL)) 205 { 206 GNUNET_break_op (0); 207 tcd.http_status = 0; 208 tcd.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 209 break; 210 } 211 tcd.details.success.cs = ANASTASIS_CS_WIRE_FUNDS; 212 tco->cb (tco->cb_cls, 213 &tcd); 214 ANASTASIS_truth_challenge_cancel (tco); 215 return; 216 } 217 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 218 "Unexpected challenge type `%s'\n", 219 ct); 220 json_dumpf (j, 221 stderr, 222 JSON_INDENT (2)); 223 tcd.http_status = 0; 224 tcd.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 225 break; 226 } 227 case MHD_HTTP_BAD_REQUEST: 228 /* This should never happen, either us or the anastasis server is buggy 229 (or API version conflict); just pass JSON reply to the application */ 230 GNUNET_break (0); 231 tcd.ec = TALER_JSON_get_error_code (j); 232 break; 233 case MHD_HTTP_PAYMENT_REQUIRED: 234 { 235 struct TALER_MERCHANT_PayUriData pd; 236 237 if ( (NULL == tco->pay_uri) || 238 (GNUNET_OK != 239 TALER_MERCHANT_parse_pay_uri (tco->pay_uri, 240 &pd)) ) 241 { 242 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 243 "Failed to parse `%s'\n", 244 tco->pay_uri); 245 tcd.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 246 break; 247 } 248 if (GNUNET_OK != 249 GNUNET_STRINGS_string_to_data ( 250 pd.order_id, 251 strlen (pd.order_id), 252 &tcd.details.payment_required.ps, 253 sizeof (tcd.details.payment_required.ps))) 254 { 255 GNUNET_break (0); 256 tcd.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 257 TALER_MERCHANT_parse_pay_uri_free (&pd); 258 break; 259 } 260 tcd.details.payment_required.pd = &pd; 261 tcd.details.payment_required.payment_request = tco->pay_uri; 262 tco->cb (tco->cb_cls, 263 &tcd); 264 TALER_MERCHANT_parse_pay_uri_free (&pd); 265 ANASTASIS_truth_challenge_cancel (tco); 266 return; 267 } 268 break; 269 case MHD_HTTP_FORBIDDEN: 270 /* Nothing really to verify, authentication required/failed */ 271 tcd.ec = TALER_JSON_get_error_code (j); 272 break; 273 case MHD_HTTP_NOT_FOUND: 274 /* Nothing really to verify */ 275 tcd.ec = TALER_JSON_get_error_code (j); 276 break; 277 case MHD_HTTP_INTERNAL_SERVER_ERROR: 278 tcd.ec = TALER_JSON_get_error_code (j); 279 break; 280 case MHD_HTTP_BAD_GATEWAY: 281 tcd.ec = TALER_JSON_get_error_code (j); 282 break; 283 default: 284 /* unexpected response code */ 285 tcd.ec = TALER_JSON_get_error_code (j); 286 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 287 "Unexpected response code %u/%d to POST /truth/$TID/challenge\n", 288 (unsigned int) response_code, 289 (int) tcd.ec); 290 GNUNET_break (0); 291 break; 292 } 293 tco->cb (tco->cb_cls, 294 &tcd); 295 ANASTASIS_truth_challenge_cancel (tco); 296 } 297 298 299 /** 300 * Patch value in @a val, replacing new line with '\0'. 301 * 302 * @param[in,out] val 0-terminated string to replace '\\n' and '\\r' with '\\0' in. 303 */ 304 static void 305 patch_value (char *val) 306 { 307 size_t len; 308 309 /* found location URI we care about! */ 310 len = strlen (val); 311 while ( (len > 0) && 312 ( ('\n' == val[len - 1]) || 313 ('\r' == val[len - 1]) ) ) 314 { 315 len--; 316 val[len] = '\0'; 317 } 318 } 319 320 321 /** 322 * Handle HTTP header received by curl. 323 * 324 * @param buffer one line of HTTP header data 325 * @param size size of an item 326 * @param nitems number of items passed 327 * @param userdata our `struct ANASTASIS_StorePolicyOperation *` 328 * @return `size * nitems` 329 */ 330 static size_t 331 handle_header (char *buffer, 332 size_t size, 333 size_t nitems, 334 void *userdata) 335 { 336 struct ANASTASIS_TruthChallengeOperation *tco = userdata; 337 size_t total = size * nitems; 338 char *ndup; 339 const char *hdr_type; 340 char *hdr_val; 341 char *sp; 342 343 ndup = GNUNET_strndup (buffer, 344 total); 345 hdr_type = strtok_r (ndup, 346 ":", 347 &sp); 348 if (NULL == hdr_type) 349 { 350 GNUNET_free (ndup); 351 return total; 352 } 353 hdr_val = strtok_r (NULL, 354 "", 355 &sp); 356 if (NULL == hdr_val) 357 { 358 GNUNET_free (ndup); 359 return total; 360 } 361 if (' ' == *hdr_val) 362 hdr_val++; 363 if (0 == strcasecmp (hdr_type, 364 ANASTASIS_HTTP_HEADER_TALER)) 365 { 366 /* found payment URI we care about! */ 367 GNUNET_free (tco->pay_uri); 368 tco->pay_uri = GNUNET_strdup (hdr_val); 369 patch_value (tco->pay_uri); 370 } 371 if (0 == strcasecmp (hdr_type, 372 MHD_HTTP_HEADER_CONTENT_TYPE)) 373 { 374 /* found location URI we care about! */ 375 GNUNET_free (tco->content_type); 376 tco->content_type = GNUNET_strdup (hdr_val); 377 patch_value (tco->content_type); 378 } 379 GNUNET_free (ndup); 380 return total; 381 } 382 383 384 struct ANASTASIS_TruthChallengeOperation * 385 ANASTASIS_truth_challenge ( 386 struct GNUNET_CURL_Context *ctx, 387 const char *backend_url, 388 const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid, 389 const struct ANASTASIS_CRYPTO_TruthKeyP *truth_key, 390 const struct ANASTASIS_PaymentSecretP *payment_secret, 391 ANASTASIS_TruthChallengeCallback cb, 392 void *cb_cls) 393 { 394 struct ANASTASIS_TruthChallengeOperation *tco; 395 CURL *eh; 396 json_t *body; 397 398 body = GNUNET_JSON_PACK ( 399 GNUNET_JSON_pack_data_auto ("truth_decryption_key", 400 truth_key), 401 GNUNET_JSON_pack_allow_null ( 402 GNUNET_JSON_pack_data_auto ("payment_secret", 403 payment_secret))); 404 GNUNET_assert (NULL != body); 405 tco = GNUNET_new (struct ANASTASIS_TruthChallengeOperation); 406 tco->cb = cb; 407 tco->cb_cls = cb_cls; 408 { 409 char *path; 410 char *uuid_str; 411 412 uuid_str = GNUNET_STRINGS_data_to_string_alloc (truth_uuid, 413 sizeof (*truth_uuid)); 414 GNUNET_asprintf (&path, 415 "truth/%s/challenge", 416 uuid_str); 417 GNUNET_free (uuid_str); 418 tco->url = TALER_url_join (backend_url, 419 path, 420 NULL); 421 GNUNET_free (path); 422 } 423 eh = ANASTASIS_curl_easy_get_ (tco->url); 424 if ( (NULL == eh) || 425 (GNUNET_OK != 426 TALER_curl_easy_post (&tco->ctx, 427 eh, 428 body)) ) 429 { 430 GNUNET_break (0); 431 if (NULL != eh) 432 curl_easy_cleanup (eh); 433 json_decref (body); 434 GNUNET_free (tco->url); 435 GNUNET_free (tco); 436 return NULL; 437 } 438 json_decref (body); 439 GNUNET_assert (CURLE_OK == 440 curl_easy_setopt (eh, 441 CURLOPT_HEADERFUNCTION, 442 &handle_header)); 443 GNUNET_assert (CURLE_OK == 444 curl_easy_setopt (eh, 445 CURLOPT_HEADERDATA, 446 tco)); 447 tco->job = GNUNET_CURL_job_add2 (ctx, 448 eh, 449 tco->ctx.headers, 450 &handle_truth_challenge_finished, 451 tco); 452 return tco; 453 } 454 455 456 /* end of anastasis_api_truth_challenge.c */