2 This file is part of GNUnet.
3 (C) 2012-2013 Christian Grothoff (and other contributing authors)
5 GNUnet is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published
7 by the Free Software Foundation; either version 3, or (at your
8 option) any later version.
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with GNUnet; see the file COPYING. If not, write to the
17 Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 Boston, MA 02111-1307, USA.
21 * @file gnunet-namestore-fcfsd.c
22 * @brief HTTP daemon that offers first-come-first-serve GNS domain registration
23 * @author Christian Grothoff
26 * - the code currently contains a 'race' between checking that the
27 * domain name is available and allocating it to the new public key
28 * (should this race be solved by namestore or by fcfsd?)
29 * - nicer error reporting to browser
32 #include <microhttpd.h>
33 #include "gnunet_util_lib.h"
34 #include "gnunet_namestore_service.h"
37 * Invalid method page.
39 #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>"
44 #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>"
49 #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>"
52 * Fcfs zoneinfo page (/Zoneinfo)
54 #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>"
56 #define FCFS_ZONEINFO_URL "/Zoneinfo"
59 * Mime type for HTML pages.
61 #define MIME_HTML "text/html"
66 #define COOKIE_NAME "gns-fcfs"
68 #define DEFAULT_ZONEINFO_BUFSIZE 2048
71 * Phases a request goes through.
76 * Start phase (parsing POST, checking).
81 * Lookup to see if the domain name is taken.
86 * Storing of the record.
91 * We're done with success.
96 * Send failure message.
103 * Data kept per request.
109 * Associated session.
111 struct Session *session;
114 * Post processor handling form data (IF this is
117 struct MHD_PostProcessor *pp;
120 * URL to serve in response to this POST (if this request
123 const char *post_url;
126 * Active request with the namestore.
128 struct GNUNET_NAMESTORE_QueueEntry *qe;
131 * Current processing phase.
136 * Domain name submitted via form.
138 char domain_name[64];
141 * Public key submitted via form.
143 char public_key[128];
150 struct ZoneinfoRequest
155 struct MHD_Connection *connection;
160 struct GNUNET_NAMESTORE_ZoneIterator *list_it;
173 * Buffer write offset
179 * MHD deamon reference.
181 static struct MHD_Daemon *httpd;
186 static GNUNET_SCHEDULER_TaskIdentifier httpd_task;
189 * Handle to the namestore.
191 static struct GNUNET_NAMESTORE_Handle *ns;
194 * Private key for the fcfsd zone.
196 static struct GNUNET_CRYPTO_EccPrivateKey *fcfs_zone_pkey;
200 * Task run whenever HTTP server operations are pending.
203 * @param tc scheduler context
207 const struct GNUNET_SCHEDULER_TaskContext *tc);
211 * Schedule task to run MHD server now.
216 if (GNUNET_SCHEDULER_NO_TASK != httpd_task)
218 GNUNET_SCHEDULER_cancel (httpd_task);
219 httpd_task = GNUNET_SCHEDULER_NO_TASK;
221 httpd_task = GNUNET_SCHEDULER_add_now (&do_httpd, NULL);
226 iterate_cb (void *cls,
227 const struct GNUNET_CRYPTO_EccPrivateKey *zone_key,
230 const struct GNUNET_NAMESTORE_RecordData *rd)
232 struct ZoneinfoRequest *zr = cls;
233 struct MHD_Response *response;
244 /* return static form */
245 GNUNET_asprintf (&full_page,
249 response = MHD_create_response_from_buffer (strlen (full_page),
251 MHD_RESPMEM_MUST_FREE);
252 MHD_add_response_header (response,
253 MHD_HTTP_HEADER_CONTENT_TYPE,
255 MHD_queue_response (zr->connection,
258 MHD_destroy_response (response);
259 GNUNET_free (zr->zoneinfo);
267 GNUNET_NAMESTORE_zone_iterator_next (zr->list_it);
271 if (GNUNET_NAMESTORE_TYPE_PKEY != rd->record_type)
273 GNUNET_NAMESTORE_zone_iterator_next (zr->list_it);
277 bytes_free = zr->buf_len - zr->write_offset;
278 pkey = GNUNET_NAMESTORE_value_to_string (rd->record_type,
282 if (bytes_free < (strlen (name) + strlen (pkey) + 40))
284 new_buf = GNUNET_malloc (zr->buf_len * 2);
285 memcpy (new_buf, zr->zoneinfo, zr->write_offset);
286 GNUNET_free (zr->zoneinfo);
287 zr->zoneinfo = new_buf;
290 sprintf (zr->zoneinfo + zr->write_offset,
291 "<tr><td>%s</td><td>%s</td></tr>",
294 zr->write_offset = strlen (zr->zoneinfo);
295 GNUNET_NAMESTORE_zone_iterator_next (zr->list_it);
302 * Handler that returns FCFS zoneinfo page.
304 * @param connection connection to use
305 * @return MHD_YES on success
308 serve_zoneinfo_page (struct MHD_Connection *connection)
310 struct ZoneinfoRequest *zr;
312 zr = GNUNET_new (struct ZoneinfoRequest);
313 zr->zoneinfo = GNUNET_malloc (DEFAULT_ZONEINFO_BUFSIZE);
314 zr->buf_len = DEFAULT_ZONEINFO_BUFSIZE;
315 zr->connection = connection;
316 zr->write_offset = 0;
317 zr->list_it = GNUNET_NAMESTORE_zone_iteration_start (ns,
326 * Handler that returns a simple static HTTP page.
328 * @param connection connection to use
329 * @return MHD_YES on success
332 serve_main_page (struct MHD_Connection *connection)
335 struct MHD_Response *response;
337 /* return static form */
338 response = MHD_create_response_from_buffer (strlen (MAIN_PAGE),
340 MHD_RESPMEM_PERSISTENT);
341 MHD_add_response_header (response,
342 MHD_HTTP_HEADER_CONTENT_TYPE,
344 ret = MHD_queue_response (connection,
347 MHD_destroy_response (response);
353 * Send the 'SUBMIT_PAGE'.
355 * @param info information string to send to the user
356 * @param request request information
357 * @param connection connection to use
360 fill_s_reply (const char *info,
361 struct Request *request,
362 struct MHD_Connection *connection)
366 struct MHD_Response *response;
368 GNUNET_asprintf (&reply,
372 /* return static form */
373 response = MHD_create_response_from_buffer (strlen (reply),
375 MHD_RESPMEM_MUST_FREE);
376 MHD_add_response_header (response,
377 MHD_HTTP_HEADER_CONTENT_TYPE,
379 ret = MHD_queue_response (connection,
382 MHD_destroy_response (response);
388 * Iterator over key-value pairs where the value
389 * maybe made available in increments and/or may
390 * not be zero-terminated. Used for processing
393 * @param cls user-specified closure
394 * @param kind type of the value
395 * @param key 0-terminated key for the value
396 * @param filename name of the uploaded file, NULL if not known
397 * @param content_type mime-type of the data, NULL if not known
398 * @param transfer_encoding encoding of the data, NULL if not known
399 * @param data pointer to size bytes of data at the
401 * @param off offset of data in the overall value
402 * @param size number of bytes in data available
403 * @return MHD_YES to continue iterating,
404 * MHD_NO to abort the iteration
407 post_iterator (void *cls,
408 enum MHD_ValueKind kind,
410 const char *filename,
411 const char *content_type,
412 const char *transfer_encoding,
413 const char *data, uint64_t off, size_t size)
415 struct Request *request = cls;
417 if (0 == strcmp ("domain", key))
419 if (size + off >= sizeof(request->domain_name))
420 size = sizeof (request->domain_name) - off - 1;
421 memcpy (&request->domain_name[off],
424 request->domain_name[size+off] = '\0';
427 if (0 == strcmp ("pkey", key))
429 if (size + off >= sizeof(request->public_key))
430 size = sizeof (request->public_key) - off - 1;
431 memcpy (&request->public_key[off],
434 request->public_key[size+off] = '\0';
437 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
438 _("Unsupported form value `%s'\n"),
447 * Continuation called to notify client about result of the
451 * @param success GNUNET_SYSERR on failure (including timeout/queue drop/failure to validate)
452 * GNUNET_NO if content was already there
453 * GNUNET_YES (or other positive value) on success
454 * @param emsg NULL on success, otherwise an error message
457 put_continuation (void *cls,
461 struct Request *request = cls;
466 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
467 _("Failed to create record for domain `%s': %s\n"),
468 request->domain_name,
470 request->phase = RP_FAIL;
473 request->phase = RP_SUCCESS;
479 * Test if a name mapping was found, if so, refuse. If not, initiate storing of the record.
482 * @param zone_key public key of the zone
483 * @param name name that is being mapped (at most 255 characters long)
484 * @param rd_count number of entries in 'rd' array
485 * @param rd array of records with data to store
488 zone_to_name_cb (void *cls,
489 const struct GNUNET_CRYPTO_EccPrivateKey *zone_key,
491 unsigned int rd_count,
492 const struct GNUNET_NAMESTORE_RecordData *rd)
494 struct Request *request = cls;
495 struct GNUNET_NAMESTORE_RecordData r;
496 struct GNUNET_CRYPTO_ShortHashCode pub;
501 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
502 _("Found existing name `%s' for the given key\n"),
504 request->phase = RP_FAIL;
509 r.data_size = sizeof (pub);
510 r.expiration_time = UINT64_MAX;
511 r.record_type = GNUNET_NAMESTORE_TYPE_PKEY;
512 r.flags = GNUNET_NAMESTORE_RF_AUTHORITY;
513 request->qe = GNUNET_NAMESTORE_records_store (ns,
515 request->domain_name,
523 * Process a record that was stored in the namestore. Used to check if
524 * the requested name already exists in the namestore. If not,
525 * proceed to check if the requested key already exists.
528 * @param rd_count number of entries in 'rd' array
529 * @param rd array of records with data to store
532 lookup_result_processor (void *cls,
533 unsigned int rd_count,
534 const struct GNUNET_NAMESTORE_RecordData *rd)
536 struct Request *request = cls;
537 struct GNUNET_CRYPTO_EccPublicKey pub;
541 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
542 _("Found %u existing records for domain `%s'\n"),
544 request->domain_name);
545 request->phase = RP_FAIL;
550 GNUNET_CRYPTO_ecc_public_key_from_string (request->public_key,
551 strlen (request->public_key),
555 request->phase = RP_FAIL;
559 request->qe = GNUNET_NAMESTORE_zone_to_name (ns,
568 * We got a block back from the namestore. Decrypt it
569 * and continue to process the result.
571 * @param cls the 'struct Request' we are processing
572 * @param block block returned form namestore, NULL on error
575 lookup_block_processor (void *cls,
576 const struct GNUNET_NAMESTORE_Block *block)
578 struct Request *request = cls;
579 struct GNUNET_CRYPTO_EccPublicKey pub;
584 lookup_result_processor (request, 0, NULL);
587 GNUNET_CRYPTO_ecc_key_get_public (fcfs_zone_pkey,
590 GNUNET_NAMESTORE_block_decrypt (block,
592 request->domain_name,
593 &lookup_result_processor,
597 request->phase = RP_FAIL;
605 * Main MHD callback for handling requests.
608 * @param connection MHD connection handle
609 * @param url the requested url
610 * @param method the HTTP method used ("GET", "PUT", etc.)
611 * @param version the HTTP version string (i.e. "HTTP/1.1")
612 * @param upload_data the data being uploaded (excluding HEADERS,
613 * for a POST that fits into memory and that is encoded
614 * with a supported encoding, the POST data will NOT be
615 * given in upload_data and is instead available as
616 * part of MHD_get_connection_values; very large POST
617 * data *will* be made available incrementally in
619 * @param upload_data_size set initially to the size of the
620 * upload_data provided; the method must update this
621 * value to the number of bytes NOT processed;
622 * @param ptr pointer to location where we store the 'struct Request'
623 * @return MHD_YES if the connection was handled successfully,
624 * MHD_NO if the socket must be closed due to a serious
625 * error while handling the request
628 create_response (void *cls,
629 struct MHD_Connection *connection,
633 const char *upload_data,
634 size_t *upload_data_size,
637 struct MHD_Response *response;
638 struct Request *request;
640 struct GNUNET_CRYPTO_EccPublicKey pub;
641 struct GNUNET_HashCode query;
643 if ( (0 == strcmp (method, MHD_HTTP_METHOD_GET)) ||
644 (0 == strcmp (method, MHD_HTTP_METHOD_HEAD)) )
646 if (0 == strcmp (url, FCFS_ZONEINFO_URL))
647 ret = serve_zoneinfo_page (connection);
649 ret = serve_main_page (connection);
651 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
652 _("Failed to create page for `%s'\n"),
656 if (0 == strcmp (method, MHD_HTTP_METHOD_POST))
661 request = GNUNET_malloc (sizeof (struct Request));
663 request->pp = MHD_create_post_processor (connection, 1024,
664 &post_iterator, request);
665 if (NULL == request->pp)
667 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
668 _("Failed to setup post processor for `%s'\n"),
670 return MHD_NO; /* internal error */
674 if (NULL != request->pp)
676 /* evaluate POST data */
677 MHD_post_process (request->pp,
680 if (0 != *upload_data_size)
682 *upload_data_size = 0;
685 /* done with POST data, serve response */
686 MHD_destroy_post_processor (request->pp);
690 GNUNET_CRYPTO_ecc_public_key_from_string (request->public_key,
691 strlen (request->public_key),
695 return fill_s_reply ("Failed to parse given public key",
696 request, connection);
698 switch (request->phase)
701 if (NULL != strchr (request->domain_name, (int) '.'))
703 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
704 _("Domain name must not contain `.'\n"));
705 request->phase = RP_FAIL;
706 return fill_s_reply ("Domain name must not contain `.', sorry.",
707 request, connection);
709 if (NULL != strchr (request->domain_name, (int) '+'))
711 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
712 _("Domain name must not contain `+'\n"));
713 request->phase = RP_FAIL;
714 return fill_s_reply ("Domain name must not contain `+', sorry.",
715 request, connection);
717 request->phase = RP_LOOKUP;
718 GNUNET_CRYPTO_ecc_key_get_public (fcfs_zone_pkey,
720 GNUNET_NAMESTORE_query_from_public_key (&pub,
721 request->domain_name,
723 request->qe = GNUNET_NAMESTORE_lookup_block (ns,
725 &lookup_block_processor,
733 return fill_s_reply ("Request failed, sorry.",
734 request, connection);
736 return fill_s_reply ("Success.",
737 request, connection);
742 return MHD_YES; /* will have a reply later... */
744 /* unsupported HTTP method */
745 response = MHD_create_response_from_buffer (strlen (METHOD_ERROR),
746 (void *) METHOD_ERROR,
747 MHD_RESPMEM_PERSISTENT);
748 ret = MHD_queue_response (connection,
749 MHD_HTTP_METHOD_NOT_ACCEPTABLE,
751 MHD_destroy_response (response);
757 * Callback called upon completion of a request.
758 * Decrements session reference counter.
760 * @param cls not used
761 * @param connection connection that completed
762 * @param con_cls session handle
763 * @param toe status code
766 request_completed_callback (void *cls,
767 struct MHD_Connection *connection,
769 enum MHD_RequestTerminationCode toe)
771 struct Request *request = *con_cls;
775 if (NULL != request->pp)
776 MHD_destroy_post_processor (request->pp);
777 if (NULL != request->qe)
778 GNUNET_NAMESTORE_cancel (request->qe);
779 GNUNET_free (request);
783 #define UNSIGNED_MHD_LONG_LONG unsigned MHD_LONG_LONG
787 * Schedule tasks to run MHD server.
795 struct GNUNET_NETWORK_FDSet *wrs;
796 struct GNUNET_NETWORK_FDSet *wws;
797 struct GNUNET_NETWORK_FDSet *wes;
800 UNSIGNED_MHD_LONG_LONG timeout;
801 struct GNUNET_TIME_Relative tv;
806 wrs = GNUNET_NETWORK_fdset_create ();
807 wes = GNUNET_NETWORK_fdset_create ();
808 wws = GNUNET_NETWORK_fdset_create ();
810 GNUNET_assert (MHD_YES == MHD_get_fdset (httpd, &rs, &ws, &es, &max));
811 haveto = MHD_get_timeout (httpd, &timeout);
812 if (haveto == MHD_YES)
813 tv.rel_value_us = (uint64_t) timeout * 1000LL;
815 tv = GNUNET_TIME_UNIT_FOREVER_REL;
816 GNUNET_NETWORK_fdset_copy_native (wrs, &rs, max + 1);
817 GNUNET_NETWORK_fdset_copy_native (wws, &ws, max + 1);
818 GNUNET_NETWORK_fdset_copy_native (wes, &es, max + 1);
820 GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_HIGH,
823 GNUNET_NETWORK_fdset_destroy (wrs);
824 GNUNET_NETWORK_fdset_destroy (wws);
825 GNUNET_NETWORK_fdset_destroy (wes);
830 * Task run whenever HTTP server operations are pending.
833 * @param tc scheduler context
837 const struct GNUNET_SCHEDULER_TaskContext *tc)
839 httpd_task = GNUNET_SCHEDULER_NO_TASK;
846 * Task run on shutdown. Cleans up everything.
849 * @param tc scheduler context
852 do_shutdown (void *cls,
853 const struct GNUNET_SCHEDULER_TaskContext *tc)
855 if (GNUNET_SCHEDULER_NO_TASK != httpd_task)
857 GNUNET_SCHEDULER_cancel (httpd_task);
858 httpd_task = GNUNET_SCHEDULER_NO_TASK;
862 GNUNET_NAMESTORE_disconnect (ns);
867 MHD_stop_daemon (httpd);
870 if (NULL != fcfs_zone_pkey)
872 GNUNET_free (fcfs_zone_pkey);
873 fcfs_zone_pkey = NULL;
879 * Main function that will be run.
882 * @param args remaining command-line arguments
883 * @param cfgfile name of the configuration file used (for saving, can be NULL!)
884 * @param cfg configuration
887 run (void *cls, char *const *args, const char *cfgfile,
888 const struct GNUNET_CONFIGURATION_Handle *cfg)
891 unsigned long long port;
894 GNUNET_CONFIGURATION_get_value_number (cfg,
899 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
900 "fcfsd", "HTTPPORT");
904 GNUNET_CONFIGURATION_get_value_filename (cfg,
909 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
913 fcfs_zone_pkey = GNUNET_CRYPTO_ecc_key_create_from_file (keyfile);
914 GNUNET_free (keyfile);
915 if (NULL == fcfs_zone_pkey)
917 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
918 _("Failed to read or create private zone key\n"));
921 ns = GNUNET_NAMESTORE_connect (cfg);
924 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
925 _("Failed to connect to namestore\n"));
928 httpd = MHD_start_daemon (MHD_USE_DEBUG,
931 &create_response, NULL,
932 MHD_OPTION_CONNECTION_LIMIT, (unsigned int) 128,
933 MHD_OPTION_PER_IP_CONNECTION_LIMIT, (unsigned int) 1,
934 MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 16,
935 MHD_OPTION_CONNECTION_MEMORY_LIMIT, (size_t) (4 * 1024),
936 MHD_OPTION_NOTIFY_COMPLETED, &request_completed_callback, NULL,
940 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
941 _("Failed to start HTTP server\n"));
942 GNUNET_NAMESTORE_disconnect (ns);
947 GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL,
953 * The main function for the fcfs daemon.
955 * @param argc number of arguments from the command line
956 * @param argv command line arguments
957 * @return 0 ok, 1 on error
960 main (int argc, char *const *argv)
962 static const struct GNUNET_GETOPT_CommandLineOption options[] = {
963 GNUNET_GETOPT_OPTION_END
968 if (GNUNET_OK != GNUNET_STRINGS_get_utf8_args (argc, argv, &argc, &argv))
971 GNUNET_log_setup ("fcfsd", "WARNING", NULL);
974 GNUNET_PROGRAM_run (argc, argv, "fcfsd",
975 _("GNUnet GNS first come first serve registration service"),
977 &run, NULL)) ? 0 : 1;
978 GNUNET_free ((void*) argv);
982 /* end of gnunet-gns-fcfsd.c */