/*
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
*
* TODO:
* - blocked on #6992
* - needs XXX_bank_service to access new facade once #6992 is implemented
* - needs to load authentication information
* - needs to load 'last known' transaction from DB
*/
#include "platform.h"
#include
#include
#include
#include
#include
#include
#include "anastasis_db_lib.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
/**
* Authentication data needed to access the account.
*/
static struct BANK_AccountInfo *auth;
/**
* Active request for history.
*/
static struct BANK_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;
/**
* Current task waiting for execution, if any.
*/
static struct GNUNET_SCHEDULER_Task *task;
/**
* 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)
{
BANK_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;
cfg = NULL;
}
/**
* Query for incoming wire transfers.
*
* @param cls NULL
*/
static void
find_transfers (void *cls);
/**
* 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 BANK_CreditDetails *details)
{
enum GNUNET_DB_QueryStatus qs;
(void) json;
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_at (delayed_until,
&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_time);
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;
return GNUNET_OK;
}
/**
* Query for incoming wire transfers.
*
* @param cls NULL
*/
static void
find_transfers (void *cls)
{
enum GNUNET_DB_QueryStatus qs;
(void) cls;
task = NULL;
GNUNET_assert (NULL == hh);
hh = BANK_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;
}
// FIXME: initialize 'auth' from cfg!
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;
}
latest_row_off = FIXME; // need new DB function!
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 */