exchange

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

taler-auditor-offline.c (40221B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2020-2023 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU General Public License as published by the Free Software
      7   Foundation; either version 3, or (at your option) any later version.
      8 
      9   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
     10   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11   A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
     12 
     13   You should have received a copy of the GNU General Public License along with
     14   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 /**
     17  * @file taler-auditor-offline.c
     18  * @brief Support for operations involving the auditor's (offline) key.
     19  * @author Christian Grothoff
     20  */
     21 #include "taler/platform.h"
     22 #include <gnunet/gnunet_json_lib.h>
     23 #include <microhttpd.h>
     24 #include "taler/taler_json_lib.h"
     25 
     26 #define TALER_EXCHANGE_GET_KEYS_RESULT_CLOSURE  \
     27         char *const
     28 #include "taler/taler-exchange/get-keys.h"
     29 
     30 struct DenominationAddRequest;
     31 #define TALER_EXCHANGE_POST_AUDITORS_RESULT_CLOSURE \
     32         struct DenominationAddRequest
     33 #include "taler/taler-exchange/post-auditors-AUDITOR_PUB-H_DENOM_PUB.h"
     34 
     35 /**
     36  * Name of the input of a denomination key signature for the 'upload' operation.
     37  * The "auditor-" prefix ensures that there is no ambiguity between
     38  * taler-exchange-offline and taler-auditor-offline JSON formats.
     39  * The last component --by convention-- identifies the protocol version
     40  * and should be incremented whenever the JSON format of the 'argument' changes.
     41  */
     42 #define OP_SIGN_DENOMINATION "auditor-sign-denomination-0"
     43 
     44 /**
     45  * Name of the input for the 'sign' and 'show' operations.
     46  * The "auditor-" prefix ensures that there is no ambiguity between
     47  * taler-exchange-offline and taler-auditor-offline JSON formats.
     48  * The last component --by convention-- identifies the protocol version
     49  * and should be incremented whenever the JSON format of the 'argument' changes.
     50  */
     51 #define OP_INPUT_KEYS "auditor-keys-0"
     52 
     53 /**
     54  * Show the offline signing key.
     55  * The last component --by convention-- identifies the protocol version
     56  * and should be incremented whenever the JSON format of the 'argument' changes.
     57  */
     58 #define OP_SETUP "auditor-setup-0"
     59 
     60 /**
     61  * Our private key, initialized in #load_offline_key().
     62  */
     63 static struct TALER_AuditorPrivateKeyP auditor_priv;
     64 
     65 /**
     66  * Our private key, initialized in #load_offline_key().
     67  */
     68 static struct TALER_AuditorPublicKeyP auditor_pub;
     69 
     70 /**
     71  * Base URL of this auditor's REST endpoint.
     72  */
     73 static char *auditor_url;
     74 
     75 /**
     76  * Exchange's master public key.
     77  */
     78 static struct TALER_MasterPublicKeyP master_pub;
     79 
     80 /**
     81  * Our context for making HTTP requests.
     82  */
     83 static struct GNUNET_CURL_Context *ctx;
     84 
     85 /**
     86  * Reschedule context for #ctx.
     87  */
     88 static struct GNUNET_CURL_RescheduleContext *rc;
     89 
     90 /**
     91  * Handle to the exchange's configuration
     92  */
     93 static const struct GNUNET_CONFIGURATION_Handle *kcfg;
     94 
     95 /**
     96  * Return value from main().
     97  */
     98 static int global_ret;
     99 
    100 /**
    101  * Input to consume.
    102  */
    103 static json_t *in;
    104 
    105 /**
    106  * Array of actions to perform.
    107  */
    108 static json_t *out;
    109 
    110 /**
    111  * Currency supported by this auditor.
    112  */
    113 static char *currency;
    114 
    115 
    116 /**
    117  * A subcommand supported by this program.
    118  */
    119 struct SubCommand
    120 {
    121   /**
    122    * Name of the command.
    123    */
    124   const char *name;
    125 
    126   /**
    127    * Help text for the command.
    128    */
    129   const char *help;
    130 
    131   /**
    132    * Function implementing the command.
    133    *
    134    * @param args subsequent command line arguments (char **)
    135    */
    136   void (*cb)(char *const *args);
    137 };
    138 
    139 
    140 /**
    141  * Data structure for wire add requests.
    142  */
    143 struct DenominationAddRequest
    144 {
    145 
    146   /**
    147    * Kept in a DLL.
    148    */
    149   struct DenominationAddRequest *next;
    150 
    151   /**
    152    * Kept in a DLL.
    153    */
    154   struct DenominationAddRequest *prev;
    155 
    156   /**
    157    * Operation handle.
    158    */
    159   struct TALER_EXCHANGE_PostAuditorsHandle *h;
    160 
    161   /**
    162    * Array index of the associated command.
    163    */
    164   size_t idx;
    165 };
    166 
    167 
    168 /**
    169  * Next work item to perform.
    170  */
    171 static struct GNUNET_SCHEDULER_Task *nxt;
    172 
    173 /**
    174  * Active denomination add requests.
    175  */
    176 static struct DenominationAddRequest *dar_head;
    177 
    178 /**
    179  * Active denomination add requests.
    180  */
    181 static struct DenominationAddRequest *dar_tail;
    182 
    183 /**
    184  * Handle to the exchange, used to request /keys.
    185  */
    186 static struct TALER_EXCHANGE_GetKeysHandle *exchange;
    187 
    188 
    189 /**
    190  * Shutdown task. Invoked when the application is being terminated.
    191  *
    192  * @param cls NULL
    193  */
    194 static void
    195 do_shutdown (void *cls)
    196 {
    197   (void) cls;
    198 
    199   {
    200     struct DenominationAddRequest *dar;
    201 
    202     while (NULL != (dar = dar_head))
    203     {
    204       fprintf (stderr,
    205                "Aborting incomplete wire add #%u\n",
    206                (unsigned int) dar->idx);
    207       TALER_EXCHANGE_post_auditors_cancel (dar->h);
    208       GNUNET_CONTAINER_DLL_remove (dar_head,
    209                                    dar_tail,
    210                                    dar);
    211       GNUNET_free (dar);
    212     }
    213   }
    214   if (NULL != out)
    215   {
    216     json_dumpf (out,
    217                 stdout,
    218                 JSON_INDENT (2));
    219     json_decref (out);
    220     out = NULL;
    221   }
    222   if (NULL != in)
    223   {
    224     fprintf (stderr,
    225              "Darning: input not consumed!\n");
    226     json_decref (in);
    227     in = NULL;
    228   }
    229   if (NULL != exchange)
    230   {
    231     TALER_EXCHANGE_get_keys_cancel (exchange);
    232     exchange = NULL;
    233   }
    234   if (NULL != nxt)
    235   {
    236     GNUNET_SCHEDULER_cancel (nxt);
    237     nxt = NULL;
    238   }
    239   if (NULL != ctx)
    240   {
    241     GNUNET_CURL_fini (ctx);
    242     ctx = NULL;
    243   }
    244   if (NULL != rc)
    245   {
    246     GNUNET_CURL_gnunet_rc_destroy (rc);
    247     rc = NULL;
    248   }
    249 }
    250 
    251 
    252 /**
    253  * Test if we should shut down because all tasks are done.
    254  */
    255 static void
    256 test_shutdown (void)
    257 {
    258   if ( (NULL == dar_head) &&
    259        (NULL == exchange) &&
    260        (NULL == nxt) )
    261     GNUNET_SCHEDULER_shutdown ();
    262 }
    263 
    264 
    265 /**
    266  * Function to continue processing the next command.
    267  *
    268  * @param cls must be a `char *const*` with the array of
    269  *        command-line arguments to process next
    270  */
    271 static void
    272 work (void *cls);
    273 
    274 
    275 /**
    276  * Function to schedule job to process the next command.
    277  *
    278  * @param args the array of command-line arguments to process next
    279  */
    280 static void
    281 next (char *const *args)
    282 {
    283   GNUNET_assert (NULL == nxt);
    284   if (NULL == args[0])
    285   {
    286     test_shutdown ();
    287     return;
    288   }
    289   nxt = GNUNET_SCHEDULER_add_now (&work,
    290                                   (void *) args);
    291 }
    292 
    293 
    294 /**
    295  * Add an operation to the #out JSON array for processing later.
    296  *
    297  * @param op_name name of the operation
    298  * @param op_value values for the operation (consumed)
    299  */
    300 static void
    301 output_operation (const char *op_name,
    302                   json_t *op_value)
    303 {
    304   json_t *action;
    305 
    306   GNUNET_assert (NULL != out);
    307   action = GNUNET_JSON_PACK (
    308     GNUNET_JSON_pack_string ("operation",
    309                              op_name),
    310     GNUNET_JSON_pack_object_steal ("arguments",
    311                                    op_value));
    312   GNUNET_break (0 ==
    313                 json_array_append_new (out,
    314                                        action));
    315 }
    316 
    317 
    318 /**
    319  * Information about a subroutine for an upload.
    320  */
    321 struct UploadHandler
    322 {
    323   /**
    324    * Key to trigger this subroutine.
    325    */
    326   const char *key;
    327 
    328   /**
    329    * Function implementing an upload.
    330    *
    331    * @param exchange_url URL of the exchange
    332    * @param idx index of the operation we are performing
    333    * @param value arguments to drive the upload.
    334    */
    335   void (*cb)(const char *exchange_url,
    336              size_t idx,
    337              const json_t *value);
    338 
    339 };
    340 
    341 
    342 /**
    343  * Load the offline key (if not yet done). Triggers shutdown on failure.
    344  *
    345  * @param do_create #GNUNET_YES if the key may be created
    346  * @return #GNUNET_OK on success
    347  */
    348 static int
    349 load_offline_key (int do_create)
    350 {
    351   static bool done;
    352   int ret;
    353   char *fn;
    354 
    355   if (done)
    356     return GNUNET_OK;
    357   if (GNUNET_OK !=
    358       GNUNET_CONFIGURATION_get_value_filename (kcfg,
    359                                                "auditor",
    360                                                "AUDITOR_PRIV_FILE",
    361                                                &fn))
    362   {
    363     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    364                                "auditor",
    365                                "AUDITOR_PRIV_FILE");
    366     test_shutdown ();
    367     return GNUNET_SYSERR;
    368   }
    369   ret = GNUNET_CRYPTO_eddsa_key_from_file (fn,
    370                                            do_create,
    371                                            &auditor_priv.eddsa_priv);
    372   if (GNUNET_SYSERR == ret)
    373   {
    374     if (do_create)
    375       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    376                   "Failed to initialize auditor key at `%s': %s\n",
    377                   fn,
    378                   "could not create file");
    379     else
    380       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    381                   "Failed to load auditor key from file `%s': try running `taler-auditor-offline setup'?\n",
    382                   fn);
    383     GNUNET_free (fn);
    384     test_shutdown ();
    385     return GNUNET_SYSERR;
    386   }
    387   GNUNET_free (fn);
    388   GNUNET_CRYPTO_eddsa_key_get_public (&auditor_priv.eddsa_priv,
    389                                       &auditor_pub.eddsa_pub);
    390   done = true;
    391   return GNUNET_OK;
    392 }
    393 
    394 
    395 /**
    396  * Function called with information about the post denomination (signature)
    397  * add operation result.
    398  *
    399  * @param dar the add request
    400  * @param adr response data
    401  */
    402 static void
    403 denomination_add_cb (
    404   struct DenominationAddRequest *dar,
    405   const struct TALER_EXCHANGE_PostAuditorsResponse *adr)
    406 {
    407   const struct TALER_EXCHANGE_HttpResponse *hr = &adr->hr;
    408 
    409   if (MHD_HTTP_NO_CONTENT != hr->http_status)
    410   {
    411     fprintf (stderr,
    412              "Upload failed for command #%u with status %u: %s (%s)\n",
    413              (unsigned int) dar->idx,
    414              hr->http_status,
    415              TALER_ErrorCode_get_hint (hr->ec),
    416              NULL != hr->hint
    417              ? hr->hint
    418              : "no hint provided");
    419     global_ret = EXIT_FAILURE;
    420   }
    421   GNUNET_CONTAINER_DLL_remove (dar_head,
    422                                dar_tail,
    423                                dar);
    424   GNUNET_free (dar);
    425   test_shutdown ();
    426 }
    427 
    428 
    429 /**
    430  * Upload denomination add data.
    431  *
    432  * @param exchange_url base URL of the exchange
    433  * @param idx index of the operation we are performing (for logging)
    434  * @param value arguments for denomination revocation
    435  */
    436 static void
    437 upload_denomination_add (const char *exchange_url,
    438                          size_t idx,
    439                          const json_t *value)
    440 {
    441   struct TALER_AuditorSignatureP auditor_sig;
    442   struct TALER_DenominationHashP h_denom_pub;
    443   struct DenominationAddRequest *dar;
    444   const char *err_name;
    445   unsigned int err_line;
    446   struct GNUNET_JSON_Specification spec[] = {
    447     GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
    448                                  &h_denom_pub),
    449     GNUNET_JSON_spec_fixed_auto ("auditor_sig",
    450                                  &auditor_sig),
    451     GNUNET_JSON_spec_end ()
    452   };
    453 
    454   if (GNUNET_OK !=
    455       GNUNET_JSON_parse (value,
    456                          spec,
    457                          &err_name,
    458                          &err_line))
    459   {
    460     fprintf (stderr,
    461              "Invalid input for adding denomination: %s#%u at %u (skipping)\n",
    462              err_name,
    463              err_line,
    464              (unsigned int) idx);
    465     global_ret = EXIT_FAILURE;
    466     test_shutdown ();
    467     return;
    468   }
    469   dar = GNUNET_new (struct DenominationAddRequest);
    470   dar->idx = idx;
    471   dar->h = TALER_EXCHANGE_post_auditors_create (ctx,
    472                                                 exchange_url,
    473                                                 &h_denom_pub,
    474                                                 &auditor_pub,
    475                                                 &auditor_sig);
    476   GNUNET_assert (NULL != dar->h);
    477   GNUNET_assert (TALER_EC_NONE ==
    478                  TALER_EXCHANGE_post_auditors_start (dar->h,
    479                                                      &denomination_add_cb,
    480                                                      dar));
    481   GNUNET_CONTAINER_DLL_insert (dar_head,
    482                                dar_tail,
    483                                dar);
    484 }
    485 
    486 
    487 /**
    488  * Perform uploads based on the JSON in #out.
    489  *
    490  * @param exchange_url base URL of the exchange to use
    491  */
    492 static void
    493 trigger_upload (const char *exchange_url)
    494 {
    495   struct UploadHandler uhs[] = {
    496     {
    497       .key = OP_SIGN_DENOMINATION,
    498       .cb = &upload_denomination_add
    499     },
    500     /* array termination */
    501     {
    502       .key = NULL
    503     }
    504   };
    505   size_t index;
    506   json_t *obj;
    507 
    508   json_array_foreach (out, index, obj) {
    509     bool found = false;
    510     const char *key;
    511     const json_t *value;
    512 
    513     key = json_string_value (json_object_get (obj, "operation"));
    514     value = json_object_get (obj, "arguments");
    515     if (NULL == key)
    516     {
    517       fprintf (stderr,
    518                "Malformed JSON input\n");
    519       global_ret = EXIT_FAILURE;
    520       test_shutdown ();
    521       return;
    522     }
    523     /* block of code that uses key and value */
    524     for (unsigned int i = 0; NULL != uhs[i].key; i++)
    525     {
    526       if (0 == strcasecmp (key,
    527                            uhs[i].key))
    528       {
    529         found = true;
    530         uhs[i].cb (exchange_url,
    531                    index,
    532                    value);
    533         break;
    534       }
    535     }
    536     if (! found)
    537     {
    538       fprintf (stderr,
    539                "Upload does not know how to handle `%s'\n",
    540                key);
    541       global_ret = EXIT_FAILURE;
    542       test_shutdown ();
    543       return;
    544     }
    545   }
    546   /* test here, in case no upload was triggered (i.e. empty input) */
    547   test_shutdown ();
    548 }
    549 
    550 
    551 /**
    552  * Upload operation result (signatures) to exchange.
    553  *
    554  * @param args the array of command-line arguments to process next
    555  */
    556 static void
    557 do_upload (char *const *args)
    558 {
    559   char *exchange_url;
    560 
    561   (void) args;
    562   if (GNUNET_YES == GNUNET_is_zero (&auditor_pub))
    563   {
    564     /* private key not available, try configuration for public key */
    565     char *auditor_public_key_str;
    566 
    567     if (GNUNET_OK !=
    568         GNUNET_CONFIGURATION_get_value_string (kcfg,
    569                                                "auditor",
    570                                                "PUBLIC_KEY",
    571                                                &auditor_public_key_str))
    572     {
    573       GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    574                                  "auditor",
    575                                  "PUBLIC_KEY");
    576       global_ret = EXIT_NOTCONFIGURED;
    577       test_shutdown ();
    578       return;
    579     }
    580     if (GNUNET_OK !=
    581         GNUNET_CRYPTO_eddsa_public_key_from_string (
    582           auditor_public_key_str,
    583           strlen (auditor_public_key_str),
    584           &auditor_pub.eddsa_pub))
    585     {
    586       GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    587                                  "auditor",
    588                                  "PUBLIC_KEY",
    589                                  "invalid key");
    590       GNUNET_free (auditor_public_key_str);
    591       global_ret = EXIT_NOTCONFIGURED;
    592       test_shutdown ();
    593       return;
    594     }
    595     GNUNET_free (auditor_public_key_str);
    596   }
    597   if (NULL != in)
    598   {
    599     fprintf (stderr,
    600              "Downloaded data was not consumed, refusing upload\n");
    601     test_shutdown ();
    602     global_ret = EXIT_FAILURE;
    603     return;
    604   }
    605   if (NULL == out)
    606   {
    607     json_error_t err;
    608 
    609     out = json_loadf (stdin,
    610                       JSON_REJECT_DUPLICATES,
    611                       &err);
    612     if (NULL == out)
    613     {
    614       fprintf (stderr,
    615                "Failed to read JSON input: %s at %d:%s (offset: %d)\n",
    616                err.text,
    617                err.line,
    618                err.source,
    619                err.position);
    620       test_shutdown ();
    621       global_ret = EXIT_FAILURE;
    622       return;
    623     }
    624   }
    625   if (! json_is_array (out))
    626   {
    627     fprintf (stderr,
    628              "Error: expected JSON array for `upload` command\n");
    629     test_shutdown ();
    630     global_ret = EXIT_FAILURE;
    631     return;
    632   }
    633   if (GNUNET_OK !=
    634       GNUNET_CONFIGURATION_get_value_string (kcfg,
    635                                              "exchange",
    636                                              "BASE_URL",
    637                                              &exchange_url))
    638   {
    639     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    640                                "exchange",
    641                                "BASE_URL");
    642     global_ret = EXIT_NOTCONFIGURED;
    643     test_shutdown ();
    644     return;
    645   }
    646   trigger_upload (exchange_url);
    647   json_decref (out);
    648   out = NULL;
    649   GNUNET_free (exchange_url);
    650 }
    651 
    652 
    653 /**
    654  * Function called with information about who is auditing
    655  * a particular exchange and what keys the exchange is using.
    656  *
    657  * @param args the remaining args
    658  * @param kr response data
    659  * @param keys key data from the exchange
    660  */
    661 static void
    662 keys_cb (
    663   char *const *args,
    664   const struct TALER_EXCHANGE_KeysResponse *kr,
    665   struct TALER_EXCHANGE_Keys *keys)
    666 {
    667   exchange = NULL;
    668   switch (kr->hr.http_status)
    669   {
    670   case MHD_HTTP_OK:
    671     if (NULL == kr->hr.reply)
    672     {
    673       GNUNET_break (0);
    674       test_shutdown ();
    675       global_ret = EXIT_FAILURE;
    676       return;
    677     }
    678     break;
    679   default:
    680     fprintf (stderr,
    681              "Failed to download keys: %s (HTTP status: %u/%u)\n",
    682              kr->hr.hint,
    683              kr->hr.http_status,
    684              (unsigned int) kr->hr.ec);
    685     test_shutdown ();
    686     global_ret = EXIT_FAILURE;
    687     return;
    688   }
    689   in = GNUNET_JSON_PACK (
    690     GNUNET_JSON_pack_string ("operation",
    691                              OP_INPUT_KEYS),
    692     GNUNET_JSON_pack_object_incref ("arguments",
    693                                     (json_t *) kr->hr.reply));
    694   if (NULL == args[0])
    695   {
    696     json_dumpf (in,
    697                 stdout,
    698                 JSON_INDENT (2));
    699     json_decref (in);
    700     in = NULL;
    701   }
    702   next (args);
    703   TALER_EXCHANGE_keys_decref (keys);
    704 }
    705 
    706 
    707 /**
    708  * Download future keys.
    709  *
    710  * @param args the array of command-line arguments to process next
    711  */
    712 static void
    713 do_download (char *const *args)
    714 {
    715   char *exchange_url;
    716 
    717   if (GNUNET_OK !=
    718       GNUNET_CONFIGURATION_get_value_string (kcfg,
    719                                              "exchange",
    720                                              "BASE_URL",
    721                                              &exchange_url))
    722   {
    723     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    724                                "exchange",
    725                                "BASE_URL");
    726     test_shutdown ();
    727     global_ret = EXIT_NOTCONFIGURED;
    728     return;
    729   }
    730   exchange = TALER_EXCHANGE_get_keys_create (ctx,
    731                                              exchange_url);
    732   TALER_EXCHANGE_get_keys_start (exchange,
    733                                  &keys_cb,
    734                                  args);
    735   GNUNET_free (exchange_url);
    736 }
    737 
    738 
    739 /**
    740  * Output @a denomkeys for human consumption.
    741  *
    742  * @param denomkeys keys to output
    743  * @return #GNUNET_OK on success
    744  */
    745 static enum GNUNET_GenericReturnValue
    746 show_denomkeys (const json_t *denomkeys)
    747 {
    748   size_t index;
    749   json_t *value;
    750 
    751   json_array_foreach (denomkeys, index, value) {
    752     struct TALER_DenominationGroup group;
    753     const json_t *denoms;
    754     const char *err_name;
    755     unsigned int err_line;
    756     struct GNUNET_JSON_Specification spec[] = {
    757       TALER_JSON_spec_denomination_group (NULL,
    758                                           currency,
    759                                           &group),
    760       GNUNET_JSON_spec_array_const ("denoms",
    761                                     &denoms),
    762       GNUNET_JSON_spec_end ()
    763     };
    764     size_t index2;
    765     json_t *value2;
    766 
    767     if (GNUNET_OK !=
    768         GNUNET_JSON_parse (value,
    769                            spec,
    770                            &err_name,
    771                            &err_line))
    772     {
    773       fprintf (stderr,
    774                "Invalid input for denomination key to 'show': %s#%u at %u (skipping)\n",
    775                err_name,
    776                err_line,
    777                (unsigned int) index);
    778       GNUNET_JSON_parse_free (spec);
    779       global_ret = EXIT_FAILURE;
    780       test_shutdown ();
    781       return GNUNET_SYSERR;
    782     }
    783     json_array_foreach (denoms, index2, value2) {
    784       struct GNUNET_TIME_Timestamp stamp_start;
    785       struct GNUNET_TIME_Timestamp stamp_expire_withdraw;
    786       struct GNUNET_TIME_Timestamp stamp_expire_deposit;
    787       struct GNUNET_TIME_Timestamp stamp_expire_legal;
    788       struct TALER_DenominationPublicKey denom_pub;
    789       struct TALER_MasterSignatureP master_sig;
    790       struct GNUNET_JSON_Specification ispec[] = {
    791         TALER_JSON_spec_denom_pub_cipher (NULL,
    792                                           group.cipher,
    793                                           &denom_pub),
    794         GNUNET_JSON_spec_timestamp ("stamp_start",
    795                                     &stamp_start),
    796         GNUNET_JSON_spec_timestamp ("stamp_expire_withdraw",
    797                                     &stamp_expire_withdraw),
    798         GNUNET_JSON_spec_timestamp ("stamp_expire_deposit",
    799                                     &stamp_expire_deposit),
    800         GNUNET_JSON_spec_timestamp ("stamp_expire_legal",
    801                                     &stamp_expire_legal),
    802         GNUNET_JSON_spec_fixed_auto ("master_sig",
    803                                      &master_sig),
    804         GNUNET_JSON_spec_end ()
    805       };
    806       struct GNUNET_TIME_Relative duration;
    807       struct TALER_DenominationHashP h_denom_pub;
    808 
    809       if (GNUNET_OK !=
    810           GNUNET_JSON_parse (value2,
    811                              ispec,
    812                              &err_name,
    813                              &err_line))
    814       {
    815         fprintf (stderr,
    816                  "Invalid input for denomination key to 'show': %s#%u at %u/%u (skipping)\n",
    817                  err_name,
    818                  err_line,
    819                  (unsigned int) index,
    820                  (unsigned int) index2);
    821         GNUNET_JSON_parse_free (spec);
    822         global_ret = EXIT_FAILURE;
    823         test_shutdown ();
    824         return GNUNET_SYSERR;
    825       }
    826       duration = GNUNET_TIME_absolute_get_difference (
    827         stamp_start.abs_time,
    828         stamp_expire_withdraw.abs_time);
    829       TALER_denom_pub_hash (&denom_pub,
    830                             &h_denom_pub);
    831       if (GNUNET_OK !=
    832           TALER_exchange_offline_denom_validity_verify (
    833             &h_denom_pub,
    834             stamp_start,
    835             stamp_expire_withdraw,
    836             stamp_expire_deposit,
    837             stamp_expire_legal,
    838             &group.value,
    839             &group.fees,
    840             &master_pub,
    841             &master_sig))
    842       {
    843         fprintf (stderr,
    844                  "Invalid master signature for key %s (aborting)\n",
    845                  TALER_B2S (&h_denom_pub));
    846         global_ret = EXIT_FAILURE;
    847         GNUNET_JSON_parse_free (ispec);
    848         GNUNET_JSON_parse_free (spec);
    849         test_shutdown ();
    850         return GNUNET_SYSERR;
    851       }
    852 
    853       {
    854         char *withdraw_fee_s;
    855         char *deposit_fee_s;
    856         char *refresh_fee_s;
    857         char *refund_fee_s;
    858         char *deposit_s;
    859         char *legal_s;
    860 
    861         withdraw_fee_s = TALER_amount_to_string (&group.fees.withdraw);
    862         deposit_fee_s = TALER_amount_to_string (&group.fees.deposit);
    863         refresh_fee_s = TALER_amount_to_string (&group.fees.refresh);
    864         refund_fee_s = TALER_amount_to_string (&group.fees.refund);
    865         deposit_s = GNUNET_strdup (
    866           GNUNET_TIME_timestamp2s (stamp_expire_deposit));
    867         legal_s = GNUNET_strdup (
    868           GNUNET_TIME_timestamp2s (stamp_expire_legal));
    869 
    870         printf (
    871           "DENOMINATION-KEY %s of value %s starting at %s "
    872           "(used for: %s, deposit until: %s legal end: %s) with fees %s/%s/%s/%s\n",
    873           TALER_B2S (&h_denom_pub),
    874           TALER_amount2s (&group.value),
    875           GNUNET_TIME_timestamp2s (stamp_start),
    876           GNUNET_TIME_relative2s (duration,
    877                                   false),
    878           deposit_s,
    879           legal_s,
    880           withdraw_fee_s,
    881           deposit_fee_s,
    882           refresh_fee_s,
    883           refund_fee_s);
    884         GNUNET_free (withdraw_fee_s);
    885         GNUNET_free (deposit_fee_s);
    886         GNUNET_free (refresh_fee_s);
    887         GNUNET_free (refund_fee_s);
    888         GNUNET_free (deposit_s);
    889         GNUNET_free (legal_s);
    890       }
    891       GNUNET_JSON_parse_free (ispec);
    892     }
    893     GNUNET_JSON_parse_free (spec);
    894   }
    895   return GNUNET_OK;
    896 }
    897 
    898 
    899 /**
    900  * Parse the '/keys' input for operation called @a command_name.
    901  *
    902  * @param command_name name of the command, for logging errors
    903  * @return NULL if the input is malformed
    904  */
    905 static json_t *
    906 parse_keys (const char *command_name)
    907 {
    908   json_t *keys;
    909   const char *op_str;
    910   struct GNUNET_JSON_Specification spec[] = {
    911     GNUNET_JSON_spec_json ("arguments",
    912                            &keys),
    913     GNUNET_JSON_spec_string ("operation",
    914                              &op_str),
    915     GNUNET_JSON_spec_end ()
    916   };
    917   const char *err_name;
    918   unsigned int err_line;
    919 
    920   if (NULL == in)
    921   {
    922     json_error_t err;
    923 
    924     in = json_loadf (stdin,
    925                      JSON_REJECT_DUPLICATES,
    926                      &err);
    927     if (NULL == in)
    928     {
    929       fprintf (stderr,
    930                "Failed to read JSON input: %s at %d:%s (offset: %d)\n",
    931                err.text,
    932                err.line,
    933                err.source,
    934                err.position);
    935       global_ret = EXIT_FAILURE;
    936       test_shutdown ();
    937       return NULL;
    938     }
    939   }
    940   if (GNUNET_OK !=
    941       GNUNET_JSON_parse (in,
    942                          spec,
    943                          &err_name,
    944                          &err_line))
    945   {
    946     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    947                 "Invalid input to '%s': %s#%u (skipping)\n",
    948                 command_name,
    949                 err_name,
    950                 err_line);
    951     json_dumpf (in,
    952                 stderr,
    953                 JSON_INDENT (2));
    954     global_ret = EXIT_FAILURE;
    955     test_shutdown ();
    956     return NULL;
    957   }
    958   if (0 != strcmp (op_str,
    959                    OP_INPUT_KEYS))
    960   {
    961     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    962                 "Invalid input to '%s' : operation is `%s', expected `%s'\n",
    963                 command_name,
    964                 op_str,
    965                 OP_INPUT_KEYS);
    966     GNUNET_JSON_parse_free (spec);
    967     return NULL;
    968   }
    969   json_decref (in);
    970   in = NULL;
    971   return keys;
    972 }
    973 
    974 
    975 /**
    976  * Show exchange denomination keys.
    977  *
    978  * @param args the array of command-line arguments to process next
    979  */
    980 static void
    981 do_show (char *const *args)
    982 {
    983   json_t *keys;
    984   const char *err_name;
    985   unsigned int err_line;
    986   const json_t *denomkeys;
    987   struct TALER_MasterPublicKeyP mpub;
    988   struct GNUNET_JSON_Specification spec[] = {
    989     GNUNET_JSON_spec_array_const ("denominations",
    990                                   &denomkeys),
    991     GNUNET_JSON_spec_fixed_auto ("master_public_key",
    992                                  &mpub),
    993     GNUNET_JSON_spec_end ()
    994   };
    995 
    996   keys = parse_keys ("show");
    997   if (NULL == keys)
    998   {
    999     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1000                 "Showing failed: no valid input\n");
   1001     return;
   1002   }
   1003   if (GNUNET_OK !=
   1004       GNUNET_JSON_parse (keys,
   1005                          spec,
   1006                          &err_name,
   1007                          &err_line))
   1008   {
   1009     fprintf (stderr,
   1010              "Invalid input to 'show': %s#%u (skipping)\n",
   1011              err_name,
   1012              err_line);
   1013     global_ret = EXIT_FAILURE;
   1014     test_shutdown ();
   1015     json_decref (keys);
   1016     return;
   1017   }
   1018   if (0 !=
   1019       GNUNET_memcmp (&mpub,
   1020                      &master_pub))
   1021   {
   1022     fprintf (stderr,
   1023              "Exchange master public key does not match key we have configured (aborting)\n");
   1024     global_ret = EXIT_FAILURE;
   1025     test_shutdown ();
   1026     json_decref (keys);
   1027     return;
   1028   }
   1029   if (GNUNET_OK !=
   1030       show_denomkeys (denomkeys))
   1031   {
   1032     global_ret = EXIT_FAILURE;
   1033     test_shutdown ();
   1034     json_decref (keys);
   1035     return;
   1036   }
   1037   json_decref (keys);
   1038   /* do NOT consume input if next argument is '-' */
   1039   if ( (NULL != args[0]) &&
   1040        (0 == strcmp ("-",
   1041                      args[0])) )
   1042   {
   1043     next (args + 1);
   1044     return;
   1045   }
   1046   next (args);
   1047 }
   1048 
   1049 
   1050 /**
   1051  * Sign @a denomkeys with offline key.
   1052  *
   1053  * @param denomkeys keys to output
   1054  * @return #GNUNET_OK on success
   1055  */
   1056 static enum GNUNET_GenericReturnValue
   1057 sign_denomkeys (const json_t *denomkeys)
   1058 {
   1059   size_t group_idx;
   1060   json_t *value;
   1061 
   1062   json_array_foreach (denomkeys, group_idx, value) {
   1063     struct TALER_DenominationGroup group = { 0 };
   1064     const json_t *denom_keys_array;
   1065     const char *err_name;
   1066     unsigned int err_line;
   1067     struct GNUNET_JSON_Specification spec[] = {
   1068       TALER_JSON_spec_denomination_group (NULL,
   1069                                           currency,
   1070                                           &group),
   1071       GNUNET_JSON_spec_array_const ("denoms",
   1072                                     &denom_keys_array),
   1073       GNUNET_JSON_spec_end ()
   1074     };
   1075     size_t index;
   1076     json_t *denom_key_obj;
   1077 
   1078     if (GNUNET_OK !=
   1079         GNUNET_JSON_parse (value,
   1080                            spec,
   1081                            &err_name,
   1082                            &err_line))
   1083     {
   1084       fprintf (stderr,
   1085                "Invalid input for denomination key to 'sign': %s#%u at %u (skipping)\n",
   1086                err_name,
   1087                err_line,
   1088                (unsigned int) group_idx);
   1089       GNUNET_JSON_parse_free (spec);
   1090       global_ret = EXIT_FAILURE;
   1091       test_shutdown ();
   1092       return GNUNET_SYSERR;
   1093     }
   1094     json_array_foreach (denom_keys_array, index, denom_key_obj) {
   1095       struct GNUNET_TIME_Timestamp stamp_start;
   1096       struct GNUNET_TIME_Timestamp stamp_expire_withdraw;
   1097       struct GNUNET_TIME_Timestamp stamp_expire_deposit;
   1098       struct GNUNET_TIME_Timestamp stamp_expire_legal;
   1099       struct TALER_DenominationPublicKey denom_pub = {
   1100         .age_mask = group.age_mask
   1101       };
   1102       struct TALER_MasterSignatureP master_sig;
   1103       struct GNUNET_JSON_Specification ispec[] = {
   1104         TALER_JSON_spec_denom_pub_cipher (NULL,
   1105                                           group.cipher,
   1106                                           &denom_pub),
   1107         GNUNET_JSON_spec_timestamp ("stamp_start",
   1108                                     &stamp_start),
   1109         GNUNET_JSON_spec_timestamp ("stamp_expire_withdraw",
   1110                                     &stamp_expire_withdraw),
   1111         GNUNET_JSON_spec_timestamp ("stamp_expire_deposit",
   1112                                     &stamp_expire_deposit),
   1113         GNUNET_JSON_spec_timestamp ("stamp_expire_legal",
   1114                                     &stamp_expire_legal),
   1115         GNUNET_JSON_spec_fixed_auto ("master_sig",
   1116                                      &master_sig),
   1117         GNUNET_JSON_spec_end ()
   1118       };
   1119       struct TALER_DenominationHashP h_denom_pub;
   1120 
   1121       if (GNUNET_OK !=
   1122           GNUNET_JSON_parse (denom_key_obj,
   1123                              ispec,
   1124                              &err_name,
   1125                              &err_line))
   1126       {
   1127         fprintf (stderr,
   1128                  "Invalid input for denomination key to 'show': %s#%u at %u/%u (skipping)\n",
   1129                  err_name,
   1130                  err_line,
   1131                  (unsigned int) group_idx,
   1132                  (unsigned int) index);
   1133         GNUNET_JSON_parse_free (spec);
   1134         global_ret = EXIT_FAILURE;
   1135         test_shutdown ();
   1136         return GNUNET_SYSERR;
   1137       }
   1138       TALER_denom_pub_hash (&denom_pub,
   1139                             &h_denom_pub);
   1140       if (GNUNET_OK !=
   1141           TALER_exchange_offline_denom_validity_verify (
   1142             &h_denom_pub,
   1143             stamp_start,
   1144             stamp_expire_withdraw,
   1145             stamp_expire_deposit,
   1146             stamp_expire_legal,
   1147             &group.value,
   1148             &group.fees,
   1149             &master_pub,
   1150             &master_sig))
   1151       {
   1152         fprintf (stderr,
   1153                  "Invalid master signature for key %s (aborting)\n",
   1154                  TALER_B2S (&h_denom_pub));
   1155         global_ret = EXIT_FAILURE;
   1156         test_shutdown ();
   1157         return GNUNET_SYSERR;
   1158       }
   1159 
   1160       {
   1161         struct TALER_AuditorSignatureP auditor_sig;
   1162 
   1163         TALER_auditor_denom_validity_sign (auditor_url,
   1164                                            &h_denom_pub,
   1165                                            &master_pub,
   1166                                            stamp_start,
   1167                                            stamp_expire_withdraw,
   1168                                            stamp_expire_deposit,
   1169                                            stamp_expire_legal,
   1170                                            &group.value,
   1171                                            &group.fees,
   1172                                            &auditor_priv,
   1173                                            &auditor_sig);
   1174         output_operation (OP_SIGN_DENOMINATION,
   1175                           GNUNET_JSON_PACK (
   1176                             GNUNET_JSON_pack_data_auto ("h_denom_pub",
   1177                                                         &h_denom_pub),
   1178                             GNUNET_JSON_pack_data_auto ("auditor_sig",
   1179                                                         &auditor_sig)));
   1180       }
   1181       GNUNET_JSON_parse_free (ispec);
   1182     }
   1183     GNUNET_JSON_parse_free (spec);
   1184   }
   1185   return GNUNET_OK;
   1186 }
   1187 
   1188 
   1189 /**
   1190  * Sign denomination keys.
   1191  *
   1192  * @param args the array of command-line arguments to process next
   1193  */
   1194 static void
   1195 do_sign (char *const *args)
   1196 {
   1197   json_t *keys;
   1198   const char *err_name;
   1199   unsigned int err_line;
   1200   struct TALER_MasterPublicKeyP mpub;
   1201   const json_t *denomkeys;
   1202   struct GNUNET_JSON_Specification spec[] = {
   1203     GNUNET_JSON_spec_array_const ("denominations",
   1204                                   &denomkeys),
   1205     GNUNET_JSON_spec_fixed_auto ("master_public_key",
   1206                                  &mpub),
   1207     GNUNET_JSON_spec_end ()
   1208   };
   1209 
   1210   keys = parse_keys ("sign");
   1211   if (NULL == keys)
   1212   {
   1213     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1214                 "Signing failed: no valid input\n");
   1215     return;
   1216   }
   1217   if (GNUNET_OK !=
   1218       load_offline_key (GNUNET_NO))
   1219   {
   1220     json_decref (keys);
   1221     return;
   1222   }
   1223   if (GNUNET_OK !=
   1224       GNUNET_JSON_parse (keys,
   1225                          spec,
   1226                          &err_name,
   1227                          &err_line))
   1228   {
   1229     fprintf (stderr,
   1230              "Invalid input to 'sign': %s#%u (skipping)\n",
   1231              err_name,
   1232              err_line);
   1233     global_ret = EXIT_FAILURE;
   1234     test_shutdown ();
   1235     json_decref (keys);
   1236     return;
   1237   }
   1238   if (0 !=
   1239       GNUNET_memcmp (&mpub,
   1240                      &master_pub))
   1241   {
   1242     fprintf (stderr,
   1243              "Exchange master public key does not match key we have configured (aborting)\n");
   1244     global_ret = EXIT_FAILURE;
   1245     test_shutdown ();
   1246     json_decref (keys);
   1247     return;
   1248   }
   1249   if (NULL == out)
   1250   {
   1251     out = json_array ();
   1252     GNUNET_assert (NULL != out);
   1253   }
   1254   if (GNUNET_OK !=
   1255       sign_denomkeys (denomkeys))
   1256   {
   1257     global_ret = EXIT_FAILURE;
   1258     test_shutdown ();
   1259     json_decref (keys);
   1260     return;
   1261   }
   1262   json_decref (keys);
   1263   next (args);
   1264 }
   1265 
   1266 
   1267 /**
   1268  * Setup and output offline signing key.
   1269  *
   1270  * @param args the array of command-line arguments to process next
   1271  */
   1272 static void
   1273 do_setup (char *const *args)
   1274 {
   1275   if (GNUNET_OK !=
   1276       load_offline_key (GNUNET_YES))
   1277   {
   1278     global_ret = EXIT_FAILURE;
   1279     return;
   1280   }
   1281   if (NULL != *args)
   1282   {
   1283     if (NULL == out)
   1284     {
   1285       out = json_array ();
   1286       GNUNET_assert (NULL != out);
   1287     }
   1288     output_operation (OP_SETUP,
   1289                       GNUNET_JSON_PACK (
   1290                         GNUNET_JSON_pack_data_auto ("auditor_pub",
   1291                                                     &auditor_pub)));
   1292   }
   1293 
   1294   else
   1295   {
   1296     char *pub_s;
   1297 
   1298     pub_s = GNUNET_STRINGS_data_to_string_alloc (&auditor_pub,
   1299                                                  sizeof (auditor_pub));
   1300     fprintf (stdout,
   1301              "%s\n",
   1302              pub_s);
   1303     GNUNET_free (pub_s);
   1304   }
   1305   if ( (NULL != *args) &&
   1306        (0 == strcmp (*args,
   1307                      "-")) )
   1308     args++;
   1309   next (args);
   1310 }
   1311 
   1312 
   1313 static void
   1314 work (void *cls)
   1315 {
   1316   char *const *args = cls;
   1317   struct SubCommand cmds[] = {
   1318     {
   1319       .name = "setup",
   1320       .help =
   1321         "setup auditor offline private key and show the public key",
   1322       .cb = &do_setup
   1323     },
   1324     {
   1325       .name = "download",
   1326       .help =
   1327         "obtain keys from exchange (to be performed online!)",
   1328       .cb = &do_download
   1329     },
   1330     {
   1331       .name = "show",
   1332       .help =
   1333         "display keys from exchange for human review (pass '-' as argument to disable consuming input)",
   1334       .cb = &do_show
   1335     },
   1336     {
   1337       .name = "sign",
   1338       .help =
   1339         "sing all denomination keys from the input",
   1340       .cb = &do_sign
   1341     },
   1342     {
   1343       .name = "upload",
   1344       .help =
   1345         "upload operation result to exchange (to be performed online!)",
   1346       .cb = &do_upload
   1347     },
   1348     /* list terminator */
   1349     {
   1350       .name = NULL,
   1351     }
   1352   };
   1353   (void) cls;
   1354 
   1355   nxt = NULL;
   1356   for (unsigned int i = 0; NULL != cmds[i].name; i++)
   1357   {
   1358     if (0 == strcasecmp (cmds[i].name,
   1359                          args[0]))
   1360     {
   1361       cmds[i].cb (&args[1]);
   1362       return;
   1363     }
   1364   }
   1365 
   1366   if (0 != strcasecmp ("help",
   1367                        args[0]))
   1368   {
   1369     fprintf (stderr,
   1370              "Unexpected command `%s'\n",
   1371              args[0]);
   1372     global_ret = EXIT_INVALIDARGUMENT;
   1373   }
   1374   fprintf (stderr,
   1375            "Supported subcommands:\n");
   1376   for (unsigned int i = 0; NULL != cmds[i].name; i++)
   1377   {
   1378     fprintf (stderr,
   1379              "\t%s - %s\n",
   1380              cmds[i].name,
   1381              cmds[i].help);
   1382   }
   1383 }
   1384 
   1385 
   1386 /**
   1387  * Main function that will be run.
   1388  *
   1389  * @param cls closure
   1390  * @param args remaining command-line arguments
   1391  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
   1392  * @param cfg configuration
   1393  */
   1394 static void
   1395 run (void *cls,
   1396      char *const *args,
   1397      const char *cfgfile,
   1398      const struct GNUNET_CONFIGURATION_Handle *cfg)
   1399 {
   1400   (void) cls;
   1401   (void) cfgfile;
   1402   kcfg = cfg;
   1403   if (GNUNET_OK !=
   1404       TALER_config_get_currency (kcfg,
   1405                                  "exchange",
   1406                                  &currency))
   1407   {
   1408     global_ret = EXIT_NOTCONFIGURED;
   1409     return;
   1410   }
   1411   if (GNUNET_OK !=
   1412       GNUNET_CONFIGURATION_get_value_string (kcfg,
   1413                                              "auditor",
   1414                                              "BASE_URL",
   1415                                              &auditor_url))
   1416   {
   1417     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
   1418                                "auditor",
   1419                                "BASE_URL");
   1420     global_ret = EXIT_NOTCONFIGURED;
   1421     return;
   1422   }
   1423   {
   1424     char *master_public_key_str;
   1425 
   1426     if (GNUNET_OK !=
   1427         GNUNET_CONFIGURATION_get_value_string (cfg,
   1428                                                "exchange",
   1429                                                "MASTER_PUBLIC_KEY",
   1430                                                &master_public_key_str))
   1431     {
   1432       GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
   1433                                  "exchange",
   1434                                  "MASTER_PUBLIC_KEY");
   1435       global_ret = EXIT_NOTCONFIGURED;
   1436       return;
   1437     }
   1438     if (GNUNET_OK !=
   1439         GNUNET_CRYPTO_eddsa_public_key_from_string (
   1440           master_public_key_str,
   1441           strlen (master_public_key_str),
   1442           &master_pub.eddsa_pub))
   1443     {
   1444       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1445                   "Invalid master public key given in exchange configuration.");
   1446       GNUNET_free (master_public_key_str);
   1447       global_ret = EXIT_NOTCONFIGURED;
   1448       return;
   1449     }
   1450     GNUNET_free (master_public_key_str);
   1451   }
   1452   ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
   1453                           &rc);
   1454   rc = GNUNET_CURL_gnunet_rc_create (ctx);
   1455   GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
   1456                                  NULL);
   1457   next (args);
   1458 }
   1459 
   1460 
   1461 /**
   1462  * The main function of the taler-auditor-offline tool.  This tool is used to
   1463  * sign denomination keys with the auditor's key.  It uses the long-term
   1464  * offline private key of the auditor and generates signatures with it. It
   1465  * also supports online operations with the exchange to download its input
   1466  * data and to upload its results. Those online operations should be performed
   1467  * on another machine in production!
   1468  *
   1469  * @param argc number of arguments from the command line
   1470  * @param argv command line arguments
   1471  * @return 0 ok, 1 on error
   1472  */
   1473 int
   1474 main (int argc,
   1475       char *const *argv)
   1476 {
   1477   struct GNUNET_GETOPT_CommandLineOption options[] = {
   1478     GNUNET_GETOPT_OPTION_END
   1479   };
   1480   enum GNUNET_GenericReturnValue ret;
   1481 
   1482   ret = GNUNET_PROGRAM_run (
   1483     TALER_AUDITOR_project_data (),
   1484     argc, argv,
   1485     "taler-auditor-offline",
   1486     gettext_noop ("Operations for offline signing for a Taler exchange"),
   1487     options,
   1488     &run, NULL);
   1489   if (GNUNET_SYSERR == ret)
   1490     return EXIT_INVALIDARGUMENT;
   1491   if (GNUNET_NO == ret)
   1492     return EXIT_SUCCESS;
   1493   return global_ret;
   1494 }
   1495 
   1496 
   1497 /* end of taler-auditor-offline.c */