gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-exchange] 20/124: kyclogic API planning


From: gnunet
Subject: [taler-exchange] 20/124: kyclogic API planning
Date: Tue, 17 Sep 2024 21:27:12 +0200

This is an automated email from the git hooks/post-receive script.

grothoff pushed a commit to tag cg-aml-branch-compiles
in repository exchange.

commit e575ada4d1e7f01cfd1d2592ae2c95b7721967c4
Author: Christian Grothoff <christian@grothoff.org>
AuthorDate: Mon May 6 14:08:14 2024 +0200

    kyclogic API planning
---
 src/exchangedb/Makefile.am                         |   2 +-
 src/exchangedb/pg_get_kyc_rules.c                  |  62 +++++++++
 ...atisfied_kyc_processes.h => pg_get_kyc_rules.h} |  27 ++--
 src/exchangedb/pg_select_satisfied_kyc_processes.c | 141 -------------------
 src/include/taler_exchangedb_plugin.h              |  28 +---
 src/include/taler_kyclogic_lib.h                   | 155 ++++++++-------------
 src/kyclogic/kyclogic_api.c                        |  73 ++++++++--
 7 files changed, 206 insertions(+), 282 deletions(-)

diff --git a/src/exchangedb/Makefile.am b/src/exchangedb/Makefile.am
index f23895f4b..e9db91ae3 100644
--- a/src/exchangedb/Makefile.am
+++ b/src/exchangedb/Makefile.am
@@ -105,7 +105,7 @@ libtaler_plugin_exchangedb_postgres_la_SOURCES = \
   pg_reserves_update.h pg_reserves_update.c \
   pg_select_aggregation_amounts_for_kyc_check.h 
pg_select_aggregation_amounts_for_kyc_check.c \
   pg_lookup_wire_fee_by_time.h pg_lookup_wire_fee_by_time.c \
-  pg_select_satisfied_kyc_processes.h pg_select_satisfied_kyc_processes.c \
+  pg_get_kyc_rules.h pg_get_kyc_rules.c \
   pg_get_pending_kyc_requirement_process.h 
pg_get_pending_kyc_requirement_process.c \
   pg_kyc_provider_account_lookup.h pg_kyc_provider_account_lookup.c \
   pg_insert_kyc_requirement_for_account.h 
pg_insert_kyc_requirement_for_account.c \
diff --git a/src/exchangedb/pg_get_kyc_rules.c 
b/src/exchangedb/pg_get_kyc_rules.c
new file mode 100644
index 000000000..0364470b7
--- /dev/null
+++ b/src/exchangedb/pg_get_kyc_rules.c
@@ -0,0 +1,62 @@
+/*
+   This file is part of TALER
+   Copyright (C) 2022-2024 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 exchangedb/pg_get_kyc_rules.c
+ * @brief Implementation of the get_kyc_rules function for Postgres
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "taler_error_codes.h"
+#include "taler_dbevents.h"
+#include "taler_pq_lib.h"
+#include "pg_get_kyc_rules.h"
+#include "pg_helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TEH_PG_get_kyc_rules (
+  void *cls,
+  const struct TALER_PaytoHashP *h_payto,
+  struct GNUNET_TIME_Timestamp *expiration_time,
+  json_t **jrules)
+{
+  struct PostgresClosure *pg = cls;
+  struct GNUNET_PQ_QueryParam params[] = {
+    GNUNET_PQ_query_param_auto_from_type (h_payto),
+    GNUNET_PQ_query_param_end
+  };
+  struct GNUNET_PQ_ResultSpec rs[] = {
+    GNUNET_PQ_result_spec_json ("jnew_rules",
+                                jrules),
+    GNUNET_PQ_result_spec_timestamp ("expiration_time",
+                                     expiration_time),
+    GNUNET_PQ_result_spec_end
+  };
+
+  PREPARE (pg,
+           "get_kyc_rules",
+           "SELECT"
+           "  jnew_rules"
+           " ,expiration_time"
+           "  FROM legitimization_outcomes"
+           " WHERE h_payto=$1"
+           "   AND is_active;");
+  return GNUNET_PQ_eval_prepared_singleton_select (
+    pg->conn,
+    "get_kyc_rules",
+    params,
+    rs);
+}
diff --git a/src/exchangedb/pg_select_satisfied_kyc_processes.h 
b/src/exchangedb/pg_get_kyc_rules.h
similarity index 65%
rename from src/exchangedb/pg_select_satisfied_kyc_processes.h
rename to src/exchangedb/pg_get_kyc_rules.h
index bc1639ff9..8d43b9546 100644
--- a/src/exchangedb/pg_select_satisfied_kyc_processes.h
+++ b/src/exchangedb/pg_get_kyc_rules.h
@@ -1,6 +1,6 @@
 /*
    This file is part of TALER
-   Copyright (C) 2022 Taler Systems SA
+   Copyright (C) 2022-2024 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
@@ -14,34 +14,33 @@
    TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 /**
- * @file exchangedb/pg_select_satisfied_kyc_processes.h
- * @brief implementation of the select_satisfied_kyc_processes function for 
Postgres
+ * @file exchangedb/pg_get_kyc_rules.h
+ * @brief implementation of the get_kyc_rules function for Postgres
  * @author Christian Grothoff
  */
