anastasis

Credential backup and recovery protocol and service
Log | Files | Refs | Submodules | README | LICENSE

anastasis-helper-authorization-iban.c (13038B)


      1 /*
      2   This file is part of Anastasis
      3   Copyright (C) 2016--2021 Anastasis SARL
      4 
      5   Anastasis 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   Anastasis 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   Anastasis; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 /**
     17  * @file anastasis-helper-authorization-iban.c
     18  * @brief Process that watches for wire transfers to Anastasis bank account
     19  * @author Christian Grothoff
     20  */
     21 #include "platform.h"
     22 #include "anastasis_eufin_lib.h"
     23 #include "anastasis_database_lib.h"
     24 #include "anastasis_util_lib.h"
     25 #include <taler/taler_json_lib.h>
     26 #include <gnunet/gnunet_util_lib.h>
     27 #include <jansson.h>
     28 #include <pthread.h>
     29 #include <microhttpd.h>
     30 #include "iban.h"
     31 
     32 /**
     33  * How long to wait for an HTTP reply if there
     34  * are no transactions pending at the server?
     35  */
     36 #define LONGPOLL_TIMEOUT GNUNET_TIME_UNIT_HOURS
     37 
     38 /**
     39  * How long to wait between HTTP requests?
     40  */
     41 #define RETRY_TIMEOUT GNUNET_TIME_UNIT_MINUTES
     42 
     43 /**
     44  * Authentication data needed to access the account.
     45  */
     46 static struct ANASTASIS_EUFIN_AuthenticationData auth;
     47 
     48 /**
     49  * Bank account IBAN this process is monitoring.
     50  */
     51 static char *authorization_iban;
     52 
     53 /**
     54  * Active request for history.
     55  */
     56 static struct ANASTASIS_EUFIN_CreditHistoryHandle *hh;
     57 
     58 /**
     59  * Handle to the context for interacting with the bank.
     60  */
     61 static struct GNUNET_CURL_Context *ctx;
     62 
     63 /**
     64  * What is the last row ID that we have already processed?
     65  */
     66 static uint64_t latest_row_off;
     67 
     68 /**
     69  * Scheduler context for running the @e ctx.
     70  */
     71 static struct GNUNET_CURL_RescheduleContext *rc;
     72 
     73 /**
     74  * The configuration (global)
     75  */
     76 static const struct GNUNET_CONFIGURATION_Handle *cfg;
     77 
     78 /**
     79  * Our DB plugin.
     80  */
     81 static struct ANASTASIS_DatabasePlugin *db_plugin;
     82 
     83 /**
     84  * How long should we sleep when idle before trying to find more work?
     85  * Useful in case bank does not support long polling.
     86  */
     87 static struct GNUNET_TIME_Relative idle_sleep_interval;
     88 
     89 /**
     90  * Value to return from main(). 0 on success, non-zero on
     91  * on serious errors.
     92  */
     93 static int global_ret;
     94 
     95 /**
     96  * Run in test-mode, do not background, only import currently
     97  * pending transactions.
     98  */
     99 static int test_mode;
    100 
    101 /**
    102  * Current task waiting for execution, if any.
    103  */
    104 static struct GNUNET_SCHEDULER_Task *task;
    105 
    106 
    107 #include "iban.c"
    108 
    109 /**
    110  * Extract IBAN from a payto URI.
    111  *
    112  * @return NULL on error
    113  */
    114 static char *
    115 payto_get_iban (const char *payto_uri)
    116 {
    117   const char *start;
    118   const char *q;
    119   const char *bic_end;
    120 
    121   if (0 !=
    122       strncasecmp (payto_uri,
    123                    "payto://iban/",
    124                    strlen ("payto://iban/")))
    125     return NULL;
    126   start = &payto_uri[strlen ("payto://iban/")];
    127   q = strchr (start,
    128               '?');
    129   bic_end = strchr (start,
    130                     '/');
    131   if ( (NULL != q) &&
    132        (NULL != bic_end) &&
    133        (bic_end < q) )
    134     start = bic_end + 1;
    135   if ( (NULL == q) &&
    136        (NULL != bic_end) )
    137     start = bic_end + 1;
    138   if (NULL == q)
    139     return GNUNET_strdup (start);
    140   return GNUNET_strndup (start,
    141                          q - start);
    142 }
    143 
    144 
    145 /**
    146  * Notify anastasis-http that we received @a amount
    147  * from @a sender_account_uri with @a code.
    148  *
    149  * @param sender_account_uri payto:// URI of the sending account
    150  * @param code numeric code used in the wire transfer subject
    151  * @param amount the amount that was wired
    152  */
    153 static void
    154 notify (const char *sender_account_uri,
    155         uint64_t code,
    156         const struct TALER_Amount *amount)
    157 {
    158   struct IbanEventP ev = {
    159     .header.type = htons (TALER_DBEVENT_ANASTASIS_AUTH_IBAN_TRANSFER),
    160     .header.size = htons (sizeof (ev)),
    161     .code = GNUNET_htonll (code)
    162   };
    163   const char *as;
    164   char *iban;
    165 
    166   iban = payto_get_iban (sender_account_uri);
    167   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    168               "Generating events for code %llu from %s\n",
    169               (unsigned long long) code,
    170               iban);
    171   GNUNET_CRYPTO_hash (iban,
    172                       strlen (iban),
    173                       &ev.debit_iban_hash);
    174   GNUNET_free (iban);
    175   as = TALER_amount2s (amount);
    176   db_plugin->event_notify (db_plugin->cls,
    177                            &ev.header,
    178                            as,
    179                            strlen (as));
    180 }
    181 
    182 
    183 /**
    184  * We're being aborted with CTRL-C (or SIGTERM). Shut down.
    185  *
    186  * @param cls closure
    187  */
    188 static void
    189 shutdown_task (void *cls)
    190 {
    191   (void) cls;
    192   if (NULL != hh)
    193   {
    194     ANASTASIS_EUFIN_credit_history_cancel (hh);
    195     hh = NULL;
    196   }
    197   if (NULL != ctx)
    198   {
    199     GNUNET_CURL_fini (ctx);
    200     ctx = NULL;
    201   }
    202   if (NULL != rc)
    203   {
    204     GNUNET_CURL_gnunet_rc_destroy (rc);
    205     rc = NULL;
    206   }
    207   if (NULL != task)
    208   {
    209     GNUNET_SCHEDULER_cancel (task);
    210     task = NULL;
    211   }
    212   ANASTASIS_DB_plugin_unload (db_plugin);
    213   db_plugin = NULL;
    214   ANASTASIS_EUFIN_auth_free (&auth);
    215   cfg = NULL;
    216 }
    217 
    218 
    219 /**
    220  * Query for incoming wire transfers.
    221  *
    222  * @param cls NULL
    223  */
    224 static void
    225 find_transfers (void *cls);
    226 
    227 
    228 /**
    229  * Callbacks of this type are used to serve the result of asking
    230  * the bank for the transaction history.
    231  *
    232  * @param cls closure with the `struct WioreAccount *` we are processing
    233  * @param http_status HTTP status code from the server
    234  * @param ec taler error code
    235  * @param serial_id identification of the position at which we are querying
    236  * @param details details about the wire transfer
    237  * @return #GNUNET_OK to continue, #GNUNET_SYSERR to abort iteration
    238  */
    239 static int
    240 history_cb (void *cls,
    241             unsigned int http_status,
    242             enum TALER_ErrorCode ec,
    243             uint64_t serial_id,
    244             const struct ANASTASIS_EUFIN_CreditDetails *details)
    245 {
    246   enum GNUNET_DB_QueryStatus qs;
    247 
    248   if (NULL == details)
    249   {
    250     hh = NULL;
    251     if (TALER_EC_NONE != ec)
    252     {
    253       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    254                   "Error fetching history: ec=%u, http_status=%u\n",
    255                   (unsigned int) ec,
    256                   http_status);
    257     }
    258     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    259                 "End of list.\n");
    260     GNUNET_assert (NULL == task);
    261     if (test_mode)
    262     {
    263       GNUNET_SCHEDULER_shutdown ();
    264       return GNUNET_OK; /* will be ignored anyway */
    265     }
    266     task = GNUNET_SCHEDULER_add_delayed (idle_sleep_interval,
    267                                          &find_transfers,
    268                                          NULL);
    269     return GNUNET_OK; /* will be ignored anyway */
    270   }
    271   if (serial_id <= latest_row_off)
    272   {
    273     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    274                 "Serial ID %llu not monotonic (got %llu before). Failing!\n",
    275                 (unsigned long long) serial_id,
    276                 (unsigned long long) latest_row_off);
    277     GNUNET_SCHEDULER_shutdown ();
    278     hh = NULL;
    279     return GNUNET_SYSERR;
    280   }
    281   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
    282               "Adding wire transfer over %s with (hashed) subject `%s'\n",
    283               TALER_amount2s (&details->amount),
    284               details->wire_subject);
    285   {
    286     char *dcanon = payto_get_iban (details->debit_account_uri);
    287     char *ccanon = payto_get_iban (details->credit_account_uri);
    288 
    289     qs = db_plugin->record_auth_iban_payment (db_plugin->cls,
    290                                               serial_id,
    291                                               details->wire_subject,
    292                                               &details->amount,
    293                                               dcanon,
    294                                               ccanon,
    295                                               details->execution_date);
    296     GNUNET_free (ccanon);
    297     GNUNET_free (dcanon);
    298   }
    299   switch (qs)
    300   {
    301   case GNUNET_DB_STATUS_HARD_ERROR:
    302     GNUNET_break (0);
    303     GNUNET_SCHEDULER_shutdown ();
    304     hh = NULL;
    305     return GNUNET_SYSERR;
    306   case GNUNET_DB_STATUS_SOFT_ERROR:
    307     GNUNET_break (0);
    308     hh = NULL;
    309     return GNUNET_SYSERR;
    310   case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    311     /* already existed (!?), should be impossible */
    312     GNUNET_break (0);
    313     hh = NULL;
    314     return GNUNET_SYSERR;
    315   case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    316     /* normal case */
    317     break;
    318   }
    319   latest_row_off = serial_id;
    320   {
    321     uint64_t code;
    322 
    323     if (GNUNET_OK !=
    324         extract_code (details->wire_subject,
    325                       &code))
    326       return GNUNET_OK;
    327     notify (details->debit_account_uri,
    328             code,
    329             &details->amount);
    330   }
    331   return GNUNET_OK;
    332 }
    333 
    334 
    335 /**
    336  * Query for incoming wire transfers.
    337  *
    338  * @param cls NULL
    339  */
    340 static void
    341 find_transfers (void *cls)
    342 {
    343   (void) cls;
    344   task = NULL;
    345   GNUNET_assert (NULL == hh);
    346   hh = ANASTASIS_EUFIN_credit_history (ctx,
    347                                        &auth,
    348                                        latest_row_off,
    349                                        1024,
    350                                        test_mode
    351                                        ? GNUNET_TIME_UNIT_ZERO
    352                                        : LONGPOLL_TIMEOUT,
    353                                        &history_cb,
    354                                        NULL);
    355   if (NULL == hh)
    356   {
    357     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    358                 "Failed to start request for account history!\n");
    359     global_ret = EXIT_FAILURE;
    360     GNUNET_SCHEDULER_shutdown ();
    361     return;
    362   }
    363 }
    364 
    365 
    366 /**
    367  * First task.
    368  *
    369  * @param cls closure, NULL
    370  * @param args remaining command-line arguments
    371  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
    372  * @param c configuration
    373  */
    374 static void
    375 run (void *cls,
    376      char *const *args,
    377      const char *cfgfile,
    378      const struct GNUNET_CONFIGURATION_Handle *c)
    379 {
    380   (void) cls;
    381   (void) args;
    382   (void) cfgfile;
    383   cfg = c;
    384   if (NULL ==
    385       (db_plugin = ANASTASIS_DB_plugin_load (cfg,
    386                                              false)))
    387   {
    388     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    389                 "Database not set up. Did you run anastasis-dbinit?\n");
    390     global_ret = EXIT_NOTCONFIGURED;
    391     return;
    392   }
    393   if (GNUNET_OK !=
    394       GNUNET_CONFIGURATION_get_value_string (cfg,
    395                                              "authorization-iban",
    396                                              "CREDIT_IBAN",
    397                                              &authorization_iban))
    398   {
    399     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    400                                "authorization-iban",
    401                                "CREDIT_IBAN");
    402     global_ret = EXIT_NOTCONFIGURED;
    403     ANASTASIS_DB_plugin_unload (db_plugin);
    404     db_plugin = NULL;
    405     return;
    406   }
    407 
    408   if (GNUNET_OK !=
    409       ANASTASIS_EUFIN_auth_parse_cfg (cfg,
    410                                       "authorization-iban",
    411                                       &auth))
    412   {
    413     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    414                 "Failed to load bank access configuration data\n");
    415     ANASTASIS_DB_plugin_unload (db_plugin);
    416     db_plugin = NULL;
    417     global_ret = EXIT_NOTCONFIGURED;
    418     return;
    419   }
    420   {
    421     enum GNUNET_DB_QueryStatus qs;
    422 
    423     qs = db_plugin->get_last_auth_iban_payment_row (db_plugin->cls,
    424                                                     authorization_iban,
    425                                                     &latest_row_off);
    426     if (qs < 0)
    427     {
    428       GNUNET_break (0);
    429       ANASTASIS_EUFIN_auth_free (&auth);
    430       ANASTASIS_DB_plugin_unload (db_plugin);
    431       db_plugin = NULL;
    432       return;
    433     }
    434   }
    435   GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
    436                                  cls);
    437   ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
    438                           &rc);
    439   rc = GNUNET_CURL_gnunet_rc_create (ctx);
    440   if (NULL == ctx)
    441   {
    442     GNUNET_break (0);
    443     return;
    444   }
    445   idle_sleep_interval = RETRY_TIMEOUT;
    446   task = GNUNET_SCHEDULER_add_now (&find_transfers,
    447                                    NULL);
    448 }
    449 
    450 
    451 /**
    452  * The main function of anastasis-helper-authorization-iban
    453  *
    454  * @param argc number of arguments from the command line
    455  * @param argv command line arguments
    456  * @return 0 ok, non-zero on error
    457  */
    458 int
    459 main (int argc,
    460       char *const *argv)
    461 {
    462   struct GNUNET_GETOPT_CommandLineOption options[] = {
    463     GNUNET_GETOPT_option_flag ('t',
    464                                "test",
    465                                "run in test mode and exit when idle",
    466                                &test_mode),
    467     GNUNET_GETOPT_OPTION_END
    468   };
    469   enum GNUNET_GenericReturnValue ret;
    470 
    471   ret = GNUNET_PROGRAM_run (
    472     ANASTASIS_project_data (),
    473     argc, argv,
    474     "anastasis-helper-authorization-iban",
    475     gettext_noop (
    476       "background process that watches for incoming wire transfers from customers"),
    477     options,
    478     &run, NULL);
    479   if (GNUNET_SYSERR == ret)
    480     return EXIT_INVALIDARGUMENT;
    481   if (GNUNET_NO == ret)
    482     return EXIT_SUCCESS;
    483   return global_ret;
    484 }
    485 
    486 
    487 /* end of anastasis-helper-authorization-iban.c */