2 This file is part of GNUnet.
3 (C) 2012 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-gns-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
30 * - figure out where this binary should go (is gns the right directory!?)
33 #include <gnunet_util_lib.h>
34 #include <microhttpd.h>
35 #include <gnunet_namestore_service.h>
38 * Invalid method page.
40 #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>"
45 #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? <input type=\"text\" name=\"domain\" /> <p> What is your public key? <input type=\"text\" name=\"pkey\" /> <input type=\"submit\" value=\"Next\" /></body></html>"
50 #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>"
53 * Mime type for HTML pages.
55 #define MIME_HTML "text/html"
60 #define COOKIE_NAME "gns-fcfs"
64 * Phases a request goes through.
69 * Start phase (parsing POST, checking).
74 * Lookup to see if the domain name is taken.
79 * Storing of the record.
84 * We're done with success.
89 * Send failure message.
96 * Data kept per request.
102 * Associated session.
104 struct Session *session;
107 * Post processor handling form data (IF this is
110 struct MHD_PostProcessor *pp;
113 * URL to serve in response to this POST (if this request
116 const char *post_url;
119 * Active request with the namestore.
121 struct GNUNET_NAMESTORE_QueueEntry *qe;
124 * Current processing phase.
129 * Domain name submitted via form.
131 char domain_name[64];
134 * Public key submitted via form.
142 * MHD deamon reference.
144 static struct MHD_Daemon *httpd;
149 static GNUNET_SCHEDULER_TaskIdentifier httpd_task;
152 * Handle to the namestore.
154 static struct GNUNET_NAMESTORE_Handle *ns;
157 * Hash of the public key of the fcfsd zone.
159 static struct GNUNET_CRYPTO_ShortHashCode fcfsd_zone;
162 * Private key for the fcfsd zone.
164 static struct GNUNET_CRYPTO_RsaPrivateKey *fcfs_zone_pkey;
168 * Handler that returns a simple static HTTP page.
170 * @param connection connection to use
171 * @return MHD_YES on success
174 serve_main_page (struct MHD_Connection *connection)
177 struct MHD_Response *response;
179 /* return static form */
180 response = MHD_create_response_from_buffer (strlen (MAIN_PAGE),
182 MHD_RESPMEM_PERSISTENT);
183 MHD_add_response_header (response,
184 MHD_HTTP_HEADER_CONTENT_TYPE,
186 ret = MHD_queue_response (connection,
189 MHD_destroy_response (response);
195 * Send the 'SUBMIT_PAGE'.
197 * @param info information string to send to the user
198 * @param request request information
199 * @param connection connection to use
202 fill_s_reply (const char *info,
203 struct Request *request,
204 struct MHD_Connection *connection)
208 struct MHD_Response *response;
210 GNUNET_asprintf (&reply,
214 /* return static form */
215 response = MHD_create_response_from_buffer (strlen (reply),
217 MHD_RESPMEM_MUST_FREE);
218 MHD_add_response_header (response,
219 MHD_HTTP_HEADER_CONTENT_TYPE,
221 ret = MHD_queue_response (connection,
224 MHD_destroy_response (response);
230 * Iterator over key-value pairs where the value
231 * maybe made available in increments and/or may
232 * not be zero-terminated. Used for processing
235 * @param cls user-specified closure
236 * @param kind type of the value
237 * @param key 0-terminated key for the value
238 * @param filename name of the uploaded file, NULL if not known
239 * @param content_type mime-type of the data, NULL if not known
240 * @param transfer_encoding encoding of the data, NULL if not known
241 * @param data pointer to size bytes of data at the
243 * @param off offset of data in the overall value
244 * @param size number of bytes in data available
245 * @return MHD_YES to continue iterating,
246 * MHD_NO to abort the iteration
249 post_iterator (void *cls,
250 enum MHD_ValueKind kind,
252 const char *filename,
253 const char *content_type,
254 const char *transfer_encoding,
255 const char *data, uint64_t off, size_t size)
257 struct Request *request = cls;
259 if (0 == strcmp ("domain", key))
261 if (size + off >= sizeof(request->domain_name))
262 size = sizeof (request->domain_name) - off - 1;
263 memcpy (&request->domain_name[off],
266 request->domain_name[size+off] = '\0';
269 if (0 == strcmp ("pkey", key))
271 if (size + off >= sizeof(request->public_key))
272 size = sizeof (request->public_key) - off - 1;
273 memcpy (&request->public_key[off],
276 request->public_key[size+off] = '\0';
279 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
280 _("Unsupported form value `%s'\n"),
287 * Task run whenever HTTP server operations are pending.
290 * @param tc scheduler context
294 const struct GNUNET_SCHEDULER_TaskContext *tc);
298 * Schedule task to run MHD server now.
303 if (GNUNET_SCHEDULER_NO_TASK != httpd_task)
305 GNUNET_SCHEDULER_cancel (httpd_task);
306 httpd_task = GNUNET_SCHEDULER_NO_TASK;
308 httpd_task = GNUNET_SCHEDULER_add_now (&do_httpd, NULL);
313 * Continuation called to notify client about result of the
317 * @param success GNUNET_SYSERR on failure (including timeout/queue drop/failure to validate)
318 * GNUNET_NO if content was already there
319 * GNUNET_YES (or other positive value) on success
320 * @param emsg NULL on success, otherwise an error message
323 put_continuation (void *cls,
327 struct Request *request = cls;
332 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
333 _("Failed to create record for domain `%s': %s\n"),
334 request->domain_name,
336 request->phase = RP_FAIL;
339 request->phase = RP_SUCCESS;
345 * Test if a name mapping was found, if so, refuse. If not, initiate storing of the record.
348 * @param zone_key public key of the zone
349 * @param expire when does the corresponding block in the DHT expire (until
350 * when should we never do a DHT lookup for the same name again)?;
351 * GNUNET_TIME_UNIT_ZERO_ABS if there are no records of any type in the namestore,
352 * or the expiration time of the block in the namestore (even if there are zero
353 * records matching the desired record type)
354 * @param name name that is being mapped (at most 255 characters long)
355 * @param rd_count number of entries in 'rd' array
356 * @param rd array of records with data to store
357 * @param signature signature of the record block, NULL if signature is unavailable (i.e.
358 * because the user queried for a particular record type only)
361 zone_to_name_cb (void *cls,
362 const struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded *zone_key,
363 struct GNUNET_TIME_Absolute expire,
365 unsigned int rd_count,
366 const struct GNUNET_NAMESTORE_RecordData *rd,
367 const struct GNUNET_CRYPTO_RsaSignature *signature)
369 struct Request *request = cls;
370 struct GNUNET_NAMESTORE_RecordData r;
371 struct GNUNET_CRYPTO_ShortHashCode pub;
376 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
377 _("Found existing name `%s' for the given key\n"),
379 request->phase = RP_FAIL;
383 GNUNET_assert (GNUNET_OK ==
384 GNUNET_CRYPTO_short_hash_from_string2 (request->public_key,
385 strlen (request->public_key),
388 r.data_size = sizeof (pub);
389 r.expiration_time = UINT64_MAX;
390 r.record_type = GNUNET_NAMESTORE_TYPE_PKEY;
391 r.flags = GNUNET_NAMESTORE_RF_AUTHORITY;
392 request->qe = GNUNET_NAMESTORE_record_create (ns,
394 request->domain_name,
402 * Process a record that was stored in the namestore. Used to check if
403 * the requested name already exists in the namestore. If not,
404 * proceed to check if the requested key already exists.
407 * @param zone_key public key of the zone
408 * @param expire when does the corresponding block in the DHT expire (until
409 * when should we never do a DHT lookup for the same name again)?;
410 * GNUNET_TIME_UNIT_ZERO_ABS if there are no records of any type in the namestore,
411 * or the expiration time of the block in the namestore (even if there are zero
412 * records matching the desired record type)
413 * @param name name that is being mapped (at most 255 characters long)
414 * @param rd_count number of entries in 'rd' array
415 * @param rd array of records with data to store
416 * @param signature signature of the record block, NULL if signature is unavailable (i.e.
417 * because the user queried for a particular record type only)
420 lookup_result_processor (void *cls,
421 const struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded *zone_key,
422 struct GNUNET_TIME_Absolute expire,
424 unsigned int rd_count,
425 const struct GNUNET_NAMESTORE_RecordData *rd,
426 const struct GNUNET_CRYPTO_RsaSignature *signature)
428 struct Request *request = cls;
429 struct GNUNET_CRYPTO_ShortHashCode pub;
432 GNUNET_assert (GNUNET_OK ==
433 GNUNET_CRYPTO_short_hash_from_string2 (request->public_key,
434 strlen (request->public_key),
438 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
439 _("Found %u existing records for domain `%s'\n"),
441 request->domain_name);
442 request->phase = RP_FAIL;
446 request->qe = GNUNET_NAMESTORE_zone_to_name (ns,
455 * Main MHD callback for handling requests.
458 * @param connection MHD connection handle
459 * @param url the requested url
460 * @param method the HTTP method used ("GET", "PUT", etc.)
461 * @param version the HTTP version string (i.e. "HTTP/1.1")
462 * @param upload_data the data being uploaded (excluding HEADERS,
463 * for a POST that fits into memory and that is encoded
464 * with a supported encoding, the POST data will NOT be
465 * given in upload_data and is instead available as
466 * part of MHD_get_connection_values; very large POST
467 * data *will* be made available incrementally in
469 * @param upload_data_size set initially to the size of the
470 * upload_data provided; the method must update this
471 * value to the number of bytes NOT processed;
472 * @param ptr pointer to location where we store the 'struct Request'
473 * @return MHD_YES if the connection was handled successfully,
474 * MHD_NO if the socket must be closed due to a serious
475 * error while handling the request
478 create_response (void *cls,
479 struct MHD_Connection *connection,
483 const char *upload_data,
484 size_t *upload_data_size,
487 struct MHD_Response *response;
488 struct Request *request;
490 struct GNUNET_CRYPTO_ShortHashCode pub;
492 if ( (0 == strcmp (method, MHD_HTTP_METHOD_GET)) ||
493 (0 == strcmp (method, MHD_HTTP_METHOD_HEAD)) )
495 ret = serve_main_page (connection);
497 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
498 _("Failed to create page for `%s'\n"),
502 if (0 == strcmp (method, MHD_HTTP_METHOD_POST))
507 request = GNUNET_malloc (sizeof (struct Request));
509 request->pp = MHD_create_post_processor (connection, 1024,
510 &post_iterator, request);
511 if (NULL == request->pp)
513 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
514 _("Failed to setup post processor for `%s'\n"),
516 return MHD_NO; /* internal error */
520 if (NULL != request->pp)
522 /* evaluate POST data */
523 MHD_post_process (request->pp,
526 if (0 != *upload_data_size)
528 *upload_data_size = 0;
531 /* done with POST data, serve response */
532 MHD_destroy_post_processor (request->pp);
536 GNUNET_CRYPTO_short_hash_from_string2 (request->public_key,
537 strlen (request->public_key),
541 return fill_s_reply ("Failed to parse given public key",
542 request, connection);
544 switch (request->phase)
547 request->phase = RP_LOOKUP;
548 request->qe = GNUNET_NAMESTORE_lookup_record (ns,
550 request->domain_name,
551 GNUNET_NAMESTORE_TYPE_PKEY,
552 &lookup_result_processor,
560 return fill_s_reply ("Request failed, sorry.",
561 request, connection);
563 return fill_s_reply ("Success.",
564 request, connection);
569 return MHD_YES; /* will have a reply later... */
571 /* unsupported HTTP method */
572 response = MHD_create_response_from_buffer (strlen (METHOD_ERROR),
573 (void *) METHOD_ERROR,
574 MHD_RESPMEM_PERSISTENT);
575 ret = MHD_queue_response (connection,
576 MHD_HTTP_METHOD_NOT_ACCEPTABLE,
578 MHD_destroy_response (response);
584 * Callback called upon completion of a request.
585 * Decrements session reference counter.
587 * @param cls not used
588 * @param connection connection that completed
589 * @param con_cls session handle
590 * @param toe status code
593 request_completed_callback (void *cls,
594 struct MHD_Connection *connection,
596 enum MHD_RequestTerminationCode toe)
598 struct Request *request = *con_cls;
602 if (NULL != request->pp)
603 MHD_destroy_post_processor (request->pp);
604 if (NULL != request->qe)
605 GNUNET_NAMESTORE_cancel (request->qe);
606 GNUNET_free (request);
611 * Schedule tasks to run MHD server.
619 struct GNUNET_NETWORK_FDSet *wrs;
620 struct GNUNET_NETWORK_FDSet *wws;
621 struct GNUNET_NETWORK_FDSet *wes;
624 unsigned MHD_LONG_LONG timeout;
625 struct GNUNET_TIME_Relative tv;
630 wrs = GNUNET_NETWORK_fdset_create ();
631 wes = GNUNET_NETWORK_fdset_create ();
632 wws = GNUNET_NETWORK_fdset_create ();
634 GNUNET_assert (MHD_YES == MHD_get_fdset (httpd, &rs, &ws, &es, &max));
635 haveto = MHD_get_timeout (httpd, &timeout);
636 if (haveto == MHD_YES)
637 tv.rel_value = (uint64_t) timeout;
639 tv = GNUNET_TIME_UNIT_FOREVER_REL;
640 GNUNET_NETWORK_fdset_copy_native (wrs, &rs, max + 1);
641 GNUNET_NETWORK_fdset_copy_native (wws, &ws, max + 1);
642 GNUNET_NETWORK_fdset_copy_native (wes, &es, max + 1);
644 GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_HIGH,
647 GNUNET_NETWORK_fdset_destroy (wrs);
648 GNUNET_NETWORK_fdset_destroy (wws);
649 GNUNET_NETWORK_fdset_destroy (wes);
654 * Task run whenever HTTP server operations are pending.
657 * @param tc scheduler context
661 const struct GNUNET_SCHEDULER_TaskContext *tc)
663 httpd_task = GNUNET_SCHEDULER_NO_TASK;
670 * Task run on shutdown. Cleans up everything.
673 * @param tc scheduler context
676 do_shutdown (void *cls,
677 const struct GNUNET_SCHEDULER_TaskContext *tc)
679 if (GNUNET_SCHEDULER_NO_TASK != httpd_task)
681 GNUNET_SCHEDULER_cancel (httpd_task);
682 httpd_task = GNUNET_SCHEDULER_NO_TASK;
686 GNUNET_NAMESTORE_disconnect (ns);
691 MHD_stop_daemon (httpd);
694 if (NULL != fcfs_zone_pkey)
696 GNUNET_CRYPTO_rsa_key_free (fcfs_zone_pkey);
697 fcfs_zone_pkey = NULL;
703 * Main function that will be run.
706 * @param args remaining command-line arguments
707 * @param cfgfile name of the configuration file used (for saving, can be NULL!)
708 * @param cfg configuration
711 run (void *cls, char *const *args, const char *cfgfile,
712 const struct GNUNET_CONFIGURATION_Handle *cfg)
715 unsigned long long port;
716 struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded pub;
719 GNUNET_CONFIGURATION_get_value_number (cfg,
724 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
725 _("Option `%s' not specified in configuration section `%s'\n"),
731 GNUNET_CONFIGURATION_get_value_filename (cfg,
736 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
737 _("Option `%s' not specified in configuration section `%s'\n"),
742 fcfs_zone_pkey = GNUNET_CRYPTO_rsa_key_create_from_file (keyfile);
743 GNUNET_free (keyfile);
744 if (NULL == fcfs_zone_pkey)
746 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
747 _("Failed to read or create private zone key\n"));
750 GNUNET_CRYPTO_rsa_key_get_public (fcfs_zone_pkey,
752 GNUNET_CRYPTO_short_hash (&pub, sizeof (pub), &fcfsd_zone);
753 ns = GNUNET_NAMESTORE_connect (cfg);
756 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
757 _("Failed to connect to namestore\n"));
760 httpd = MHD_start_daemon (MHD_USE_DEBUG,
763 &create_response, NULL,
764 MHD_OPTION_CONNECTION_LIMIT, (unsigned int) 128,
765 MHD_OPTION_PER_IP_CONNECTION_LIMIT, (unsigned int) 1,
766 MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 16,
767 MHD_OPTION_CONNECTION_MEMORY_LIMIT, (size_t) (4 * 1024),
768 MHD_OPTION_NOTIFY_COMPLETED, &request_completed_callback, NULL,
772 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
773 _("Failed to start HTTP server\n"));
774 GNUNET_NAMESTORE_disconnect (ns);
779 GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL,
785 * The main function for the fcfs daemon.
787 * @param argc number of arguments from the command line
788 * @param argv command line arguments
789 * @return 0 ok, 1 on error
792 main (int argc, char *const *argv)
794 static const struct GNUNET_GETOPT_CommandLineOption options[] = {
795 GNUNET_GETOPT_OPTION_END
800 if (GNUNET_OK != GNUNET_STRINGS_get_utf8_args (argc, argv, &argc, &argv))
803 GNUNET_log_setup ("fcfsd", "WARNING", NULL);
806 GNUNET_PROGRAM_run (argc, argv, "fcfsd",
807 _("GNUnet GNS first come first serve registration service"),
809 &run, NULL)) ? 0 : 1;
814 /* end of gnunet-gns-fcfsd.c */