-#ifndef PG_SELECT_SATISFIED_KYC_PROCESSES_H
-#define PG_SELECT_SATISFIED_KYC_PROCESSES_H
+#ifndef PG_GET_KYC_RULES_H
+#define PG_GET_KYC_RULES_H
 
 #include "taler_util.h"
 #include "taler_json_lib.h"
 #include "taler_exchangedb_plugin.h"
 
+
 /**
- * Call us on KYC processes satisfied for the given
- * account.
+ * Return KYC rules that apply to the given account.
  *
  * @param cls the @e cls of this struct with the plugin-specific state
  * @param h_payto account identifier
- * @param spc function to call for each satisfied KYC process
- * @param spc_cls closure for @a spc
+ * @param[out] expiration_time when do the @a jrules expire
+ * @param[out] jrules set to the active KYC rules for the
+ *    given account, set to NULL if no custom rules are active
  * @return transaction status code
  */
-
-
 enum GNUNET_DB_QueryStatus
-TEH_PG_select_satisfied_kyc_processes (
+TEH_PG_get_kyc_rules (
   void *cls,
   const struct TALER_PaytoHashP *h_payto,
-  TALER_EXCHANGEDB_SatisfiedProviderCallback spc,
-  void *spc_cls);
+  struct GNUNET_TIME_Timestamp *expiration_time,
+  json_t **jrules);
 
 #endif
