anastasis

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

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


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