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