anastasis

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

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


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