/* This file is part of Anastasis Copyright (C) 2016--2021 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with Anastasis; see the file COPYING. If not, see */ /** * @file anastasis-helper-authorization-iban.c * @brief Process that watches for wire transfers to Anastasis bank account * @author Christian Grothoff */ #include "platform.h" #include "anastasis_eufin_lib.h" #include "anastasis_database_lib.h" #include "anastasis_util_lib.h" #include #include #include #include #include #include "iban.h" /** * How long to wait for an HTTP reply if there * are no transactions pending at the server? */ #define LONGPOLL_TIMEOUT GNUNET_TIME_UNIT_HOURS /** * How long to wait between HTTP requests? */ #define RETRY_TIMEOUT GNUNET_TIME_UNIT_MINUTES /** * Authentication data needed to access the account. */ static struct ANASTASIS_EUFIN_AuthenticationData auth; /** * Bank account payto://-URI this process is monitoring. */ static char *credit_account_uri; /** * Active request for history. */ static struct ANASTASIS_EUFIN_CreditHistoryHandle *hh; /** * Handle to the context for interacting with the bank. */ static struct GNUNET_CURL_Context *ctx; /** * What is the last row ID that we have already processed? */ static uint64_t latest_row_off; /** * Scheduler context for running the @e ctx. */ static struct GNUNET_CURL_RescheduleContext *rc; /** * The configuration (global) */ static const struct GNUNET_CONFIGURATION_Handle *cfg; /** * Our DB plugin. */ static struct ANASTASIS_DatabasePlugin *db_plugin; /** * How long should we sleep when idle before trying to find more work? * Useful in case bank does not support long polling. */ static struct GNUNET_TIME_Relative idle_sleep_interval; /** * Value to return from main(). 0 on success, non-zero on * on serious errors. */ static int global_ret; /** * Run in test-mode, do not background, only import currently * pending transactions. */ static int test_mode; /** * Current task waiting for execution, if any. */ static struct GNUNET_SCHEDULER_Task *task; /** * Notify anastasis-http that we received @a amount * from @a sender_account_uri with @a code. * * @param sender_account_uri payto:// URI of the sending account * @param code numeric code used in the wire transfer subject * @param amount the amount that was wired */ static void notify (const char *sender_account_uri, uint64_t code, const struct TALER_Amount *amount) { struct IbanEventP ev = { .header.type = htons (TALER_DBEVENT_ANASTASIS_AUTH_IBAN_TRANSFER), .header.size = htons (sizeof (ev)), .code = GNUNET_htonll (code) }; const char *as; GNUNET_CRYPTO_hash (sender_account_uri, strlen (sender_account_uri), &ev.debit_iban_hash); as = TALER_amount2s (amount); db_plugin->event_notify (db_plugin->cls, &ev.header, as, strlen (as)); } /** * We're being aborted with CTRL-C (or SIGTERM). Shut down. * * @param cls closure */ static void shutdown_task (void *cls) { (void) cls; if (NULL != hh) { ANASTASIS_EUFIN_credit_history_cancel (hh); hh = NULL; } if (NULL != ctx) { GNUNET_CURL_fini (ctx); ctx = NULL; } if (NULL != rc) { GNUNET_CURL_gnunet_rc_destroy (rc); rc = NULL; } if (NULL != task) { GNUNET_SCHEDULER_cancel (task); task = NULL; } ANASTASIS_DB_plugin_unload (db_plugin); db_plugin = NULL; ANASTASIS_EUFIN_auth_free (&auth); cfg = NULL; } /** * Query for incoming wire transfers. * * @param cls NULL */ static void find_transfers (void *cls); #include "iban.c" /** * Callbacks of this type are used to serve the result of asking * the bank for the transaction history. * * @param cls closure with the `struct WioreAccount *` we are processing * @param http_status HTTP status code from the server * @param ec taler error code * @param serial_id identification of the position at which we are querying * @param details details about the wire transfer * @return #GNUNET_OK to continue, #GNUNET_SYSERR to abort iteration */ static int history_cb (void *cls, unsigned int http_status, enum TALER_ErrorCode ec, uint64_t serial_id, const struct ANASTASIS_EUFIN_CreditDetails *details) { enum GNUNET_DB_QueryStatus qs; if (NULL == details) { hh = NULL; if (TALER_EC_NONE != ec) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Error fetching history: ec=%u, http_status=%u\n", (unsigned int) ec, http_status); } GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "End of list.\n"); GNUNET_assert (NULL == task); task = GNUNET_SCHEDULER_add_delayed (idle_sleep_interval, &find_transfers, NULL); return GNUNET_OK; /* will be ignored anyway */ } if (serial_id <= latest_row_off) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Serial ID %llu not monotonic (got %llu before). Failing!\n", (unsigned long long) serial_id, (unsigned long long) latest_row_off); GNUNET_SCHEDULER_shutdown (); hh = NULL; return GNUNET_SYSERR; } GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Adding wire transfer over %s with (hashed) subject `%s'\n", TALER_amount2s (&details->amount), details->wire_subject); qs = db_plugin->record_auth_iban_payment (db_plugin->cls, serial_id, details->wire_subject, &details->amount, details->debit_account_uri, details->credit_account_uri, details->execution_date); switch (qs) { case GNUNET_DB_STATUS_HARD_ERROR: GNUNET_break (0); GNUNET_SCHEDULER_shutdown (); hh = NULL; return GNUNET_SYSERR; case GNUNET_DB_STATUS_SOFT_ERROR: GNUNET_break (0); hh = NULL; return GNUNET_SYSERR; case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: /* already existed (!?), should be impossible */ GNUNET_break (0); hh = NULL; return GNUNET_SYSERR; case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: /* normal case */ break; } latest_row_off = serial_id; { uint64_t code; if (GNUNET_OK != extract_code (details->wire_subject, &code)) return GNUNET_OK; notify (details->debit_account_uri, code, &details->amount); } return GNUNET_OK; } /** * Query for incoming wire transfers. * * @param cls NULL */ static void find_transfers (void *cls) { (void) cls; task = NULL; GNUNET_assert (NULL == hh); hh = ANASTASIS_EUFIN_credit_history (ctx, &auth, latest_row_off, 1024, test_mode ? GNUNET_TIME_UNIT_ZERO : LONGPOLL_TIMEOUT, &history_cb, NULL); if (NULL == hh) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to start request for account history!\n"); global_ret = EXIT_FAILURE; GNUNET_SCHEDULER_shutdown (); return; } } /** * First task. * * @param cls closure, NULL * @param args remaining command-line arguments * @param cfgfile name of the configuration file used (for saving, can be NULL!) * @param c configuration */ static void run (void *cls, char *const *args, const char *cfgfile, const struct GNUNET_CONFIGURATION_Handle *c) { (void) cls; (void) args; (void) cfgfile; cfg = c; if (NULL == (db_plugin = ANASTASIS_DB_plugin_load (cfg))) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to initialize DB subsystem\n"); global_ret = EXIT_NOTCONFIGURED; return; } { char *iban; char *receiver_name; if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, "authorization-iban", "CREDIT_IBAN", &iban)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, "authorization-iban", "CREDIT_IBAN"); global_ret = EXIT_NOTCONFIGURED; ANASTASIS_DB_plugin_unload (db_plugin); db_plugin = NULL; return; } if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, "authorization-iban", "BUSINESS_NAME", &receiver_name)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, "authorization-iban", "BUSINESS_NAME"); global_ret = EXIT_NOTCONFIGURED; ANASTASIS_DB_plugin_unload (db_plugin); db_plugin = NULL; return; } { size_t len; char *uri_receiver_name; len = GNUNET_STRINGS_urlencode (receiver_name, strlen (receiver_name), &uri_receiver_name); GNUNET_assert (uri_receiver_name[len] == '\0'); GNUNET_asprintf (&credit_account_uri, "payto://iban/%s?receiver-name=%s", iban, uri_receiver_name); GNUNET_free (uri_receiver_name); } GNUNET_free (iban); GNUNET_free (receiver_name); } if (GNUNET_OK != ANASTASIS_EUFIN_auth_parse_cfg (cfg, "authorization-iban", &auth)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to load bank access configuration data\n"); ANASTASIS_DB_plugin_unload (db_plugin); db_plugin = NULL; global_ret = EXIT_NOTCONFIGURED; return; } { enum GNUNET_DB_QueryStatus qs; qs = db_plugin->get_last_auth_iban_payment_row (db_plugin->cls, credit_account_uri, &latest_row_off); if (qs < 0) { GNUNET_break (0); ANASTASIS_EUFIN_auth_free (&auth); ANASTASIS_DB_plugin_unload (db_plugin); db_plugin = NULL; return; } } GNUNET_SCHEDULER_add_shutdown (&shutdown_task, cls); ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule, &rc); rc = GNUNET_CURL_gnunet_rc_create (ctx); if (NULL == ctx) { GNUNET_break (0); return; } idle_sleep_interval = RETRY_TIMEOUT; task = GNUNET_SCHEDULER_add_now (&find_transfers, NULL); } /** * The main function of anastasis-helper-authorization-iban * * @param argc number of arguments from the command line * @param argv command line arguments * @return 0 ok, non-zero on error */ int main (int argc, char *const *argv) { struct GNUNET_GETOPT_CommandLineOption options[] = { GNUNET_GETOPT_option_flag ('t', "test", "run in test mode and exit when idle", &test_mode), GNUNET_GETOPT_OPTION_END }; enum GNUNET_GenericReturnValue ret; if (GNUNET_OK != GNUNET_STRINGS_get_utf8_args (argc, argv, &argc, &argv)) return EXIT_INVALIDARGUMENT; ANASTASIS_OS_init (); ret = GNUNET_PROGRAM_run ( argc, argv, "anastasis-helper-authorization-iban", gettext_noop ( "background process that watches for incoming wire transfers from customers"), options, &run, NULL); GNUNET_free_nz ((void *) argv); if (GNUNET_SYSERR == ret) return EXIT_INVALIDARGUMENT; if (GNUNET_NO == ret) return EXIT_SUCCESS; return global_ret; } /* end of anastasis-helper-authorization-iban.c */