gnunet-svn
[Top][All Lists]
Advanced

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

[taler-donau] branch master updated: [lib] add submit


From: gnunet
Subject: [taler-donau] branch master updated: [lib] add submit
Date: Wed, 24 Apr 2024 12:13:17 +0200

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

lukas-matyja pushed a commit to branch master
in repository donau.

The following commit(s) were added to refs/heads/master by this push:
     new 65024d3  [lib] add submit
65024d3 is described below

commit 65024d3bd34b337fe7c8a21c58b8d822311f3664
Author: Matyja Lukas Adam <lukas.matyja@students.bfh.ch>
AuthorDate: Wed Apr 24 12:14:03 2024 +0200

    [lib] add submit
---
 src/donau/donau-httpd_keys.c              | 272 +++++++++++++++---------------
 src/include/donau_service.h               |   8 +-
 src/include/donaudb_plugin.h              |  10 +-
 src/lib/Makefile.am                       |   7 +-
 src/lib/donau_api_batch_submit_receipts.c | 253 +++++++++++++++++++++++++++
 5 files changed, 404 insertions(+), 146 deletions(-)

diff --git a/src/donau/donau-httpd_keys.c b/src/donau/donau-httpd_keys.c
index ec3c94c..a788516 100644
--- a/src/donau/donau-httpd_keys.c
+++ b/src/donau/donau-httpd_keys.c
@@ -1293,142 +1293,142 @@ DH_keys_donation_unit_batch_sign (
   const struct DONAU_BkpSignData bkps[num_bkps],
   struct DONAU_BlindedDonationUnitSignature du_sigs[num_bkps])
 {
-  // struct DH_KeyStateHandle *ksh;
-  // struct DH_DonationUnitKey *du;
-  // struct TALER_CRYPTO_RsaSignRequest rsrs[num_bkps];
-  // struct TALER_CRYPTO_CsSignRequest csrs[num_bkps];
-  // struct TALER_BlindedDenominationSignature rs[num_bkps];
-  // struct TALER_BlindedDenominationSignature cs[num_bkps];
-  // unsigned int rsrs_pos = 0;
-  // unsigned int csrs_pos = 0;
-  // enum TALER_ErrorCode ec;
-
-  // ksh = DH_keys_get_state ();
-  // if (NULL == ksh)
-  //   // FIXME change error code
-  //   return TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING;
-  // for (unsigned int i = 0; i<num_bkps; i++)
-  // {
-  //   const struct DONAU_DonationUnitHashP *h_du_pub =
-  //     bkps[i].h_donation_unit_pub;
-  //   const struct DONAU_BlindedUniqueDonorIdentifier *budi = bkps[i].budi;
-
-  //   du = GNUNET_CONTAINER_multihashmap_get (du_keys,
-  //                                           &h_du_pub->hash);
-  //   if (NULL == du)
-  //     // FIXME change error code
-  //     return TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN;
-  //   if (budi->blinded_message->cipher !=
-  //       du->donation_unit_pub.bsign_pub_key->cipher)
-  //     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
-  //   switch (du->donation_unit_pub.bsign_pub_key->cipher)
-  //   {
-  //   case GNUNET_CRYPTO_BSA_RSA:
-  //     /* See DONAU_donation_unit_pub_hash: we guarantee that these
-  //  hashes are equivalent! */
-  //     rsrs[rsrs_pos].h_rsa
-  //       = (const struct TALER_RsaPubHashP *) &du->h_donation_unit_pub;
-  //     rsrs[rsrs_pos].msg
-  //       = budi->blinded_message->details.rsa_blinded_message.blinded_msg;
-  //     rsrs[rsrs_pos].msg_size
-  //       = 
budi->blinded_message->details.rsa_blinded_message.blinded_msg_size;
-  //     rsrs_pos++;
-  //     break;
-  //   case GNUNET_CRYPTO_BSA_CS:
-  //     /* See DONAU_donation_unit_pub_hash: we guarantee that these
-  //  hashes are equivalent! */
-  //     csrs[csrs_pos].h_cs
-  //       = (const struct TALER_CsPubHashP *) &du->h_donation_unit_pub;
-  //     csrs[csrs_pos].blinded_planchet
-  //       = &budi->blinded_message->details.cs_blinded_message;
-  //     csrs_pos++;
-  //     break;
-  //   default:
-  //     return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
-  //   }
-  // }
-
-  // if ( (0 != csrs_pos) &&
-  //      (0 != rsrs_pos) )
-  // {
-  //   memset (rs,
-  //           0,
-  //           sizeof (rs));
-  //   memset (cs,
-  //           0,
-  //           sizeof (cs));
-  // }
-  // ec = TALER_EC_NONE;
-  // if (0 != csrs_pos)
-  // {
-  //   ec = TALER_CRYPTO_helper_cs_batch_sign (
-  //     csdh,
-  //     csrs_pos,
-  //     csrs,
-  //     false, // for_melt
-  //     cs);
-  //   if (TALER_EC_NONE != ec)
-  //   {
-  //     for (unsigned int i = 0; i<csrs_pos; i++)
-  //     {
-  //       if (NULL != cs[i].blinded_sig)
-  //       {
-  //         GNUNET_CRYPTO_blinded_sig_decref (cs[i].blinded_sig);
-  //         cs[i].blinded_sig = NULL;
-  //       }
-  //     }
-  //     return ec;
-  //   }
-  //   // TEH_METRICS_num_signatures[TEH_MT_SIGNATURE_CS] += csrs_pos;
-  // }
-  // if (0 != rsrs_pos)
-  // {
-  //   ec = TALER_CRYPTO_helper_rsa_batch_sign (
-  //     rsadh,
-  //     rsrs_pos,
-  //     rsrs,
-  //     rs);
-  //   if (TALER_EC_NONE != ec)
-  //   {
-  //     for (unsigned int i = 0; i<csrs_pos; i++)
-  //     {
-  //       if (NULL != cs[i].blinded_sig)
-  //       {
-  //         GNUNET_CRYPTO_blinded_sig_decref (cs[i].blinded_sig);
-  //         cs[i].blinded_sig = NULL;
-  //       }
-  //     }
-  //     for (unsigned int i = 0; i<rsrs_pos; i++)
-  //     {
-  //       if (NULL != rs[i].blinded_sig)
-  //       {
-  //         GNUNET_CRYPTO_blinded_sig_decref (rs[i].blinded_sig);
-  //         rs[i].blinded_sig = NULL;
-  //       }
-  //     }
-  //     return ec;
-  //   }
-  //   // TEH_METRICS_num_signatures[TEH_MT_SIGNATURE_RSA] += rsrs_pos;
-  // }
-
-  // rsrs_pos = 0;
-  // csrs_pos = 0;
-  // for (unsigned int i = 0; i<num_bkps; i++)
-  // {
-  //   const struct DONAU_BlindedUniqueDonorIdentifier *budi = bkps[i].budi;
-
-  //   switch (budi->blinded_message->cipher)
-  //   {
-  //   case GNUNET_CRYPTO_BSA_RSA:
-  //     du_sigs[i].blinded_sig = rs[rsrs_pos++].blinded_sig;
-  //     break;
-  //   case GNUNET_CRYPTO_BSA_CS:
-  //     du_sigs[i].blinded_sig = cs[csrs_pos++].blinded_sig;
-  //     break;
-  //   default:
-  //     GNUNET_assert (0);
-  //   }
-  // }
+  struct DH_KeyStateHandle *ksh;
+  struct DH_DonationUnitKey *du;
+  struct TALER_CRYPTO_RsaSignRequest rsrs[num_bkps];
+  struct TALER_CRYPTO_CsSignRequest csrs[num_bkps];
+  struct TALER_BlindedDenominationSignature rs[num_bkps];
+  struct TALER_BlindedDenominationSignature cs[num_bkps];
+  unsigned int rsrs_pos = 0;
+  unsigned int csrs_pos = 0;
+  enum TALER_ErrorCode ec;
+
+  ksh = DH_keys_get_state ();
+  if (NULL == ksh)
+    // FIXME change error code
+    return TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING;
+  for (unsigned int i = 0; i<num_bkps; i++)
+  {
+    const struct DONAU_DonationUnitHashP *h_du_pub =
+      bkps[i].h_donation_unit_pub;
+    const struct DONAU_BlindedUniqueDonorIdentifier *budi = bkps[i].budi;
+
+    du = GNUNET_CONTAINER_multihashmap_get (du_keys,
+                                            &h_du_pub->hash);
+    if (NULL == du)
+      // FIXME change error code
+      return TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN;
+    if (budi->blinded_message->cipher !=
+        du->donation_unit_pub.bsign_pub_key->cipher)
+      return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
+    switch (du->donation_unit_pub.bsign_pub_key->cipher)
+    {
+    case GNUNET_CRYPTO_BSA_RSA:
+      /* See DONAU_donation_unit_pub_hash: we guarantee that these
+   hashes are equivalent! */
+      rsrs[rsrs_pos].h_rsa
+        = (const struct TALER_RsaPubHashP *) &du->h_donation_unit_pub;
+      rsrs[rsrs_pos].msg
+        = budi->blinded_message->details.rsa_blinded_message.blinded_msg;
+      rsrs[rsrs_pos].msg_size
+        = budi->blinded_message->details.rsa_blinded_message.blinded_msg_size;
+      rsrs_pos++;
+      break;
+    case GNUNET_CRYPTO_BSA_CS:
+      /* See DONAU_donation_unit_pub_hash: we guarantee that these
+   hashes are equivalent! */
+      csrs[csrs_pos].h_cs
+        = (const struct TALER_CsPubHashP *) &du->h_donation_unit_pub;
+      csrs[csrs_pos].blinded_planchet
+        = &budi->blinded_message->details.cs_blinded_message;
+      csrs_pos++;
+      break;
+    default:
+      return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
+    }
+  }
+
+  if ( (0 != csrs_pos) &&
+       (0 != rsrs_pos) )
+  {
+    memset (rs,
+            0,
+            sizeof (rs));
+    memset (cs,
+            0,
+            sizeof (cs));
+  }
+  ec = TALER_EC_NONE;
+  if (0 != csrs_pos)
+  {
+    ec = TALER_CRYPTO_helper_cs_batch_sign (
+      csdh,
+      csrs_pos,
+      csrs,
+      false, // for_melt
+      cs);
+    if (TALER_EC_NONE != ec)
+    {
+      for (unsigned int i = 0; i<csrs_pos; i++)
+      {
+        if (NULL != cs[i].blinded_sig)
+        {
+          GNUNET_CRYPTO_blinded_sig_decref (cs[i].blinded_sig);
+          cs[i].blinded_sig = NULL;
+        }
+      }
+      return ec;
+    }
+    // TEH_METRICS_num_signatures[TEH_MT_SIGNATURE_CS] += csrs_pos;
+  }
+  if (0 != rsrs_pos)
+  {
+    ec = TALER_CRYPTO_helper_rsa_batch_sign (
+      rsadh,
+      rsrs_pos,
+      rsrs,
+      rs);
+    if (TALER_EC_NONE != ec)
+    {
+      for (unsigned int i = 0; i<csrs_pos; i++)
+      {
+        if (NULL != cs[i].blinded_sig)
+        {
+          GNUNET_CRYPTO_blinded_sig_decref (cs[i].blinded_sig);
+          cs[i].blinded_sig = NULL;
+        }
+      }
+      for (unsigned int i = 0; i<rsrs_pos; i++)
+      {
+        if (NULL != rs[i].blinded_sig)
+        {
+          GNUNET_CRYPTO_blinded_sig_decref (rs[i].blinded_sig);
+          rs[i].blinded_sig = NULL;
+        }
+      }
+      return ec;
+    }
+    // TEH_METRICS_num_signatures[TEH_MT_SIGNATURE_RSA] += rsrs_pos;
+  }
+
+  rsrs_pos = 0;
+  csrs_pos = 0;
+  for (unsigned int i = 0; i<num_bkps; i++)
+  {
+    const struct DONAU_BlindedUniqueDonorIdentifier *budi = bkps[i].budi;
+
+    switch (budi->blinded_message->cipher)
+    {
+    case GNUNET_CRYPTO_BSA_RSA:
+      du_sigs[i].blinded_sig = rs[rsrs_pos++].blinded_sig;
+      break;
+    case GNUNET_CRYPTO_BSA_CS:
+      du_sigs[i].blinded_sig = cs[csrs_pos++].blinded_sig;
+      break;
+    default:
+      GNUNET_assert (0);
+    }
+  }
   return TALER_EC_NONE;
 }
 
