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 * - actually parse uploaded public key and store it
27 * - the code currently contains a 'race' between checking that the
28 * domain name is available and allocating it to the new public key
29 * (should this race be solved by namestore or by fcfsd?)
30 * - nicer error reporting to browser
31 * - figure out where this binary should go (is gns the right directory!?)
34 #include <gnunet_util_lib.h>
35 #include <microhttpd.h>
36 #include <gnunet_namestore_service.h>
39 * Invalid method page.
41 #define METHOD_ERROR "<html><head><title>Illegal request</title></head><body>Go away.</body></html>"
46 #define MAIN_PAGE "<html><head><title>Welcome</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>"
51 #define SUBMIT_PAGE "<html><head><title>%s</title></head><body>%s</body></html>"
54 * Mime type for HTML pages.
56 #define MIME_HTML "text/html"
61 #define COOKIE_NAME "gns-fcfs"
65 * Phases a request goes through.
70 * Start phase (parsing POST, checking).
75 * Lookup to see if the domain name is taken.
80 * Storing of the record.
85 * We're done with success.
90 * Send failure message.
97 * Data kept per request.
103 * Associated session.
105 struct Session *session;
108 * Post processor handling form data (IF this is
111 struct MHD_PostProcessor *pp;
114 * URL to serve in response to this POST (if this request
117 const char *post_url;
120 * Active request with the namestore.
122 struct GNUNET_NAMESTORE_QueueEntry *qe;
125 * Current processing phase.
130 * Domain name submitted via form.
132 char domain_name[64];
135 * Public key submitted via form.
137 char public_key[1024];
143 * MHD deamon reference.
145 static struct MHD_Daemon *httpd;
150 static GNUNET_SCHEDULER_TaskIdentifier httpd_task;
153 * Handle to the namestore.
155 static struct GNUNET_NAMESTORE_Handle *ns;
158 * Hash of the public key of the fcfsd zone.
160 static GNUNET_HashCode fcfsd_zone;
163 * Private key for the fcfsd zone.
165 static struct GNUNET_CRYPTO_RsaPrivateKey *fcfs_zone_pkey;
169 * Handler that returns a simple static HTTP page.
171 * @param connection connection to use
172 * @return MHD_YES on success
175 serve_main_page (struct MHD_Connection *connection)
178 struct MHD_Response *response;
180 /* return static form */
181 response = MHD_create_response_from_buffer (strlen (MAIN_PAGE),
183 MHD_RESPMEM_PERSISTENT);
184 MHD_add_response_header (response,
185 MHD_HTTP_HEADER_CONTENT_ENCODING,
187 ret = MHD_queue_response (connection,
190 MHD_destroy_response (response);
196 * Send the 'SUBMIT_PAGE'.
198 * @param info information string to send to the user
199 * @param request request information
200 * @param connection connection to use
203 fill_s_reply (const char *info,
204 struct Request *request,
205 struct MHD_Connection *connection)
209 struct MHD_Response *response;
211 GNUNET_asprintf (&reply,
215 /* return static form */
216 response = MHD_create_response_from_buffer (strlen (reply),
218 MHD_RESPMEM_MUST_FREE);
219 MHD_add_response_header (response,
220 MHD_HTTP_HEADER_CONTENT_ENCODING,
222 ret = MHD_queue_response (connection,
225 MHD_destroy_response (response);
231 * Iterator over key-value pairs where the value
232 * maybe made available in increments and/or may
233 * not be zero-terminated. Used for processing
236 * @param cls user-specified closure
237 * @param kind type of the value
238 * @param key 0-terminated key for the value
239 * @param filename name of the uploaded file, NULL if not known
240 * @param content_type mime-type of the data, NULL if not known
241 * @param transfer_encoding encoding of the data, NULL if not known
242 * @param data pointer to size bytes of data at the
244 * @param off offset of data in the overall value
245 * @param size number of bytes in data available
246 * @return MHD_YES to continue iterating,
247 * MHD_NO to abort the iteration
250 post_iterator (void *cls,
251 enum MHD_ValueKind kind,
253 const char *filename,
254 const char *content_type,
255 const char *transfer_encoding,
256 const char *data, uint64_t off, size_t size)
258 struct Request *request = cls;
260 if (0 == strcmp ("domain", key))
262 if (size + off >= sizeof(request->domain_name))
263 size = sizeof (request->domain_name) - off - 1;
264 memcpy (&request->domain_name[off],
267 request->domain_name[size+off] = '\0';
270 if (0 == strcmp ("pkey", key))
272 if (size + off >= sizeof(request->public_key))
273 size = sizeof (request->public_key) - off - 1;
274 memcpy (&request->public_key[off],
277 request->public_key[size+off] = '\0';
280 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
281 _("Unsupported form value `%s'\n"),
288 * Task run whenever HTTP server operations are pending.
291 * @param tc scheduler context
295 const struct GNUNET_SCHEDULER_TaskContext *tc);
299 * Schedule task to run MHD server now.
304 if (GNUNET_SCHEDULER_NO_TASK != httpd_task)
306 GNUNET_SCHEDULER_cancel (httpd_task);
307 httpd_task = GNUNET_SCHEDULER_NO_TASK;
309 httpd_task = GNUNET_SCHEDULER_add_now (&do_httpd, NULL);
314 * Continuation called to notify client about result of the
318 * @param success GNUNET_SYSERR on failure (including timeout/queue drop/failure to validate)
319 * GNUNET_NO if content was already there
320 * GNUNET_YES (or other positive value) on success
321 * @param emsg NULL on success, otherwise an error message
324 put_continuation (void *cls,
328 struct Request *request = cls;
333 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
334 _("Failed to create record for domain `%s': %s\n"),
335 request->domain_name,
337 request->phase = RP_FAIL;
340 request->phase = RP_SUCCESS;
346 * Process a record that was stored in the namestore.
349 * @param zone_key public key of the zone
350 * @param expire when does the corresponding block in the DHT expire (until
351 * when should we never do a DHT lookup for the same name again)?;
352 * GNUNET_TIME_UNIT_ZERO_ABS if there are no records of any type in the namestore,
353 * or the expiration time of the block in the namestore (even if there are zero
354 * records matching the desired record type)
355 * @param name name that is being mapped (at most 255 characters long)
356 * @param rd_count number of entries in 'rd' array
357 * @param rd array of records with data to store
358 * @param signature signature of the record block, NULL if signature is unavailable (i.e.
359 * because the user queried for a particular record type only)
362 lookup_result_processor (void *cls,
363 const struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded *zone_key,
364 struct GNUNET_TIME_Absolute expire,
367 const struct GNUNET_NAMESTORE_RecordData *rd,
368 const struct GNUNET_CRYPTO_RsaSignature *signature)
370 struct Request *request = cls;
371 struct GNUNET_NAMESTORE_RecordData r;
376 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
377 _("Found %u existing records for domain `%s'\n"),
379 request->domain_name);
380 request->phase = RP_FAIL;
384 r.data = "binary public key";
385 r.expiration = GNUNET_TIME_UNIT_FOREVER_ABS;
386 r.data_size = sizeof ("binary public key");
387 r.record_type = htonl (GNUNET_GNS_TYPE_PKEY);
388 r.flags = htonl (GNUNET_NAMESTORE_RF_AUTHORITY);
389 request->qe = GNUNET_NAMESTORE_record_create (ns,
391 request->domain_name,
399 * Main MHD callback for handling requests.
402 * @param cls argument given together with the function
403 * pointer when the handler was registered with MHD
404 * @param url the requested url
405 * @param method the HTTP method used ("GET", "PUT", etc.)
406 * @param version the HTTP version string (i.e. "HTTP/1.1")
407 * @param upload_data the data being uploaded (excluding HEADERS,
408 * for a POST that fits into memory and that is encoded
409 * with a supported encoding, the POST data will NOT be
410 * given in upload_data and is instead available as
411 * part of MHD_get_connection_values; very large POST
412 * data *will* be made available incrementally in
414 * @param upload_data_size set initially to the size of the
415 * upload_data provided; the method must update this
416 * value to the number of bytes NOT processed;
417 * @param con_cls pointer that the callback can set to some
418 * address and that will be preserved by MHD for future
419 * calls for this request; since the access handler may
420 * be called many times (i.e., for a PUT/POST operation
421 * with plenty of upload data) this allows the application
422 * to easily associate some request-specific state.
423 * If necessary, this state can be cleaned up in the
424 * global "MHD_RequestCompleted" callback (which
425 * can be set with the MHD_OPTION_NOTIFY_COMPLETED).
426 * Initially, <tt>*con_cls</tt> will be NULL.
427 * @return MHS_YES if the connection was handled successfully,
428 * MHS_NO if the socket must be closed due to a serios
429 * error while handling the request
432 create_response (void *cls,
433 struct MHD_Connection *connection,
437 const char *upload_data,
438 size_t *upload_data_size,
441 struct MHD_Response *response;
442 struct Request *request;
445 if ( (0 == strcmp (method, MHD_HTTP_METHOD_GET)) ||
446 (0 == strcmp (method, MHD_HTTP_METHOD_HEAD)) )
448 ret = serve_main_page (connection);
450 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
451 _("Failed to create page for `%s'\n"),
455 if (0 == strcmp (method, MHD_HTTP_METHOD_POST))
460 request = GNUNET_malloc (sizeof (struct Request));
462 request->pp = MHD_create_post_processor (connection, 1024,
463 &post_iterator, request);
464 if (NULL == request->pp)
466 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
467 _("Failed to setup post processor for `%s'\n"),
469 return MHD_NO; /* internal error */
473 if (NULL != request->pp)
475 /* evaluate POST data */
476 MHD_post_process (request->pp,
479 if (0 != *upload_data_size)
481 *upload_data_size = 0;
484 /* done with POST data, serve response */
485 MHD_destroy_post_processor (request->pp);
488 if (strlen (request->public_key) != 2)
491 return fill_s_reply ("Failed to parse given public key",
492 request, connection);
494 switch (request->phase)
497 request->phase = RP_LOOKUP;
498 request->qe = GNUNET_NAMESTORE_lookup_record (ns,
500 request->domain_name,
501 GNUNET_GNS_TYPE_PKEY,
502 &lookup_result_processor,
510 return fill_s_reply ("Request failed, sorry.",
511 request, connection);
513 return fill_s_reply ("Success.",
514 request, connection);
519 return MHD_YES; /* will have a reply later... */
521 /* unsupported HTTP method */
522 response = MHD_create_response_from_buffer (strlen (METHOD_ERROR),
523 (void *) METHOD_ERROR,
524 MHD_RESPMEM_PERSISTENT);
525 ret = MHD_queue_response (connection,
526 MHD_HTTP_METHOD_NOT_ACCEPTABLE,
528 MHD_destroy_response (response);
534 * Callback called upon completion of a request.
535 * Decrements session reference counter.
537 * @param cls not used
538 * @param connection connection that completed
539 * @param con_cls session handle
540 * @param toe status code
543 request_completed_callback (void *cls,
544 struct MHD_Connection *connection,
546 enum MHD_RequestTerminationCode toe)
548 struct Request *request = *con_cls;
552 if (NULL != request->pp)
553 MHD_destroy_post_processor (request->pp);
554 if (NULL != request->qe)
555 GNUNET_NAMESTORE_cancel (request->qe);
556 GNUNET_free (request);
561 * Schedule tasks to run MHD server.
569 struct GNUNET_NETWORK_FDSet *wrs;
570 struct GNUNET_NETWORK_FDSet *wws;
571 struct GNUNET_NETWORK_FDSet *wes;
574 unsigned MHD_LONG_LONG timeout;
575 struct GNUNET_TIME_Relative tv;
580 wrs = GNUNET_NETWORK_fdset_create ();
581 wes = GNUNET_NETWORK_fdset_create ();
582 wws = GNUNET_NETWORK_fdset_create ();
584 GNUNET_assert (MHD_YES == MHD_get_fdset (httpd, &rs, &ws, &es, &max));
585 haveto = MHD_get_timeout (httpd, &timeout);
586 if (haveto == MHD_YES)
587 tv.rel_value = (uint64_t) timeout;
589 tv = GNUNET_TIME_UNIT_FOREVER_REL;
590 GNUNET_NETWORK_fdset_copy_native (wrs, &rs, max + 1);
591 GNUNET_NETWORK_fdset_copy_native (wws, &ws, max + 1);
592 GNUNET_NETWORK_fdset_copy_native (wes, &es, max + 1);
594 GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_HIGH,
595 GNUNET_SCHEDULER_NO_TASK, tv, wrs, wws,
597 GNUNET_NETWORK_fdset_destroy (wrs);
598 GNUNET_NETWORK_fdset_destroy (wws);
599 GNUNET_NETWORK_fdset_destroy (wes);
604 * Task run whenever HTTP server operations are pending.
607 * @param tc scheduler context
611 const struct GNUNET_SCHEDULER_TaskContext *tc)
613 httpd_task = GNUNET_SCHEDULER_NO_TASK;
620 * Task run on shutdown. Cleans up everything.
623 * @param tc scheduler context
626 do_shutdown (void *cls,
627 const struct GNUNET_SCHEDULER_TaskContext *tc)
629 if (GNUNET_SCHEDULER_NO_TASK != httpd_task)
631 GNUNET_SCHEDULER_cancel (httpd_task);
632 httpd_task = GNUNET_SCHEDULER_NO_TASK;
636 GNUNET_NAMESTORE_disconnect (ns, GNUNET_NO);
641 MHD_stop_daemon (httpd);
644 if (NULL != fcfs_zone_pkey)
646 GNUNET_CRYPTO_rsa_key_free (fcfs_zone_pkey);
647 fcfs_zone_pkey = NULL;
653 * Main function that will be run.
656 * @param args remaining command-line arguments
657 * @param cfgfile name of the configuration file used (for saving, can be NULL!)
658 * @param cfg configuration
661 run (void *cls, char *const *args, const char *cfgfile,
662 const struct GNUNET_CONFIGURATION_Handle *cfg)
665 unsigned long long port;
666 struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded pub;
669 GNUNET_CONFIGURATION_get_value_number (cfg,
674 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
675 _("Option `%s' not specified in configuration section `%s'\n"),
681 GNUNET_CONFIGURATION_get_value_filename (cfg,
686 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
687 _("Option `%s' not specified in configuration section `%s'\n"),
692 fcfs_zone_pkey = GNUNET_CRYPTO_rsa_key_create_from_file (keyfile);
693 GNUNET_free (keyfile);
694 if (NULL == fcfs_zone_pkey)
696 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
697 _("Failed to read or create private zone key\n"));
700 GNUNET_CRYPTO_rsa_key_get_public (fcfs_zone_pkey,
702 GNUNET_CRYPTO_hash (&pub, sizeof (pub), &fcfsd_zone);
703 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
704 _("Managing `%s' as FCFS zone on port %llu\n"),
705 GNUNET_h2s_full (&fcfsd_zone),
707 ns = GNUNET_NAMESTORE_connect (cfg);
710 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
711 _("Failed to connect to namestore\n"));
714 httpd = MHD_start_daemon (MHD_USE_DEBUG,
717 &create_response, NULL,
718 MHD_OPTION_CONNECTION_LIMIT, (unsigned int) 128,
719 MHD_OPTION_PER_IP_CONNECTION_LIMIT, (unsigned int) 1,
720 MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 16,
721 MHD_OPTION_CONNECTION_MEMORY_LIMIT, (size_t) (4 * 1024),
722 MHD_OPTION_NOTIFY_COMPLETED, &request_completed_callback, NULL,
726 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
727 _("Failed to start HTTP server\n"));
728 GNUNET_NAMESTORE_disconnect (ns, GNUNET_NO);
733 GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL,
739 * The main function for the fcfs daemon.
741 * @param argc number of arguments from the command line
742 * @param argv command line arguments
743 * @return 0 ok, 1 on error
746 main (int argc, char *const *argv)
748 static const struct GNUNET_GETOPT_CommandLineOption options[] = {
749 GNUNET_GETOPT_OPTION_END
754 GNUNET_log_setup ("fcfsd", "WARNING", NULL);
757 GNUNET_PROGRAM_run (argc, argv, "fcfsd",
758 _("GNUnet GNS first come first serve registration service"),
760 &run, NULL)) ? 0 : 1;
765 /* end of gnunet-gns-fcfsd.c */