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


      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     global_ret = EXIT_NOTCONFIGURED;
    367     test_shutdown ();
    368     return GNUNET_SYSERR;
    369   }
    370   ret = GNUNET_CRYPTO_eddsa_key_from_file (fn,
    371                                            do_create,
    372                                            &auditor_priv.eddsa_priv);
    373   if (GNUNET_SYSERR == ret)
    374   {
    375     if (do_create)
    376       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    377                   "Failed to initialize auditor key at `%s': %s\n",
    378                   fn,
    379                   "could not create file");
    380     else
    381       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    382                   "Failed to load auditor key from file `%s': try running `taler-auditor-offline setup'?\n",
    383                   fn);
    384     GNUNET_free (fn);
    385     global_ret = EXIT_FAILURE;
    386     test_shutdown ();
    387     return GNUNET_SYSERR;
    388   }
    389   GNUNET_free (fn);
    390   GNUNET_CRYPTO_eddsa_key_get_public (&auditor_priv.eddsa_priv,
    391                                       &auditor_pub.eddsa_pub);
    392   done = true;
    393   return GNUNET_OK;
    394 }
    395 
    396 
    397 /**
    398  * Function called with information about the post denomination (signature)
    399  * add operation result.
    400  *
    401  * @param dar the add request
    402  * @param adr response data
    403  */
    404 static void
    405 denomination_add_cb (
    406   struct DenominationAddRequest *dar,
    407   const struct TALER_EXCHANGE_PostAuditorsResponse *adr)
    408 {
    409   const struct TALER_EXCHANGE_HttpResponse *hr = &adr->hr;
    410 
    411   if (MHD_HTTP_NO_CONTENT != hr->http_status)
    412   {
    413     fprintf (stderr,
    414              "Upload failed for command #%u with status %u: %s (%s)\n",
    415              (unsigned int) dar->idx,
    416              hr->http_status,
    417              TALER_ErrorCode_get_hint (hr->ec),
    418              NULL != hr->hint
    419              ? hr->hint
    420              : "no hint provided");
    421     global_ret = EXIT_FAILURE;
    422   }
    423   GNUNET_CONTAINER_DLL_remove (dar_head,
    424                                dar_tail,
    425                                dar);
    426   GNUNET_free (dar);
    427   test_shutdown ();
    428 }
    429 
    430 
    431 /**
    432  * Upload denomination add data.
    433  *
    434  * @param exchange_url base URL of the exchange
    435  * @param idx index of the operation we are performing (for logging)
    436  * @param value arguments for adding denominations
    437  */
    438 static void
    439 upload_denomination_add (const char *exchange_url,
    440                          size_t idx,
    441                          const json_t *value)
    442 {
    443   struct TALER_AuditorSignatureP auditor_sig;
    444   struct TALER_DenominationHashP h_denom_pub;
    445   struct DenominationAddRequest *dar;
    446   const char *err_name;
    447   unsigned int err_line;
    448   struct GNUNET_JSON_Specification spec[] = {
    449     GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
    450                                  &h_denom_pub),
    451     GNUNET_JSON_spec_fixed_auto ("auditor_sig",
    452                                  &auditor_sig),
    453     GNUNET_JSON_spec_end ()
    454   };
    455 
    456   if (GNUNET_OK !=
    457       GNUNET_JSON_parse (value,
    458                          spec,
    459                          &err_name,
    460                          &err_line))
    461   {
    462     fprintf (stderr,
    463              "Invalid input for adding denomination: %s#%u at %u (skipping)\n",
    464              err_name,
    465              err_line,
    466              (unsigned int) idx);
    467     global_ret = EXIT_FAILURE;
    468     test_shutdown ();
    469     return;
    470   }
    471   dar = GNUNET_new (struct DenominationAddRequest);
    472   dar->idx = idx;
    473   dar->h = TALER_EXCHANGE_post_auditors_create (ctx,
    474                                                 exchange_url,
    475                                                 &h_denom_pub,
    476                                                 &auditor_pub,
    477                                                 &auditor_sig);
    478   GNUNET_assert (NULL != dar->h);
    479   GNUNET_assert (TALER_EC_NONE ==
    480                  TALER_EXCHANGE_post_auditors_start (dar->h,
    481                                                      &denomination_add_cb,
    482                                                      dar));
    483   GNUNET_CONTAINER_DLL_insert (dar_head,
    484                                dar_tail,
    485                                dar);
    486 }
    487 
    488 
    489 /**
    490  * Perform uploads based on the JSON in #out.
    491  *
    492  * @param exchange_url base URL of the exchange to use
    493  */
    494 static void
    495 trigger_upload (const char *exchange_url)
    496 {
    497   struct UploadHandler uhs[] = {
    498     {
    499       .key = OP_SIGN_DENOMINATION,
    500       .cb = &upload_denomination_add
    501     },
    502     /* array termination */
    503     {
    504       .key = NULL
    505     }
    506   };
    507   size_t index;
    508   json_t *obj;
    509 
    510   json_array_foreach (out, index, obj) {
    511     bool found = false;
    512     const char *key;
    513     const json_t *value;
    514 
    515     key = json_string_value (json_object_get (obj, "operation"));
    516     value = json_object_get (obj, "arguments");
    517     if (NULL == key)
    518     {
    519       fprintf (stderr,
    520                "Malformed JSON input\n");
    521       global_ret = EXIT_FAILURE;
    522       test_shutdown ();
    523       return;
    524     }
    525     /* block of code that uses key and value */
    526     for (unsigned int i = 0; NULL != uhs[i].key; i++)
    527     {
    528       if (0 == strcasecmp (key,
    529                            uhs[i].key))
    530       {
    531         found = true;
    532         uhs[i].cb (exchange_url,
    533                    index,
    534                    value);
    535         break;
    536       }
    537     }
    538     if (! found)
    539     {
    540       fprintf (stderr,
    541                "Upload does not know how to handle `%s'\n",
    542                key);
    543       global_ret = EXIT_FAILURE;
    544       test_shutdown ();
    545       return;
    546     }
    547   }
    548   /* test here, in case no upload was triggered (i.e. empty input) */
    549   test_shutdown ();
    550 }
    551 
    552 
    553 /**
    554  * Upload operation result (signatures) to exchange.
    555  *
    556  * @param args the array of command-line arguments to process next
    557  */
    558 static void
    559 do_upload (char *const *args)
    560 {
    561   char *exchange_url;
    562 
    563   (void) args;
    564   if (GNUNET_YES == GNUNET_is_zero (&auditor_pub))
    565   {
    566     /* private key not available, try configuration for public key */
    567     char *auditor_public_key_str;
    568 
    569     if (GNUNET_OK !=
    570         GNUNET_CONFIGURATION_get_value_string (kcfg,
    571                                                "auditor",
    572                                                "PUBLIC_KEY",
    573                                                &auditor_public_key_str))
    574     {
    575       GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    576                                  "auditor",
    577                                  "PUBLIC_KEY");
    578       global_ret = EXIT_NOTCONFIGURED;
    579       test_shutdown ();
    580       return;
    581     }
    582     if (GNUNET_OK !=
    583         GNUNET_CRYPTO_eddsa_public_key_from_string (
    584           auditor_public_key_str,
    585           strlen (auditor_public_key_str),
    586           &auditor_pub.eddsa_pub))
    587     {
    588       GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    589                                  "auditor",
    590                                  "PUBLIC_KEY",
    591                                  "invalid key");
    592       GNUNET_free (auditor_public_key_str);
    593       global_ret = EXIT_NOTCONFIGURED;
    594       test_shutdown ();
    595       return;
    596     }
    597     GNUNET_free (auditor_public_key_str);
    598   }
    599   if (NULL != in)
    600   {
    601     fprintf (stderr,
    602              "Downloaded data was not consumed, refusing upload\n");
    603     test_shutdown ();
    604     global_ret = EXIT_FAILURE;
    605     return;
    606   }
    607   if (NULL == out)
    608   {
    609     json_error_t err;
    610 
    611     out = json_loadf (stdin,
    612                       JSON_REJECT_DUPLICATES,
    613                       &err);
    614     if (NULL == out)
    615     {
    616       fprintf (stderr,
    617                "Failed to read JSON input: %s at %d:%s (offset: %d)\n",
    618                err.text,
    619                err.line,
    620                err.source,
    621                err.position);
    622       test_shutdown ();
    623       global_ret = EXIT_FAILURE;
    624       return;
    625     }
    626   }
    627   if (! json_is_array (out))
    628   {
    629     fprintf (stderr,
    630              "Error: expected JSON array for `upload` command\n");
    631     test_shutdown ();
    632     global_ret = EXIT_FAILURE;
    633     return;
    634   }
    635   if (GNUNET_OK !=
    636       GNUNET_CONFIGURATION_get_value_string (kcfg,
    637                                              "exchange",
    638                                              "BASE_URL",
    639                                              &exchange_url))
    640   {
    641     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    642                                "exchange",
    643                                "BASE_URL");
    644     global_ret = EXIT_NOTCONFIGURED;
    645     test_shutdown ();
    646     return;
    647   }
    648   trigger_upload (exchange_url);
    649   json_decref (out);
    650   out = NULL;
    651   GNUNET_free (exchange_url);
    652 }
    653 
    654 
    655 /**
    656  * Function called with information about who is auditing
    657  * a particular exchange and what keys the exchange is using.
    658  *
    659  * @param args the remaining args
    660  * @param kr response data
    661  * @param keys key data from the exchange
    662  */
    663 static void
    664 keys_cb (
    665   char *const *args,
    666   const struct TALER_EXCHANGE_KeysResponse *kr,
    667   struct TALER_EXCHANGE_Keys *keys)
    668 {
    669   exchange = NULL;
    670   switch (kr->hr.http_status)
    671   {
    672   case MHD_HTTP_OK:
    673     if (NULL == kr->hr.reply)
    674     {
    675       GNUNET_break_op (0);
    676       test_shutdown ();
    677       global_ret = EXIT_FAILURE;
    678       if (NULL != keys)
    679         TALER_EXCHANGE_keys_decref (keys);
    680       return;
    681     }
    682     break;
    683   default:
    684     fprintf (stderr,
    685              "Failed to download keys: %s (HTTP status: %u/EC: %u)\n",
    686              kr->hr.hint,
    687              kr->hr.http_status,
    688              (unsigned int) kr->hr.ec);
    689     test_shutdown ();
    690     global_ret = EXIT_FAILURE;
    691     return;
    692   }
    693   in = GNUNET_JSON_PACK (
    694     GNUNET_JSON_pack_string ("operation",
    695                              OP_INPUT_KEYS),
    696     GNUNET_JSON_pack_object_incref ("arguments",
    697                                     (json_t *) kr->hr.reply));
    698   if (NULL == args[0])
    699   {
    700     json_dumpf (in,
    701                 stdout,
    702                 JSON_INDENT (2));
    703     json_decref (in);
    704     in = NULL;
    705   }
    706   next (args);
    707   TALER_EXCHANGE_keys_decref (keys);
    708 }
    709 
    710 
    711 /**
    712  * Download future keys.
    713  *
    714  * @param args the array of command-line arguments to process next
    715  */
    716 static void
    717 do_download (char *const *args)
    718 {
    719   char *exchange_url;
    720 
    721   if (GNUNET_OK !=
    722       GNUNET_CONFIGURATION_get_value_string (kcfg,
    723                                              "exchange",
    724                                              "BASE_URL",
    725                                              &exchange_url))
    726   {
    727     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    728                                "exchange",
    729                                "BASE_URL");
    730     test_shutdown ();
    731     global_ret = EXIT_NOTCONFIGURED;
    732     return;
    733   }
    734   exchange = TALER_EXCHANGE_get_keys_create (ctx,
    735                                              exchange_url);
    736   GNUNET_assert (TALER_EC_NONE ==
    737                  TALER_EXCHANGE_get_keys_start (exchange,
    738                                                 &keys_cb,
    739                                                 args));
    740   GNUNET_free (exchange_url);
    741 }
    742 
    743 
    744 /**
    745  * Output @a denomkeys for human consumption.
    746  *
    747  * @param denomkeys keys to output
    748  * @return #GNUNET_OK on success
    749  */
    750 static enum GNUNET_GenericReturnValue
    751 show_denomkeys (const json_t *denomkeys)
    752 {
    753   size_t index;
    754   json_t *value;
    755 
    756   json_array_foreach (denomkeys, index, value) {
    757     struct TALER_DenominationGroup group;
    758     const json_t *denoms;
    759     const char *err_name;
    760     unsigned int err_line;
    761     struct GNUNET_JSON_Specification spec[] = {
    762       TALER_JSON_spec_denomination_group (NULL,
    763                                           currency,
    764                                           &group),
    765       GNUNET_JSON_spec_array_const ("denoms",
    766                                     &denoms),
    767       GNUNET_JSON_spec_end ()
    768     };
    769     size_t index2;
    770     json_t *value2;
    771 
    772     if (GNUNET_OK !=
    773         GNUNET_JSON_parse (value,
    774                            spec,
    775                            &err_name,
    776                            &err_line))
    777     {
    778       fprintf (stderr,
    779                "Invalid input for denomination key to 'show': %s#%u at %u (skipping)\n",
    780                err_name,
    781                err_line,
    782                (unsigned int) index);
    783       GNUNET_JSON_parse_free (spec);
    784       global_ret = EXIT_FAILURE;
    785       test_shutdown ();
    786       return GNUNET_SYSERR;
    787     }
    788     json_array_foreach (denoms, index2, value2) {
    789       struct GNUNET_TIME_Timestamp stamp_start;
    790       struct GNUNET_TIME_Timestamp stamp_expire_withdraw;
    791       struct GNUNET_TIME_Timestamp stamp_expire_deposit;
    792       struct GNUNET_TIME_Timestamp stamp_expire_legal;
    793       struct TALER_DenominationPublicKey denom_pub;
    794       struct TALER_MasterSignatureP master_sig;
    795       struct GNUNET_JSON_Specification ispec[] = {
    796         TALER_JSON_spec_denom_pub_cipher (NULL,
    797                                           group.cipher,
    798                                           &denom_pub),
    799         GNUNET_JSON_spec_timestamp ("stamp_start",
    800                                     &stamp_start),
    801         GNUNET_JSON_spec_timestamp ("stamp_expire_withdraw",
    802                                     &stamp_expire_withdraw),
    803         GNUNET_JSON_spec_timestamp ("stamp_expire_deposit",
    804                                     &stamp_expire_deposit),
    805         GNUNET_JSON_spec_timestamp ("stamp_expire_legal",
    806                                     &stamp_expire_legal),
    807         GNUNET_JSON_spec_fixed_auto ("master_sig",
    808                                      &master_sig),
    809         GNUNET_JSON_spec_end ()
    810       };
    811       struct GNUNET_TIME_Relative duration;
    812       struct TALER_DenominationHashP h_denom_pub;
    813 
    814       if (GNUNET_OK !=
    815           GNUNET_JSON_parse (value2,
    816                              ispec,
    817                              &err_name,
    818                              &err_line))
    819       {
    820         fprintf (stderr,
    821                  "Invalid input for denomination key to 'show': %s#%u at %u/%u (skipping)\n",
    822                  err_name,
    823                  err_line,
    824                  (unsigned int) index,
    825                  (unsigned int) index2);
    826         GNUNET_JSON_parse_free (spec);
    827         global_ret = EXIT_FAILURE;
    828         test_shutdown ();
    829         return GNUNET_SYSERR;
    830       }
    831       duration = GNUNET_TIME_absolute_get_difference (
    832         stamp_start.abs_time,
    833         stamp_expire_withdraw.abs_time);
    834       TALER_denom_pub_hash (&denom_pub,
    835                             &h_denom_pub);
    836       if (GNUNET_OK !=
    837           TALER_exchange_offline_denom_validity_verify (
    838             &h_denom_pub,
    839             stamp_start,
    840             stamp_expire_withdraw,
    841             stamp_expire_deposit,
    842             stamp_expire_legal,
    843             &group.value,
    844             &group.fees,
    845             &master_pub,
    846             &master_sig))
    847       {
    848         fprintf (stderr,
    849                  "Invalid master signature for key %s (aborting)\n",
    850                  TALER_B2S (&h_denom_pub));
    851         global_ret = EXIT_FAILURE;
    852         GNUNET_JSON_parse_free (ispec);
    853         GNUNET_JSON_parse_free (spec);
    854         test_shutdown ();
    855         return GNUNET_SYSERR;
    856       }
    857 
    858       {
    859         char *withdraw_fee_s;
    860         char *deposit_fee_s;
    861         char *refresh_fee_s;
    862         char *refund_fee_s;
    863         char *deposit_s;
    864         char *legal_s;
    865 
    866         withdraw_fee_s = TALER_amount_to_string (&group.fees.withdraw);
    867         deposit_fee_s = TALER_amount_to_string (&group.fees.deposit);
    868         refresh_fee_s = TALER_amount_to_string (&group.fees.refresh);
    869         refund_fee_s = TALER_amount_to_string (&group.fees.refund);
    870         deposit_s = GNUNET_strdup (
    871           GNUNET_TIME_timestamp2s (stamp_expire_deposit));
    872         legal_s = GNUNET_strdup (
    873           GNUNET_TIME_timestamp2s (stamp_expire_legal));
    874 
    875         printf (
    876           "DENOMINATION-KEY %s of value %s starting at %s "
    877           "(used for: %s, deposit until: %s legal end: %s) with fees %s/%s/%s/%s\n",
    878           TALER_B2S (&h_denom_pub),
    879           TALER_amount2s (&group.value),
    880           GNUNET_TIME_timestamp2s (stamp_start),
    881           GNUNET_TIME_relative2s (duration,
    882                                   false),
    883           deposit_s,
    884           legal_s,
    885           withdraw_fee_s,
    886           deposit_fee_s,
    887           refresh_fee_s,
    888           refund_fee_s);
    889         GNUNET_free (withdraw_fee_s);
    890         GNUNET_free (deposit_fee_s);
    891         GNUNET_free (refresh_fee_s);
    892         GNUNET_free (refund_fee_s);
    893         GNUNET_free (deposit_s);
    894         GNUNET_free (legal_s);
    895       }
    896       GNUNET_JSON_parse_free (ispec);
    897     }
    898     GNUNET_JSON_parse_free (spec);
    899   }
    900   return GNUNET_OK;
    901 }
    902 
    903 
    904 /**
    905  * Parse the '/keys' input for operation called @a command_name.
    906  *
    907  * @param command_name name of the command, for logging errors
    908  * @return NULL if the input is malformed
    909  */
    910 static json_t *
    911 parse_keys (const char *command_name)
    912 {
    913   json_t *keys;
    914   const char *op_str;
    915   struct GNUNET_JSON_Specification spec[] = {
    916     GNUNET_JSON_spec_json ("arguments",
    917                            &keys),
    918     GNUNET_JSON_spec_string ("operation",
    919                              &op_str),
    920     GNUNET_JSON_spec_end ()
    921   };
    922   const char *err_name;
    923   unsigned int err_line;
    924 
    925   if (NULL == in)
    926   {
    927     json_error_t err;
    928 
    929     in = json_loadf (stdin,
    930                      JSON_REJECT_DUPLICATES,
    931                      &err);
    932     if (NULL == in)
    933     {
    934       fprintf (stderr,
    935                "Failed to read JSON input: %s at %d:%s (offset: %d)\n",
    936                err.text,
    937                err.line,
    938                err.source,
    939                err.position);
    940       global_ret = EXIT_FAILURE;
    941       test_shutdown ();
    942       return NULL;
    943     }
    944   }
    945   if (GNUNET_OK !=
    946       GNUNET_JSON_parse (in,
    947                          spec,
    948                          &err_name,
    949                          &err_line))
    950   {
    951     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    952                 "Invalid input to '%s': %s#%u (skipping)\n",
    953                 command_name,
    954                 err_name,
    955                 err_line);
    956     json_dumpf (in,
    957                 stderr,
    958                 JSON_INDENT (2));
    959     global_ret = EXIT_FAILURE;
    960     test_shutdown ();
    961     return NULL;
    962   }
    963   if (0 != strcmp (op_str,
    964                    OP_INPUT_KEYS))
    965   {
    966     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    967                 "Invalid input to '%s' : operation is `%s', expected `%s'\n",
    968                 command_name,
    969                 op_str,
    970                 OP_INPUT_KEYS);
    971     GNUNET_JSON_parse_free (spec);
    972     global_ret = EXIT_FAILURE;
    973     test_shutdown ();
    974     return NULL;
    975   }
    976   json_decref (in);
    977   in = NULL;
    978   return keys;
    979 }
    980 
    981 
    982 /**
    983  * Show exchange denomination keys.
    984  *
    985  * @param args the array of command-line arguments to process next
    986  */
    987 static void
    988 do_show (char *const *args)
    989 {
    990   json_t *keys;
    991   const char *err_name;
    992   unsigned int err_line;
    993   const json_t *denomkeys;
    994   struct TALER_MasterPublicKeyP mpub;
    995   struct GNUNET_JSON_Specification spec[] = {
    996     GNUNET_JSON_spec_array_const ("denominations",
    997                                   &denomkeys),
    998     GNUNET_JSON_spec_fixed_auto ("master_public_key",
    999                                  &mpub),
   1000     GNUNET_JSON_spec_end ()
   1001   };
   1002 
   1003   keys = parse_keys ("show");
   1004   if (NULL == keys)
   1005   {
   1006     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1007                 "Showing failed: no valid input\n");
   1008     return;
   1009   }
   1010   if (GNUNET_OK !=
   1011       GNUNET_JSON_parse (keys,
   1012                          spec,
   1013                          &err_name,
   1014                          &err_line))
   1015   {
   1016     fprintf (stderr,
   1017              "Invalid input to 'show': %s#%u (skipping)\n",
   1018              err_name,
   1019              err_line);
   1020     global_ret = EXIT_FAILURE;
   1021     test_shutdown ();
   1022     json_decref (keys);
   1023     return;
   1024   }
   1025   if (0 !=
   1026       GNUNET_memcmp (&mpub,
   1027                      &master_pub))
   1028   {
   1029     fprintf (stderr,
   1030              "Exchange master public key does not match key we have configured (aborting)\n");
   1031     global_ret = EXIT_FAILURE;
   1032     test_shutdown ();
   1033     json_decref (keys);
   1034     return;
   1035   }
   1036   if (GNUNET_OK !=
   1037       show_denomkeys (denomkeys))
   1038   {
   1039     global_ret = EXIT_FAILURE;
   1040     test_shutdown ();
   1041     json_decref (keys);
   1042     return;
   1043   }
   1044   json_decref (keys);
   1045   /* do NOT consume input if next argument is '-' */
   1046   if ( (NULL != args[0]) &&
   1047        (0 == strcmp ("-",
   1048                      args[0])) )
   1049   {
   1050     next (args + 1);
   1051     return;
   1052   }
   1053   next (args);
   1054 }
   1055 
   1056 
   1057 /**
   1058  * Sign @a denomkeys with offline key.
   1059  *
   1060  * @param denomkeys keys to output
   1061  * @return #GNUNET_OK on success
   1062  */
   1063 static enum GNUNET_GenericReturnValue
   1064 sign_denomkeys (const json_t *denomkeys)
   1065 {
   1066   size_t group_idx;
   1067   json_t *value;
   1068 
   1069   json_array_foreach (denomkeys, group_idx, value) {
   1070     struct TALER_DenominationGroup group = { 0 };
   1071     const json_t *denom_keys_array;
   1072     const char *err_name;
   1073     unsigned int err_line;
   1074     struct GNUNET_JSON_Specification spec[] = {
   1075       TALER_JSON_spec_denomination_group (NULL,
   1076                                           currency,
   1077                                           &group),
   1078       GNUNET_JSON_spec_array_const ("denoms",
   1079                                     &denom_keys_array),
   1080       GNUNET_JSON_spec_end ()
   1081     };
   1082     size_t index;
   1083     json_t *denom_key_obj;
   1084 
   1085     if (GNUNET_OK !=
   1086         GNUNET_JSON_parse (value,
   1087                            spec,
   1088                            &err_name,
   1089                            &err_line))
   1090     {
   1091       fprintf (stderr,
   1092                "Invalid input for denomination key to 'sign': %s#%u at %u (skipping)\n",
   1093                err_name,
   1094                err_line,
   1095                (unsigned int) group_idx);
   1096       GNUNET_JSON_parse_free (spec);
   1097       global_ret = EXIT_FAILURE;
   1098       test_shutdown ();
   1099       return GNUNET_SYSERR;
   1100     }
   1101     json_array_foreach (denom_keys_array, index, denom_key_obj) {
   1102       struct GNUNET_TIME_Timestamp stamp_start;
   1103       struct GNUNET_TIME_Timestamp stamp_expire_withdraw;
   1104       struct GNUNET_TIME_Timestamp stamp_expire_deposit;
   1105       struct GNUNET_TIME_Timestamp stamp_expire_legal;
   1106       struct TALER_DenominationPublicKey denom_pub = {
   1107         .age_mask = group.age_mask
   1108       };
   1109       struct TALER_MasterSignatureP master_sig;
   1110       struct GNUNET_JSON_Specification ispec[] = {
   1111         TALER_JSON_spec_denom_pub_cipher (NULL,
   1112                                           group.cipher,
   1113                                           &denom_pub),
   1114         GNUNET_JSON_spec_timestamp ("stamp_start",
   1115                                     &stamp_start),
   1116         GNUNET_JSON_spec_timestamp ("stamp_expire_withdraw",
   1117                                     &stamp_expire_withdraw),
   1118         GNUNET_JSON_spec_timestamp ("stamp_expire_deposit",
   1119                                     &stamp_expire_deposit),
   1120         GNUNET_JSON_spec_timestamp ("stamp_expire_legal",
   1121                                     &stamp_expire_legal),
   1122         GNUNET_JSON_spec_fixed_auto ("master_sig",
   1123                                      &master_sig),
   1124         GNUNET_JSON_spec_end ()
   1125       };
   1126       struct TALER_DenominationHashP h_denom_pub;
   1127 
   1128       if (GNUNET_OK !=
   1129           GNUNET_JSON_parse (denom_key_obj,
   1130                              ispec,
   1131                              &err_name,
   1132                              &err_line))
   1133       {
   1134         fprintf (stderr,
   1135                  "Invalid input for denomination key to 'sign': %s#%u at %u/%u (skipping)\n",
   1136                  err_name,
   1137                  err_line,
   1138                  (unsigned int) group_idx,
   1139                  (unsigned int) index);
   1140         GNUNET_JSON_parse_free (spec);
   1141         global_ret = EXIT_FAILURE;
   1142         test_shutdown ();
   1143         return GNUNET_SYSERR;
   1144       }
   1145       TALER_denom_pub_hash (&denom_pub,
   1146                             &h_denom_pub);
   1147       if (GNUNET_OK !=
   1148           TALER_exchange_offline_denom_validity_verify (
   1149             &h_denom_pub,
   1150             stamp_start,
   1151             stamp_expire_withdraw,
   1152             stamp_expire_deposit,
   1153             stamp_expire_legal,
   1154             &group.value,
   1155             &group.fees,
   1156             &master_pub,
   1157             &master_sig))
   1158       {
   1159         fprintf (stderr,
   1160                  "Invalid master signature for key %s (aborting)\n",
   1161                  TALER_B2S (&h_denom_pub));
   1162         global_ret = EXIT_FAILURE;
   1163         test_shutdown ();
   1164         return GNUNET_SYSERR;
   1165       }
   1166 
   1167       {
   1168         struct TALER_AuditorSignatureP auditor_sig;
   1169 
   1170         TALER_auditor_denom_validity_sign (auditor_url,
   1171                                            &h_denom_pub,
   1172                                            &master_pub,
   1173                                            stamp_start,
   1174                                            stamp_expire_withdraw,
   1175                                            stamp_expire_deposit,
   1176                                            stamp_expire_legal,
   1177                                            &group.value,
   1178                                            &group.fees,
   1179                                            &auditor_priv,
   1180                                            &auditor_sig);
   1181         output_operation (OP_SIGN_DENOMINATION,
   1182                           GNUNET_JSON_PACK (
   1183                             GNUNET_JSON_pack_data_auto ("h_denom_pub",
   1184                                                         &h_denom_pub),
   1185                             GNUNET_JSON_pack_data_auto ("auditor_sig",
   1186                                                         &auditor_sig)));
   1187       }
   1188       GNUNET_JSON_parse_free (ispec);
   1189     }
   1190     GNUNET_JSON_parse_free (spec);
   1191   }
   1192   return GNUNET_OK;
   1193 }
   1194 
   1195 
   1196 /**
   1197  * Sign denomination keys.
   1198  *
   1199  * @param args the array of command-line arguments to process next
   1200  */
   1201 static void
   1202 do_sign (char *const *args)
   1203 {
   1204   json_t *keys;
   1205   const char *err_name;
   1206   unsigned int err_line;
   1207   struct TALER_MasterPublicKeyP mpub;
   1208   const json_t *denomkeys;
   1209   struct GNUNET_JSON_Specification spec[] = {
   1210     GNUNET_JSON_spec_array_const ("denominations",
   1211                                   &denomkeys),
   1212     GNUNET_JSON_spec_fixed_auto ("master_public_key",
   1213                                  &mpub),
   1214     GNUNET_JSON_spec_end ()
   1215   };
   1216 
   1217   keys = parse_keys ("sign");
   1218   if (NULL == keys)
   1219   {
   1220     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1221                 "Signing failed: no valid input\n");
   1222     return;
   1223   }
   1224   if (GNUNET_OK !=
   1225       load_offline_key (GNUNET_NO))
   1226   {
   1227     json_decref (keys);
   1228     return;
   1229   }
   1230   if (GNUNET_OK !=
   1231       GNUNET_JSON_parse (keys,
   1232                          spec,
   1233                          &err_name,
   1234                          &err_line))
   1235   {
   1236     fprintf (stderr,
   1237              "Invalid input to 'sign': %s#%u (skipping)\n",
   1238              err_name,
   1239              err_line);
   1240     global_ret = EXIT_FAILURE;
   1241     test_shutdown ();
   1242     json_decref (keys);
   1243     return;
   1244   }
   1245   if (0 !=
   1246       GNUNET_memcmp (&mpub,
   1247                      &master_pub))
   1248   {
   1249     fprintf (stderr,
   1250              "Exchange master public key does not match key we have configured (aborting)\n");
   1251     global_ret = EXIT_FAILURE;
   1252     test_shutdown ();
   1253     json_decref (keys);
   1254     return;
   1255   }
   1256   if (NULL == out)
   1257   {
   1258     out = json_array ();
   1259     GNUNET_assert (NULL != out);
   1260   }
   1261   if (GNUNET_OK !=
   1262       sign_denomkeys (denomkeys))
   1263   {
   1264     global_ret = EXIT_FAILURE;
   1265     test_shutdown ();
   1266     json_decref (keys);
   1267     return;
   1268   }
   1269   json_decref (keys);
   1270   next (args);
   1271 }
   1272 
   1273 
   1274 /**
   1275  * Setup and output offline signing key.
   1276  *
   1277  * @param args the array of command-line arguments to process next
   1278  */
   1279 static void
   1280 do_setup (char *const *args)
   1281 {
   1282   if (GNUNET_OK !=
   1283       load_offline_key (GNUNET_YES))
   1284   {
   1285     global_ret = EXIT_FAILURE;
   1286     return;
   1287   }
   1288   if (NULL != *args)
   1289   {
   1290     if (NULL == out)
   1291     {
   1292       out = json_array ();
   1293       GNUNET_assert (NULL != out);
   1294     }
   1295     output_operation (OP_SETUP,
   1296                       GNUNET_JSON_PACK (
   1297                         GNUNET_JSON_pack_data_auto ("auditor_pub",
   1298                                                     &auditor_pub)));
   1299   }
   1300 
   1301   else
   1302   {
   1303     char *pub_s;
   1304 
   1305     pub_s = GNUNET_STRINGS_data_to_string_alloc (&auditor_pub,
   1306                                                  sizeof (auditor_pub));
   1307     fprintf (stdout,
   1308              "%s\n",
   1309              pub_s);
   1310     GNUNET_free (pub_s);
   1311   }
   1312   if ( (NULL != *args) &&
   1313        (0 == strcmp (*args,
   1314                      "-")) )
   1315     args++;
   1316   next (args);
   1317 }
   1318 
   1319 
   1320 static void
   1321 work (void *cls)
   1322 {
   1323   char *const *args = cls;
   1324   struct SubCommand cmds[] = {
   1325     {
   1326       .name = "setup",
   1327       .help =
   1328         "setup auditor offline private key and show the public key",
   1329       .cb = &do_setup
   1330     },
   1331     {
   1332       .name = "download",
   1333       .help =
   1334         "obtain keys from exchange (to be performed online!)",
   1335       .cb = &do_download
   1336     },
   1337     {
   1338       .name = "show",
   1339       .help =
   1340         "display keys from exchange for human review (pass '-' as argument to disable consuming input)",
   1341       .cb = &do_show
   1342     },
   1343     {
   1344       .name = "sign",
   1345       .help =
   1346         "sign all denomination keys from the input",
   1347       .cb = &do_sign
   1348     },
   1349     {
   1350       .name = "upload",
   1351       .help =
   1352         "upload operation result to exchange (to be performed online!)",
   1353       .cb = &do_upload
   1354     },
   1355     /* list terminator */
   1356     {
   1357       .name = NULL,
   1358     }
   1359   };
   1360   (void) cls;
   1361 
   1362   nxt = NULL;
   1363   for (unsigned int i = 0; NULL != cmds[i].name; i++)
   1364   {
   1365     if (0 == strcasecmp (cmds[i].name,
   1366                          args[0]))
   1367     {
   1368       cmds[i].cb (&args[1]);
   1369       return;
   1370     }
   1371   }
   1372 
   1373   if (0 != strcasecmp ("help",
   1374                        args[0]))
   1375   {
   1376     fprintf (stderr,
   1377              "Unexpected command `%s'\n",
   1378              args[0]);
   1379     global_ret = EXIT_INVALIDARGUMENT;
   1380   }
   1381   fprintf (stderr,
   1382            "Supported subcommands:\n");
   1383   for (unsigned int i = 0; NULL != cmds[i].name; i++)
   1384   {
   1385     fprintf (stderr,
   1386              "\t%s - %s\n",
   1387              cmds[i].name,
   1388              cmds[i].help);
   1389   }
   1390 }
   1391 
   1392 
   1393 /**
   1394  * Main function that will be run.
   1395  *
   1396  * @param cls closure
   1397  * @param args remaining command-line arguments
   1398  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
   1399  * @param cfg configuration
   1400  */
   1401 static void
   1402 run (void *cls,
   1403      char *const *args,
   1404      const char *cfgfile,
   1405      const struct GNUNET_CONFIGURATION_Handle *cfg)
   1406 {
   1407   (void) cls;
   1408   (void) cfgfile;
   1409   kcfg = cfg;
   1410   if (GNUNET_OK !=
   1411       TALER_config_get_currency (kcfg,
   1412                                  "exchange",
   1413                                  &currency))
   1414   {
   1415     global_ret = EXIT_NOTCONFIGURED;
   1416     return;
   1417   }
   1418   if (GNUNET_OK !=
   1419       GNUNET_CONFIGURATION_get_value_string (kcfg,
   1420                                              "auditor",
   1421                                              "BASE_URL",
   1422                                              &auditor_url))
   1423   {
   1424     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
   1425                                "auditor",
   1426                                "BASE_URL");
   1427     global_ret = EXIT_NOTCONFIGURED;
   1428     return;
   1429   }
   1430   {
   1431     char *master_public_key_str;
   1432 
   1433     if (GNUNET_OK !=
   1434         GNUNET_CONFIGURATION_get_value_string (cfg,
   1435                                                "exchange",
   1436                                                "MASTER_PUBLIC_KEY",
   1437                                                &master_public_key_str))
   1438     {
   1439       GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
   1440                                  "exchange",
   1441                                  "MASTER_PUBLIC_KEY");
   1442       global_ret = EXIT_NOTCONFIGURED;
   1443       return;
   1444     }
   1445     if (GNUNET_OK !=
   1446         GNUNET_CRYPTO_eddsa_public_key_from_string (
   1447           master_public_key_str,
   1448           strlen (master_public_key_str),
   1449           &master_pub.eddsa_pub))
   1450     {
   1451       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
   1452                   "Invalid master public key given in exchange configuration.");
   1453       GNUNET_free (master_public_key_str);
   1454       global_ret = EXIT_NOTCONFIGURED;
   1455       return;
   1456     }
   1457     GNUNET_free (master_public_key_str);
   1458   }
   1459   ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
   1460                           &rc);
   1461   rc = GNUNET_CURL_gnunet_rc_create (ctx);
   1462   GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
   1463                                  NULL);
   1464   next (args);
   1465 }
   1466 
   1467 
   1468 /**
   1469  * The main function of the taler-auditor-offline tool.  This tool is used to
   1470  * sign denomination keys with the auditor's key.  It uses the long-term
   1471  * offline private key of the auditor and generates signatures with it. It
   1472  * also supports online operations with the exchange to download its input
   1473  * data and to upload its results. Those online operations should be performed
   1474  * on another machine in production!
   1475  *
   1476  * @param argc number of arguments from the command line
   1477  * @param argv command line arguments
   1478  * @return 0 ok, 1 on error
   1479  */
   1480 int
   1481 main (int argc,
   1482       char *const *argv)
   1483 {
   1484   struct GNUNET_GETOPT_CommandLineOption options[] = {
   1485     GNUNET_GETOPT_OPTION_END
   1486   };
   1487   enum GNUNET_GenericReturnValue ret;
   1488 
   1489   ret = GNUNET_PROGRAM_run (
   1490     TALER_AUDITOR_project_data (),
   1491     argc, argv,
   1492     "taler-auditor-offline",
   1493     gettext_noop ("Operations for offline signing for a Taler exchange"),
   1494     options,
   1495     &run, NULL);
   1496   if (GNUNET_SYSERR == ret)
   1497     return EXIT_INVALIDARGUMENT;
   1498   if (GNUNET_NO == ret)
   1499     return EXIT_SUCCESS;
   1500   return global_ret;
   1501 }
   1502 
   1503 
   1504 /* end of taler-auditor-offline.c */