diff --git a/src/exchange-tools/taler-exchange-offline.c b/src/exchange-tools/taler-exchange-offline.c index 6ad345ebf..3ef022ec8 100644 --- a/src/exchange-tools/taler-exchange-offline.c +++ b/src/exchange-tools/taler-exchange-offline.c @@ -1924,6 +1924,7 @@ trigger_upload (const char *exchange_url) if (0 == strcasecmp (key, uhs[i].key)) { + found = true; uhs[i].cb (exchange_url, index, diff --git a/src/exchange/taler-exchange-httpd.c b/src/exchange/taler-exchange-httpd.c index ae5847d11..0a1276c7b 100644 --- a/src/exchange/taler-exchange-httpd.c +++ b/src/exchange/taler-exchange-httpd.c @@ -736,6 +736,12 @@ handle_post_management (struct TEH_RequestContext *rc, return TEH_handler_management_post_wire_fees (rc->connection, root); } + if (0 == strcmp (args[0], + "extensions")) + { + return TEH_handler_management_post_extensions (rc->connection, + root); + } GNUNET_break_op (0); return r404 (rc->connection, "/management/*"); diff --git a/src/exchange/taler-exchange-httpd_keys.c b/src/exchange/taler-exchange-httpd_keys.c index de9b81cdd..6e25876dc 100644 --- a/src/exchange/taler-exchange-httpd_keys.c +++ b/src/exchange/taler-exchange-httpd_keys.c @@ -1755,7 +1755,7 @@ create_krd (struct TEH_KeyStateHandle *ksh, json_t *sig; int r; - r = json_object_set_new ( + r = json_object_set ( keys, "extensions", extensions); @@ -1778,7 +1778,7 @@ create_krd (struct TEH_KeyStateHandle *ksh, { GNUNET_assert ( 0 == - json_object_set_new ( + json_object_set ( keys, "age_restricted_denoms", age_restricted_denoms)); @@ -1858,6 +1858,8 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh) struct GNUNET_TIME_Timestamp last_cpd; struct GNUNET_CONTAINER_Heap *heap; struct GNUNET_HashContext *hash_context; + bool age_restriction_active = + TALER_extensions_is_enabled_type (TALER_Extension_AgeRestriction); sctx.signkeys = json_array (); GNUNET_assert (NULL != sctx.signkeys); @@ -1887,7 +1889,7 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh) // If age restriction is enabled, initialize the array of age restricted denoms. /* TODO: optimize by putting this into global? */ - if (TALER_extensions_is_enabled_type (TALER_Extension_AgeRestriction)) + if (age_restriction_active) { age_restricted_denoms = json_array (); GNUNET_assert (NULL != age_restricted_denoms); @@ -1972,15 +1974,14 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh) /* Put the denom into the correct array - denoms or age_restricted_denoms - * depending on the settings and the properties of the denomination */ - if (NULL != age_restricted_denoms && - 0 != dk->meta.age_restrictions.mask) - { + if (age_restriction_active && + (0 != dk->denom_pub.age_mask.mask)) + { + GNUNET_log(GNUNET_ERROR_TYPE_WARNING, "got agerestricted denom %p with mask %d\n", &dk->denom_pub, dk->denom_pub.age_mask.mask); array = age_restricted_denoms; - } + } else - { array = denoms; - } GNUNET_assert ( 0 == @@ -1990,6 +1991,7 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh) } } } + GNUNET_CONTAINER_heap_destroy (heap); if (! GNUNET_TIME_absolute_is_zero (last_cpd.abs_time)) { @@ -2010,7 +2012,7 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh) "Failed to generate key response data for %s\n", GNUNET_TIME_timestamp2s (last_cpd)); json_decref (denoms); - if (NULL != age_restricted_denoms) + if (age_restriction_active && NULL != age_restricted_denoms) json_decref (age_restricted_denoms); json_decref (sctx.signkeys); json_decref (recoup); diff --git a/src/exchange/taler-exchange-httpd_management_extensions.c b/src/exchange/taler-exchange-httpd_management_extensions.c index 17b000067..ce3ab3f13 100644 --- a/src/exchange/taler-exchange-httpd_management_extensions.c +++ b/src/exchange/taler-exchange-httpd_management_extensions.c @@ -91,6 +91,8 @@ set_extensions (void *cls, return GNUNET_DB_STATUS_HARD_ERROR; } + GNUNET_assert(NULL != ext->config); + config = json_dumps (ext->config, JSON_COMPACT | JSON_SORT_KEYS); if (NULL == config) { @@ -140,6 +142,58 @@ set_extensions (void *cls, } + +static enum GNUNET_GenericReturnValue +verify_extensions_from_json ( + json_t *extensions, + struct SetExtensionsContext *sec) +{ + const char*name; + const struct TALER_Extension *extension; + size_t i=0; + json_t *blob; + + GNUNET_assert (NULL != extensions); + GNUNET_assert (json_is_object (extensions)); + + sec->num_extensions = json_object_size (extensions); + sec->extensions = GNUNET_new_array (sec->num_extensions, + struct Extension); + + json_object_foreach (extensions, name, blob) + { + int critical = 0; + json_t *config; + const char *version = NULL; + + /* load and verify criticality, version, etc. */ + extension = TALER_extensions_get_by_name (name); + if (NULL == extension) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "no such extension: %s\n", name); + return GNUNET_SYSERR; + } + + if (GNUNET_OK != + TALER_extensions_is_json_config ( + blob, &critical, &version, &config)) + return GNUNET_SYSERR; + + if (critical != extension->critical + || 0 != strcmp (version, extension->version) // TODO: libtool compare? + || NULL == config + || GNUNET_OK != extension->test_json_config (config)) + return GNUNET_SYSERR; + + sec->extensions[i].type = extension->type; + sec->extensions[i].config = config; + } + + return GNUNET_OK; +} + + MHD_RESULT TEH_handler_management_post_extensions ( struct MHD_Connection *connection, @@ -204,57 +258,18 @@ TEH_handler_management_post_extensions ( GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Received /management/extensions\n"); - sec.num_extensions = json_object_size (extensions); - sec.extensions = GNUNET_new_array (sec.num_extensions, - struct Extension); - /* Now parse individual extensions and signatures from those objects. */ + if (GNUNET_OK != + verify_extensions_from_json (extensions, &sec)) { - const struct TALER_Extension *extension = NULL; - const char *name; - json_t *config; - int idx = 0; - - json_object_foreach (extensions, name, config){ - - /* 1. Make sure name refers to a supported extension */ - extension = TALER_extensions_get_by_name (name); - if (NULL == extension) - { - ret = TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "invalid extension type"); - goto CLEANUP; - } - - sec.extensions[idx].config = config; - sec.extensions[idx].type = extension->type; - - /* 2. Make sure the config is sound */ - if (GNUNET_OK != - extension->test_json_config ( - sec.extensions[idx].config)) - { - ret = TALER_MHD_reply_with_error ( - connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_GENERIC_PARAMETER_MALFORMED, - "invalid configuration for extension"); - goto CLEANUP; - } - - /* We have a validly signed JSON object for the extension. Increment its - * refcount. - */ - json_incref (sec.extensions[idx].config); - idx++; - - } /* json_object_foreach */ + GNUNET_JSON_parse_free (top_spec); + return TALER_MHD_reply_with_error ( + connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_GENERIC_PARAMETER_MALFORMED, + "invalid object"); } - GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Received %u extensions\n", sec.num_extensions); diff --git a/src/extensions/extension_age_restriction.c b/src/extensions/extension_age_restriction.c index 704161e56..0676312e9 100644 --- a/src/extensions/extension_age_restriction.c +++ b/src/extensions/extension_age_restriction.c @@ -247,7 +247,7 @@ age_restriction_load_json_config ( struct TALER_AgeMask mask = {0}; enum GNUNET_GenericReturnValue ret; - ret = TALER_JSON_parse_agemask (jconfig, &mask); + ret = TALER_JSON_parse_age_groups (jconfig, &mask); if (GNUNET_OK != ret) return ret; @@ -323,7 +323,7 @@ age_restriction_test_json_config ( { struct TALER_AgeMask mask = {0}; - return TALER_JSON_parse_agemask (config, &mask); + return TALER_JSON_parse_age_groups (config, &mask); } @@ -357,4 +357,29 @@ TALER_extensions_age_restriction_num_groups () } +enum GNUNET_GenericReturnValue +TALER_JSON_parse_age_groups (const json_t *root, + struct TALER_AgeMask *mask) +{ + enum GNUNET_GenericReturnValue ret; + const char *str; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("age_groups", + &str), + GNUNET_JSON_spec_end () + }; + + ret = GNUNET_JSON_parse (root, + spec, + NULL, + NULL); + if (GNUNET_OK == ret) + TALER_parse_age_group_string (str, mask); + + GNUNET_JSON_parse_free (spec); + + return ret; +} + + /* end of extension_age_restriction.c */ diff --git a/src/extensions/extensions.c b/src/extensions/extensions.c index 55d970c57..244514377 100644 --- a/src/extensions/extensions.c +++ b/src/extensions/extensions.c @@ -247,27 +247,31 @@ TALER_extensions_load_taler_config ( } -static enum GNUNET_GenericReturnValue -is_json_extension_config ( +enum GNUNET_GenericReturnValue +TALER_extensions_is_json_config ( json_t *obj, int *critical, const char **version, json_t **config) { enum GNUNET_GenericReturnValue ret; + json_t *cfg; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_boolean ("critical", critical), GNUNET_JSON_spec_string ("version", version), GNUNET_JSON_spec_json ("config", - config), + &cfg), GNUNET_JSON_spec_end () }; ret = GNUNET_JSON_parse (obj, spec, NULL, NULL); if (GNUNET_OK == ret) + { + *config = json_copy (cfg); GNUNET_JSON_parse_free (spec); + } return ret; } @@ -300,7 +304,7 @@ TALER_extensions_load_json_config ( /* load and verify criticality, version, etc. */ if (GNUNET_OK != - is_json_extension_config ( + TALER_extensions_is_json_config ( blob, &critical, &version, &config)) return GNUNET_SYSERR; diff --git a/src/include/taler_extensions.h b/src/include/taler_extensions.h index be28c9618..cb09297b5 100644 --- a/src/include/taler_extensions.h +++ b/src/include/taler_extensions.h @@ -85,6 +85,31 @@ enum GNUNET_GenericReturnValue TALER_extensions_load_taler_config ( const struct GNUNET_CONFIGURATION_Handle *cfg); +/* + * Check the given obj to be a valid extension object and fill the fields + * accordingly. + */ +enum GNUNET_GenericReturnValue +TALER_extensions_is_json_config ( + json_t *obj, + int *critical, + const char **version, + json_t **config); + +/* + * Sets the configuration of the extensions from a given JSON object. + * + * he JSON object must be of type ExchangeKeysResponse as described in + * https://docs.taler.net/design-documents/006-extensions.html#exchange + * + * @param cfg JSON object containting the configuration for all extensions + * @return GNUNET_OK on success, GNUNET_SYSERR if unknown extensions were found + * or any particular configuration couldn't be parsed. + */ +enum GNUNET_GenericReturnValue +TALER_extensions_load_json_config ( + json_t *cfg); + /* * Returns the head of the linked list of extensions */ @@ -156,20 +181,6 @@ TALER_extensions_verify_json_config_signature ( struct TALER_MasterSignatureP *extensions_sig, struct TALER_MasterPublicKeyP *master_pub); -/* - * Sets the configuration of the extensions from a given JSON object. - * - * The JSON object must be of type ExchangeKeysResponse as described in - * https://docs.taler.net/design-documents/006-extensions.html#exchange - * - * @param cfg Handle to the TALER configuration - * @return GNUNET_OK on success, GNUNET_SYSERR if unknown extensions were found - * or any particular configuration couldn't be parsed. - */ -enum GNUNET_GenericReturnValue -TALER_extensions_load_json_config ( - json_t *extensions); - /* * TALER Age Restriction Extension @@ -234,6 +245,18 @@ TALER_extensions_age_restriction_enabled (); size_t TALER_extensions_age_restriction_num_groups (); +/** + * Parses a JSON object { "age_groups": "a:b:...y:z" }. + * + * @param root is the json object + * @param[out] mask on succes, will contain the age mask + * @return #GNUNET_OK on success and #GNUNET_SYSERR on failure. + */ +enum GNUNET_GenericReturnValue +TALER_JSON_parse_age_groups (const json_t *root, + struct TALER_AgeMask *mask); + + /* * TODO: Add Peer2Peer Extension */ diff --git a/src/include/taler_json_lib.h b/src/include/taler_json_lib.h index 2a101d269..df95e042b 100644 --- a/src/include/taler_json_lib.h +++ b/src/include/taler_json_lib.h @@ -552,17 +552,6 @@ enum GNUNET_GenericReturnValue TALER_JSON_extensions_config_hash (const json_t *config, struct TALER_ExtensionConfigHash *eh); -/** - * Parses a JSON object { "extension": "age_restriction", "mask": }. - * - * @param root is the json object - * @param[out] mask on succes, will contain the age mask - * @return #GNUNET_OK on success and #GNUNET_SYSERR on failure. - */ -enum GNUNET_GenericReturnValue -TALER_JSON_parse_agemask (const json_t *root, - struct TALER_AgeMask *mask); - #endif /* TALER_JSON_LIB_H_ */ /* End of taler_json_lib.h */ diff --git a/src/include/taler_testing_lib.h b/src/include/taler_testing_lib.h index 7c4e622bf..4d8b8a407 100644 --- a/src/include/taler_testing_lib.h +++ b/src/include/taler_testing_lib.h @@ -2144,6 +2144,19 @@ TALER_TESTING_cmd_wire_del (const char *label, unsigned int expected_http_status, bool bad_sig); +/** + * Sign all extensions that the exchange has to offer, f. e. the extension for + * age restriction. This has to be run before any withdrawal of age restricted + * can be performed. + * + * @param label command label. + * @param config_filename configuration filename. + * @return the command + */ +struct TALER_TESTING_Command +TALER_TESTING_cmd_exec_offline_sign_extensions (const char *label, + const char *config_filename); + /** * Sign all exchange denomination and online signing keys diff --git a/src/json/json_helper.c b/src/json/json_helper.c index 1942d09bd..c777f69c9 100644 --- a/src/json/json_helper.c +++ b/src/json/json_helper.c @@ -658,37 +658,4 @@ TALER_JSON_spec_i18n_str (const char *name, return ret; } - -enum GNUNET_GenericReturnValue -TALER_JSON_parse_agemask (const json_t *root, - struct TALER_AgeMask *mask) -{ - const char *name; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_string ("extension", - &name), - GNUNET_JSON_spec_uint32 ("mask", - &mask->mask), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != GNUNET_JSON_parse (root, - spec, - NULL, - NULL)) - { - return GNUNET_SYSERR; - } - - if (! strncmp (name, - "age_restriction", - sizeof("age_restriction"))) - { - return GNUNET_SYSERR; - } - - return GNUNET_OK; -} - - /* end of json/json_helper.c */ diff --git a/src/lib/exchange_api_handle.c b/src/lib/exchange_api_handle.c index 372f81735..c76845f5c 100644 --- a/src/lib/exchange_api_handle.c +++ b/src/lib/exchange_api_handle.c @@ -67,7 +67,7 @@ /** * Set to 1 for extra debug logging. */ -#define DEBUG 0 +#define DEBUG 1 /* FIXME-oec */ /** * Log error related to CURL operations. @@ -839,9 +839,10 @@ decode_keys_json (const json_t *resp_obj, */ struct { char *name; - bool is_optional_age_restriction;} hive[2] = { + bool is_optional_age_restriction;} + hive[2] = { { "denoms", false }, - { "age_restricted_denoms", true }, + { "age_restricted_denoms", true } }; for (size_t s = 0; s < sizeof(hive) / sizeof(hive[0]); s++) diff --git a/src/lib/exchange_api_management_post_extensions.c b/src/lib/exchange_api_management_post_extensions.c index c0ab143f6..87b0e0be8 100644 --- a/src/lib/exchange_api_management_post_extensions.c +++ b/src/lib/exchange_api_management_post_extensions.c @@ -151,7 +151,7 @@ TALER_EXCHANGE_management_post_extensions ( body = GNUNET_JSON_PACK ( GNUNET_JSON_pack_object_steal ("extensions", ped->extensions), - GNUNET_JSON_pack_data_auto ("extensions_sigs", + GNUNET_JSON_pack_data_auto ("extensions_sig", &ped->extensions_sig)); eh = curl_easy_init (); @@ -168,7 +168,7 @@ TALER_EXCHANGE_management_post_extensions ( return NULL; } json_decref (body); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Requesting URL '%s'\n", ph->url); GNUNET_assert (CURLE_OK == curl_easy_setopt (eh, diff --git a/src/testing/Makefile.am b/src/testing/Makefile.am index bc78217b3..af456bb77 100644 --- a/src/testing/Makefile.am +++ b/src/testing/Makefile.am @@ -68,6 +68,7 @@ libtalertesting_la_SOURCES = \ testing_api_cmd_oauth.c \ testing_api_cmd_offline_sign_fees.c \ testing_api_cmd_offline_sign_keys.c \ + testing_api_cmd_offline_sign_extensions.c \ testing_api_cmd_set_wire_fee.c \ testing_api_cmd_recoup.c \ testing_api_cmd_recoup_refresh.c \ diff --git a/src/testing/test_exchange_api.c b/src/testing/test_exchange_api.c index 71f58d790..5e72070e4 100644 --- a/src/testing/test_exchange_api.c +++ b/src/testing/test_exchange_api.c @@ -168,6 +168,42 @@ run (void *cls, TALER_TESTING_cmd_end () }; + /** + * Test withdrawal with age restriction. Success is expected, so it MUST be + * called _after_ TALER_TESTING_cmd_exec_offline_sign_extensions is called, + * i. e. age restriction is activated in the exchange! + * + * TODO: create a test that tries to withdraw coins with age restriction but + * (expectedly) fails because the exchange doesn't support age restriction + * yet. + */ + struct TALER_TESTING_Command withdraw_age[] = { + /** + * Move money to the exchange's bank account. + */ + CMD_TRANSFER_TO_EXCHANGE ("create-reserve-age", + "EUR:6.02"), + TALER_TESTING_cmd_check_bank_admin_transfer ("check-create-reserve-age", + "EUR:6.02", + bc.user42_payto, + bc.exchange_payto, + "create-reserve-age"), + /** + * Make a reserve exist, according to the previous + * transfer. + */ + CMD_EXEC_WIREWATCH ("wirewatch-age"), + /** + * Withdraw EUR:5. + */ + TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-age", + "create-reserve-age", + "EUR:5", + 13, + MHD_HTTP_OK), + TALER_TESTING_cmd_end () + }; + struct TALER_TESTING_Command spend[] = { /** * Spend the coin. @@ -951,6 +987,8 @@ run (void *cls, "payto://x-taler-bank/localhost/2", MHD_HTTP_NO_CONTENT, false), + TALER_TESTING_cmd_exec_offline_sign_extensions ("offline-sign-extensions", + CONFIG_FILE), TALER_TESTING_cmd_exec_offline_sign_keys ("offline-sign-future-keys", CONFIG_FILE), TALER_TESTING_cmd_exec_offline_sign_fees ("offline-sign-fees", @@ -963,6 +1001,8 @@ run (void *cls, wire), TALER_TESTING_cmd_batch ("withdraw", withdraw), + TALER_TESTING_cmd_batch ("withdraw-age", + withdraw_age), TALER_TESTING_cmd_batch ("spend", spend), TALER_TESTING_cmd_batch ("refresh", diff --git a/src/testing/test_exchange_api.conf b/src/testing/test_exchange_api.conf index 48d5c2004..6bb7bd59f 100644 --- a/src/testing/test_exchange_api.conf +++ b/src/testing/test_exchange_api.conf @@ -150,7 +150,7 @@ fee_deposit = EUR:0.00 fee_refresh = EUR:0.01 fee_refund = EUR:0.01 rsa_keysize = 1024 -age_restricted = true +age_restricted = YES [coin_eur_ct_10_age_restricted] value = EUR:0.10 @@ -162,7 +162,7 @@ fee_deposit = EUR:0.01 fee_refresh = EUR:0.03 fee_refund = EUR:0.01 rsa_keysize = 1024 -age_restricted = true +age_restricted = YES [coin_eur_1_age_restricted] value = EUR:1 @@ -174,7 +174,7 @@ fee_deposit = EUR:0.01 fee_refresh = EUR:0.03 fee_refund = EUR:0.01 rsa_keysize = 1024 -age_restricted = true +age_restricted = YES [coin_eur_5_age_restricted] value = EUR:5 @@ -186,7 +186,7 @@ fee_deposit = EUR:0.01 fee_refresh = EUR:0.03 fee_refund = EUR:0.01 rsa_keysize = 1024 -age_restricted = true +age_restricted = YES [coin_eur_10_age_restricted] value = EUR:10 @@ -198,4 +198,4 @@ fee_deposit = EUR:0.01 fee_refresh = EUR:0.03 fee_refund = EUR:0.01 rsa_keysize = 1024 -age_restricted = true +age_restricted = YES diff --git a/src/testing/testing_api_cmd_offline_sign_extensions.c b/src/testing/testing_api_cmd_offline_sign_extensions.c new file mode 100644 index 000000000..973c495f9 --- /dev/null +++ b/src/testing/testing_api_cmd_offline_sign_extensions.c @@ -0,0 +1,164 @@ +/* + This file is part of TALER + Copyright (C) 2022 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published + by the Free Software Foundation; either version 3, or (at your + option) any later version. + + TALER is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public + License along with TALER; see the file COPYING. If not, + see +*/ + +/** + * @file testing/testing_api_cmd_offline_sign_extensions.c + * @brief run the taler-exchange-offline command to sign extensions (and therefore activate them) + * @author Özgür Kesim + */ +#include "platform.h" +#include "taler_json_lib.h" +#include +#include "taler_signatures.h" +#include "taler_testing_lib.h" + + +/** + * State for a "extensionssign" CMD. + */ +struct ExtensionsSignState +{ + + /** + * Process for the "extensionssign" command. + */ + struct GNUNET_OS_Process *extensionssign_proc; + + /** + * Configuration file used by the command. + */ + const char *config_filename; + +}; + + +/** + * Run the command; calls the `taler-exchange-offline' program. + * + * @param cls closure. + * @param cmd the commaind being run. + * @param is interpreter state. + */ +static void +extensionssign_run (void *cls, + const struct TALER_TESTING_Command *cmd, + struct TALER_TESTING_Interpreter *is) +{ + struct ExtensionsSignState *ks = cls; + + ks->extensionssign_proc + = GNUNET_OS_start_process ( + GNUNET_OS_INHERIT_STD_ALL, + NULL, NULL, NULL, + "taler-exchange-offline", + "taler-exchange-offline", + "-c", ks->config_filename, + "-L", "INFO", + "extensions", + "sign", + "upload", + NULL); + if (NULL == ks->extensionssign_proc) + { + GNUNET_break (0); + TALER_TESTING_interpreter_fail (is); + return; + } + TALER_TESTING_wait_for_sigchld (is); +} + + +/** + * Free the state of a "extensionssign" CMD, and possibly kills its + * process if it did not terminate correctly. + * + * @param cls closure. + * @param cmd the command being freed. + */ +static void +extensionssign_cleanup (void *cls, + const struct TALER_TESTING_Command *cmd) +{ + struct ExtensionsSignState *ks = cls; + + (void) cmd; + if (NULL != ks->extensionssign_proc) + { + GNUNET_break (0 == + GNUNET_OS_process_kill (ks->extensionssign_proc, + SIGKILL)); + GNUNET_OS_process_wait (ks->extensionssign_proc); + GNUNET_OS_process_destroy (ks->extensionssign_proc); + ks->extensionssign_proc = NULL; + } + GNUNET_free (ks); +} + + +/** + * Offer "extensionssign" CMD internal data to other commands. + * + * @param cls closure. + * @param[out] ret result + * @param trait name of the trait. + * @param index index number of the object to offer. + * @return #GNUNET_OK on success. + */ +static enum GNUNET_GenericReturnValue +extensionssign_traits (void *cls, + const void **ret, + const char *trait, + unsigned int index) +{ + struct ExtensionsSignState *ks = cls; + struct TALER_TESTING_Trait traits[] = { + TALER_TESTING_make_trait_process (&ks->extensionssign_proc), + TALER_TESTING_trait_end () + }; + + return TALER_TESTING_get_trait (traits, + ret, + trait, + index); +} + + +struct TALER_TESTING_Command +TALER_TESTING_cmd_exec_offline_sign_extensions (const char *label, + const char *config_filename) +{ + struct ExtensionsSignState *ks; + + ks = GNUNET_new (struct ExtensionsSignState); + ks->config_filename = config_filename; + { + struct TALER_TESTING_Command cmd = { + .cls = ks, + .label = label, + .run = &extensionssign_run, + .cleanup = &extensionssign_cleanup, + .traits = &extensionssign_traits + }; + + return cmd; + } +} + + +/* end of testing_api_cmd_exec_offline_sign_extensions.c */