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?)
31 #include <gnunet_util_lib.h>
32 #include <microhttpd.h>
33 #include <gnunet_namestore_service.h>
36 * Invalid method page.
38 #define METHOD_ERROR "<html><head><title>Illegal request</title></head><body>Go away.</body></html>"
43 #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>"
48 #define SUBMIT_PAGE "<html><head><title>%s</title></head><body>%s</body></html>"
51 * Mime type for HTML pages.
53 #define MIME_HTML "text/html"
58 #define COOKIE_NAME "gns-fcfs"
72 * Data kept per request.
80 struct Session *session;
83 * Post processor handling form data (IF this is
86 struct MHD_PostProcessor *pp;
89 * URL to serve in response to this POST (if this request
95 * Active request with the namestore.
97 struct GNUNET_NAMESTORE_QueueEntry *qe;
100 * Current processing phase.
105 * Domain name submitted via form.
107 char domain_name[64];
110 * Public key submitted via form.
112 char public_key[1024];
118 * MHD deamon reference.
120 static struct MHD_Daemon *httpd;
125 static GNUNET_SCHEDULER_TaskIdentifier httpd_task;
128 * Handle to the namestore.
130 static struct GNUNET_NAMESTORE_Handle *ns;
133 * Hash of the public key of the fcfsd zone.
135 static GNUNET_HashCode fcfsd_zone;
138 * Private key for the fcfsd zone.
140 static struct GNUNET_CRYPTO_RsaPrivateKey *fcfs_zone_pkey;
144 * Handler that returns a simple static HTTP page.
146 * @param connection connection to use
147 * @return MHD_YES on success
150 serve_main_page (struct MHD_Connection *connection)
153 struct MHD_Response *response;
155 /* return static form */
156 response = MHD_create_response_from_buffer (strlen (MAIN_PAGE),
158 MHD_RESPMEM_PERSISTENT);
159 MHD_add_response_header (response,
160 MHD_HTTP_HEADER_CONTENT_ENCODING,
162 ret = MHD_queue_response (connection,
165 MHD_destroy_response (response);
171 * Send the 'SUBMIT_PAGE'.
173 * @param info information string to send to the user
174 * @param request request information
175 * @param connection connection to use
178 fill_s_reply (const char *info,
179 struct Request *request,
180 struct MHD_Connection *connection)
184 struct MHD_Response *response;
186 GNUNET_asprintf (&reply,
190 /* return static form */
191 response = MHD_create_response_from_buffer (strlen (reply),
193 MHD_RESPMEM_MUST_FREE);
194 MHD_add_response_header (response,
195 MHD_HTTP_HEADER_CONTENT_ENCODING,
197 ret = MHD_queue_response (connection,
200 MHD_destroy_response (response);
206 * Iterator over key-value pairs where the value
207 * maybe made available in increments and/or may
208 * not be zero-terminated. Used for processing
211 * @param cls user-specified closure
212 * @param kind type of the value
213 * @param key 0-terminated key for the value
214 * @param filename name of the uploaded file, NULL if not known
215 * @param content_type mime-type of the data, NULL if not known
216 * @param transfer_encoding encoding of the data, NULL if not known
217 * @param data pointer to size bytes of data at the
219 * @param off offset of data in the overall value
220 * @param size number of bytes in data available
221 * @return MHD_YES to continue iterating,
222 * MHD_NO to abort the iteration
225 post_iterator (void *cls,
226 enum MHD_ValueKind kind,
228 const char *filename,
229 const char *content_type,
230 const char *transfer_encoding,
231 const char *data, uint64_t off, size_t size)
233 struct Request *request = cls;
235 if (0 == strcmp ("domain", key))
237 if (size + off > sizeof(request->domain_name))
238 size = sizeof (request->domain_name) - off;
239 memcpy (&request->domain_name[off],
242 if (size + off < sizeof (request->domain_name))
243 request->domain_name[size+off] = '\0';
246 if (0 == strcmp ("pkey", key))
248 if (size + off > sizeof(request->public_key))
249 size = sizeof (request->public_key) - off;
250 memcpy (&request->public_key[off],
253 if (size + off < sizeof (request->public_key))
254 request->public_key[size+off] = '\0';
257 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
258 "Unsupported form value `%s'\n", key);
264 * Task run whenever HTTP server operations are pending.
267 * @param tc scheduler context
271 const struct GNUNET_SCHEDULER_TaskContext *tc);
275 * Schedule task to run MHD server now.
280 if (GNUNET_SCHEDULER_NO_TASK != httpd_task)
282 GNUNET_SCHEDULER_cancel (httpd_task);
283 httpd_task = GNUNET_SCHEDULER_NO_TASK;
285 httpd_task = GNUNET_SCHEDULER_add_now (&do_httpd, NULL);
290 * Continuation called to notify client about result of the
294 * @param success GNUNET_SYSERR on failure (including timeout/queue drop/failure to validate)
295 * GNUNET_NO if content was already there
296 * GNUNET_YES (or other positive value) on success
297 * @param emsg NULL on success, otherwise an error message
300 put_continuation (void *cls,
304 struct Request *request = cls;
309 GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
310 _("Failed to create record for domain `%s': %s\n"),
311 request->domain_name,
313 request->phase = RP_FAIL;
316 request->phase = RP_SUCCESS;
322 * Process a record that was stored in the namestore.
325 * @param zone_key public key of the zone
326 * @param expire when does the corresponding block in the DHT expire (until
327 * when should we never do a DHT lookup for the same name again)?;
328 * GNUNET_TIME_UNIT_ZERO_ABS if there are no records of any type in the namestore,
329 * or the expiration time of the block in the namestore (even if there are zero
330 * records matching the desired record type)
331 * @param name name that is being mapped (at most 255 characters long)
332 * @param rd_count number of entries in 'rd' array
333 * @param rd array of records with data to store
334 * @param signature signature of the record block, NULL if signature is unavailable (i.e.
335 * because the user queried for a particular record type only)
338 lookup_result_processor (void *cls,
339 const struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded *zone_key,
340 struct GNUNET_TIME_Absolute expire,
343 const struct GNUNET_NAMESTORE_RecordData *rd,
344 const struct GNUNET_CRYPTO_RsaSignature *signature)
346 struct Request *request = cls;
347 struct GNUNET_NAMESTORE_RecordData r;
352 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
353 _("Found %u existing records for domain `%s'\n"),
355 request->domain_name);
356 request->phase = RP_FAIL;
360 r.data = "binary public key";
361 r.expiration = GNUNET_TIME_UNIT_FOREVER_ABS;
362 r.data_size = sizeof ("binary public key");
363 r.record_type = htonl (GNUNET_GNS_TYPE_PKEY);
364 r.flags = htonl (GNUNET_NAMESTORE_RF_AUTHORITY);
365 request->qe = GNUNET_NAMESTORE_record_create (ns,
367 request->domain_name,
375 * Main MHD callback for handling requests.
378 * @param cls argument given together with the function
379 * pointer when the handler was registered with MHD
380 * @param url the requested url
381 * @param method the HTTP method used ("GET", "PUT", etc.)
382 * @param version the HTTP version string (i.e. "HTTP/1.1")
383 * @param upload_data the data being uploaded (excluding HEADERS,
384 * for a POST that fits into memory and that is encoded
385 * with a supported encoding, the POST data will NOT be
386 * given in upload_data and is instead available as
387 * part of MHD_get_connection_values; very large POST
388 * data *will* be made available incrementally in
390 * @param upload_data_size set initially to the size of the
391 * upload_data provided; the method must update this
392 * value to the number of bytes NOT processed;
393 * @param con_cls pointer that the callback can set to some
394 * address and that will be preserved by MHD for future
395 * calls for this request; since the access handler may
396 * be called many times (i.e., for a PUT/POST operation
397 * with plenty of upload data) this allows the application
398 * to easily associate some request-specific state.
399 * If necessary, this state can be cleaned up in the
400 * global "MHD_RequestCompleted" callback (which
401 * can be set with the MHD_OPTION_NOTIFY_COMPLETED).
402 * Initially, <tt>*con_cls</tt> will be NULL.
403 * @return MHS_YES if the connection was handled successfully,
404 * MHS_NO if the socket must be closed due to a serios
405 * error while handling the request
408 create_response (void *cls,
409 struct MHD_Connection *connection,
413 const char *upload_data,
414 size_t *upload_data_size,
417 struct MHD_Response *response;
418 struct Request *request;
421 if ( (0 == strcmp (method, MHD_HTTP_METHOD_GET)) ||
422 (0 == strcmp (method, MHD_HTTP_METHOD_HEAD)) )
424 ret = serve_main_page (connection);
426 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
427 _("Failed to create page for `%s'\n"),
431 if (0 == strcmp (method, MHD_HTTP_METHOD_POST))
436 request = GNUNET_malloc (sizeof (struct Request));
438 request->pp = MHD_create_post_processor (connection, 1024,
439 &post_iterator, request);
440 if (NULL == request->pp)
442 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
443 _("Failed to setup post processor for `%s'\n"),
445 return MHD_NO; /* internal error */
449 if (NULL != request->pp)
451 /* evaluate POST data */
452 MHD_post_process (request->pp,
455 if (0 != *upload_data_size)
457 *upload_data_size = 0;
460 /* done with POST data, serve response */
461 MHD_destroy_post_processor (request->pp);
464 if (strlen (request->public_key) != 2)
467 return fill_s_reply ("Failed to parse given public key",
468 request, connection);
470 fprintf (stderr, "Now in phase %d\n", request->phase);
471 switch (request->phase)
474 request->phase = RP_LOOKUP;
475 request->qe = GNUNET_NAMESTORE_lookup_record (ns,
477 request->domain_name,
478 GNUNET_GNS_TYPE_PKEY,
479 &lookup_result_processor,
487 return fill_s_reply ("Request failed, sorry.",
488 request, connection);
490 return fill_s_reply ("Success.",
491 request, connection);
496 return MHD_YES; /* will have a reply later... */
498 /* unsupported HTTP method */
499 response = MHD_create_response_from_buffer (strlen (METHOD_ERROR),
500 (void *) METHOD_ERROR,
501 MHD_RESPMEM_PERSISTENT);
502 ret = MHD_queue_response (connection,
503 MHD_HTTP_METHOD_NOT_ACCEPTABLE,
505 MHD_destroy_response (response);
511 * Callback called upon completion of a request.
512 * Decrements session reference counter.
514 * @param cls not used
515 * @param connection connection that completed
516 * @param con_cls session handle
517 * @param toe status code
520 request_completed_callback (void *cls,
521 struct MHD_Connection *connection,
523 enum MHD_RequestTerminationCode toe)
525 struct Request *request = *con_cls;
529 if (NULL != request->pp)
530 MHD_destroy_post_processor (request->pp);
531 if (NULL != request->qe)
532 GNUNET_NAMESTORE_cancel (request->qe);
533 GNUNET_free (request);
538 * Schedule tasks to run MHD server.
546 struct GNUNET_NETWORK_FDSet *wrs;
547 struct GNUNET_NETWORK_FDSet *wws;
548 struct GNUNET_NETWORK_FDSet *wes;
551 unsigned MHD_LONG_LONG timeout;
552 struct GNUNET_TIME_Relative tv;
557 wrs = GNUNET_NETWORK_fdset_create ();
558 wes = GNUNET_NETWORK_fdset_create ();
559 wws = GNUNET_NETWORK_fdset_create ();
561 GNUNET_assert (MHD_YES == MHD_get_fdset (httpd, &rs, &ws, &es, &max));
562 haveto = MHD_get_timeout (httpd, &timeout);
563 if (haveto == MHD_YES)
564 tv.rel_value = (uint64_t) timeout;
566 tv = GNUNET_TIME_UNIT_FOREVER_REL;
567 GNUNET_NETWORK_fdset_copy_native (wrs, &rs, max + 1);
568 GNUNET_NETWORK_fdset_copy_native (wws, &ws, max + 1);
569 GNUNET_NETWORK_fdset_copy_native (wes, &es, max + 1);
571 GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_HIGH,
572 GNUNET_SCHEDULER_NO_TASK, tv, wrs, wws,
574 GNUNET_NETWORK_fdset_destroy (wrs);
575 GNUNET_NETWORK_fdset_destroy (wws);
576 GNUNET_NETWORK_fdset_destroy (wes);
581 * Task run whenever HTTP server operations are pending.
584 * @param tc scheduler context
588 const struct GNUNET_SCHEDULER_TaskContext *tc)
590 httpd_task = GNUNET_SCHEDULER_NO_TASK;
597 * Task run on shutdown. Cleans up everything.
600 * @param tc scheduler context
603 do_shutdown (void *cls,
604 const struct GNUNET_SCHEDULER_TaskContext *tc)
606 if (GNUNET_SCHEDULER_NO_TASK != httpd_task)
608 GNUNET_SCHEDULER_cancel (httpd_task);
609 httpd_task = GNUNET_SCHEDULER_NO_TASK;
613 GNUNET_NAMESTORE_disconnect (ns, GNUNET_NO);
618 MHD_stop_daemon (httpd);
621 if (NULL != fcfs_zone_pkey)
623 GNUNET_CRYPTO_rsa_key_free (fcfs_zone_pkey);
624 fcfs_zone_pkey = NULL;
630 * Main function that will be run.
633 * @param args remaining command-line arguments
634 * @param cfgfile name of the configuration file used (for saving, can be NULL!)
635 * @param cfg configuration
638 run (void *cls, char *const *args, const char *cfgfile,
639 const struct GNUNET_CONFIGURATION_Handle *cfg)
642 unsigned long long port;
643 struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded pub;
646 GNUNET_CONFIGURATION_get_value_number (cfg,
651 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
652 _("Option `%s' not specified in configuration section `%s'\n"),
658 GNUNET_CONFIGURATION_get_value_filename (cfg,
663 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
664 _("Option `%s' not specified in configuration section `%s'\n"),
669 fcfs_zone_pkey = GNUNET_CRYPTO_rsa_key_create_from_file (keyfile);
670 GNUNET_free (keyfile);
671 if (NULL == fcfs_zone_pkey)
673 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
674 _("Failed to read or create private zone key\n"));
677 GNUNET_CRYPTO_rsa_key_get_public (fcfs_zone_pkey,
679 GNUNET_CRYPTO_hash (&pub, sizeof (pub), &fcfsd_zone);
680 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
681 _("Managing `%s' as FCFS zone on port %llu\n"),
682 GNUNET_h2s_full (&fcfsd_zone),
684 ns = GNUNET_NAMESTORE_connect (cfg);
687 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
688 _("Failed to connect to namestore\n"));
691 httpd = MHD_start_daemon (MHD_USE_DEBUG,
694 &create_response, NULL,
695 MHD_OPTION_CONNECTION_LIMIT, (unsigned int) 128,
696 MHD_OPTION_PER_IP_CONNECTION_LIMIT, (unsigned int) 1,
697 MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 16,
698 MHD_OPTION_CONNECTION_MEMORY_LIMIT, (size_t) (4 * 1024),
699 MHD_OPTION_NOTIFY_COMPLETED, &request_completed_callback, NULL,
703 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
704 _("Failed to start HTTP server\n"));
705 GNUNET_NAMESTORE_disconnect (ns, GNUNET_NO);
710 GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL,
716 * The main function for the fcfs daemon.
718 * @param argc number of arguments from the command line
719 * @param argv command line arguments
720 * @return 0 ok, 1 on error
723 main (int argc, char *const *argv)
725 static const struct GNUNET_GETOPT_CommandLineOption options[] = {
726 GNUNET_GETOPT_OPTION_END
731 GNUNET_log_setup ("fcfsd", "WARNING", NULL);
734 GNUNET_PROGRAM_run (argc, argv, "fcfsd",
735 _("GNUnet GNS first come first serve registration service"),
737 &run, NULL)) ? 0 : 1;
742 /* end of gnunet-gns-fcfsd.c */