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