LCOV - code coverage report
Current view: top level - testing - testing_api_cmd_auditor_exec_auditor.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 64.8 % 128 83
Test Date: 2026-10-17 15:46:49 Functions: 100.0 % 7 7

            Line data    Source code
       1              : /*
       2              :   This file is part of TALER
       3              :   Copyright (C) 2018, 2026 Taler Systems SA
       4              : 
       5              :   TALER is free software; you can redistribute it and/or modify it
       6              :   under the terms of the GNU General Public License as published
       7              :   by the Free Software Foundation; either version 3, or (at your
       8              :   option) any later version.
       9              : 
      10              :   TALER is distributed in the hope that it will be useful, but
      11              :   WITHOUT ANY WARRANTY; without even the implied warranty of
      12              :   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      13              :   GNU General Public License for more details.
      14              : 
      15              :   You should have received a copy of the GNU General Public
      16              :   License along with TALER; see the file COPYING.  If not,
      17              :   see <http://www.gnu.org/licenses/>
      18              : */
      19              : /**
      20              :  * @file testing/testing_api_cmd_auditor_exec_auditor.c
      21              :  * @brief run all taler-helper-auditor-* programs and check that
      22              :  *        taler-auditor-httpd reports no findings
      23              :  * @author Marcello Stanisci
      24              :  * @author Christian Grothoff
      25              :  */
      26              : #include "platform.h"
      27              : #include "taler/taler_json_lib.h"
      28              : #include <gnunet/gnunet_curl_lib.h>
      29              : #include "taler/taler_testing_lib.h"
      30              : 
      31              : 
      32              : /**
      33              :  * Auditor helpers to run, in this order.  Each is run in test mode
      34              :  * ("-t"), so it audits everything the exchange database holds so far
      35              :  * and then exits.
      36              :  */
      37              : static const struct
      38              : {
      39              :   /**
      40              :    * Name of the helper binary.
      41              :    */
      42              :   const char *name;
      43              : 
      44              :   /**
      45              :    * True if the helper reads the bank's history of the exchange's
      46              :    * accounts.  Test configurations may list accounts the bank does
      47              :    * not have, so those helpers are told to skip them ("-I").
      48              :    */
      49              :   bool wire;
      50              : } helpers[] = {
      51              :   { "taler-helper-auditor-aggregation", false },
      52              :   { "taler-helper-auditor-aml", false },
      53              :   { "taler-helper-auditor-coins", false },
      54              :   { "taler-helper-auditor-deposits", false },
      55              :   { "taler-helper-auditor-purses", false },
      56              :   { "taler-helper-auditor-reserves", false },
      57              :   { "taler-helper-auditor-transfer", false },
      58              :   { "taler-helper-auditor-wire-credit", true },
      59              :   { "taler-helper-auditor-wire-debit", true },
      60              :   { NULL, false }
      61              : };
      62              : 
      63              : 
      64              : /**
      65              :  * /monitoring/ reports of taler-auditor-httpd that must be empty after a
      66              :  * correct exchange was audited.  Not listed are the balances, progress
      67              :  * and per-reserve/per-purse summaries, which are not findings, and
      68              :  * "pending-deposits" and "aml-holds", which list payouts the exchange has
      69              :  * not made *yet* (deposits before their wire deadline, transfers held
      70              :  * for KYC) and thus depend on when the test last ran the aggregator.
      71              :  */
      72              : static const char *const reports[] = {
      73              :   "amount-arithmetic-inconsistencies",
      74              :   "bad-sig-losses",
      75              :   "closure-lags",
      76              :   "coin-inconsistencies",
      77              :   "denomination-key-validity-withdraw-inconsistencies",
      78              :   "denominations-without-sigs",
      79              :   "deposit-confirmations",
      80              :   "early-aggregations",
      81              :   "emergencies",
      82              :   "emergencies-by-count",
      83              :   "fee-time-inconsistencies",
      84              :   "kycauth-in-inconsistencies",
      85              :   "misattribution-in-inconsistencies",
      86              :   "purse-not-closed-inconsistencies",
      87              :   "reserve-balance-insufficient-inconsistencies",
      88              :   "reserve-balance-summary-wrong-inconsistencies",
      89              :   "reserve-in-inconsistencies",
      90              :   "reserve-not-closed-inconsistencies",
      91              :   "row-inconsistencies",
      92              :   "row-minor-inconsistencies",
      93              :   "wire-format-inconsistencies",
      94              :   "wire-out-inconsistencies",
      95              :   NULL
      96              : };
      97              : 
      98              : 
      99              : /**
     100              :  * State for a "auditor" CMD.
     101              :  */
     102              : struct AuditorState
     103              : {
     104              : 
     105              :   /**
     106              :    * Our interpreter.
     107              :    */
     108              :   struct TALER_TESTING_Interpreter *is;
     109              : 
     110              :   /**
     111              :    * Process of the helper currently running.
     112              :    */
     113              :   struct GNUNET_Process *helper_proc;
     114              : 
     115              :   /**
     116              :    * Handle to wait for @e helper_proc to terminate.
     117              :    */
     118              :   struct GNUNET_ChildWaitHandle *cwh;
     119              : 
     120              :   /**
     121              :    * Task to start the next helper.
     122              :    */
     123              :   struct GNUNET_SCHEDULER_Task *task;
     124              : 
     125              :   /**
     126              :    * Request for a report, or NULL.
     127              :    */
     128              :   struct GNUNET_CURL_Job *job;
     129              : 
     130              :   /**
     131              :    * Headers to send with each request for a report.
     132              :    */
     133              :   struct curl_slist *job_headers;
     134              : 
     135              :   /**
     136              :    * Configuration file used by the command.
     137              :    */
     138              :   const char *config_filename;
     139              : 
     140              :   /**
     141              :    * Base URL of the auditor, from @e config_filename.
     142              :    */
     143              :   char *auditor_url;
     144              : 
     145              :   /**
     146              :    * Index into #helpers of the helper currently running.
     147              :    */
     148              :   unsigned int helper_off;
     149              : 
     150              :   /**
     151              :    * Index into #reports of the report currently requested.
     152              :    */
     153              :   unsigned int report_off;
     154              : 
     155              :   /**
     156              :    * Number of reports that were not empty.
     157              :    */
     158              :   unsigned int findings;
     159              : };
     160              : 
     161              : 
     162              : /**
     163              :  * Request the next report from #reports, or finish the command
     164              :  * if all of them were checked.
     165              :  *
     166              :  * @param as our state
     167              :  */
     168              : static void
     169              : check_next_report (struct AuditorState *as);
     170              : 
     171              : 
     172              : /**
     173              :  * Called with a report from the auditor.
     174              :  *
     175              :  * @param cls our `struct AuditorState`
     176              :  * @param response_code HTTP status returned by the auditor
     177              :  * @param response response body, NULL if not in JSON
     178              :  */
     179              : static void
     180          132 : report_cb (void *cls,
     181              :            long response_code,
     182              :            const void *response)
     183              : {
     184          132 :   struct AuditorState *as = cls;
     185          132 :   const json_t *reply = response;
     186          132 :   const char *report = reports[as->report_off];
     187              :   const json_t *records;
     188              : 
     189          132 :   as->job = NULL;
     190          132 :   if (MHD_HTTP_OK != response_code)
     191              :   {
     192            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     193              :                 "Auditor returned HTTP status %u for report `%s'\n",
     194              :                 (unsigned int) response_code,
     195              :                 report);
     196            0 :     TALER_TESTING_interpreter_fail (as->is);
     197            0 :     return;
     198              :   }
     199          132 :   records = json_object_get (reply,
     200              :                              "records");
     201          132 :   if (! json_is_array (records))
     202              :   {
     203            0 :     GNUNET_break_op (0);
     204            0 :     TALER_TESTING_interpreter_fail (as->is);
     205            0 :     return;
     206              :   }
     207          132 :   if (0 != json_array_size (records))
     208              :   {
     209              :     char *s;
     210              : 
     211            0 :     s = json_dumps (records,
     212              :                     JSON_COMPACT);
     213            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     214              :                 "Auditor reports %u %s: %s\n",
     215              :                 (unsigned int) json_array_size (records),
     216              :                 report,
     217              :                 s);
     218            0 :     free (s);
     219            0 :     as->findings++;
     220              :   }
     221          132 :   as->report_off++;
     222          132 :   check_next_report (as);
     223              : }
     224              : 
     225              : 
     226              : static void
     227          138 : check_next_report (struct AuditorState *as)
     228              : {
     229              :   CURL *eh;
     230              :   char *url;
     231              : 
     232          138 :   if (NULL == reports[as->report_off])
     233              :   {
     234            6 :     if (0 != as->findings)
     235              :     {
     236            0 :       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     237              :                   "Auditor has findings in %u reports\n",
     238              :                   as->findings);
     239            0 :       TALER_TESTING_interpreter_fail (as->is);
     240            6 :       return;
     241              :     }
     242            6 :     TALER_TESTING_interpreter_next (as->is);
     243            6 :     return;
     244              :   }
     245          132 :   GNUNET_asprintf (&url,
     246              :                    "%smonitoring/%s",
     247              :                    as->auditor_url,
     248          132 :                    reports[as->report_off]);
     249          132 :   eh = curl_easy_init ();
     250          132 :   GNUNET_assert (NULL != eh);
     251          132 :   GNUNET_assert (CURLE_OK ==
     252              :                  curl_easy_setopt (eh,
     253              :                                    CURLOPT_URL,
     254              :                                    url));
     255          132 :   GNUNET_free (url);
     256          132 :   as->job = GNUNET_CURL_job_add2 (TALER_TESTING_interpreter_get_context (
     257              :                                     as->is),
     258              :                                   eh,
     259          132 :                                   as->job_headers,
     260              :                                   &report_cb,
     261              :                                   as);
     262          132 :   if (NULL == as->job)
     263              :   {
     264            0 :     GNUNET_break (0);
     265            0 :     TALER_TESTING_interpreter_fail (as->is);
     266            0 :     return;
     267              :   }
     268              : }
     269              : 
     270              : 
     271              : /**
     272              :  * Start the next helper from #helpers, or start checking
     273              :  * the reports if all helpers have run.
     274              :  *
     275              :  * @param cls our `struct AuditorState`
     276              :  */
     277              : static void
     278              : run_next_helper (void *cls);
     279              : 
     280              : 
     281              : /**
     282              :  * Called when the helper we started has terminated.
     283              :  *
     284              :  * @param cls our `struct AuditorState`
     285              :  * @param type how the helper terminated
     286              :  * @param code exit code of the helper
     287              :  */
     288              : static void
     289           54 : helper_done (void *cls,
     290              :              enum GNUNET_OS_ProcessStatusType type,
     291              :              long unsigned int code)
     292              : {
     293           54 :   struct AuditorState *as = cls;
     294              : 
     295           54 :   as->cwh = NULL;
     296           54 :   GNUNET_process_destroy (as->helper_proc);
     297           54 :   as->helper_proc = NULL;
     298           54 :   if ( (GNUNET_OS_PROCESS_EXITED != type) ||
     299              :        (0 != code) )
     300              :   {
     301            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     302              :                 "%s failed (status %d, code %lu)\n",
     303              :                 helpers[as->helper_off].name,
     304              :                 (int) type,
     305              :                 code);
     306            0 :     TALER_TESTING_interpreter_fail (as->is);
     307            0 :     return;
     308              :   }
     309           54 :   as->helper_off++;
     310              :   /* Not directly: GNUNET_wait_child() must not be called from
     311              :      within a GNUNET_ChildCompletedCallback. */
     312           54 :   as->task = GNUNET_SCHEDULER_add_now (&run_next_helper,
     313              :                                        as);
     314              : }
     315              : 
     316              : 
     317              : static void
     318           60 : run_next_helper (void *cls)
     319              : {
     320           60 :   struct AuditorState *as = cls;
     321           60 :   const char *helper = helpers[as->helper_off].name;
     322              : 
     323           60 :   as->task = NULL;
     324           60 :   if (NULL == helper)
     325              :   {
     326            6 :     check_next_report (as);
     327            6 :     return;
     328              :   }
     329           54 :   as->helper_proc = GNUNET_process_create (GNUNET_OS_INHERIT_STD_ERR);
     330           54 :   if (GNUNET_OK !=
     331           54 :       GNUNET_process_run_command_va (as->helper_proc,
     332              :                                      helper,
     333              :                                      helper,
     334              :                                      "-c", as->config_filename,
     335              :                                      "-L", "INFO",
     336              :                                      "-i", /* we audit the exchange DB directly */
     337              :                                      "-t", /* exit when done */
     338           54 :                                      helpers[as->helper_off].wire
     339              :                                      ? "-I" /* ignore unknown accounts */
     340              :                                      : NULL,
     341              :                                      NULL))
     342              :   {
     343            0 :     GNUNET_break (0);
     344            0 :     GNUNET_process_destroy (as->helper_proc);
     345            0 :     as->helper_proc = NULL;
     346            0 :     TALER_TESTING_interpreter_fail (as->is);
     347            0 :     return;
     348              :   }
     349           54 :   as->cwh = GNUNET_wait_child (as->helper_proc,
     350              :                                &helper_done,
     351              :                                as);
     352              : }
     353              : 
     354              : 
     355              : /**
     356              :  * Run the command; runs all auditor helpers against the exchange
     357              :  * database and then asks the auditor for its findings.
     358              :  *
     359              :  * @param cls closure.
     360              :  * @param cmd the commaind being run.
     361              :  * @param is interpreter state.
     362              :  */
     363              : static void
     364            6 : auditor_run (void *cls,
     365              :              const struct TALER_TESTING_Command *cmd,
     366              :              struct TALER_TESTING_Interpreter *is)
     367              : {
     368            6 :   struct AuditorState *as = cls;
     369              :   struct GNUNET_CONFIGURATION_Handle *cfg;
     370              : 
     371              :   (void) cmd;
     372            6 :   as->is = is;
     373            6 :   cfg = GNUNET_CONFIGURATION_create (TALER_AUDITOR_project_data ());
     374            6 :   if (GNUNET_OK !=
     375            6 :       GNUNET_CONFIGURATION_load (cfg,
     376              :                                  as->config_filename))
     377              :   {
     378            0 :     GNUNET_break (0);
     379            0 :     GNUNET_CONFIGURATION_destroy (cfg);
     380            0 :     TALER_TESTING_interpreter_fail (is);
     381            0 :     return;
     382              :   }
     383            6 :   if (GNUNET_OK !=
     384            6 :       GNUNET_CONFIGURATION_get_value_string (cfg,
     385              :                                              "auditor",
     386              :                                              "BASE_URL",
     387              :                                              &as->auditor_url))
     388              :   {
     389            0 :     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
     390              :                                "auditor",
     391              :                                "BASE_URL");
     392            0 :     GNUNET_CONFIGURATION_destroy (cfg);
     393            0 :     TALER_TESTING_interpreter_fail (is);
     394            0 :     return;
     395              :   }
     396              :   {
     397            6 :     const char *tok = getenv ("TALER_AUDITOR_ACCESS_TOKEN");
     398            6 :     char *cfg_tok = NULL;
     399              : 
     400              :     /* Find the token the way taler-auditor-httpd does: the environment
     401              :        variable overrides the configuration, and the token may be stored
     402              :        with or without the RFC 8959 prefix.  If there is none, the httpd
     403              :        must have been started with -n. */
     404           12 :     if ( (NULL == tok) &&
     405              :          (GNUNET_OK ==
     406            6 :           GNUNET_CONFIGURATION_get_value_string (cfg,
     407              :                                                  "auditor",
     408              :                                                  "ACCESS_TOKEN",
     409              :                                                  &cfg_tok)) )
     410            6 :       tok = cfg_tok;
     411            6 :     if (NULL != tok)
     412              :     {
     413              :       char *hdr;
     414              : 
     415            6 :       if (0 != strncasecmp (tok,
     416              :                             "secret-token:",
     417              :                             strlen ("secret-token:")))
     418              :       {
     419            0 :         GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
     420              :                                    "auditor",
     421              :                                    "ACCESS_TOKEN",
     422              :                                    "lacks RFC 8959 prefix");
     423            0 :         GNUNET_CONFIGURATION_destroy (cfg);
     424            0 :         TALER_TESTING_interpreter_fail (is);
     425            0 :         return;
     426              :       }
     427            6 :       tok += strlen ("secret-token:");
     428            6 :       GNUNET_asprintf (&hdr,
     429              :                        "%s: Bearer secret-token:%s",
     430              :                        MHD_HTTP_HEADER_AUTHORIZATION,
     431              :                        tok);
     432            6 :       as->job_headers = curl_slist_append (as->job_headers,
     433              :                                            hdr);
     434            6 :       GNUNET_free (hdr);
     435              :     }
     436            6 :     GNUNET_free (cfg_tok);
     437              :   }
     438            6 :   GNUNET_CONFIGURATION_destroy (cfg);
     439            6 :   run_next_helper (as);
     440              : }
     441              : 
     442              : 
     443              : /**
     444              :  * Free the state of a "auditor" CMD, and possibly kills its
     445              :  * process if it did not terminate correctly.
     446              :  *
     447              :  * @param cls closure.
     448              :  * @param cmd the command being freed.
     449              :  */
     450              : static void
     451            6 : auditor_cleanup (void *cls,
     452              :                  const struct TALER_TESTING_Command *cmd)
     453              : {
     454            6 :   struct AuditorState *as = cls;
     455              : 
     456              :   (void) cmd;
     457            6 :   if (NULL != as->task)
     458              :   {
     459            0 :     GNUNET_SCHEDULER_cancel (as->task);
     460            0 :     as->task = NULL;
     461              :   }
     462            6 :   if (NULL != as->cwh)
     463              :   {
     464            0 :     GNUNET_wait_child_cancel (as->cwh);
     465            0 :     as->cwh = NULL;
     466              :   }
     467            6 :   if (NULL != as->helper_proc)
     468              :   {
     469            0 :     GNUNET_break (GNUNET_OK ==
     470              :                   GNUNET_process_kill (as->helper_proc,
     471              :                                        SIGKILL));
     472            0 :     GNUNET_process_wait (as->helper_proc,
     473              :                          true,
     474              :                          NULL,
     475              :                          NULL);
     476            0 :     GNUNET_process_destroy (as->helper_proc);
     477            0 :     as->helper_proc = NULL;
     478              :   }
     479            6 :   if (NULL != as->job)
     480              :   {
     481            0 :     GNUNET_CURL_job_cancel (as->job);
     482            0 :     as->job = NULL;
     483              :   }
     484            6 :   curl_slist_free_all (as->job_headers);
     485            6 :   GNUNET_free (as->auditor_url);
     486            6 :   GNUNET_free (as);
     487            6 : }
     488              : 
     489              : 
     490              : struct TALER_TESTING_Command
     491            6 : TALER_TESTING_cmd_exec_auditor (const char *label,
     492              :                                 const char *config_filename)
     493              : {
     494              :   struct AuditorState *as;
     495              : 
     496            6 :   as = GNUNET_new (struct AuditorState);
     497            6 :   as->config_filename = config_filename;
     498              :   {
     499            6 :     struct TALER_TESTING_Command cmd = {
     500              :       .cls = as,
     501              :       .label = label,
     502              :       .run = &auditor_run,
     503              :       .cleanup = &auditor_cleanup
     504              :     };
     505              : 
     506            6 :     return cmd;
     507              :   }
     508              : }
     509              : 
     510              : 
     511              : /* end of testing_api_cmd_auditor_exec_auditor.c */
        

Generated by: LCOV version 2.0-1