anastasis_api_policy_store.c (16542B)
1 /* 2 This file is part of ANASTASIS 3 Copyright (C) 2014-2022 Anastasis SARL 4 5 ANASTASIS is free software; you can redistribute it and/or modify 6 it under the terms of the GNU General Public License as 7 published by the Free Software Foundation; either version 2.1, 8 or (at your option) any later version. 9 10 ANASTASIS is distributed in the hope that it will be useful, but 11 WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public 16 License along with ANASTASIS; see the file COPYING.LGPL. If not, 17 see <http://www.gnu.org/licenses/> 18 */ 19 20 /** 21 * @file restclient/anastasis_api_policy_store.c 22 * @brief Implementation of the /policy GET and POST 23 * @author Christian Grothoff 24 * @author Dennis Neufeld 25 * @author Dominik Meister 26 */ 27 #include "platform.h" 28 #include <curl/curl.h> 29 #include <microhttpd.h> /* just for HTTP status codes */ 30 #include "anastasis_service.h" 31 #include "anastasis_api_curl_defaults.h" 32 #include <taler/taler_signatures.h> 33 #include <taler/taler_merchant_service.h> 34 #include <taler/taler_json_lib.h> 35 36 37 struct ANASTASIS_PolicyStoreOperation 38 { 39 /** 40 * Complete URL where the backend offers /policy 41 */ 42 char *url; 43 44 /** 45 * Handle for the request. 46 */ 47 struct GNUNET_CURL_Job *job; 48 49 /** 50 * The CURL context to connect to the backend 51 */ 52 struct GNUNET_CURL_Context *ctx; 53 54 /** 55 * The callback to pass the backend response to 56 */ 57 ANASTASIS_PolicyStoreCallback cb; 58 59 /** 60 * Closure for @e cb. 61 */ 62 void *cb_cls; 63 64 /** 65 * Payment URI we received from the service, or NULL. 66 */ 67 char *pay_uri; 68 69 /** 70 * Policy version we received from the service, or NULL. 71 */ 72 char *policy_version; 73 74 /** 75 * Policy expiration we received from the service, or NULL. 76 */ 77 char *policy_expiration; 78 79 /** 80 * Copy of the uploaded data. Needed by curl. 81 */ 82 void *postcopy; 83 84 /** 85 * Hash of the data we are uploading. 86 */ 87 struct GNUNET_HashCode new_upload_hash; 88 }; 89 90 91 void 92 ANASTASIS_policy_store_cancel ( 93 struct ANASTASIS_PolicyStoreOperation *pso) 94 { 95 if (NULL != pso->job) 96 { 97 GNUNET_CURL_job_cancel (pso->job); 98 pso->job = NULL; 99 } 100 GNUNET_free (pso->policy_version); 101 GNUNET_free (pso->policy_expiration); 102 GNUNET_free (pso->pay_uri); 103 GNUNET_free (pso->url); 104 GNUNET_free (pso->postcopy); 105 GNUNET_free (pso); 106 } 107 108 109 /** 110 * Callback to process POST /policy response 111 * 112 * @param cls the `struct ANASTASIS_PolicyStoreOperation` 113 * @param response_code HTTP response code, 0 on error 114 * @param data response body 115 * @param data_size number of bytes in @a data 116 */ 117 static void 118 handle_policy_store_finished (void *cls, 119 long response_code, 120 const void *data, 121 size_t data_size) 122 { 123 struct ANASTASIS_PolicyStoreOperation *pso = cls; 124 struct ANASTASIS_UploadDetails ud; 125 126 pso->job = NULL; 127 memset (&ud, 0, sizeof (ud)); 128 ud.http_status = response_code; 129 ud.ec = TALER_EC_NONE; 130 131 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 132 "Policy store finished with HTTP status %u\n", 133 (unsigned int) response_code); 134 switch (response_code) 135 { 136 case 0: 137 ud.us = ANASTASIS_US_SERVER_ERROR; 138 ud.ec = TALER_EC_GENERIC_INVALID_RESPONSE; 139 break; 140 case MHD_HTTP_NO_CONTENT: 141 case MHD_HTTP_NOT_MODIFIED: 142 { 143 unsigned long long version; 144 unsigned long long expiration; 145 char dummy; 146 147 if (1 != sscanf (pso->policy_version, 148 "%llu%c", 149 &version, 150 &dummy)) 151 { 152 ud.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 153 ud.us = ANASTASIS_US_SERVER_ERROR; 154 break; 155 } 156 if (1 != sscanf (pso->policy_expiration, 157 "%llu%c", 158 &expiration, 159 &dummy)) 160 { 161 ud.ec = TALER_EC_GENERIC_REPLY_MALFORMED; 162 ud.us = ANASTASIS_US_SERVER_ERROR; 163 break; 164 } 165 ud.us = ANASTASIS_US_SUCCESS; 166 ud.details.success.curr_backup_hash = &pso->new_upload_hash; 167 ud.details.success.policy_expiration 168 = GNUNET_TIME_relative_to_timestamp ( 169 GNUNET_TIME_relative_multiply ( 170 GNUNET_TIME_UNIT_SECONDS, 171 expiration)); 172 ud.details.success.policy_version = version; 173 } 174 break; 175 case MHD_HTTP_BAD_REQUEST: 176 GNUNET_break (0); 177 ud.us = ANASTASIS_US_CLIENT_ERROR; 178 ud.ec = TALER_JSON_get_error_code2 (data, 179 data_size); 180 break; 181 case MHD_HTTP_PAYMENT_REQUIRED: 182 { 183 struct TALER_MERCHANT_PayUriData pd; 184 185 if ( (NULL == pso->pay_uri) || 186 (GNUNET_OK != 187 TALER_MERCHANT_parse_pay_uri (pso->pay_uri, 188 &pd)) ) 189 { 190 GNUNET_break_op (0); 191 ud.ec = TALER_EC_ANASTASIS_GENERIC_INVALID_PAYMENT_REQUEST; 192 break; 193 } 194 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 195 "Policy store operation requires payment `%s'\n", 196 pso->pay_uri); 197 if (GNUNET_OK != 198 GNUNET_STRINGS_string_to_data ( 199 pd.order_id, 200 strlen (pd.order_id), 201 &ud.details.payment.ps, 202 sizeof (ud.details.payment.ps))) 203 { 204 GNUNET_break (0); 205 ud.ec = TALER_EC_ANASTASIS_GENERIC_INVALID_PAYMENT_REQUEST; 206 TALER_MERCHANT_parse_pay_uri_free (&pd); 207 break; 208 } 209 TALER_MERCHANT_parse_pay_uri_free (&pd); 210 } 211 ud.us = ANASTASIS_US_PAYMENT_REQUIRED; 212 ud.details.payment.payment_request = pso->pay_uri; 213 break; 214 case MHD_HTTP_REQUEST_TIMEOUT: 215 ud.us = ANASTASIS_US_CLIENT_ERROR; 216 ud.ec = TALER_EC_ANASTASIS_PAYMENT_GENERIC_TIMEOUT; 217 break; 218 case MHD_HTTP_PAYLOAD_TOO_LARGE: 219 ud.us = ANASTASIS_US_CLIENT_ERROR; 220 ud.ec = TALER_EC_GENERIC_UPLOAD_EXCEEDS_LIMIT; 221 break; 222 case MHD_HTTP_LENGTH_REQUIRED: 223 GNUNET_break (0); 224 ud.ec = TALER_JSON_get_error_code2 (data, 225 data_size); 226 ud.us = ANASTASIS_US_SERVER_ERROR; 227 break; 228 case MHD_HTTP_INTERNAL_SERVER_ERROR: 229 ud.ec = TALER_JSON_get_error_code2 (data, 230 data_size); 231 ud.us = ANASTASIS_US_SERVER_ERROR; 232 break; 233 case MHD_HTTP_BAD_GATEWAY: 234 ud.ec = TALER_JSON_get_error_code2 (data, 235 data_size); 236 ud.us = ANASTASIS_US_SERVER_ERROR; 237 break; 238 default: 239 ud.ec = TALER_JSON_get_error_code2 (data, 240 data_size); 241 ud.us = ANASTASIS_US_SERVER_ERROR; 242 break; 243 } 244 pso->cb (pso->cb_cls, 245 &ud); 246 pso->cb = NULL; 247 ANASTASIS_policy_store_cancel (pso); 248 } 249 250 251 /** 252 * Handle HTTP header received by curl. 253 * 254 * @param buffer one line of HTTP header data 255 * @param size size of an item 256 * @param nitems number of items passed 257 * @param userdata our `struct ANASTASIS_StorePolicyOperation *` 258 * @return `size * nitems` 259 */ 260 static size_t 261 handle_header (char *buffer, 262 size_t size, 263 size_t nitems, 264 void *userdata) 265 { 266 struct ANASTASIS_PolicyStoreOperation *pso = userdata; 267 size_t total = size * nitems; 268 char *ndup; 269 const char *hdr_type; 270 char *hdr_val; 271 char *sp; 272 273 ndup = GNUNET_strndup (buffer, 274 total); 275 hdr_type = strtok_r (ndup, 276 ":", 277 &sp); 278 if (NULL == hdr_type) 279 { 280 GNUNET_free (ndup); 281 return total; 282 } 283 hdr_val = strtok_r (NULL, 284 "", 285 &sp); 286 if (NULL == hdr_val) 287 { 288 GNUNET_free (ndup); 289 return total; 290 } 291 if (' ' == *hdr_val) 292 hdr_val++; 293 if (0 == strcasecmp (hdr_type, 294 "Taler")) 295 { 296 size_t len; 297 298 /* found payment URI we care about! */ 299 GNUNET_free (pso->pay_uri); /* In case of duplicate header */ 300 pso->pay_uri = GNUNET_strdup (hdr_val); 301 len = strlen (pso->pay_uri); 302 while ( (len > 0) && 303 ( ('\n' == pso->pay_uri[len - 1]) || 304 ('\r' == pso->pay_uri[len - 1]) ) ) 305 { 306 len--; 307 pso->pay_uri[len] = '\0'; 308 } 309 } 310 311 if (0 == strcasecmp (hdr_type, 312 ANASTASIS_HTTP_HEADER_POLICY_VERSION)) 313 { 314 size_t len; 315 316 /* found policy version we care about! */ 317 GNUNET_free (pso->policy_version); /* In case of duplicate header */ 318 pso->policy_version = GNUNET_strdup (hdr_val); 319 len = strlen (pso->policy_version); 320 while ( (len > 0) && 321 ( ('\n' == pso->policy_version[len - 1]) || 322 ('\r' == pso->policy_version[len - 1]) ) ) 323 { 324 len--; 325 pso->policy_version[len] = '\0'; 326 } 327 } 328 329 if (0 == strcasecmp (hdr_type, 330 ANASTASIS_HTTP_HEADER_POLICY_EXPIRATION)) 331 { 332 size_t len; 333 334 /* found policy expiration we care about! */ 335 GNUNET_free (pso->policy_expiration); /* In case of duplicate header */ 336 pso->policy_expiration = GNUNET_strdup (hdr_val); 337 len = strlen (pso->policy_expiration); 338 while ( (len > 0) && 339 ( ('\n' == pso->policy_expiration[len - 1]) || 340 ('\r' == pso->policy_expiration[len - 1]) ) ) 341 { 342 len--; 343 pso->policy_expiration[len] = '\0'; 344 } 345 } 346 347 GNUNET_free (ndup); 348 return total; 349 } 350 351 352 struct ANASTASIS_PolicyStoreOperation * 353 ANASTASIS_policy_store ( 354 struct GNUNET_CURL_Context *ctx, 355 const char *backend_url, 356 const struct ANASTASIS_CRYPTO_AccountPrivateKeyP *anastasis_priv, 357 const void *recovery_data, 358 size_t recovery_data_size, 359 const void *recovery_meta_data, 360 size_t recovery_meta_data_size, 361 uint32_t payment_years_requested, 362 const struct ANASTASIS_PaymentSecretP *payment_secret, 363 struct GNUNET_TIME_Relative payment_timeout, 364 ANASTASIS_PolicyStoreCallback cb, 365 void *cb_cls) 366 { 367 struct ANASTASIS_PolicyStoreOperation *pso; 368 struct ANASTASIS_AccountSignatureP account_sig; 369 unsigned long long tms; 370 CURL *eh; 371 struct curl_slist *job_headers; 372 struct ANASTASIS_UploadSignaturePS usp = { 373 .purpose.purpose = htonl (TALER_SIGNATURE_ANASTASIS_POLICY_UPLOAD), 374 .purpose.size = htonl (sizeof (usp)) 375 }; 376 377 if (NULL == recovery_meta_data) 378 { 379 GNUNET_break (0); 380 return NULL; 381 } 382 tms = (unsigned long long) (payment_timeout.rel_value_us 383 / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us); 384 GNUNET_CRYPTO_hash (recovery_data, 385 recovery_data_size, 386 &usp.new_recovery_data_hash); 387 GNUNET_CRYPTO_eddsa_sign (&anastasis_priv->priv, 388 &usp, 389 &account_sig.eddsa_sig); 390 /* setup our HTTP headers */ 391 job_headers = NULL; 392 { 393 struct curl_slist *ext; 394 char *val; 395 char *hdr; 396 397 /* Set Anastasis-Policy-Signature header */ 398 val = GNUNET_STRINGS_data_to_string_alloc (&account_sig, 399 sizeof (account_sig)); 400 GNUNET_asprintf (&hdr, 401 "%s: %s", 402 ANASTASIS_HTTP_HEADER_POLICY_SIGNATURE, 403 val); 404 GNUNET_free (val); 405 ext = curl_slist_append (job_headers, 406 hdr); 407 GNUNET_free (hdr); 408 if (NULL == ext) 409 { 410 GNUNET_break (0); 411 curl_slist_free_all (job_headers); 412 return NULL; 413 } 414 job_headers = ext; 415 416 /* set Etag header */ 417 val = GNUNET_STRINGS_data_to_string_alloc (&usp.new_recovery_data_hash, 418 sizeof (struct GNUNET_HashCode)); 419 GNUNET_asprintf (&hdr, 420 "%s: \"%s\"", 421 MHD_HTTP_HEADER_IF_NONE_MATCH, 422 val); 423 GNUNET_free (val); 424 ext = curl_slist_append (job_headers, 425 hdr); 426 GNUNET_free (hdr); 427 if (NULL == ext) 428 { 429 GNUNET_break (0); 430 curl_slist_free_all (job_headers); 431 return NULL; 432 } 433 job_headers = ext; 434 435 /* Setup meta-data header */ 436 { 437 char *meta_val; 438 439 meta_val = GNUNET_STRINGS_data_to_string_alloc ( 440 recovery_meta_data, 441 recovery_meta_data_size); 442 GNUNET_asprintf (&hdr, 443 "%s: %s", 444 ANASTASIS_HTTP_HEADER_POLICY_META_DATA, 445 meta_val); 446 GNUNET_free (meta_val); 447 ext = curl_slist_append (job_headers, 448 hdr); 449 GNUNET_free (hdr); 450 if (NULL == ext) 451 { 452 GNUNET_break (0); 453 curl_slist_free_all (job_headers); 454 return NULL; 455 } 456 job_headers = ext; 457 } 458 459 /* Setup Payment-Identifier header */ 460 if (NULL != payment_secret) 461 { 462 char *paid_order_id; 463 464 paid_order_id = GNUNET_STRINGS_data_to_string_alloc ( 465 payment_secret, 466 sizeof (*payment_secret)); 467 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 468 "Beginning policy store operation with payment secret `%s'\n", 469 paid_order_id); 470 GNUNET_asprintf (&hdr, 471 "%s: %s", 472 ANASTASIS_HTTP_HEADER_PAYMENT_IDENTIFIER, 473 paid_order_id); 474 GNUNET_free (paid_order_id); 475 ext = curl_slist_append (job_headers, 476 hdr); 477 GNUNET_free (hdr); 478 if (NULL == ext) 479 { 480 GNUNET_break (0); 481 curl_slist_free_all (job_headers); 482 return NULL; 483 } 484 job_headers = ext; 485 } 486 else 487 { 488 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 489 "Beginning policy store operation without payment secret\n"); 490 } 491 } 492 /* Finished setting up headers */ 493 pso = GNUNET_new (struct ANASTASIS_PolicyStoreOperation); 494 pso->postcopy = GNUNET_memdup (recovery_data, 495 recovery_data_size); 496 pso->new_upload_hash = usp.new_recovery_data_hash; 497 { 498 char *acc_pub_str; 499 char *path; 500 struct ANASTASIS_CRYPTO_AccountPublicKeyP pub; 501 char timeout_ms[32]; 502 char pyrs[32]; 503 504 GNUNET_snprintf (timeout_ms, 505 sizeof (timeout_ms), 506 "%llu", 507 tms); 508 GNUNET_snprintf (pyrs, 509 sizeof (pyrs), 510 "%u", 511 (unsigned int) payment_years_requested); 512 GNUNET_CRYPTO_eddsa_key_get_public (&anastasis_priv->priv, 513 &pub.pub); 514 acc_pub_str 515 = GNUNET_STRINGS_data_to_string_alloc (&pub, 516 sizeof (pub)); 517 GNUNET_asprintf (&path, 518 "policy/%s", 519 acc_pub_str); 520 GNUNET_free (acc_pub_str); 521 pso->url = TALER_url_join (backend_url, 522 path, 523 "storage_duration", 524 (0 != payment_years_requested) 525 ? pyrs 526 : NULL, 527 "timeout_ms", 528 (0 != payment_timeout.rel_value_us) 529 ? timeout_ms 530 : NULL, 531 NULL); 532 GNUNET_free (path); 533 } 534 pso->ctx = ctx; 535 pso->cb = cb; 536 pso->cb_cls = cb_cls; 537 eh = ANASTASIS_curl_easy_get_ (pso->url); 538 if (0 != tms) 539 GNUNET_assert (CURLE_OK == 540 curl_easy_setopt (eh, 541 CURLOPT_TIMEOUT_MS, 542 (long) (tms + 5000))); 543 GNUNET_assert (CURLE_OK == 544 curl_easy_setopt (eh, 545 CURLOPT_POSTFIELDS, 546 pso->postcopy)); 547 GNUNET_assert (CURLE_OK == 548 curl_easy_setopt (eh, 549 CURLOPT_POSTFIELDSIZE, 550 (long) recovery_data_size)); 551 GNUNET_assert (CURLE_OK == 552 curl_easy_setopt (eh, 553 CURLOPT_HEADERFUNCTION, 554 &handle_header)); 555 GNUNET_assert (CURLE_OK == 556 curl_easy_setopt (eh, 557 CURLOPT_HEADERDATA, 558 pso)); 559 pso->job = GNUNET_CURL_job_add_raw (ctx, 560 eh, 561 job_headers, 562 &handle_policy_store_finished, 563 pso); 564 curl_slist_free_all (job_headers); 565 return pso; 566 }