diff --git a/src/exchangedb/pg_select_satisfied_kyc_processes.c 
b/src/exchangedb/pg_select_satisfied_kyc_processes.c
deleted file mode 100644
index e0d884ef1..000000000
--- a/src/exchangedb/pg_select_satisfied_kyc_processes.c
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
-   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 exchangedb/pg_select_satisfied_kyc_processes.c
- * @brief Implementation of the select_satisfied_kyc_processes function for 
Postgres
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include "taler_error_codes.h"
-#include "taler_dbevents.h"
-#include "taler_pq_lib.h"
-#include "pg_select_satisfied_kyc_processes.h"
-#include "pg_helper.h"
-
-
-/**
- * Closure for #get_legitimizations_cb().
- */
-struct GetLegitimizationsContext
-{
-  /**
-   * Function to call per result.
-   */
-  TALER_EXCHANGEDB_SatisfiedProviderCallback cb;
-
-  /**
-   * Closure for @e cb.
-   */
-  void *cb_cls;
-
-  /**
-   * Plugin context.
-   */
-  struct PostgresClosure *pg;
-
-  /**
-   * Flag set to #GNUNET_OK as long as everything is fine.
-   */
-  enum GNUNET_GenericReturnValue status;
-
-};
-
-
-/**
- * Invoke the callback for each result.
- *
- * @param cls a `struct GetLegitimizationsContext *`
- * @param result SQL result
- * @param num_results number of rows in @a result
- */
-static void
-get_legitimizations_cb (void *cls,
-                        PGresult *result,
-                        unsigned int num_results)
-{
-  struct GetLegitimizationsContext *ctx = cls;
-
-  for (unsigned int i = 0; i < num_results; i++)
-  {
-    char *provider_section;
-    struct GNUNET_PQ_ResultSpec rs[] = {
-      GNUNET_PQ_result_spec_string ("provider_section",
-                                    &provider_section),
-      GNUNET_PQ_result_spec_end
-    };
-
-    if (GNUNET_OK !=
-        GNUNET_PQ_extract_result (result,
-                                  rs,
-                                  i))
-    {
-      GNUNET_break (0);
-      ctx->status = GNUNET_SYSERR;
-      return;
-    }
-    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-                "Found satisfied LEGI: %s\n",
-                provider_section);
-    ctx->cb (ctx->cb_cls,
-             provider_section);
-    GNUNET_PQ_cleanup_result (rs);
-  }
-}
-
-
-enum GNUNET_DB_QueryStatus
-TEH_PG_select_satisfied_kyc_processes (
-  void *cls,
-  const struct TALER_PaytoHashP *h_payto,
-  TALER_EXCHANGEDB_SatisfiedProviderCallback spc,
-  void *spc_cls)
-{
-  struct PostgresClosure *pg = cls;
-  struct GNUNET_TIME_Absolute now
-    = GNUNET_TIME_absolute_get ();
-  struct GNUNET_PQ_QueryParam params[] = {
-    GNUNET_PQ_query_param_auto_from_type (h_payto),
-    GNUNET_PQ_query_param_absolute_time (&now),
-    GNUNET_PQ_query_param_end
-  };
-  struct GetLegitimizationsContext ctx = {
-    .cb = spc,
-    .cb_cls = spc_cls,
-    .pg = pg,
-    .status = GNUNET_OK
-  };
-  enum GNUNET_DB_QueryStatus qs;
-
-  PREPARE (pg,
-           "get_satisfied_legitimizations",
-           "SELECT "
-           " provider_section"
-           " FROM legitimization_processes"
-           " WHERE h_payto=$1"
-           "   AND expiration_time>=$2;");
-  qs = GNUNET_PQ_eval_prepared_multi_select (
-    pg->conn,
-    "get_satisfied_legitimizations",
-    params,
-    &get_legitimizations_cb,
-    &ctx);
-  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
-              "Satisfied LEGI check returned %d\n",
-              qs);
-  if (GNUNET_OK != ctx.status)
-    return GNUNET_DB_STATUS_HARD_ERROR;
-  return qs;
-}
diff --git a/src/include/taler_exchangedb_plugin.h 
b/src/include/taler_exchangedb_plugin.h
index 69287644c..e6b2e78fa 100644
--- a/src/include/taler_exchangedb_plugin.h
+++ b/src/include/taler_exchangedb_plugin.h
@@ -1111,20 +1111,6 @@ typedef void
   const struct TALER_MasterSignatureP *master_sig);
 
 
