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 (40416B)


      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 "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/exchange/get-keys.h"
     29 
     30 struct DenominationAddRequest;
     31 #define TALER_EXCHANGE_POST_AUDITORS_RESULT_CLOSURE \
     32         struct DenominationAddRequest
     33 #include "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  * Setup 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 denomination 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              "Warning: 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 adding denominations
    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_op (0);
    674       test_shutdown ();
    675       global_ret = EXIT_FAILURE;
    676       if (NULL != keys)
    677         TALER_EXCHANGE_keys_decref (keys);
    678       return;
    679     }
    680     break;
    681   default:
    682     fprintf (stderr,
    683              "Failed to download keys: %s (HTTP status: %u/EC: %u)\n",
    684              kr->hr.hint,
    685              kr->hr.http_status,
    686              (unsigned int) kr->hr.ec);
    687     test_shutdown ();
    688     global_ret = EXIT_FAILURE;
    689     return;
    690   }
    691   in = GNUNET_JSON_PACK (
    692     GNUNET_JSON_pack_string ("operation",
    693                              OP_INPUT_KEYS),
    694     GNUNET_JSON_pack_object_incref ("arguments",
    695                                     (json_t *) kr->hr.reply));
    696   if (NULL == args[0])
    697   {
    698     json_dumpf (in,
    699                 stdout,
    700                 JSON_INDENT (2));
    701     json_decref (in);
    702     in = NULL;
    703   }
    704   next (args);
    705   TALER_EXCHANGE_keys_decref (keys);
    706 }
    707 
    708 
    709 /**
    710  * Download future keys.
    711  *
    712  * @param args the array of command-line arguments to process next
    713  */
    714 static void
    715 do_download (char *const *args)
    716 {
    717   char *exchange_url;
    718 
    719   if (GNUNET_OK !=
    720       GNUNET_CONFIGURATION_get_value_string (kcfg,
    721                                              "exchange",
    722                                              "BASE_URL",
    723                                              &exchange_url))
    724   {
    725     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    726                                "exchange",
    727                                "BASE_URL");
    728     test_shutdown ();
    729     global_ret = EXIT_NOTCONFIGURED;
    730     return;
    731   }
    732   exchange = TALER_EXCHANGE_get_keys_create (ctx,
    733                                              exchange_url);
    734   GNUNET_assert (TALER_EC_NONE ==
    735                  TALER_EXCHANGE_get_keys_start (exchange,
    736                                                 &keys_cb,
    737                                                 args));
    738   GNUNET_free (exchange_url);
    739 }
    740 
    741 
    742 /**
    743  * Output @a denomkeys for human consumption.
    744  *
    745  * @param denomkeys keys to output
    746  * @return #GNUNET_OK on success
    747  */
    748 static enum GNUNET_GenericReturnValue
    749 show_denomkeys (const json_t *denomkeys)
    750 {
    751   size_t index;
    752   json_t *value;
    753 
    754   json_array_foreach (denomkeys, index, value) {
    755     struct TALER_DenominationGroup group;
    756     const json_t *denoms;
    757     const char *err_name;
    758     unsigned int err_line;
    759     struct GNUNET_JSON_Specification spec[] = {
    760       TALER_JSON_spec_denomination_group (NULL,
    761                                           currency,
    762                                           &group),
    763       GNUNET_JSON_spec_array_const ("denoms",
    764                                     &denoms),
    765       GNUNET_JSON_spec_end ()
    766     };
    767     size_t index2;
    768     json_t *value2;
    769 
    770     if (GNUNET_OK !=
    771         GNUNET_JSON_parse (value,
    772                            spec,
    773                            &err_name,
    774                            &err_line))
    775     {
    776       fprintf (stderr,
    777                "Invalid input for denomination key to 'show': %s#%u at %u (skipping)\n",
    778                err_name,
    779                err_line,
    780                (unsigned int) index);
    781       GNUNET_JSON_parse_free (spec);
    782       global_ret = EXIT_FAILURE;
    783       test_shutdown ();
    784       return GNUNET_SYSERR;
    785     }
    786     json_array_foreach (denoms, index2, value2) {
    787       struct GNUNET_TIME_Timestamp stamp_start;
    788       struct GNUNET_TIME_Timestamp stamp_expire_withdraw;
    789       struct GNUNET_TIME_Timestamp stamp_expire_deposit;
    790       struct GNUNET_TIME_Timestamp stamp_expire_legal;
    791       struct TALER_DenominationPublicKey denom_pub;
    792       struct TALER_MasterSignatureP master_sig;
    793       struct GNUNET_JSON_Specification ispec[] = {
    794         TALER_JSON_spec_denom_pub_cipher (NULL,
    795                                           group.cipher,
    796                                           &denom_pub),
    797         GNUNET_JSON_spec_timestamp ("stamp_start",
    798                                     &stamp_start),
    799         GNUNET_JSON_spec_timestamp ("stamp_expire_withdraw",
    800                                     &stamp_expire_withdraw),
    801         GNUNET_JSON_spec_timestamp ("stamp_expire_deposit",
    802                                     &stamp_expire_deposit),
    803         GNUNET_JSON_spec_timestamp ("stamp_expire_legal",
    804                                     &stamp_expire_legal),
    805         GNUNET_JSON_spec_fixed_auto ("master_sig",
    806                                      &master_sig),
    807         GNUNET_JSON_spec_end ()
    808       };
    809       struct GNUNET_TIME_Relative duration;
    810       struct TALER_DenominationHashP h_denom_pub;
    811 
    812       if (GNUNET_OK !=
    813           GNUNET_JSON_parse (value2,
    814                              ispec,
    815                              &err_name,
    816                              &err_line))
    817       {
    818         fprintf (stderr,
    819                  "Invalid input for denomination key to 'show': %s#%u at %u/%u (skipping)\n",
    820                  err_name,
    821                  err_line,
    822                  (unsigned int) index,
    823                  (unsigned int) index2);
    824         GNUNET_JSON_parse_free (spec);
    825         global_ret = EXIT_FAILURE;
    826         test_shutdown ();
    827         return GNUNET_SYSERR;
    828       }
    829       duration = GNUNET_TIME_absolute_get_difference (
    830         stamp_start.abs_time,
    831         stamp_expire_withdraw.abs_time);
    832       TALER_denom_pub_hash (&denom_pub,
    833                             &h_denom_pub);
    834       if (GNUNET_OK !=
    835           TALER_exchange_offline_denom_validity_verify (
    836             &h_denom_pub,
    837             stamp_start,
    838             stamp_expire_withdraw,
    839             stamp_expire_deposit,
    840             stamp_expire_legal,
    841             &group.value,
    842             &group.fees,
    843             &master_pub,
    844             &master_sig))
    845       {
    846         fprintf (stderr,
    847                  "Invalid master signature for key %s (aborting)\n",
    848                  TALER_B2S (&h_denom_pub));
    849         global_ret = EXIT_FAILURE;
    850         GNUNET_JSON_parse_free (ispec);
    851         GNUNET_JSON_parse_free (spec);
    852         test_shutdown ();
    853         return GNUNET_SYSERR;
    854       }
    855 
    856       {
    857         char *withdraw_fee_s;
    858         char *deposit_fee_s;
    859         char *refresh_fee_s;
    860         char *refund_fee_s;
    861         char *deposit_s;
    862         char *legal_s;
    863 
    864         withdraw_fee_s = TALER_amount_to_string (&group.fees.withdraw);
    865         deposit_fee_s = TALER_amount_to_string (&group.fees.deposit);
    866         refresh_fee_s = TALER_amount_to_string (&group.fees.refresh);
    867         refund_fee_s = TALER_amount_to_string (&group.fees.refund);
    868         deposit_s = GNUNET_strdup (
    869           GNUNET_TIME_timestamp2s (stamp_expire_deposit));
    870         legal_s = GNUNET_strdup (
    871           GNUNET_TIME_timestamp2s (stamp_expire_legal));
    872 
    873         printf (
    874           "DENOMINATION-KEY %s of value %s starting at %s "
    875           "(used for: %s, deposit until: %s legal end: %s) with fees %s/%s/%s/%s\n",
    876           TALER_B2S (&h_denom_pub),
    877           TALER_amount2s (&group.value),
    878           GNUNET_TIME_timestamp2s (stamp_start),
    879           GNUNET_TIME_relative2s (duration,
    880                                   false),
    881           deposit_s,
    882           legal_s,
    883           withdraw_fee_s,
    884           deposit_fee_s,
    885           refresh_fee_s,
    886           refund_fee_s);
    887         GNUNET_free (withdraw_fee_s);
    888         GNUNET_free (deposit_fee_s);
    889         GNUNET_free (refresh_fee_s);
    890         GNUNET_free (refund_fee_s);
    891         GNUNET_free (deposit_s);
    892         GNUNET_free (legal_s);
    893       }
    894       GNUNET_JSON_parse_free (ispec);
    895     }
    896     GNUNET_JSON_parse_free (spec);
    897   }
    898   return GNUNET_OK;
    899 }
    900 
    901 
    902 /**
    903  * Parse the '/keys' input for operation called @a command_name.
    904  *
    905  * @param command_name name of the command, for logging errors
    906  * @return NULL if the input is malformed
    907  */
    908 static json_t *
    909 parse_keys (const char *command_name)
    910 {
    911   json_t *keys;
    912   const char *op_str;
    913   struct GNUNET_JSON_Specification spec[] = {
    914     GNUNET_JSON_spec_json ("arguments",
    915                            &keys),
    916     GNUNET_JSON_spec_string ("operation",
    917                              &op_str),
    918     GNUNET_JSON_spec_end ()
    919   };
    920   const char *err_name;
    921   unsigned int err_line;
    922 
    923   if (NULL == in)
    924   {
    925     json_error_t err;
    926 
    927     in = json_loadf (stdin,
    928                      JSON_REJECT_DUPLICATES,
    929                      &err);
    930     if (NULL == in)
    931     {
    932       fprintf (stderr,
    933                "Failed to read JSON input: %s at %d:%s (offset: %d)\n",
    934                err.text,
    935                err.line,
    936                err.source,
    937                err.position);
    938       global_ret = EXIT_FAILURE;
    939       test_shutdown ();
    940       return NULL;
    941     }
    942   }
    943   if (GNUNET_OK !=
    944       GNUNET_JSON_parse (in,
    945                          spec,
    946                          &err_name,
    947                          &err_line))
    948   {
    949     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    950                 "Invalid input to '%s': %s#%u (skipping)\n",
    951                 command_name,
    952                 err_name,
    953                 err_line);
    954     json_dumpf (in,
    955                 stderr,
    956                 JSON_INDENT (2));
    957     global_ret = EXIT_FAILURE;
    958     test_shutdown ();
    959     return NULL;
    960   }
    961   if (0 != strcmp (op_str,
    962                    OP_INPUT_KEYS))
    963   {
    964     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    965                 "Invalid input to '%s' : operation is `%s', expected `%s'\n",
    966                 command_name,
    967                 op_str,
    968                 OP_INPUT_KEYS);
    969     GNUNET_JSON_parse_free (spec);
    970     global_ret = EXIT_FAILURE;
    971     test_shutdown ();
    972     return NULL;
    973   }
    974   json_decref (in);
    975   in = NULL;
    976   return keys;
    977 }
    978 
    979 
    980 /**
    981  * Show exchange denomination keys.
    982  *
    983  * @param args the array of command-line arguments to process next
    984  */
    985 static void
    986 do_show (char *const *args)
    987 {
    988   json_t *keys;
    989   const char *err_name;
    990   unsigned int err_line;
    991   const json_t *denomkeys;
    992   struct TALER_MasterPublicKeyP mpub;
    993   struct GNUNET_JSON_Specification spec[] = {
    994     GNUNET_JSON_spec_array_const ("denominations",
    995                                   &denomkeys),
    996     GNUNET_JSON_spec_fixed_auto ("master_public_key",
    997                                  &mpub),
    998     GNUNET_JSON_spec_end ()
    999   };
   1000 
   1001   keys = parse_keys ("show");
   1002   if (NULL == keys)
   1003   {
   1004     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1005                 "Showing failed: no valid input\n");
   1006     return;
   1007   }
   1008   if (GNUNET_OK !=
   1009       GNUNET_JSON_parse (keys,
   1010                          spec,
   1011                          &err_name,
   1012                          &err_line))
   1013   {
   1014     fprintf (stderr,
   1015              "Invalid input to 'show': %s#%u (skipping)\n",
   1016              err_name,
   1017              err_line);
   1018     global_ret = EXIT_FAILURE;
   1019     test_shutdown ();
   1020     json_decref (keys);
   1021     return;
   1022   }
   1023   if (0 !=
   1024       GNUNET_memcmp (&mpub,
   1025                      &master_pub))
   1026   {
   1027     fprintf (stderr,
   1028              "Exchange master public key does not match key we have configured (aborting)\n");
   1029     global_ret = EXIT_FAILURE;
   1030     test_shutdown ();
   1031     json_decref (keys);
   1032     return;
   1033   }
   1034   if (GNUNET_OK !=
   1035       show_denomkeys (denomkeys))
   1036   {
   1037     global_ret = EXIT_FAILURE;
   1038     test_shutdown ();
   1039     json_decref (keys);
   1040     return;
   1041   }
   1042   json_decref (keys);
   1043   /* do NOT consume input if next argument is '-' */
   1044   if ( (NULL != args[0]) &&
   1045        (0 == strcmp ("-",
   1046                      args[0])) )
   1047   {
   1048     next (args + 1);
   1049     return;
   1050   }
   1051   next (args);
   1052 }
   1053 
   1054 
   1055 /**
   1056  * Sign @a denomkeys with offline key.
   1057  *
   1058  * @param denomkeys keys to output
   1059  * @return #GNUNET_OK on success
   1060  */
   1061 static enum GNUNET_GenericReturnValue
   1062 sign_denomkeys (const json_t *denomkeys)
   1063 {
   1064   size_t group_idx;
   1065   json_t *value;
   1066 
   1067   json_array_foreach (denomkeys, group_idx, value) {
   1068     struct TALER_DenominationGroup group = { 0 };
   1069     const json_t *denom_keys_array;
   1070     const char *err_name;
   1071     unsigned int err_line;
   1072     struct GNUNET_JSON_Specification spec[] = {
   1073       TALER_JSON_spec_denomination_group (NULL,
   1074                                           currency,
   1075                                           &group),
   1076       GNUNET_JSON_spec_array_const ("denoms",
   1077                                     &denom_keys_array),
   1078       GNUNET_JSON_spec_end ()
   1079     };
   1080     size_t index;
   1081     json_t *denom_key_obj;
   1082 
   1083     if (GNUNET_OK !=
   1084         GNUNET_JSON_parse (value,
   1085                            spec,
   1086                            &err_name,
   1087                            &err_line))
   1088     {
   1089       fprintf (stderr,
   1090                "Invalid input for denomination key to 'sign': %s#%u at %u (skipping)\n",
   1091                err_name,
   1092                err_line,
   1093                (unsigned int) group_idx);
   1094       GNUNET_JSON_parse_free (spec);
   1095       global_ret = EXIT_FAILURE;
   1096       test_shutdown ();
   1097       return GNUNET_SYSERR;
   1098     }
   1099     json_array_foreach (denom_keys_array, index, denom_key_obj) {
   1100       struct GNUNET_TIME_Timestamp stamp_start;
   1101       struct GNUNET_TIME_Timestamp stamp_expire_withdraw;
   1102       struct GNUNET_TIME_Timestamp stamp_expire_deposit;
   1103       struct GNUNET_TIME_Timestamp stamp_expire_legal;
   1104       struct TALER_DenominationPublicKey denom_pub = {
   1105         .age_mask = group.age_mask
   1106       };
   1107       struct TALER_MasterSignatureP master_sig;
   1108       struct GNUNET_JSON_Specification ispec[] = {
   1109         TALER_JSON_spec_denom_pub_cipher (NULL,
   1110                                           group.cipher,
   1111                                           &denom_pub),
   1112         GNUNET_JSON_spec_timestamp ("stamp_start",
   1113                                     &stamp_start),
   1114         GNUNET_JSON_spec_timestamp ("stamp_expire_withdraw",
   1115                                     &stamp_expire_withdraw),
   1116         GNUNET_JSON_spec_timestamp ("stamp_expire_deposit",
   1117                                     &stamp_expire_deposit),
   1118         GNUNET_JSON_spec_timestamp ("stamp_expire_legal",
   1119                                     &stamp_expire_legal),
   1120         GNUNET_JSON_spec_fixed_auto ("master_sig",
   1121                                      &master_sig),
   1122         GNUNET_JSON_spec_end ()
   1123       };
   1124       struct TALER_DenominationHashP h_denom_pub;
   1125 
   1126       if (GNUNET_OK !=
   1127           GNUNET_JSON_parse (denom_key_obj,
   1128                              ispec,
   1129                              &err_name,
   1130                              &err_line))
   1131       {
   1132         fprintf (stderr,
   1133                  "Invalid input for denomination key to 'sign': %s#%u at %u/%u (skipping)\n",
   1134                  err_name,
   1135                  err_line,
   1136                  (unsigned int) group_idx,
   1137                  (unsigned int) index);
   1138         GNUNET_JSON_parse_free (spec);
   1139         global_ret = EXIT_FAILURE;
   1140         test_shutdown ();
   1141         return GNUNET_SYSERR;
   1142       }
   1143       TALER_denom_pub_hash (&denom_pub,
   1144                             &h_denom_pub);
   1145       if (GNUNET_OK !=
   1146           TALER_exchange_offline_denom_validity_verify (
   1147             &h_denom_pub,
   1148             stamp_start,
   1149             stamp_expire_withdraw,
   1150             stamp_expire_deposit,
   1151             stamp_expire_legal,
   1152             &group.value,
   1153             &group.fees,
   1154             &master_pub,
   1155             &master_sig))
   1156       {
   1157         fprintf (stderr,
   1158                  "Invalid master signature for key %s (aborting)\n",
   1159                  TALER_B2S (&h_denom_pub));
   1160         global_ret = EXIT_FAILURE;
   1161         test_shutdown ();
   1162         return GNUNET_SYSERR;
   1163       }
   1164 
   1165       {
   1166         struct TALER_AuditorSignatureP auditor_sig;
   1167 
   1168         TALER_auditor_denom_validity_sign (auditor_url,
   1169                                            &h_denom_pub,
   1170                                            &master_pub,
   1171                                            stamp_start,
   1172                                            stamp_expire_withdraw,
   1173                                            stamp_expire_deposit,
   1174                                            stamp_expire_legal,
   1175                                            &group.value,
   1176                                            &group.fees,
   1177                                            &auditor_priv,
   1178                                            &auditor_sig);
   1179         output_operation (OP_SIGN_DENOMINATION,
   1180                           GNUNET_JSON_PACK (
   1181                             GNUNET_JSON_pack_data_auto ("h_denom_pub",
   1182                                                         &h_denom_pub),
   1183                             GNUNET_JSON_pack_data_auto ("auditor_sig",
   1184                                                         &auditor_sig)));
   1185       }
   1186       GNUNET_JSON_parse_free (ispec);
   1187     }
   1188     GNUNET_JSON_parse_free (spec);
   1189   }
   1190   return GNUNET_OK;
   1191 }
   1192 
   1193 
   1194 /**
   1195  * Sign denomination keys.
   1196  *
   1197  * @param args the array of command-line arguments to process next
   1198  */
   1199 static void
   1200 do_sign (char *const *args)
   1201 {
   1202   json_t *keys;
   1203   const char *err_name;
   1204   unsigned int err_line;
   1205   struct TALER_MasterPublicKeyP mpub;
   1206   const json_t *denomkeys;
   1207   struct GNUNET_JSON_Specification spec[] = {
   1208     GNUNET_JSON_spec_array_const ("denominations",
   1209                                   &denomkeys),
   1210     GNUNET_JSON_spec_fixed_auto ("master_public_key",
   1211                                  &mpub),
   1212     GNUNET_JSON_spec_end ()
   1213   };
   1214 
   1215   keys = parse_keys ("sign");
   1216   if (NULL == keys)
   1217   {
   1218     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1219                 "Signing failed: no valid input\n");
   1220     return;
   1221   }
   1222   if (GNUNET_OK !=
   1223       load_offline_key (GNUNET_NO))
   1224   {
   1225     json_decref (keys);
   1226     return;
   1227   }
   1228   if (GNUNET_OK !=
   1229       GNUNET_JSON_parse (keys,
   1230                          spec,
   1231                          &err_name,
   1232                          &err_line))
   1233   {
   1234     fprintf (stderr,
   1235              "Invalid input to 'sign': %s#%u (skipping)\n",
   1236              err_name,
   1237              err_line);
   1238     global_ret = EXIT_FAILURE;
   1239     test_shutdown ();
   1240     json_decref (keys);
   1241     return;
   1242   }
   1243   if (0 !=
   1244       GNUNET_memcmp (&mpub,
   1245                      &master_pub))
   1246   {
   1247     fprintf (stderr,
   1248              "Exchange master public key does not match key we have configured (aborting)\n");
   1249     global_ret = EXIT_FAILURE;
   1250     test_shutdown ();
   1251     json_decref (keys);
   1252     return;
   1253   }
   1254   if (NULL == out)
   1255   {
   1256     out = json_array ();
   1257     GNUNET_assert (NULL != out);
   1258   }
   1259   if (GNUNET_OK !=
   1260       sign_denomkeys (denomkeys))
   1261   {
   1262     global_ret = EXIT_FAILURE;
   1263     test_shutdown ();
   1264     json_decref (keys);
   1265     return;
   1266   }
   1267   json_decref (keys);
   1268   next (args);
   1269 }
   1270 
   1271 
   1272 /**
   1273  * Setup and output offline signing key.
   1274  *
   1275  * @param args the array of command-line arguments to process next
   1276  */
   1277 static void
   1278 do_setup (char *const *args)
   1279 {
   1280   if (GNUNET_OK !=
   1281       load_offline_key (GNUNET_YES))
   1282   {
   1283     global_ret = EXIT_FAILURE;
   1284     return;
   1285   }
   1286   if (NULL != *args)
   1287   {
   1288     if (NULL == out)
   1289     {
   1290       out = json_array ();
   1291       GNUNET_assert (NULL != out);
   1292     }
   1293     output_operation (OP_SETUP,
   1294                       GNUNET_JSON_PACK (
   1295                         GNUNET_JSON_pack_data_auto ("auditor_pub",
   1296                                                     &auditor_pub)));
   1297   }
   1298 
   1299   else
   1300   {
   1301     char *pub_s;
   1302 
   1303     pub_s = GNUNET_STRINGS_data_to_string_alloc (&auditor_pub,
   1304                                                  sizeof (auditor_pub));
   1305     fprintf (stdout,
   1306              "%s\n",
   1307              pub_s);
   1308     GNUNET_free (pub_s);
   1309   }
   1310   if ( (NULL != *args) &&
   1311        (0 == strcmp (*args,
   1312                      "-")) )
   1313     args++;
   1314   next (args);
   1315 }
   1316 
   1317 
   1318 static void
   1319 work (void *cls)
   1320 {
   1321   char *const *args = cls;
   1322   struct SubCommand cmds[] = {
   1323     {
   1324       .name = "setup",
   1325       .help =
   1326         "setup auditor offline private key and show the public key",
   1327       .cb = &do_setup
   1328     },
   1329     {
   1330       .name = "download",
   1331       .help =
   1332         "obtain keys from exchange (to be performed online!)",
   1333       .cb = &do_download
   1334     },
   1335     {
   1336       .name = "show",
   1337       .help =
   1338         "display keys from exchange for human review (pass '-' as argument to disable consuming input)",
   1339       .cb = &do_show
   1340     },
   1341     {
   1342       .name = "sign",
   1343       .help =
   1344         "sign all denomination keys from the input",
   1345       .cb = &do_sign
   1346     },
   1347     {
   1348       .name = "upload",
   1349       .help =
   1350         "upload operation result to exchange (to be performed online!)",
   1351       .cb = &do_upload
   1352     },
   1353     /* list terminator */
   1354     {
   1355       .name = NULL,
   1356     }
   1357   };
   1358   (void) cls;
   1359 
   1360   nxt = NULL;
   1361   for (unsigned int i = 0; NULL != cmds[i].name; i++)
   1362   {
   1363     if (0 == strcasecmp (cmds[i].name,
   1364                          args[0]))
   1365     {
   1366       cmds[i].cb (&args[1]);
   1367       return;
   1368     }
   1369   }
   1370 
   1371   if (0 != strcasecmp ("help",
   1372                        args[0]))
   1373   {
   1374     fprintf (stderr,
   1375              "Unexpected command `%s'\n",
   1376              args[0]);
   1377     global_ret = EXIT_INVALIDARGUMENT;
   1378   }
   1379   fprintf (stderr,
   1380            "Supported subcommands:\n");
   1381   for (unsigned int i = 0; NULL != cmds[i].name; i++)
   1382   {
   1383     fprintf (stderr,
   1384              "\t%s - %s\n",
   1385              cmds[i].name,
   1386              cmds[i].help);
   1387   }
   1388 }
   1389 
   1390 
   1391 /**
   1392  * Main function that will be run.
   1393  *
   1394  * @param cls closure
   1395  * @param args remaining command-line arguments
   1396  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
   1397  * @param cfg configuration
   1398  */
   1399 static void
   1400 run (void *cls,
   1401      char *const *args,
   1402      const char *cfgfile,
   1403      const struct GNUNET_CONFIGURATION_Handle *cfg)
   1404 {
   1405   (void) cls;
   1406   (void) cfgfile;
   1407   kcfg = cfg;
   1408   if (GNUNET_OK !=
   1409       TALER_config_get_currency (kcfg,
   1410                                  "exchange",
   1411                                  &currency))
   1412   {
   1413     global_ret = EXIT_NOTCONFIGURED;
   1414     return;
   1415   }
   1416   if (GNUNET_OK !=
   1417       GNUNET_CONFIGURATION_get_value_string (kcfg,
   1418                                              "auditor",
   1419                                              "BASE_URL",
   1420                                              &auditor_url))
   1421   {
   1422     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
   1423                                "auditor",
   1424                                "BASE_URL");
   1425     global_ret = EXIT_NOTCONFIGURED;
   1426     return;
   1427   }
   1428   {
   1429     char *master_public_key_str;
   1430 
   1431     if (GNUNET_OK !=
   1432         GNUNET_CONFIGURATION_get_value_string (cfg,
   1433                                                "exchange",
   1434                                                "MASTER_PUBLIC_KEY",
   1435                                                &master_public_key_str))
   1436     {
   1437       GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
   1438                                  "exchange",
   1439                                  "MASTER_PUBLIC_KEY");
   1440       global_ret = EXIT_NOTCONFIGURED;
   1441       return;
   1442     }
   1443     if (GNUNET_OK !=
   1444         GNUNET_CRYPTO_eddsa_public_key_from_string (
   1445           master_public_key_str,
   1446           strlen (master_public_key_str),
   1447           &master_pub.eddsa_pub))
   1448     {
   1449       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1450                   "Invalid master public key given in exchange configuration.");
   1451       GNUNET_free (master_public_key_str);
   1452       global_ret = EXIT_NOTCONFIGURED;
   1453       return;
   1454     }
   1455     GNUNET_free (master_public_key_str);
   1456   }
   1457   ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
   1458                           &rc);
   1459   rc = GNUNET_CURL_gnunet_rc_create (ctx);
   1460   GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
   1461                                  NULL);
   1462   next (args);
   1463 }
   1464 
   1465 
   1466 /**
   1467  * The main function of the taler-auditor-offline tool.  This tool is used to
   1468  * sign denomination keys with the auditor's key.  It uses the long-term
   1469  * offline private key of the auditor and generates signatures with it. It
   1470  * also supports online operations with the exchange to download its input
   1471  * data and to upload its results. Those online operations should be performed
   1472  * on another machine in production!
   1473  *
   1474  * @param argc number of arguments from the command line
   1475  * @param argv command line arguments
   1476  * @return 0 ok, 1 on error
   1477  */
   1478 int
   1479 main (int argc,
   1480       char *const *argv)
   1481 {
   1482   struct GNUNET_GETOPT_CommandLineOption options[] = {
   1483     GNUNET_GETOPT_OPTION_END
   1484   };
   1485   enum GNUNET_GenericReturnValue ret;
   1486 
   1487   ret = GNUNET_PROGRAM_run (
   1488     TALER_AUDITOR_project_data (),
   1489     argc, argv,
   1490     "taler-auditor-offline",
   1491     gettext_noop ("Operations for offline signing for a Taler exchange"),
   1492     options,
   1493     &run, NULL);
   1494   if (GNUNET_SYSERR == ret)
   1495     return EXIT_INVALIDARGUMENT;
   1496   if (GNUNET_NO == ret)
   1497     return EXIT_SUCCESS;
   1498   return global_ret;
   1499 }
   1500 
   1501 
   1502 /* end of taler-auditor-offline.c */