commit 2d9148508662517464efd36116fc5e48d7118d1e
Author: Martin Schanzenbach <schanzen@gnunet.org>
Date: Sat, 30 Sep 2023 09:07:10 +0200
Initial commit
Diffstat:
4 files changed, 1174 insertions(+), 0 deletions(-)
diff --git a/meson.build b/meson.build
@@ -0,0 +1,10 @@
+project('gns-registrar', 'c')
+gnunetdeps = [dependency('libmicrohttpd'),
+ dependency('gnunetutil'),
+ dependency('gnunetnamestore'),
+ dependency('jansson'),
+ dependency('gnunetjson'),
+ dependency('gnunetgnsrecord'),
+ dependency('gnunetidentity'),
+ dependency('gnunetgns')]
+subdir('src')
diff --git a/src/meson.build b/src/meson.build
@@ -0,0 +1 @@
+subdir('service')
diff --git a/src/service/gnunet-namestore-fcfsd.c b/src/service/gnunet-namestore-fcfsd.c
@@ -0,0 +1,1160 @@
+/*
+ This file is part of GNUnet.
+ Copyright (C) 2012-2021 GNUnet e.V.
+
+ GNUnet is free software: you can redistribute it and/or modify it
+ under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ GNUnet 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
+ Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ SPDX-License-Identifier: AGPL3.0-or-later
+ */
+
+/**
+ * @file gnunet-namestore-fcfsd.c
+ * @brief HTTP daemon that offers first-come-first-serve GNS domain registration
+ * @author Christian Grothoff
+ */
+
+#include <microhttpd.h>
+#include "gnunet/platform.h"
+#include "gnunet/gnunet_util_lib.h"
+#include "gnunet/gnunet_identity_service.h"
+#include "gnunet/gnunet_gnsrecord_lib.h"
+#include "gnunet/gnunet_namestore_service.h"
+#include "gnunet/gnunet_mhd_compat.h"
+#include "gnunet/gnunet_json_lib.h"
+
+/**
+ * Structure representing a static page.
+ * "Static" means that the server does not process the page before sending it
+ * to the client. Clients can still process the received data, for example
+ * because there are scripting elements within.
+ */
+struct StaticPage
+{
+ /**
+ * Handle to file on disk.
+ */
+ struct GNUNET_DISK_FileHandle *handle;
+
+ /**
+ * Size in bytes of the file.
+ */
+ uint64_t size;
+
+ /**
+ * Cached response object to send to clients.
+ */
+ struct MHD_Response *response;
+};
+
+/**
+ * Structure containing some request-specific data.
+ */
+struct RequestData
+{
+ /**
+ * The connection this request was sent in.
+ */
+ struct MHD_Connection *c;
+
+ /**
+ * Body of the response object.
+ */
+ char *body;
+
+ /**
+ * Length in bytes of the body.
+ */
+ size_t body_length;
+
+ /**
+ * Response code.
+ */
+ int code;
+
+ /**
+ * Task started to search for an entry in the namestore.
+ */
+ struct GNUNET_NAMESTORE_QueueEntry *searching;
+
+ /**
+ * Task started to iterate over the namestore.
+ */
+ struct GNUNET_NAMESTORE_ZoneIterator *iterating;
+
+ /**
+ * Pointer used while processing POST data.
+ */
+ void *ptr;
+
+ /**
+ * Name requested to be registered.
+ */
+ char *register_name;
+
+ /**
+ * Key (encoded as a string) to be associated with the requested name.
+ */
+ char *register_key;
+
+ /**
+ * Key to be associated with the requested name.
+ */
+ struct GNUNET_IDENTITY_PublicKey key;
+};
+
+/**
+ * Name of the zone being managed.
+ */
+static char *zone = NULL;
+
+/**
+ * The port the daemon is listening to for HTTP requests.
+ */
+static unsigned long long port = 18080;
+
+/**
+ * Connection with the namestore service.
+ */
+static struct GNUNET_NAMESTORE_Handle *namestore = NULL;
+
+/**
+ * Connection with the identity service.
+ */
+static struct GNUNET_IDENTITY_Handle *identity = NULL;
+
+/**
+ * Private key of the zone.
+ */
+static const struct GNUNET_IDENTITY_PrivateKey *zone_key = NULL;
+
+/**
+ * The HTTP daemon.
+ */
+static struct MHD_Daemon *httpd = NULL;
+
+/**
+ * Task executing the HTTP daemon.
+ */
+static struct GNUNET_SCHEDULER_Task *httpd_task = NULL;
+
+/**
+ * The main page, a.k.a. "index.html"
+ */
+static struct StaticPage *main_page = NULL;
+
+/**
+ * Page indicating the requested resource could not be found.
+ */
+static struct StaticPage *notfound_page = NULL;
+
+/**
+ * Page indicating the requested resource could not be accessed, and other
+ * errors.
+ */
+static struct StaticPage *forbidden_page = NULL;
+
+/**
+ * The relative expiration time for added records
+ */
+static struct GNUNET_TIME_Relative record_exp;
+
+/**
+ * Task ran at shutdown to clean up everything.
+ *
+ * @param cls unused
+ */
+static void
+do_shutdown (void *cls)
+{
+ /* We cheat a bit here: the file descriptor is implicitly closed by MHD, so
+ calling `GNUNET_DISK_file_close' would generate a spurious warning message
+ in the log. Since that function does nothing but close the descriptor and
+ free the allocated memory, After destroying the response all that's left to
+ do is call `GNUNET_free'. */
+ if (NULL != main_page)
+ {
+ MHD_destroy_response (main_page->response);
+ GNUNET_free (main_page->handle);
+ GNUNET_free (main_page);
+ }
+ if (NULL != notfound_page)
+ {
+ MHD_destroy_response (notfound_page->response);
+ GNUNET_free (notfound_page->handle);
+ GNUNET_free (notfound_page);
+ }
+ if (NULL != forbidden_page)
+ {
+ MHD_destroy_response (forbidden_page->response);
+ GNUNET_free (forbidden_page->handle);
+ GNUNET_free (forbidden_page);
+ }
+
+ if (NULL != namestore)
+ {
+ GNUNET_NAMESTORE_disconnect (namestore);
+ }
+
+ if (NULL != identity)
+ {
+ GNUNET_IDENTITY_disconnect (identity);
+ }
+
+ if (NULL != httpd_task)
+ {
+ GNUNET_SCHEDULER_cancel (httpd_task);
+ }
+ if (NULL != httpd)
+ {
+ MHD_stop_daemon (httpd);
+ }
+}
+
+
+/**
+ * Called when the HTTP server has some pending operations.
+ *
+ * @param cls unused
+ */
+static void
+do_httpd (void *cls);
+
+/**
+ * Schedule a task to run MHD.
+ */
+static void
+run_httpd (void)
+{
+ fd_set rs;
+ fd_set ws;
+ fd_set es;
+
+ struct GNUNET_NETWORK_FDSet *grs = GNUNET_NETWORK_fdset_create ();
+ struct GNUNET_NETWORK_FDSet *gws = GNUNET_NETWORK_fdset_create ();
+ struct GNUNET_NETWORK_FDSet *ges = GNUNET_NETWORK_fdset_create ();
+
+ FD_ZERO (&rs);
+ FD_ZERO (&ws);
+ FD_ZERO (&es);
+
+ int max = -1;
+ GNUNET_assert (MHD_YES == MHD_get_fdset (httpd, &rs, &ws, &es, &max));
+
+ unsigned MHD_LONG_LONG timeout = 0;
+ struct GNUNET_TIME_Relative gtime = GNUNET_TIME_UNIT_FOREVER_REL;
+ if (MHD_YES == MHD_get_timeout (httpd, &timeout))
+ {
+ gtime = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
+ timeout);
+ }
+
+ GNUNET_NETWORK_fdset_copy_native (grs, &rs, max + 1);
+ GNUNET_NETWORK_fdset_copy_native (gws, &ws, max + 1);
+ GNUNET_NETWORK_fdset_copy_native (ges, &es, max + 1);
+
+ httpd_task = GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_HIGH,
+ gtime,
+ grs,
+ gws,
+ &do_httpd,
+ NULL);
+ GNUNET_NETWORK_fdset_destroy (grs);
+ GNUNET_NETWORK_fdset_destroy (gws);
+ GNUNET_NETWORK_fdset_destroy (ges);
+}
+
+
+/**
+ * Called when the HTTP server has some pending operations.
+ *
+ * @param cls unused
+ */
+static void
+do_httpd (void *cls)
+{
+ httpd_task = NULL;
+ MHD_run (httpd);
+ run_httpd ();
+}
+
+
+static void
+run_httpd_now (void)
+{
+ if (NULL != httpd_task)
+ {
+ GNUNET_SCHEDULER_cancel (httpd_task);
+ httpd_task = NULL;
+ }
+ httpd_task = GNUNET_SCHEDULER_add_now (&do_httpd, NULL);
+}
+
+
+/**
+ * Generate a JSON object.
+ *
+ * @param key the key for the first element
+ * @param value the value for the first element
+ * @param ... key-value pairs of the object, terminated by NULL
+ * @return a JSON string (allocated)
+ */
+static char *
+make_json (const char *key, const char *value, ...)
+{
+ va_list args;
+ va_start (args, value);
+
+ json_t *obj = NULL;
+
+ obj = json_object ();
+ if ((NULL == key) || (NULL == value))
+ {
+ va_end (args);
+ return json_dumps (obj, JSON_COMPACT);
+ }
+
+ json_object_set (obj, key, json_string (value));
+
+ char *k = va_arg (args, char *);
+ if (NULL == k)
+ {
+ va_end (args);
+ return json_dumps (obj, JSON_COMPACT);
+ }
+ char *v = va_arg (args, char *);
+ if (NULL == v)
+ {
+ va_end (args);
+ return json_dumps (obj, JSON_COMPACT);
+ }
+
+ while (NULL != k && NULL != v)
+ {
+ json_object_set (obj, k, json_string (v));
+ k = va_arg (args, char *);
+ if (NULL != k)
+ {
+ v = va_arg (args, char *);
+ }
+ }
+
+ va_end (args);
+
+ char *json = json_dumps (obj, JSON_COMPACT);
+ json_decref (obj);
+
+ return json;
+}
+
+
+/**
+ * The namestore search task failed.
+ *
+ * @param cls the request data
+ */
+static void
+search_error_cb (void *cls)
+{
+ struct RequestData *rd = cls;
+ MHD_resume_connection (rd->c);
+ rd->searching = NULL;
+ rd->body = make_json ("error", "true",
+ "message", _ ("can not search the namestore"),
+ NULL);
+ rd->body_length = strlen (rd->body);
+ rd->code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ run_httpd_now ();
+}
+
+
+/**
+ * The lookup terminated with some results.
+ *
+ * @param cls closure
+ * @param zone the private key of the zone
+ * @param label the result label
+ * @param count number of records found
+ * @param d records found
+ */
+static void
+search_done_cb (void *cls,
+ const struct GNUNET_IDENTITY_PrivateKey *zone,
+ const char *label,
+ unsigned int count,
+ const struct GNUNET_GNSRECORD_Data *d)
+{
+ (void) zone;
+ (void) d;
+
+ struct RequestData *rd = cls;
+ MHD_resume_connection (rd->c);
+
+ rd->searching = NULL;
+ rd->body = make_json ("error", "false",
+ "free", (0 == count) ? "true" : "false",
+ NULL);
+ rd->body_length = strlen (rd->body);
+ rd->code = MHD_HTTP_OK;
+
+ run_httpd_now ();
+}
+
+
+/**
+ * An error occurred while registering a name.
+ *
+ * @param cls the connection
+ */
+static void
+register_error_cb (void *cls)
+{
+ struct RequestData *rd = cls;
+
+ MHD_resume_connection (rd->c);
+ rd->searching = NULL;
+ rd->body = make_json ("error", "true",
+ "message", _ ("unable to scan namestore"),
+ NULL);
+ rd->body_length = strlen (rd->body);
+ rd->code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ run_httpd_now ();
+}
+
+
+static void
+register_done_cb (void *cls,
+ enum GNUNET_ErrorCode ec)
+{
+ struct RequestData *rd = cls;
+
+ MHD_resume_connection (rd->c);
+ rd->searching = NULL;
+
+ if (GNUNET_EC_NONE != ec)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ _ ("Failed to create record for `%s': %s\n"),
+ rd->register_name,
+ GNUNET_ErrorCode_get_hint (ec));
+ rd->body = make_json ("error", "true",
+ "message",
+ GNUNET_ErrorCode_get_hint (ec),
+ NULL);
+ rd->body_length = strlen (rd->body);
+ rd->code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ }
+ else
+ {
+ rd->body = make_json ("error", "false",
+ "message", _ ("no errors"),
+ NULL);
+ rd->body_length = strlen (rd->body);
+ rd->code = MHD_HTTP_OK;
+ }
+
+ run_httpd_now ();
+}
+
+
+/**
+ * Attempt to register the requested name.
+ *
+ * @param cls the connection
+ * @param key the zone key
+ * @param label name of the record
+ * @param count number of records found
+ * @param d records
+ */
+static void
+register_do_cb (void *cls,
+ const struct GNUNET_IDENTITY_PrivateKey *key,
+ const char *label,
+ unsigned int count,
+ const struct GNUNET_GNSRECORD_Data *d)
+{
+ (void) key;
+ (void) d;
+
+ struct RequestData *rd = cls;
+
+ rd->searching = NULL;
+
+ if (0 != count)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ _ ("The requested key `%s' exists as `%s'\n"),
+ rd->register_key,
+ label);
+
+ MHD_resume_connection (rd->c);
+ rd->searching = NULL;
+ rd->body = make_json ("error", "true",
+ "message", _ ("key exists"),
+ NULL);
+ rd->body_length = strlen (rd->body);
+ rd->code = MHD_HTTP_FORBIDDEN;
+ run_httpd_now ();
+ return;
+ }
+
+ struct GNUNET_GNSRECORD_Data gd;
+ char *gdraw = NULL;
+
+ if (GNUNET_OK != GNUNET_GNSRECORD_data_from_identity (&(rd->key),
+ &gdraw,
+ &(gd.data_size),
+ &(gd.record_type)))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ _ ("Error creating record data\n"));
+ MHD_resume_connection (rd->c);
+ rd->searching = NULL;
+ rd->body = make_json ("error", "true",
+ "message", _ ("unable to store record"),
+ NULL);
+ rd->body_length = strlen (rd->body);
+ rd->code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ run_httpd_now ();
+ return;
+ }
+
+ gd.data = gdraw;
+ gd.expiration_time = record_exp.rel_value_us;
+ gd.flags = GNUNET_GNSRECORD_RF_RELATIVE_EXPIRATION;
+
+ rd->searching = GNUNET_NAMESTORE_records_store (namestore,
+ zone_key,
+ rd->register_name,
+ 1,
+ &gd,
+ ®ister_done_cb,
+ rd);
+
+ GNUNET_free (gdraw);
+}
+
+
+/**
+ * An error occurred while iterating the namestore.
+ *
+ * @param cls the connection
+ */
+static void
+iterate_error_cb (void *cls)
+{
+ struct RequestData *rd = cls;
+
+ MHD_resume_connection (rd->c);
+ rd->iterating = NULL;
+ rd->body = make_json ("error", "true",
+ "message", _ ("unable to scan namestore"),
+ NULL);
+ rd->body_length = strlen (rd->body);
+ rd->code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ run_httpd_now ();
+}
+
+
+/**
+ * A block was received from the namestore.
+ *
+ * @param cls the connection
+ * @param key the zone key
+ * @param label the records' label
+ * @param count number of records found
+ * @param d the found records
+ */
+static void
+iterate_do_cb (void *cls,
+ const struct GNUNET_IDENTITY_PrivateKey *key,
+ const char *label,
+ unsigned int count,
+ const struct GNUNET_GNSRECORD_Data *d)
+{
+ (void) key;
+ (void) label;
+ (void) d;
+
+ struct RequestData *rd = cls;
+
+ if (0 == strcmp (label, rd->register_name))
+ {
+ GNUNET_break (0 != count);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ _ ("Requested name `%s' exists with `%u' records\n"),
+ rd->register_name,
+ count);
+
+ MHD_resume_connection (rd->c);
+ rd->body = make_json ("error", "true",
+ "message", _ ("name exists\n"),
+ NULL);
+ rd->body_length = strlen (rd->body);
+ rd->code = MHD_HTTP_FORBIDDEN;
+ GNUNET_NAMESTORE_zone_iteration_stop (rd->iterating);
+ run_httpd_now ();
+ return;
+ }
+
+ GNUNET_NAMESTORE_zone_iterator_next (rd->iterating, 1);
+}
+
+
+/**
+ * All entries in the namestore have been iterated over.
+ *
+ * @param cls the connection
+ */
+static void
+iterate_done_cb (void *cls)
+{
+ struct RequestData *rd = cls;
+
+ rd->iterating = NULL;
+
+ /* See if the key was not registered already */
+ rd->searching = GNUNET_NAMESTORE_zone_to_name (namestore,
+ zone_key,
+ &(rd->key),
+ ®ister_error_cb,
+ rd,
+ ®ister_do_cb,
+ rd);
+}
+
+
+/**
+ * Generate a response containing JSON and send it to the client.
+ *
+ * @param c the connection
+ * @param body the response body
+ * @param length the body length in bytes
+ * @param code the response code
+ * @return MHD_NO on error
+ */
+static MHD_RESULT
+serve_json (struct MHD_Connection *c,
+ char *body,
+ size_t length,
+ int code)
+{
+ struct MHD_Response *response =
+ MHD_create_response_from_buffer (length,
+ body,
+ MHD_RESPMEM_PERSISTENT);
+ MHD_RESULT r = MHD_queue_response (c, code, response);
+ MHD_destroy_response (response);
+ return r;
+}
+
+
+/**
+ * Send a response back to a connected client.
+ *
+ * @param cls unused
+ * @param connection the connection with the client
+ * @param url the requested address
+ * @param method the HTTP method used
+ * @param version the protocol version (including the "HTTP/" part)
+ * @param upload_data data sent with a POST request
+ * @param upload_data_size length in bytes of the POST data
+ * @param ptr used to pass data between request handling phases
+ * @return MHD_NO on error
+ */
+static MHD_RESULT
+create_response (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data,
+ size_t *upload_data_size,
+ void **ptr)
+{
+ (void) cls;
+ (void) version;
+
+ struct RequestData *rd = *ptr;
+
+ if (0 == strcmp (method, MHD_HTTP_METHOD_GET))
+ {
+ /* Handle a previously suspended request */
+ if (NULL != rd)
+ {
+ return serve_json (rd->c, rd->body, rd->body_length, rd->code);
+ }
+
+ if (0 == strcmp ("/", url))
+ {
+ return MHD_queue_response (connection,
+ MHD_HTTP_OK,
+ main_page->response);
+ }
+
+ if (0 == strcmp ("/search", url))
+ {
+ const char *name = MHD_lookup_connection_value (connection,
+ MHD_GET_ARGUMENT_KIND,
+ "name");
+ if (NULL == name)
+ {
+ return MHD_queue_response (connection,
+ MHD_HTTP_BAD_REQUEST,
+ forbidden_page->response);
+ }
+
+ MHD_suspend_connection (connection);
+ rd = GNUNET_new (struct RequestData);
+ rd->c = connection;
+ rd->searching = GNUNET_NAMESTORE_records_lookup (namestore,
+ zone_key,
+ name,
+ &search_error_cb,
+ rd,
+ &search_done_cb,
+ rd);
+ *ptr = rd;
+ return MHD_YES;
+ }
+
+ return MHD_queue_response (connection,
+ MHD_HTTP_NOT_FOUND,
+ notfound_page->response);
+ }
+
+ if (0 == strcmp (method, MHD_HTTP_METHOD_HEAD))
+ {
+ /* We take a shortcut here by always serving the main page: starting a
+ namestore lookup, allocating the necessary resources, waiting for the
+ lookup to complete and then discard everything just because it was a HEAD
+ and thus only the headers are significative, is an unnecessary waste of
+ resources. The handling of this method could be smarter, for example by
+ sending a proper content type header based on the endpoint, but this is
+ not a service in which HEAD requests are significant, so there's no need
+ to spend too much time here. */
+ return MHD_queue_response (connection,
+ MHD_HTTP_OK,
+ main_page->response);
+ }
+
+ if (0 == strcmp (method, MHD_HTTP_METHOD_POST))
+ {
+ if (0 == strcmp ("/register", url))
+ {
+ /* Handle a previously suspended request */
+ if ((NULL != rd) && (NULL != rd->body))
+ {
+ return serve_json (rd->c, rd->body, rd->body_length, rd->code);
+ }
+
+ if (NULL == rd)
+ {
+ rd = GNUNET_new (struct RequestData);
+ rd->c = connection;
+ rd->body = NULL;
+ rd->ptr = NULL;
+ *ptr = rd;
+ }
+
+ json_t *json = NULL;
+ enum GNUNET_JSON_PostResult result =
+ GNUNET_JSON_post_parser (32 * 1024,
+ connection,
+ &(rd->ptr),
+ upload_data,
+ upload_data_size,
+ &json);
+
+ switch (result)
+ {
+ case GNUNET_JSON_PR_CONTINUE:
+ /* Keep processing POST data */
+ return MHD_YES;
+ case GNUNET_JSON_PR_OUT_OF_MEMORY:
+ case GNUNET_JSON_PR_REQUEST_TOO_LARGE:
+ rd->body = make_json ("error", "true",
+ "message", _ ("unable to process submitted data"),
+ NULL);
+ rd->body_length = strlen (rd->body);
+#ifdef MHD_HTTP_CONTENT_TOO_LARGE
+ rd->code = MHD_HTTP_CONTENT_TOO_LARGE;
+#else
+ rd->code = MHD_HTTP_PAYLOAD_TOO_LARGE;
+#endif
+ return MHD_YES;
+ case GNUNET_JSON_PR_JSON_INVALID:
+ rd->body = make_json ("error", "true",
+ "message", _ ("the submitted data is invalid"),
+ NULL);
+ rd->body_length = strlen (rd->body);
+ rd->code = MHD_HTTP_BAD_REQUEST;
+ return MHD_YES;
+ default:
+ break;
+ }
+
+ /* POST data has been read in its entirety */
+
+ const char *name = json_string_value (json_object_get (json, "name"));
+ const char *key = json_string_value (json_object_get (json, "key"));
+ if ((NULL == name) || (NULL == key) || (0 == strlen (name)) || (0 ==
+ strlen (
+ key)))
+ {
+ json_decref (json);
+ rd->body = make_json ("error", "true",
+ "message", _ ("invalid parameters"),
+ NULL);
+ rd->body_length = strlen (rd->body);
+ rd->code = MHD_HTTP_BAD_REQUEST;
+ return MHD_YES;
+ }
+
+ rd->register_name = strdup (name);
+ rd->register_key = strdup (key);
+
+ json_decref (json);
+ GNUNET_JSON_post_parser_cleanup (rd->ptr);
+
+ if ((NULL != strchr (rd->register_name, '.')) ||
+ (NULL != strchr (rd->register_name, '+')))
+ {
+ rd->body = make_json ("error", "true",
+ "message", _ ("invalid name"),
+ NULL);
+ rd->body_length = strlen (rd->body);
+ rd->code = MHD_HTTP_BAD_REQUEST;
+ return MHD_YES;
+ }
+
+ if (GNUNET_OK != GNUNET_IDENTITY_public_key_from_string (rd->register_key,
+ &(rd->key)))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ _ ("Unable to parse key %s\n"),
+ rd->register_key);
+
+ rd->body = make_json ("error", "true",
+ "message", _ ("unable to parse key"),
+ NULL);
+ rd->body_length = strlen (rd->body);
+ rd->code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ return MHD_YES;
+ }
+
+ MHD_suspend_connection (connection);
+ /* See if the requested name is free */
+ rd->iterating =
+ GNUNET_NAMESTORE_zone_iteration_start (namestore,
+ zone_key,
+ &iterate_error_cb,
+ rd,
+ &iterate_do_cb,
+ rd,
+ &iterate_done_cb,
+ rd);
+ return MHD_YES;
+ }
+
+ return MHD_queue_response (connection,
+ MHD_HTTP_FORBIDDEN,
+ forbidden_page->response);
+ }
+
+ return MHD_queue_response (connection,
+ MHD_HTTP_NOT_IMPLEMENTED,
+ forbidden_page->response);
+}
+
+
+/**
+ * Called when a request is completed.
+ *
+ * @param cls unused
+ * @param connection the connection
+ * @param ptr connection-specific data
+ * @param status status code
+ */
+static void
+completed_cb (void *cls,
+ struct MHD_Connection *connection,
+ void **ptr,
+ enum MHD_RequestTerminationCode status)
+{
+ (void) cls;
+ (void) connection;
+ (void) status;
+
+ struct RequestData *rd = *ptr;
+
+ if (NULL == rd)
+ {
+ return;
+ }
+
+ if (NULL == rd->body)
+ {
+ GNUNET_free (rd->body);
+ }
+
+ if (NULL != rd->searching)
+ {
+ GNUNET_NAMESTORE_cancel (rd->searching);
+ }
+
+ if (NULL != rd->register_name)
+ {
+ GNUNET_free (rd->register_name);
+ }
+
+ if (NULL != rd->register_key)
+ {
+ GNUNET_free (rd->register_key);
+ }
+
+ if (NULL != rd->iterating)
+ {
+ GNUNET_NAMESTORE_zone_iteration_stop (rd->iterating);
+ }
+
+ GNUNET_free (rd);
+}
+
+
+/**
+ * Called for each ego provided by the identity service.
+ *
+ * @param cls closure
+ * @param ego the ego
+ * @param ctx application-provided data for the ego
+ * @param name the ego name
+ */
+static void
+identity_cb (void *cls,
+ struct GNUNET_IDENTITY_Ego *ego,
+ void **ctx,
+ const char *name)
+{
+ (void) cls;
+ (void) ctx;
+
+ if ((NULL == name) || (0 != strcmp (name, zone)))
+ {
+ return;
+ }
+
+ if (NULL == ego)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ _ ("No ego configured for `fcfsd` subsystem\n"));
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+
+ zone_key = GNUNET_IDENTITY_ego_get_private_key (ego);
+
+ int flags = MHD_USE_DUAL_STACK | MHD_USE_DEBUG | MHD_ALLOW_SUSPEND_RESUME;
+ do
+ {
+ httpd = MHD_start_daemon (flags,
+ (uint16_t) port,
+ NULL, NULL,
+ &create_response, NULL,
+ MHD_OPTION_CONNECTION_LIMIT, 128,
+ MHD_OPTION_PER_IP_CONNECTION_LIMIT, 1,
+ MHD_OPTION_CONNECTION_TIMEOUT, 4 * 1024,
+ MHD_OPTION_NOTIFY_COMPLETED, &completed_cb, NULL,
+ MHD_OPTION_END);
+ flags = MHD_USE_DEBUG;
+ } while (NULL == httpd && flags != MHD_USE_DEBUG);
+
+ if (NULL == httpd)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ _ ("Failed to start HTTP server\n"));
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+
+ run_httpd ();
+}
+
+
+/**
+ * Open a file on disk and generate a response object for it.
+ *
+ * @param name name of the file to open
+ * @param basedir directory where the file is located
+ * @return NULL on error
+ */
+static struct StaticPage *
+open_static_page (const char *name, const char *basedir)
+{
+ char *fullname = NULL;
+ GNUNET_asprintf (&fullname, "%s/fcfsd-%s", basedir, name);
+
+ struct GNUNET_DISK_FileHandle *f =
+ GNUNET_DISK_file_open (fullname,
+ GNUNET_DISK_OPEN_READ,
+ GNUNET_DISK_PERM_NONE);
+ GNUNET_free (fullname);
+
+ if (NULL == f)
+ {
+ return NULL;
+ }
+
+ off_t size = 0;
+ if (GNUNET_SYSERR == GNUNET_DISK_file_handle_size (f, &size))
+ {
+ GNUNET_DISK_file_close (f);
+ return NULL;
+ }
+
+ struct MHD_Response *response =
+ MHD_create_response_from_fd64 (size,
+ f->fd);
+
+ if (NULL == response)
+ {
+ GNUNET_DISK_file_close (f);
+ return NULL;
+ }
+
+ struct StaticPage *page = GNUNET_new (struct StaticPage);
+ page->handle = f;
+ page->size = (uint64_t) size;
+ page->response = response;
+ return page;
+}
+
+
+/**
+ * Called after the service is up.
+ *
+ * @param cls closure
+ * @param args remaining command line arguments
+ * @param cfgfile name of the configuration file
+ * @param cfg the service configuration
+ */
+static void
+run_service (void *cls,
+ char *const *args,
+ const char *cfgfile,
+ const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+ (void) cls;
+ (void) args;
+ (void) cfgfile;
+
+ GNUNET_log_setup ("fcfsd", "WARNING", NULL);
+
+ if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_time (cfg,
+ "fcfsd",
+ "RELATIVE_RECORD_EXPIRATION",
+ &record_exp))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ _("No expiration specified for records.\n"));
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+
+ if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_number (cfg,
+ "fcfsd",
+ "HTTPPORT",
+ &port))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ _ ("No port specified, using default value\n"));
+ }
+
+ GNUNET_SCHEDULER_add_shutdown (&do_shutdown, NULL);
+
+ namestore = GNUNET_NAMESTORE_connect (cfg);
+ if (NULL == namestore)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ _ ("Failed to connect to namestore\n"));
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+
+ identity = GNUNET_IDENTITY_connect (cfg, &identity_cb, NULL);
+ if (NULL == identity)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ _ ("Failed to connect to identity\n"));
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+
+ char *basedir = NULL;
+ if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_filename (cfg,
+ "fcfsd",
+ "HTMLDIR",
+ &basedir))
+ {
+ basedir = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_DATADIR);
+ }
+
+ main_page = open_static_page ("index.html", basedir);
+ notfound_page = open_static_page ("notfound.html", basedir);
+ forbidden_page = open_static_page ("forbidden.html", basedir);
+
+ GNUNET_free (basedir);
+
+ if ((NULL == main_page) || (NULL == notfound_page) || (NULL ==
+ forbidden_page) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ _ ("Unable to set up the daemon\n"));
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+}
+
+
+/**
+ * The main function of the fcfs daemon.
+ *
+ * @param argc number of arguments from the command line
+ * @param argv the command line arguments
+ * @return 0 successful exit, a different value otherwise
+ */
+int
+main (int argc, char *const *argv)
+{
+ struct GNUNET_GETOPT_CommandLineOption options[] = {
+ GNUNET_GETOPT_option_mandatory
+ (GNUNET_GETOPT_option_string ('z',
+ "zone",
+ "EGO",
+ gettext_noop (
+ "name of the zone managed by FCFSD"),
+ &zone)),
+ GNUNET_GETOPT_OPTION_END
+ };
+
+ return ((GNUNET_OK == GNUNET_PROGRAM_run (argc,
+ argv,
+ "gnunet-namestore-fcfsd",
+ _ (
+ "GNU Name System First-Come-First-Served name registration service"),
+ options,
+ &run_service,
+ NULL)) ?
+ 0 :
+ 1);
+}
diff --git a/src/service/meson.build b/src/service/meson.build
@@ -0,0 +1,3 @@
+executable('gnunet-namestore-fcfsd',
+ 'gnunet-namestore-fcfsd.c',
+ dependencies: gnunetdeps)