From 8684a9bfea9223808e33edca9f91b8bd76379fd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96zg=C3=BCr=20Kesim?= Date: Sun, 23 Jan 2022 01:31:02 +0100 Subject: [age_restriction] progress 13/n - major refactoring of extensions - extensions live now in a separate library, libtalerextensions - refactored all components using age_restriction accordingly - plumbing for plugin support for extensions roughly layed down --- src/extensions/Makefile.am | 30 +++ src/extensions/extension_age_restriction.c | 321 +++++++++++++++++++++++++++ src/extensions/extensions.c | 333 +++++++++++++++++++++++++++++ 3 files changed, 684 insertions(+) create mode 100644 src/extensions/Makefile.am create mode 100644 src/extensions/extension_age_restriction.c create mode 100644 src/extensions/extensions.c (limited to 'src/extensions') diff --git a/src/extensions/Makefile.am b/src/extensions/Makefile.am new file mode 100644 index 000000000..792b7eeb3 --- /dev/null +++ b/src/extensions/Makefile.am @@ -0,0 +1,30 @@ +# 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 + + +# Libraries + +lib_LTLIBRARIES = \ + libtalerextensions.la + +libtalerextensions_la_LDFLAGS = \ + -version-info 0:0:0 \ + -no-undefined + +libtalerextensions_la_SOURCES = \ + extensions.c \ + extension_age_restriction.c + +libtalerextensions_la_LIBADD = \ + -lgnunetjson \ + -ljansson \ + $(XLIB) diff --git a/src/extensions/extension_age_restriction.c b/src/extensions/extension_age_restriction.c new file mode 100644 index 000000000..a9ffb7f1a --- /dev/null +++ b/src/extensions/extension_age_restriction.c @@ -0,0 +1,321 @@ +/* + 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 + */ +/** + * @file extension_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" + + +/** + * @param groups String representation of the age groups. Must be of the form + * a:b:...:n:m + * with + * 0 < a < b <...< n < m < 32 + * @param[out] mask Bit representation of the age groups. + * @return Error if string was invalid, OK otherwise. + */ +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->mask |= 1 << val; + prev = val; + val = 0; + continue; + } + + if ('0'>c || '9'=val || 32<=val) + return GNUNET_SYSERR; + } + + if (0>val || 32<=val || prev>=val) + return GNUNET_SYSERR; + + mask->mask |= (1 << val); + mask->mask |= 1; // mark zeroth group, too + + return GNUNET_OK; +} + + +/** + * Encodes the age mask into a string, like "8:10:12:14:16:18:21" + * + * @param mask Age mask + * @return String representation of the age mask, allocated by GNUNET_malloc. + * Can be used as value in the TALER config. + */ +char * +TALER_age_mask_to_string ( + const struct TALER_AgeMask *m) +{ + uint32_t mask = m->mask; + unsigned int n = 0; + char *buf = GNUNET_malloc (32 * 3); // max characters possible + char *pos = buf; + + if (NULL == buf) + { + return buf; + } + + while (mask != 0) + { + mask >>= 1; + n++; + if (0 == (mask & 1)) + { + continue; + } + + if (n > 9) + { + *(pos++) = '0' + n / 10; + } + *(pos++) = '0' + n % 10; + + if (0 != (mask >> 1)) + { + *(pos++) = ':'; + } + } + return buf; +} + + +/* ================================================== + * + * Age Restriction TALER_Extension imlementation + * + * ================================================== + */ + + +/** + * @brief implements the TALER_Extension.disable interface. + */ +void +age_restriction_disable ( + struct TALER_Extension *this) +{ + if (NULL == this) + return; + + this->config = NULL; + + if (NULL != this->config_json) + { + json_decref (this->config_json); + this->config_json = NULL; + } +} + + +/** + * @brief implements the TALER_Extension.load_taler_config interface. + * @param cfg Handle to the GNUNET configuration + * @param[out] enabled Set to true if age restriction is enabled in the config, false otherwise. + * @param[out] mask Mask for age restriction. Will be 0 if age restriction was not enabled in the config. + * @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 *this, + 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 */ + this->config = NULL; + this->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.mask = 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.mask = TALER_EXTENSION_AGE_RESTRICTION_DEFAULT_AGE_MASK; + } + + if (GNUNET_OK == ret) + this->config = (void *) (size_t) mask.mask; + + GNUNET_free (groups); + return ret; +} + + +/** + * @brief implements the TALER_Extension.load_json_config interface. + * @param this if NULL, only tests the configuration + * @param config the configuration as json + */ +static enum GNUNET_GenericReturnValue +age_restriction_load_json_config ( + struct TALER_Extension *this, + json_t *config) +{ + struct TALER_AgeMask mask = {0}; + enum GNUNET_GenericReturnValue ret; + + ret = TALER_JSON_parse_agemask (config, &mask); + if (GNUNET_OK != ret) + return ret; + + /* only testing the parser */ + if (this == NULL) + return GNUNET_OK; + + if (TALER_Extension_AgeRestriction != this->type) + return GNUNET_SYSERR; + + if (NULL != this->config) + GNUNET_free (this->config); + + this->config = GNUNET_malloc (sizeof(struct TALER_AgeMask)); + GNUNET_memcpy (this->config, &mask, sizeof(struct TALER_AgeMask)); + + if (NULL != this->config_json) + json_decref (this->config_json); + + this->config_json = config; + + return GNUNET_OK; +} + + +/** + * @brief implements the TALER_Extension.load_json_config interface. + * @param this if NULL, only tests the configuration + * @param config the configuration as json + */ +json_t * +age_restriction_config_to_json ( + const struct TALER_Extension *this) +{ + struct TALER_AgeMask mask; + char *mask_str; + json_t *conf; + + GNUNET_assert (NULL != this); + GNUNET_assert (NULL != this->config); + + if (NULL != this->config_json) + { + return json_copy (this->config_json); + } + + mask.mask = (uint32_t) (size_t) this->config; + mask_str = TALER_age_mask_to_string (&mask); + conf = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("age_groups", mask_str) + ); + + return GNUNET_JSON_PACK ( + GNUNET_JSON_pack_bool ("critical", this->critical), + GNUNET_JSON_pack_string ("version", this->version), + GNUNET_JSON_pack_object_steal ("config", conf) + ); +} + + +/** + * @brief implements the TALER_Extension.test_json_config interface. + */ +static enum GNUNET_GenericReturnValue +age_restriction_test_json_config ( + const json_t *config) +{ + struct TALER_AgeMask mask = {0}; + + return TALER_JSON_parse_agemask (config, &mask); +} + + +/* The extension for age restriction */ +struct TALER_Extension _extension_age_restriction = { + .next = NULL, + .type = TALER_Extension_AgeRestriction, + .name = "age_restriction", + .critical = false, + .version = "1", + .config = NULL, // disabled per default + .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, +}; + +/* end of extension_age_restriction.c */ diff --git a/src/extensions/extensions.c b/src/extensions/extensions.c new file mode 100644 index 000000000..55d970c57 --- /dev/null +++ b/src/extensions/extensions.c @@ -0,0 +1,333 @@ +/* + 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 + */ +/** + * @file extensions.c + * @brief Utility functions for extensions + * @author Özgür Kesim + */ +#include "platform.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_Extension *_extensions = NULL; +static bool _initialized = false; + +void +TALER_extensions_init () +{ + extern struct TALER_Extension _extension_age_restriction; + if (! _initialized) + _extensions = &_extension_age_restriction; + + _initialized = true; +} + + +const struct TALER_Extension * +TALER_extensions_get_head () +{ + return _extensions; +} + + +enum GNUNET_GenericReturnValue +TALER_extensions_add ( + const struct TALER_Extension *new) +{ + struct TALER_Extension *ext; + + if (_initialized) + return GNUNET_SYSERR; + + GNUNET_assert (NULL != _extensions); + + /* Sanity checks */ + if (NULL == new || + NULL == new->name || + NULL == new->version || + NULL == new->disable || + NULL == new->test_json_config || + NULL == new->load_json_config || + NULL == new->config_to_json || + NULL == new->load_taler_config || + NULL == new->next) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "invalid extension\n"); + return GNUNET_SYSERR; + } + + /* Check for collisions */ + for (ext = _extensions; NULL != ext; ext = ext->next) + { + if (new->type == ext->type || + 0 == strcmp (new->name, ext->name)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "extension collision\n"); + return GNUNET_NO; + } + } + + /* No collisions found, so add this extension to the list */ + ext->next = (struct TALER_Extension *) new; + + return GNUNET_OK; +} + + +const struct TALER_Extension * +TALER_extensions_get_by_type ( + enum TALER_Extension_Type type) +{ + + for (const struct TALER_Extension *it = _extensions; + NULL != it; + it = it->next) + { + if (it->type == type) + return it; + } + + /* 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 && + TALER_extensions_is_enabled (ext)); +} + + +const struct TALER_Extension * +TALER_extensions_get_by_name ( + const char *name) +{ + for (const struct TALER_Extension *it = _extensions; + NULL != it; + it = it->next) + { + if (0 == strcmp (name, it->name)) + return it; + } + /* No extension found. */ + return NULL; +} + + +enum GNUNET_GenericReturnValue +config_hash_verify ( + const struct TALER_ExtensionConfigHash *h_config, + const struct TALER_MasterPublicKeyP *master_pub, + const struct TALER_MasterSignatureP *master_sig + ) +{ + struct TALER_MasterExtensionConfigurationPS ec = { + .purpose.purpose = htonl (TALER_SIGNATURE_MASTER_EXTENSION), + .purpose.size = htonl (sizeof(ec)), + .h_config = *h_config + }; + + return GNUNET_CRYPTO_eddsa_verify ( + TALER_SIGNATURE_MASTER_EXTENSION, + &ec, + &master_sig->eddsa_signature, + &master_pub->eddsa_pub); +} + + +enum GNUNET_GenericReturnValue +TALER_extensions_verify_json_config_signature ( + json_t *extensions, + struct TALER_MasterSignatureP *extensions_sig, + struct TALER_MasterPublicKeyP *master_pub) +{ + struct TALER_ExtensionConfigHash h_config; + + if (GNUNET_OK != + TALER_JSON_extensions_config_hash (extensions, &h_config)) + return GNUNET_SYSERR; + + if (GNUNET_OK != config_hash_verify ( + &h_config, + master_pub, + extensions_sig)) + return GNUNET_NO; + + return GNUNET_OK; +} + + +struct load_conf_closure +{ + const struct GNUNET_CONFIGURATION_Handle *cfg; + enum GNUNET_GenericReturnValue error; +}; + +static void +collect_extensions ( + void *cls, + const char *section) +{ + struct load_conf_closure *col = cls; + const char *name; + const 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; + + if (NULL == (extension = TALER_extensions_get_by_name (name))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unsupported extension `%s` (section [%s]).\n", name, + section); + col->error = GNUNET_SYSERR; + return; + } + + if (GNUNET_OK != + extension->load_taler_config ( + (struct TALER_Extension *) extension, + col->cfg)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Couldn't parse configuration for extension `%s` (section [%s]).\n", + name, + section); + col->error = GNUNET_SYSERR; + return; + } +} + + +enum GNUNET_GenericReturnValue +TALER_extensions_load_taler_config ( + const struct GNUNET_CONFIGURATION_Handle *cfg) +{ + struct load_conf_closure col = { + .cfg = cfg, + .error = GNUNET_OK, + }; + + GNUNET_CONFIGURATION_iterate_sections (cfg, + &collect_extensions, + &col); + return col.error; +} + + +static enum GNUNET_GenericReturnValue +is_json_extension_config ( + 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 () + }; + + ret = GNUNET_JSON_parse (obj, spec, NULL, NULL); + if (GNUNET_OK == ret) + GNUNET_JSON_parse_free (spec); + + return ret; +} + + +enum GNUNET_GenericReturnValue +TALER_extensions_load_json_config ( + json_t *extensions) +{ + const char*name; + json_t *blob; + + GNUNET_assert (NULL != extensions); + GNUNET_assert (json_is_object (extensions)); + + json_object_foreach (extensions, name, blob) + { + int critical; + const char *version; + json_t *config; + const struct TALER_Extension *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 != + is_json_extension_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; + + /* This _should_ work now */ + if (GNUNET_OK != + extension->load_json_config ((struct TALER_Extension *) extension, + config)) + return GNUNET_SYSERR; + } + + /* make sure to disable all extensions that weren't mentioned in the json */ + for (const struct TALER_Extension *it = TALER_extensions_get_head (); + NULL != it; + it = it->next) + { + if (NULL == json_object_get (extensions, it->name)) + it->disable ((struct TALER_Extension *) it); + } + + return GNUNET_OK; +} + + +/* end of extensions.c */ -- cgit v1.2.3