taler-helper-auditor-aml.c (33063B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2026 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 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 Public License for more details. 12 13 You should have received a copy of the GNU Affero Public License along with 14 TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 /** 17 * @file auditor/taler-helper-auditor-aml.c 18 * @brief audits the AML decisions and the appointments of the AML staff who 19 * made them 20 * @author Christian Grothoff 21 * 22 * Three checks, in one transaction and in this order, because each is judged 23 * against the result of the one before it: 24 * 25 * 1) every row in `aml_staff` must carry a valid signature by the exchange's 26 * *offline* master key. Without this an exchange could appoint an AML 27 * officer -- or quietly widen a read-only officer to read-write -- with 28 * the offline key never having been involved. 29 * 30 * 2) every AML decision in `aml_history` must carry a valid signature by an 31 * officer who, at the time of the decision, was appointed and had 32 * read-write access. 33 * 34 * 3) every row in `legitimization_outcomes` must have something that accounts 35 * for it. That table holds the rules currently in force for an account, 36 * so it is what decides whether a customer's transaction is allowed; a row 37 * that nothing produced is precisely how an exchange would quietly exempt 38 * a customer from KYC. See #check_outcome_cb() for what counts. 39 * 40 * None of the findings is a loss: neither an AML decision nor a rule change 41 * moves money, so a bad signature or an unaccounted-for rule set costs the 42 * exchange nothing directly. What it costs is the point of having officers 43 * sign at all, so the findings are reported as row inconsistencies rather 44 * than as bad-sig losses. 45 * 46 * `aml_staff` is append-only -- a status change is a new row -- so the 47 * exchange's own database does answer "was this officer allowed to decide at 48 * that time?". The auditor nevertheless keeps its own record of every status 49 * change it has seen, in `auditor_aml_staff`, and answers the question from 50 * that: append-only is a promise the exchange's code makes, not one the 51 * database enforces against the exchange's operator, and an independent 52 * record is the point of an auditor. 53 * 54 * That record is also what makes backdating detectable. The master key is 55 * the exchange operator's own, so a dishonest operator can sign an 56 * appointment naming any `last_change` it likes, and a decision naming any 57 * `decision_time`. What it cannot do is change what the auditor already 58 * saw: if the auditor recorded an officer as disabled before this round 59 * began, a decision by that officer surfacing now is backdated, whatever 60 * `decision_time` claims. This does not stop backdating *within* the set of 61 * rows processed in one round -- an auditor running promptly keeps that 62 * window small, which is the whole defence. 63 */ 64 #include "platform.h" 65 #include <gnunet/gnunet_util_lib.h> 66 #include "auditordb_lib.h" 67 #include "exchangedb_lib.h" 68 #include "report-lib.h" 69 #include "taler/taler_dbevents.h" 70 #include <jansson.h> 71 #include <inttypes.h> 72 #include "auditor-database/event_listen.h" 73 #include "auditor-database/get_auditor_progress.h" 74 #include "auditor-database/insert_aml_staff.h" 75 #include "auditor-database/insert_auditor_progress.h" 76 #include "auditor-database/insert_row_inconsistency.h" 77 #include "auditor-database/iterate_aml_staff.h" 78 #include "auditor-database/update_auditor_progress.h" 79 #include "exchange-database/iterate_aml_history_above_serial_id.h" 80 #include "exchange-database/iterate_aml_staff_above_serial_id.h" 81 #include "exchange-database/iterate_legitimization_outcomes_above_serial_id.h" 82 83 84 /** 85 * Return value from main(). 86 */ 87 static int global_ret; 88 89 /** 90 * Row of `aml_history` up to which we have checked AML decisions. 91 */ 92 static TALER_ARL_DEF_PP (aml_history_serial_id); 93 94 /** 95 * Row of `aml_staff` up to which we have checked status changes. 96 */ 97 static TALER_ARL_DEF_PP (aml_staff_uuid); 98 99 /** 100 * Row of `legitimization_outcomes` up to which we have checked that the 101 * rules in force for an account are accounted for. 102 */ 103 static TALER_ARL_DEF_PP (legitimization_outcome_serial_id); 104 105 /** 106 * Run in test mode. Exit when idle instead of 107 * going to sleep and waiting for more work. 108 */ 109 static int test_mode; 110 111 /** 112 * Should we run checks that only work for exchange-internal audits? 113 * Does nothing for this helper (present only for uniformity). 114 */ 115 static int internal_checks; 116 117 /** 118 * Handle to the database event we wait on when running resident. 119 */ 120 static struct GNUNET_DB_EventHandler *eh; 121 122 /** 123 * The auditors's configuration. 124 */ 125 static const struct GNUNET_CONFIGURATION_Handle *cfg; 126 127 /** 128 * Map from the hash of an officer's public key to a `struct StaffMember`. 129 */ 130 static struct GNUNET_CONTAINER_MultiHashMap *staff_map; 131 132 /** 133 * Status of the DB operations of our callbacks; they cannot return a 134 * query status themselves. 135 */ 136 static enum GNUNET_DB_QueryStatus global_qs; 137 138 139 /** 140 * One observed status of an AML staff member, in force from @e last_change 141 * until the next status of the same member. 142 */ 143 struct StaffStatus 144 { 145 /** 146 * When this status took effect, as claimed by the exchange. 147 */ 148 struct GNUNET_TIME_Timestamp last_change; 149 150 /** 151 * Was the member allowed to act? 152 */ 153 bool is_active; 154 155 /** 156 * Was the member restricted to read-only access? 157 */ 158 bool read_only; 159 160 /** 161 * Did the exchange's offline master key actually sign this status? 162 * A status that fails this check confers no authority. 163 */ 164 bool master_sig_valid; 165 }; 166 167 168 /** 169 * What we know about one AML staff member. 170 */ 171 struct StaffMember 172 { 173 /** 174 * Key under which this member is stored in #staff_map. The map is 175 * created with @a do_not_copy_keys, so it keeps this pointer: the key 176 * has to live as long as the entry does. 177 */ 178 struct GNUNET_HashCode key; 179 180 /** 181 * Observed statuses, oldest first. 182 */ 183 struct StaffStatus *statuses; 184 185 /** 186 * Length of @e statuses. 187 */ 188 unsigned int num_statuses; 189 190 /** 191 * Did we already know this member when the round began? 192 */ 193 bool known_at_start; 194 195 /** 196 * Was the member enabled according to the most recent status we knew 197 * when the round began? Meaningless unless @e known_at_start. 198 */ 199 bool active_at_start; 200 201 /** 202 * Did we observe the member being enabled during this round? 203 */ 204 bool enabled_this_round; 205 }; 206 207 208 /** 209 * Compute the map key for @a decider_pub. 210 * 211 * @param decider_pub public key of the staff member 212 * @param[out] key set to the key to use 213 */ 214 static void 215 staff_key (const struct TALER_AmlOfficerPublicKeyP *decider_pub, 216 struct GNUNET_HashCode *key) 217 { 218 GNUNET_CRYPTO_hash (decider_pub, 219 sizeof (*decider_pub), 220 key); 221 } 222 223 224 /** 225 * Look up @a decider_pub in #staff_map, creating the entry if needed. 226 * 227 * @param decider_pub public key of the staff member 228 * @return the entry, never NULL 229 */ 230 static struct StaffMember * 231 staff_member_get (const struct TALER_AmlOfficerPublicKeyP *decider_pub) 232 { 233 struct GNUNET_HashCode key; 234 struct StaffMember *sm; 235 236 staff_key (decider_pub, 237 &key); 238 sm = GNUNET_CONTAINER_multihashmap_get (staff_map, 239 &key); 240 if (NULL != sm) 241 return sm; 242 sm = GNUNET_new (struct StaffMember); 243 sm->key = key; 244 GNUNET_assert (GNUNET_OK == 245 GNUNET_CONTAINER_multihashmap_put ( 246 staff_map, 247 &sm->key, 248 sm, 249 GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); 250 return sm; 251 } 252 253 254 /** 255 * Append @a status to the statuses of @a sm, keeping them ordered by 256 * @e last_change. 257 * 258 * @param[in,out] sm staff member to extend 259 * @param status status to add 260 */ 261 static void 262 staff_member_add (struct StaffMember *sm, 263 const struct StaffStatus *status) 264 { 265 unsigned int i; 266 267 GNUNET_array_grow (sm->statuses, 268 sm->num_statuses, 269 sm->num_statuses + 1); 270 for (i = sm->num_statuses - 1; i > 0; i--) 271 { 272 if (GNUNET_TIME_timestamp_cmp (sm->statuses[i - 1].last_change, 273 <=, 274 status->last_change)) 275 break; 276 sm->statuses[i] = sm->statuses[i - 1]; 277 } 278 sm->statuses[i] = *status; 279 } 280 281 282 /** 283 * Find the status that was in force for @a sm at @a when, considering only 284 * statuses the offline master key really signed. 285 * 286 * @param sm staff member to look at 287 * @param when point in time of interest 288 * @return NULL if no signed status covers @a when 289 */ 290 static const struct StaffStatus * 291 staff_member_status_at (const struct StaffMember *sm, 292 struct GNUNET_TIME_Timestamp when) 293 { 294 const struct StaffStatus *ret = NULL; 295 296 for (unsigned int i = 0; i < sm->num_statuses; i++) 297 { 298 const struct StaffStatus *ss = &sm->statuses[i]; 299 300 if (GNUNET_TIME_timestamp_cmp (ss->last_change, 301 >, 302 when)) 303 break; /* sorted, so no later entry can apply either */ 304 if (ss->master_sig_valid) 305 ret = ss; 306 } 307 return ret; 308 } 309 310 311 /** 312 * Report a row inconsistency. 313 * 314 * @param table name of the exchange table the bad row is in 315 * @param rowid row that is bad 316 * @param diagnostic what is wrong with it 317 * @return true on success, false if the database failed us 318 */ 319 static bool 320 report_row (const char *table, 321 uint64_t rowid, 322 const char *diagnostic) 323 { 324 struct TALER_AUDITORDB_RowInconsistency ri = { 325 .row_id = rowid, 326 .row_table = (char *) table, 327 .diagnostic = (char *) diagnostic 328 }; 329 enum GNUNET_DB_QueryStatus qs; 330 331 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 332 "Row %llu of `%s' is bad: %s\n", 333 (unsigned long long) rowid, 334 table, 335 diagnostic); 336 qs = TALER_AUDITORDB_insert_row_inconsistency (TALER_ARL_adb, 337 &ri); 338 if (0 > qs) 339 { 340 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 341 global_qs = qs; 342 return false; 343 } 344 return true; 345 } 346 347 348 /** 349 * Function called with an AML staff status change we recorded earlier. 350 * Rebuilds our view of who was allowed to decide when. 351 * 352 * @param cls NULL 353 * @param decider_pub public key of the staff member 354 * @param decider_name legal name of the staff member 355 * @param is_active true if the member could act from @a last_change on 356 * @param read_only true if the member had read-only access 357 * @param master_sig_valid true if the master key really signed this status 358 * @param last_change when the status took effect 359 * @return #GNUNET_OK to continue to iterate 360 */ 361 static enum GNUNET_GenericReturnValue 362 known_staff_cb (void *cls, 363 const struct TALER_AmlOfficerPublicKeyP *decider_pub, 364 const char *decider_name, 365 bool is_active, 366 bool read_only, 367 bool master_sig_valid, 368 struct GNUNET_TIME_Timestamp last_change) 369 { 370 struct StaffMember *sm = staff_member_get (decider_pub); 371 struct StaffStatus ss = { 372 .last_change = last_change, 373 .is_active = is_active, 374 .read_only = read_only, 375 .master_sig_valid = master_sig_valid 376 }; 377 378 (void) cls; 379 (void) decider_name; 380 staff_member_add (sm, 381 &ss); 382 /* We are called oldest-first, so the last call for a member leaves the 383 status that was in force when this round began. */ 384 sm->known_at_start = true; 385 sm->active_at_start = is_active && master_sig_valid; 386 return GNUNET_OK; 387 } 388 389 390 /** 391 * Function called with a status change of an AML staff member the exchange 392 * recorded. Verifies the offline master key's signature on it and, if this 393 * is a status we have not seen before, records it. 394 * 395 * @param cls NULL 396 * @param rowid row in `aml_staff` 397 * @param decider_pub public key of the staff member 398 * @param master_sig signature by the offline master key, NULL if absent 399 * @param decider_name legal name of the staff member 400 * @param is_active true if the member may currently act 401 * @param read_only true if the member has read-only access 402 * @param last_change when the status took effect, as claimed 403 * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop 404 */ 405 static enum GNUNET_GenericReturnValue 406 check_staff_cb (void *cls, 407 uint64_t rowid, 408 const struct TALER_AmlOfficerPublicKeyP *decider_pub, 409 const struct TALER_MasterSignatureP *master_sig, 410 const char *decider_name, 411 bool is_active, 412 bool read_only, 413 struct GNUNET_TIME_Timestamp last_change) 414 { 415 struct StaffMember *sm = staff_member_get (decider_pub); 416 struct StaffStatus ss = { 417 .last_change = last_change, 418 .is_active = is_active, 419 .read_only = read_only, 420 .master_sig_valid = false 421 }; 422 enum GNUNET_DB_QueryStatus qs; 423 bool regression = false; 424 425 (void) cls; 426 TALER_ARL_USE_PP (aml_staff_uuid) = rowid + 1; 427 for (unsigned int i = 0; i < sm->num_statuses; i++) 428 { 429 if (GNUNET_TIME_timestamp_cmp (sm->statuses[i].last_change, 430 ==, 431 last_change)) 432 return GNUNET_OK; /* already seen and judged in an earlier round */ 433 if (GNUNET_TIME_timestamp_cmp (sm->statuses[i].last_change, 434 >, 435 last_change)) 436 regression = true; 437 } 438 if (NULL == master_sig) 439 { 440 if (! report_row ("aml_staff", 441 rowid, 442 "staff status without a master key signature")) 443 return GNUNET_SYSERR; 444 } 445 else if (GNUNET_OK != 446 TALER_exchange_offline_aml_officer_status_verify ( 447 decider_pub, 448 decider_name, 449 last_change, 450 is_active, 451 read_only, 452 &TALER_ARL_master_pub, 453 master_sig)) 454 { 455 if (! report_row ("aml_staff", 456 rowid, 457 "invalid master key signature on staff status") 458 ) 459 return GNUNET_SYSERR; 460 } 461 else 462 { 463 ss.master_sig_valid = true; 464 } 465 if (regression) 466 { 467 /* exchange_do_insert_aml_officer() refuses to store a status older than 468 the one it has, so seeing one means somebody wrote to the table 469 behind the exchange's back. */ 470 if (! report_row ("aml_staff", 471 rowid, 472 "staff status is older than one seen before")) 473 return GNUNET_SYSERR; 474 } 475 if (ss.master_sig_valid && is_active) 476 sm->enabled_this_round = true; 477 staff_member_add (sm, 478 &ss); 479 qs = TALER_AUDITORDB_insert_aml_staff (TALER_ARL_adb, 480 decider_pub, 481 decider_name, 482 is_active, 483 read_only, 484 ss.master_sig_valid, 485 last_change); 486 if (0 > qs) 487 { 488 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 489 global_qs = qs; 490 return GNUNET_SYSERR; 491 } 492 return GNUNET_OK; 493 } 494 495 496 /** 497 * Function called with an AML decision the exchange recorded. Checks the 498 * officer's signature and that the officer was allowed to make it. 499 * 500 * @param cls NULL 501 * @param rowid row in `aml_history` 502 * @param h_payto account the decision is about 503 * @param justification justification given 504 * @param decider_pub officer who decided, NULL if not recorded 505 * @param decider_sig the officer's signature, NULL if not recorded 506 * @param decision_time when the decision was taken, as claimed 507 * @param jproperties new account properties, NULL for none 508 * @param jnew_rules new KYC rules, NULL if not recorded 509 * @param new_measure_name measure to apply, NULL for none 510 * @param to_investigate whether staff should investigate the account 511 * @param attributes_expiration when attributes set with the decision expire 512 * @param h_attributes hash of the attributes set, NULL if none 513 * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop 514 */ 515 static enum GNUNET_GenericReturnValue 516 check_decision_cb (void *cls, 517 uint64_t rowid, 518 const struct TALER_NormalizedPaytoHashP *h_payto, 519 const char *justification, 520 const struct TALER_AmlOfficerPublicKeyP *decider_pub, 521 const struct TALER_AmlOfficerSignatureP *decider_sig, 522 struct GNUNET_TIME_Timestamp decision_time, 523 const json_t *jproperties, 524 const json_t *jnew_rules, 525 const char *new_measure_name, 526 bool to_investigate, 527 struct GNUNET_TIME_Timestamp attributes_expiration, 528 const struct GNUNET_HashCode *h_attributes) 529 { 530 struct StaffMember *sm; 531 const struct StaffStatus *ss; 532 533 (void) cls; 534 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 535 "Analyzing AML decision %llu taken at %s\n", 536 (unsigned long long) rowid, 537 GNUNET_TIME_timestamp2s (decision_time)); 538 TALER_ARL_USE_PP (aml_history_serial_id) = rowid + 1; 539 if (NULL == decider_pub) 540 { 541 /* exchange_do_insert_aml_decision() only writes an aml_history row when 542 it has a decider, so this cannot happen without tampering. */ 543 if (! report_row ("aml_history", 544 rowid, 545 "decision without an officer public key")) 546 return GNUNET_SYSERR; 547 return GNUNET_OK; 548 } 549 if (NULL == decider_sig) 550 { 551 if (! report_row ("aml_history", 552 rowid, 553 "decision without an officer signature")) 554 return GNUNET_SYSERR; 555 } 556 else if (NULL == jnew_rules) 557 { 558 /* The signature is over the new rules, so without them there is nothing 559 we could check it against. */ 560 if (! report_row ("aml_history", 561 rowid, 562 "decision without the new rules it signed over")) 563 return GNUNET_SYSERR; 564 } 565 else if (GNUNET_OK != 566 TALER_officer_aml_decision_verify_hashed ( 567 justification, 568 decision_time, 569 h_payto, 570 jnew_rules, 571 jproperties, 572 new_measure_name, 573 to_investigate, 574 decider_pub, 575 decider_sig, 576 attributes_expiration, 577 h_attributes)) 578 { 579 if (! report_row ("aml_history", 580 rowid, 581 "invalid officer signature on decision")) 582 return GNUNET_SYSERR; 583 } 584 sm = staff_member_get (decider_pub); 585 ss = staff_member_status_at (sm, 586 decision_time); 587 if (NULL == ss) 588 { 589 if (! report_row ("aml_history", 590 rowid, 591 "officer was not appointed when the decision was made")) 592 return GNUNET_SYSERR; 593 } 594 else if (! ss->is_active) 595 { 596 if (! report_row ("aml_history", 597 rowid, 598 "officer was not active when the decision was made")) 599 return GNUNET_SYSERR; 600 } 601 else if (ss->read_only) 602 { 603 if (! report_row ("aml_history", 604 rowid, 605 "officer had read-only access when deciding")) 606 return GNUNET_SYSERR; 607 } 608 /* The eligibility check above trusts decision_time, which the exchange 609 picks. This one does not: whatever the decision claims, a record for an 610 officer we already knew to be disabled has no business appearing now. */ 611 if (sm->known_at_start && 612 (! sm->active_at_start) && 613 (! sm->enabled_this_round)) 614 { 615 if (! report_row ("aml_history", 616 rowid, 617 "decision appeared after the officer was disabled")) 618 return GNUNET_SYSERR; 619 } 620 return GNUNET_OK; 621 } 622 623 624 /** 625 * Function called with a row of `legitimization_outcomes`, the table that 626 * says which KYC rules currently apply to an account. Checks that something 627 * in the exchange's books accounts for the row existing. 628 * 629 * The exchange has exactly three ways to create one, and each leaves its own 630 * trace: 631 * 632 * - an AML officer decides, and `exchange_do_insert_aml_decision()` writes an 633 * `aml_history` row pointing at the outcome. #check_decision_cb() has 634 * already checked that decision's signature and the officer's appointment, 635 * so this is the strongest of the three; 636 * - a customer passes (or fails) a KYC check, and the AML program's verdict 637 * on it becomes the outcome. What is left of the check is a row in 638 * `legitimization_processes` for the same account, started no later than 639 * the decision; 640 * - the rules in force expire, and `exchange_do_insert_successor_measure()` 641 * or a re-run of the AML program replaces them. What is left of that is 642 * the expired predecessor outcome. 643 * 644 * A row with none of the three was written by something other than the 645 * exchange's own code. Note that the third alternative is the weak one: an 646 * account that has ever had an outcome expire can be given further outcomes 647 * without any fresh justification, exactly as the exchange legitimately does 648 * on expiry. The check is a floor, not a proof that the rules are right. 649 * 650 * @param cls NULL 651 * @param rowid row in `legitimization_outcomes` 652 * @param h_payto account the outcome is about 653 * @param decision_time when the outcome was decided, as claimed 654 * @param expiration_time when the outcome expires 655 * @param has_aml_decision an `aml_history` row points at this outcome 656 * @param has_legitimization_process the account had a legitimization process 657 * that had started by @a decision_time 658 * @param has_expired_predecessor an earlier outcome for the account had 659 * expired by @a decision_time 660 * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop 661 */ 662 static enum GNUNET_GenericReturnValue 663 check_outcome_cb (void *cls, 664 uint64_t rowid, 665 const struct TALER_NormalizedPaytoHashP *h_payto, 666 struct GNUNET_TIME_Timestamp decision_time, 667 struct GNUNET_TIME_Timestamp expiration_time, 668 bool has_aml_decision, 669 bool has_legitimization_process, 670 bool has_expired_predecessor) 671 { 672 (void) cls; 673 (void) h_payto; 674 (void) expiration_time; 675 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 676 "Analyzing legitimization outcome %llu decided at %s\n", 677 (unsigned long long) rowid, 678 GNUNET_TIME_timestamp2s (decision_time)); 679 TALER_ARL_USE_PP (legitimization_outcome_serial_id) = rowid + 1; 680 if (has_aml_decision || 681 has_legitimization_process || 682 has_expired_predecessor) 683 return GNUNET_OK; 684 if (! report_row ("legitimization_outcomes", 685 rowid, 686 "KYC rules in force without an AML decision," 687 " a legitimization process or an expired predecessor")) 688 return GNUNET_SYSERR; 689 return GNUNET_OK; 690 } 691 692 693 /** 694 * Free a `struct StaffMember`. 695 * 696 * @param cls NULL 697 * @param key unused 698 * @param value the `struct StaffMember` to free 699 * @return #GNUNET_OK 700 */ 701 static enum GNUNET_GenericReturnValue 702 free_staff_member (void *cls, 703 const struct GNUNET_HashCode *key, 704 void *value) 705 { 706 struct StaffMember *sm = value; 707 708 (void) cls; 709 (void) key; 710 GNUNET_assert (GNUNET_YES == 711 GNUNET_CONTAINER_multihashmap_remove (staff_map, 712 &sm->key, 713 sm)); 714 GNUNET_array_grow (sm->statuses, 715 sm->num_statuses, 716 0); 717 GNUNET_free (sm); 718 return GNUNET_OK; 719 } 720 721 722 /** 723 * Drop our in-memory view of the AML staff. 724 */ 725 static void 726 clear_staff_map (void) 727 { 728 if (NULL == staff_map) 729 return; 730 GNUNET_CONTAINER_multihashmap_iterate (staff_map, 731 &free_staff_member, 732 NULL); 733 GNUNET_CONTAINER_multihashmap_destroy (staff_map); 734 staff_map = NULL; 735 } 736 737 738 /** 739 * Analyze the AML staff appointments and the decisions they justify. 740 * 741 * @param cls NULL 742 * @return transaction status code 743 */ 744 static enum GNUNET_DB_QueryStatus 745 analyze_aml (void *cls) 746 { 747 enum GNUNET_DB_QueryStatus qs; 748 bool had_pp; 749 750 (void) cls; 751 global_qs = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; 752 clear_staff_map (); 753 staff_map = GNUNET_CONTAINER_multihashmap_create (32, 754 GNUNET_YES); 755 qs = TALER_AUDITORDB_get_auditor_progress ( 756 TALER_ARL_adb, 757 TALER_ARL_GET_PP (aml_history_serial_id), 758 TALER_ARL_GET_PP (aml_staff_uuid), 759 TALER_ARL_GET_PP (legitimization_outcome_serial_id), 760 NULL); 761 if (0 > qs) 762 { 763 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 764 return qs; 765 } 766 /* Not from @a qs: auditor_do_get_auditor_progress() returns one row per 767 key whether or not the key is on file, so the query status says nothing 768 about whether we have run before. */ 769 had_pp = (0 != TALER_ARL_USE_PP (aml_staff_uuid)) || 770 (0 != TALER_ARL_USE_PP (aml_history_serial_id)) || 771 (0 != TALER_ARL_USE_PP (legitimization_outcome_serial_id)); 772 if (had_pp) 773 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 774 "Resuming AML audit at %llu/%llu/%llu\n", 775 (unsigned long long) TALER_ARL_USE_PP (aml_staff_uuid), 776 (unsigned long long) TALER_ARL_USE_PP (aml_history_serial_id), 777 (unsigned long long) TALER_ARL_USE_PP ( 778 legitimization_outcome_serial_id)); 779 else 780 GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, 781 "First analysis using AML auditor, starting audit from scratch\n"); 782 783 /* What we knew about the staff when this round began. */ 784 qs = TALER_AUDITORDB_iterate_aml_staff (TALER_ARL_adb, 785 &known_staff_cb, 786 NULL); 787 if (0 > qs) 788 { 789 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 790 return qs; 791 } 792 793 /* Status changes the exchange recorded since we last looked. Must run 794 before the decisions below, which are judged against the result. */ 795 qs = TALER_EXCHANGEDB_iterate_aml_staff_above_serial_id ( 796 TALER_ARL_edb, 797 TALER_ARL_USE_PP (aml_staff_uuid), 798 &check_staff_cb, 799 NULL); 800 if (0 > qs) 801 { 802 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 803 return qs; 804 } 805 if (0 > global_qs) 806 return global_qs; 807 808 qs = TALER_EXCHANGEDB_iterate_aml_history_above_serial_id ( 809 TALER_ARL_edb, 810 TALER_ARL_USE_PP (aml_history_serial_id), 811 &check_decision_cb, 812 NULL); 813 if (0 > qs) 814 { 815 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 816 return qs; 817 } 818 if (0 > global_qs) 819 return global_qs; 820 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 821 "Analyzed %d AML decisions\n", 822 (int) qs); 823 824 /* Runs after the decisions: an outcome an AML officer signed is accounted 825 for by the `aml_history' row we just judged, so leaving that judgement 826 for a later round would report the outcome against an incomplete 827 picture. */ 828 qs = TALER_EXCHANGEDB_iterate_legitimization_outcomes_above_serial_id ( 829 TALER_ARL_edb, 830 TALER_ARL_USE_PP (legitimization_outcome_serial_id), 831 &check_outcome_cb, 832 NULL); 833 if (0 > qs) 834 { 835 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 836 return qs; 837 } 838 if (0 > global_qs) 839 return global_qs; 840 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 841 "Analyzed %d legitimization outcomes\n", 842 (int) qs); 843 /* Insert first (a no-op once the rows exist), then update: the three 844 progress points are written on the very first round as well as on 845 every later one. Branching on @e had_pp instead would leave the 846 rows uncreated for ever, and the helper would re-audit the whole 847 history every time it woke up. */ 848 qs = TALER_AUDITORDB_insert_auditor_progress ( 849 TALER_ARL_adb, 850 TALER_ARL_SET_PP (aml_history_serial_id), 851 TALER_ARL_SET_PP (aml_staff_uuid), 852 TALER_ARL_SET_PP (legitimization_outcome_serial_id), 853 NULL); 854 if (0 > qs) 855 { 856 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 857 "Failed to update auditor DB, not recording progress\n"); 858 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 859 return qs; 860 } 861 qs = TALER_AUDITORDB_update_auditor_progress ( 862 TALER_ARL_adb, 863 TALER_ARL_SET_PP (aml_history_serial_id), 864 TALER_ARL_SET_PP (aml_staff_uuid), 865 TALER_ARL_SET_PP (legitimization_outcome_serial_id), 866 NULL); 867 if (0 > qs) 868 { 869 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 870 "Failed to update auditor DB, not recording progress\n"); 871 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 872 return qs; 873 } 874 return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; 875 } 876 877 878 /** 879 * Function called when the exchange recorded an AML staff status change or 880 * an AML decision. Re-runs the analysis. 881 * 882 * @param cls NULL 883 * @param extra additional event data provided 884 * @param extra_size number of bytes in @a extra 885 */ 886 static void 887 db_notify (void *cls, 888 const void *extra, 889 size_t extra_size) 890 { 891 (void) cls; 892 (void) extra; 893 (void) extra_size; 894 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 895 "Received notification for new AML data\n"); 896 if (GNUNET_OK != 897 TALER_ARL_setup_sessions_and_run (&analyze_aml, 898 NULL)) 899 { 900 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 901 "Audit failed\n"); 902 GNUNET_SCHEDULER_shutdown (); 903 global_ret = EXIT_FAILURE; 904 return; 905 } 906 } 907 908 909 /** 910 * Function called on shutdown. 911 * 912 * @param cls NULL 913 */ 914 static void 915 do_shutdown (void *cls) 916 { 917 (void) cls; 918 if (NULL != eh) 919 { 920 TALER_AUDITORDB_event_listen_cancel (eh); 921 eh = NULL; 922 } 923 clear_staff_map (); 924 TALER_ARL_done (); 925 } 926 927 928 /** 929 * Main function that will be run. 930 * 931 * @param cls closure 932 * @param args remaining command-line arguments 933 * @param cfgfile name of the configuration file used (for saving, can be NULL!) 934 * @param c configuration 935 */ 936 static void 937 run (void *cls, 938 char *const *args, 939 const char *cfgfile, 940 const struct GNUNET_CONFIGURATION_Handle *c) 941 { 942 (void) cls; 943 (void) args; 944 (void) cfgfile; 945 cfg = c; 946 GNUNET_SCHEDULER_add_shutdown (&do_shutdown, 947 NULL); 948 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 949 "Launching AML auditor\n"); 950 if (GNUNET_OK != 951 TALER_ARL_init (c)) 952 { 953 global_ret = EXIT_FAILURE; 954 return; 955 } 956 if (test_mode != 1) 957 { 958 struct GNUNET_DB_EventHeaderP es = { 959 .size = htons (sizeof (es)), 960 .type = htons (TALER_DBEVENT_EXCHANGE_AUDITOR_WAKE_HELPER_AML) 961 }; 962 963 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 964 "Running helper indefinitely\n"); 965 eh = TALER_AUDITORDB_event_listen (TALER_ARL_adb, 966 &es, 967 GNUNET_TIME_UNIT_FOREVER_REL, 968 &db_notify, 969 NULL); 970 } 971 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 972 "Starting audit\n"); 973 if (GNUNET_OK != 974 TALER_ARL_setup_sessions_and_run (&analyze_aml, 975 NULL)) 976 { 977 GNUNET_SCHEDULER_shutdown (); 978 global_ret = EXIT_FAILURE; 979 return; 980 } 981 } 982 983 984 /** 985 * The main function of the AML auditing helper tool. 986 * 987 * @param argc number of arguments from the command line 988 * @param argv command line arguments 989 * @return 0 ok, 1 on error 990 */ 991 int 992 main (int argc, 993 char *const *argv) 994 { 995 const struct GNUNET_GETOPT_CommandLineOption options[] = { 996 GNUNET_GETOPT_option_flag ('i', 997 "internal", 998 "perform checks only applicable for exchange-internal audits", 999 &internal_checks), 1000 GNUNET_GETOPT_option_flag ('t', 1001 "test", 1002 "run in test mode and exit when idle", 1003 &test_mode), 1004 GNUNET_GETOPT_option_timetravel ('T', 1005 "timetravel"), 1006 GNUNET_GETOPT_OPTION_END 1007 }; 1008 enum GNUNET_GenericReturnValue ret; 1009 1010 ret = GNUNET_PROGRAM_run ( 1011 TALER_AUDITOR_project_data (), 1012 argc, 1013 argv, 1014 "taler-helper-auditor-aml", 1015 gettext_noop ( 1016 "Audit exchange database for AML decisions made by unauthorised staff"), 1017 options, 1018 &run, 1019 NULL); 1020 if (GNUNET_SYSERR == ret) 1021 return EXIT_INVALIDARGUMENT; 1022 if (GNUNET_NO == ret) 1023 return EXIT_SUCCESS; 1024 return global_ret; 1025 } 1026 1027 1028 /* end of taler-helper-auditor-aml.c */