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 */