aboutsummaryrefslogtreecommitdiff
path: root/src/extensions
diff options
context:
space:
mode:
authorÖzgür Kesim <oec-taler@kesim.org>2022-01-23 01:31:02 +0100
committerÖzgür Kesim <oec-taler@kesim.org>2022-01-23 01:36:21 +0100
commit8684a9bfea9223808e33edca9f91b8bd76379fd0 (patch)
tree2354ad02b8ea515fe2de64cb8a42ca078f9f8b64 /src/extensions
parent1962ed6b0b44c6c7d3503b3340da1be147e25f87 (diff)
[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
Diffstat (limited to 'src/extensions')
-rw-r--r--src/extensions/Makefile.am30
-rw-r--r--src/extensions/extension_age_restriction.c321
-rw-r--r--src/extensions/extensions.c333
3 files changed, 684 insertions, 0 deletions
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 <http://www.gnu.org/licenses/>
+ */
+/**
+ * @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'<c)
+ return GNUNET_SYSERR;
+
+ val = 10 * val + c - '0';
+
+ if (0>=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 <http://www.gnu.org/licenses/>
+ */
+/**
+ * @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 */