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 ¤cy)) 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 */