update_rules.c (18182B)
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 (qs <= 0) 392 { 393 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 394 fail_update (ru, 395 GNUNET_DB_STATUS_SOFT_ERROR == qs 396 ? TALER_EC_GENERIC_DB_SOFT_FAILURE 397 : TALER_EC_GENERIC_DB_STORE_FAILED, 398 "set_aml_lock"); 399 return; 400 } 401 if (GNUNET_TIME_absolute_is_future (xlock)) 402 { 403 struct TALER_EXCHANGEDB_KycCompletedEventP eh = { 404 .header.size = htons (sizeof (eh)), 405 .header.type = htons (TALER_DBEVENT_EXCHANGE_KYC_COMPLETED), 406 .h_payto = ru->account 407 }; 408 /* Wait for either timeout or notification */ 409 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 410 "AML program already running, waiting for it to finish\n"); 411 TALER_EXCHANGEDB_rollback (ru->pg); 412 ru->eh 413 = TALER_EXCHANGEDB_event_listen ( 414 ru->pg, 415 GNUNET_TIME_absolute_get_remaining (xlock), 416 &eh.header, 417 &trigger_fetch_latest_rules, 418 ru); 419 return; 420 } 421 qs = TALER_EXCHANGEDB_commit (ru->pg); 422 if (qs < 0) 423 { 424 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 425 fail_update (ru, 426 GNUNET_DB_STATUS_SOFT_ERROR == qs 427 ? TALER_EC_GENERIC_DB_SOFT_FAILURE 428 : TALER_EC_GENERIC_DB_COMMIT_FAILED, 429 "current-aml-rule-fetch"); 430 return; 431 } 432 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 433 "Check is of type 'SKIP', running AML program %s.\n", 434 m->prog_name); 435 GNUNET_assert (NULL == ru->t); 436 ru->amlh = TALER_KYCLOGIC_run_aml_program3 ( 437 ru->is_wallet, 438 m, 439 &TALER_EXCHANGEDB_current_attributes_builder, 440 &hbc, 441 &TALER_EXCHANGEDB_current_rule_builder, 442 &hbc, 443 &TALER_EXCHANGEDB_aml_history_builder, 444 &hbc, 445 &TALER_EXCHANGEDB_kyc_history_builder, 446 &hbc, 447 ru->pg->max_aml_program_runtime, 448 &aml_result_callback, 449 ru); 450 return; 451 } 452 453 /* User MUST pass interactive check */ 454 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 455 "Measure %s involves check %s\n", 456 m->measure_name, 457 m->check_name); 458 { 459 /* activate the measure/check */ 460 json_t *succ_jmeasures 461 = TALER_KYCLOGIC_get_jmeasures ( 462 ru->lrs, 463 m->measure_name); 464 bool unknown_account; 465 struct GNUNET_TIME_Timestamp last_date; 466 enum GNUNET_DB_QueryStatus qs; 467 468 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 469 "Inserting LEGI OUTCOME as successor measure\n"); 470 qs = TALER_EXCHANGEDB_insert_successor_measure ( 471 ru->pg, 472 &ru->account, 473 GNUNET_TIME_timestamp_get (), 474 m->measure_name, 475 succ_jmeasures, 476 &unknown_account, 477 &last_date); 478 json_decref (succ_jmeasures); 479 switch (qs) 480 { 481 case GNUNET_DB_STATUS_SOFT_ERROR: 482 GNUNET_log ( 483 GNUNET_ERROR_TYPE_INFO, 484 "Serialization issue!\n"); 485 fail_update (ru, 486 TALER_EC_GENERIC_DB_SOFT_FAILURE, 487 "insert_successor_measure"); 488 return; 489 case GNUNET_DB_STATUS_HARD_ERROR: 490 GNUNET_break (0); 491 fail_update (ru, 492 TALER_EC_GENERIC_DB_STORE_FAILED, 493 "insert_successor_measure"); 494 return; 495 default: 496 break; 497 } 498 499 if (unknown_account) 500 { 501 fail_update (ru, 502 TALER_EC_EXCHANGE_GENERIC_BANK_ACCOUNT_UNKNOWN, 503 NULL); 504 return; 505 } 506 } 507 /* The rules remain these rules until the user passes the check */ 508 return_result (ru); 509 } 510 511 512 /** 513 * Update the expired legitimization rules in @a ru, checking for expiration 514 * first. Called with an open database transaction. 515 * 516 * @param[in,out] ru account we are processing 517 */ 518 static void 519 update_rules (struct TALER_EXCHANGEDB_RuleUpdater *ru) 520 { 521 const struct TALER_KYCLOGIC_Measure *m; 522 523 GNUNET_assert (NULL != ru->lrs); 524 GNUNET_assert (GNUNET_TIME_absolute_is_past ( 525 TALER_KYCLOGIC_rules_get_expiration (ru->lrs).abs_time)); 526 m = TALER_KYCLOGIC_rules_get_successor (ru->lrs); 527 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 528 "Successor measure is %s.\n", 529 (NULL != m) ? m->measure_name : "(null)"); 530 run_measure (ru, 531 m); 532 } 533 534 535 static void 536 check_rules (struct TALER_EXCHANGEDB_RuleUpdater *ru) 537 { 538 ru->depth++; 539 if (ru->depth > MAX_DEPTH) 540 { 541 fail_update (ru, 542 TALER_EC_EXCHANGE_GENERIC_AML_PROGRAM_RECURSION_DETECTED, 543 NULL); 544 return; 545 } 546 if (NULL == ru->lrs) 547 { 548 /* return NULL, aka default rules */ 549 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 550 "Default rules apply\n"); 551 return_result (ru); 552 return; 553 } 554 if (! GNUNET_TIME_absolute_is_past 555 (TALER_KYCLOGIC_rules_get_expiration (ru->lrs).abs_time) ) 556 { 557 /* Rules did not expire, return them! */ 558 return_result (ru); 559 return; 560 } 561 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 562 "Custom rules expired, updating...\n"); 563 update_rules (ru); 564 } 565 566 567 static void 568 fetch_latest_rules (void *cls) 569 { 570 struct TALER_EXCHANGEDB_RuleUpdater *ru = cls; 571 enum GNUNET_DB_QueryStatus qs; 572 json_t *jnew_rules; 573 enum GNUNET_GenericReturnValue res; 574 575 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 576 "Fetching latest rules."); 577 578 ru->t = NULL; 579 if (NULL != ru->eh) 580 { 581 /* cancel event listener, if we have one */ 582 TALER_TALER_EXCHANGEDB_event_listen_cancel (ru->pg, 583 ru->eh); 584 ru->eh = NULL; 585 } 586 GNUNET_break (NULL == ru->lrs); 587 res = TALER_EXCHANGEDB_start (ru->pg, 588 "aml-begin-lookup-rules-by-access-token"); 589 if (GNUNET_OK != res) 590 { 591 GNUNET_break (0); 592 fail_update (ru, 593 TALER_EC_GENERIC_DB_START_FAILED, 594 "aml-begin-lookup-rules-by-access-token"); 595 return; 596 } 597 qs = TALER_EXCHANGEDB_lookup_rules_by_access_token ( 598 ru->pg, 599 &ru->account, 600 &jnew_rules, 601 &ru->legitimization_outcome_last_row); 602 if (qs < 0) 603 { 604 GNUNET_break (0); 605 fail_update (ru, 606 TALER_EC_GENERIC_DB_FETCH_FAILED, 607 "lookup_rules_by_access_token"); 608 return; 609 } 610 if ( (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) && 611 (NULL != jnew_rules) ) 612 { 613 ru->lrs = TALER_KYCLOGIC_rules_parse (jnew_rules); 614 GNUNET_break (NULL != ru->lrs); 615 json_decref (jnew_rules); 616 } 617 check_rules (ru); 618 } 619 620 621 struct TALER_EXCHANGEDB_RuleUpdater * 622 TALER_EXCHANGEDB_update_rules ( 623 struct TALER_EXCHANGEDB_PostgresContext *pg, 624 const struct TALER_AttributeEncryptionKeyP *attribute_key, 625 const struct TALER_NormalizedPaytoHashP *account, 626 bool is_wallet, 627 TALER_EXCHANGEDB_CurrentRulesCallback cb, 628 void *cb_cls) 629 { 630 struct TALER_EXCHANGEDB_RuleUpdater *ru; 631 632 ru = GNUNET_new (struct TALER_EXCHANGEDB_RuleUpdater); 633 ru->pg = pg; 634 ru->attribute_key = *attribute_key; 635 ru->account = *account; 636 ru->is_wallet = is_wallet; 637 ru->cb = cb; 638 ru->cb_cls = cb_cls; 639 ru->t = GNUNET_SCHEDULER_add_now (&fetch_latest_rules, 640 ru); 641 return ru; 642 } 643 644 645 void 646 TALER_EXCHANGEDB_update_rules_cancel ( 647 struct TALER_EXCHANGEDB_RuleUpdater *ru) 648 { 649 if (NULL != ru->t) 650 { 651 GNUNET_SCHEDULER_cancel (ru->t); 652 ru->t = NULL; 653 } 654 if (NULL != ru->amlh) 655 { 656 TALER_KYCLOGIC_run_aml_program_cancel (ru->amlh); 657 ru->amlh = NULL; 658 } 659 if (NULL != ru->lrs) 660 { 661 TALER_KYCLOGIC_rules_free (ru->lrs); 662 ru->lrs = NULL; 663 } 664 if (NULL != ru->eh) 665 { 666 TALER_TALER_EXCHANGEDB_event_listen_cancel (ru->pg, 667 ru->eh); 668 ru->eh = NULL; 669 } 670 GNUNET_free (ru->aml_program_name); 671 GNUNET_free (ru); 672 }