diff options
Diffstat (limited to 'src/extensions')
-rw-r--r-- | src/extensions/Makefile.am | 5 | ||||
-rw-r--r-- | src/extensions/age_restriction/Makefile.am | 33 | ||||
-rw-r--r-- | src/extensions/age_restriction/extension_age_restriction.c (renamed from src/extensions/extension_age_restriction.c) | 345 | ||||
-rw-r--r-- | src/extensions/age_restriction_helper.c | 74 | ||||
-rw-r--r-- | src/extensions/extensions.c | 125 | ||||
-rw-r--r-- | src/extensions/policy_auction/Makefile.am | 34 | ||||
-rw-r--r-- | src/extensions/policy_auction/policy_auction.c | 731 |
7 files changed, 1066 insertions, 281 deletions
diff --git a/src/extensions/Makefile.am b/src/extensions/Makefile.am index 5d4ed128..c867a951 100644 --- a/src/extensions/Makefile.am +++ b/src/extensions/Makefile.am @@ -11,7 +11,7 @@ if USE_COVERAGE endif -# Libraries +# Basic extension handling library lib_LTLIBRARIES = \ libtalerextensions.la @@ -22,7 +22,7 @@ libtalerextensions_la_LDFLAGS = \ libtalerextensions_la_SOURCES = \ extensions.c \ - extension_age_restriction.c + age_restriction_helper.c libtalerextensions_la_LIBADD = \ $(top_builddir)/src/json/libtalerjson.la \ @@ -31,3 +31,4 @@ libtalerextensions_la_LIBADD = \ -lgnunetutil \ -ljansson \ $(XLIB) + diff --git a/src/extensions/age_restriction/Makefile.am b/src/extensions/age_restriction/Makefile.am new file mode 100644 index 00000000..e90c1962 --- /dev/null +++ b/src/extensions/age_restriction/Makefile.am @@ -0,0 +1,33 @@ +# This Makefile.am is in the public domain + +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/include \ + $(LIBGCRYPT_CFLAGS) \ + $(POSTGRESQL_CPPFLAGS) + +if USE_COVERAGE + AM_CFLAGS = --coverage -O0 + XLIB = -lgcov +endif + +# Age restriction as extension library + +plugindir = $(libdir)/taler + +plugin_LTLIBRARIES = \ + libtaler_extension_age_restriction.la + +libtaler_extension_age_restriction_la_LDFLAGS = \ + -version-info 0:0:0 \ + -no-undefined + +libtaler_extension_age_restriction_la_SOURCES = \ + extension_age_restriction.c + +libtaler_extension_age_restriction_la_LIBADD = \ + $(top_builddir)/src/json/libtalerjson.la \ + $(top_builddir)/src/util/libtalerutil.la \ + -lgnunetjson \ + -lgnunetutil \ + -ljansson \ + $(XLIB) diff --git a/src/extensions/extension_age_restriction.c b/src/extensions/age_restriction/extension_age_restriction.c index 00a03841..697d066f 100644 --- a/src/extensions/extension_age_restriction.c +++ b/src/extensions/age_restriction/extension_age_restriction.c @@ -23,102 +23,6 @@ #include "taler_extensions.h" #include "stdint.h" -/** - * Carries all the information we need for age restriction - */ -struct age_restriction_config -{ - struct TALER_AgeMask mask; - size_t num_groups; -}; - -/** - * Global config for this extension - */ -static struct age_restriction_config TE_age_restriction_config = {0}; - -enum GNUNET_GenericReturnValue -TALER_parse_age_group_string ( - const char *groups, - struct TALER_AgeMask *mask) -{ - - const char *pos = groups; - unsigned int prev = 0; - unsigned int val = 0; - char c; - - while (*pos) - { - c = *pos++; - if (':' == c) - { - if (prev >= val) - return GNUNET_SYSERR; - - mask->bits |= 1 << val; - prev = val; - val = 0; - continue; - } - - if ('0'>c || '9'<c) - return GNUNET_SYSERR; - - val = 10 * val + c - '0'; - - if (0>=val || 32<=val) - return GNUNET_SYSERR; - } - - if (32<=val || prev>=val) - return GNUNET_SYSERR; - - mask->bits |= (1 << val); - mask->bits |= 1; // mark zeroth group, too - - return GNUNET_OK; -} - - -char * -TALER_age_mask_to_string ( - const struct TALER_AgeMask *mask) -{ - uint32_t bits = mask->bits; - unsigned int n = 0; - char *buf = GNUNET_malloc (32 * 3); // max characters possible - char *pos = buf; - - if (NULL == buf) - { - return buf; - } - - while (bits != 0) - { - bits >>= 1; - n++; - if (0 == (bits & 1)) - { - continue; - } - - if (n > 9) - { - *(pos++) = '0' + n / 10; - } - *(pos++) = '0' + n % 10; - - if (0 != (bits >> 1)) - { - *(pos++) = ':'; - } - } - return buf; -} - - /* ================================================== * * Age Restriction TALER_Extension implementation @@ -127,6 +31,12 @@ TALER_age_mask_to_string ( */ /** + * @brief local configuration + */ + +static struct TALER_AgeRestrictionConfig AR_config = {0}; + +/** * @brief implements the TALER_Extension.disable interface. * * @param ext Pointer to the current extension @@ -138,6 +48,7 @@ age_restriction_disable ( if (NULL == ext) return; + ext->enabled = false; ext->config = NULL; if (NULL != ext->config_json) @@ -146,86 +57,9 @@ age_restriction_disable ( ext->config_json = NULL; } - TE_age_restriction_config.mask.bits = 0; - TE_age_restriction_config.num_groups = 0; -} - - -/** - * @brief implements the TALER_Extension.load_taler_config interface. - * - * @param ext Pointer to the current extension - * @param cfg Handle to the GNUNET configuration - * @return Error if extension for age restriction was set, but age groups were - * invalid, OK otherwise. - */ -static enum GNUNET_GenericReturnValue -age_restriction_load_taler_config ( - struct TALER_Extension *ext, - const struct GNUNET_CONFIGURATION_Handle *cfg) -{ - char *groups = NULL; - enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR; - struct TALER_AgeMask mask = {0}; - - if ((GNUNET_YES != - GNUNET_CONFIGURATION_have_value (cfg, - TALER_EXTENSION_SECTION_AGE_RESTRICTION, - "ENABLED")) - || - (GNUNET_YES != - GNUNET_CONFIGURATION_get_value_yesno (cfg, - TALER_EXTENSION_SECTION_AGE_RESTRICTION, - "ENABLED"))) - { - /* Age restriction is not enabled */ - ext->config = NULL; - ext->config_json = NULL; - return GNUNET_OK; - } - - /* Age restriction is enabled, extract age groups */ - if ((GNUNET_YES == - GNUNET_CONFIGURATION_have_value (cfg, - TALER_EXTENSION_SECTION_AGE_RESTRICTION, - "AGE_GROUPS")) - && - (GNUNET_YES != - GNUNET_CONFIGURATION_get_value_string (cfg, - TALER_EXTENSION_SECTION_AGE_RESTRICTION, - "AGE_GROUPS", - &groups))) - return GNUNET_SYSERR; - - - mask.bits = TALER_EXTENSION_AGE_RESTRICTION_DEFAULT_AGE_MASK; - ret = GNUNET_OK; - - if (groups != NULL) - { - ret = TALER_parse_age_group_string (groups, &mask); - if (GNUNET_OK != ret) - mask.bits = TALER_EXTENSION_AGE_RESTRICTION_DEFAULT_AGE_MASK; - } - - if (GNUNET_OK == ret) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "setting age mask to %x with #groups: %d\n", mask.bits, - __builtin_popcount (mask.bits) - 1); - TE_age_restriction_config.mask.bits = mask.bits; - TE_age_restriction_config.num_groups = __builtin_popcount (mask.bits) - 1; /* no underflow, first bit always set */ - ext->config = &TE_age_restriction_config; - - /* Note: we do now have TE_age_restriction_config set, however - * ext->config_json is NOT set, i.e. the extension is not yet active! For - * age restriction to become active, load_json_config must have been - * called. */ - } - - - GNUNET_free (groups); - return ret; + AR_config.enabled = false; + AR_config.mask.bits = 0; + AR_config.num_groups = 0; } @@ -254,24 +88,25 @@ age_restriction_load_json_config ( if (TALER_Extension_AgeRestriction != ext->type) return GNUNET_SYSERR; - TE_age_restriction_config.mask.bits = mask.bits; - TE_age_restriction_config.num_groups = 0; - if (mask.bits > 0) { /* if the mask is not zero, the first bit MUST be set */ if (0 == (mask.bits & 1)) return GNUNET_SYSERR; - TE_age_restriction_config.num_groups = __builtin_popcount (mask.bits) - 1; + AR_config.mask.bits = mask.bits; + AR_config.num_groups = __builtin_popcount (mask.bits) - 1; } - ext->config = &TE_age_restriction_config; + AR_config.enabled = true; + ext->config = &AR_config; if (NULL != ext->config_json) json_decref (ext->config_json); - ext->config_json = jconfig; + ext->enabled = true; + ext->config_json = json_copy (jconfig); + json_decref (jconfig); GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "loaded new age restriction config with age groups: %s\n", @@ -296,6 +131,13 @@ age_restriction_config_to_json ( GNUNET_assert (NULL != ext); + if (! ext->enabled) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "age restriction not enabled"); + return json_null (); + } + if (NULL == ext->config) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, @@ -308,11 +150,13 @@ age_restriction_config_to_json ( return json_copy (ext->config_json); } - mask_str = TALER_age_mask_to_string (&TE_age_restriction_config.mask); + mask_str = TALER_age_mask_to_string (&AR_config.mask); conf = GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("age_groups", mask_str) ); + free (mask_str); + return GNUNET_JSON_PACK ( GNUNET_JSON_pack_bool ("critical", ext->critical), GNUNET_JSON_pack_string ("version", ext->version), @@ -338,71 +182,120 @@ age_restriction_test_json_config ( /* The extension for age restriction */ -struct TALER_Extension TE_age_restriction = { - .next = NULL, +struct TALER_Extension TE_extension_age_restriction = { .type = TALER_Extension_AgeRestriction, .name = "age_restriction", .critical = false, .version = "1", - .config = NULL, // disabled per default + .enabled = false, /* disabled per default */ + .has_config = true, /* we need to store configuration */ + .config = NULL, .config_json = NULL, .disable = &age_restriction_disable, .test_json_config = &age_restriction_test_json_config, .load_json_config = &age_restriction_load_json_config, .config_to_json = &age_restriction_config_to_json, - .load_taler_config = &age_restriction_load_taler_config, + .http_post_handler = NULL, }; -enum GNUNET_GenericReturnValue -TALER_extension_age_restriction_register () + +/** + * @brief implements the init() function for GNUNET_PLUGIN_load + * + * @param arg Pointer to the GNUNET_CONFIGURATION_Handle + * @return pointer to TALER_Extension on success or NULL otherwise. + */ +void * +libtaler_extension_age_restriction_init (void *arg) { - return TALER_extensions_add (&TE_age_restriction); -} + const struct GNUNET_CONFIGURATION_Handle *cfg = arg; + char *groups = NULL; + struct TALER_AgeMask mask = {0}; + if ((GNUNET_YES != + GNUNET_CONFIGURATION_have_value (cfg, + TALER_EXTENSION_SECTION_AGE_RESTRICTION, + "ENABLED")) + || + (GNUNET_YES != + GNUNET_CONFIGURATION_get_value_yesno (cfg, + TALER_EXTENSION_SECTION_AGE_RESTRICTION, + "ENABLED"))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "[age restriction] no section %s found in configuration\n", + TALER_EXTENSION_SECTION_AGE_RESTRICTION); -bool -TALER_extensions_age_restriction_is_configured () -{ - return (0 != TE_age_restriction_config.mask.bits); -} + return NULL; + } + + /* Age restriction is enabled, extract age groups */ + if ((GNUNET_YES == + GNUNET_CONFIGURATION_have_value (cfg, + TALER_EXTENSION_SECTION_AGE_RESTRICTION, + "AGE_GROUPS")) + && + (GNUNET_YES != + GNUNET_CONFIGURATION_get_value_string (cfg, + TALER_EXTENSION_SECTION_AGE_RESTRICTION, + "AGE_GROUPS", + &groups))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "[age restriction] AGE_GROUPS in %s is not a string\n", + TALER_EXTENSION_SECTION_AGE_RESTRICTION); + return NULL; + } -struct TALER_AgeMask -TALER_extensions_age_restriction_ageMask () -{ - return TE_age_restriction_config.mask; -} + mask.bits = TALER_EXTENSION_AGE_RESTRICTION_DEFAULT_AGE_MASK; + if ((groups != NULL) && + (GNUNET_OK != TALER_parse_age_group_string (groups, &mask))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "[age restriction] couldn't parse age groups: '%s'\n", + groups); + return NULL; + } -size_t -TALER_extensions_age_restriction_num_groups () -{ - return TE_age_restriction_config.num_groups; + AR_config.mask = mask; + AR_config.num_groups = __builtin_popcount (mask.bits) - 1; /* no underflow, first bit always set */ + AR_config.enabled = true; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "[age restriction] setting age mask to %s with #groups: %d\n", + TALER_age_mask_to_string (&AR_config.mask), + __builtin_popcount (AR_config.mask.bits) - 1); + + TE_extension_age_restriction.config = &AR_config; + TE_extension_age_restriction.enabled = true; + + /* Note: we do now have TE_age_restriction_config set, however + * ext->config_json is NOT set, i.e. the extension is not yet active! For + * age restriction to become active, load_json_config must have been + * called. */ + + GNUNET_free (groups); + return &TE_extension_age_restriction; } -enum GNUNET_GenericReturnValue -TALER_JSON_parse_age_groups (const json_t *root, - struct TALER_AgeMask *mask) +/** + * @brief implements the done() function for GNUNET_PLUGIN_load + * + * @param cfg unsued + * @return pointer to TALER_Extension on success or NULL otherwise. + */ +void * +libtaler_extension_age_restriction_done (void *arg) { - 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; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "[age restriction] disabling and unloading"); + AR_config.enabled = 0; + AR_config.mask.bits = 0; + AR_config.num_groups = 0; + return NULL; } diff --git a/src/extensions/age_restriction_helper.c b/src/extensions/age_restriction_helper.c new file mode 100644 index 00000000..2cd77515 --- /dev/null +++ b/src/extensions/age_restriction_helper.c @@ -0,0 +1,74 @@ +/* + 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 <http://www.gnu.org/licenses/> + */ +/** + * @file age_restriction_helper.c + * @brief Helper functions for age restriction + * @author Özgür Kesim + */ + +#include "platform.h" +#include "taler_util.h" +#include "taler_signatures.h" +#include "taler_extensions.h" +#include "stdint.h" + + +const struct TALER_AgeRestrictionConfig * +TALER_extensions_get_age_restriction_config () +{ + const struct TALER_Extension *ext; + + ext = TALER_extensions_get_by_type (TALER_Extension_AgeRestriction); + if (NULL == ext) + return NULL; + + return ext->config; +} + + +bool +TALER_extensions_is_age_restriction_enabled () +{ + const struct TALER_Extension *ext; + + ext = TALER_extensions_get_by_type (TALER_Extension_AgeRestriction); + if (NULL == ext) + return false; + + return ext->enabled; +} + + +struct TALER_AgeMask +TALER_extensions_get_age_restriction_mask () +{ + const struct TALER_Extension *ext; + const struct TALER_AgeRestrictionConfig *conf; + + ext = TALER_extensions_get_by_type (TALER_Extension_AgeRestriction); + + if ((NULL == ext) || + (NULL == ext->config) || + (! ext->enabled)) + return (struct TALER_AgeMask) {0} + ; + + conf = ext->config; + return conf->mask; +} + + +/* end age_restriction_helper.c */ diff --git a/src/extensions/extensions.c b/src/extensions/extensions.c index 0df0bae3..95fb8cf0 100644 --- a/src/extensions/extensions.c +++ b/src/extensions/extensions.c @@ -24,21 +24,22 @@ #include "taler_extensions.h" #include "stdint.h" - /* head of the list of all registered extensions */ -static struct TALER_Extension *TE_extensions = NULL; - +static struct TALER_Extensions TE_extensions = { + .next = NULL, + .extension = NULL, +}; -const struct TALER_Extension * +const struct TALER_Extensions * TALER_extensions_get_head () { - return TE_extensions; + return &TE_extensions; } -enum GNUNET_GenericReturnValue -TALER_extensions_add ( - struct TALER_Extension *extension) +static enum GNUNET_GenericReturnValue +add_extension ( + const struct TALER_Extension *extension) { /* Sanity checks */ if ((NULL == extension) || @@ -47,28 +48,30 @@ TALER_extensions_add ( (NULL == extension->disable) || (NULL == extension->test_json_config) || (NULL == extension->load_json_config) || - (NULL == extension->config_to_json) || - (NULL == extension->load_taler_config)) + (NULL == extension->config_to_json)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "invalid extension\n"); return GNUNET_SYSERR; } - if (NULL == TE_extensions) /* first extension ?*/ - TE_extensions = (struct TALER_Extension *) extension; + if (NULL == TE_extensions.extension) /* first extension ?*/ + TE_extensions.extension = extension; else { - struct TALER_Extension *iter; - struct TALER_Extension *last; + struct TALER_Extensions *iter; + struct TALER_Extensions *last; /* Check for collisions */ - for (iter = TE_extensions; NULL != iter; iter = iter->next) + for (iter = &TE_extensions; + NULL != iter && NULL != iter->extension; + iter = iter->next) { + const struct TALER_Extension *ext = iter->extension; last = iter; - if (extension->type == iter->type || + if (extension->type == ext->type || 0 == strcasecmp (extension->name, - iter->name)) + ext->name)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "extension collision for `%s'\n", @@ -78,7 +81,11 @@ TALER_extensions_add ( } /* No collisions found, so add this extension to the list */ - last->next = extension; + { + struct TALER_Extensions *extn = GNUNET_new (struct TALER_Extensions); + extn->extension = extension; + last->next = extn; + } } return GNUNET_OK; @@ -89,12 +96,12 @@ const struct TALER_Extension * TALER_extensions_get_by_type ( enum TALER_Extension_Type type) { - for (const struct TALER_Extension *it = TE_extensions; - NULL != it; + for (const struct TALER_Extensions *it = &TE_extensions; + NULL != it && NULL != it->extension; it = it->next) { - if (it->type == type) - return it; + if (it->extension->type == type) + return it->extension; } /* No extension found. */ @@ -109,8 +116,7 @@ TALER_extensions_is_enabled_type ( const struct TALER_Extension *ext = TALER_extensions_get_by_type (type); - return (NULL != ext && - TALER_extensions_is_enabled (ext)); + return (NULL != ext && ext->enabled); } @@ -118,14 +124,15 @@ const struct TALER_Extension * TALER_extensions_get_by_name ( const char *name) { - for (const struct TALER_Extension *it = TE_extensions; + for (const struct TALER_Extensions *it = &TE_extensions; NULL != it; it = it->next) { - if (0 == strcasecmp (name, it->name)) - return it; + if (0 == strcasecmp (name, it->extension->name)) + return it->extension; } - /* No extension found. */ + /* No extension found, try to load it. */ + return NULL; } @@ -178,7 +185,8 @@ configure_extension ( { struct LoadConfClosure *col = cls; const char *name; - const struct TALER_Extension *extension; + char *lib_name; + struct TALER_Extension *extension; if (GNUNET_OK != col->error) return; @@ -190,33 +198,49 @@ configure_extension ( name = section + sizeof(TALER_EXTENSION_SECTION_PREFIX) - 1; - if (NULL == - (extension = TALER_extensions_get_by_name (name))) + + /* Load the extension library */ + GNUNET_asprintf (&lib_name, + "libtaler_extension_%s", + name); + extension = GNUNET_PLUGIN_load ( + lib_name, + (void *) col->cfg); + if (NULL == extension) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unsupported extension `%s` (section [%s]).\n", name, + "Couldn't load extension library to `%s` (section [%s]).\n", + name, section); col->error = GNUNET_SYSERR; return; } - if (GNUNET_OK != - extension->load_taler_config ( - (struct TALER_Extension *) extension, - col->cfg)) + + if (GNUNET_OK != add_extension (extension)) { + /* TODO: Ignoring return values here */ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Couldn't parse configuration for extension `%s` (section [%s]).\n", + "Couldn't add extension `%s` (section [%s]).\n", name, section); col->error = GNUNET_SYSERR; + GNUNET_PLUGIN_unload ( + lib_name, + (void *) col->cfg); return; } + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "extension library '%s' loaded\n", + lib_name); } +static bool extensions_loaded = false; + enum GNUNET_GenericReturnValue -TALER_extensions_load_taler_config ( +TALER_extensions_load ( const struct GNUNET_CONFIGURATION_Handle *cfg) { struct LoadConfClosure col = { @@ -224,9 +248,16 @@ TALER_extensions_load_taler_config ( .error = GNUNET_OK, }; + if (extensions_loaded) + return GNUNET_OK; + GNUNET_CONFIGURATION_iterate_sections (cfg, &configure_extension, &col); + + if (GNUNET_OK == col.error) + extensions_loaded = true; + return col.error; } @@ -309,28 +340,16 @@ TALER_extensions_load_json_config ( } /* make sure to disable all extensions that weren't mentioned in the json */ - for (const struct TALER_Extension *it = TALER_extensions_get_head (); + for (const struct TALER_Extensions *it = TALER_extensions_get_head (); NULL != it; it = it->next) { - if (NULL == json_object_get (extensions, it->name)) - it->disable ((struct TALER_Extension *) it); + if (NULL == json_object_get (extensions, it->extension->name)) + it->extension->disable ((struct TALER_Extension *) it); } return GNUNET_OK; } -bool -TALER_extensions_age_restriction_is_enabled () -{ - const struct TALER_Extension *age = - TALER_extensions_get_by_type (TALER_Extension_AgeRestriction); - - return (NULL != age && - NULL != age->config_json && - TALER_extensions_age_restriction_is_configured ()); -} - - /* end of extensions.c */ diff --git a/src/extensions/policy_auction/Makefile.am b/src/extensions/policy_auction/Makefile.am new file mode 100644 index 00000000..cf44b95e --- /dev/null +++ b/src/extensions/policy_auction/Makefile.am @@ -0,0 +1,34 @@ +# This Makefile.am is in the public domain + +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/include \ + $(LIBGCRYPT_CFLAGS) \ + $(POSTGRESQL_CPPFLAGS) + +if USE_COVERAGE + AM_CFLAGS = --coverage -O0 + XLIB = -lgcov +endif + + +# Auction of Brandt type as an extension library + +plugindir = $(libdir)/taler + +plugin_LTLIBRARIES = \ + libtaler_extension_policy_auction.la + +libtaler_extension_policy_auction_la_LDFLAGS = \ + -version-info 0:0:0 \ + -no-undefined + +libtaler_extension_policy_auction_la_SOURCES = \ + policy_auction.c + +libtaler_extension_policy_auction_la_LIBADD = \ + $(top_builddir)/src/json/libtalerjson.la \ + $(top_builddir)/src/util/libtalerutil.la \ + -lgnunetjson \ + -lgnunetutil \ + -ljansson \ + $(XLIB) diff --git a/src/extensions/policy_auction/policy_auction.c b/src/extensions/policy_auction/policy_auction.c new file mode 100644 index 00000000..d1c3237c --- /dev/null +++ b/src/extensions/policy_auction/policy_auction.c @@ -0,0 +1,731 @@ +/* + This file is part of TALER + Copyright (C) 2021-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 <http://www.gnu.org/licenses/> + */ +/** + * @file policy_auction.c + * @brief Extension for replay of auctions of type Brandt + * @author Özgür Kesim + */ +#include "platform.h" +#include "taler_util.h" +#include "taler_extensions.h" +#include "../../exchange/taler-exchange-httpd.h" +#include "taler_mhd_lib.h" +#include "stdint.h" +#include <microhttpd.h> + +#define POLICY_AUCTION "policy_auction" +#define LOG_PREFIX "[policy_auction] " +#define MAX_RESULT_SIZE 10 * 1024 + +/* Path to the replay program. */ +static char *replay_program; + +/* supported currency */ +static char *currency; + +/* This is basically BRANDT_Result with an extra string field */ +struct result +{ + uint16_t bidder; + uint16_t price_idx; + const char *price; +}; + +/* + * @brief Transcript information + * + */ +struct transcript +{ + /* + * The first couple of fields are from a JSON transcript + */ + + /* Public key of seller */ + struct GNUNET_CRYPTO_EddsaPublicKey seller_pub; + + /* Payto URL */ + const char *payto; + + /* Number of bidders + 1 (for seller) */ + uint16_t n; + + /* (n-1) public keys of bidders */ + struct GNUNET_CRYPTO_EddsaPublicKey *bidder_pub; + + /* Type of auction, see libbrandt */ + uint16_t m; + + /* Auction public outcome? */ + bool public; + + /* Start date of the auction */ + struct GNUNET_TIME_Timestamp time_start; + + /* End date of the auction */ + struct GNUNET_TIME_Relative time_round; + + /* Number of prices */ + uint16_t k; + + /* Prices, must be length k */ + struct TALER_Amount *prices; + + /* Expected winner(s), maybe NULL */ + struct result *expected; + size_t expected_len; + + /* + * These are the results from the replay via the external program. + */ + struct result *results; + size_t results_len; +}; + +/** + * @brief returns an JSON with the error + */ +static enum GNUNET_GenericReturnValue +json_error (json_t **output, + char *error, ...) +{ + va_list ap; + int n = 0; + char buf[4096]; + GNUNET_assert (error); + + va_start (ap, error); + n = vsprintf (buf, error, ap); + va_end (ap); + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + LOG_PREFIX "got error: %s\n", + n < 0 ? error: buf); + + *output = json_pack ("{s:s}", + "error", + n < 0 ? error : buf); + GNUNET_assert (*output); + + return GNUNET_SYSERR; +}; + +/** + * @brief returns an JSON with the result + */ +#if 0 +static enum GNUNET_GenericReturnValue +json_result (json_t **output, + const struct transcript *tr) +{ + json_t *results; + + GNUNET_assert (NULL != tr); + + *output = json_object (); + results = json_array (); + GNUNET_assert (*output); + GNUNET_assert (results); + + for (size_t i = 0; i < tr->results_len; i++) + { + json_t *result = json_pack ("{s:i, s:s}", + "bidder", tr->results[i].bidder, + "price", tr->results[i].price); + GNUNET_assert (result); + + GNUNET_assert (-1 != + json_array_append_new (results, result)); + } + + GNUNET_assert (-1 != + json_object_set_new (*output, + "winners", + results)); + + return GNUNET_OK; +} + + +#endif + + +/* + * @brief Parses a given json as transcript. + * + * @param[in] jtr JSON input + * @param[out] tr Parsed transcript data + * @param[out] jerror JSON output for errors + * @return GNUNET_OK on succes + * + * TODO: + * - parse and verify signatures + */ +static enum GNUNET_GenericReturnValue +parse_transcript (const json_t *jtr, + struct transcript *tr, + json_t **jerror) +{ + json_t *auc; + + // TODO: struct GNUNET_CRYPTO_EddsaSignature sig; + + GNUNET_assert (jtr); + GNUNET_assert (tr); + + // Parse auction + { + char *perr; + unsigned int eline; + struct GNUNET_JSON_Specification au_spec[] = { + GNUNET_JSON_spec_bool ("public", &tr->public), + GNUNET_JSON_spec_uint16 ("type", &tr->m), + GNUNET_JSON_spec_fixed_auto ("pubkey", &tr->seller_pub), + GNUNET_JSON_spec_timestamp ("time_start", &tr->time_start), + GNUNET_JSON_spec_relative_time ("time_round", &tr->time_round), + GNUNET_JSON_spec_string ("payto", &tr->payto), + GNUNET_JSON_spec_end () + }; + + auc = json_object_get (jtr, "auction"); + if (NULL == auc) + return json_error (jerror, + "no auction found in transcript"); + + if (GNUNET_OK != + GNUNET_JSON_parse (auc, + au_spec, + (const char **) &perr, + &eline)) + return json_error (jerror, + perr); + + // Prices... + { + size_t idx; + json_t *val; + json_t *prices; + + prices = json_object_get (auc, "prices"); + if (! json_is_array (prices)) + return json_error (jerror, + "no prices found"); + + tr->k = json_array_size (prices); + + tr->prices = GNUNET_new_array (tr->k, struct TALER_Amount); + json_array_foreach (prices, idx, val) + { + struct GNUNET_JSON_Specification spec[] = { + TALER_JSON_spec_amount (NULL, + currency, + &(tr->prices[idx])), + GNUNET_JSON_spec_end (), + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (val, + spec, + NULL, + NULL)) + return json_error (jerror, + "price no. %ld couldn't be parsed", + idx + 1); + } + } + } + + // Bidders + { + size_t idx; + json_t *val; + json_t *bidders; + + bidders = json_object_get (jtr, "bidders"); + if (! bidders || ! json_is_array (bidders)) + return json_error (jerror, + "no bidders found"); + + tr->n = json_array_size (bidders); + + tr->bidder_pub = GNUNET_new_array (tr->n, struct + GNUNET_CRYPTO_EddsaPublicKey); + json_array_foreach (bidders, idx, val) + { + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto (NULL, + &(tr->bidder_pub[idx])), + GNUNET_JSON_spec_end (), + }; + if (GNUNET_OK != + GNUNET_JSON_parse (val, + spec, + NULL, + NULL)) + return json_error (jerror, + "bidder no %ld public key couldn't be parsed", + idx + 1); + } + } + + // TODO: parse and verify signatures from bidders of the auction + + + // Messages + { + size_t nm; + json_t *messages = json_object_get (jtr, "transcript"); + + if (! json_is_array (messages)) + return json_error (jerror, + "no messages found"); + + + nm = json_array_size (messages); + + if (nm != (4 * tr->n)) + return json_error (jerror, + "not the right no. of messages found"); + + /* TODO: parse and evaluate signatures */ + } + + // Winners + { + size_t idx; + json_t *val; + json_t *winners = json_object_get (jtr, "winners"); + + if (! json_is_array (winners)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + LOG_PREFIX "winners not provided, continuing without\n"); + goto DONE; + } + + tr->expected_len = json_array_size (winners); + tr->expected = GNUNET_new_array (tr->expected_len, + struct result); + + json_array_foreach (winners, idx, val) { + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_uint16 ("bidder", + &(tr->expected[idx].bidder)), + GNUNET_JSON_spec_uint16 ("price_idx", + &(tr->expected[idx].price_idx)), + GNUNET_JSON_spec_string ("price", + &(tr->expected[idx].price)), + GNUNET_JSON_spec_end () + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (val, + spec, + NULL, + NULL)) + return json_error (jerror, + "couldn't parse winner no. %ld", + idx + 1); + } + } + + // TODO: parse and evalue sig of seller + +// TODO: check for max values + +DONE: + + *jerror = NULL; + return GNUNET_OK; +} + + +/** + * @brief replay an auction using the external program + * + * @param[in] root The original JSON transcript + * @param[in] transcript The transcript object parsed so far + * @param[out] result The JSON result from the program + * @return GNUNET_OK on success + * + * TODO: Make this resumable + */ +static enum GNUNET_GenericReturnValue +replay_transcript (const json_t*root, + struct transcript *tr, + json_t **result) +{ + struct GNUNET_DISK_PipeHandle *pi; + struct GNUNET_DISK_PipeHandle *po; + const struct GNUNET_DISK_FileHandle *fd; + struct GNUNET_OS_Process *proc; + + pi = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_WRITE); + po = GNUNET_DISK_pipe (GNUNET_DISK_PF_BLOCKING_READ); + proc = GNUNET_OS_start_process (GNUNET_OS_INHERIT_STD_ERR, + pi, po, NULL, + replay_program, + replay_program, + NULL); + if (NULL == proc) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + LOG_PREFIX "couldn't create auction replay program '%s'\n", + replay_program); + + return json_error (result, "internal error"); + } + + // Write original transcript JSON to stdin + { + ssize_t sz; + char *str; + size_t str_len; + + + fd = GNUNET_DISK_pipe_handle (pi, GNUNET_DISK_PIPE_END_WRITE); + str = json_dumps (root, JSON_COMPACT); + str_len = strlen (str); + sz = GNUNET_DISK_file_write (fd, + str, + str_len); + free (str); + if (sz != str_len) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + LOG_PREFIX "couldn't write all data to replay_program\n"); + } + } + + // Read output from stdout + { + ssize_t sz; + char buf[MAX_RESULT_SIZE]; + json_error_t error; + json_t *res; + + fd = GNUNET_DISK_pipe_handle (po, GNUNET_DISK_PIPE_END_READ); + + sz = GNUNET_DISK_file_read (fd, + buf, + sizeof(buf)); + if (GNUNET_SYSERR == sz) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + LOG_PREFIX "couldn't read data from replay_program\n"); + return json_error (result, "internal error"); + } + + buf[sz] = 0; + res = json_loads (buf, + JSON_DECODE_ANY, + &error); + + if (! res) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + LOG_PREFIX + "couldn't parse response from replay_program: %s (for '%s')\n", + error.text, + buf); + return json_error (result, error.text); + } + + // Handle error case first + { + json_t *err = json_object_get (res, + "error"); + if (NULL != err) + { + *result = json_copy (res); + json_decref (res); + return GNUNET_SYSERR; + } + } + + // Parse the result + { + json_t *winners = json_object_get (res, + "winners"); + if ((NULL == winners) || + (! json_is_array (winners))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + LOG_PREFIX + "replay program didn't return a known result type, instead: '%s'\n", + json_dumps (res, JSON_INDENT (2))); + return json_error (result, "internal error"); + } + + { + // TODO: check each winner with tr->expected, if applicable + json_object_set (res, "exchange_sig", json_string ( + "sig(priv_E, winners)")); + + } + + // TODO: return own result object. + *result = json_copy (res); + json_decref (res); + } + + } + + if (GNUNET_OK != GNUNET_OS_process_wait (proc)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + LOG_PREFIX "error while launching auction replay program '%s'\n", + replay_program); + + json_object_clear (*result); + return json_error (result, "internal error"); + } + + + return GNUNET_OK; +} + + +/** + * @brief implements the TALER_Extension.disable interface. + * + * @param ext Pointer to the current extension + */ +static void +auction_disable ( + struct TALER_Extension *ext) +{ + /* TODO: cleanup configuration */ + ext->enabled = false; +} + + +/** + * @brief implements the TALER_Extension.test_json_config interface. + * + * @param config configuration as json_t* to test + * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise. + */ +static enum GNUNET_GenericReturnValue +auction_test_json_config ( + const json_t *config) +{ + /* This extension has no configuration */ + return GNUNET_OK; +} + + +/** + * @brief implements the TALER_Extension.config_to_json interface. + * + * @param ext if NULL, only tests the configuration + * @return configuration as json_t* object, maybe NULL + */ +static json_t * +auction_config_to_json ( + const struct TALER_Extension *ext) +{ + /* TODO: add configuration */ + return GNUNET_JSON_PACK ( + GNUNET_JSON_pack_bool ("critical", ext->critical), + GNUNET_JSON_pack_string ("version", ext->version)); +} + + +/** + * @brief implements the TALER_Extension.load_json_config interface. + * + * @param ext if NULL, only tests the configuration + * @param jconfig the configuration as json + */ +static enum GNUNET_GenericReturnValue +auction_load_json_config ( + struct TALER_Extension *ext, + json_t *jconfig) +{ + /* TODO: add configuration */ + ext->enabled = true; + return GNUNET_OK; +} + + +/** + * @brief implements the TALER_Extension.http_get_handler + */ +static MHD_RESULT +auction_http_get_handler ( + struct MHD_Connection *connection, + const char *const args[]) +{ + /* TODO: return some meta-data about supported version, limits, etc.*/ + + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + LOG_PREFIX "auction_http_get_handler not implemented yet\n"); + + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_NOT_IMPLEMENTED, + TALER_EC_EXCHANGE_GENERIC_OPERATION_UNKNOWN, + "auction_http_get_handler not implemented yet\n"); + +} + + +/** + * @brief implements the TALER_Extension.http_post_handler + * + * TODO: make this non-blocking + */ +static MHD_RESULT +auction_http_post_handler ( + struct MHD_Connection *connection, + const json_t *root, + const char *const args[]) +{ + struct transcript tr = {}; + enum GNUNET_GenericReturnValue ret; + json_t *result1; + json_t *result2; + + ret = parse_transcript (root, + &tr, + &result1); + if (GNUNET_OK != ret) + return TALER_MHD_reply_json_steal (connection, + result1, + MHD_HTTP_BAD_REQUEST); + GNUNET_assert (NULL == result1); + + ret = replay_transcript (root, + &tr, + &result2); + + return TALER_MHD_reply_json_steal (connection, + result2, + GNUNET_OK == ret? + MHD_HTTP_OK : + MHD_HTTP_BAD_REQUEST); +} + + +/* The extension struct for auctions of brandt-style */ +struct TALER_Extension TE_auction_brandt = { + .type = TALER_Extension_PolicyAuction, + .name = POLICY_AUCTION, + .critical = false, + .version = "0", + .enabled = false, /* disabled per default */ + .has_config = true, + .config = NULL, + .config_json = NULL, + .disable = &auction_disable, + .test_json_config = &auction_test_json_config, + .load_json_config = &auction_load_json_config, + .config_to_json = &auction_config_to_json, + .http_get_handler = &auction_http_get_handler, + .http_post_handler = &auction_http_post_handler, +}; + + +/** + * =========================================== + * Handler for GNUNET_PLUGIN_load and _unload + * =========================================== + */ + +/** + * @brief Initialization function for the extension. + * Will be called by GNUNET_PLUGIN_load. + * + * @param arg Configuration - ptr to GNUNET_CONFIGURATION_Handle + * @return Pointer to TE_auction_brandt + */ +struct TALER_Extension * +libtaler_extension_policy_auction_init (void *arg) +{ + const struct GNUNET_CONFIGURATION_Handle *cfg = arg; + + + if (GNUNET_OK != + TALER_config_get_currency (cfg, + ¤cy)) + return NULL; + + if (GNUNET_SYSERR == + GNUNET_CONFIGURATION_get_value_string (cfg, + TALER_EXTENSION_SECTION_PREFIX + POLICY_AUCTION, + "REPLAY_PROGRAM", + &replay_program)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + TALER_EXTENSION_SECTION_PREFIX POLICY_AUCTION, + "REPLAY_PROGRAM"); + return NULL; + } + + /* check if replay_program is actually an executable */ + { + struct stat sb; + + if (0 != stat (replay_program, &sb)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + LOG_PREFIX "replay_program '%s' not found\n", + replay_program); + return NULL; + } + + if ( (sb.st_mode & S_IFDIR) || + ! (sb.st_mode & S_IXUSR)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + LOG_PREFIX "replay_program '%s' is not an executable\n", + replay_program); + return NULL; + } + + } + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + LOG_PREFIX "loading... using replay_program '%s'\n", + replay_program); + + /* TODO: read other config parameters and generate configuration */ + + + return &TE_auction_brandt; +} + + +/** + * @brief Tear-down function for the extension. + * Will be called by GNUNET_PLUGIN_unload. + * + * @param ignored + * @return null + */ +void * +libtaler_extension_policy_auction_done (void *arg) +{ + auction_disable (&TE_auction_brandt); + GNUNET_free (replay_program); + replay_program = NULL; + + return NULL; +} + + +/* end of policy_auction.c */ |