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