diff --git a/src/include/donau_service.h b/src/include/donau_service.h
index 127c39c..213d84f 100644
--- a/src/include/donau_service.h
+++ b/src/include/donau_service.h
@@ -643,6 +643,8 @@ typedef void
  * @param url donau base URL
  * @param num_drs length of the @a drs array
  * @param drs array with details about the donation receipts
+ * @param year corresponding year
+ * @param h_tax_id salted and hashed tax id
  * @param cb the callback to call when a reply for this request is available
  * @param cls closure for the above callback
  * @param[out] ec if NULL is returned, set to the error code explaining why 
the operation failed
@@ -653,8 +655,10 @@ struct DONAU_DonorReceiptsToStatementHandle *
 DONAU_donor_receipts_to_statement (
   struct GNUNET_CURL_Context *ctx,
   const char *url,
-  unsigned int num_drs,
-  const struct DONAU_DonationReceipt drs[static num_drs],
+  const size_t num_drs,
+  const struct DONAU_DonationReceipt drs[num_drs],
+  const uint64_t year,
+  const struct DONAU_HashDonorTaxId *h_tax_id,
   DONAU_DonorReceiptsToStatementResultCallback cb,
   void *cls);
 
diff --git a/src/include/donaudb_plugin.h b/src/include/donaudb_plugin.h
index 3fd3f48..5f12c16 100644
--- a/src/include/donaudb_plugin.h
+++ b/src/include/donaudb_plugin.h
@@ -528,11 +528,11 @@ struct DONAUDB_Plugin
     */
   enum GNUNET_DB_QueryStatus
   (*insert_submitted_receipts)(
-  void *cls,
-  const struct DONAU_HashDonorTaxId *h_tax_number,
-  const struct DONAU_DonationReceipt *donation_receipts,
-  const size_t num_dr,
-  const uint64_t donation_year);
+    void *cls,
+    const struct DONAU_HashDonorTaxId *h_tax_number,
+    const struct DONAU_DonationReceipt *donation_receipts,
+    const size_t num_dr,
+    const uint64_t donation_year);
 
   /**
   * Lookup issued receipts from the charity.
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
index 0679a90..31ad82e 100644
--- a/src/lib/Makefile.am
+++ b/src/lib/Makefile.am
@@ -25,9 +25,10 @@ libdonau_la_SOURCES = \
   donau_api_charity_post.c \
   donau_api_charity_delete.c \
   donau_api_charities_get.c \
-  donau_api_curl_defaults.c donau_api_curl_defaults.h \
-  donau_api_batch_issue_receipts.c   
- 
+  donau_api_curl_defaults.c donau_api_curl_defaults.h \ 
+  donau_api_batch_issue_receipts.c \  
+ # donau_api_batch_submit_receipts.c  
+
 ## maybe need libtalercurl
 libdonau_la_LIBADD = \
        $(top_builddir)/src/util/libdonauutil.la \
diff --git a/src/lib/donau_api_batch_submit_receipts.c 
b/src/lib/donau_api_batch_submit_receipts.c
new file mode 100644
index 0000000..a71a09d
--- /dev/null
+++ b/src/lib/donau_api_batch_submit_receipts.c
@@ -0,0 +1,253 @@
+/*
+  This file is part of TALER
+  Copyright (C) 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 lib/donau_api_batch_submit_receipts.c
+ * @brief Implementation of the "handle" component of the donau's HTTP API
+ * @author Lukas Matyja
+ */
+#include <gnunet/gnunet_curl_lib.h>
+#include <taler/taler_json_lib.h>
+#include <taler/taler_curl_lib.h>
+#include "donau_service.h"
+#include "donau_util.h"
+#include "donau_api_curl_defaults.h"
+#include "donau_json_lib.h"
+
+
+/**
+ * Handle for a POST /submit request.
+ */
+struct DONAU_DonorReceiptsToStatementHandle
+{
+  /**
+   * The url for the /submit request.
+   */
+  char *url;
+
+  /**
+   * Minor context that holds body and headers.
+   */
+  struct TALER_CURL_PostContext post_ctx;
+
+  /**
+   * Entry for this request with the `struct GNUNET_CURL_Context`.
+   */
+  struct GNUNET_CURL_Job *job;
+
+  /**
+   * Function to call with the result.
+   */
+  DONAU_DonorReceiptsToStatementResultCallback cb;
+
+  /**
+   * BUDI-key-pair signature.
+   */
+  struct DONAU_CharitySignatureP charity_sig;
+
+  /**
+   * Closure to pass to @e cb.
+   */
+  void *cb_cls;
+
+  /**
+   * Reference to the execution context.
+   */
+  struct GNUNET_CURL_Context *ctx;
+
+};
+
+/**
+ * Transform submit receipt request into JSON.
+ *
+ * @param num_drs number of donation receipts in @drs
+ * @param drs donation receipts array
+ * @param year corresponding year
+ * @param h_tax_id salted and hashed tax id
+ */
+json_t *
+submit_request_body_to_json (const size_t num_drs,
+                            const struct
+                                                       DONAU_DonationReceipt 
drs[num_drs],
+                            const uint64_t year,
+                            const struct DONAU_HashDonorTaxId *h_tax_id)
+{
+  json_t *donation_receipts = json_array ();
+  GNUNET_assert (NULL != donation_receipts);
+
+  for (size_t i = 0; i < num_drs; i++)
+  {
+    json_t *receipt = GNUNET_JSON_PACK (
+      GNUNET_JSON_pack_data_auto ("h_donation_unit_pub",
+                 &drs[i]->h_donation_unit_pub),
+         GNUNET_JSON_pack_data_auto ("nonce",
+                         &drs[i]->nonce),
+         GNUNET_JSON_pack_data_auto ("donau_sig",
+                         &drs[i]->donau_sig));
+    GNUNET_assert (0 ==
+                   json_array_append_new (donation_receipts,
+                                  receipt));
+  }
+  return GNUNET_JSON_PACK (
+    GNUNET_JSON_pack_array_steal ("donation_receipts",
+               donation_receipts),
+    GNUNET_JSON_pack_data_auto ("h_tax_number",
+                                                       h_tax_id),
+    GNUNET_JSON_pack_uint64 ("donation_year",
+                             year));
+}
+
+
+/**
+ * Function called when we're done processing the
+ * HTTP POST /submit request.
+ *
+ * @param cls the `struct KeysRequest`
+ * @param response_code HTTP response code, 0 on error
+ * @param resp_obj parsed JSON result, NULL on error
+ */
+static void
+handle_batch_submit_finished (void *cls,
+                             long response_code,
+                             const void *resp_obj)
+{
+  struct DONAU_DonorReceiptsToStatementHandle *birh = cls;
+  const json_t *j = resp_obj;
+
+  struct DONAU_DonorReceiptsToStatementResult biresp = {
+    .hr.reply = j,
+    .hr.http_status = (unsigned int) response_code
+  };
+
+  birh->job = NULL;
+  switch (response_code)
+  {
+  case MHD_HTTP_CREATED:
+    break;
+  case MHD_HTTP_NO_CONTENT:
+    biresp.hr.ec = TALER_JSON_get_error_code (j);
+    biresp.hr.hint = TALER_JSON_get_error_hint (j);
+    break;
+  case MHD_HTTP_FORBIDDEN:
+    biresp.hr.ec = TALER_JSON_get_error_code (j);
+    biresp.hr.hint = TALER_JSON_get_error_hint (j);
+    break;
+  case MHD_HTTP_NOT_FOUND:
+    biresp.hr.ec = TALER_JSON_get_error_code (j);
+    biresp.hr.hint = TALER_JSON_get_error_hint (j);
+    break;
+  case MHD_HTTP_CONTENT_TOO_LARGE:
+    biresp.hr.ec = TALER_JSON_get_error_code (j);
+    biresp.hr.hint = TALER_JSON_get_error_hint (j);
+    break;
+  default:
+    /* unexpected response code */
+    GNUNET_break_op (0);
+    biresp.hr.ec = TALER_JSON_get_error_code (j);
+    biresp.hr.hint = TALER_JSON_get_error_hint (j);
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Unexpected response code %u/%d for POST %s\n",
+                (unsigned int) response_code,
+                (int) biresp.hr.ec,
+                birh->url);
+    break;
+  }
+  if (NULL != birh->cb)
+  {
+    birh->cb (birh->cb_cls,
+              &biresp);
+    birh->cb = NULL;
+  }
+  DONAU_donor_receipts_to_statement_cancel (birh);
+}
+
+
+struct DONAU_DonorReceiptsToStatementHandle *
+DONAU_donor_receipts_to_statement (
+                 struct GNUNET_CURL_Context *ctx,
+                 const char *url,
+                 const size_t num_drs,
+                 const struct DONAU_DonationReceipt drs[num_drs],
+                 const uint64_t year,
+                 const struct DONAU_HashDonorTaxId *h_tax_id,
+                 DONAU_DonorReceiptsToStatementResultCallback cb,
+                 void *cls)
+{
+  struct DONAU_DonorReceiptsToStatementHandle *birh;
+  birh = GNUNET_new (struct DONAU_DonorReceiptsToStatementHandle);
+  CURL *eh;
+  json_t *body;
+
+  TALER_LOG_DEBUG ("Connecting to the donau (%s)\n",
+                   url);
+  birh->url = GNUNET_strdup (url);
+  birh->cb = cb;
+  birh->cb_cls = cls;
+  birh->ctx = ctx;
+  birh->url = TALER_url_join (url,
+                                                         "submit",
+                              NULL);
+  if (NULL == birh->url)
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                "Could not construct request URL.\n");
+    GNUNET_free (birh);
+    return NULL;
+  }
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "submit_receipts_with_URL `%s'.\n",
+              birh->url);
+  body = submit_request_body_to_json (num_drs, drs, year, h_tax_id);
+  eh = DONAU_curl_easy_get_ (birh->url);
+  if ( (NULL == eh) ||
+       (GNUNET_OK !=
+        TALER_curl_easy_post (&birh->post_ctx,
+                              eh,
+                              body)) )
+  {
+    GNUNET_break (0);
+    if (NULL != eh)
+      curl_easy_cleanup (eh);
+    json_decref (body);
+    GNUNET_free (birh->url);
+    return NULL;
+  }
+  json_decref (body);
+  birh->job = GNUNET_CURL_job_add2 (ctx,
+                                    eh,
+                                    birh->post_ctx.headers,
+                                    &handle_batch_submit_finished,
+                                    birh);
+  return birh;
+}
+
+
+void
+DONAU_donor_receipts_to_statement_cancel (
+  struct DONAU_DonorReceiptsToStatementHandle *drsh)
+{
+  if (NULL != drsh->job)
+  {
+    GNUNET_CURL_job_cancel (drsh->job);
+    drsh->job = NULL;
+  }
+  TALER_curl_easy_post_finished (&drsh->post_ctx);
+  GNUNET_free (drsh->url);
+  GNUNET_free (drsh);
+}

-- 
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]