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