taler-exchange-wire-gateway-client.c (21661B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2017-2023 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it under the 6 terms of the GNU General Public License as published by the Free Software 7 Foundation; either version 3, or (at your option) any later version. 8 9 TALER is distributed in the hope that it will be useful, but WITHOUT ANY 10 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 11 A PARTICULAR PURPOSE. See the GNU General Public License for more details. 12 13 You should have received a copy of the GNU General Public License along with 14 TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 /** 17 * @file taler-exchange-wire-gateway-client.c 18 * @brief Execute wire transfer. 19 * @author Christian Grothoff 20 */ 21 #include "taler/platform.h" 22 #include <gnunet/gnunet_util_lib.h> 23 #include <gnunet/gnunet_json_lib.h> 24 #include <jansson.h> 25 #include <microhttpd.h> 26 #include "taler/taler_bank_service.h" 27 28 /** 29 * If set to #GNUNET_YES, then we'll ask the bank for a list 30 * of incoming transactions from the account. 31 */ 32 static int incoming_history; 33 34 /** 35 * If set to #GNUNET_YES, then we'll ask the bank for a list 36 * of outgoing transactions from the account. 37 */ 38 static int outgoing_history; 39 40 /** 41 * Amount to transfer. 42 */ 43 static struct TALER_Amount amount; 44 45 /** 46 * Credit account payto://-URI. 47 */ 48 static struct TALER_FullPayto credit_account; 49 50 /** 51 * Debit account payto://-URI. 52 */ 53 static struct TALER_FullPayto debit_account; 54 55 /** 56 * Wire transfer subject. 57 */ 58 static char *subject; 59 60 /** 61 * Which config section has the credentials to access the bank. 62 */ 63 static char *account_section; 64 65 /** 66 * Starting row. 67 */ 68 static unsigned long long start_row = UINT64_MAX; 69 70 /** 71 * Authentication data. 72 */ 73 static struct TALER_BANK_AuthenticationData auth; 74 75 /** 76 * Return value from main(). 77 */ 78 static int global_ret = 1; 79 80 /** 81 * Main execution context for the main loop. 82 */ 83 static struct GNUNET_CURL_Context *ctx; 84 85 /** 86 * Handle to ongoing credit history operation. 87 */ 88 static struct TALER_BANK_CreditHistoryHandle *chh; 89 90 /** 91 * Handle to ongoing debit history operation. 92 */ 93 static struct TALER_BANK_DebitHistoryHandle *dhh; 94 95 /** 96 * Handle for executing the wire transfer. 97 */ 98 static struct TALER_BANK_TransferHandle *eh; 99 100 /** 101 * Handle to access the exchange. 102 */ 103 static struct TALER_BANK_AdminAddIncomingHandle *op; 104 105 /** 106 * Context for running the CURL event loop. 107 */ 108 static struct GNUNET_CURL_RescheduleContext *rc; 109 110 111 /** 112 * Function run when the test terminates (good or bad). 113 * Cleans up our state. 114 * 115 * @param cls NULL 116 */ 117 static void 118 do_shutdown (void *cls) 119 { 120 (void) cls; 121 if (NULL != op) 122 { 123 TALER_BANK_admin_add_incoming_cancel (op); 124 op = NULL; 125 } 126 if (NULL != chh) 127 { 128 TALER_BANK_credit_history_cancel (chh); 129 chh = NULL; 130 } 131 if (NULL != dhh) 132 { 133 TALER_BANK_debit_history_cancel (dhh); 134 dhh = NULL; 135 } 136 if (NULL != eh) 137 { 138 TALER_BANK_transfer_cancel (eh); 139 eh = NULL; 140 } 141 if (NULL != ctx) 142 { 143 GNUNET_CURL_fini (ctx); 144 ctx = NULL; 145 } 146 if (NULL != rc) 147 { 148 GNUNET_CURL_gnunet_rc_destroy (rc); 149 rc = NULL; 150 } 151 TALER_BANK_auth_free (&auth); 152 } 153 154 155 /** 156 * Callback used to process the transaction 157 * history returned by the bank. 158 * 159 * @param cls closure 160 * @param reply response we got from the bank 161 */ 162 static void 163 credit_history_cb (void *cls, 164 const struct TALER_BANK_CreditHistoryResponse *reply) 165 { 166 (void) cls; 167 168 chh = NULL; 169 switch (reply->http_status) 170 { 171 case 0: 172 fprintf (stderr, 173 "Failed to obtain HTTP reply from `%s'\n", 174 auth.wire_gateway_url); 175 global_ret = 2; 176 break; 177 case MHD_HTTP_NO_CONTENT: 178 fprintf (stdout, 179 "No transactions.\n"); 180 global_ret = 0; 181 break; 182 case MHD_HTTP_OK: 183 for (unsigned int i = 0; i<reply->details.ok.details_length; i++) 184 { 185 const struct TALER_BANK_CreditDetails *cd = 186 &reply->details.ok.details[i]; 187 188 /* If credit/debit accounts were specified, use as a filter */ 189 if ( (NULL != credit_account.full_payto) && 190 (0 != TALER_full_payto_cmp (credit_account, 191 reply->details.ok.credit_account_uri) ) ) 192 continue; 193 if ( (NULL != debit_account.full_payto) && 194 (0 != TALER_full_payto_cmp (debit_account, 195 cd->debit_account_uri) ) ) 196 continue; 197 switch (cd->type) 198 { 199 case TALER_BANK_CT_RESERVE: 200 fprintf (stdout, 201 "%llu: %s->%s (%s) over %s at %s\n", 202 (unsigned long long) cd->serial_id, 203 cd->debit_account_uri.full_payto, 204 reply->details.ok.credit_account_uri.full_payto, 205 TALER_B2S (&cd->details.reserve.reserve_pub), 206 TALER_amount2s (&cd->amount), 207 GNUNET_TIME_timestamp2s (cd->execution_date)); 208 break; 209 case TALER_BANK_CT_KYCAUTH: 210 fprintf (stdout, 211 "%llu: %s->%s (KYC:%s) over %s at %s\n", 212 (unsigned long long) cd->serial_id, 213 cd->debit_account_uri.full_payto, 214 reply->details.ok.credit_account_uri.full_payto, 215 TALER_B2S (&cd->details.kycauth.account_pub), 216 TALER_amount2s (&cd->amount), 217 GNUNET_TIME_timestamp2s (cd->execution_date)); 218 break; 219 case TALER_BANK_CT_WAD: 220 GNUNET_break (0); // FIXME-#7271 (support wad payments) 221 break; 222 } 223 } 224 global_ret = 0; 225 break; 226 default: 227 fprintf (stderr, 228 "Failed to obtain credit history from `%s': HTTP status %u (%s)\n", 229 auth.wire_gateway_url, 230 reply->http_status, 231 TALER_ErrorCode_get_hint (reply->ec)); 232 if (NULL != reply->response) 233 json_dumpf (reply->response, 234 stderr, 235 JSON_INDENT (2)); 236 global_ret = 2; 237 break; 238 } 239 GNUNET_SCHEDULER_shutdown (); 240 } 241 242 243 /** 244 * Ask the bank the list of transactions for the bank account 245 * mentioned in the config section given by the user. 246 */ 247 static void 248 execute_credit_history (void) 249 { 250 if (NULL != subject) 251 { 252 fprintf (stderr, 253 "Specifying subject is not supported when inspecting credit history\n"); 254 GNUNET_SCHEDULER_shutdown (); 255 return; 256 } 257 chh = TALER_BANK_credit_history (ctx, 258 &auth, 259 start_row, 260 -10, 261 GNUNET_TIME_UNIT_ZERO, 262 &credit_history_cb, 263 NULL); 264 if (NULL == chh) 265 { 266 fprintf (stderr, 267 "Could not request the credit transaction history.\n"); 268 GNUNET_SCHEDULER_shutdown (); 269 return; 270 } 271 } 272 273 274 /** 275 * Function with the debit transaction history. 276 * 277 * @param cls closure 278 * @param reply response details 279 */ 280 static void 281 debit_history_cb (void *cls, 282 const struct TALER_BANK_DebitHistoryResponse *reply) 283 { 284 (void) cls; 285 286 dhh = NULL; 287 switch (reply->http_status) 288 { 289 case 0: 290 fprintf (stderr, 291 "Failed to obtain HTTP reply from `%s'\n", 292 auth.wire_gateway_url); 293 global_ret = 2; 294 break; 295 case MHD_HTTP_NO_CONTENT: 296 fprintf (stdout, 297 "No transactions.\n"); 298 global_ret = 0; 299 break; 300 case MHD_HTTP_OK: 301 for (unsigned int i = 0; i<reply->details.ok.details_length; i++) 302 { 303 const struct TALER_BANK_DebitDetails *dd = 304 &reply->details.ok.details[i]; 305 306 /* If credit/debit accounts were specified, use as a filter */ 307 if ( (NULL != credit_account.full_payto) && 308 (0 != TALER_full_payto_cmp (credit_account, 309 dd->credit_account_uri) ) ) 310 continue; 311 if ( (NULL != debit_account.full_payto) && 312 (0 != TALER_full_payto_cmp (debit_account, 313 reply->details.ok.debit_account_uri) ) ) 314 continue; 315 fprintf (stdout, 316 "%llu: %s->%s (%s) over %s at %s\n", 317 (unsigned long long) dd->serial_id, 318 reply->details.ok.debit_account_uri.full_payto, 319 dd->credit_account_uri.full_payto, 320 TALER_B2S (&dd->wtid), 321 TALER_amount2s (&dd->amount), 322 GNUNET_TIME_timestamp2s (dd->execution_date)); 323 } 324 global_ret = 0; 325 break; 326 default: 327 fprintf (stderr, 328 "Failed to obtain debit history from `%s': HTTP status %u (%s)\n", 329 auth.wire_gateway_url, 330 reply->http_status, 331 TALER_ErrorCode_get_hint (reply->ec)); 332 if (NULL != reply->response) 333 json_dumpf (reply->response, 334 stderr, 335 JSON_INDENT (2)); 336 global_ret = 2; 337 break; 338 } 339 GNUNET_SCHEDULER_shutdown (); 340 } 341 342 343 /** 344 * Ask the bank the list of transactions for the bank account 345 * mentioned in the config section given by the user. 346 */ 347 static void 348 execute_debit_history (void) 349 { 350 if (NULL != subject) 351 { 352 fprintf (stderr, 353 "Specifying subject is not supported when inspecting debit history\n"); 354 GNUNET_SCHEDULER_shutdown (); 355 return; 356 } 357 dhh = TALER_BANK_debit_history (ctx, 358 &auth, 359 start_row, 360 -10, 361 GNUNET_TIME_UNIT_ZERO, 362 &debit_history_cb, 363 NULL); 364 if (NULL == dhh) 365 { 366 fprintf (stderr, 367 "Could not request the debit transaction history.\n"); 368 GNUNET_SCHEDULER_shutdown (); 369 return; 370 } 371 } 372 373 374 /** 375 * Callback that processes the outcome of a wire transfer 376 * execution. 377 * 378 * @param cls closure 379 * @param tr response details 380 */ 381 static void 382 confirmation_cb (void *cls, 383 const struct TALER_BANK_TransferResponse *tr) 384 { 385 (void) cls; 386 eh = NULL; 387 if (MHD_HTTP_OK != tr->http_status) 388 { 389 fprintf (stderr, 390 "The wire transfer didn't execute correctly (%u/%d).\n", 391 tr->http_status, 392 tr->ec); 393 GNUNET_SCHEDULER_shutdown (); 394 return; 395 } 396 397 fprintf (stdout, 398 "Wire transfer #%llu executed successfully at %s.\n", 399 (unsigned long long) tr->details.ok.row_id, 400 GNUNET_TIME_timestamp2s (tr->details.ok.timestamp)); 401 global_ret = 0; 402 GNUNET_SCHEDULER_shutdown (); 403 } 404 405 406 /** 407 * Ask the bank to execute a wire transfer. 408 */ 409 static void 410 execute_wire_transfer (void) 411 { 412 struct TALER_WireTransferIdentifierRawP wtid; 413 void *buf; 414 size_t buf_size; 415 char *params; 416 417 if (NULL != debit_account.full_payto) 418 { 419 fprintf (stderr, 420 "Invalid option -C specified, conflicts with -D\n"); 421 GNUNET_SCHEDULER_shutdown (); 422 return; 423 } 424 425 /* See if subject was given as a payto-parameter. */ 426 if (NULL == subject) 427 subject = TALER_payto_get_subject (credit_account); 428 if (NULL != subject) 429 { 430 if (GNUNET_OK != 431 GNUNET_STRINGS_string_to_data (subject, 432 strlen (subject), 433 &wtid, 434 sizeof (wtid))) 435 { 436 fprintf (stderr, 437 "Error: wire transfer subject must be a WTID\n"); 438 GNUNET_SCHEDULER_shutdown (); 439 return; 440 } 441 } 442 else 443 { 444 /* pick one at random */ 445 GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, 446 &wtid, 447 sizeof (wtid)); 448 } 449 params = strchr (credit_account.full_payto, 450 (unsigned char) '&'); 451 if (NULL != params) 452 *params = '\0'; 453 TALER_BANK_prepare_transfer (credit_account, 454 &amount, 455 "http://exchange.example.com/", 456 &wtid, 457 &buf, 458 &buf_size); 459 eh = TALER_BANK_transfer (ctx, 460 &auth, 461 buf, 462 buf_size, 463 &confirmation_cb, 464 NULL); 465 GNUNET_free (buf); 466 if (NULL == eh) 467 { 468 fprintf (stderr, 469 "Could not execute the wire transfer\n"); 470 GNUNET_SCHEDULER_shutdown (); 471 return; 472 } 473 } 474 475 476 /** 477 * Function called with the result of the operation. 478 * 479 * @param cls closure 480 * @param air response details 481 */ 482 static void 483 res_cb (void *cls, 484 const struct TALER_BANK_AdminAddIncomingResponse *air) 485 { 486 (void) cls; 487 op = NULL; 488 switch (air->http_status) 489 { 490 case MHD_HTTP_OK: 491 global_ret = 0; 492 fprintf (stdout, 493 "%llu\n", 494 (unsigned long long) air->details.ok.serial_id); 495 break; 496 default: 497 fprintf (stderr, 498 "Operation failed with status code %u/%u\n", 499 (unsigned int) air->ec, 500 air->http_status); 501 if (NULL != air->response) 502 json_dumpf (air->response, 503 stderr, 504 JSON_INDENT (2)); 505 break; 506 } 507 GNUNET_SCHEDULER_shutdown (); 508 } 509 510 511 /** 512 * Ask the bank to execute a wire transfer to the exchange. 513 */ 514 static void 515 execute_admin_transfer (void) 516 { 517 struct TALER_ReservePublicKeyP reserve_pub; 518 519 if (NULL != subject) 520 { 521 if (GNUNET_OK != 522 GNUNET_STRINGS_string_to_data (subject, 523 strlen (subject), 524 &reserve_pub, 525 sizeof (reserve_pub))) 526 { 527 fprintf (stderr, 528 "Error: wire transfer subject must be a reserve public key\n"); 529 return; 530 } 531 } 532 else 533 { 534 /* pick one that is kind-of well-formed at random */ 535 GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, 536 &reserve_pub, 537 sizeof (reserve_pub)); 538 } 539 op = TALER_BANK_admin_add_incoming (ctx, 540 &auth, 541 &reserve_pub, 542 &amount, 543 debit_account, 544 &res_cb, 545 NULL); 546 if (NULL == op) 547 { 548 fprintf (stderr, 549 "Could not execute the wire transfer to the exchange\n"); 550 GNUNET_SCHEDULER_shutdown (); 551 return; 552 } 553 } 554 555 556 /** 557 * Main function that will be run. 558 * 559 * @param cls closure 560 * @param args remaining command-line arguments 561 * @param cfgfile name of the configuration file used (for saving, can be NULL!) 562 * @param cfg configuration 563 */ 564 static void 565 run (void *cls, 566 char *const *args, 567 const char *cfgfile, 568 const struct GNUNET_CONFIGURATION_Handle *cfg) 569 { 570 (void) cls; 571 (void) args; 572 (void) cfgfile; 573 (void) cfg; 574 575 GNUNET_SCHEDULER_add_shutdown (&do_shutdown, 576 NULL); 577 ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule, 578 &rc); 579 GNUNET_assert (NULL != ctx); 580 rc = GNUNET_CURL_gnunet_rc_create (ctx); 581 if (NULL != account_section) 582 { 583 if (0 != strncasecmp ("exchange-accountcredentials-", 584 account_section, 585 strlen ("exchange-accountcredentials-"))) 586 { 587 fprintf (stderr, 588 "Error: invalid section specified, must begin with `%s`\n", 589 "exchange-accountcredentials-"); 590 GNUNET_SCHEDULER_shutdown (); 591 return; 592 } 593 if ( (NULL != auth.wire_gateway_url) || 594 (NULL != auth.details.basic.username) || 595 (NULL != auth.details.basic.password) ) 596 { 597 fprintf (stderr, 598 "Error: Conflicting authentication options provided. Please only use one method.\n"); 599 GNUNET_SCHEDULER_shutdown (); 600 return; 601 } 602 if (GNUNET_OK != 603 TALER_BANK_auth_parse_cfg (cfg, 604 account_section, 605 &auth)) 606 { 607 fprintf (stderr, 608 "Error: Authentication information not found in configuration section `%s'\n", 609 account_section); 610 GNUNET_SCHEDULER_shutdown (); 611 return; 612 } 613 } 614 else 615 { 616 if ( (NULL != auth.wire_gateway_url) && 617 (NULL != auth.details.basic.username) && 618 (NULL != auth.details.basic.password) ) 619 { 620 auth.method = TALER_BANK_AUTH_BASIC; 621 } 622 else if ( (NULL != auth.wire_gateway_url) && 623 (NULL != auth.details.bearer.token) ) 624 { 625 auth.method = TALER_BANK_AUTH_BEARER; 626 } 627 628 else if (NULL == auth.wire_gateway_url) 629 { 630 fprintf (stderr, 631 "Error: No account specified (use -b or -s options).\n"); 632 GNUNET_SCHEDULER_shutdown (); 633 return; 634 } 635 } 636 if ( (NULL == auth.wire_gateway_url) || 637 (0 == strlen (auth.wire_gateway_url)) || 638 (0 != strncasecmp ("http", 639 auth.wire_gateway_url, 640 strlen ("http"))) ) 641 { 642 fprintf (stderr, 643 "Error: Invalid wire gateway URL `%s' configured.\n", 644 auth.wire_gateway_url); 645 GNUNET_SCHEDULER_shutdown (); 646 return; 647 } 648 if ( (GNUNET_YES == incoming_history) && 649 (GNUNET_YES == outgoing_history) ) 650 { 651 fprintf (stderr, 652 "Error: Please specify only -i or -o, but not both.\n"); 653 GNUNET_SCHEDULER_shutdown (); 654 return; 655 } 656 if (GNUNET_YES == incoming_history) 657 { 658 execute_credit_history (); 659 return; 660 } 661 if (GNUNET_YES == outgoing_history) 662 { 663 execute_debit_history (); 664 return; 665 } 666 if (NULL != credit_account.full_payto) 667 { 668 execute_wire_transfer (); 669 return; 670 } 671 if (NULL != debit_account.full_payto) 672 { 673 execute_admin_transfer (); 674 return; 675 } 676 677 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 678 "No operation specified.\n"); 679 global_ret = 0; 680 GNUNET_SCHEDULER_shutdown (); 681 } 682 683 684 /** 685 * The main function of the taler-exchange-wire-gateway-client 686 * 687 * @param argc number of arguments from the command line 688 * @param argv command line arguments 689 * @return 0 ok, 1 on error 690 */ 691 int 692 main (int argc, 693 char *const *argv) 694 { 695 const struct GNUNET_GETOPT_CommandLineOption options[] = { 696 TALER_getopt_get_amount ('a', 697 "amount", 698 "VALUE", 699 "value to transfer", 700 &amount), 701 GNUNET_GETOPT_option_string ('b', 702 "bank", 703 "URL", 704 "Wire gateway URL to use to talk to the bank", 705 &auth.wire_gateway_url), 706 GNUNET_GETOPT_option_string ('C', 707 "credit", 708 "ACCOUNT", 709 "payto URI of the bank account to credit (when making outgoing transfers)", 710 &credit_account.full_payto), 711 GNUNET_GETOPT_option_string ('D', 712 "debit", 713 "PAYTO-URL", 714 "payto URI of the bank account to debit (when making incoming transfers)", 715 &debit_account.full_payto), 716 GNUNET_GETOPT_option_flag ('i', 717 "credit-history", 718 "Ask to get a list of 10 incoming transactions.", 719 &incoming_history), 720 GNUNET_GETOPT_option_flag ('o', 721 "debit-history", 722 "Ask to get a list of 10 outgoing transactions.", 723 &outgoing_history), 724 GNUNET_GETOPT_option_string ('p', 725 "pass", 726 "PASSPHRASE", 727 "passphrase to use for authentication", 728 &auth.details.basic.password), 729 GNUNET_GETOPT_option_string ('s', 730 "section", 731 "ACCOUNT-SECTION", 732 "Which config section has the credentials to access the bank. Conflicts with -b -u and -p options.\n", 733 &account_section), 734 GNUNET_GETOPT_option_string ('S', 735 "subject", 736 "SUBJECT", 737 "specifies the wire transfer subject", 738 &subject), 739 GNUNET_GETOPT_option_string ('u', 740 "user", 741 "USERNAME", 742 "username to use for authentication", 743 &auth.details.basic.username), 744 GNUNET_GETOPT_option_ulong ('w', 745 "since-when", 746 "ROW", 747 "When asking the bank for transactions history, this option commands that all the results should have IDs settled after SW. If not given, then the 10 youngest transactions are returned.", 748 &start_row), 749 GNUNET_GETOPT_OPTION_END 750 }; 751 enum GNUNET_GenericReturnValue ret; 752 753 global_ret = 1; 754 ret = GNUNET_PROGRAM_run ( 755 TALER_EXCHANGE_project_data (), 756 argc, argv, 757 "taler-wire-gateway-client", 758 gettext_noop ("Client tool of the Taler Wire Gateway"), 759 options, 760 &run, NULL); 761 if (GNUNET_SYSERR == ret) 762 return 3; 763 if (GNUNET_NO == ret) 764 return 0; 765 return global_ret; 766 } 767 768 769 /* end taler-wire-gateway-client.c */