exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

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 */