taler-exchange-sanctionscheck.c (26899B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2025 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 taler-exchange-sanctionscheck.c 18 * @brief Process that checks all existing customers against a sanctions list 19 * @author Christian Grothoff 20 */ 21 #include "platform.h" 22 #include <gnunet/gnunet_util_lib.h> 23 #include <jansson.h> 24 #include <pthread.h> 25 #include <microhttpd.h> 26 #include "taler/taler_dbevents.h" 27 #include "exchangedb_lib.h" 28 #include "taler/taler_json_lib.h" 29 #include "taler/taler_kyclogic_lib.h" 30 #include <math.h> 31 #include "exchange-database/release_revolving_shard.h" 32 #include "exchange-database/commit.h" 33 #include "exchange-database/preflight.h" 34 #include "exchange-database/insert_sanction_list_hit.h" 35 #include "exchange-database/start.h" 36 #include "exchange-database/rollback.h" 37 #include "exchange-database/iterate_all_kyc_attributes.h" 38 #include "exchange-database/event_listen.h" 39 #include "exchange-database/event_listen_cancel.h" 40 #include "exchange-database/start.h" 41 42 /** 43 * Account we are currently checking. 44 */ 45 struct Account 46 { 47 /** 48 * Kept in a DLL. 49 */ 50 struct Account *next; 51 52 /** 53 * Kept in a DLL. 54 */ 55 struct Account *prev; 56 57 /** 58 * Original properties of the account. 59 */ 60 json_t *properties; 61 62 /** 63 * Evaluation entry with the sanction list checker. 64 */ 65 struct TALER_KYCLOGIC_EvaluationEntry *ee; 66 67 /** 68 * Row of the attributes in the kyc_attributes table. 69 */ 70 uint64_t row_id; 71 72 /** 73 * Hash of the normalized payto:// URI of the account. 74 */ 75 struct TALER_NormalizedPaytoHashP h_payto; 76 77 }; 78 79 80 /** 81 * The exchange's configuration (global) 82 */ 83 static const struct GNUNET_CONFIGURATION_Handle *cfg; 84 85 /** 86 * Our DB plugin. 87 */ 88 static struct TALER_EXCHANGEDB_PostgresContext *pg; 89 90 /** 91 * Helper process for the actual rating. 92 */ 93 static struct TALER_KYCLOGIC_SanctionRater *sr; 94 95 /** 96 * Value to return from main(). 0 on success, non-zero on 97 * on serious errors. 98 */ 99 static int global_ret; 100 101 /** 102 * Key used to encrypt KYC attribute data in our database. 103 */ 104 static struct TALER_AttributeEncryptionKeyP attribute_key; 105 106 /** 107 * Account rules to set if we have a good match against the 108 * sanction list and should freeze an account immediately. 109 */ 110 static json_t *freeze_rules; 111 112 /** 113 * Head of DLL of accounts we are currently checking. 114 */ 115 static struct Account *acc_head; 116 117 /** 118 * Tail of DLL of accounts we are currently checking. 119 */ 120 static struct Account *acc_tail; 121 122 /** 123 * Minimum row ID to process records from. This is the value we persist, 124 * and it is only advanced once the transaction that evaluated the 125 * respective rows was committed. 126 */ 127 static uint64_t min_row_id; 128 129 /** 130 * Highest row ID evaluated in the current transaction. Folded into 131 * #min_row_id when that transaction commits. 132 */ 133 static uint64_t max_row_id; 134 135 /** 136 * File descriptor with the name of the file where we track our 137 * progress. 138 */ 139 static int min_row_fd = -1; 140 141 /** 142 * '-t' command line flag. Disables background processing mode. 143 */ 144 static int testmode; 145 146 /** 147 * '-r' command line flag. Restarts analysis from scratch (for 148 * fresh sanction list). 149 */ 150 static int reset; 151 152 /** 153 * '-n' command line flag. Do not actually run, only reset. 154 */ 155 static int norun; 156 157 /** 158 * Handler to learn about updated KYC attributes. 159 */ 160 static struct GNUNET_DB_EventHandler *eh; 161 162 /** 163 * Set to true if we should restart immediately after 164 * finishing the current transaction. 165 */ 166 static bool restart_now; 167 168 /** 169 * Set to true while we are in a database transaction iterating 170 * over KYC attributes. 171 */ 172 static bool in_transaction; 173 174 /** 175 * Match quality needed for instantly freezing an account. 176 */ 177 static float freeze_rating_limit = 0.9; 178 179 /** 180 * Match confidence needed for instantly freezing an account. 181 */ 182 static float freeze_confidence_limit = 0.9; 183 184 /** 185 * Threshold on rating/confidence that must be exceeded to begin an 186 * investigation (when the account is not automatically frozen). A low 187 * confidence relative to the rating raises the ratio, which is intentional: 188 * the less sure the automated match is, the more a human should look. 189 */ 190 static float investigation_limit = 0.9; 191 192 /** 193 * Minimum match confidence for an investigation. This gates the ratio test 194 * above so that near-zero-confidence noise (e.g. a fuzzy match against an 195 * attribute that is not a name at all) does not, via its large ratio, flag 196 * every account. Optional; defaults to 0 to preserve the historic behaviour 197 * of having no confidence floor. 198 */ 199 static float investigation_confidence_limit = 0.0; 200 201 /** 202 * Write @a min_row_id to @a min_row_fd. 203 */ 204 static void 205 sync_row (void) 206 { 207 uint64_t r = GNUNET_htonll (min_row_id); 208 ssize_t rval; 209 210 GNUNET_assert (-1 != min_row_fd); 211 GNUNET_break (0 == lseek (min_row_fd, 212 0, 213 SEEK_SET)); 214 rval = write (min_row_fd, 215 &r, 216 sizeof (r)); 217 if (rval != (ssize_t) sizeof (r)) 218 { 219 GNUNET_break (-1 == rval); 220 GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, 221 "write"); 222 } 223 GNUNET_break (0 == 224 fsync (min_row_fd)); 225 } 226 227 228 /** 229 * We're being aborted with CTRL-C (or SIGTERM). Shut down. 230 * 231 * @param cls closure 232 */ 233 static void 234 shutdown_task (void *cls) 235 { 236 struct Account *acc; 237 238 (void) cls; 239 if (-1 != min_row_fd) 240 { 241 sync_row (); 242 GNUNET_break (0 == close (min_row_fd)); 243 min_row_fd = -1; 244 } 245 while (NULL != (acc = acc_head)) 246 { 247 GNUNET_CONTAINER_DLL_remove (acc_head, 248 acc_tail, 249 acc); 250 json_decref (acc->properties); 251 GNUNET_free (acc); 252 } 253 if (NULL != eh) 254 { 255 TALER_EXCHANGEDB_event_listen_cancel (pg, 256 eh); 257 eh = NULL; 258 } 259 if (NULL != sr) 260 { 261 TALER_KYCLOGIC_sanction_rater_stop (sr); 262 sr = NULL; 263 } 264 TALER_EXCHANGEDB_disconnect (pg); 265 pg = NULL; 266 cfg = NULL; 267 } 268 269 270 /** 271 * Convert double value @a d to numeric score. 272 * 273 * @param d score from [0,1] 274 * @return value from 0 to 1 billion proportional to @a d 275 */ 276 static uint64_t 277 double_to_billion (double d) 278 { 279 double r; 280 281 GNUNET_break (d >= 0); 282 GNUNET_break (d <= 1.0); 283 r = round (d * 1000000000); 284 return (uint64_t) r; 285 } 286 287 288 /** 289 * Start the actual database transaction. 290 */ 291 static void 292 begin_transaction (void); 293 294 295 /** 296 * Function called with the result of a sanction evaluation. 297 * 298 * @param cls closure 299 * @param ec error code, #TALER_EC_NONE on success 300 * @param best_match identifies the sanction list entry with the best match 301 * @param rating likelihood of the match, from 0 (none) to 1 (perfect) 302 * @param confidence confidence in the evaluation, from 0 (none) to 1 (perfect) 303 */ 304 static void 305 sanction_cb (void *cls, 306 enum TALER_ErrorCode ec, 307 const char *best_match, 308 double rating, 309 double confidence) 310 { 311 struct Account *acc = cls; 312 bool freeze = false; 313 bool investigate = false; 314 315 if (TALER_EC_NONE != ec) 316 { 317 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 318 "Error %s (%d) when analyzing record\n", 319 TALER_ErrorCode_get_hint (ec), 320 (int) ec); 321 TALER_EXCHANGEDB_rollback (pg); 322 global_ret = 1; 323 GNUNET_SCHEDULER_shutdown (); 324 return; 325 } 326 if ( (rating > (double) freeze_rating_limit) && 327 (confidence > (double) freeze_confidence_limit) ) 328 { 329 freeze = true; 330 investigate = true; 331 } 332 else if ( (rating > (double) investigation_limit * confidence) && 333 (confidence > (double) investigation_confidence_limit) ) 334 { 335 investigate = true; 336 } 337 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 338 "Best match is %f/%f at `%s' will %s\n", 339 rating, 340 confidence, 341 best_match, 342 freeze || investigate 343 ? (freeze 344 ? "freeze" 345 : "investigate") 346 : "do nothing"); 347 if (freeze || investigate) 348 { 349 static const char *freeze_event[] = { 350 "sanction-list-hit-account-frozen", 351 }; 352 static const char *partial_match_event[] = { 353 "sanction-list-hit-partial-account-investigated", 354 }; 355 enum GNUNET_DB_QueryStatus qs; 356 json_t *properties; 357 const char **events; 358 json_t *new_rules; 359 360 new_rules = freeze ? freeze_rules : NULL; 361 events = freeze ? freeze_event : partial_match_event; 362 properties = GNUNET_JSON_PACK ( 363 GNUNET_JSON_pack_string ("SANCTION_LIST_BEST_MATCH", 364 best_match), 365 GNUNET_JSON_pack_uint64 ("SANCTION_LIST_RATING", 366 double_to_billion (rating)), 367 GNUNET_JSON_pack_uint64 ("SANCTION_LIST_CONFIDENCE", 368 double_to_billion (confidence)), 369 GNUNET_JSON_pack_string ("AML_INVESTIGATION_STATE", 370 "INVESTIGATION_PENDING"), 371 GNUNET_JSON_pack_string ("AML_INVESTIGATION_TIGGER", 372 "SANCTION_LIST_MATCH")); 373 if (NULL != acc->properties) 374 GNUNET_assert (0 == 375 json_object_update_missing (properties, 376 acc->properties)); 377 qs = TALER_EXCHANGEDB_insert_sanction_list_hit (pg, 378 &acc->h_payto, 379 investigate, 380 new_rules, 381 properties, 382 1, 383 events); 384 json_decref (properties); 385 if (qs < 0) 386 { 387 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 388 "Failed to insert sanction list evaluation result\n"); 389 global_ret = EXIT_FAILURE; 390 GNUNET_SCHEDULER_shutdown (); 391 return; 392 } 393 } 394 GNUNET_CONTAINER_DLL_remove (acc_head, 395 acc_tail, 396 acc); 397 json_decref (acc->properties); 398 /* Evaluations complete out of order, so keep the highest row we did. */ 399 max_row_id = GNUNET_MAX (max_row_id, 400 acc->row_id); 401 GNUNET_free (acc); 402 if (NULL != acc_head) 403 return; /* more work */ 404 { 405 enum GNUNET_DB_QueryStatus qs; 406 407 qs = TALER_EXCHANGEDB_commit (pg); 408 if (qs < 0) 409 { 410 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 411 "Failed to commit DB transaction\n"); 412 global_ret = EXIT_NOTCONFIGURED; 413 GNUNET_SCHEDULER_shutdown (); 414 return; 415 } 416 } 417 in_transaction = false; 418 /* Our writes are durable now, so the resume point may move. */ 419 min_row_id = max_row_id; 420 sync_row (); 421 if (restart_now) 422 { 423 begin_transaction (); 424 return; 425 } 426 if (testmode) 427 GNUNET_SCHEDULER_shutdown (); 428 } 429 430 431 /** 432 * Function called on each account. 433 * 434 * @param cls closure 435 * @param row_id row of the attributes in the database 436 * @param h_payto account for which the attribute data is stored 437 * @param provider_name provider that must be checked 438 * @param collection_time when was the data collected 439 * @param expiration_time when does the data expire 440 * @param properties properties that were set for @a h_payto 441 * @param enc_attributes_size number of bytes in @a enc_attributes 442 * @param enc_attributes encrypted attribute data 443 * @return true to continue to iterate 444 */ 445 static bool 446 account_cb (void *cls, 447 uint64_t row_id, 448 const struct TALER_NormalizedPaytoHashP *h_payto, 449 const char *provider_name, 450 struct GNUNET_TIME_Timestamp collection_time, 451 struct GNUNET_TIME_Timestamp expiration_time, 452 const json_t *properties, 453 size_t enc_attributes_size, 454 const void *enc_attributes) 455 { 456 json_t *attributes; 457 struct Account *acc; 458 459 (void) cls; 460 if (json_boolean_value (json_object_get (properties, 461 "SANCTION_LIST_SUPPRESS"))) 462 { 463 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 464 "Skipping %llu as suppressed by staff as false-positive\n", 465 (unsigned long long) row_id); 466 return true; 467 } 468 attributes = TALER_CRYPTO_kyc_attributes_decrypt (&attribute_key, 469 enc_attributes, 470 enc_attributes_size); 471 if (NULL == attributes) 472 { 473 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 474 "Failed to decrypt attributes at row #%llu\n", 475 (unsigned long long) row_id); 476 return true; 477 } 478 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 479 "Found KYC data %llu\n", 480 (unsigned long long) row_id); 481 #if DEBUG 482 json_dumpf (attributes, 483 stderr, 484 JSON_INDENT (2)); 485 #endif 486 acc = GNUNET_new (struct Account); 487 acc->row_id = row_id; 488 acc->h_payto = *h_payto; 489 acc->properties = json_incref ((json_t *) properties); 490 acc->ee = TALER_KYCLOGIC_sanction_rater_eval (sr, 491 attributes, 492 &sanction_cb, 493 acc); 494 if (NULL == acc->ee) 495 { 496 json_decref (acc->properties); 497 GNUNET_free (acc); 498 return false; 499 } 500 GNUNET_CONTAINER_DLL_insert (acc_head, 501 acc_tail, 502 acc); 503 return true; 504 } 505 506 507 /** 508 * Initialize JSON rules for freezing an account. 509 * 510 * @return true on success 511 */ 512 static bool 513 init_freeze (void) 514 { 515 char *currency; 516 struct TALER_Amount zero; 517 json_t *rules; 518 json_t *verboten; 519 520 if (GNUNET_OK != 521 TALER_config_get_currency (cfg, 522 "exchange", 523 ¤cy)) 524 { 525 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 526 "exchange", 527 "currency"); 528 return false; 529 } 530 GNUNET_assert (GNUNET_OK == 531 TALER_amount_set_zero (currency, 532 &zero)); 533 verboten = json_array (); 534 GNUNET_assert (NULL != verboten); 535 GNUNET_assert (0 == 536 json_array_append_new (verboten, 537 json_string ("verboten"))); 538 rules = json_array (); 539 GNUNET_assert (NULL != rules); 540 for (enum TALER_KYCLOGIC_KycTriggerEvent et = 541 TALER_KYCLOGIC_KYC_TRIGGER_WITHDRAW; 542 et <= TALER_KYCLOGIC_KYC_TRIGGER_REFUND; 543 et++) 544 { 545 json_t *rule; 546 547 rule = GNUNET_JSON_PACK ( 548 TALER_JSON_pack_kycte ("operation_type", 549 et), 550 TALER_JSON_pack_amount ("threshold", 551 &zero), 552 GNUNET_JSON_pack_time_rel ("timeframe", 553 GNUNET_TIME_UNIT_YEARS), 554 GNUNET_JSON_pack_array_incref ("measures", 555 verboten), 556 GNUNET_JSON_pack_uint64 ("display_priority", 557 1), 558 GNUNET_JSON_pack_bool ("exposed", 559 false), 560 GNUNET_JSON_pack_bool ("is_and_combinator", 561 false)); 562 GNUNET_assert (0 == 563 json_array_append_new (rules, 564 rule)); 565 } 566 json_decref (verboten); 567 freeze_rules = 568 GNUNET_JSON_PACK ( 569 GNUNET_JSON_pack_timestamp ("expiration_time", 570 GNUNET_TIME_UNIT_FOREVER_TS), 571 GNUNET_JSON_pack_array_steal ("rules", 572 rules)); 573 return true; 574 } 575 576 577 static void 578 begin_transaction () 579 { 580 enum GNUNET_DB_QueryStatus qs; 581 582 restart_now = false; 583 max_row_id = min_row_id; 584 GNUNET_assert (! in_transaction); 585 if (GNUNET_OK != 586 TALER_EXCHANGEDB_start (pg, 587 "sanctionscheck")) 588 { 589 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 590 "Failed to begin DB transaction\n"); 591 global_ret = EXIT_NOTCONFIGURED; 592 GNUNET_SCHEDULER_shutdown (); 593 return; 594 } 595 in_transaction = true; 596 /* FIXME-10063: we may want to eventually limit the number of 597 records we process in a single transaction. */ 598 qs = TALER_EXCHANGEDB_iterate_all_kyc_attributes (pg, 599 min_row_id, 600 &account_cb, 601 NULL); 602 if (qs < 0) 603 { 604 global_ret = EXIT_FAILURE; 605 GNUNET_break (0); 606 GNUNET_SCHEDULER_shutdown (); 607 return; 608 } 609 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 610 { 611 TALER_EXCHANGEDB_rollback (pg); 612 if (testmode) 613 GNUNET_SCHEDULER_shutdown (); 614 return; 615 } 616 if (NULL == acc_head) 617 in_transaction = false; 618 if ( (NULL == acc_head) && 619 (testmode) ) 620 { 621 /* no work, not incremental, we are done */ 622 GNUNET_SCHEDULER_shutdown (); 623 } 624 } 625 626 627 /** 628 * Function called on new KYC attributes being available in Postgres. 629 * 630 * @param cls closure 631 * @param extra additional event data provided 632 * @param extra_size number of bytes in @a extra 633 */ 634 static void 635 db_event_cb (void *cls, 636 const void *extra, 637 size_t extra_size) 638 { 639 GNUNET_break (NULL == cls); 640 (void) extra; 641 (void) extra_size; 642 if (in_transaction) 643 restart_now = true; 644 else 645 begin_transaction (); 646 } 647 648 649 /** 650 * First task. 651 * 652 * @param cls closure, NULL 653 * @param args remaining command-line arguments 654 * @param cfgfile name of the configuration file used (for saving, can be NULL!) 655 * @param c configuration 656 */ 657 static void 658 run (void *cls, 659 char *const *args, 660 const char *cfgfile, 661 const struct GNUNET_CONFIGURATION_Handle *c) 662 { 663 (void) cls; 664 (void) cfgfile; 665 cfg = c; 666 if (GNUNET_OK != 667 GNUNET_CONFIGURATION_get_value_float (cfg, 668 "exchange-sanctionscheck", 669 "FREEZE_RATING_LIMIT", 670 &freeze_rating_limit)) 671 { 672 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 673 "exchange-sanctionscheck", 674 "FREEZE_RATING_LIMIT"); 675 global_ret = EXIT_NOTCONFIGURED; 676 return; 677 } 678 if (GNUNET_OK != 679 GNUNET_CONFIGURATION_get_value_float (cfg, 680 "exchange-sanctionscheck", 681 "FREEZE_CONFIDENCE_LIMIT", 682 &freeze_confidence_limit)) 683 { 684 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 685 "exchange-sanctionscheck", 686 "FREEZE_CONFIDENCE_LIMIT"); 687 global_ret = EXIT_NOTCONFIGURED; 688 return; 689 } 690 if (GNUNET_OK != 691 GNUNET_CONFIGURATION_get_value_float (cfg, 692 "exchange-sanctionscheck", 693 "INVESTIGATION_LIMIT", 694 &investigation_limit)) 695 { 696 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 697 "exchange-sanctionscheck", 698 "INVESTIGATION_LIMIT"); 699 global_ret = EXIT_NOTCONFIGURED; 700 return; 701 } 702 if (GNUNET_OK != 703 GNUNET_CONFIGURATION_get_value_float (cfg, 704 "exchange-sanctionscheck", 705 "INVESTIGATION_CONFIDENCE_LIMIT", 706 &investigation_confidence_limit)) 707 { 708 /* Optional; absence restores the historic no-floor behaviour. It gates 709 every investigation, so say which value is in force. */ 710 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 711 "INVESTIGATION_CONFIDENCE_LIMIT not configured, using %f\n", 712 (double) investigation_confidence_limit); 713 } 714 if (! init_freeze ()) 715 return; 716 { 717 char *attr_enc_key_str; 718 719 if (GNUNET_OK != 720 GNUNET_CONFIGURATION_get_value_string (cfg, 721 "exchange", 722 "ATTRIBUTE_ENCRYPTION_KEY", 723 &attr_enc_key_str)) 724 { 725 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 726 "exchange", 727 "ATTRIBUTE_ENCRYPTION_KEY"); 728 global_ret = EXIT_NOTCONFIGURED; 729 return; 730 } 731 GNUNET_CRYPTO_hash (attr_enc_key_str, 732 strlen (attr_enc_key_str), 733 &attribute_key.hash); 734 GNUNET_free (attr_enc_key_str); 735 } 736 if (NULL == 737 (pg = TALER_EXCHANGEDB_connect (cfg))) 738 { 739 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 740 "Failed to initialize DB subsystem\n"); 741 global_ret = EXIT_NOTCONFIGURED; 742 return; 743 } 744 GNUNET_SCHEDULER_add_shutdown (&shutdown_task, 745 cls); 746 { 747 char *rater; 748 char **sargv = NULL; 749 unsigned int sargc = 0; 750 751 if (GNUNET_OK != 752 GNUNET_CONFIGURATION_get_value_string (cfg, 753 "exchange-sanctionscheck", 754 "RATER_COMMAND", 755 &rater)) 756 { 757 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 758 "exchange-sanctionscheck", 759 "RATER_COMMAND"); 760 global_ret = EXIT_NOTCONFIGURED; 761 return; 762 } 763 for (const char *tok = strtok (rater, 764 " "); 765 NULL != tok; 766 tok = strtok (NULL, 767 " ")) 768 { 769 char *arg = GNUNET_strdup (tok); 770 771 GNUNET_array_append (sargv, 772 sargc, 773 arg); 774 } 775 GNUNET_free (rater); 776 GNUNET_array_append (sargv, 777 sargc, 778 NULL); 779 sr = TALER_KYCLOGIC_sanction_rater_start (sargv[0], 780 sargv); 781 for (unsigned int i = 0; i<sargc; i++) 782 GNUNET_free (sargv[i]); 783 GNUNET_array_grow (sargv, 784 sargc, 785 0); 786 if (NULL == sr) 787 { 788 global_ret = EXIT_INVALIDARGUMENT; 789 GNUNET_SCHEDULER_shutdown (); 790 return; 791 } 792 } 793 { 794 char *min_row_fn; 795 uint64_t r; 796 797 if (GNUNET_OK != 798 GNUNET_CONFIGURATION_get_value_filename (cfg, 799 "exchange-sanctionscheck", 800 "MIN_ROW_FILENAME", 801 &min_row_fn)) 802 { 803 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 804 "exchange-sanctionscheck", 805 "MIN_ROW_FILENAME"); 806 global_ret = EXIT_NOTCONFIGURED; 807 GNUNET_SCHEDULER_shutdown (); 808 return; 809 } 810 if (reset && 811 (0 != unlink (min_row_fn)) && 812 (ENOENT != errno) ) 813 { 814 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, 815 "unlink", 816 min_row_fn); 817 GNUNET_free (min_row_fn); 818 global_ret = EXIT_NOPERMISSION; 819 GNUNET_SCHEDULER_shutdown (); 820 return; 821 } 822 if (GNUNET_OK != 823 GNUNET_DISK_directory_create_for_file (min_row_fn)) 824 { 825 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, 826 "open", 827 min_row_fn); 828 GNUNET_free (min_row_fn); 829 global_ret = EXIT_NOPERMISSION; 830 GNUNET_SCHEDULER_shutdown (); 831 return; 832 } 833 min_row_fd = open (min_row_fn, 834 O_CREAT | O_RDWR, 835 S_IRUSR | S_IWUSR); 836 if (-1 == min_row_fd) 837 { 838 GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, 839 "open", 840 min_row_fn); 841 GNUNET_free (min_row_fn); 842 global_ret = EXIT_NOTCONFIGURED; 843 GNUNET_SCHEDULER_shutdown (); 844 return; 845 } 846 if (sizeof (r) != 847 read (min_row_fd, 848 &r, 849 sizeof (r))) 850 { 851 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 852 "Could not read starting row from `%s', will start from 0\n", 853 min_row_fn); 854 min_row_id = 0; 855 } 856 else 857 { 858 min_row_id = GNUNET_ntohll (r); 859 } 860 GNUNET_free (min_row_fn); 861 } 862 if (norun) 863 { 864 GNUNET_SCHEDULER_shutdown (); 865 return; 866 } 867 if (! testmode) 868 { 869 struct GNUNET_DB_EventHeaderP hdr = { 870 .size = htons (sizeof (hdr)), 871 .type = htons (TALER_DBEVENT_EXCHANGE_NEW_KYC_ATTRIBUTES), 872 }; 873 874 eh = TALER_EXCHANGEDB_event_listen ( 875 pg, 876 GNUNET_TIME_UNIT_FOREVER_REL, 877 &hdr, 878 &db_event_cb, 879 NULL); 880 } 881 begin_transaction (); 882 } 883 884 885 /** 886 * The main function of taler-exchange-sanctionscheck 887 * 888 * @param argc number of arguments from the command line 889 * @param argv command line arguments 890 * @return 0 ok, non-zero on error 891 */ 892 int 893 main (int argc, 894 char *const *argv) 895 { 896 struct GNUNET_GETOPT_CommandLineOption options[] = { 897 GNUNET_GETOPT_option_version (VERSION), 898 GNUNET_GETOPT_option_flag ('n', 899 "norun", 900 "do not actually start a scan (to be used to only reset without starting a scan)", 901 &norun), 902 GNUNET_GETOPT_option_flag ('r', 903 "reset", 904 "rescan all records (to be used when the sanction list was updated)", 905 &reset), 906 GNUNET_GETOPT_option_flag ('t', 907 "test", 908 "run in test mode and exit when idle", 909 &testmode), 910 GNUNET_GETOPT_OPTION_END 911 }; 912 enum GNUNET_GenericReturnValue ret; 913 914 ret = GNUNET_PROGRAM_run ( 915 TALER_EXCHANGE_project_data (), 916 argc, argv, 917 "taler-exchange-sanctionscheck -- HELPER [HELPER ARGS]", 918 gettext_noop ( 919 "process that checks all existing customer accounts against a sanctions list"), 920 options, 921 &run, NULL); 922 json_decref (freeze_rules); 923 freeze_rules = NULL; 924 if (GNUNET_SYSERR == ret) 925 return EXIT_INVALIDARGUMENT; 926 if (GNUNET_NO == ret) 927 return EXIT_SUCCESS; 928 return global_ret; 929 } 930 931 932 /* end of taler-exchange-sanctionscheck.c */