update_rules.c (17862B)
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 if (unknown_account) 490 { 491 fail_update (ru, 492 TALER_EC_EXCHANGE_GENERIC_BANK_ACCOUNT_UNKNOWN, 493 NULL); 494 return; 495 } 496 } 497 /* The rules remain these rules until the user passes the check */ 498 return_result (ru); 499 } 500 501 502 /** 503 * Update the expired legitimization rules in @a ru, checking for expiration 504 * first. Called with an open database transaction. 505 * 506 * @param[in,out] ru account we are processing 507 */ 508 static void 509 update_rules (struct TALER_EXCHANGEDB_RuleUpdater *ru) 510 { 511 const struct TALER_KYCLOGIC_Measure *m; 512 513 GNUNET_assert (NULL != ru->lrs); 514 GNUNET_assert (GNUNET_TIME_absolute_is_past ( 515 TALER_KYCLOGIC_rules_get_expiration (ru->lrs).abs_time)); 516 m = TALER_KYCLOGIC_rules_get_successor (ru->lrs); 517 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 518 "Successor measure is %s.\n", 519 (NULL != m) ? m->measure_name : "(null)"); 520 run_measure (ru, 521 m); 522 } 523 524 525 static void 526 check_rules (struct TALER_EXCHANGEDB_RuleUpdater *ru) 527 { 528 ru->depth++; 529 if (ru->depth > MAX_DEPTH) 530 { 531 fail_update (ru, 532 TALER_EC_EXCHANGE_GENERIC_AML_PROGRAM_RECURSION_DETECTED, 533 NULL); 534 return; 535 } 536 if (NULL == ru->lrs) 537 { 538 /* return NULL, aka default rules */ 539 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 540 "Default rules apply\n"); 541 return_result (ru); 542 return; 543 } 544 if (! GNUNET_TIME_absolute_is_past 545 (TALER_KYCLOGIC_rules_get_expiration (ru->lrs).abs_time) ) 546 { 547 /* Rules did not expire, return them! */ 548 return_result (ru); 549 return; 550 } 551 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 552 "Custom rules expired, updating...\n"); 553 update_rules (ru); 554 } 555 556 557 static void 558 fetch_latest_rules (void *cls) 559 { 560 struct TALER_EXCHANGEDB_RuleUpdater *ru = cls; 561 enum GNUNET_DB_QueryStatus qs; 562 json_t *jnew_rules; 563 enum GNUNET_GenericReturnValue res; 564 565 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 566 "Fetching latest rules."); 567 568 ru->t = NULL; 569 if (NULL != ru->eh) 570 { 571 /* cancel event listener, if we have one */ 572 TALER_TALER_EXCHANGEDB_event_listen_cancel (ru->pg, 573 ru->eh); 574 ru->eh = NULL; 575 } 576 GNUNET_break (NULL == ru->lrs); 577 res = TALER_EXCHANGEDB_start (ru->pg, 578 "aml-begin-lookup-rules-by-access-token"); 579 if (GNUNET_OK != res) 580 { 581 GNUNET_break (0); 582 fail_update (ru, 583 TALER_EC_GENERIC_DB_START_FAILED, 584 "aml-begin-lookup-rules-by-access-token"); 585 return; 586 } 587 qs = TALER_EXCHANGEDB_lookup_rules_by_access_token ( 588 ru->pg, 589 &ru->account, 590 &jnew_rules, 591 &ru->legitimization_outcome_last_row); 592 if (qs < 0) 593 { 594 GNUNET_break (0); 595 fail_update (ru, 596 TALER_EC_GENERIC_DB_FETCH_FAILED, 597 "lookup_rules_by_access_token"); 598 return; 599 } 600 if ( (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) && 601 (NULL != jnew_rules) ) 602 { 603 ru->lrs = TALER_KYCLOGIC_rules_parse (jnew_rules); 604 GNUNET_break (NULL != ru->lrs); 605 json_decref (jnew_rules); 606 } 607 check_rules (ru); 608 } 609 610 611 struct TALER_EXCHANGEDB_RuleUpdater * 612 TALER_EXCHANGEDB_update_rules ( 613 struct TALER_EXCHANGEDB_PostgresContext *pg, 614 const struct TALER_AttributeEncryptionKeyP *attribute_key, 615 const struct TALER_NormalizedPaytoHashP *account, 616 bool is_wallet, 617 TALER_EXCHANGEDB_CurrentRulesCallback cb, 618 void *cb_cls) 619 { 620 struct TALER_EXCHANGEDB_RuleUpdater *ru; 621 622 ru = GNUNET_new (struct TALER_EXCHANGEDB_RuleUpdater); 623 ru->pg = pg; 624 ru->attribute_key = *attribute_key; 625 ru->account = *account; 626 ru->is_wallet = is_wallet; 627 ru->cb = cb; 628 ru->cb_cls = cb_cls; 629 ru->t = GNUNET_SCHEDULER_add_now (&fetch_latest_rules, 630 ru); 631 return ru; 632 } 633 634 635 void 636 TALER_EXCHANGEDB_update_rules_cancel ( 637 struct TALER_EXCHANGEDB_RuleUpdater *ru) 638 { 639 if (NULL != ru->t) 640 { 641 GNUNET_SCHEDULER_cancel (ru->t); 642 ru->t = NULL; 643 } 644 if (NULL != ru->amlh) 645 { 646 TALER_KYCLOGIC_run_aml_program_cancel (ru->amlh); 647 ru->amlh = NULL; 648 } 649 if (NULL != ru->lrs) 650 { 651 TALER_KYCLOGIC_rules_free (ru->lrs); 652 ru->lrs = NULL; 653 } 654 if (NULL != ru->eh) 655 { 656 TALER_TALER_EXCHANGEDB_event_listen_cancel (ru->pg, 657 ru->eh); 658 ru->eh = NULL; 659 } 660 GNUNET_free (ru->aml_program_name); 661 GNUNET_free (ru); 662 }