update_rules.c (18408B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2023, 2024 Taler Systems SA 4 5 TALER 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 TALER 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 TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 /** 17 * @file update_rules.c 18 * @brief helper function to handle AML programs 19 * @author Christian Grothoff 20 */ 21 #include "exchangedb_lib.h" 22 #include "taler/taler_kyclogic_lib.h" 23 #include "taler/taler_dbevents.h" 24 #include "exchange-database/start.h" 25 #include "exchange-database/rollback.h" 26 #include "exchange-database/commit.h" 27 #include "exchange-database/set_aml_lock.h" 28 #include "exchange-database/clear_aml_lock.h" 29 #include "exchange-database/persist_aml_program_result.h" 30 #include "exchange-database/insert_successor_measure.h" 31 #include "exchange-database/event_notify.h" 32 #include "exchange-database/event_listen.h" 33 #include "exchange-database/event_listen_cancel.h" 34 #include "exchange-database/lookup_rules_by_access_token.h" 35 #include "exchange-database/update_rules.h" 36 #include "exchange-database/account_history.h" 37 #include "helper.h" 38 #include <gnunet/gnunet_common.h> 39 40 /** 41 * Maximum recursion depth we allow for AML programs. 42 * Basically, after this number of "skip" processes 43 * we forcefully terminate the recursion and fail hard. 44 */ 45 #define MAX_DEPTH 16 46 47 48 struct TALER_EXCHANGEDB_RuleUpdater 49 { 50 /** 51 * database pg to use 52 */ 53 struct TALER_EXCHANGEDB_PostgresContext *pg; 54 55 /** 56 * key to use to decrypt attributes 57 */ 58 struct TALER_AttributeEncryptionKeyP attribute_key; 59 60 /** 61 * account to get the rule set for 62 */ 63 struct TALER_NormalizedPaytoHashP account; 64 65 /** 66 * function to call with the result 67 */ 68 TALER_EXCHANGEDB_CurrentRulesCallback cb; 69 70 /** 71 * Closure for @e cb. 72 */ 73 void *cb_cls; 74 75 /** 76 * Current rule set we are working on. 77 */ 78 struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs; 79 80 /** 81 * Task for asynchronous continuations. 82 */ 83 struct GNUNET_SCHEDULER_Task *t; 84 85 /** 86 * Handler waiting notification that (previous) AML program 87 * finished. 88 */ 89 struct GNUNET_DB_EventHandler *eh; 90 91 /** 92 * Handle to running AML program. 93 */ 94 struct TALER_KYCLOGIC_AmlProgramRunnerHandle *amlh; 95 96 /** 97 * Name of the AML program we were running asynchronously, 98 * for diagnostics. 99 */ 100 char *aml_program_name; 101 102 /** 103 * Error hint to return with @e ec. 104 */ 105 const char *hint; 106 107 /** 108 * Row the rule set in @a lrs is based on. 109 */ 110 uint64_t legitimization_outcome_last_row; 111 112 /** 113 * Taler error code to return. 114 */ 115 enum TALER_ErrorCode ec; 116 117 /** 118 * Counter used to limit recursion depth. 119 */ 120 unsigned int depth; 121 122 /** 123 * True if @e account is for a wallet. 124 */ 125 bool is_wallet; 126 }; 127 128 129 /** 130 * Function that finally returns the result to the application and cleans 131 * up. Called with an open database transaction on success; on failure, the 132 * transaction will have already been rolled back. 133 * 134 * @param[in,out] ru rule updater to return result for 135 */ 136 static void 137 return_result (struct TALER_EXCHANGEDB_RuleUpdater *ru) 138 { 139 struct TALER_EXCHANGEDB_RuleUpdaterResult rur = { 140 .legitimization_outcome_last_row = ru->legitimization_outcome_last_row, 141 .lrs = ru->lrs, 142 .ec = ru->ec, 143 }; 144 145 ru->cb (ru->cb_cls, 146 &rur); 147 ru->lrs = NULL; 148 TALER_EXCHANGEDB_update_rules_cancel (ru); 149 } 150 151 152 /** 153 * Fail the update with the given @a ec and @a hint. 154 * Called with an open database transaction, which will 155 * be rolled back (!). 156 * 157 * @param[in,out] ru account we are processing 158 * @param ec error code to fail with 159 * @param hint hint to return, can be NULL 160 */ 161 static void 162 fail_update (struct TALER_EXCHANGEDB_RuleUpdater *ru, 163 enum TALER_ErrorCode ec, 164 const char *hint) 165 { 166 GNUNET_assert (NULL == ru->t); 167 TALER_EXCHANGEDB_rollback (ru->pg); 168 ru->ec = ec; 169 ru->hint = hint; 170 return_result (ru); 171 } 172 173 174 /** 175 * Check the rules in @a ru to see if they are current, and 176 * if not begin the updating process. Called with an open 177 * database transaction. 178 * 179 * @param[in] ru rule updater context 180 */ 181 static void 182 check_rules (struct TALER_EXCHANGEDB_RuleUpdater *ru); 183 184 185 /** 186 * Run the measure @a m in the context of the legitimisation rules 187 * of @a ru. Called with an open database transaction. 188 * 189 * @param ru updating context we are using 190 * @param m measure we need to run next 191 */ 192 static void 193 run_measure (struct TALER_EXCHANGEDB_RuleUpdater *ru, 194 const struct TALER_KYCLOGIC_Measure *m); 195 196 197 /** 198 * Function called after AML program was run. Called 199 * without an open database transaction, will start one! 200 * 201 * @param cls the `struct TALER_EXCHANGEDB_RuleUpdater *` 202 * @param apr result of the AML program. 203 */ 204 static void 205 aml_result_callback ( 206 void *cls, 207 const struct TALER_KYCLOGIC_AmlProgramResult *apr) 208 { 209 struct TALER_EXCHANGEDB_RuleUpdater *ru = cls; 210 enum GNUNET_DB_QueryStatus qs; 211 enum GNUNET_GenericReturnValue res; 212 enum TALER_EXCHANGEDB_PersistProgramResultStatus pprs; 213 214 ru->amlh = NULL; 215 res = TALER_EXCHANGEDB_start (ru->pg, 216 "aml-persist-aml-program-result"); 217 if (GNUNET_OK != res) 218 { 219 GNUNET_break (0); 220 fail_update (ru, 221 TALER_EC_GENERIC_DB_START_FAILED, 222 "aml-persist-aml-program-result"); 223 return; 224 } 225 /* Update database update based on result */ 226 qs = TALER_EXCHANGEDB_persist_aml_program_result ( 227 ru->pg, 228 0LLU, /* 0: no existing legitimization process, creates new row */ 229 &ru->account, 230 apr, 231 &pprs); 232 switch (qs) 233 { 234 case GNUNET_DB_STATUS_HARD_ERROR: 235 GNUNET_break (0); 236 fail_update (ru, 237 TALER_EC_GENERIC_DB_STORE_FAILED, 238 "persist_aml_program_result"); 239 return; 240 case GNUNET_DB_STATUS_SOFT_ERROR: 241 /* Bad, couldn't persist AML result. Try again... */ 242 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 243 "Serialization issue persisting result of AML program. Restarting.\n"); 244 fail_update (ru, 245 TALER_EC_GENERIC_DB_SOFT_FAILURE, 246 "persist_aml_program_result"); 247 return; 248 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 249 /* Strange, but let's just continue */ 250 break; 251 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 252 /* normal case */ 253 break; 254 } 255 switch (pprs) 256 { 257 case TALER_EXCHANGEDB_PPRS_OK: 258 break; 259 case TALER_EXCHANGEDB_PPRS_BAD_OUTCOME: 260 fail_update (ru, 261 TALER_EC_EXCHANGE_KYC_AML_PROGRAM_MALFORMED_RESULT, 262 "persist_aml_program_result"); 263 return; 264 } 265 switch (apr->status) 266 { 267 case TALER_KYCLOGIC_AMLR_SUCCESS: 268 TALER_KYCLOGIC_rules_free (ru->lrs); 269 ru->lrs = NULL; 270 ru->lrs = TALER_KYCLOGIC_rules_parse (apr->details.success.new_rules); 271 /* Fall back to default rules on parse error! */ 272 GNUNET_break (NULL != ru->lrs); 273 check_rules (ru); 274 return; 275 case TALER_KYCLOGIC_AMLR_FAILURE: 276 { 277 const char *fmn = apr->details.failure.fallback_measure; 278 const struct TALER_KYCLOGIC_Measure *m; 279 280 m = TALER_KYCLOGIC_get_measure (ru->lrs, 281 fmn); 282 if (NULL == m) 283 { 284 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 285 "Fallback measure `%s' does not exist (anymore?).\n", 286 fmn); 287 TALER_KYCLOGIC_rules_free (ru->lrs); 288 ru->lrs = NULL; 289 return_result (ru); 290 return; 291 } 292 run_measure (ru, 293 m); 294 return; 295 } 296 } 297 /* This should be impossible */ 298 GNUNET_assert (0); 299 } 300 301 302 /** 303 * Entrypoint that fetches the latest rules from the database 304 * and starts processing them. Called without an open database 305 * transaction, will start one. 306 * 307 * @param[in] cls the `struct TALER_EXCHANGEDB_RuleUpdater *` to run 308 */ 309 static void 310 fetch_latest_rules (void *cls); 311 312 313 /** 314 * Notification called when we either timeout on the AML program lock 315 * or when the (previous) AML program finished and we can thus try again. 316 * 317 * @param cls the `struct TALER_EXCHANGEDB_RuleUpdater *` to continue 318 * @param extra additional event data provided (unused) 319 * @param extra_size number of bytes in @a extra (unused) 320 */ 321 static void 322 trigger_fetch_latest_rules (void *cls, 323 const void *extra, 324 size_t extra_size) 325 { 326 struct TALER_EXCHANGEDB_RuleUpdater *ru = cls; 327 328 (void) extra; 329 (void) extra_size; 330 if (NULL != ru->t) 331 return; /* multiple events triggered us, ignore */ 332 ru->t = GNUNET_SCHEDULER_add_now (&fetch_latest_rules, 333 ru); 334 } 335 336 337 static void 338 run_measure (struct TALER_EXCHANGEDB_RuleUpdater *ru, 339 const struct TALER_KYCLOGIC_Measure *m) 340 { 341 if (NULL == m) 342 { 343 /* fall back to default rules */ 344 TALER_KYCLOGIC_rules_free (ru->lrs); 345 ru->lrs = NULL; 346 return_result (ru); 347 return; 348 } 349 ru->depth++; 350 if (ru->depth > MAX_DEPTH) 351 { 352 fail_update (ru, 353 TALER_EC_EXCHANGE_GENERIC_AML_PROGRAM_RECURSION_DETECTED, 354 NULL); 355 return; 356 } 357 if ( (NULL == m->check_name) || 358 (0 == 359 strcasecmp ("SKIP", 360 m->check_name)) ) 361 { 362 struct TALER_EXCHANGEDB_HistoryBuilderContext hbc = { 363 .account = &ru->account, 364 .is_wallet = ru->is_wallet, 365 .pg = ru->pg, 366 .attribute_key = &ru->attribute_key 367 }; 368 enum GNUNET_DB_QueryStatus qs; 369 struct GNUNET_TIME_Absolute xlock; 370 371 /* Free previous one, in case we are iterating... */ 372 GNUNET_free (ru->aml_program_name); 373 if (NULL != m->prog_name) 374 { 375 ru->aml_program_name = GNUNET_strdup (m->prog_name); 376 } 377 else 378 { 379 /* How do we get to run a measure if the check type 380 is INFO (which is the only case where prog_name 381 is allowed to be NULL?) */ 382 GNUNET_break (0); 383 ru->aml_program_name = NULL; 384 } 385 qs = TALER_EXCHANGEDB_set_aml_lock ( 386 ru->pg, 387 &ru->account, 388 GNUNET_TIME_relative_multiply (ru->pg->max_aml_program_runtime, 389 2), 390 &xlock); 391 if (GNUNET_TIME_absolute_is_future (xlock)) 392 { 393 struct TALER_EXCHANGEDB_KycCompletedEventP eh = { 394 .header.size = htons (sizeof (eh)), 395 .header.type = htons (TALER_DBEVENT_EXCHANGE_KYC_COMPLETED), 396 .h_payto = ru->account 397 }; 398 /* Wait for either timeout or notification */ 399 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 400 "AML program already running, waiting for it to finish\n"); 401 TALER_EXCHANGEDB_rollback (ru->pg); 402 ru->eh 403 = TALER_EXCHANGEDB_event_listen ( 404 ru->pg, 405 GNUNET_TIME_absolute_get_remaining (xlock), 406 &eh.header, 407 &trigger_fetch_latest_rules, 408 ru); 409 return; 410 } 411 qs = TALER_EXCHANGEDB_commit (ru->pg); 412 if (qs < 0) 413 { 414 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 415 fail_update (ru, 416 GNUNET_DB_STATUS_SOFT_ERROR == qs 417 ? TALER_EC_GENERIC_DB_SOFT_FAILURE 418 : TALER_EC_GENERIC_DB_COMMIT_FAILED, 419 "current-aml-rule-fetch"); 420 return; 421 } 422 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 423 "Check is of type 'SKIP', running AML program %s.\n", 424 m->prog_name); 425 GNUNET_assert (NULL == ru->t); 426 ru->amlh = TALER_KYCLOGIC_run_aml_program3 ( 427 ru->is_wallet, 428 m, 429 &TALER_EXCHANGEDB_current_attributes_builder, 430 &hbc, 431 &TALER_EXCHANGEDB_current_rule_builder, 432 &hbc, 433 &TALER_EXCHANGEDB_aml_history_builder, 434 &hbc, 435 &TALER_EXCHANGEDB_kyc_history_builder, 436 &hbc, 437 ru->pg->max_aml_program_runtime, 438 &aml_result_callback, 439 ru); 440 return; 441 } 442 443 /* User MUST pass interactive check */ 444 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 445 "Measure %s involves check %s\n", 446 m->measure_name, 447 m->check_name); 448 { 449 /* activate the measure/check */ 450 json_t *succ_jmeasures 451 = TALER_KYCLOGIC_get_jmeasures ( 452 ru->lrs, 453 m->measure_name); 454 bool unknown_account; 455 struct GNUNET_TIME_Timestamp last_date; 456 enum GNUNET_DB_QueryStatus qs; 457 458 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 459 "Inserting LEGI OUTCOME as successor measure\n"); 460 qs = TALER_EXCHANGEDB_insert_successor_measure ( 461 ru->pg, 462 &ru->account, 463 GNUNET_TIME_timestamp_get (), 464 m->measure_name, 465 succ_jmeasures, 466 &unknown_account, 467 &last_date); 468 json_decref (succ_jmeasures); 469 switch (qs) 470 { 471 case GNUNET_DB_STATUS_SOFT_ERROR: 472 GNUNET_log ( 473 GNUNET_ERROR_TYPE_INFO, 474 "Serialization issue!\n"); 475 fail_update (ru, 476 TALER_EC_GENERIC_DB_SOFT_FAILURE, 477 "insert_successor_measure"); 478 return; 479 case GNUNET_DB_STATUS_HARD_ERROR: 480 GNUNET_break (0); 481 fail_update (ru, 482 TALER_EC_GENERIC_DB_STORE_FAILED, 483 "insert_successor_measure"); 484 return; 485 default: 486 break; 487 } 488 489 // FIXME: combine with above transaction... 490 { 491 struct TALER_EXCHANGEDB_KycCompletedEventP eh = { 492 .header.size = htons (sizeof (eh)), 493 .header.type = htons (TALER_DBEVENT_EXCHANGE_KYC_COMPLETED), 494 .h_payto = ru->account 495 }; 496 497 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 498 "Triggering KYC COMPLETED event\n"); 499 TALER_EXCHANGEDB_event_notify (ru->pg, 500 &eh.header, 501 NULL, 502 0); 503 } 504 505 if (unknown_account) 506 { 507 fail_update (ru, 508 TALER_EC_EXCHANGE_GENERIC_BANK_ACCOUNT_UNKNOWN, 509 NULL); 510 return; 511 } 512 } 513 /* The rules remain these rules until the user passes the check */ 514 return_result (ru); 515 } 516 517 518 /** 519 * Update the expired legitimization rules in @a ru, checking for expiration 520 * first. Called with an open database transaction. 521 * 522 * @param[in,out] ru account we are processing 523 */ 524 static void 525 update_rules (struct TALER_EXCHANGEDB_RuleUpdater *ru) 526 { 527 const struct TALER_KYCLOGIC_Measure *m; 528 529 GNUNET_assert (NULL != ru->lrs); 530 GNUNET_assert (GNUNET_TIME_absolute_is_past ( 531 TALER_KYCLOGIC_rules_get_expiration (ru->lrs).abs_time)); 532 m = TALER_KYCLOGIC_rules_get_successor (ru->lrs); 533 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 534 "Successor measure is %s.\n", 535 (NULL != m) ? m->measure_name : "(null)"); 536 run_measure (ru, 537 m); 538 } 539 540 541 static void 542 check_rules (struct TALER_EXCHANGEDB_RuleUpdater *ru) 543 { 544 ru->depth++; 545 if (ru->depth > MAX_DEPTH) 546 { 547 fail_update (ru, 548 TALER_EC_EXCHANGE_GENERIC_AML_PROGRAM_RECURSION_DETECTED, 549 NULL); 550 return; 551 } 552 if (NULL == ru->lrs) 553 { 554 /* return NULL, aka default rules */ 555 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 556 "Default rules apply\n"); 557 return_result (ru); 558 return; 559 } 560 if (! GNUNET_TIME_absolute_is_past 561 (TALER_KYCLOGIC_rules_get_expiration (ru->lrs).abs_time) ) 562 { 563 /* Rules did not expire, return them! */ 564 return_result (ru); 565 return; 566 } 567 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 568 "Custom rules expired, updating...\n"); 569 update_rules (ru); 570 } 571 572 573 static void 574 fetch_latest_rules (void *cls) 575 { 576 struct TALER_EXCHANGEDB_RuleUpdater *ru = cls; 577 enum GNUNET_DB_QueryStatus qs; 578 json_t *jnew_rules; 579 enum GNUNET_GenericReturnValue res; 580 581 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 582 "Fetching latest rules."); 583 584 ru->t = NULL; 585 if (NULL != ru->eh) 586 { 587 /* cancel event listener, if we have one */ 588 TALER_TALER_EXCHANGEDB_event_listen_cancel (ru->pg, 589 ru->eh); 590 ru->eh = NULL; 591 } 592 GNUNET_break (NULL == ru->lrs); 593 res = TALER_EXCHANGEDB_start (ru->pg, 594 "aml-begin-lookup-rules-by-access-token"); 595 if (GNUNET_OK != res) 596 { 597 GNUNET_break (0); 598 fail_update (ru, 599 TALER_EC_GENERIC_DB_START_FAILED, 600 "aml-begin-lookup-rules-by-access-token"); 601 return; 602 } 603 qs = TALER_EXCHANGEDB_lookup_rules_by_access_token ( 604 ru->pg, 605 &ru->account, 606 &jnew_rules, 607 &ru->legitimization_outcome_last_row); 608 if (qs < 0) 609 { 610 GNUNET_break (0); 611 fail_update (ru, 612 TALER_EC_GENERIC_DB_FETCH_FAILED, 613 "lookup_rules_by_access_token"); 614 return; 615 } 616 if ( (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) && 617 (NULL != jnew_rules) ) 618 { 619 ru->lrs = TALER_KYCLOGIC_rules_parse (jnew_rules); 620 GNUNET_break (NULL != ru->lrs); 621 json_decref (jnew_rules); 622 } 623 check_rules (ru); 624 } 625 626 627 struct TALER_EXCHANGEDB_RuleUpdater * 628 TALER_EXCHANGEDB_update_rules ( 629 struct TALER_EXCHANGEDB_PostgresContext *pg, 630 const struct TALER_AttributeEncryptionKeyP *attribute_key, 631 const struct TALER_NormalizedPaytoHashP *account, 632 bool is_wallet, 633 TALER_EXCHANGEDB_CurrentRulesCallback cb, 634 void *cb_cls) 635 { 636 struct TALER_EXCHANGEDB_RuleUpdater *ru; 637 638 ru = GNUNET_new (struct TALER_EXCHANGEDB_RuleUpdater); 639 ru->pg = pg; 640 ru->attribute_key = *attribute_key; 641 ru->account = *account; 642 ru->is_wallet = is_wallet; 643 ru->cb = cb; 644 ru->cb_cls = cb_cls; 645 ru->t = GNUNET_SCHEDULER_add_now (&fetch_latest_rules, 646 ru); 647 return ru; 648 } 649 650 651 void 652 TALER_EXCHANGEDB_update_rules_cancel ( 653 struct TALER_EXCHANGEDB_RuleUpdater *ru) 654 { 655 if (NULL != ru->t) 656 { 657 GNUNET_SCHEDULER_cancel (ru->t); 658 ru->t = NULL; 659 } 660 if (NULL != ru->amlh) 661 { 662 TALER_KYCLOGIC_run_aml_program_cancel (ru->amlh); 663 ru->amlh = NULL; 664 } 665 if (NULL != ru->lrs) 666 { 667 TALER_KYCLOGIC_rules_free (ru->lrs); 668 ru->lrs = NULL; 669 } 670 if (NULL != ru->eh) 671 { 672 TALER_TALER_EXCHANGEDB_event_listen_cancel (ru->pg, 673 ru->eh); 674 ru->eh = NULL; 675 } 676 GNUNET_free (ru->aml_program_name); 677 GNUNET_free (ru); 678 }