-/**
- * Function called on all KYC process names that the given
- * account has already passed.
- *
- * @param cls closure
- * @param kyc_provider_section_name configuration section
- *        of the respective KYC process
- */
-typedef void
-(*TALER_EXCHANGEDB_SatisfiedProviderCallback)(
-  void *cls,
-  const char *kyc_provider_section_name);
-
-
 /**
  * Function called on all legitimization operations
  * we have performed for the given account so far
@@ -6874,21 +6860,21 @@ struct TALER_EXCHANGEDB_Plugin
 
 
   /**
-   * Call us on KYC processes satisfied for the given
-   * account.
+   * Return KYC rules that apply to the given account.
    *
    * @param cls the @e cls of this struct with the plugin-specific state
    * @param h_payto account identifier
-   * @param spc function to call for each satisfied KYC process
-   * @param spc_cls closure for @a spc
+   * @param[out] expiration_time when do the @a jrules expire
+   * @param[out] jrules set to the active KYC rules for the
+   *    given account, set to NULL if no custom rules are active
    * @return transaction status code
    */
   enum GNUNET_DB_QueryStatus
-    (*select_satisfied_kyc_processes)(
+    (*get_kyc_rules)(
     void *cls,
     const struct TALER_PaytoHashP *h_payto,
-    TALER_EXCHANGEDB_SatisfiedProviderCallback spc,
-    void *spc_cls);
+    struct GNUNET_TIME_Timestamp *expiration_time,
+    json_t **jrules);
 
 
   /**
diff --git a/src/include/taler_kyclogic_lib.h b/src/include/taler_kyclogic_lib.h
index d5251394f..a5b627769 100644
--- a/src/include/taler_kyclogic_lib.h
+++ b/src/include/taler_kyclogic_lib.h
@@ -151,21 +151,36 @@ typedef void
 
 
 /**
- * Call us on KYC processes satisfied for the given
- * account. Must match the ``select_satisfied_kyc_processes`` of the exchange 
database plugin.
+ * Rule that triggers some measure(s).
+ */
+struct TALER_KYCLOGIC_KycRule;
+
+/**
+ * Set of rules that applies to an account.
+ */
+struct TALER_KYCLOGIC_KycRuleSet;
+
+
+/**
+ * Parse set of rules that applies to an account.
  *
- * @param cls the @e cls of this struct with the plugin-specific state
- * @param h_payto account identifier
- * @param spc function to call for each satisfied KYC process
- * @param spc_cls closure for @a spc
- * @return transaction status code
+ * @param jrules JSON representation to parse
+ * @param[out] lrs set to rule set
+ * @return #GNUNET_SYSERR JSON is invalid
  */
