diff options
Diffstat (limited to 'src/extensions')
-rw-r--r-- | src/extensions/Makefile.am | 34 | ||||
-rw-r--r-- | src/extensions/age_restriction/Makefile.am | 32 | ||||
-rw-r--r-- | src/extensions/age_restriction/age_restriction.c | 256 | ||||
-rw-r--r-- | src/extensions/age_restriction_helper.c | 73 | ||||
-rw-r--r-- | src/extensions/extensions.c | 452 |
5 files changed, 847 insertions, 0 deletions
diff --git a/src/extensions/Makefile.am b/src/extensions/Makefile.am new file mode 100644 index 000000000..c867a9512 --- /dev/null +++ b/src/extensions/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 + + +# Basic extension handling library + +lib_LTLIBRARIES = \ + libtalerextensions.la + +libtalerextensions_la_LDFLAGS = \ + -version-info 0:0:0 \ + -no-undefined + +libtalerextensions_la_SOURCES = \ + extensions.c \ + age_restriction_helper.c + +libtalerextensions_la_LIBADD = \ + $(top_builddir)/src/json/libtalerjson.la \ + $(top_builddir)/src/util/libtalerutil.la \ + -lgnunetjson \ + -lgnunetutil \ + -ljansson \ + $(XLIB) + diff --git a/src/extensions/age_restriction/Makefile.am b/src/extensions/age_restriction/Makefile.am new file mode 100644 index 000000000..bf5b2f5f5 --- /dev/null +++ b/src/extensions/age_restriction/Makefile.am @@ -0,0 +1,32 @@ +# 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 = \ + $(TALER_PLUGIN_LDFLAGS) \ + -no-undefined + +libtaler_extension_age_restriction_la_SOURCES = \ + 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/age_restriction/age_restriction.c b/src/extensions/age_restriction/age_restriction.c new file mode 100644 index 000000000..08b598d50 --- /dev/null +++ b/src/extensions/age_restriction/age_restriction.c @@ -0,0 +1,256 @@ +/* + 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 age_restriction.c + * @brief Utility functions regarding age restriction + * @author Özgür Kesim + */ +#include "platform.h" +#include "taler_util.h" +#include "taler_extensions.h" +#include "stdint.h" + +/* ================================================== + * + * Age Restriction TALER_Extension implementation + * + * ================================================== + */ + +/** + * @brief local configuration + */ + +static struct TALER_AgeRestrictionConfig AR_config = {0}; + +/** + * @brief implements the TALER_Extension.disable interface. + * + * @param ext Pointer to the current extension + */ +static void +age_restriction_disable ( + struct TALER_Extension *ext) +{ + if (NULL == ext) + return; + + ext->enabled = false; + ext->config = NULL; + + AR_config.mask.bits = 0; + AR_config.num_groups = 0; +} + + +/** + * @brief implements the TALER_Extension.load_config interface. + * + * @param ext if NULL, only tests the configuration + * @param jconfig the configuration as json + */ +static enum GNUNET_GenericReturnValue +age_restriction_load_config ( + const json_t *jconfig, + struct TALER_Extension *ext) +{ + struct TALER_AgeMask mask = {0}; + enum GNUNET_GenericReturnValue ret; + + ret = TALER_JSON_parse_age_groups (jconfig, &mask); + if (GNUNET_OK != ret) + return ret; + + /* only testing the parser */ + if (ext == NULL) + return GNUNET_OK; + + if (TALER_Extension_AgeRestriction != ext->type) + return GNUNET_SYSERR; + + if (mask.bits > 0) + { + /* if the mask is not zero, the first bit MUST be set */ + if (0 == (mask.bits & 1)) + return GNUNET_SYSERR; + + AR_config.mask.bits = mask.bits; + AR_config.num_groups = __builtin_popcount (mask.bits) - 1; + } + + ext->config = &AR_config; + ext->enabled = true; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "loaded new age restriction config with age groups: %s\n", + TALER_age_mask_to_string (&mask)); + + return GNUNET_OK; +} + + +/** + * @brief implements the TALER_Extension.manifest interface. + * + * @param ext if NULL, only tests the configuration + * @return configuration as json_t* object, maybe NULL + */ +static json_t * +age_restriction_manifest ( + const struct TALER_Extension *ext) +{ + json_t *conf; + + GNUNET_assert (NULL != ext); + + if (NULL == ext->config) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "age restriction not configured"); + return json_null (); + } + + conf = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("age_groups", + TALER_age_mask_to_string (&AR_config.mask)) + ); + return GNUNET_JSON_PACK ( + GNUNET_JSON_pack_bool ("critical", + ext->critical), + GNUNET_JSON_pack_string ("version", + ext->version), + GNUNET_JSON_pack_object_steal ("config", + conf) + ); +} + + +/* The extension for age restriction */ +struct TALER_Extension TE_age_restriction = { + .type = TALER_Extension_AgeRestriction, + .name = "age_restriction", + .critical = false, + .version = "1", + .enabled = false, /* disabled per default */ + .config = NULL, + .disable = &age_restriction_disable, + .load_config = &age_restriction_load_config, + .manifest = &age_restriction_manifest, + + /* This extension is not a policy extension */ + .create_policy_details = NULL, + .policy_get_handler = NULL, + .policy_post_handler = NULL, +}; + + +/** + * @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) +{ + 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); + + 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; + } + + if (NULL == groups) + groups = GNUNET_strdup (TALER_EXTENSION_AGE_RESTRICTION_DEFAULT_AGE_GROUPS); + + if (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; + } + + AR_config.mask = mask; + AR_config.num_groups = __builtin_popcount (mask.bits) - 1; /* no underflow, first bit always set */ + + 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_age_restriction.config = &AR_config; + + /* Note: we do now have TE_age_restriction_config set, however the extension + * is not yet enabled! For age restriction to become active, load_config must + * have been called. */ + + GNUNET_free (groups); + return &TE_age_restriction; +} + + +/** + * @brief implements the done() function for GNUNET_PLUGIN_load + * + * @param arg unused + * @return pointer to TALER_Extension on success or NULL otherwise. + */ +void * +libtaler_extension_age_restriction_done (void *arg) +{ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "[age restriction] disabling and unloading"); + AR_config.mask.bits = 0; + AR_config.num_groups = 0; + return NULL; +} + + +/* end of age_restriction.c */ diff --git a/src/extensions/age_restriction_helper.c b/src/extensions/age_restriction_helper.c new file mode 100644 index 000000000..8ba835117 --- /dev/null +++ b/src/extensions/age_restriction_helper.c @@ -0,0 +1,73 @@ +/* + 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)) + 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 new file mode 100644 index 000000000..999e9317a --- /dev/null +++ b/src/extensions/extensions.c @@ -0,0 +1,452 @@ +/* + 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 extensions.c + * @brief Utility functions for extensions + * @author Özgür Kesim + */ +#include "platform.h" +#include "taler_extensions_policy.h" +#include "taler_util.h" +#include "taler_signatures.h" +#include "taler_extensions.h" +#include "stdint.h" + +/* head of the list of all registered extensions */ +static struct TALER_Extensions TE_extensions = { + .next = NULL, + .extension = NULL, +}; + +const struct TALER_Extensions * +TALER_extensions_get_head () +{ + return &TE_extensions; +} + + +static enum GNUNET_GenericReturnValue +add_extension ( + const struct TALER_Extension *extension) +{ + /* Sanity checks */ + if ((NULL == extension) || + (NULL == extension->name) || + (NULL == extension->version) || + (NULL == extension->disable) || + (NULL == extension->load_config) || + (NULL == extension->manifest)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "invalid extension\n"); + return GNUNET_SYSERR; + } + + if (NULL == TE_extensions.extension) /* first extension ?*/ + TE_extensions.extension = extension; + else + { + struct TALER_Extensions *iter; + struct TALER_Extensions *last; + + /* Check for collisions */ + for (iter = &TE_extensions; + NULL != iter && NULL != iter->extension; + iter = iter->next) + { + const struct TALER_Extension *ext = iter->extension; + last = iter; + if (extension->type == ext->type || + 0 == strcasecmp (extension->name, + ext->name)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "extension collision for `%s'\n", + extension->name); + return GNUNET_NO; + } + } + + /* No collisions found, so add this extension to the list */ + { + struct TALER_Extensions *extn = GNUNET_new (struct TALER_Extensions); + extn->extension = extension; + last->next = extn; + } + } + + return GNUNET_OK; +} + + +const struct TALER_Extension * +TALER_extensions_get_by_type ( + enum TALER_Extension_Type type) +{ + for (const struct TALER_Extensions *it = &TE_extensions; + NULL != it && NULL != it->extension; + it = it->next) + { + if (it->extension->type == type) + return it->extension; + } + + /* No extension found. */ + return NULL; +} + + +bool +TALER_extensions_is_enabled_type ( + enum TALER_Extension_Type type) +{ + const struct TALER_Extension *ext = + TALER_extensions_get_by_type (type); + + return (NULL != ext && ext->enabled); +} + + +const struct TALER_Extension * +TALER_extensions_get_by_name ( + const char *name) +{ + for (const struct TALER_Extensions *it = &TE_extensions; + NULL != it; + it = it->next) + { + if (0 == strcasecmp (name, it->extension->name)) + return it->extension; + } + /* No extension found, try to load it. */ + + return NULL; +} + + +enum GNUNET_GenericReturnValue +TALER_extensions_verify_manifests_signature ( + const json_t *manifests, + struct TALER_MasterSignatureP *extensions_sig, + struct TALER_MasterPublicKeyP *master_pub) +{ + struct TALER_ExtensionManifestsHashP h_manifests; + + if (GNUNET_OK != + TALER_JSON_extensions_manifests_hash (manifests, + &h_manifests)) + return GNUNET_SYSERR; + if (GNUNET_OK != + TALER_exchange_offline_extension_manifests_hash_verify ( + &h_manifests, + master_pub, + extensions_sig)) + return GNUNET_NO; + return GNUNET_OK; +} + + +/* + * Closure used in TALER_extensions_load_taler_config during call to + * GNUNET_CONFIGURATION_iterate_sections with configure_extension. + */ +struct LoadConfClosure +{ + const struct GNUNET_CONFIGURATION_Handle *cfg; + enum GNUNET_GenericReturnValue error; +}; + + +/* + * Used in TALER_extensions_load_taler_config during call to + * GNUNET_CONFIGURATION_iterate_sections to load the configuration + * of supported extensions. + * + * @param cls Closure of type LoadConfClosure + * @param section name of the current section + */ +static void +configure_extension ( + void *cls, + const char *section) +{ + struct LoadConfClosure *col = cls; + const char *name; + char lib_name[1024] = {0}; + struct TALER_Extension *extension; + + if (GNUNET_OK != col->error) + return; + + if (0 != strncasecmp (section, + TALER_EXTENSION_SECTION_PREFIX, + sizeof(TALER_EXTENSION_SECTION_PREFIX) - 1)) + return; + + name = section + sizeof(TALER_EXTENSION_SECTION_PREFIX) - 1; + + + /* Load the extension library */ + GNUNET_snprintf (lib_name, + sizeof(lib_name), + "libtaler_extension_%s", + name); + /* Lower-case extension name, config is case-insensitive */ + for (unsigned int i = 0; i < strlen (lib_name); i++) + lib_name[i] = tolower (lib_name[i]); + + extension = GNUNET_PLUGIN_load (lib_name, + (void *) col->cfg); + if (NULL == extension) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Couldn't load extension library to `%s` (section [%s]).\n", + name, + section); + col->error = GNUNET_SYSERR; + return; + } + + + if (GNUNET_OK != add_extension (extension)) + { + /* TODO: Ignoring return values here */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "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_init ( + const struct GNUNET_CONFIGURATION_Handle *cfg) +{ + struct LoadConfClosure col = { + .cfg = cfg, + .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; +} + + +enum GNUNET_GenericReturnValue +TALER_extensions_parse_manifest ( + json_t *obj, + int *critical, + const char **version, + json_t **config) +{ + enum GNUNET_GenericReturnValue ret; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_boolean ("critical", + critical), + GNUNET_JSON_spec_string ("version", + version), + GNUNET_JSON_spec_json ("config", + config), + GNUNET_JSON_spec_end () + }; + + *config = NULL; + if (GNUNET_OK != + (ret = GNUNET_JSON_parse (obj, + spec, + NULL, + NULL))) + return ret; + return GNUNET_OK; +} + + +enum GNUNET_GenericReturnValue +TALER_extensions_load_manifests ( + const json_t *extensions) +{ + const char *name; + json_t *manifest; + + GNUNET_assert (NULL != extensions); + GNUNET_assert (json_is_object (extensions)); + + json_object_foreach ((json_t *) extensions, name, manifest) + { + int critical; + const char *version; + json_t *config; + struct TALER_Extension *extension + = (struct TALER_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; + } + + /* load and verify criticality, version, etc. */ + if (GNUNET_OK != + TALER_extensions_parse_manifest ( + manifest, + &critical, + &version, + &config)) + return GNUNET_SYSERR; + + if (critical != extension->critical + || 0 != strcmp (version, + extension->version) // TODO: libtool compare? + || NULL == config + || (GNUNET_OK != + extension->load_config (config, + NULL)) ) + return GNUNET_SYSERR; + + /* This _should_ work now */ + if (GNUNET_OK != + extension->load_config (config, + extension)) + return GNUNET_SYSERR; + + extension->enabled = true; + } + + /* make sure to disable all extensions that weren't mentioned in the json */ + for (const struct TALER_Extensions *it = TALER_extensions_get_head (); + NULL != it; + it = it->next) + { + if (NULL == json_object_get (extensions, it->extension->name)) + it->extension->disable ((struct TALER_Extension *) it); + } + + return GNUNET_OK; +} + + +/* + * Policy related + */ + +static char *fulfillment2str[] = { + [TALER_PolicyFulfillmentInitial] = "<init>", + [TALER_PolicyFulfillmentReady] = "Ready", + [TALER_PolicyFulfillmentSuccess] = "Success", + [TALER_PolicyFulfillmentFailure] = "Failure", + [TALER_PolicyFulfillmentTimeout] = "Timeout", + [TALER_PolicyFulfillmentInsufficient] = "Insufficient", +}; + +const char * +TALER_policy_fulfillment_state_str ( + enum TALER_PolicyFulfillmentState state) +{ + GNUNET_assert (TALER_PolicyFulfillmentStateCount > state); + return fulfillment2str[state]; +} + + +enum GNUNET_GenericReturnValue +TALER_extensions_create_policy_details ( + const char *currency, + const json_t *policy_options, + struct TALER_PolicyDetails *details, + const char **error_hint) +{ + enum GNUNET_GenericReturnValue ret; + const struct TALER_Extension *extension; + const json_t *jtype; + const char *type; + + *error_hint = NULL; + + if ((NULL == policy_options) || + (! json_is_object (policy_options))) + { + *error_hint = "invalid policy object"; + return GNUNET_SYSERR; + } + + jtype = json_object_get (policy_options, "type"); + if (NULL == jtype) + { + *error_hint = "no type in policy object"; + return GNUNET_SYSERR; + } + + type = json_string_value (jtype); + if (NULL == type) + { + *error_hint = "invalid type in policy object"; + return GNUNET_SYSERR; + } + + extension = TALER_extensions_get_by_name (type); + if ((NULL == extension) || + (NULL == extension->create_policy_details)) + { + GNUNET_break (0); + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Unsupported extension policy '%s' requested\n", + type); + return GNUNET_NO; + } + + /* Set state fields in the policy details to initial values. */ + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (currency, + &details->accumulated_total)); + GNUNET_assert (GNUNET_OK == + TALER_amount_set_zero (currency, + &details->policy_fee)); + details->deadline = GNUNET_TIME_UNIT_FOREVER_TS; + details->fulfillment_state = TALER_PolicyFulfillmentInitial; + details->no_policy_fulfillment_id = true; + ret = extension->create_policy_details (currency, + policy_options, + details, + error_hint); + return ret; + +} + + +/* end of extensions.c */ |