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