taler-merchant-httpd_helper.c (43630B)
1 /* 2 This file is part of TALER 3 (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 Lesser 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_helper.c 18 * @brief shared logic for various handlers 19 * @author Christian Grothoff 20 */ 21 #include "platform.h" 22 #include <gnunet/gnunet_util_lib.h> 23 #include <gnunet/gnunet_db_lib.h> 24 #include <taler/taler_json_lib.h> 25 #include "taler-merchant-httpd_helper.h" 26 #include <taler/taler_templating_lib.h> 27 #include <taler/taler_dbevents.h> 28 29 30 enum GNUNET_GenericReturnValue 31 TMH_parse_fractional_string (const char *value, 32 int64_t *integer_part, 33 uint32_t *fractional_part) 34 { 35 const char *ptr; 36 uint64_t integer = 0; 37 uint32_t frac = 0; 38 unsigned int digits = 0; 39 40 GNUNET_assert (NULL != integer_part); 41 GNUNET_assert (NULL != fractional_part); 42 43 if (NULL == value) 44 { 45 GNUNET_break_op (0); 46 return GNUNET_SYSERR; 47 } 48 ptr = value; 49 if ('\0' == *ptr) 50 { 51 GNUNET_break_op (0); 52 return GNUNET_SYSERR; 53 } 54 if ('-' == *ptr) 55 { 56 GNUNET_break_op (0); 57 return GNUNET_SYSERR; 58 } 59 if (! isdigit ((unsigned char) *ptr)) 60 { 61 GNUNET_break_op (0); 62 return GNUNET_SYSERR; 63 } 64 while (isdigit ((unsigned char) *ptr)) 65 { 66 unsigned int digit = (unsigned int) (*ptr - '0'); 67 68 if (integer > (UINT64_MAX - digit) / 10) 69 { 70 GNUNET_break_op (0); 71 return GNUNET_SYSERR; 72 } 73 integer = integer * 10 + digit; 74 ptr++; 75 } 76 if ('.' == *ptr) 77 { 78 ptr++; 79 if ('\0' == *ptr) 80 { 81 GNUNET_break_op (0); 82 return GNUNET_SYSERR; 83 } 84 while (isdigit ((unsigned char) *ptr)) 85 { 86 unsigned int digit = (unsigned int) (*ptr - '0'); 87 88 if (digits >= MERCHANT_UNIT_FRAC_MAX_DIGITS) 89 { 90 GNUNET_break_op (0); 91 return GNUNET_SYSERR; 92 } 93 frac = (uint32_t) (frac * 10 + digit); 94 digits++; 95 ptr++; 96 } 97 while (digits < MERCHANT_UNIT_FRAC_MAX_DIGITS) 98 { 99 frac *= 10; 100 digits++; 101 } 102 } 103 if ('\0' != *ptr) 104 { 105 GNUNET_break_op (0); 106 return GNUNET_SYSERR; 107 } 108 if (integer > (uint64_t) INT64_MAX) 109 { 110 GNUNET_break_op (0); 111 return GNUNET_SYSERR; 112 } 113 *integer_part = integer; 114 *fractional_part = frac; 115 return GNUNET_OK; 116 } 117 118 119 enum GNUNET_GenericReturnValue 120 TMH_process_quantity_inputs (enum TMH_ValueKind kind, 121 bool allow_fractional, 122 bool int_missing, 123 int64_t int_raw, 124 bool str_missing, 125 const char *str_raw, 126 uint64_t *int_out, 127 uint32_t *frac_out, 128 const char **error_param) 129 { 130 static char errbuf[128]; 131 int64_t parsed_int = 0; 132 uint32_t parsed_frac = 0; 133 const char *int_field = (TMH_VK_STOCK == kind) 134 ? "total_stock" 135 : "quantity"; 136 const char *str_field = (TMH_VK_STOCK == kind) 137 ? "unit_total_stock" 138 : "unit_quantity"; 139 140 *error_param = NULL; 141 142 if (int_missing && str_missing) 143 { 144 GNUNET_snprintf (errbuf, 145 sizeof (errbuf), 146 "missing %s and %s", 147 int_field, 148 str_field); 149 *error_param = errbuf; 150 GNUNET_break_op (0); 151 return GNUNET_SYSERR; 152 } 153 154 if (! str_missing) 155 { 156 if ( (TMH_VK_STOCK == kind) && 157 (0 == strcmp ("-1", 158 str_raw)) ) 159 { 160 parsed_int = -1; 161 parsed_frac = 0; 162 } 163 else 164 { 165 if (GNUNET_OK != 166 TMH_parse_fractional_string (str_raw, 167 &parsed_int, 168 &parsed_frac)) 169 { 170 GNUNET_snprintf (errbuf, 171 sizeof (errbuf), 172 "malformed %s", 173 str_field); 174 *error_param = errbuf; 175 GNUNET_break_op (0); 176 return GNUNET_SYSERR; 177 } 178 } 179 } 180 181 if ( (! int_missing) && (! str_missing) ) 182 { 183 if ( (parsed_int != int_raw) || (0 != parsed_frac) ) 184 { 185 GNUNET_snprintf (errbuf, 186 sizeof (errbuf), 187 "%s/%s mismatch", 188 int_field, 189 str_field); 190 *error_param = errbuf; 191 GNUNET_break_op (0); 192 return GNUNET_SYSERR; 193 } 194 } 195 else if (int_missing) 196 { 197 int_raw = parsed_int; 198 } 199 200 if ( (TMH_VK_STOCK == kind) && (-1 == int_raw) ) 201 { 202 if ( (! str_missing) && (0 != parsed_frac) ) 203 { 204 GNUNET_snprintf (errbuf, 205 sizeof (errbuf), 206 "fractional part forbidden with %s='-1'", 207 str_field); 208 *error_param = errbuf; 209 GNUNET_break_op (0); 210 return GNUNET_SYSERR; 211 } 212 *int_out = INT64_MAX; 213 *frac_out = INT32_MAX; 214 return GNUNET_OK; 215 } 216 217 if (int_raw < 0) 218 { 219 GNUNET_snprintf (errbuf, 220 sizeof (errbuf), 221 "%s must be non-negative", 222 int_field); 223 *error_param = errbuf; 224 GNUNET_break_op (0); 225 return GNUNET_SYSERR; 226 } 227 228 if (! allow_fractional) 229 { 230 if ( (! str_missing) && (0 != parsed_frac) ) 231 { 232 GNUNET_snprintf (errbuf, 233 sizeof (errbuf), 234 "fractional part not allowed for %s", 235 str_field); 236 *error_param = errbuf; 237 GNUNET_break_op (0); 238 return GNUNET_SYSERR; 239 } 240 parsed_frac = 0; 241 } 242 else if (! str_missing) 243 { 244 if (parsed_frac >= MERCHANT_UNIT_FRAC_BASE) 245 { 246 GNUNET_snprintf (errbuf, 247 sizeof (errbuf), 248 "%s fractional part exceeds base %u", 249 str_field, 250 MERCHANT_UNIT_FRAC_BASE); 251 *error_param = errbuf; 252 GNUNET_break_op (0); 253 return GNUNET_SYSERR; 254 } 255 } 256 257 *int_out = (uint64_t) int_raw; 258 *frac_out = parsed_frac; 259 return GNUNET_OK; 260 } 261 262 263 void 264 TMH_format_fractional_string (enum TMH_ValueKind kind, 265 uint64_t integer, 266 uint32_t fractional, 267 size_t buffer_length, 268 char buffer[static buffer_length]) 269 { 270 GNUNET_assert (0 < buffer_length); 271 272 if ( (TMH_VK_STOCK == kind) && 273 (INT64_MAX == (int64_t) integer) && 274 (INT32_MAX == (int32_t) fractional) ) 275 { 276 GNUNET_snprintf (buffer, 277 buffer_length, 278 "-1"); 279 return; 280 } 281 282 GNUNET_assert ( (TMH_VK_QUANTITY != kind) || 283 ((INT64_MAX != (int64_t) integer) && 284 (INT32_MAX != (int32_t) fractional)) ); 285 GNUNET_assert (fractional < MERCHANT_UNIT_FRAC_BASE); 286 287 if (0 == fractional) 288 { 289 GNUNET_snprintf (buffer, 290 buffer_length, 291 "%lu", 292 integer); 293 return; 294 } 295 { 296 char frac_buf[MERCHANT_UNIT_FRAC_MAX_DIGITS + 1]; 297 size_t idx; 298 299 GNUNET_snprintf (frac_buf, 300 sizeof (frac_buf), 301 "%0*u", 302 MERCHANT_UNIT_FRAC_MAX_DIGITS, 303 (unsigned int) fractional); 304 for (idx = strlen (frac_buf); idx > 0; idx--) 305 { 306 if ('0' != frac_buf[idx - 1]) 307 break; 308 frac_buf[idx - 1] = '\0'; 309 } 310 GNUNET_snprintf (buffer, 311 buffer_length, 312 "%lu.%s", 313 integer, 314 frac_buf); 315 } 316 } 317 318 319 void 320 TMH_quantity_defaults_from_unit (const struct TMH_MerchantInstance *mi, 321 const char *unit, 322 bool *allow_fractional, 323 uint32_t *precision_level) 324 { 325 GNUNET_assert (NULL != allow_fractional); 326 GNUNET_assert (NULL != precision_level); 327 if (GNUNET_OK != 328 TMH_unit_defaults_for_instance (mi, 329 unit, 330 allow_fractional, 331 precision_level)) 332 { 333 *allow_fractional = false; 334 *precision_level = 0; 335 } 336 } 337 338 339 enum GNUNET_GenericReturnValue 340 TMH_unit_defaults_for_instance (const struct TMH_MerchantInstance *mi, 341 const char *unit, 342 bool *allow_fractional, 343 uint32_t *precision_level) 344 { 345 struct TALER_MERCHANTDB_UnitDetails ud = { 0 }; 346 enum GNUNET_DB_QueryStatus qs; 347 bool allow = false; 348 uint32_t precision = 0; 349 350 GNUNET_assert (NULL != allow_fractional); 351 GNUNET_assert (NULL != precision_level); 352 353 qs = TMH_db->select_unit (TMH_db->cls, 354 mi->settings.id, 355 unit, 356 &ud); 357 switch (qs) 358 { 359 case GNUNET_DB_STATUS_HARD_ERROR: 360 case GNUNET_DB_STATUS_SOFT_ERROR: 361 TALER_MERCHANTDB_unit_details_free (&ud); 362 return GNUNET_SYSERR; 363 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 364 allow = ud.unit_allow_fraction; 365 precision = ud.unit_precision_level; 366 break; 367 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 368 break; 369 default: 370 GNUNET_break (0); 371 TALER_MERCHANTDB_unit_details_free (&ud); 372 return GNUNET_SYSERR; 373 } 374 TALER_MERCHANTDB_unit_details_free (&ud); 375 376 /* This is definitely not supposed to happen 377 combination of allow -> false, and precision > 0 378 yet let's fix it */ 379 if (! allow) 380 { 381 GNUNET_break (0); 382 precision = 0; 383 } 384 *allow_fractional = allow; 385 *precision_level = precision; 386 return GNUNET_OK; 387 } 388 389 390 enum GNUNET_GenericReturnValue 391 TMH_cmp_wire_account ( 392 const json_t *account, 393 const struct TMH_WireMethod *wm) 394 { 395 const char *credit_facade_url = NULL; 396 const json_t *credit_facade_credentials = NULL; 397 struct TALER_FullPayto uri; 398 struct GNUNET_JSON_Specification ispec[] = { 399 TALER_JSON_spec_full_payto_uri ("payto_uri", 400 &uri), 401 GNUNET_JSON_spec_mark_optional ( 402 TALER_JSON_spec_web_url ("credit_facade_url", 403 &credit_facade_url), 404 NULL), 405 GNUNET_JSON_spec_mark_optional ( 406 GNUNET_JSON_spec_object_const ("credit_facade_credentials", 407 &credit_facade_credentials), 408 NULL), 409 GNUNET_JSON_spec_end () 410 }; 411 enum GNUNET_GenericReturnValue res; 412 const char *ename; 413 unsigned int eline; 414 415 res = GNUNET_JSON_parse (account, 416 ispec, 417 &ename, 418 &eline); 419 if (GNUNET_OK != res) 420 { 421 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 422 "Failed to parse account spec: %s (%u)\n", 423 ename, 424 eline); 425 return GNUNET_SYSERR; 426 } 427 if (0 != 428 TALER_full_payto_cmp (wm->payto_uri, 429 uri)) 430 { 431 return GNUNET_SYSERR; 432 } 433 if ( (NULL == credit_facade_url) != 434 (NULL == wm->credit_facade_url) || 435 (NULL == credit_facade_credentials) != 436 (NULL == wm->credit_facade_credentials) ) 437 { 438 return GNUNET_NO; 439 } 440 if ( (NULL != credit_facade_url) && 441 (0 != strcmp (credit_facade_url, 442 wm->credit_facade_url)) ) 443 { 444 return GNUNET_NO; 445 } 446 if ( (NULL != credit_facade_credentials) && 447 (0 != json_equal (credit_facade_credentials, 448 wm->credit_facade_credentials)) ) 449 { 450 return GNUNET_NO; 451 } 452 return GNUNET_YES; 453 } 454 455 456 bool 457 TMH_accounts_array_valid (const json_t *accounts) 458 { 459 size_t len; 460 461 if (! json_is_array (accounts)) 462 { 463 GNUNET_break_op (0); 464 return false; 465 } 466 len = json_array_size (accounts); 467 for (size_t i = 0; i<len; i++) 468 { 469 json_t *payto_uri = json_array_get (accounts, 470 i); 471 const char *credit_facade_url = NULL; 472 const json_t *credit_facade_credentials = NULL; 473 struct TALER_FullPayto uri; 474 struct GNUNET_JSON_Specification ispec[] = { 475 TALER_JSON_spec_full_payto_uri ("payto_uri", 476 &uri), 477 GNUNET_JSON_spec_mark_optional ( 478 TALER_JSON_spec_web_url ("credit_facade_url", 479 &credit_facade_url), 480 NULL), 481 GNUNET_JSON_spec_mark_optional ( 482 GNUNET_JSON_spec_object_const ("credit_facade_credentials", 483 &credit_facade_credentials), 484 NULL), 485 GNUNET_JSON_spec_end () 486 }; 487 enum GNUNET_GenericReturnValue res; 488 const char *ename; 489 unsigned int eline; 490 491 res = GNUNET_JSON_parse (payto_uri, 492 ispec, 493 &ename, 494 &eline); 495 if (GNUNET_OK != res) 496 { 497 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 498 "Failed to parse account spec: %s (%u)\n", 499 ename, 500 eline); 501 return false; 502 } 503 504 /* Test for the same payto:// URI being given twice */ 505 for (size_t j = 0; j<i; j++) 506 { 507 json_t *old_uri = json_array_get (accounts, 508 j); 509 if (0 == strcmp (uri.full_payto, 510 json_string_value ( 511 json_object_get (old_uri, 512 "payto_uri")))) 513 { 514 GNUNET_break_op (0); 515 return false; 516 } 517 } 518 { 519 char *err; 520 521 if (NULL != 522 (err = TALER_payto_validate (uri))) 523 { 524 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 525 "Encountered invalid payto://-URI `%s': %s\n", 526 uri.full_payto, 527 err); 528 GNUNET_free (err); 529 return false; 530 } 531 } 532 if ( (NULL == credit_facade_url) != 533 (NULL == credit_facade_credentials) ) 534 { 535 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 536 "If credit_facade_url is given, credit_facade_credentials must also be specified (violated for %s)\n", 537 uri.full_payto); 538 return false; 539 } 540 if ( (NULL != credit_facade_url) || 541 (NULL != credit_facade_credentials) ) 542 { 543 struct TALER_MERCHANT_BANK_AuthenticationData auth; 544 545 if (GNUNET_OK != 546 TALER_MERCHANT_BANK_auth_parse_json (credit_facade_credentials, 547 credit_facade_url, 548 &auth)) 549 { 550 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 551 "Invalid credit facade URL or credentials `%s'\n", 552 credit_facade_url); 553 return false; 554 } 555 TALER_MERCHANT_BANK_auth_free (&auth); 556 } 557 } /* end for all accounts */ 558 return true; 559 } 560 561 562 bool 563 TMH_location_object_valid (const json_t *location) 564 { 565 const char *country = NULL; 566 const char *subdivision = NULL; 567 const char *district = NULL; 568 const char *town = NULL; 569 const char *town_loc = NULL; 570 const char *postcode = NULL; 571 const char *street = NULL; 572 const char *building = NULL; 573 const char *building_no = NULL; 574 const json_t *lines = NULL; 575 struct GNUNET_JSON_Specification spec[] = { 576 GNUNET_JSON_spec_mark_optional ( 577 GNUNET_JSON_spec_string ("country", 578 &country), 579 NULL), 580 GNUNET_JSON_spec_mark_optional ( 581 GNUNET_JSON_spec_string ("country_subdivision", 582 &subdivision), 583 NULL), 584 GNUNET_JSON_spec_mark_optional ( 585 GNUNET_JSON_spec_string ("district", 586 &district), 587 NULL), 588 GNUNET_JSON_spec_mark_optional ( 589 GNUNET_JSON_spec_string ("town", 590 &town), 591 NULL), 592 GNUNET_JSON_spec_mark_optional ( 593 GNUNET_JSON_spec_string ("town_location", 594 &town_loc), 595 NULL), 596 GNUNET_JSON_spec_mark_optional ( 597 GNUNET_JSON_spec_string ("post_code", 598 &postcode), 599 NULL), 600 GNUNET_JSON_spec_mark_optional ( 601 GNUNET_JSON_spec_string ("street", 602 &street), 603 NULL), 604 GNUNET_JSON_spec_mark_optional ( 605 GNUNET_JSON_spec_string ("building_name", 606 &building), 607 NULL), 608 GNUNET_JSON_spec_mark_optional ( 609 GNUNET_JSON_spec_string ("building_number", 610 &building_no), 611 NULL), 612 GNUNET_JSON_spec_mark_optional ( 613 GNUNET_JSON_spec_array_const ("address_lines", 614 &lines), 615 NULL), 616 GNUNET_JSON_spec_end () 617 }; 618 const char *ename; 619 unsigned int eline; 620 621 if (GNUNET_OK != 622 GNUNET_JSON_parse (location, 623 spec, 624 &ename, 625 &eline)) 626 { 627 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 628 "Invalid location for field %s\n", 629 ename); 630 return false; 631 } 632 if (NULL != lines) 633 { 634 size_t idx; 635 json_t *line; 636 637 json_array_foreach (lines, idx, line) 638 { 639 if (! json_is_string (line)) 640 { 641 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 642 "Invalid address line #%u in location\n", 643 (unsigned int) idx); 644 return false; 645 } 646 } 647 } 648 return true; 649 } 650 651 652 bool 653 TMH_products_array_valid (const json_t *products) 654 { 655 const json_t *product; 656 size_t idx; 657 bool valid = true; 658 659 if (! json_is_array (products)) 660 { 661 GNUNET_break_op (0); 662 return false; 663 } 664 json_array_foreach ((json_t *) products, idx, product) 665 { 666 const char *product_id = NULL; 667 const char *description; 668 uint64_t quantity = 0; 669 bool quantity_missing = true; 670 const char *unit_quantity = NULL; 671 bool unit_quantity_missing = true; 672 const char *unit = NULL; 673 struct TALER_Amount price = { .value = 0 }; 674 const char *image_data_url = NULL; 675 const json_t *taxes = NULL; 676 struct GNUNET_TIME_Timestamp delivery_date = { 0 }; 677 struct GNUNET_JSON_Specification spec[] = { 678 GNUNET_JSON_spec_mark_optional ( 679 GNUNET_JSON_spec_string ("product_id", 680 &product_id), 681 NULL), 682 TALER_JSON_spec_i18n_str ("description", 683 &description), 684 GNUNET_JSON_spec_mark_optional ( 685 GNUNET_JSON_spec_uint64 ("quantity", 686 &quantity), 687 &quantity_missing), 688 GNUNET_JSON_spec_mark_optional ( 689 GNUNET_JSON_spec_string ("unit_quantity", 690 &unit_quantity), 691 &unit_quantity_missing), 692 GNUNET_JSON_spec_mark_optional ( 693 GNUNET_JSON_spec_string ("unit", 694 &unit), 695 NULL), 696 GNUNET_JSON_spec_mark_optional ( 697 TALER_JSON_spec_amount_any ("price", 698 &price), 699 NULL), 700 GNUNET_JSON_spec_mark_optional ( 701 GNUNET_JSON_spec_string ("image", 702 &image_data_url), 703 NULL), 704 GNUNET_JSON_spec_mark_optional ( 705 GNUNET_JSON_spec_array_const ("taxes", 706 &taxes), 707 NULL), 708 GNUNET_JSON_spec_mark_optional ( 709 GNUNET_JSON_spec_timestamp ("delivery_date", 710 &delivery_date), 711 NULL), 712 GNUNET_JSON_spec_end () 713 }; 714 const char *ename; 715 unsigned int eline; 716 717 if (GNUNET_OK != 718 GNUNET_JSON_parse (product, 719 spec, 720 &ename, 721 &eline)) 722 { 723 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 724 "Invalid product #%u for field %s\n", 725 (unsigned int) idx, 726 ename); 727 return false; 728 } 729 if (quantity_missing) 730 { 731 if (unit_quantity_missing) 732 { 733 GNUNET_break_op (0); 734 valid = false; 735 } 736 } 737 if ( (NULL != image_data_url) && 738 (! TMH_image_data_url_valid (image_data_url)) ) 739 { 740 GNUNET_break_op (0); 741 valid = false; 742 } 743 if ( (NULL != taxes) && 744 (! TMH_taxes_array_valid (taxes)) ) 745 { 746 GNUNET_break_op (0); 747 valid = false; 748 } 749 GNUNET_JSON_parse_free (spec); 750 if (! valid) 751 break; 752 } 753 754 return valid; 755 } 756 757 758 bool 759 TMH_image_data_url_valid (const char *image_data_url) 760 { 761 if (0 == strcmp (image_data_url, 762 "")) 763 return true; 764 if (0 != strncasecmp ("data:image/", 765 image_data_url, 766 strlen ("data:image/"))) 767 { 768 GNUNET_break_op (0); 769 return false; 770 } 771 if (NULL == strstr (image_data_url, 772 ";base64,")) 773 { 774 GNUNET_break_op (0); 775 return false; 776 } 777 if (! TALER_url_valid_charset (image_data_url)) 778 { 779 GNUNET_break_op (0); 780 return false; 781 } 782 return true; 783 } 784 785 786 bool 787 TMH_template_contract_valid (const json_t *template_contract) 788 { 789 const char *summary; 790 const char *currency; 791 struct TALER_Amount amount = { .value = 0}; 792 uint32_t minimum_age = 0; 793 struct GNUNET_TIME_Relative pay_duration = { 0 }; 794 struct GNUNET_JSON_Specification spec[] = { 795 GNUNET_JSON_spec_mark_optional ( 796 GNUNET_JSON_spec_string ("summary", 797 &summary), 798 NULL), 799 GNUNET_JSON_spec_mark_optional ( 800 GNUNET_JSON_spec_string ("currency", 801 ¤cy), 802 NULL), 803 GNUNET_JSON_spec_mark_optional ( 804 TALER_JSON_spec_amount_any ("amount", 805 &amount), 806 NULL), 807 GNUNET_JSON_spec_uint32 ("minimum_age", 808 &minimum_age), 809 GNUNET_JSON_spec_relative_time ("pay_duration", 810 &pay_duration), 811 GNUNET_JSON_spec_end () 812 }; 813 const char *ename; 814 unsigned int eline; 815 816 if (GNUNET_OK != 817 GNUNET_JSON_parse (template_contract, 818 spec, 819 &ename, 820 &eline)) 821 { 822 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 823 "Invalid template_contract for field %s\n", 824 ename); 825 return false; 826 } 827 return true; 828 } 829 830 831 bool 832 TMH_taxes_array_valid (const json_t *taxes) 833 { 834 json_t *tax; 835 size_t idx; 836 837 if (! json_is_array (taxes)) 838 return false; 839 json_array_foreach (taxes, idx, tax) 840 { 841 struct TALER_Amount amount; 842 const char *name; 843 struct GNUNET_JSON_Specification spec[] = { 844 GNUNET_JSON_spec_string ("name", 845 &name), 846 TALER_JSON_spec_amount_any ("tax", 847 &amount), 848 GNUNET_JSON_spec_end () 849 }; 850 enum GNUNET_GenericReturnValue res; 851 852 res = TALER_MHD_parse_json_data (NULL, 853 tax, 854 spec); 855 if (GNUNET_OK != res) 856 { 857 GNUNET_break_op (0); 858 return false; 859 } 860 } 861 return true; 862 } 863 864 865 struct TMH_WireMethod * 866 TMH_setup_wire_account ( 867 struct TALER_FullPayto payto_uri, 868 const char *credit_facade_url, 869 const json_t *credit_facade_credentials) 870 { 871 struct TMH_WireMethod *wm; 872 char *emsg; 873 874 if (NULL != (emsg = TALER_payto_validate (payto_uri))) 875 { 876 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 877 "Invalid URI `%s': %s\n", 878 payto_uri.full_payto, 879 emsg); 880 GNUNET_free (emsg); 881 return NULL; 882 } 883 884 wm = GNUNET_new (struct TMH_WireMethod); 885 if (NULL != credit_facade_url) 886 wm->credit_facade_url 887 = GNUNET_strdup (credit_facade_url); 888 if (NULL != credit_facade_credentials) 889 wm->credit_facade_credentials 890 = json_incref ((json_t*) credit_facade_credentials); 891 GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, 892 &wm->wire_salt, 893 sizeof (wm->wire_salt)); 894 wm->payto_uri.full_payto 895 = GNUNET_strdup (payto_uri.full_payto); 896 TALER_merchant_wire_signature_hash (payto_uri, 897 &wm->wire_salt, 898 &wm->h_wire); 899 wm->wire_method 900 = TALER_payto_get_method (payto_uri.full_payto); 901 wm->active = true; 902 return wm; 903 } 904 905 906 enum TALER_ErrorCode 907 TMH_check_token (const char *token, 908 const char *instance_id, 909 enum TMH_AuthScope *as) 910 { 911 enum TMH_AuthScope scope; 912 struct GNUNET_TIME_Timestamp expiration; 913 enum GNUNET_DB_QueryStatus qs; 914 struct TALER_MERCHANTDB_LoginTokenP btoken; 915 916 if (NULL == token) 917 { 918 *as = TMH_AS_NONE; 919 return TALER_EC_NONE; 920 } 921 if (0 != strncasecmp (token, 922 RFC_8959_PREFIX, 923 strlen (RFC_8959_PREFIX))) 924 { 925 *as = TMH_AS_NONE; 926 return TALER_EC_NONE; 927 } 928 token += strlen (RFC_8959_PREFIX); 929 if (GNUNET_OK != 930 GNUNET_STRINGS_string_to_data (token, 931 strlen (token), 932 &btoken, 933 sizeof (btoken))) 934 { 935 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 936 "Given authorization token `%s' is malformed\n", 937 token); 938 GNUNET_break_op (0); 939 return TALER_EC_GENERIC_TOKEN_MALFORMED; 940 } 941 qs = TMH_db->select_login_token (TMH_db->cls, 942 instance_id, 943 &btoken, 944 &expiration, 945 (uint32_t*) &scope); 946 if (qs < 0) 947 { 948 GNUNET_break (0); 949 return TALER_EC_GENERIC_DB_FETCH_FAILED; 950 } 951 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 952 { 953 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 954 "Authorization token `%s' unknown\n", 955 token); 956 return TALER_EC_GENERIC_TOKEN_UNKNOWN; 957 } 958 if (GNUNET_TIME_absolute_is_past (expiration.abs_time)) 959 { 960 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 961 "Authorization token `%s' expired\n", 962 token); 963 return TALER_EC_GENERIC_TOKEN_EXPIRED; 964 } 965 *as = scope; 966 return TALER_EC_NONE; 967 } 968 969 970 enum GNUNET_GenericReturnValue 971 TMH_check_auth_config (struct MHD_Connection *connection, 972 const json_t *jauth, 973 const char **auth_password) 974 { 975 bool auth_wellformed = false; 976 const char *auth_method = json_string_value (json_object_get (jauth, 977 "method")); 978 979 *auth_password = NULL; 980 if (NULL == auth_method) 981 { 982 GNUNET_break_op (0); 983 } 984 else if ((GNUNET_YES != TMH_strict_v19) && 985 (0 == strcmp (auth_method, 986 "external"))) 987 { 988 auth_wellformed = true; 989 } 990 else if (GNUNET_YES == TMH_auth_disabled) 991 { 992 auth_wellformed = true; 993 } 994 else if (0 == strcmp (auth_method, 995 "token")) 996 { 997 json_t *pw_value; 998 999 pw_value = json_object_get (jauth, 1000 "password"); 1001 if (NULL == pw_value) 1002 { 1003 pw_value = json_object_get (jauth, 1004 "token"); 1005 } 1006 if (NULL == pw_value) 1007 { 1008 auth_wellformed = false; 1009 GNUNET_break_op (0); 1010 } 1011 else 1012 { 1013 *auth_password = json_string_value (pw_value); 1014 if (NULL != *auth_password) 1015 { 1016 if (0 == strncasecmp (RFC_8959_PREFIX, 1017 *auth_password, 1018 strlen (RFC_8959_PREFIX))) 1019 { 1020 *auth_password = *auth_password + strlen (RFC_8959_PREFIX); 1021 } 1022 auth_wellformed = true; 1023 } 1024 } 1025 } 1026 1027 if (! auth_wellformed) 1028 { 1029 GNUNET_break_op (0); 1030 return (MHD_YES == 1031 TALER_MHD_reply_with_error (connection, 1032 MHD_HTTP_BAD_REQUEST, 1033 TALER_EC_MERCHANT_PRIVATE_POST_INSTANCES_BAD_AUTH, 1034 "bad authentication config")) 1035 ? GNUNET_NO 1036 : GNUNET_SYSERR; 1037 } 1038 return GNUNET_OK; 1039 } 1040 1041 1042 void 1043 TMH_uuid_from_string (const char *uuids, 1044 struct GNUNET_Uuid *uuid) 1045 { 1046 struct GNUNET_HashCode hc; 1047 1048 GNUNET_CRYPTO_hash (uuids, 1049 strlen (uuids), 1050 &hc); 1051 GNUNET_static_assert (sizeof (hc) > sizeof (*uuid)); 1052 GNUNET_memcpy (uuid, 1053 &hc, 1054 sizeof (*uuid)); 1055 } 1056 1057 1058 /** 1059 * Closure for #trigger_webhook_cb. 1060 * 1061 * @param instance which is the instance we work with 1062 * @param root JSON data to fill into the template 1063 * @param rv, query of the TALER_TEMPLATEING_fill 1064 */ 1065 struct Trigger 1066 { 1067 const char *instance; 1068 1069 const json_t *root; 1070 1071 enum GNUNET_DB_QueryStatus rv; 1072 1073 }; 1074 1075 /** 1076 * Typically called by `TMH_trigger_webhook`. 1077 * 1078 * @param[in,out] cls a `struct Trigger` with information about the webhook 1079 * @param webhook_serial reference to the configured webhook template. 1080 * @param event_type is the event/action of the webhook 1081 * @param url to make request to 1082 * @param http_method use for the webhook 1083 * @param header_template of the webhook 1084 * @param body_template of the webhook 1085 */ 1086 static void 1087 trigger_webhook_cb (void *cls, 1088 uint64_t webhook_serial, 1089 const char *event_type, 1090 const char *url, 1091 const char *http_method, 1092 const char *header_template, 1093 const char *body_template) 1094 { 1095 struct Trigger *t = cls; 1096 void *header = NULL; 1097 void *body = NULL; 1098 size_t header_size; 1099 size_t body_size; 1100 1101 if (NULL != header_template) 1102 { 1103 int ret; 1104 1105 ret = TALER_TEMPLATING_fill (header_template, 1106 t->root, 1107 &header, 1108 &header_size); 1109 if (0 != ret) 1110 { 1111 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 1112 "Failed to expand webhook header template for webhook %llu (%d)\n", 1113 (unsigned long long) webhook_serial, 1114 ret); 1115 t->rv = GNUNET_DB_STATUS_HARD_ERROR; 1116 return; 1117 } 1118 /* Note: header is actually header_size+1 bytes long here, see mustach.c::memfile_close() */ 1119 GNUNET_assert ('\0' == ((const char *) header)[header_size]); 1120 } 1121 if (NULL != body_template) 1122 { 1123 int ret; 1124 ret = TALER_TEMPLATING_fill (body_template, 1125 t->root, 1126 &body, 1127 &body_size); 1128 if (0 != ret) 1129 { 1130 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 1131 "Failed to expand webhook body template for webhook %llu (%d)\n", 1132 (unsigned long long) webhook_serial, 1133 ret); 1134 t->rv = GNUNET_DB_STATUS_HARD_ERROR; 1135 return; 1136 } 1137 /* Note: body is actually body_size+1 bytes long here, see mustach.c::memfile_close() */ 1138 GNUNET_assert ('\0' == ((const char *) body)[body_size]); 1139 } 1140 t->rv = TMH_db->insert_pending_webhook (TMH_db->cls, 1141 t->instance, 1142 webhook_serial, 1143 url, 1144 http_method, 1145 header, 1146 body); 1147 if (t->rv > 0) 1148 { 1149 struct GNUNET_DB_EventHeaderP es = { 1150 .size = htons (sizeof(es)), 1151 .type = htons (TALER_DBEVENT_MERCHANT_WEBHOOK_PENDING) 1152 }; 1153 const void *extra = NULL; 1154 size_t extra_size = 0; 1155 TMH_db->event_notify (TMH_db->cls, 1156 &es, 1157 &extra, 1158 extra_size); 1159 } 1160 free (header); 1161 free (body); 1162 } 1163 1164 1165 /** 1166 * TMH_trigger_webhook is a function that need to be use when someone 1167 * pay. Merchant need to have a notification. 1168 * 1169 * @param instance that we need to send the webhook as a notification 1170 * @param event of the webhook 1171 * @param args argument of the function 1172 */ 1173 enum GNUNET_DB_QueryStatus 1174 TMH_trigger_webhook (const char *instance, 1175 const char *event, 1176 const json_t *args) 1177 { 1178 struct Trigger t = { 1179 .instance = instance, 1180 .root = args 1181 }; 1182 enum GNUNET_DB_QueryStatus qs; 1183 1184 qs = TMH_db->lookup_webhook_by_event (TMH_db->cls, 1185 instance, 1186 event, 1187 &trigger_webhook_cb, 1188 &t); 1189 if (qs < 0) 1190 return qs; 1191 return t.rv; 1192 } 1193 1194 1195 enum GNUNET_GenericReturnValue 1196 TMH_base_url_by_connection (struct MHD_Connection *connection, 1197 const char *instance, 1198 struct GNUNET_Buffer *buf) 1199 { 1200 const char *host; 1201 const char *forwarded_host; 1202 const char *forwarded_port; 1203 const char *uri_path; 1204 1205 memset (buf, 1206 0, 1207 sizeof (*buf)); 1208 if (NULL != TMH_base_url) 1209 { 1210 GNUNET_buffer_write_str (buf, 1211 TMH_base_url); 1212 } 1213 else 1214 { 1215 if (GNUNET_YES == 1216 TALER_mhd_is_https (connection)) 1217 GNUNET_buffer_write_str (buf, 1218 "https://"); 1219 else 1220 GNUNET_buffer_write_str (buf, 1221 "http://"); 1222 host = MHD_lookup_connection_value (connection, 1223 MHD_HEADER_KIND, 1224 MHD_HTTP_HEADER_HOST); 1225 forwarded_host = MHD_lookup_connection_value (connection, 1226 MHD_HEADER_KIND, 1227 "X-Forwarded-Host"); 1228 if (NULL != forwarded_host) 1229 { 1230 GNUNET_buffer_write_str (buf, 1231 forwarded_host); 1232 } 1233 else 1234 { 1235 if (NULL == host) 1236 { 1237 GNUNET_buffer_clear (buf); 1238 GNUNET_break (0); 1239 return GNUNET_SYSERR; 1240 } 1241 GNUNET_buffer_write_str (buf, 1242 host); 1243 } 1244 forwarded_port = MHD_lookup_connection_value (connection, 1245 MHD_HEADER_KIND, 1246 "X-Forwarded-Port"); 1247 if (NULL != forwarded_port) 1248 { 1249 GNUNET_buffer_write_str (buf, 1250 ":"); 1251 GNUNET_buffer_write_str (buf, 1252 forwarded_port); 1253 } 1254 uri_path = MHD_lookup_connection_value (connection, 1255 MHD_HEADER_KIND, 1256 "X-Forwarded-Prefix"); 1257 if (NULL != uri_path) 1258 GNUNET_buffer_write_path (buf, 1259 uri_path); 1260 } 1261 if (0 != strcmp (instance, 1262 "admin")) 1263 { 1264 GNUNET_buffer_write_path (buf, 1265 "/instances/"); 1266 GNUNET_buffer_write_str (buf, 1267 instance); 1268 } 1269 return GNUNET_OK; 1270 } 1271 1272 1273 enum GNUNET_GenericReturnValue 1274 TMH_taler_uri_by_connection (struct MHD_Connection *connection, 1275 const char *method, 1276 const char *instance, 1277 struct GNUNET_Buffer *buf) 1278 { 1279 const char *host; 1280 const char *forwarded_host; 1281 const char *forwarded_port; 1282 const char *uri_path; 1283 1284 memset (buf, 1285 0, 1286 sizeof (*buf)); 1287 host = MHD_lookup_connection_value (connection, 1288 MHD_HEADER_KIND, 1289 "Host"); 1290 forwarded_host = MHD_lookup_connection_value (connection, 1291 MHD_HEADER_KIND, 1292 "X-Forwarded-Host"); 1293 forwarded_port = MHD_lookup_connection_value (connection, 1294 MHD_HEADER_KIND, 1295 "X-Forwarded-Port"); 1296 uri_path = MHD_lookup_connection_value (connection, 1297 MHD_HEADER_KIND, 1298 "X-Forwarded-Prefix"); 1299 if (NULL != forwarded_host) 1300 host = forwarded_host; 1301 if (NULL == host) 1302 { 1303 GNUNET_break (0); 1304 return GNUNET_SYSERR; 1305 } 1306 GNUNET_buffer_write_str (buf, 1307 "taler"); 1308 if (GNUNET_NO == TALER_mhd_is_https (connection)) 1309 GNUNET_buffer_write_str (buf, 1310 "+http"); 1311 GNUNET_buffer_write_str (buf, 1312 "://"); 1313 GNUNET_buffer_write_str (buf, 1314 method); 1315 GNUNET_buffer_write_str (buf, 1316 "/"); 1317 GNUNET_buffer_write_str (buf, 1318 host); 1319 if (NULL != forwarded_port) 1320 { 1321 GNUNET_buffer_write_str (buf, 1322 ":"); 1323 GNUNET_buffer_write_str (buf, 1324 forwarded_port); 1325 } 1326 if (NULL != uri_path) 1327 GNUNET_buffer_write_path (buf, 1328 uri_path); 1329 if (0 != strcmp ("admin", 1330 instance)) 1331 { 1332 GNUNET_buffer_write_path (buf, 1333 "instances"); 1334 GNUNET_buffer_write_path (buf, 1335 instance); 1336 } 1337 return GNUNET_OK; 1338 } 1339 1340 1341 /** 1342 * Closure for #add_matching_account(). 1343 */ 1344 struct ExchangeMatchContext 1345 { 1346 /** 1347 * Wire method to match, NULL for all. 1348 */ 1349 const char *wire_method; 1350 1351 /** 1352 * Array of accounts to return. 1353 */ 1354 json_t *accounts; 1355 }; 1356 1357 1358 /** 1359 * If the given account is feasible, add it to the array 1360 * of accounts we return. 1361 * 1362 * @param cls a `struct PostReserveContext` 1363 * @param payto_uri URI of the account 1364 * @param conversion_url URL of a conversion service 1365 * @param debit_restrictions restrictions for debits from account 1366 * @param credit_restrictions restrictions for credits to account 1367 * @param master_sig signature affirming the account 1368 */ 1369 static void 1370 add_matching_account ( 1371 void *cls, 1372 struct TALER_FullPayto payto_uri, 1373 const char *conversion_url, 1374 const json_t *debit_restrictions, 1375 const json_t *credit_restrictions, 1376 const struct TALER_MasterSignatureP *master_sig) 1377 { 1378 struct ExchangeMatchContext *rc = cls; 1379 char *method; 1380 1381 method = TALER_payto_get_method (payto_uri.full_payto); 1382 if ( (NULL == rc->wire_method) || 1383 (0 == strcmp (method, 1384 rc->wire_method)) ) 1385 { 1386 json_t *acc; 1387 1388 acc = GNUNET_JSON_PACK ( 1389 TALER_JSON_pack_full_payto ("payto_uri", 1390 payto_uri), 1391 GNUNET_JSON_pack_data_auto ("master_sig", 1392 master_sig), 1393 GNUNET_JSON_pack_allow_null ( 1394 GNUNET_JSON_pack_string ("conversion_url", 1395 conversion_url)), 1396 GNUNET_JSON_pack_array_incref ("credit_restrictions", 1397 (json_t *) credit_restrictions), 1398 GNUNET_JSON_pack_array_incref ("debit_restrictions", 1399 (json_t *) debit_restrictions) 1400 ); 1401 GNUNET_assert (0 == 1402 json_array_append_new (rc->accounts, 1403 acc)); 1404 } 1405 GNUNET_free (method); 1406 } 1407 1408 1409 /** 1410 * Return JSON array with all of the exchange accounts 1411 * that support the given @a wire_method. 1412 * 1413 * @param master_pub master public key to match exchange by 1414 * @param wire_method NULL for any 1415 * @return JSON array with information about all matching accounts 1416 */ 1417 json_t * 1418 TMH_exchange_accounts_by_method ( 1419 const struct TALER_MasterPublicKeyP *master_pub, 1420 const char *wire_method) 1421 { 1422 struct ExchangeMatchContext emc = { 1423 .wire_method = wire_method, 1424 .accounts = json_array () 1425 }; 1426 enum GNUNET_DB_QueryStatus qs; 1427 1428 GNUNET_assert (NULL != emc.accounts); 1429 qs = TMH_db->select_accounts_by_exchange (TMH_db->cls, 1430 master_pub, 1431 &add_matching_account, 1432 &emc); 1433 if (qs < 0) 1434 { 1435 json_decref (emc.accounts); 1436 return NULL; 1437 } 1438 return emc.accounts; 1439 } 1440 1441 1442 char * 1443 TMH_make_order_status_url (struct MHD_Connection *con, 1444 const char *order_id, 1445 const char *session_id, 1446 const char *instance_id, 1447 struct TALER_ClaimTokenP *claim_token, 1448 struct TALER_PrivateContractHashP *h_contract) 1449 { 1450 struct GNUNET_Buffer buf; 1451 /* Number of query parameters written so far */ 1452 unsigned int num_qp = 0; 1453 1454 GNUNET_assert (NULL != instance_id); 1455 GNUNET_assert (NULL != order_id); 1456 if (GNUNET_OK != 1457 TMH_base_url_by_connection (con, 1458 instance_id, 1459 &buf)) 1460 { 1461 GNUNET_break (0); 1462 return NULL; 1463 } 1464 GNUNET_buffer_write_path (&buf, 1465 "/orders"); 1466 GNUNET_buffer_write_path (&buf, 1467 order_id); 1468 if ( (NULL != claim_token) && 1469 (! GNUNET_is_zero (claim_token)) ) 1470 { 1471 /* 'token=' for human readability */ 1472 GNUNET_buffer_write_str (&buf, 1473 "?token="); 1474 GNUNET_buffer_write_data_encoded (&buf, 1475 (char *) claim_token, 1476 sizeof (*claim_token)); 1477 num_qp++; 1478 } 1479 1480 if (NULL != session_id) 1481 { 1482 if (num_qp > 0) 1483 GNUNET_buffer_write_str (&buf, 1484 "&session_id="); 1485 else 1486 GNUNET_buffer_write_str (&buf, 1487 "?session_id="); 1488 GNUNET_buffer_write_str (&buf, 1489 session_id); 1490 num_qp++; 1491 } 1492 1493 if (NULL != h_contract) 1494 { 1495 if (num_qp > 0) 1496 GNUNET_buffer_write_str (&buf, 1497 "&h_contract="); 1498 else 1499 GNUNET_buffer_write_str (&buf, 1500 "?h_contract="); 1501 GNUNET_buffer_write_data_encoded (&buf, 1502 (char *) h_contract, 1503 sizeof (*h_contract)); 1504 } 1505 1506 return GNUNET_buffer_reap_str (&buf); 1507 } 1508 1509 1510 char * 1511 TMH_make_taler_pay_uri (struct MHD_Connection *con, 1512 const char *order_id, 1513 const char *session_id, 1514 const char *instance_id, 1515 struct TALER_ClaimTokenP *claim_token) 1516 { 1517 struct GNUNET_Buffer buf; 1518 1519 GNUNET_assert (NULL != instance_id); 1520 GNUNET_assert (NULL != order_id); 1521 if (GNUNET_OK != 1522 TMH_taler_uri_by_connection (con, 1523 "pay", 1524 instance_id, 1525 &buf)) 1526 { 1527 GNUNET_break (0); 1528 return NULL; 1529 } 1530 GNUNET_buffer_write_path (&buf, 1531 order_id); 1532 GNUNET_buffer_write_path (&buf, 1533 (NULL == session_id) 1534 ? "" 1535 : session_id); 1536 if ( (NULL != claim_token) && 1537 (! GNUNET_is_zero (claim_token))) 1538 { 1539 /* Just 'c=' because this goes into QR 1540 codes, so this is more compact. */ 1541 GNUNET_buffer_write_str (&buf, 1542 "?c="); 1543 GNUNET_buffer_write_data_encoded (&buf, 1544 (char *) claim_token, 1545 sizeof (struct TALER_ClaimTokenP)); 1546 } 1547 1548 return GNUNET_buffer_reap_str (&buf); 1549 }