-typedef enum GNUNET_DB_QueryStatus
-(*TALER_KYCLOGIC_KycSatisfiedIterator)(
-  void *cls,
-  const struct TALER_PaytoHashP *h_payto,
-  TALER_EXCHANGEDB_SatisfiedProviderCallback spc,
-  void *spc_cls);
+enum GNUNET_GenericReturnValue
+TALER_KYCLOGIC_rules_parse (
+  const json_t *jrules,
+  struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs);
+
+
+/**
+ * Free set of legitimization rules.
+ *
+ * @param[in] lrs set of rules to free
+ */
+void
+TALER_KYCLOGIC_rules_free (struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs);
 
 
 /**
@@ -177,55 +192,30 @@ typedef enum GNUNET_DB_QueryStatus
  * @param event what type of operation is triggering the
  *         test if KYC is required
  * @param h_payto account the event is about
- * @param ki callback that returns list of already
- *    satisfied KYC checks, implemented by ``select_satisfied_kyc_processes`` 
of the exchangedb
- * @param ki_cls closure for @a ki
+ * @param lrs legitimization rules for @a h_payto
  * @param ai callback offered to inquire about historic
  *         amounts involved in this type of operation
  *         at the given account
  * @param ai_cls closure for @a ai
- * @param[out] required set to NULL if no check is needed,
- *   otherwise space-separated list of required checks
+ * @param[out] triggered_rule set to NULL if no rule
+ *   is triggered, otherwise the rule with measures
+ *   that must be satisfied (will be the highest
+ *   applicable rule by display priority)
  * @return transaction status
  */
 enum GNUNET_DB_QueryStatus
 TALER_KYCLOGIC_kyc_test_required (
   enum TALER_KYCLOGIC_KycTriggerEvent event,
   const struct TALER_PaytoHashP *h_payto,
-  TALER_KYCLOGIC_KycSatisfiedIterator ki,
-  void *ki_cls,
+  const struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs,
   TALER_KYCLOGIC_KycAmountIterator ai,
   void *ai_cls,
-  char **required);
+  struct TALER_KYCLOGIC_KycRule **triggered_rule);
 
 
 /**
- * Check if the @a requirements are now satsified for
- * @a h_payto account.
- *
- * @param[in,out] requirements space-spearated list of requirements,
- *       already satisfied requirements are removed from the list
- * @param h_payto hash over the account
- * @param[out] kyc_details if satisfied, set to what kind of
- *             KYC information was collected
- * @param ki iterator over satisfied providers
- * @param ki_cls closure for @a ki
- * @param[out] satisfied set to true if the KYC check was satisfied
- * @return transaction status (from @a ki)
- */
-enum GNUNET_DB_QueryStatus
-TALER_KYCLOGIC_check_satisfied (
-  char **requirements,
-  const struct TALER_PaytoHashP *h_payto,
-  json_t **kyc_details,
-  TALER_KYCLOGIC_KycSatisfiedIterator ki,
-  void *ki_cls,
-  bool *satisfied);
-
-
-/**
- * Iterate over all thresholds that are applicable
- * to a particular type of @a event
+ * Iterate over all thresholds that are applicable to a particular type of @a
+ * event under exposed global rules.
  *
  * @param event thresholds to look up
  * @param it function to call on each
@@ -239,61 +229,25 @@ TALER_KYCLOGIC_kyc_iterate_thresholds (
 
 
 /**
- * Function called with the provider details and
- * associated plugin closures for matching logics.
+ * Check if a given @a rule can be satisfied in principle.
  *
- * @param cls closure
- * @param pd provider details of a matching logic
- * @param plugin_cls closure of the plugin
- * @return #GNUNET_OK to continue to iterate
+ * @param rule the rule to check if it is verboten
+ * @return true if the check can be satisfied,
+ *         false if the check can never be satisfied,
  */
-typedef enum GNUNET_GenericReturnValue
-(*TALER_KYCLOGIC_DetailsCallback)(
-  void *cls,
-  const struct TALER_KYCLOGIC_ProviderDetails *pd,
-  void *plugin_cls);
+bool
+TALER_KYCLOGIC_is_satisfiable (
+  const struct TALER_KYCLOGIC_KycRule *rule);
 
 
 /**
- * Call @a cb for all logics with name @a logic_name,
- * providing the plugin closure and the @a pd configurations.
+ * Obtain the provider logic for a given set of @a lrs
+ * and a specific @a kyc_rule from @a lrs that was
+ * triggered and the choosen @a measure_name from the
+ * list of measures of that @a kyc_rule.
  *
- * @param logic_name name of the logic to match
- * @param cb function to call on matching results
- * @param cb_cls closure for @a cb
- */
-void
-TALER_KYCLOGIC_kyc_get_details (
-  const char *logic_name,
-  TALER_KYCLOGIC_DetailsCallback cb,
-  void *cb_cls);
-
-
-/**
- * Check if a given @a check_name is a legal name (properly
- * configured) and can be satisfied in principle.
- *
- * @param check_name name of the check to see if it is configured
- * @return #GNUNET_OK if the check can be satisfied,
- *         #GNUNET_NO if the check can never be satisfied,
- *         #GNUNET_SYSERR if the type of the check is unknown
- */
-enum GNUNET_GenericReturnValue
-TALER_KYCLOGIC_check_satisfiable (
-  const char *check_name);
-
-
-/**
- * Return list of all KYC checks that are possible.
- *
- * @return JSON array of strings with the allowed KYC checks
- */
-json_t *
-TALER_KYCLOGIC_get_satisfiable (void);
-
-
-/**
- * Obtain the provider logic for a given set of @a requirements.
+ * FIXME: we probably want to instead set up the logic
+ * with the context instead of just returning it here!
  *
  * @param requirements space-separated list of required checks
  * @param ut type of the entity performing the check
@@ -303,7 +257,9 @@ TALER_KYCLOGIC_get_satisfiable (void);
  */
 enum GNUNET_GenericReturnValue
 TALER_KYCLOGIC_requirements_to_logic (
-  const char *requirements,
+  const struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs,
+  const struct TALER_KYCLOGIC_KycRule *kyc_rule,
+  const char *measure_name,
   struct TALER_KYCLOGIC_Plugin **plugin,
   struct TALER_KYCLOGIC_ProviderDetails **pd,
   const char **configuration_section);
