LCOV - code coverage report
Current view: top level - auditor - taler-helper-auditor-aml.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 25.8 % 221 57
Test Date: 2026-10-17 15:46:49 Functions: 31.2 % 16 5

            Line data    Source code
       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            0 : staff_key (const struct TALER_AmlOfficerPublicKeyP *decider_pub,
     215              :            struct GNUNET_HashCode *key)
     216              : {
     217            0 :   GNUNET_CRYPTO_hash (decider_pub,
     218              :                       sizeof (*decider_pub),
     219              :                       key);
     220            0 : }
     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            0 : staff_member_get (const struct TALER_AmlOfficerPublicKeyP *decider_pub)
     231              : {
     232              :   struct GNUNET_HashCode key;
     233              :   struct StaffMember *sm;
     234              : 
     235            0 :   staff_key (decider_pub,
     236              :              &key);
     237            0 :   sm = GNUNET_CONTAINER_multihashmap_get (staff_map,
     238              :                                           &key);
     239            0 :   if (NULL != sm)
     240            0 :     return sm;
     241            0 :   sm = GNUNET_new (struct StaffMember);
     242            0 :   sm->key = key;
     243            0 :   GNUNET_assert (GNUNET_OK ==
     244              :                  GNUNET_CONTAINER_multihashmap_put (
     245              :                    staff_map,
     246              :                    &sm->key,
     247              :                    sm,
     248              :                    GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
     249            0 :   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            0 : staff_member_add (struct StaffMember *sm,
     262              :                   const struct StaffStatus *status)
     263              : {
     264              :   unsigned int i;
     265              : 
     266            0 :   GNUNET_array_grow (sm->statuses,
     267              :                      sm->num_statuses,
     268              :                      sm->num_statuses + 1);
     269            0 :   for (i = sm->num_statuses - 1; i > 0; i--)
     270              :   {
     271            0 :     if (GNUNET_TIME_timestamp_cmp (sm->statuses[i - 1].last_change,
     272              :                                    <=,
     273              :                                    status->last_change))
     274            0 :       break;
     275            0 :     sm->statuses[i] = sm->statuses[i - 1];
     276              :   }
     277            0 :   sm->statuses[i] = *status;
     278            0 : }
     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            0 : staff_member_status_at (const struct StaffMember *sm,
     291              :                         struct GNUNET_TIME_Timestamp when)
     292              : {
     293            0 :   const struct StaffStatus *ret = NULL;
     294              : 
     295            0 :   for (unsigned int i = 0; i < sm->num_statuses; i++)
     296              :   {
     297            0 :     const struct StaffStatus *ss = &sm->statuses[i];
     298              : 
     299            0 :     if (GNUNET_TIME_timestamp_cmp (ss->last_change,
     300              :                                    >,
     301              :                                    when))
     302            0 :       break; /* sorted, so no later entry can apply either */
     303            0 :     if (ss->master_sig_valid)
     304            0 :       ret = ss;
     305              :   }
     306            0 :   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            0 : report_row (const char *table,
     320              :             uint64_t rowid,
     321              :             const char *diagnostic)
     322              : {
     323            0 :   struct TALER_AUDITORDB_RowInconsistency ri = {
     324              :     .problem_row_id = rowid,
     325              :     .row_table = (char *) table,
     326              :     .diagnostic = (char *) diagnostic
     327              :   };
     328              :   enum GNUNET_DB_QueryStatus qs;
     329              : 
     330            0 :   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            0 :   qs = TALER_AUDITORDB_insert_row_inconsistency (TALER_ARL_adb,
     336              :                                                  &ri);
     337            0 :   if (0 > qs)
     338              :   {
     339            0 :     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
     340            0 :     global_qs = qs;
     341            0 :     return false;
     342              :   }
     343            0 :   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            0 : 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            0 :   struct StaffMember *sm = staff_member_get (decider_pub);
     370            0 :   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            0 :   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            0 :   sm->known_at_start = true;
     384            0 :   sm->active_at_start = is_active && master_sig_valid;
     385            0 :   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            0 : 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            0 :   struct StaffMember *sm = staff_member_get (decider_pub);
     415            0 :   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            0 :   bool regression = false;
     423              : 
     424              :   (void) cls;
     425            0 :   TALER_ARL_USE_PP (aml_staff_uuid) = rowid + 1;
     426            0 :   for (unsigned int i = 0; i < sm->num_statuses; i++)
     427              :   {
     428            0 :     if (GNUNET_TIME_timestamp_cmp (sm->statuses[i].last_change,
     429              :                                    ==,
     430              :                                    last_change))
     431            0 :       return GNUNET_OK; /* already seen and judged in an earlier round */
     432            0 :     if (GNUNET_TIME_timestamp_cmp (sm->statuses[i].last_change,
     433              :                                    >,
     434              :                                    last_change))
     435            0 :       regression = true;
     436              :   }
     437            0 :   if (NULL == master_sig)
     438              :   {
     439            0 :     if (! report_row ("aml_staff",
     440              :                       rowid,
     441              :                       "staff status without a master key signature"))
     442            0 :       return GNUNET_SYSERR;
     443              :   }
     444            0 :   else if (GNUNET_OK !=
     445            0 :            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            0 :     if (! report_row ("aml_staff",
     455              :                       rowid,
     456              :                       "invalid master key signature on staff status")
     457              :         )
     458            0 :       return GNUNET_SYSERR;
     459              :   }
     460              :   else
     461              :   {
     462            0 :     ss.master_sig_valid = true;
     463              :   }
     464            0 :   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            0 :     if (! report_row ("aml_staff",
     470              :                       rowid,
     471              :                       "staff status is older than one seen before"))
     472            0 :       return GNUNET_SYSERR;
     473              :   }
     474            0 :   if (ss.master_sig_valid && is_active)
     475            0 :     sm->enabled_this_round = true;
     476            0 :   staff_member_add (sm,
     477              :                     &ss);
     478            0 :   qs = TALER_AUDITORDB_insert_aml_staff (TALER_ARL_adb,
     479              :                                          decider_pub,
     480              :                                          decider_name,
     481              :                                          is_active,
     482              :                                          read_only,
     483            0 :                                          ss.master_sig_valid,
     484              :                                          last_change);
     485            0 :   if (0 > qs)
     486              :   {
     487            0 :     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
     488            0 :     global_qs = qs;
     489            0 :     return GNUNET_SYSERR;
     490              :   }
     491            0 :   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            0 : 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            0 :   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            0 :   TALER_ARL_USE_PP (aml_history_serial_id) = rowid + 1;
     538            0 :   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            0 :     if (! report_row ("aml_history",
     543              :                       rowid,
     544              :                       "decision without an officer public key"))
     545            0 :       return GNUNET_SYSERR;
     546            0 :     return GNUNET_OK;
     547              :   }
     548            0 :   if (NULL == decider_sig)
     549              :   {
     550            0 :     if (! report_row ("aml_history",
     551              :                       rowid,
     552              :                       "decision without an officer signature"))
     553            0 :       return GNUNET_SYSERR;
     554              :   }
     555            0 :   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            0 :     if (! report_row ("aml_history",
     560              :                       rowid,
     561              :                       "decision without the new rules it signed over"))
     562            0 :       return GNUNET_SYSERR;
     563              :   }
     564            0 :   else if (GNUNET_OK !=
     565            0 :            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            0 :     if (! report_row ("aml_history",
     579              :                       rowid,
     580              :                       "invalid officer signature on decision"))
     581            0 :       return GNUNET_SYSERR;
     582              :   }
     583            0 :   sm = staff_member_get (decider_pub);
     584            0 :   ss = staff_member_status_at (sm,
     585              :                                decision_time);
     586            0 :   if (NULL == ss)
     587              :   {
     588            0 :     if (! report_row ("aml_history",
     589              :                       rowid,
     590              :                       "officer was not appointed when the decision was made"))
     591            0 :       return GNUNET_SYSERR;
     592              :   }
     593            0 :   else if (! ss->is_active)
     594              :   {
     595            0 :     if (! report_row ("aml_history",
     596              :                       rowid,
     597              :                       "officer was not active when the decision was made"))
     598            0 :       return GNUNET_SYSERR;
     599              :   }
     600            0 :   else if (ss->read_only)
     601              :   {
     602            0 :     if (! report_row ("aml_history",
     603              :                       rowid,
     604              :                       "officer had read-only access when deciding"))
     605            0 :       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            0 :   if (sm->known_at_start &&
     611            0 :       (! sm->active_at_start) &&
     612            0 :       (! sm->enabled_this_round))
     613              :   {
     614            0 :     if (! report_row ("aml_history",
     615              :                       rowid,
     616              :                       "decision appeared after the officer was disabled"))
     617            0 :       return GNUNET_SYSERR;
     618              :   }
     619            0 :   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            0 : 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            0 :   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            0 :   TALER_ARL_USE_PP (legitimization_outcome_serial_id) = rowid + 1;
     679            0 :   if (has_aml_decision ||
     680            0 :       has_legitimization_process ||
     681              :       has_expired_predecessor)
     682            0 :     return GNUNET_OK;
     683            0 :   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            0 :     return GNUNET_SYSERR;
     688            0 :   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            0 : free_staff_member (void *cls,
     702              :                    const struct GNUNET_HashCode *key,
     703              :                    void *value)
     704              : {
     705            0 :   struct StaffMember *sm = value;
     706              : 
     707              :   (void) cls;
     708              :   (void) key;
     709            0 :   GNUNET_assert (GNUNET_YES ==
     710              :                  GNUNET_CONTAINER_multihashmap_remove (staff_map,
     711              :                                                        &sm->key,
     712              :                                                        sm));
     713            0 :   GNUNET_array_grow (sm->statuses,
     714              :                      sm->num_statuses,
     715              :                      0);
     716            0 :   GNUNET_free (sm);
     717            0 :   return GNUNET_OK;
     718              : }
     719              : 
     720              : 
     721              : /**
     722              :  * Drop our in-memory view of the AML staff.
     723              :  */
     724              : static void
     725           12 : clear_staff_map (void)
     726              : {
     727           12 :   if (NULL == staff_map)
     728            6 :     return;
     729            6 :   GNUNET_CONTAINER_multihashmap_iterate (staff_map,
     730              :                                          &free_staff_member,
     731              :                                          NULL);
     732            6 :   GNUNET_CONTAINER_multihashmap_destroy (staff_map);
     733            6 :   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            6 : analyze_aml (void *cls)
     745              : {
     746              :   enum GNUNET_DB_QueryStatus qs;
     747              :   bool had_pp;
     748              : 
     749              :   (void) cls;
     750            6 :   global_qs = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
     751            6 :   clear_staff_map ();
     752            6 :   staff_map = GNUNET_CONTAINER_multihashmap_create (32,
     753              :                                                     GNUNET_YES);
     754            6 :   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            6 :   if (0 > qs)
     761              :   {
     762            0 :     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
     763            0 :     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           18 :   had_pp = (0 != TALER_ARL_USE_PP (aml_staff_uuid)) ||
     769           12 :            (0 != TALER_ARL_USE_PP (aml_history_serial_id)) ||
     770            6 :            (0 != TALER_ARL_USE_PP (legitimization_outcome_serial_id));
     771            6 :   if (had_pp)
     772            0 :     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            6 :     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            6 :   qs = TALER_AUDITORDB_iterate_aml_staff (TALER_ARL_adb,
     784              :                                           &known_staff_cb,
     785              :                                           NULL);
     786            6 :   if (0 > qs)
     787              :   {
     788            0 :     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
     789            0 :     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            6 :   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            6 :   if (0 > qs)
     800              :   {
     801            0 :     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
     802            0 :     return qs;
     803              :   }
     804            6 :   if (0 > global_qs)
     805            0 :     return global_qs;
     806              : 
     807            6 :   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            6 :   if (0 > qs)
     813              :   {
     814            0 :     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
     815            0 :     return qs;
     816              :   }
     817            6 :   if (0 > global_qs)
     818            0 :     return global_qs;
     819            6 :   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            6 :   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            6 :   if (0 > qs)
     833              :   {
     834            0 :     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
     835            0 :     return qs;
     836              :   }
     837            6 :   if (0 > global_qs)
     838            0 :     return global_qs;
     839            6 :   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            6 :   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            6 :   if (0 > qs)
     854              :   {
     855            0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     856              :                 "Failed to update auditor DB, not recording progress\n");
     857            0 :     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
     858            0 :     return qs;
     859              :   }
     860            6 :   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            0 : 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            0 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     881              :               "Received notification for new AML data\n");
     882            0 :   if (GNUNET_OK !=
     883            0 :       TALER_ARL_setup_sessions_and_run (&analyze_aml,
     884              :                                         NULL))
     885              :   {
     886            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     887              :                 "Audit failed\n");
     888            0 :     GNUNET_SCHEDULER_shutdown ();
     889            0 :     global_ret = EXIT_FAILURE;
     890            0 :     return;
     891              :   }
     892              : }
     893              : 
     894              : 
     895              : /**
     896              :  * Function called on shutdown.
     897              :  *
     898              :  * @param cls NULL
     899              :  */
     900              : static void
     901            6 : do_shutdown (void *cls)
     902              : {
     903              :   (void) cls;
     904            6 :   if (NULL != eh)
     905              :   {
     906            0 :     TALER_AUDITORDB_event_listen_cancel (eh);
     907            0 :     eh = NULL;
     908              :   }
     909            6 :   clear_staff_map ();
     910            6 :   TALER_ARL_done ();
     911            6 : }
     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            6 : 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            6 :   cfg = c;
     932            6 :   GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
     933              :                                  NULL);
     934            6 :   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
     935              :               "Launching AML auditor\n");
     936            6 :   if (EXIT_SUCCESS !=
     937            6 :       (global_ret = TALER_ARL_init (c)))
     938              :   {
     939            0 :     return;
     940              :   }
     941            6 :   if (test_mode != 1)
     942              :   {
     943            0 :     struct GNUNET_DB_EventHeaderP es = {
     944            0 :       .size = htons (sizeof (es)),
     945            0 :       .type = htons (TALER_DBEVENT_EXCHANGE_AUDITOR_WAKE_HELPER_AML)
     946              :     };
     947              : 
     948            0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     949              :                 "Running helper indefinitely\n");
     950            0 :     eh = TALER_AUDITORDB_event_listen (TALER_ARL_adb,
     951              :                                        &es,
     952            0 :                                        GNUNET_TIME_UNIT_FOREVER_REL,
     953              :                                        &db_notify,
     954              :                                        NULL);
     955              :   }
     956            6 :   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
     957              :               "Starting audit\n");
     958            6 :   if (GNUNET_OK !=
     959            6 :       TALER_ARL_setup_sessions_and_run (&analyze_aml,
     960              :                                         NULL))
     961              :   {
     962            0 :     GNUNET_SCHEDULER_shutdown ();
     963            0 :     global_ret = EXIT_FAILURE;
     964            0 :     return;
     965              :   }
     966              : }
     967              : 
     968              : 
     969              : /**
     970              :  * The main function of the AML auditing helper tool.
     971              :  *
     972              :  * @param argc number of arguments from the command line
     973              :  * @param argv command line arguments
     974              :  * @return 0 ok, 1 on error
     975              :  */
     976              : int
     977            6 : main (int argc,
     978              :       char *const *argv)
     979              : {
     980            6 :   const struct GNUNET_GETOPT_CommandLineOption options[] = {
     981            6 :     GNUNET_GETOPT_option_flag ('i',
     982              :                                "internal",
     983              :                                "perform checks only applicable for exchange-internal audits",
     984              :                                &internal_checks),
     985            6 :     GNUNET_GETOPT_option_flag ('t',
     986              :                                "test",
     987              :                                "run in test mode and exit when idle",
     988              :                                &test_mode),
     989            6 :     GNUNET_GETOPT_option_timetravel ('T',
     990              :                                      "timetravel"),
     991              :     GNUNET_GETOPT_OPTION_END
     992              :   };
     993              :   enum GNUNET_GenericReturnValue ret;
     994              : 
     995            6 :   ret = GNUNET_PROGRAM_run (
     996              :     TALER_AUDITOR_project_data (),
     997              :     argc,
     998              :     argv,
     999              :     "taler-helper-auditor-aml",
    1000              :     gettext_noop (
    1001              :       "Audit exchange database for AML decisions made by unauthorised staff"),
    1002              :     options,
    1003              :     &run,
    1004              :     NULL);
    1005            6 :   if (GNUNET_SYSERR == ret)
    1006            0 :     return EXIT_NOTCONFIGURED;
    1007            6 :   if (GNUNET_NO == ret)
    1008            0 :     return EXIT_SUCCESS;
    1009            6 :   return global_ret;
    1010              : }
    1011              : 
    1012              : 
    1013              : /* end of taler-helper-auditor-aml.c */
        

Generated by: LCOV version 2.0-1