taler-exchange-kyc-tester.c (51734B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2022, 2024 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it under the 6 terms of the GNU Affero 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 Affero General Public License for more details. 12 13 You should have received a copy of the GNU Affero General Public License along with 14 TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 /** 17 * @file taler-exchange-kyc-tester.c 18 * @brief tool to test KYC integrations 19 * @author Christian Grothoff 20 * @defgroup request Request handling routines 21 */ 22 #include "taler/platform.h" 23 #include <gnunet/gnunet_util_lib.h> 24 #include <jansson.h> 25 #include <microhttpd.h> 26 #include <sched.h> 27 #include <sys/resource.h> 28 #include <limits.h> 29 #include "taler/taler_mhd_lib.h" 30 #include "taler/taler_json_lib.h" 31 #include "taler/taler_templating_lib.h" 32 #include "taler/taler_util.h" 33 #include "taler/taler_kyclogic_lib.h" 34 #include "taler/taler_kyclogic_plugin.h" 35 #include <gnunet/gnunet_mhd_compat.h> 36 37 38 /** 39 * @brief Context in which the exchange is processing 40 * all requests 41 */ 42 struct TEKT_RequestContext 43 { 44 45 /** 46 * Opaque parsing context. 47 */ 48 void *opaque_post_parsing_context; 49 50 /** 51 * Request handler responsible for this request. 52 */ 53 const struct TEKT_RequestHandler *rh; 54 55 /** 56 * Request URL (for logging). 57 */ 58 const char *url; 59 60 /** 61 * Connection we are processing. 62 */ 63 struct MHD_Connection *connection; 64 65 /** 66 * HTTP response to return (or NULL). 67 */ 68 struct MHD_Response *response; 69 70 /** 71 * @e rh-specific cleanup routine. Function called 72 * upon completion of the request that should 73 * clean up @a rh_ctx. Can be NULL. 74 */ 75 void 76 (*rh_cleaner)(struct TEKT_RequestContext *rc); 77 78 /** 79 * @e rh-specific context. Place where the request 80 * handler can associate state with this request. 81 * Can be NULL. 82 */ 83 void *rh_ctx; 84 85 /** 86 * Uploaded JSON body, if any. 87 */ 88 json_t *root; 89 90 /** 91 * HTTP status to return upon resume if @e response 92 * is non-NULL. 93 */ 94 unsigned int http_status; 95 96 }; 97 98 99 /** 100 * @brief Struct describing an URL and the handler for it. 101 */ 102 struct TEKT_RequestHandler 103 { 104 105 /** 106 * URL the handler is for (first part only). 107 */ 108 const char *url; 109 110 /** 111 * Method the handler is for. 112 */ 113 const char *method; 114 115 /** 116 * Callbacks for handling of the request. Which one is used 117 * depends on @e method. 118 */ 119 union 120 { 121 /** 122 * Function to call to handle a GET requests (and those 123 * with @e method NULL). 124 * 125 * @param rc context for the request 126 * @param mime_type the @e mime_type for the reply (hint, can be NULL) 127 * @param args array of arguments, needs to be of length @e args_expected 128 * @return MHD result code 129 */ 130 MHD_RESULT 131 (*get)(struct TEKT_RequestContext *rc, 132 const char *const args[]); 133 134 135 /** 136 * Function to call to handle a POST request. 137 * 138 * @param rc context for the request 139 * @param json uploaded JSON data 140 * @param args array of arguments, needs to be of length @e args_expected 141 * @return MHD result code 142 */ 143 MHD_RESULT 144 (*post)(struct TEKT_RequestContext *rc, 145 const json_t *root, 146 const char *const args[]); 147 148 } handler; 149 150 /** 151 * Number of arguments this handler expects in the @a args array. 152 */ 153 unsigned int nargs; 154 155 /** 156 * Is the number of arguments given in @e nargs only an upper bound, 157 * and calling with fewer arguments could be OK? 158 */ 159 bool nargs_is_upper_bound; 160 161 /** 162 * Mime type to use in reply (hint, can be NULL). 163 */ 164 const char *mime_type; 165 166 /** 167 * Raw data for the @e handler, can be NULL for none provided. 168 */ 169 const void *data; 170 171 /** 172 * Number of bytes in @e data, 0 for data is 0-terminated (!). 173 */ 174 size_t data_size; 175 176 /** 177 * Default response code. 0 for none provided. 178 */ 179 unsigned int response_code; 180 }; 181 182 183 /** 184 * Information we track per ongoing kyc-proof request. 185 */ 186 struct ProofRequestState 187 { 188 /** 189 * Kept in a DLL. 190 */ 191 struct ProofRequestState *next; 192 193 /** 194 * Kept in a DLL. 195 */ 196 struct ProofRequestState *prev; 197 198 /** 199 * Handle for operation with the plugin. 200 */ 201 struct TALER_KYCLOGIC_ProofHandle *ph; 202 203 /** 204 * Logic plugin we are using. 205 */ 206 struct TALER_KYCLOGIC_Plugin *logic; 207 208 /** 209 * HTTP request details. 210 */ 211 struct TEKT_RequestContext *rc; 212 213 }; 214 215 /** 216 * Head of DLL. 217 */ 218 static struct ProofRequestState *rs_head; 219 220 /** 221 * Tail of DLL. 222 */ 223 static struct ProofRequestState *rs_tail; 224 225 /** 226 * The exchange's configuration (global) 227 */ 228 static const struct GNUNET_CONFIGURATION_Handle *TEKT_cfg; 229 230 /** 231 * Our base URL. 232 */ 233 static char *TEKT_base_url; 234 235 /** 236 * Payto set via command-line (or otherwise random). 237 */ 238 static struct TALER_NormalizedPaytoHashP cmd_line_h_payto; 239 240 /** 241 * Provider user ID to use. 242 */ 243 static char *cmd_provider_user_id; 244 245 /** 246 * Provider legitimization ID to use. 247 */ 248 static char *cmd_provider_legitimization_id; 249 250 /** 251 * Custom legitimization rule in JSON given as 252 * a string. 253 */ 254 static char *lrs_s; 255 256 /** 257 * Type of the operation that triggers legitimization. 258 */ 259 static char *operation_s; 260 261 /** 262 * Amount threshold crossed that triggers some rule. 263 */ 264 static struct TALER_Amount trigger_amount; 265 266 /** 267 * Row ID to use, override with '-r' 268 */ 269 static unsigned int kyc_row_id = 42; 270 271 /** 272 * -P command-line option. 273 */ 274 static int print_h_payto; 275 276 /** 277 * -W command-line option. 278 */ 279 static int cmd_line_is_wallet; 280 281 /** 282 * -w command-line option. 283 */ 284 static int run_webservice; 285 286 /** 287 * -M command-line option. 288 */ 289 static int list_measures; 290 291 /** 292 * Value to return from main() 293 */ 294 static int global_ret; 295 296 /** 297 * -m command-line flag. 298 */ 299 static char *measure; 300 301 /** 302 * Handle for ongoing initiation operation. 303 */ 304 static struct TALER_KYCLOGIC_InitiateHandle *ih; 305 306 /** 307 * KYC logic running for @e ih. 308 */ 309 static struct TALER_KYCLOGIC_Plugin *ih_logic; 310 311 /** 312 * True if we started any daemon. 313 */ 314 static bool have_daemons; uint16_t serve_port; 315 316 /** 317 * Context for all CURL operations (useful to the event loop) 318 */ 319 static struct GNUNET_CURL_Context *TEKT_curl_ctx; 320 321 /** 322 * Context for integrating #TEKT_curl_ctx with the 323 * GNUnet event loop. 324 */ 325 static struct GNUNET_CURL_RescheduleContext *exchange_curl_rc; 326 327 328 /** 329 * Context for the webhook. 330 */ 331 struct KycWebhookContext 332 { 333 334 /** 335 * Kept in a DLL while suspended. 336 */ 337 struct KycWebhookContext *next; 338 339 /** 340 * Kept in a DLL while suspended. 341 */ 342 struct KycWebhookContext *prev; 343 344 /** 345 * Details about the connection we are processing. 346 */ 347 struct TEKT_RequestContext *rc; 348 349 /** 350 * Plugin responsible for the webhook. 351 */ 352 struct TALER_KYCLOGIC_Plugin *plugin; 353 354 /** 355 * Configuration for the specific action. 356 */ 357 struct TALER_KYCLOGIC_ProviderDetails *pd; 358 359 /** 360 * Webhook activity. 361 */ 362 struct TALER_KYCLOGIC_WebhookHandle *wh; 363 364 /** 365 * HTTP response to return. 366 */ 367 struct MHD_Response *response; 368 369 /** 370 * Name of the configuration 371 * section defining the KYC logic. 372 */ 373 const char *section_name; 374 375 /** 376 * HTTP response code to return. 377 */ 378 unsigned int response_code; 379 380 /** 381 * #GNUNET_YES if we are suspended, 382 * #GNUNET_NO if not. 383 * #GNUNET_SYSERR if we had some error. 384 */ 385 enum GNUNET_GenericReturnValue suspended; 386 387 }; 388 389 390 /** 391 * Contexts are kept in a DLL while suspended. 392 */ 393 static struct KycWebhookContext *kwh_head; 394 395 /** 396 * Contexts are kept in a DLL while suspended. 397 */ 398 static struct KycWebhookContext *kwh_tail; 399 400 401 /** 402 * Resume processing the @a kwh request. 403 * 404 * @param kwh request to resume 405 */ 406 static void 407 kwh_resume (struct KycWebhookContext *kwh) 408 { 409 GNUNET_assert (GNUNET_YES == kwh->suspended); 410 kwh->suspended = GNUNET_NO; 411 GNUNET_CONTAINER_DLL_remove (kwh_head, 412 kwh_tail, 413 kwh); 414 MHD_resume_connection (kwh->rc->connection); 415 } 416 417 418 static void 419 kyc_webhook_cleanup (void) 420 { 421 struct KycWebhookContext *kwh; 422 423 while (NULL != (kwh = kwh_head)) 424 { 425 if (NULL != kwh->wh) 426 { 427 kwh->plugin->webhook_cancel (kwh->wh); 428 kwh->wh = NULL; 429 } 430 kwh_resume (kwh); 431 } 432 } 433 434 435 /** 436 * Function called with the result of a webhook 437 * operation. 438 * 439 * Note that the "decref" for the @a response 440 * will be done by the plugin. 441 * 442 * @param cls closure 443 * @param process_row legitimization process request the webhook was about 444 * @param account_id account the webhook was about 445 * @param is_wallet true if @a account_id is for a wallet 446 * @param provider_section configuration section of the logic 447 * @param provider_user_id set to user ID at the provider, or NULL if not supported or unknown 448 * @param provider_legitimization_id set to legitimization process ID at the provider, or NULL if not supported or unknown 449 * @param status KYC status 450 * @param expiration until when is the KYC check valid 451 * @param attributes user attributes returned by the provider 452 * @param http_status HTTP status code of @a response 453 * @param[in] response to return to the HTTP client 454 */ 455 static void 456 webhook_finished_cb ( 457 void *cls, 458 uint64_t process_row, 459 const struct TALER_NormalizedPaytoHashP *account_id, 460 bool is_wallet, 461 const char *provider_section, 462 const char *provider_user_id, 463 const char *provider_legitimization_id, 464 enum TALER_KYCLOGIC_KycStatus status, 465 struct GNUNET_TIME_Absolute expiration, 466 const json_t *attributes, 467 unsigned int http_status, 468 struct MHD_Response *response) 469 { 470 struct KycWebhookContext *kwh = cls; 471 472 (void) expiration; 473 (void) provider_section; 474 kwh->wh = NULL; 475 GNUNET_break (is_wallet); 476 if ( (NULL != account_id) && 477 (0 != GNUNET_memcmp (account_id, 478 &cmd_line_h_payto)) ) 479 { 480 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 481 "Received webhook for unexpected account\n"); 482 } 483 if ( (NULL != provider_user_id) && 484 (NULL != cmd_provider_user_id) && 485 (0 != strcmp (provider_user_id, 486 cmd_provider_user_id)) ) 487 { 488 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 489 "Received webhook for unexpected provider user ID (%s)\n", 490 provider_user_id); 491 } 492 if ( (NULL != provider_legitimization_id) && 493 (NULL != cmd_provider_legitimization_id) && 494 (0 != strcmp (provider_legitimization_id, 495 cmd_provider_legitimization_id)) ) 496 { 497 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 498 "Received webhook for unexpected provider legitimization ID (%s)\n", 499 provider_legitimization_id); 500 } 501 switch (status) 502 { 503 case TALER_KYCLOGIC_STATUS_SUCCESS: 504 /* _successfully_ resumed case */ 505 GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, 506 "KYC successful for user `%s' (legi: %s)\n", 507 provider_user_id, 508 provider_legitimization_id); 509 GNUNET_break (NULL != attributes); 510 fprintf (stderr, 511 "Extracted attributes:\n"); 512 json_dumpf (attributes, 513 stderr, 514 JSON_INDENT (2)); 515 break; 516 default: 517 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 518 "KYC status of %s/%s (process #%llu) is %d\n", 519 provider_user_id, 520 provider_legitimization_id, 521 (unsigned long long) process_row, 522 status); 523 break; 524 } 525 kwh->response = response; 526 kwh->response_code = http_status; 527 kwh_resume (kwh); 528 TALER_MHD_daemon_trigger (); 529 } 530 531 532 /** 533 * Function called to clean up a context. 534 * 535 * @param rc request context 536 */ 537 static void 538 clean_kwh (struct TEKT_RequestContext *rc) 539 { 540 struct KycWebhookContext *kwh = rc->rh_ctx; 541 542 if (NULL != kwh->wh) 543 { 544 kwh->plugin->webhook_cancel (kwh->wh); 545 kwh->wh = NULL; 546 } 547 if (NULL != kwh->response) 548 { 549 MHD_destroy_response (kwh->response); 550 kwh->response = NULL; 551 } 552 GNUNET_free (kwh); 553 } 554 555 556 /** 557 * Function the plugin can use to lookup an 558 * @a h_payto by @a provider_legitimization_id. 559 * 560 * @param cls closure, NULL 561 * @param provider_section 562 * @param provider_legitimization_id legi to look up 563 * @param[out] h_payto where to write the result 564 * @param[out] is_wallet set to true if @a h_payto is for a wallet 565 * @param[out] legi_row where to write the row ID for the legitimization ID 566 * @return database transaction status 567 */ 568 static enum GNUNET_DB_QueryStatus 569 kyc_provider_account_lookup ( 570 void *cls, 571 const char *provider_section, 572 const char *provider_legitimization_id, 573 struct TALER_NormalizedPaytoHashP *h_payto, 574 bool *is_wallet, 575 uint64_t *legi_row) 576 { 577 (void) cls; 578 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 579 "Simulated account lookup using `%s/%s'\n", 580 provider_section, 581 provider_legitimization_id); 582 *h_payto = cmd_line_h_payto; 583 *legi_row = kyc_row_id; 584 *is_wallet = (0 != cmd_line_is_wallet); 585 return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; 586 } 587 588 589 /** 590 * Handle a (GET or POST) "/kyc-webhook" request. 591 * 592 * @param rc request to handle 593 * @param method HTTP request method used by the client 594 * @param root uploaded JSON body (can be NULL) 595 * @param args one argument with the legitimization_uuid 596 * @return MHD result code 597 */ 598 static MHD_RESULT 599 handler_kyc_webhook_generic ( 600 struct TEKT_RequestContext *rc, 601 const char *method, 602 const json_t *root, 603 const char *const args[]) 604 { 605 struct KycWebhookContext *kwh = rc->rh_ctx; 606 607 if (NULL == kwh) 608 { /* first time */ 609 kwh = GNUNET_new (struct KycWebhookContext); 610 kwh->rc = rc; 611 rc->rh_ctx = kwh; 612 rc->rh_cleaner = &clean_kwh; 613 614 if ( (NULL == args[0]) || 615 (GNUNET_OK != 616 TALER_KYCLOGIC_lookup_logic (args[0], 617 &kwh->plugin, 618 &kwh->pd, 619 &kwh->section_name)) ) 620 { 621 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 622 "KYC logic `%s' unknown (check KYC provider configuration)\n", 623 args[0]); 624 return TALER_MHD_reply_with_error (rc->connection, 625 MHD_HTTP_NOT_FOUND, 626 TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_UNKNOWN, 627 args[0]); 628 } 629 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 630 "Calling KYC provider specific webhook\n"); 631 kwh->wh = kwh->plugin->webhook (kwh->plugin->cls, 632 kwh->pd, 633 &kyc_provider_account_lookup, 634 NULL, 635 method, 636 &args[1], 637 rc->connection, 638 root, 639 &webhook_finished_cb, 640 kwh); 641 if (NULL == kwh->wh) 642 { 643 GNUNET_break_op (0); 644 return TALER_MHD_reply_with_error (rc->connection, 645 MHD_HTTP_INTERNAL_SERVER_ERROR, 646 TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, 647 "failed to run webhook logic"); 648 } 649 kwh->suspended = GNUNET_YES; 650 GNUNET_CONTAINER_DLL_insert (kwh_head, 651 kwh_tail, 652 kwh); 653 MHD_suspend_connection (rc->connection); 654 return MHD_YES; 655 } 656 657 if (NULL != kwh->response) 658 { 659 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 660 "Returning queued reply for KWH\n"); 661 /* handle _failed_ resumed cases */ 662 return MHD_queue_response (rc->connection, 663 kwh->response_code, 664 kwh->response); 665 } 666 667 /* We resumed, but got no response? This should 668 not happen. */ 669 GNUNET_assert (0); 670 return TALER_MHD_reply_with_error (rc->connection, 671 MHD_HTTP_INTERNAL_SERVER_ERROR, 672 TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, 673 "resumed without response"); 674 } 675 676 677 /** 678 * Handle a GET "/kyc-webhook" request. 679 * 680 * @param rc request to handle 681 * @param args one argument with the legitimization_uuid 682 * @return MHD result code 683 */ 684 static MHD_RESULT 685 handler_kyc_webhook_get ( 686 struct TEKT_RequestContext *rc, 687 const char *const args[]) 688 { 689 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 690 "Webhook GET triggered\n"); 691 return handler_kyc_webhook_generic (rc, 692 MHD_HTTP_METHOD_GET, 693 NULL, 694 args); 695 } 696 697 698 /** 699 * Handle a POST "/kyc-webhook" request. 700 * 701 * @param rc request to handle 702 * @param root uploaded JSON body (can be NULL) 703 * @param args one argument with the legitimization_uuid 704 * @return MHD result code 705 */ 706 static MHD_RESULT 707 handler_kyc_webhook_post ( 708 struct TEKT_RequestContext *rc, 709 const json_t *root, 710 const char *const args[]) 711 { 712 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 713 "Webhook POST triggered\n"); 714 return handler_kyc_webhook_generic (rc, 715 MHD_HTTP_METHOD_POST, 716 root, 717 args); 718 } 719 720 721 /** 722 * Function called with the result of a proof check operation. 723 * 724 * Note that the "decref" for the @a response 725 * will be done by the callee and MUST NOT be done by the plugin. 726 * 727 * @param cls closure with the `struct ProofRequestState` 728 * @param status KYC status 729 * @param provider_name name of the KYC provider 730 * @param provider_user_id set to user ID at the provider, or NULL if not supported or unknown 731 * @param provider_legitimization_id set to legitimization process ID at the provider, or NULL if not supported or unknown 732 * @param expiration until when is the KYC check valid 733 * @param attributes attributes about the user 734 * @param http_status HTTP status code of @a response 735 * @param[in] response to return to the HTTP client 736 */ 737 static void 738 proof_cb ( 739 void *cls, 740 enum TALER_KYCLOGIC_KycStatus status, 741 const char *provider_name, 742 const char *provider_user_id, 743 const char *provider_legitimization_id, 744 struct GNUNET_TIME_Absolute expiration, 745 const json_t *attributes, 746 unsigned int http_status, 747 struct MHD_Response *response) 748 { 749 struct ProofRequestState *rs = cls; 750 751 (void) expiration; 752 (void) provider_name; 753 GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, 754 "KYC legitimization %s completed with status %d (%u) for %s\n", 755 provider_legitimization_id, 756 status, 757 http_status, 758 provider_user_id); 759 if (TALER_KYCLOGIC_STATUS_SUCCESS == status) 760 { 761 GNUNET_break (NULL != attributes); 762 fprintf (stderr, 763 "Extracted attributes:\n"); 764 json_dumpf (attributes, 765 stderr, 766 JSON_INDENT (2)); 767 } 768 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 769 "Returning response %p with status %u\n", 770 response, 771 http_status); 772 rs->rc->response = response; 773 rs->rc->http_status = http_status; 774 GNUNET_CONTAINER_DLL_remove (rs_head, 775 rs_tail, 776 rs); 777 MHD_resume_connection (rs->rc->connection); 778 TALER_MHD_daemon_trigger (); 779 GNUNET_free (rs); 780 } 781 782 783 /** 784 * Function called when we receive a 'GET' to the 785 * '/kyc-proof' endpoint. 786 * 787 * @param rc request context 788 * @param args remaining URL arguments; 789 * args[0] should be the logic plugin name 790 */ 791 static MHD_RESULT 792 handler_kyc_proof_get ( 793 struct TEKT_RequestContext *rc, 794 const char *const args[1]) 795 { 796 struct TALER_NormalizedPaytoHashP h_payto; 797 struct TALER_KYCLOGIC_ProviderDetails *pd; 798 struct TALER_KYCLOGIC_Plugin *logic; 799 struct ProofRequestState *rs; 800 const char *section_name; 801 const char *h_paytos; 802 803 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 804 "GET /kyc-proof triggered\n"); 805 if (NULL == args[0]) 806 { 807 GNUNET_break_op (0); 808 return TALER_MHD_reply_with_error (rc->connection, 809 MHD_HTTP_NOT_FOUND, 810 TALER_EC_GENERIC_ENDPOINT_UNKNOWN, 811 "'/kyc-proof/$PROVIDER_SECTION?state=$H_PAYTO' required"); 812 } 813 h_paytos = MHD_lookup_connection_value (rc->connection, 814 MHD_GET_ARGUMENT_KIND, 815 "state"); 816 if (NULL == h_paytos) 817 { 818 GNUNET_break_op (0); 819 return TALER_MHD_reply_with_error (rc->connection, 820 MHD_HTTP_BAD_REQUEST, 821 TALER_EC_GENERIC_PARAMETER_MISSING, 822 "h_payto"); 823 } 824 if (GNUNET_OK != 825 GNUNET_STRINGS_string_to_data (h_paytos, 826 strlen (h_paytos), 827 &h_payto, 828 sizeof (h_payto))) 829 { 830 GNUNET_break_op (0); 831 return TALER_MHD_reply_with_error (rc->connection, 832 MHD_HTTP_BAD_REQUEST, 833 TALER_EC_GENERIC_PARAMETER_MALFORMED, 834 "h_payto"); 835 } 836 if (0 != 837 GNUNET_memcmp (&h_payto, 838 &cmd_line_h_payto)) 839 { 840 GNUNET_break_op (0); 841 return TALER_MHD_reply_with_error (rc->connection, 842 MHD_HTTP_NOT_FOUND, 843 TALER_EC_EXCHANGE_KYC_PROOF_REQUEST_UNKNOWN, 844 "h_payto"); 845 } 846 847 if (GNUNET_OK != 848 TALER_KYCLOGIC_lookup_logic (args[0], 849 &logic, 850 &pd, 851 §ion_name)) 852 { 853 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 854 "Could not initiate KYC with provider `%s' (configuration error?)\n", 855 args[0]); 856 return TALER_MHD_reply_with_error (rc->connection, 857 MHD_HTTP_NOT_FOUND, 858 TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_UNKNOWN, 859 args[0]); 860 } 861 rs = GNUNET_new (struct ProofRequestState); 862 rs->rc = rc; 863 rs->logic = logic; 864 MHD_suspend_connection (rc->connection); 865 GNUNET_CONTAINER_DLL_insert (rs_head, 866 rs_tail, 867 rs); 868 rs->ph = logic->proof (logic->cls, 869 pd, 870 rc->connection, 871 &h_payto, 872 kyc_row_id, 873 cmd_provider_user_id, 874 cmd_provider_legitimization_id, 875 &proof_cb, 876 rs); 877 GNUNET_assert (NULL != rs->ph); 878 return MHD_YES; 879 } 880 881 882 /** 883 * Function called whenever MHD is done with a request. If the 884 * request was a POST, we may have stored a `struct Buffer *` in the 885 * @a con_cls that might still need to be cleaned up. Call the 886 * respective function to free the memory. 887 * 888 * @param cls client-defined closure 889 * @param connection connection handle 890 * @param con_cls value as set by the last call to 891 * the #MHD_AccessHandlerCallback 892 * @param toe reason for request termination 893 * @see #MHD_OPTION_NOTIFY_COMPLETED 894 * @ingroup request 895 */ 896 static void 897 handle_mhd_completion_callback (void *cls, 898 struct MHD_Connection *connection, 899 void **con_cls, 900 enum MHD_RequestTerminationCode toe) 901 { 902 struct TEKT_RequestContext *rc = *con_cls; 903 904 (void) cls; 905 if (NULL == rc) 906 return; 907 if (NULL != rc->rh_cleaner) 908 rc->rh_cleaner (rc); 909 { 910 #if MHD_VERSION >= 0x00097304 911 const union MHD_ConnectionInfo *ci; 912 unsigned int http_status = 0; 913 914 ci = MHD_get_connection_info (connection, 915 MHD_CONNECTION_INFO_HTTP_STATUS); 916 if (NULL != ci) 917 http_status = ci->http_status; 918 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 919 "Request for `%s' completed with HTTP status %u (%d)\n", 920 rc->url, 921 http_status, 922 toe); 923 #else 924 (void) connection; 925 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 926 "Request for `%s' completed (%d)\n", 927 rc->url, 928 toe); 929 #endif 930 } 931 932 TALER_MHD_parse_post_cleanup_callback (rc->opaque_post_parsing_context); 933 /* Sanity-check that we didn't leave any transactions hanging */ 934 if (NULL != rc->root) 935 json_decref (rc->root); 936 GNUNET_free (rc); 937 *con_cls = NULL; 938 } 939 940 941 /** 942 * We found a request handler responsible for handling a request. Parse the 943 * @a upload_data (if applicable) and the @a url and call the 944 * handler. 945 * 946 * @param rc request context 947 * @param url rest of the URL to parse 948 * @param upload_data upload data to parse (if available) 949 * @param[in,out] upload_data_size number of bytes in @a upload_data 950 * @return MHD result code 951 */ 952 static MHD_RESULT 953 proceed_with_handler (struct TEKT_RequestContext *rc, 954 const char *url, 955 const char *upload_data, 956 size_t *upload_data_size) 957 { 958 const struct TEKT_RequestHandler *rh = rc->rh; 959 const char *args[rh->nargs + 2]; 960 size_t ulen = strlen (url) + 1; 961 MHD_RESULT ret; 962 963 /* We do check for "ulen" here, because we'll later stack-allocate a buffer 964 of that size and don't want to enable malicious clients to cause us 965 huge stack allocations. */ 966 if (ulen > 512) 967 { 968 /* 512 is simply "big enough", as it is bigger than "6 * 54", 969 which is the longest URL format we ever get (for 970 /deposits/). The value should be adjusted if we ever define protocol 971 endpoints with plausibly longer inputs. */ 972 GNUNET_break_op (0); 973 return TALER_MHD_reply_with_error ( 974 rc->connection, 975 MHD_HTTP_URI_TOO_LONG, 976 TALER_EC_GENERIC_URI_TOO_LONG, 977 url); 978 } 979 980 /* All POST endpoints come with a body in JSON format. So we parse 981 the JSON here. */ 982 if ( (NULL == rc->root) && 983 (0 == strcasecmp (rh->method, 984 MHD_HTTP_METHOD_POST)) ) 985 { 986 enum GNUNET_GenericReturnValue res; 987 988 res = TALER_MHD_parse_post_json ( 989 rc->connection, 990 &rc->opaque_post_parsing_context, 991 upload_data, 992 upload_data_size, 993 &rc->root); 994 if (GNUNET_SYSERR == res) 995 { 996 GNUNET_assert (NULL == rc->root); 997 GNUNET_break (0); 998 return MHD_NO; /* bad upload, could not even generate error */ 999 } 1000 if ( (GNUNET_NO == res) || 1001 (NULL == rc->root) ) 1002 { 1003 GNUNET_assert (NULL == rc->root); 1004 return MHD_YES; /* so far incomplete upload or parser error */ 1005 } 1006 } 1007 1008 { 1009 char d[ulen]; 1010 unsigned int i; 1011 char *sp; 1012 1013 /* Parse command-line arguments */ 1014 /* make a copy of 'url' because 'strtok_r()' will modify */ 1015 GNUNET_memcpy (d, 1016 url, 1017 ulen); 1018 i = 0; 1019 args[i++] = strtok_r (d, "/", &sp); 1020 while ( (NULL != args[i - 1]) && 1021 (i <= rh->nargs + 1) ) 1022 args[i++] = strtok_r (NULL, "/", &sp); 1023 /* make sure above loop ran nicely until completion, and also 1024 that there is no excess data in 'd' afterwards */ 1025 if ( ( (rh->nargs_is_upper_bound) && 1026 (i - 1 > rh->nargs) ) || 1027 ( (! rh->nargs_is_upper_bound) && 1028 (i - 1 != rh->nargs) ) ) 1029 { 1030 char emsg[128 + 512]; 1031 1032 GNUNET_snprintf (emsg, 1033 sizeof (emsg), 1034 "Got %u+/%u segments for `%s' request (`%s')", 1035 i - 1, 1036 rh->nargs, 1037 rh->url, 1038 url); 1039 GNUNET_break_op (0); 1040 return TALER_MHD_reply_with_error ( 1041 rc->connection, 1042 MHD_HTTP_NOT_FOUND, 1043 TALER_EC_EXCHANGE_GENERIC_WRONG_NUMBER_OF_SEGMENTS, 1044 emsg); 1045 } 1046 GNUNET_assert (NULL == args[i - 1]); 1047 1048 /* Above logic ensures that 'root' is exactly non-NULL for POST operations, 1049 so we test for 'root' to decide which handler to invoke. */ 1050 if (NULL != rc->root) 1051 ret = rh->handler.post (rc, 1052 rc->root, 1053 args); 1054 else /* We also only have "POST" or "GET" in the API for at this point 1055 (OPTIONS/HEAD are taken care of earlier) */ 1056 ret = rh->handler.get (rc, 1057 args); 1058 } 1059 return ret; 1060 } 1061 1062 1063 static void 1064 rh_cleaner_cb (struct TEKT_RequestContext *rc) 1065 { 1066 if (NULL != rc->response) 1067 { 1068 MHD_destroy_response (rc->response); 1069 rc->response = NULL; 1070 } 1071 if (NULL != rc->root) 1072 { 1073 json_decref (rc->root); 1074 rc->root = NULL; 1075 } 1076 } 1077 1078 1079 /** 1080 * Handle incoming HTTP request. 1081 * 1082 * @param cls closure for MHD daemon (unused) 1083 * @param connection the connection 1084 * @param url the requested url 1085 * @param method the method (POST, GET, ...) 1086 * @param version HTTP version (ignored) 1087 * @param upload_data request data 1088 * @param upload_data_size size of @a upload_data in bytes 1089 * @param con_cls closure for request (a `struct TEKT_RequestContext *`) 1090 * @return MHD result code 1091 */ 1092 static MHD_RESULT 1093 handle_mhd_request (void *cls, 1094 struct MHD_Connection *connection, 1095 const char *url, 1096 const char *method, 1097 const char *version, 1098 const char *upload_data, 1099 size_t *upload_data_size, 1100 void **con_cls) 1101 { 1102 static struct TEKT_RequestHandler handlers[] = { 1103 /* simulated KYC endpoints */ 1104 { 1105 .url = "kyc-proof", 1106 .method = MHD_HTTP_METHOD_GET, 1107 .handler.get = &handler_kyc_proof_get, 1108 .nargs = 1 1109 }, 1110 { 1111 .url = "kyc-webhook", 1112 .method = MHD_HTTP_METHOD_POST, 1113 .handler.post = &handler_kyc_webhook_post, 1114 .nargs = 128, 1115 .nargs_is_upper_bound = true 1116 }, 1117 { 1118 .url = "kyc-webhook", 1119 .method = MHD_HTTP_METHOD_GET, 1120 .handler.get = &handler_kyc_webhook_get, 1121 .nargs = 128, 1122 .nargs_is_upper_bound = true 1123 }, 1124 /* mark end of list */ 1125 { 1126 .url = NULL 1127 } 1128 }; 1129 struct TEKT_RequestContext *rc = *con_cls; 1130 1131 (void) cls; 1132 (void) version; 1133 if (NULL == rc) 1134 { 1135 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1136 "Handling new request\n"); 1137 /* We're in a new async scope! */ 1138 rc = *con_cls = GNUNET_new (struct TEKT_RequestContext); 1139 rc->url = url; 1140 rc->connection = connection; 1141 rc->rh_cleaner = &rh_cleaner_cb; 1142 } 1143 if (NULL != rc->response) 1144 { 1145 return MHD_queue_response (rc->connection, 1146 rc->http_status, 1147 rc->response); 1148 } 1149 1150 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1151 "Handling request (%s) for URL '%s'\n", 1152 method, 1153 url); 1154 /* on repeated requests, check our cache first */ 1155 if (NULL != rc->rh) 1156 { 1157 const char *start; 1158 1159 if ('\0' == url[0]) 1160 /* strange, should start with '/', treat as just "/" */ 1161 url = "/"; 1162 start = strchr (url + 1, '/'); 1163 if (NULL == start) 1164 start = ""; 1165 return proceed_with_handler (rc, 1166 start, 1167 upload_data, 1168 upload_data_size); 1169 } 1170 if (0 == strcasecmp (method, 1171 MHD_HTTP_METHOD_HEAD)) 1172 method = MHD_HTTP_METHOD_GET; /* treat HEAD as GET here, MHD will do the rest */ 1173 1174 /* parse first part of URL */ 1175 { 1176 bool found = false; 1177 size_t tok_size; 1178 const char *tok; 1179 const char *rest; 1180 1181 if ('\0' == url[0]) 1182 /* strange, should start with '/', treat as just "/" */ 1183 url = "/"; 1184 tok = url + 1; 1185 rest = strchr (tok, '/'); 1186 if (NULL == rest) 1187 { 1188 tok_size = strlen (tok); 1189 } 1190 else 1191 { 1192 tok_size = rest - tok; 1193 rest++; /* skip over '/' */ 1194 } 1195 for (unsigned int i = 0; NULL != handlers[i].url; i++) 1196 { 1197 struct TEKT_RequestHandler *rh = &handlers[i]; 1198 1199 if ( (0 != strncmp (tok, 1200 rh->url, 1201 tok_size)) || 1202 (tok_size != strlen (rh->url) ) ) 1203 continue; 1204 found = true; 1205 /* The URL is a match! What we now do depends on the method. */ 1206 if (0 == strcasecmp (method, 1207 MHD_HTTP_METHOD_OPTIONS)) 1208 { 1209 return TALER_MHD_reply_cors_preflight (connection); 1210 } 1211 GNUNET_assert (NULL != rh->method); 1212 if (0 != strcasecmp (method, 1213 rh->method)) 1214 { 1215 found = true; 1216 continue; 1217 } 1218 /* cache to avoid the loop next time */ 1219 rc->rh = rh; 1220 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1221 "Handler found for %s '%s'\n", 1222 method, 1223 url); 1224 return MHD_YES; 1225 } 1226 1227 if (found) 1228 { 1229 /* we found a matching address, but the method is wrong */ 1230 struct MHD_Response *reply; 1231 MHD_RESULT ret; 1232 char *allowed = NULL; 1233 1234 GNUNET_break_op (0); 1235 for (unsigned int i = 0; NULL != handlers[i].url; i++) 1236 { 1237 struct TEKT_RequestHandler *rh = &handlers[i]; 1238 1239 if ( (0 != strncmp (tok, 1240 rh->url, 1241 tok_size)) || 1242 (tok_size != strlen (rh->url) ) ) 1243 continue; 1244 if (NULL == allowed) 1245 { 1246 allowed = GNUNET_strdup (rh->method); 1247 } 1248 else 1249 { 1250 char *tmp; 1251 1252 GNUNET_asprintf (&tmp, 1253 "%s, %s", 1254 allowed, 1255 rh->method); 1256 GNUNET_free (allowed); 1257 allowed = tmp; 1258 } 1259 if (0 == strcasecmp (rh->method, 1260 MHD_HTTP_METHOD_GET)) 1261 { 1262 char *tmp; 1263 1264 GNUNET_asprintf (&tmp, 1265 "%s, %s", 1266 allowed, 1267 MHD_HTTP_METHOD_HEAD); 1268 GNUNET_free (allowed); 1269 allowed = tmp; 1270 } 1271 } 1272 reply = TALER_MHD_make_error ( 1273 TALER_EC_GENERIC_METHOD_INVALID, 1274 method); 1275 GNUNET_break ( 1276 MHD_YES == 1277 MHD_add_response_header (reply, 1278 MHD_HTTP_HEADER_ALLOW, 1279 allowed)); 1280 GNUNET_free (allowed); 1281 ret = MHD_queue_response (connection, 1282 MHD_HTTP_METHOD_NOT_ALLOWED, 1283 reply); 1284 MHD_destroy_response (reply); 1285 return ret; 1286 } 1287 } 1288 1289 /* No handler matches, generate not found */ 1290 return TALER_MHD_reply_with_error (connection, 1291 MHD_HTTP_NOT_FOUND, 1292 TALER_EC_GENERIC_ENDPOINT_UNKNOWN, 1293 url); 1294 } 1295 1296 1297 /** 1298 * Load configuration parameters for the exchange 1299 * server into the corresponding global variables. 1300 * 1301 * @return #GNUNET_OK on success 1302 */ 1303 static enum GNUNET_GenericReturnValue 1304 exchange_serve_process_config (void) 1305 { 1306 if (GNUNET_OK != 1307 GNUNET_CONFIGURATION_get_value_string (TEKT_cfg, 1308 "exchange", 1309 "BASE_URL", 1310 &TEKT_base_url)) 1311 { 1312 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 1313 "exchange", 1314 "BASE_URL"); 1315 return GNUNET_SYSERR; 1316 } 1317 if (! TALER_url_valid_charset (TEKT_base_url)) 1318 { 1319 GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, 1320 "exchange", 1321 "BASE_URL", 1322 "invalid URL"); 1323 return GNUNET_SYSERR; 1324 } 1325 1326 return GNUNET_OK; 1327 } 1328 1329 1330 /** 1331 * Function run on shutdown. 1332 * 1333 * @param cls NULL 1334 */ 1335 static void 1336 do_shutdown (void *cls) 1337 { 1338 struct ProofRequestState *rs; 1339 1340 (void) cls; 1341 while (NULL != (rs = rs_head)) 1342 { 1343 GNUNET_CONTAINER_DLL_remove (rs_head, 1344 rs_tail, 1345 rs); 1346 rs->logic->proof_cancel (rs->ph); 1347 MHD_resume_connection (rs->rc->connection); 1348 GNUNET_free (rs); 1349 } 1350 if (NULL != ih) 1351 { 1352 ih_logic->initiate_cancel (ih); 1353 ih = NULL; 1354 } 1355 kyc_webhook_cleanup (); 1356 TALER_KYCLOGIC_kyc_done (); 1357 TALER_MHD_daemons_halt (); 1358 TALER_MHD_daemons_destroy (); 1359 if (NULL != TEKT_curl_ctx) 1360 { 1361 GNUNET_CURL_fini (TEKT_curl_ctx); 1362 TEKT_curl_ctx = NULL; 1363 } 1364 if (NULL != exchange_curl_rc) 1365 { 1366 GNUNET_CURL_gnunet_rc_destroy (exchange_curl_rc); 1367 exchange_curl_rc = NULL; 1368 } 1369 TALER_TEMPLATING_done (); 1370 } 1371 1372 1373 /** 1374 * Function called with the result of a KYC initiation 1375 * operation. 1376 * 1377 * @param cls closure 1378 * @param ec #TALER_EC_NONE on success 1379 * @param redirect_url set to where to redirect the user on success, NULL on failure 1380 * @param provider_user_id set to user ID at the provider, or NULL if not supported or unknown 1381 * @param provider_legitimization_id set to legitimization process ID at the provider, or NULL if not supported or unknown 1382 * @param error_msg_hint set to additional details to return to user, NULL on success 1383 */ 1384 static void 1385 initiate_cb ( 1386 void *cls, 1387 enum TALER_ErrorCode ec, 1388 const char *redirect_url, 1389 const char *provider_user_id, 1390 const char *provider_legitimization_id, 1391 const char *error_msg_hint) 1392 { 1393 (void) cls; 1394 ih = NULL; 1395 if (TALER_EC_NONE != ec) 1396 { 1397 fprintf (stderr, 1398 "Failed to start KYC process: %s (#%d)\n", 1399 error_msg_hint, 1400 ec); 1401 global_ret = EXIT_FAILURE; 1402 GNUNET_SCHEDULER_shutdown (); 1403 return; 1404 } 1405 { 1406 char *s; 1407 1408 s = GNUNET_STRINGS_data_to_string_alloc (&cmd_line_h_payto, 1409 sizeof (cmd_line_h_payto)); 1410 if (NULL != provider_user_id) 1411 { 1412 fprintf (stdout, 1413 "Visit `%s' to begin KYC process.\nAlso use: taler-exchange-kyc-tester -w -u '%s' -U '%s' -p %s\n", 1414 redirect_url, 1415 provider_user_id, 1416 provider_legitimization_id, 1417 s); 1418 } 1419 else 1420 { 1421 fprintf (stdout, 1422 "Visit `%s' to begin KYC process.\nAlso use: taler-exchange-kyc-tester -w -U '%s' -p %s\n", 1423 redirect_url, 1424 provider_legitimization_id, 1425 s); 1426 } 1427 GNUNET_free (s); 1428 } 1429 GNUNET_free (cmd_provider_user_id); 1430 GNUNET_free (cmd_provider_legitimization_id); 1431 if (NULL != provider_user_id) 1432 cmd_provider_user_id = GNUNET_strdup (provider_user_id); 1433 if (NULL != provider_legitimization_id) 1434 cmd_provider_legitimization_id = GNUNET_strdup (provider_legitimization_id); 1435 if (! run_webservice) 1436 GNUNET_SCHEDULER_shutdown (); 1437 } 1438 1439 1440 /** 1441 * Function called to iterate over KYC-relevant 1442 * transaction amounts for a particular time range. 1443 * Called within a database transaction, so must 1444 * not start a new one. 1445 * 1446 * @param cls closure, identifies the event type and 1447 * account to iterate over events for 1448 * @param limit maximum time-range for which events 1449 * should be fetched (timestamp in the past) 1450 * @param cb function to call on each event found, 1451 * events must be returned in reverse chronological 1452 * order 1453 * @param cb_cls closure for @a cb 1454 * @return transaction status 1455 */ 1456 static enum GNUNET_DB_QueryStatus 1457 amount_iterator ( 1458 void *cls, 1459 struct GNUNET_TIME_Absolute limit, 1460 TALER_EXCHANGEDB_KycAmountCallback cb, 1461 void *cb_cls) 1462 { 1463 const struct TALER_Amount *amount = cls; 1464 struct GNUNET_TIME_Absolute date; 1465 enum GNUNET_GenericReturnValue ret; 1466 1467 date = GNUNET_TIME_absolute_subtract (limit, 1468 GNUNET_TIME_UNIT_SECONDS); 1469 1470 ret = cb (cb_cls, 1471 amount, 1472 date); 1473 GNUNET_break (GNUNET_SYSERR != ret); 1474 if (GNUNET_OK != ret) 1475 return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; 1476 return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; 1477 } 1478 1479 1480 /** 1481 * Callback invoked on every listen socket to start the 1482 * respective MHD HTTP daemon. 1483 * 1484 * @param cls unused 1485 * @param lsock the listen socket 1486 */ 1487 static void 1488 start_daemon (void *cls, 1489 int lsock) 1490 { 1491 struct MHD_Daemon *mhd; 1492 1493 (void) cls; 1494 GNUNET_assert (-1 != lsock); 1495 mhd = MHD_start_daemon ( 1496 MHD_USE_SUSPEND_RESUME 1497 | MHD_USE_PIPE_FOR_SHUTDOWN 1498 | MHD_USE_DEBUG | MHD_USE_DUAL_STACK 1499 | MHD_USE_TCP_FASTOPEN, 1500 0, 1501 NULL, NULL, 1502 &handle_mhd_request, NULL, 1503 MHD_OPTION_LISTEN_SOCKET, 1504 lsock, 1505 MHD_OPTION_EXTERNAL_LOGGER, 1506 &TALER_MHD_handle_logs, 1507 NULL, 1508 MHD_OPTION_NOTIFY_COMPLETED, 1509 &handle_mhd_completion_callback, 1510 NULL, 1511 MHD_OPTION_END); 1512 if (NULL == mhd) 1513 { 1514 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1515 "Failed to launch HTTP service. Is the port in use?\n"); 1516 GNUNET_SCHEDULER_shutdown (); 1517 return; 1518 } 1519 have_daemons = true; 1520 TALER_MHD_daemon_start (mhd); 1521 } 1522 1523 1524 /** 1525 * Main function that will be run by the scheduler. 1526 * 1527 * @param cls closure 1528 * @param args remaining command-line arguments 1529 * @param cfgfile name of the configuration file used (for saving, can be 1530 * NULL!) 1531 * @param config configuration 1532 */ 1533 static void 1534 run (void *cls, 1535 char *const *args, 1536 const char *cfgfile, 1537 const struct GNUNET_CONFIGURATION_Handle *config) 1538 { 1539 enum TALER_KYCLOGIC_KycTriggerEvent event; 1540 struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs = NULL; 1541 const struct TALER_KYCLOGIC_KycRule *rule = NULL; 1542 1543 (void) cls; 1544 (void) args; 1545 (void ) cfgfile; 1546 if (GNUNET_OK != 1547 TALER_TEMPLATING_init (TALER_EXCHANGE_project_data ())) 1548 { 1549 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1550 "Could not load templates. Installation broken.\n"); 1551 global_ret = EXIT_FAILURE; 1552 return; 1553 } 1554 if (NULL != operation_s) 1555 { 1556 if (GNUNET_OK != 1557 TALER_KYCLOGIC_kyc_trigger_from_string (operation_s, 1558 &event)) 1559 { 1560 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1561 "Malformed operation type `%s'\n", 1562 operation_s); 1563 global_ret = EXIT_FAILURE; 1564 return; 1565 } 1566 } 1567 1568 if (NULL != lrs_s) 1569 { 1570 json_t *jlrs; 1571 json_error_t err; 1572 1573 jlrs = json_loads (lrs_s, 1574 JSON_REJECT_DUPLICATES, 1575 &err); 1576 if (NULL == jlrs) 1577 { 1578 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1579 "Malformed JSON for legitimization rule set: %s at %d\n", 1580 err.text, 1581 err.position); 1582 global_ret = EXIT_INVALIDARGUMENT; 1583 return; 1584 } 1585 lrs = TALER_KYCLOGIC_rules_parse (jlrs); 1586 json_decref (jlrs); 1587 if (NULL == lrs) 1588 { 1589 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1590 "Malformed legitimization rule set `%s'\n", 1591 lrs_s); 1592 global_ret = EXIT_INVALIDARGUMENT; 1593 return; 1594 } 1595 } 1596 1597 if (print_h_payto) 1598 { 1599 char *s; 1600 1601 s = GNUNET_STRINGS_data_to_string_alloc (&cmd_line_h_payto, 1602 sizeof (cmd_line_h_payto)); 1603 fprintf (stdout, 1604 "%s\n", 1605 s); 1606 GNUNET_free (s); 1607 } 1608 TALER_MHD_setup (TALER_MHD_GO_NONE); 1609 TEKT_cfg = config; 1610 GNUNET_SCHEDULER_add_shutdown (&do_shutdown, 1611 NULL); 1612 if (GNUNET_OK != 1613 TALER_KYCLOGIC_kyc_init (config, 1614 cfgfile)) 1615 { 1616 global_ret = EXIT_NOTCONFIGURED; 1617 GNUNET_SCHEDULER_shutdown (); 1618 return; 1619 } 1620 if (GNUNET_OK != 1621 exchange_serve_process_config ()) 1622 { 1623 global_ret = EXIT_NOTCONFIGURED; 1624 GNUNET_SCHEDULER_shutdown (); 1625 return; 1626 } 1627 global_ret = EXIT_SUCCESS; 1628 if (NULL != operation_s) 1629 { 1630 enum GNUNET_DB_QueryStatus qs; 1631 1632 if (GNUNET_OK == 1633 TALER_amount_is_valid (&trigger_amount)) 1634 { 1635 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1636 "Trigger amount command-line option (-t) required\n"); 1637 global_ret = EXIT_INVALIDARGUMENT; 1638 GNUNET_SCHEDULER_shutdown (); 1639 return; 1640 } 1641 { 1642 struct TALER_Amount next_threshold; 1643 1644 qs = TALER_KYCLOGIC_kyc_test_required ( 1645 event, 1646 lrs, 1647 &amount_iterator, 1648 &trigger_amount, 1649 &rule, 1650 &next_threshold); 1651 } 1652 switch (qs) 1653 { 1654 case GNUNET_DB_STATUS_HARD_ERROR: 1655 GNUNET_break (0); 1656 global_ret = EXIT_NOTCONFIGURED; 1657 GNUNET_SCHEDULER_shutdown (); 1658 return; 1659 case GNUNET_DB_STATUS_SOFT_ERROR: 1660 GNUNET_break (0); 1661 global_ret = EXIT_NOTCONFIGURED; 1662 GNUNET_SCHEDULER_shutdown (); 1663 return; 1664 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 1665 fprintf (stdout, 1666 "KYC not required for the given operation type and amount\n"); 1667 global_ret = EXIT_SUCCESS; 1668 GNUNET_SCHEDULER_shutdown (); 1669 return; 1670 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 1671 break; 1672 } 1673 } 1674 1675 if (NULL != rule) 1676 { 1677 struct TALER_KYCLOGIC_KycCheckContext kcc; 1678 1679 if (0 != list_measures) 1680 { 1681 // FIXME: print rule with possible measures! 1682 GNUNET_break (0); 1683 global_ret = EXIT_SUCCESS; 1684 GNUNET_SCHEDULER_shutdown (); 1685 return; 1686 } 1687 1688 if (GNUNET_OK != 1689 TALER_KYCLOGIC_requirements_to_check (lrs, 1690 rule, 1691 measure, 1692 &kcc)) 1693 { 1694 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1695 "Could not initiate KYC for measure `%s' (configuration error?)\n", 1696 measure); 1697 global_ret = EXIT_NOTCONFIGURED; 1698 GNUNET_SCHEDULER_shutdown (); 1699 return; 1700 } 1701 if (NULL == kcc.check) 1702 { 1703 GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, 1704 "SKIP check selected, nothing to do here\n"); 1705 global_ret = EXIT_SUCCESS; 1706 GNUNET_SCHEDULER_shutdown (); 1707 return; 1708 } 1709 switch (kcc.check->type) 1710 { 1711 case TALER_KYCLOGIC_CT_INFO: 1712 GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, 1713 "KYC information is `%s'\n", 1714 kcc.check->description); 1715 break; 1716 case TALER_KYCLOGIC_CT_FORM: 1717 GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, 1718 "Would initiate KYC check `%s' with form `%s'\n", 1719 kcc.check->check_name, 1720 kcc.check->details.form.name); 1721 break; 1722 case TALER_KYCLOGIC_CT_LINK: 1723 { 1724 struct TALER_KYCLOGIC_ProviderDetails *pd; 1725 const char *provider_name; 1726 1727 TALER_KYCLOGIC_provider_to_logic ( 1728 kcc.check->details.link.provider, 1729 &ih_logic, 1730 &pd, 1731 &provider_name); 1732 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1733 "Initiating KYC check `%s' at provider `%s'\n", 1734 kcc.check->check_name, 1735 provider_name); 1736 ih = ih_logic->initiate (ih_logic->cls, 1737 pd, 1738 &cmd_line_h_payto, 1739 kyc_row_id, 1740 NULL, /* FIXME: support passing context*/ 1741 &initiate_cb, 1742 NULL); 1743 GNUNET_break (NULL != ih); 1744 break; 1745 } 1746 } 1747 } 1748 if (run_webservice) 1749 { 1750 enum GNUNET_GenericReturnValue ret; 1751 1752 TEKT_curl_ctx 1753 = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule, 1754 &exchange_curl_rc); 1755 if (NULL == TEKT_curl_ctx) 1756 { 1757 GNUNET_break (0); 1758 global_ret = EXIT_FAILURE; 1759 GNUNET_SCHEDULER_shutdown (); 1760 return; 1761 } 1762 exchange_curl_rc = GNUNET_CURL_gnunet_rc_create (TEKT_curl_ctx); 1763 ret = TALER_MHD_listen_bind (TEKT_cfg, 1764 "exchange", 1765 &start_daemon, 1766 NULL); 1767 switch (ret) 1768 { 1769 case GNUNET_SYSERR: 1770 global_ret = EXIT_NOTCONFIGURED; 1771 GNUNET_SCHEDULER_shutdown (); 1772 return; 1773 case GNUNET_NO: 1774 if (! have_daemons) 1775 { 1776 global_ret = EXIT_NOTCONFIGURED; 1777 GNUNET_SCHEDULER_shutdown (); 1778 return; 1779 } 1780 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 1781 "Could not open all configured listen sockets\n"); 1782 break; 1783 case GNUNET_OK: 1784 break; 1785 } 1786 } 1787 } 1788 1789 1790 /** 1791 * The main function of the taler-exchange-kyc-tester, a tool for 1792 * testing KYC processes. 1793 * 1794 * @param argc number of arguments from the command line 1795 * @param argv command line arguments 1796 * @return 0 ok, non-zero on error 1797 */ 1798 int 1799 main (int argc, 1800 char *const *argv) 1801 { 1802 const struct GNUNET_GETOPT_CommandLineOption options[] = { 1803 GNUNET_GETOPT_option_help ( 1804 TALER_EXCHANGE_project_data (), 1805 "tool to test KYC provider integrations"), 1806 GNUNET_GETOPT_option_flag ( 1807 'M', 1808 "list-measures", 1809 "list available measures", 1810 &list_measures), 1811 GNUNET_GETOPT_option_string ( 1812 'm', 1813 "measure", 1814 "MEASURE_NAME", 1815 "initiate KYC check for the selected measure", 1816 &measure), 1817 GNUNET_GETOPT_option_string ( 1818 'o', 1819 "operation", 1820 "OPERATION_TYPE", 1821 "name of the operation that triggers legitimization (WITHDRAW, DEPOSIT, etc.)", 1822 &operation_s), 1823 GNUNET_GETOPT_option_flag ( 1824 'P', 1825 "print-payto-hash", 1826 "output the hash of the (normalized) payto://-URI", 1827 &print_h_payto), 1828 GNUNET_GETOPT_option_base32_fixed_size ( 1829 'p', 1830 "payto-hash", 1831 "HASH", 1832 "base32 encoding of the hash of a payto://-URI to use for the account (otherwise a random value will be used)", 1833 &cmd_line_h_payto, 1834 sizeof (cmd_line_h_payto)), 1835 GNUNET_GETOPT_option_string ( 1836 'R', 1837 "ruleset", 1838 "JSON", 1839 "use the given legitimization rule set (otherwise defaults from configuration are used)", 1840 &lrs_s), 1841 GNUNET_GETOPT_option_uint ( 1842 'r', 1843 "rowid", 1844 "NUMBER", 1845 "override row ID to use in simulation (default: 42)", 1846 &kyc_row_id), 1847 TALER_getopt_get_amount ( 1848 't', 1849 "trigger", 1850 "AMOUNT", 1851 "threshold crossed that would trigger some legitimization rule", 1852 &trigger_amount), 1853 GNUNET_GETOPT_option_string ( 1854 'U', 1855 "legitimization", 1856 "ID", 1857 "use the given provider legitimization ID (overridden if -i is also used)", 1858 &cmd_provider_legitimization_id), 1859 GNUNET_GETOPT_option_string ( 1860 'u', 1861 "user", 1862 "ID", 1863 "use the given provider user ID (overridden if -i is also used)", 1864 &cmd_provider_user_id), 1865 GNUNET_GETOPT_option_flag ( 1866 'w', 1867 "run-webservice", 1868 "run the integrated HTTP service", 1869 &run_webservice), 1870 GNUNET_GETOPT_option_flag ( 1871 'W', 1872 "wallet-payto", 1873 "simulate that the address the KYC process is about is a wallet", 1874 &cmd_line_is_wallet), 1875 GNUNET_GETOPT_OPTION_END 1876 }; 1877 enum GNUNET_GenericReturnValue ret; 1878 1879 GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, 1880 &cmd_line_h_payto, 1881 sizeof (cmd_line_h_payto)); 1882 ret = GNUNET_PROGRAM_run (TALER_EXCHANGE_project_data (), 1883 argc, argv, 1884 "taler-exchange-kyc-tester", 1885 "tool to test KYC provider integrations", 1886 options, 1887 &run, NULL); 1888 if (GNUNET_SYSERR == ret) 1889 return EXIT_INVALIDARGUMENT; 1890 if (GNUNET_NO == ret) 1891 return EXIT_SUCCESS; 1892 return global_ret; 1893 } 1894 1895 1896 /* end of taler-exchange-kyc-tester.c */