diff options
-rw-r--r-- | contrib/Makefile.am | 3 | ||||
-rw-r--r-- | contrib/fcfsd/fcfsd-forbidden.html | 11 | ||||
-rw-r--r-- | contrib/fcfsd/fcfsd-index.html | 345 | ||||
-rw-r--r-- | contrib/fcfsd/fcfsd-notfound.html | 11 | ||||
-rw-r--r-- | doc/handbook/chapters/developer.texi | 134 | ||||
-rw-r--r-- | src/namestore/Makefile.am | 3 | ||||
-rw-r--r-- | src/namestore/gnunet-namestore-fcfsd.c | 1607 |
7 files changed, 1252 insertions, 862 deletions
diff --git a/contrib/Makefile.am b/contrib/Makefile.am index 331620586..f42fb684d 100644 --- a/contrib/Makefile.am +++ b/contrib/Makefile.am @@ -11,6 +11,9 @@ dist_pkgdata_DATA = \ gns/def.tex \ gns/gns-form-fields.xml \ gns/gns-form.xslt \ + fcfsd/fcfsd-index.html \ + fcfsd/fcfsd-notfound.html \ + fcfsd/fcfsd-forbidden.html \ branding/logo/gnunet-logo.pdf \ branding/logo/gnunet-logo.png \ branding/logo/gnunet-logo-color.png \ diff --git a/contrib/fcfsd/fcfsd-forbidden.html b/contrib/fcfsd/fcfsd-forbidden.html new file mode 100644 index 000000000..57ebb4c61 --- /dev/null +++ b/contrib/fcfsd/fcfsd-forbidden.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="utf-8"/> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Forbidden - GNUnet FCFS Authority Name Registration Service</title> + </head> + <body> + <h1>You can not access this resource.</h1> + </body> +</html> diff --git a/contrib/fcfsd/fcfsd-index.html b/contrib/fcfsd/fcfsd-index.html new file mode 100644 index 000000000..3fa71d7c8 --- /dev/null +++ b/contrib/fcfsd/fcfsd-index.html @@ -0,0 +1,345 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="utf-8"/> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>GNUnet FCFS Authority Name Registration Service</title> + <style> + html { + box-sizing: border-box; + font-family: sans-serif; + } + + *, *:before, *:after { + box-sizing: inherit; + } + + header { + width: 800px; + margin: 0 auto; + } + + main { + width: 800px; + margin: 0 auto; + } + + section h4 { + text-align: center; + width: 100%; + } + + section input { + width: 100%; + padding: 8px 17px; + font-size: 1rem; + border: 1px solid #aaa; + border-radius: 7px; + background-color: white; + margin-bottom: 7px; + } + + section input:focus { + box-shadow: 0px 0px 5px 3px lightblue; + } + + section button { + font-size: 1rem; + font-weight: bold; + background-color: #8b008b; + color: white; + border: none; + padding: 7px; + } + + section button:hover { + background-color: #bf00bf; + } + + section button:disabled { + background-color: gray; + } + + section h3 { + text-align: center; + width: 100%; + } + + section small { + display: block; + margin-bottom: 5px; + } + + .error-message { + color: red; + } + + .success-message { + color: green; + } + + @media screen and (max-width: 991px) { + header, main { + width: 100%; + } + } + + footer { + margin-top: 30px; + text-align: center; + } + + nav { + border-bottom: 1px solid black; + } + + nav button { + font-size: 1rem; + font-weight: bold; + background-color: #ccc; + border: 1px solid black; + border-bottom: none; + border-top-right-radius: 7px; + border-top-left-radius: 7px; + padding: 7px; + } + + nav button:hover { + background-color: #f0f0f0; + cursor: pointer; + } + + nav button.selected { + background-color: #f0f0f0; + } + </style> + </head> + <body> + <header> + <h1>Name Registration Service</h1> + <p>Here you can register a name for your zone as part of this service's + delegated names.</p> + <p>The registration is based on a <em>First Come First Served</em> + policy, meaning a name is given to the first user requesting it.</p> + <p>Use the search bar below to see if your desired name is available and + then use the form to submit your registration request.</p> + </header> + <main> + <div class="form-container"> + <nav> + <button id="tab-search">Search</button> + <button id="tab-register">Register</button> + </nav> + <section id="search-form"> + <h4>Is your name available?</h4> + <h3 id="search-result-message"></h3> + <input id="search-name" + name="search-name" + type="text" + placeholder="Your name..." + autocomplete="name" + maxlength="63" + minlength="1"> + <small class="error-message" id="search-name-error"></small> + <button>Search</button> + </section> + <section id="submit-form"> + <h4>Submit a registration request</h4> + <h3 id="submit-result-message"></h3> + <input id="register-name" + name="register-name" + type="text" + placeholder="Your name..." + autocomplete="off" + maxlength="63" + minlength="1"> + <input id="register-value" + name="register-value" + type="text" + placeholder="Your zone key..." + autocomplete="off" + minlength="1"> + <small class="error-message" id="submit-error"></small> + <button>Submit</button> + </section> + </div> + </main> + <footer> + <a href="https://gnunet.org">GNUnet homepage</a> + </footer> + <script> + const buttons = document.querySelectorAll('nav button'); + for (let i=0; i<buttons.length; ++i) { + buttons[i].onclick = function (e) { + let selected = document.querySelector('nav button.selected'); + if (selected) { + selected.classList.toggle('selected'); + } + e.target.classList.toggle('selected'); + + let show = ''; + let hide = ''; + if (e.target.id === 'tab-search') { + show = 'search-form'; + hide = 'submit-form'; + } else { + show = 'submit-form'; + hide = 'search-form' + } + + document.getElementById(hide).style.display = 'none'; + document.getElementById(show).style.display = 'block'; + }; + } + + buttons[0].click({target: buttons[0]}); + + const searchbutton = document.querySelector('#search-form button'); + const submitbutton = document.querySelector('#submit-form button'); + + document.getElementById('search-name').onkeydown = function (e) { + if (e.key !== 'Enter') { + return; + } + + searchbutton.click(); + }; + + for (let n of ['register-name', 'register-value']) { + document.getElementById(n).onkeydown = function (e) { + if (e.key !== 'Enter') { + return; + } + + submitbutton.click(); + }; + } + + searchbutton.onclick = function (e) { + const searchname = document.getElementById('search-name'); + const errormsg = document.getElementById('search-name-error'); + const resultmsg = document.getElementById('search-result-message'); + + if (0 === searchname.value.length) { + errormsg.innerText = 'The field can not be empty'; + searchname.setCustomValidity('The field can not be empty'); + return; + } + + if (-1 !== searchname.value.indexOf('.')) { + errormsg.innerText = 'The name can not contain dots'; + searchname.setCustomValidity('The name can not contain dots'); + return; + } + + searchname.setCustomValidity(''); + errormsg.innerText = ''; + + const name = searchname.value.toLowerCase(); + + searchbutton.disabled = true; + submitbutton.disabled = true; + + fetch(`/search?name=${name}`) + .then(function (response) { + if (!response.ok) { + throw 'error'; + } + + return response.json() + }) + .then(function (data) { + if ("true" === data.free) { + resultmsg.innerText = `'${name}' is available!`; + resultmsg.classList.add('success-message'); + resultmsg.classList.remove('error-message'); + } else { + resultmsg.innerText = `'${name}' is not available`; + resultmsg.classList.remove('success-message'); + resultmsg.classList.add('error-message'); + } + searchbutton.disabled = false; + submitbutton.disabled = false; + }) + .catch(function (error) { + resultmsg.innerText = 'An error occurred while processing your query'; + resultmsg.classList.remove('success-message'); + resultmsg.classList.add('error-message'); + console.error(error); + searchbutton.disabled = false; + submitbutton.disabled = false; + }); + }; + + submitbutton.onclick = function (e) { + const registername = document.getElementById('register-name'); + const registervalue = document.getElementById('register-value'); + const errormsg = document.getElementById('submit-error'); + const resultmsg = document.getElementById('submit-result-message'); + + let errors = 0; + let errs = []; + + if (0 === registername.value.length) { + errs.push('The name field can not be empty'); + registername.setCustomValidity('The name field can not be empty'); + ++errors; + } + if (-1 !== registername.value.indexOf('.')) { + errs.push('The name can not contain dots'); + registername.setCustomValidity('The name can not contain dots'); + ++errors; + } + if (0 === registervalue.value.length) { + errs.push('The value field can not be empty'); + registervalue.setCustomValidity('The value field can not be empty'); + ++errors; + } + + if (0 < errors) { + errormsg.innerHTML = 'The form contains invalid values:'; + for (let e of errs) { + errormsg.innerHTML += '<br/>' + e; + } + return; + } + + searchbutton.disabled = true; + submitbutton.disabled = true; + + fetch('/register', { + method: 'POST', + cache: 'no-cache', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + name: registername.value, + key: registervalue.value, + }), + }).then(function (response) { + return response.json(); + }).then(function (data) { + if (data.error === "false") { + resultmsg.innerText = `'${registername.value}' was registered successfully!`; + resultmsg.classList.add('success-message'); + resultmsg.classList.remove('error-message'); + } else { + resultmsg.innerText = `'${registername.value}' could not be registered! (${data.message})`; + resultmsg.classList.remove('success-message'); + resultmsg.classList.add('error-message'); + } + searchbutton.disabled = false; + submitbutton.disabled = false; + }).catch(function (error) { + resultmsg.innerText = 'An error occurred while processing your query'; + resultmsg.classList.remove('success-message'); + resultmsg.classList.add('error-message'); + console.error(error); + searchbutton.disabled = false; + submitbutton.disabled = false; + }); + }; + </script> + </body> +</html> diff --git a/contrib/fcfsd/fcfsd-notfound.html b/contrib/fcfsd/fcfsd-notfound.html new file mode 100644 index 000000000..676bf4a9a --- /dev/null +++ b/contrib/fcfsd/fcfsd-notfound.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="utf-8"/> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Not Found - GNUnet FCFS Authority Name Registration Service</title> + </head> + <body> + <h1>The requested resource could not be found</h1> + </body> +</html> diff --git a/doc/handbook/chapters/developer.texi b/doc/handbook/chapters/developer.texi index 24981c020..f8eebbb58 100644 --- a/doc/handbook/chapters/developer.texi +++ b/doc/handbook/chapters/developer.texi @@ -8224,6 +8224,7 @@ record types. * Hijacking the DNS-Traffic using gnunet-service-dns:: @c * Serving DNS lookups via GNS on W32:: * Importing DNS Zones into GNS:: +* Registering names using the FCFS daemon:: @end menu @node libgnunetgns @@ -8736,6 +8737,139 @@ single threaded implementation and inefficient, sequential calls of gnunet-namestore. In the future a solution that uses the C API would be cleaner and better. +@node Registering names using the FCFS daemon +@subsection Registering names using the FCFS daemon + +This section describes FCFSD, a daemon used to associate names with PKEY +records following a ``First Come, First Served'' policy. This policy means +that a certain name can not be registered again if someone registered it +already. + +The daemon can be started by using @code{gnunet-namestore-fcfsd}, which will +start a simple HTTP server on localhost, using a port specified by the +@code{HTTPORT} value in its configuration. + +Communication is performed by sending GET or POST requests to specific paths +(``endpoints''), as described in the following sections. + +The daemon will always respond with data structured using the JSON format. +The fields to be expected will be listed for each endpoint. + +The only exceptions are for the ``root'' endpoint (i.e. @code{/}) which will +return a HTML document, and two other HTML documents which will be served when +certain errors are encountered, like when requesting an unknown endpoint. + +@menu +* Obtaining informations from the daemon:: +* Submitting data to the daemon:: +* Customizing the HTML output:: +@end menu + +@cindex FCFSD GET requests +@node Obtaining informations from the daemon +@subsubsection Obtaining informations from the daemon + +To query the daemon, a GET request must be sent to these endpoints, placing +parameters in the address as per the HTTP specification, like so: + +@example +GET /endpoint?param1=value¶m2=value +@end example + +Each endpoint will be described using its name (@code{/endpoint} in the +example above), followed by the name of each parameter (like @code{param1} and +@code{param2}.) + +@deffn Endpoint /search name +This endpoint is used to query about the state of @var{name}, that is, whether +it is available for registration or not. + +The response JSON will contain two fields: + +@itemize @bullet +@item error +@item free +@end itemize + +@code{error} can be either the string @code{"true"} or the string +@code{"false"}: when @code{"true"}, it means there was an error within the +daemon and the name could not be searched at all. + +@code{free} can be either the string @code{"true"} or the string +@code{"false"}: when @code{"true"}, the requested name can be registered. +@end deffn + +@cindex FCFSD POST requests +@node Submitting data to the daemon +@subsubsection Submitting data to the daemon + +To send data to the daemon, a POST request must be sent to these endpoints, +placing the data to submit in the body of the request, structured using the +JSON format, like so: + +@example +POST /endpoint +Content-Type: application/json +... + +@{"param1": value1, "param2": value2, ...@} +@end example + +Each endpoint will be described using its name (@code{/endpoint} in the +example above), followed by the name of each JSON field (like @code{param1} +and @code{param2}.) + +@deffn Endpoint /register name key +This endpoint is used to register a new association between @var{name} and +@var{key}. + +For this operation to succeed, both @var{NAME} and @var{KEY} @strong{must not} +be registered already. + +The response JSON will contain two fields: + +@itemize @bullet +@item error +@item message +@end itemize + +@code{error} can be either the string @code{"true"} or the string +@code{"false"}: when @code{"true"}, it means the name could not be registered. +Clients can get the reason of the failure from the HTTP response code or from +the @code{message} field. + +@code{message} is a string which can be used by clients to let users know the +result of the operation. It might be localized to the daemon operator's +locale. +@end deffn + +@node Customizing the HTML output +@subsubsection Customizing the HTML output + +In some situations, the daemon will serve HTML documents instead of JSON +values. It is possible to configure the daemon to serve custom documents +instead of the ones provided with GNUnet, by setting the @code{HTMLDIR} value +in its configuration to a directory path. + +Within the provided path, the daemon will search for these three files: + +@itemize @bullet +@item fcfsd-index.html +@item fcfsd-notfound.html +@item fcfsd-forbidden.html +@end itemize + +The @file{fcfsd-index.html} file is the daemon's ``homepage'': operators might +want to provide informations about the service here, or provide a form with +which it is possible to register a name. + +The @file{fcfsd-notfound.html} file is used primarily to let users know they +tried to access an unknown endpoint. + +The @file{fcfsd-forbidden.html} file is served to users when they try to +access an endpoint they should not access. For example, sending an invalid +request might result in this page being served. + @cindex GNS Namecache @node GNS Namecache @section GNS Namecache diff --git a/src/namestore/Makefile.am b/src/namestore/Makefile.am index df4e5d662..61bcfbaf8 100644 --- a/src/namestore/Makefile.am +++ b/src/namestore/Makefile.am @@ -182,7 +182,8 @@ gnunet_namestore_fcfsd_LDADD = $(MHD_LIBS) \ $(top_builddir)/src/identity/libgnunetidentity.la \ libgnunetnamestore.la \ $(top_builddir)/src/util/libgnunetutil.la \ - $(GN_LIBINTL) + $(top_builddir)/src/json/libgnunetjson.la \ + $(GN_LIBINTL) -ljansson gnunet_namestore_fcfsd_CFLAGS = $(MHD_CFLAGS) $(AM_CFLAGS) diff --git a/src/namestore/gnunet-namestore-fcfsd.c b/src/namestore/gnunet-namestore-fcfsd.c index 313aea6fc..e1fa0a0a6 100644 --- a/src/namestore/gnunet-namestore-fcfsd.c +++ b/src/namestore/gnunet-namestore-fcfsd.c @@ -1,6 +1,6 @@ /* This file is part of GNUnet. - Copyright (C) 2012-2014 GNUnet e.V. + 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 @@ -17,19 +17,13 @@ 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 - * - * TODO: - * - need to track active zone info requests so we can cancel them - * during shutdown, right? - * - the code currently contains a 'race' between checking that the - * domain name is available and allocating it to the new public key - * (should this race be solved by namestore or by fcfsd?) - * - nicer error reporting to browser */ + #include "platform.h" #include <microhttpd.h> #include "gnunet_util_lib.h" @@ -37,735 +31,625 @@ #include "gnunet_gnsrecord_lib.h" #include "gnunet_namestore_service.h" #include "gnunet_mhd_compat.h" +#include "gnunet_json_lib.h" /** - * Invalid method page. - */ -#define METHOD_ERROR \ - "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"><html lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\"><html><head><title>Illegal request</title></head><body>Go away.</body></html>" - -/** - * Front page. (/) - */ -#define MAIN_PAGE \ - "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"><html lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\"><html><head><title>GNUnet FCFS Authority Name Registration Service</title></head><body><form action=\"S\" method=\"post\">What is your desired domain name? (at most 63 lowercase characters, no dots allowed.) <input type=\"text\" name=\"domain\" /> <p> What is your public key? (Copy from gnunet-setup.) <input type=\"text\" name=\"pkey\" /> <input type=\"submit\" value=\"Next\" /><br/><a href=./Zoneinfo> List of all registered names </a></body></html>" - -/** - * Second page (/S) - */ -#define SUBMIT_PAGE \ - "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"><html lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\"><html><head><title>%s</title></head><body>%s</body></html>" - -/** - * Fcfs zoneinfo page (/Zoneinfo) - */ -#define ZONEINFO_PAGE \ - "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"><html lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\"><html><head><title>FCFS Zoneinfo</title></head><body><h1> FCFS Zoneinfo </h1><table border=\"1\"><th>name</th><th>PKEY</th>%s</table></body></html>" - -#define FCFS_ZONEINFO_URL "/Zoneinfo" - -/** - * Mime type for HTML pages. - */ -#define MIME_HTML "text/html" - -/** - * Name of our cookie. - */ -#define COOKIE_NAME "namestore-fcfsd" - -#define DEFAULT_ZONEINFO_BUFSIZE 2048 - -/** - * Phases a request goes through. + * 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. */ -enum Phase +struct StaticPage { /** - * Start phase (parsing POST, checking). - */ - RP_START = 0, - - /** - * Lookup to see if the domain name is taken. + * Handle to file on disk. */ - RP_LOOKUP, + struct GNUNET_DISK_FileHandle *handle; /** - * Storing of the record. + * Size in bytes of the file. */ - RP_PUT, + uint64_t size; /** - * We're done with success. + * Cached response object to send to clients. */ - RP_SUCCESS, - - /** - * Send failure message. - */ - RP_FAIL + struct MHD_Response *response; }; - /** - * Data kept per request. + * Structure containing some request-specific data. */ -struct Request +struct RequestData { /** - * Associated session. - */ - // FIXME: struct Session *session; - - /** - * Post processor handling form data (IF this is - * a POST request). + * The connection this request was sent in. */ - struct MHD_PostProcessor *pp; + struct MHD_Connection *c; /** - * MHD Connection - */ - struct MHD_Connection *con; - /** - * URL to serve in response to this POST (if this request - * was a 'POST') + * Body of the response object. */ - const char *post_url; + char *body; /** - * Active request with the namestore. + * Length in bytes of the body. */ - struct GNUNET_NAMESTORE_QueueEntry *qe; + size_t body_length; /** - * Active lookup iterator - * TODO: deprecate or fix lookup by label and use above member + * Response code. */ - struct GNUNET_NAMESTORE_ZoneIterator *lookup_it; - /** - * Active iteration with the namestore. - */ - struct GNUNET_NAMESTORE_ZoneIterator *zi; + int code; /** - * Current processing phase. + * Task started to search for an entry in the namestore. */ - enum Phase phase; + struct GNUNET_NAMESTORE_QueueEntry *searching; /** - * Domain name submitted via form. + * Task started to iterate over the namestore. */ - char domain_name[64]; + struct GNUNET_NAMESTORE_ZoneIterator *iterating; /** - * Public key submitted via form. - */ - char public_key[128]; - - struct GNUNET_IDENTITY_PublicKey pub; -}; - -/** - * Zoneinfo request - */ -struct ZoneinfoRequest -{ - /** - * List iterator + * Pointer used while processing POST data. */ - struct GNUNET_NAMESTORE_ZoneIterator *list_it; + void *ptr; /** - * Buffer + * Name requested to be registered. */ - char*zoneinfo; + char *register_name; /** - * Buffer length + * Key (encoded as a string) to be associated with the requested name. */ - size_t buf_len; + char *register_key; /** - * Buffer write offset + * Key to be associated with the requested name. */ - size_t write_offset; + struct GNUNET_IDENTITY_PublicKey key; }; /** - * MHD daemon reference. + * Name of the zone being managed. */ -static struct MHD_Daemon *httpd; +static char *zone = NULL; /** - * Main HTTP task. + * The port the daemon is listening to for HTTP requests. */ -static struct GNUNET_SCHEDULER_Task *httpd_task; +static unsigned long long port = 18080; /** - * Handle to the namestore. + * Connection with the namestore service. */ -static struct GNUNET_NAMESTORE_Handle *ns; +static struct GNUNET_NAMESTORE_Handle *namestore = NULL; /** - * Private key for the fcfsd zone. + * Connection with the identity service. */ -static struct GNUNET_IDENTITY_PrivateKey fcfs_zone_pkey; +static struct GNUNET_IDENTITY_Handle *identity = NULL; /** - * Connection to identity service. + * Private key of the zone. */ -static struct GNUNET_IDENTITY_Handle *identity; +static const struct GNUNET_IDENTITY_PrivateKey *zone_key = NULL; /** - * Zoneinfo page we currently use. + * The HTTP daemon. */ -static struct MHD_Response *info_page; +static struct MHD_Daemon *httpd = NULL; /** - * Task that runs #update_zoneinfo_page peridicially. + * Task executing the HTTP daemon. */ -static struct GNUNET_SCHEDULER_Task *uzp_task; +static struct GNUNET_SCHEDULER_Task *httpd_task = NULL; /** - * Request for our ego. + * The main page, a.k.a. "index.html" */ -static struct GNUNET_IDENTITY_Operation *id_op; +static struct StaticPage *main_page = NULL; /** - * Port we use for the HTTP server. + * Page indicating the requested resource could not be found. */ -static unsigned long long port; +static struct StaticPage *notfound_page = NULL; /** - * Name of the zone we manage. + * Page indicating the requested resource could not be accessed, and other + * errors. */ -static char *zone; - +static struct StaticPage *forbidden_page = NULL; /** - * Task run whenever HTTP server operations are pending. + * Task ran at shutdown to clean up everything. * * @param cls unused */ static void -do_httpd (void *cls); +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 (main_page->response); + GNUNET_free (main_page->handle); + GNUNET_free (main_page); + } + if (NULL != forbidden_page) + { + MHD_destroy_response (main_page->response); + GNUNET_free (main_page->handle); + GNUNET_free (main_page); + } + if (NULL != namestore) + { + GNUNET_NAMESTORE_disconnect (namestore); + } -/** - * Schedule task to run MHD server now. - */ -static void -run_httpd_now () -{ - if (NULL != httpd_task) + if (NULL != identity) { - GNUNET_SCHEDULER_cancel (httpd_task); - httpd_task = NULL; + GNUNET_IDENTITY_disconnect (identity); } - httpd_task = GNUNET_SCHEDULER_add_now (&do_httpd, NULL); } - /** - * Create fresh version of zone information. + * Called when the HTTP server has some pending operations. + * + * @param cls unused */ static void -update_zoneinfo_page (void *cls); - +do_httpd (void *cls); /** - * Function called on error in zone iteration. + * Schedule a task to run MHD. */ static void -zone_iteration_error (void *cls) +run_httpd (void) { - struct ZoneinfoRequest *zr = cls; + fd_set rs; + fd_set ws; + fd_set es; - zr->list_it = NULL; - GNUNET_free (zr->zoneinfo); - GNUNET_SCHEDULER_cancel (uzp_task); - uzp_task = GNUNET_SCHEDULER_add_now (&update_zoneinfo_page, - NULL); -} + 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); +} /** - * Function called once the zone iteration is done. + * Called when the HTTP server has some pending operations. + * + * @param cls unused */ static void -zone_iteration_end (void *cls) +do_httpd (void *cls) { - struct ZoneinfoRequest *zr = cls; - struct MHD_Response *response; - char*full_page; - - zr->list_it = NULL; - - /* return static form */ - GNUNET_asprintf (&full_page, - ZONEINFO_PAGE, - zr->zoneinfo); - response = MHD_create_response_from_buffer (strlen (full_page), - (void *) full_page, - MHD_RESPMEM_MUST_FREE); - MHD_add_response_header (response, - MHD_HTTP_HEADER_CONTENT_TYPE, - MIME_HTML); - MHD_destroy_response (info_page); - info_page = response; - GNUNET_free (zr->zoneinfo); + 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); +} /** - * Process a record that was stored in the namestore, adding - * the information to the HTML. + * Generate a JSON object. * - * @param cls closure with the `struct ZoneinfoRequest *` - * @param zone_key private key of the zone; NULL on disconnect - * @param name label of the records; NULL on disconnect - * @param rd_len number of entries in @a rd array, 0 if label was deleted - * @param rd array of records with data to store + * @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 void -iterate_cb (void *cls, - const struct GNUNET_IDENTITY_PrivateKey *zone_key, - const char *name, - unsigned int rd_len, - const struct GNUNET_GNSRECORD_Data *rd) +static char * +make_json (const char *key, const char *value, ...) { - struct ZoneinfoRequest *zr = cls; - size_t bytes_free; - char*pkey; - char*new_buf; + va_list args; + va_start(args, value); + + json_t *obj = NULL; - (void) zone_key; - if (1 != rd_len) + obj = json_object (); + if (NULL == key || NULL == value) { - GNUNET_NAMESTORE_zone_iterator_next (zr->list_it, - 1); - return; + va_end (args); + return json_dumps (obj, JSON_COMPACT); } - if ((GNUNET_GNSRECORD_TYPE_PKEY != rd->record_type) && - (GNUNET_GNSRECORD_TYPE_EDKEY != rd->record_type)) + json_object_set (obj, key, json_string (value)); + + char *k = va_arg (args, char *); + if (NULL == k) { - GNUNET_NAMESTORE_zone_iterator_next (zr->list_it, - 1); - return; + va_end (args); + return json_dumps (obj, JSON_COMPACT); } - - bytes_free = zr->buf_len - zr->write_offset; - pkey = GNUNET_GNSRECORD_value_to_string (rd->record_type, - rd->data, - rd->data_size); - if (NULL == pkey) + char *v = va_arg (args, char *); + if (NULL == v) { - GNUNET_break (0); - GNUNET_NAMESTORE_zone_iterator_next (zr->list_it, - 1); - return; + va_end (args); + return json_dumps (obj, JSON_COMPACT); } - if (bytes_free < (strlen (name) + strlen (pkey) + 40)) + + while (NULL != k && NULL != v) { - new_buf = GNUNET_malloc (zr->buf_len * 2); - GNUNET_memcpy (new_buf, zr->zoneinfo, zr->write_offset); - GNUNET_free (zr->zoneinfo); - zr->zoneinfo = new_buf; - zr->buf_len *= 2; + json_object_set (obj, k, json_string (v)); + k = va_arg (args, char *); + if (NULL != k) + { + v = va_arg (args, char *); + } } - sprintf (zr->zoneinfo + zr->write_offset, - "<tr><td>%s</td><td>%s</td></tr>", - name, - pkey); - zr->write_offset = strlen (zr->zoneinfo); - GNUNET_NAMESTORE_zone_iterator_next (zr->list_it, - 1); - GNUNET_free (pkey); -} + va_end (args); -/** - * Handler that returns FCFS zoneinfo page. - * - * @param connection connection to use - */ -static int -serve_zoneinfo_page (struct MHD_Connection *connection) -{ - return MHD_queue_response (connection, - MHD_HTTP_OK, - info_page); -} + char *json = json_dumps (obj, JSON_COMPACT); + json_decref (obj); + return json; +} /** - * Create fresh version of zone information. + * The namestore search task failed. + * + * @param cls the request data */ static void -update_zoneinfo_page (void *cls) +search_error_cb (void *cls) { - static struct ZoneinfoRequest zr; - - (void) cls; - uzp_task = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_MINUTES, - &update_zoneinfo_page, - NULL); - if (NULL != zr.list_it) - return; - zr.zoneinfo = GNUNET_malloc (DEFAULT_ZONEINFO_BUFSIZE); - zr.buf_len = DEFAULT_ZONEINFO_BUFSIZE; - zr.write_offset = 0; - zr.list_it = GNUNET_NAMESTORE_zone_iteration_start (ns, - &fcfs_zone_pkey, - &zone_iteration_error, - &zr, - &iterate_cb, - &zr, - &zone_iteration_end, - &zr); + 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 (); } - /** - * Handler that returns a simple static HTTP page. + * The lookup terminated with some results. * - * @param connection connection to use - * @return #MHD_YES on success + * @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 int -serve_main_page (struct MHD_Connection *connection) +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) { - int ret; - struct MHD_Response *response; + (void) zone; + (void) d; - /* return static form */ - response = MHD_create_response_from_buffer (strlen (MAIN_PAGE), - (void *) MAIN_PAGE, - MHD_RESPMEM_PERSISTENT); - MHD_add_response_header (response, - MHD_HTTP_HEADER_CONTENT_TYPE, - MIME_HTML); - ret = MHD_queue_response (connection, - MHD_HTTP_OK, - response); - MHD_destroy_response (response); - return ret; -} + 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; -/** - * Send the 'SUBMIT_PAGE'. - * - * @param info information string to send to the user - * @param request request information - * @param connection connection to use - */ -static int -fill_s_reply (const char *info, - struct Request *request, - struct MHD_Connection *connection) -{ - int ret; - char *reply; - struct MHD_Response *response; - - (void) request; - GNUNET_asprintf (&reply, - SUBMIT_PAGE, - info, - info); - /* return static form */ - response = MHD_create_response_from_buffer (strlen (reply), - (void *) reply, - MHD_RESPMEM_MUST_FREE); - MHD_add_response_header (response, - MHD_HTTP_HEADER_CONTENT_TYPE, - MIME_HTML); - ret = MHD_queue_response (connection, - MHD_HTTP_OK, - response); - MHD_destroy_response (response); - return ret; + run_httpd_now (); } - /** - * Iterator over key-value pairs where the value - * maybe made available in increments and/or may - * not be zero-terminated. Used for processing - * POST data. + * An error occurred while registering a name. * - * @param cls user-specified closure - * @param kind type of the value - * @param key 0-terminated key for the value - * @param filename name of the uploaded file, NULL if not known - * @param content_type mime-type of the data, NULL if not known - * @param transfer_encoding encoding of the data, NULL if not known - * @param data pointer to size bytes of data at the - * specified offset - * @param off offset of data in the overall value - * @param size number of bytes in data available - * @return #MHD_YES to continue iterating, - * #MHD_NO to abort the iteration + * @param cls the connection */ -static MHD_RESULT -post_iterator (void *cls, - enum MHD_ValueKind kind, - const char *key, - const char *filename, - const char *content_type, - const char *transfer_encoding, - const char *data, - uint64_t off, - size_t size) +static void +register_error_cb (void *cls) { - struct Request *request = cls; - - (void) kind; - (void) filename; - (void) content_type; - (void) transfer_encoding; - if (0 == strcmp ("domain", key)) - { - if (size + off >= sizeof(request->domain_name)) - size = sizeof(request->domain_name) - off - 1; - GNUNET_memcpy (&request->domain_name[off], - data, - size); - request->domain_name[size + off] = '\0'; - return MHD_YES; - } - if (0 == strcmp ("pkey", key)) - { - if (size + off >= sizeof(request->public_key)) - size = sizeof(request->public_key) - off - 1; - GNUNET_memcpy (&request->public_key[off], - data, - size); - request->public_key[size + off] = '\0'; - return MHD_YES; - } - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - _ ("Unsupported form value `%s'\n"), - key); - return MHD_YES; + 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 (); } - /** - * Continuation called to notify client about result of the - * operation. + * A name/key pair has been successfully registered, or maybe not. * - * @param cls closure - * @param success #GNUNET_SYSERR on failure (including timeout/queue drop/failure to validate) - * #GNUNET_NO if content was already there - * #GNUNET_YES (or other positive value) on success - * @param emsg NULL on success, otherwise an error message + * @param cls the connection + * @param status result of the operation + * @param emsg error message if any */ static void -put_continuation (void *cls, - int32_t success, +register_done_cb (void *cls, + int32_t status, const char *emsg) { - struct Request *request = cls; + struct RequestData *rd = cls; + + MHD_resume_connection (rd->c); + rd->searching = NULL; - request->qe = NULL; - if (0 >= success) + if (GNUNET_SYSERR == status || GNUNET_NO == status) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - _ ("Failed to create record for domain `%s': %s\n"), - request->domain_name, + _ ("Failed to create record for `%s': %s\n"), + rd->register_name, emsg); - request->phase = RP_FAIL; + rd->body = make_json ("error", "true", + "message", emsg, + NULL); + rd->body_length = strlen (rd->body); + rd->code = MHD_HTTP_INTERNAL_SERVER_ERROR; } else - request->phase = RP_SUCCESS; - MHD_resume_connection (request->con); + { + rd->body = make_json ("error", "false", + "message", _ ("no errors"), + NULL); + rd->body_length = strlen (rd->body); + rd->code = MHD_HTTP_OK; + } + run_httpd_now (); } - /** - * Function called if we had an error in zone-to-name mapping. + * 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 -zone_to_name_error (void *cls) +register_do_cb (void *cls, + const struct GNUNET_IDENTITY_PrivateKey *key, + const char *label, + unsigned int count, + const struct GNUNET_GNSRECORD_Data *d) { - struct Request *request = cls; + (void) key; + (void) d; - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - _ ("Error when mapping zone to name\n")); - request->phase = RP_FAIL; - MHD_resume_connection (request->con); - run_httpd_now (); -} + struct RequestData *rd = cls; + rd->searching = NULL; -/** - * Test if a name mapping was found, if so, refuse. If not, initiate storing of the record. - * - * @param cls closure - * @param zone_key public key of the zone - * @param name name that is being mapped (at most 255 characters long) - * @param rd_count number of entries in @a rd array - * @param rd array of records with data to store - */ -static void -zone_to_name_cb (void *cls, - const struct GNUNET_IDENTITY_PrivateKey *zone_key, - const char *name, - unsigned int rd_count, - const struct GNUNET_GNSRECORD_Data *rd) -{ - struct Request *request = cls; - struct GNUNET_GNSRECORD_Data r; - char *rdata; - - (void) rd; - (void) zone_key; - request->qe = NULL; - if (0 != rd_count) + if (0 != count) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, - _ ("Found existing name `%s' for the given key\n"), - name); - request->phase = RP_FAIL; - MHD_resume_connection (request->con); + _ ("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; } - if (GNUNET_OK != GNUNET_GNSRECORD_data_from_identity (&request->pub, - &rdata, - &r.data_size, - &r.record_type)) + + 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")); - request->phase = RP_FAIL; - MHD_resume_connection (request->con); + _ ("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; } - r.data = rdata; - r.expiration_time = UINT64_MAX; - r.flags = GNUNET_GNSRECORD_RF_NONE; - request->qe = GNUNET_NAMESTORE_records_store (ns, - &fcfs_zone_pkey, - request->domain_name, - 1, &r, - &put_continuation, - request); - GNUNET_free (rdata); -} + gd.data = gdraw; + gd.expiration_time = UINT64_MAX; + gd.flags = GNUNET_GNSRECORD_RF_NONE; + rd->searching = GNUNET_NAMESTORE_records_store (namestore, + zone_key, + rd->register_name, + 1, + &gd, + ®ister_done_cb, + rd); + + GNUNET_free (gdraw); +} /** - * We encountered an error in the name lookup. + * An error occurred while iterating the namestore. + * + * @param cls the connection */ static void -lookup_it_error (void *cls) +iterate_error_cb (void *cls) { - struct Request *request = cls; - - MHD_resume_connection (request->con); - request->qe = NULL; - request->phase = RP_FAIL; + 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 (); } - /** - * We got a block back from the namestore. Decrypt it - * and continue to process the result. + * A block was received from the namestore. * - * @param cls the 'struct Request' we are processing - * @param zonekey private key of the zone; NULL on disconnect - * @param label label of the records; NULL on disconnect - * @param rd_count number of entries in @a rd array, 0 if label was deleted - * @param rd array of records with data to store + * @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 -lookup_it_processor (void *cls, - const struct GNUNET_IDENTITY_PrivateKey *zonekey, - const char *label, - unsigned int rd_count, - const struct GNUNET_GNSRECORD_Data *rd) +iterate_do_cb (void *cls, + const struct GNUNET_IDENTITY_PrivateKey *key, + const char *label, + unsigned int count, + const struct GNUNET_GNSRECORD_Data *d) { - struct Request *request = cls; - + (void) key; (void) label; - (void) rd; - (void) zonekey; - if (0 == strcmp (label, request->domain_name)) + (void) d; + + struct RequestData *rd = cls; + + if (0 == strcmp (label, rd->register_name)) { - GNUNET_break (0 != rd_count); + GNUNET_break (0 != count); GNUNET_log (GNUNET_ERROR_TYPE_INFO, - _ ("Found %u existing records for domain `%s'\n"), - rd_count, - request->domain_name); - request->phase = RP_FAIL; + _ ("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 (request->lookup_it, 1); -} + GNUNET_NAMESTORE_zone_iterator_next (rd->iterating, 1); +} +/** + * All entries in the namestore have been iterated over. + * + * @param cls the connection + */ static void -lookup_it_finished (void *cls) +iterate_done_cb (void *cls) { - struct Request *request = cls; - - if (RP_FAIL == request->phase) - { - MHD_resume_connection (request->con); - run_httpd_now (); - return; - } - if (GNUNET_OK != - GNUNET_IDENTITY_public_key_from_string (request->public_key, - &request->pub)) - { - GNUNET_break (0); - request->phase = RP_FAIL; - MHD_resume_connection (request->con); - run_httpd_now (); - return; - } - request->qe = GNUNET_NAMESTORE_zone_to_name (ns, - &fcfs_zone_pkey, - &request->pub, - &zone_to_name_error, - request, - &zone_to_name_cb, - request); + 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; +} /** - * Main MHD callback for handling requests. + * Send a response back to a connected client. * * @param cls unused - * @param connection MHD connection handle - * @param url the requested url - * @param method the HTTP method used ("GET", "PUT", etc.) - * @param version the HTTP version string ("HTTP/1.1" for version 1.1, etc.) - * @param upload_data the data being uploaded (excluding HEADERS, - * for a POST that fits into memory and that is encoded - * with a supported encoding, the POST data will NOT be - * given in upload_data and is instead available as - * part of MHD_get_connection_values; very large POST - * data *will* be made available incrementally in - * upload_data) - * @param upload_data_size set initially to the size of the - * @a upload_data provided; the method must update this - * value to the number of bytes NOT processed; - * @param ptr pointer to location where we store the 'struct Request' - * @return #MHD_YES if the connection was handled successfully, - * #MHD_NO if the socket must be closed due to a serious - * error while handling the request + * @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, @@ -777,300 +661,255 @@ create_response (void *cls, size_t *upload_data_size, void **ptr) { - struct MHD_Response *response; - struct Request *request; - struct GNUNET_IDENTITY_PublicKey pub; - MHD_RESULT ret; - (void) cls; (void) version; - if ((0 == strcmp (method, MHD_HTTP_METHOD_GET)) || - (0 == strcmp (method, MHD_HTTP_METHOD_HEAD))) - { - if (0 == strcmp (url, FCFS_ZONEINFO_URL)) - ret = serve_zoneinfo_page (connection); - else - ret = serve_main_page (connection); - if (ret != MHD_YES) - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - _ ("Failed to create page for `%s'\n"), - url); - return ret; - } - if (0 == strcmp (method, MHD_HTTP_METHOD_POST)) + + struct RequestData *rd = *ptr; + + if (0 == strcmp (method, MHD_HTTP_METHOD_GET)) { - request = *ptr; - if (NULL == request) + /* Handle a previously suspended request */ + if (NULL != rd) + { + return serve_json (rd->c, rd->body, rd->body_length, rd->code); + } + + if (0 == strcmp ("/", url)) { - request = GNUNET_new (struct Request); - request->con = connection; - *ptr = request; - request->pp = MHD_create_post_processor (connection, - 1024, - &post_iterator, - request); - if (NULL == request->pp) + 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) { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - _ ("Failed to setup post processor for `%s'\n"), - url); - return MHD_NO; /* internal error */ + 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; } - if (NULL != request->pp) + + 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)) { - /* evaluate POST data */ - MHD_post_process (request->pp, - upload_data, - *upload_data_size); - if (0 != *upload_data_size) + /* Handle a previously suspended request */ + if (NULL != rd && NULL != rd->body) { - *upload_data_size = 0; - return MHD_YES; + return serve_json (rd->c, rd->body, rd->body_length, rd->code); } - /* done with POST data, serve response */ - MHD_destroy_post_processor (request->pp); - request->pp = NULL; - } - if (GNUNET_OK != - GNUNET_IDENTITY_public_key_from_string (request->public_key, - &pub)) - { - /* parse error */ - return fill_s_reply ("Failed to parse given public key", - request, connection); - } - switch (request->phase) - { - case RP_START: - if (NULL != strchr (request->domain_name, (int) '.')) + + if (NULL == rd) { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - _ ("Domain name must not contain `.'\n")); - request->phase = RP_FAIL; - return fill_s_reply ("Domain name must not contain `.', sorry.", - request, - connection); + rd = GNUNET_new (struct RequestData); + rd->c = connection; + rd->body = NULL; + rd->ptr = NULL; + *ptr = rd; } - if (NULL != strchr (request->domain_name, (int) '+')) + + 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) { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - _ ("Domain name must not contain `+'\n")); - request->phase = RP_FAIL; - return fill_s_reply ("Domain name must not contain `+', sorry.", - request, connection); + 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); + rd->code = MHD_HTTP_PAYLOAD_TOO_LARGE; + 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; } - request->phase = RP_LOOKUP; - MHD_suspend_connection (request->con); - request->lookup_it - = GNUNET_NAMESTORE_zone_iteration_start (ns, - &fcfs_zone_pkey, - &lookup_it_error, - request, - &lookup_it_processor, - request, - &lookup_it_finished, - request); - break; - - case RP_LOOKUP: - break; - - case RP_PUT: - break; - - case RP_FAIL: - return fill_s_reply ("Request failed, sorry.", - request, connection); - - case RP_SUCCESS: - return fill_s_reply ("Success.", - request, connection); - - default: - GNUNET_break (0); - return MHD_NO; - } - return MHD_YES; /* will have a reply later... */ - } - /* unsupported HTTP method */ - response = MHD_create_response_from_buffer (strlen (METHOD_ERROR), - (void *) METHOD_ERROR, - MHD_RESPMEM_PERSISTENT); - ret = MHD_queue_response (connection, - MHD_HTTP_NOT_ACCEPTABLE, - response); - MHD_destroy_response (response); - return ret; -} + /* POST data has been read in its entirety */ -/** - * Callback called upon completion of a request. - * Decrements session reference counter. - * - * @param cls not used - * @param connection connection that completed - * @param con_cls session handle - * @param toe status code - */ -static void -request_completed_callback (void *cls, - struct MHD_Connection *connection, - void **con_cls, - enum MHD_RequestTerminationCode toe) -{ - struct Request *request = *con_cls; + 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; + } - (void) cls; - (void) connection; - (void) toe; - if (NULL == request) - return; - if (NULL != request->pp) - MHD_destroy_post_processor (request->pp); - if (NULL != request->qe) - GNUNET_NAMESTORE_cancel (request->qe); - GNUNET_free (request); -} + rd->register_name = strdup (name); + rd->register_key = strdup (key); + json_decref (json); + GNUNET_JSON_post_parser_cleanup (rd->ptr); -#define UNSIGNED_MHD_LONG_LONG unsigned MHD_LONG_LONG + 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; + } -/** - * Schedule tasks to run MHD server. - */ -static void -run_httpd () -{ - fd_set rs; - fd_set ws; - fd_set es; - struct GNUNET_NETWORK_FDSet *wrs; - struct GNUNET_NETWORK_FDSet *wws; - struct GNUNET_NETWORK_FDSet *wes; - int max; - int haveto; - UNSIGNED_MHD_LONG_LONG timeout; - struct GNUNET_TIME_Relative tv; + 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; + } - FD_ZERO (&rs); - FD_ZERO (&ws); - FD_ZERO (&es); - wrs = GNUNET_NETWORK_fdset_create (); - wes = GNUNET_NETWORK_fdset_create (); - wws = GNUNET_NETWORK_fdset_create (); - max = -1; - GNUNET_assert (MHD_YES == - MHD_get_fdset (httpd, - &rs, - &ws, - &es, - &max)); - haveto = MHD_get_timeout (httpd, - &timeout); - if (haveto == MHD_YES) - tv.rel_value_us = (uint64_t) timeout * 1000LL; - else - tv = GNUNET_TIME_UNIT_FOREVER_REL; - GNUNET_NETWORK_fdset_copy_native (wrs, - &rs, - max + 1); - GNUNET_NETWORK_fdset_copy_native (wws, - &ws, - max + 1); - GNUNET_NETWORK_fdset_copy_native (wes, - &es, - max + 1); - httpd_task = - GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_HIGH, - tv, - wrs, - wws, - &do_httpd, - NULL); - GNUNET_NETWORK_fdset_destroy (wrs); - GNUNET_NETWORK_fdset_destroy (wws); - GNUNET_NETWORK_fdset_destroy (wes); -} + return MHD_queue_response (connection, + MHD_HTTP_FORBIDDEN, + forbidden_page->response); + } + return MHD_queue_response (connection, + MHD_HTTP_NOT_IMPLEMENTED, + forbidden_page->response); +} /** - * Task run whenever HTTP server operations are pending. + * Called when a request is completed. * * @param cls unused + * @param connection the connection + * @param ptr connection-specific data + * @param status status code */ static void -do_httpd (void *cls) +completed_cb (void *cls, + struct MHD_Connection *connection, + void **ptr, + enum MHD_RequestTerminationCode status) { (void) cls; - httpd_task = NULL; - MHD_run (httpd); - run_httpd (); -} + (void) connection; + (void) status; + struct RequestData *rd = *ptr; -/** - * Task run on shutdown. Cleans up everything. - * - * @param cls unused - */ -static void -do_shutdown (void *cls) -{ - (void) cls; - if (NULL != httpd_task) + if (NULL == rd) { - GNUNET_SCHEDULER_cancel (httpd_task); - httpd_task = NULL; + return; } - if (NULL != uzp_task) + + if (NULL == rd->body) { - GNUNET_SCHEDULER_cancel (uzp_task); - uzp_task = NULL; + GNUNET_free (rd->body); } - if (NULL != ns) + + if (NULL != rd->searching) { - GNUNET_NAMESTORE_disconnect (ns); - ns = NULL; + GNUNET_NAMESTORE_cancel (rd->searching); } - if (NULL != httpd) + + if (NULL != rd->register_name) { - MHD_stop_daemon (httpd); - httpd = NULL; + GNUNET_free (rd->register_name); } - if (NULL != id_op) + + if (NULL != rd->register_key) { - GNUNET_IDENTITY_cancel (id_op); - id_op = NULL; + GNUNET_free (rd->register_key); } - if (NULL != identity) + + if (NULL != rd->iterating) { - GNUNET_IDENTITY_disconnect (identity); - identity = NULL; + GNUNET_NAMESTORE_zone_iteration_stop (rd->iterating); } -} + GNUNET_free (rd); +} /** - * Method called to inform about the egos of this peer. - * - * When used with #GNUNET_IDENTITY_create or #GNUNET_IDENTITY_get, this - * function is only called ONCE, and 'NULL' being passed in @a ego does - * indicate an error (for example because name is taken or no default value is - * known). If @a ego is non-NULL and if '*ctx' is set in those callbacks, the - * value WILL be passed to a subsequent call to the identity callback of - * #GNUNET_IDENTITY_connect (if that one was not NULL). + * Called for each ego provided by the identity service. * - * @param cls closure, NULL - * @param ego ego handle - * @param ctx context for application to store data for this ego - * (during the lifetime of this process, initially NULL) - * @param name name assigned by the user for this ego, - * NULL if the user just deleted the ego and it - * must thus no longer be used + * @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, @@ -1078,15 +917,14 @@ identity_cb (void *cls, void **ctx, const char *name) { - int options; - (void) cls; (void) ctx; - if (NULL == name) - return; - if (0 != strcmp (name, - zone)) + + if (NULL == name || 0 != strcmp (name, zone)) + { return; + } + if (NULL == ego) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, @@ -1094,30 +932,24 @@ identity_cb (void *cls, GNUNET_SCHEDULER_shutdown (); return; } - fcfs_zone_pkey = *GNUNET_IDENTITY_ego_get_private_key (ego); - options = MHD_USE_DUAL_STACK | MHD_USE_DEBUG | MHD_ALLOW_SUSPEND_RESUME; + 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 (options, + httpd = MHD_start_daemon (flags, (uint16_t) port, NULL, NULL, &create_response, NULL, - MHD_OPTION_CONNECTION_LIMIT, (unsigned int) 128, - MHD_OPTION_PER_IP_CONNECTION_LIMIT, (unsigned - int) 1, - MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 16, - MHD_OPTION_CONNECTION_MEMORY_LIMIT, (size_t) (4 - * - 1024), - MHD_OPTION_NOTIFY_COMPLETED, - &request_completed_callback, 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); - if (MHD_USE_DEBUG == options) - break; - options = MHD_USE_DEBUG; - } - while (NULL == httpd); + flags = MHD_USE_DEBUG; + } while (NULL == httpd && flags != MHD_USE_DEBUG); + if (NULL == httpd) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, @@ -1125,105 +957,158 @@ identity_cb (void *cls, 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 #GNUNET_SYSERR 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; +} /** - * Main function that will be run. + * Called after the service is up. * * @param cls closure - * @param args remaining command-line arguments - * @param cfgfile name of the configuration file used (for saving, can be NULL!) - * @param cfg configuration + * @param args remaining command line arguments + * @param cfgfile name of the configuration file + * @param cfg the service configuration */ static void -run (void *cls, - char *const *args, - const char *cfgfile, - const struct GNUNET_CONFIGURATION_Handle *cfg) +run_service (void *cls, + char *const *args, + const char *cfgfile, + const struct GNUNET_CONFIGURATION_Handle *cfg) { (void) cls; (void) args; (void) cfgfile; - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_number (cfg, - "fcfsd", - "HTTPPORT", - &port)) + + GNUNET_log_setup ("fcfsd", "WARNING", NULL); + + if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_number (cfg, + "fcfsd", + "HTTPPORT", + &port)) { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "fcfsd", "HTTPPORT"); - return; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + _ ("No port specified, using default value\n")); } - ns = GNUNET_NAMESTORE_connect (cfg); - if (NULL == ns) + + 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); + + 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; } - uzp_task = GNUNET_SCHEDULER_add_now (&update_zoneinfo_page, - NULL); - GNUNET_SCHEDULER_add_shutdown (&do_shutdown, - NULL); -} + 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 for the fcfs daemon. + * The main function of the fcfs daemon. * * @param argc number of arguments from the command line - * @param argv command line arguments - * @return 0 ok, 1 on error + * @parsm argv the command line argumens + * @return 0 successful exit, a different value otherwise */ int -main (int argc, - char *const *argv) +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 that is to be managed by FCFSD"), - &zone)), + (GNUNET_GETOPT_option_string ('z', + "zone", + "EGO", + gettext_noop ("name of the zone managed by FCFSD"), + &zone)), GNUNET_GETOPT_OPTION_END }; - int ret; - - if (GNUNET_OK != - GNUNET_STRINGS_get_utf8_args (argc, argv, - &argc, &argv)) - return 2; - - GNUNET_log_setup ("fcfsd", - "WARNING", - NULL); - ret = - (GNUNET_OK == - GNUNET_PROGRAM_run (argc, - argv, - "gnunet-namestore-fcfsd", - _ ( - "GNU Name System First Come First Serve name registration service"), - options, - &run, NULL)) ? 0 : 1; - GNUNET_free_nz ((void *) argv); - // FIXME - // GNUNET_CRYPTO_ecdsa_key_clear (&fcfs_zone_pkey); - return ret; -} - -/* end of gnunet-namestore-fcfsd.c */ + 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); +} |