@@ -326,6 +282,10 @@ TALER_KYCLOGIC_lookup_logic (
   const char **configuration_section);
 
 
+// FIXME: we probably want to instead have some
+// functionality that returns information that
+// is more directly applicable for /keys or /config
+// and not this:
 /**
  * Obtain array of KYC checks provided by the provider
  * configured in @a section_name.
@@ -341,4 +301,5 @@ TALER_KYCLOGIC_lookup_checks (
   unsigned int *num_checks,
   char ***provided_checks);
 
+
 #endif
diff --git a/src/kyclogic/kyclogic_api.c b/src/kyclogic/kyclogic_api.c
index da8398247..1efb23636 100644
--- a/src/kyclogic/kyclogic_api.c
+++ b/src/kyclogic/kyclogic_api.c
@@ -232,9 +232,72 @@ struct TALER_KYCLOGIC_KycRule
    */
   bool exposed;
 
+  /**
+   * True if any of the measures is 'verboten' and
+   * thus this rule cannot ever be satisfied.
+   */
+  bool verboten;
+
 };
 
 
+/**
+ * Set of rules that applies to an account.
+ */
+struct TALER_KYCLOGIC_LegitimizationRuleSet
+{
+
+  /**
+   * When does this rule set expire?
+   */
+  struct GNUNET_TIME_Timestamp expiration_time;
+
+  /**
+   * Name of the successor measure after expiration.
+   * NULL for default rules.
+   */
+  char *successor_measure;
+
+  /**
+   * Array of the rules.
+   */
+  struct TALER_KYCLOGIC_KycRule **kyc_rules;
+
+  /**
+   * Array of custom measures the @e kyc_rules may refer
+   * to.
+   */
+  struct TALER_KYCLOGIC_Measures *custom_measures;
+
+  /**
+   * Length of the @e kyc_rules array.
+   */
+  unsigned int num_kyc_rules;
+
+  /**
+   * Length of the @e custom_measures array.
+   */
+  unsigned int num_custom_measures;
+};
+
+
+enum GNUNET_GenericReturnValue
+TALER_KYCLOGIC_rules_parse (
+  const json_t *jrules,
+  struct TALER_KYCLOGIC_KycRuleSet *rules)
+{
+  // FIXME!
+  return GNUNET_SYSERR;
+}
+
+
+void
+TALER_KYCLOGIC_rules_free (struct TALER_KYCLOGIC_KycRuleSet *krs)
+{
+  // fIXME
+}
+
+
 /**
  * AML programs.
  */
@@ -317,15 +380,9 @@ static struct TALER_KYCLOGIC_KycCheck **kyc_checks;
 static unsigned int num_kyc_checks;
 
 /**
- * Array of configured rules that apply if we do
- * not have an AMLA record.
- */
-static struct TALER_KYCLOGIC_KycRule **kyc_rules;
-
-/**
- * Length of the #kyc_rules array.
+ * Rules that apply if we do not have an AMLA record.
  */
-static unsigned int num_kyc_rules;
+static struct TALER_KYCLOGIC_KycRuleSet default_rules;
 
 /**
  * Array of available AML programs.

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]