-starting with a simple domain registration WWW service
[oweals/gnunet.git] / src / gns / gnunet-gns-fcfsd.c
1 /*
2      This file is part of GNUnet.
3      (C) 2012 Christian Grothoff (and other contributing authors)
4
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.
9
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.
14
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.
19 */
20 /**
21  * @file gnunet-gns-fcfsd.c
22  * @brief HTTP daemon that offers first-come-first-serve GNS domain registration
23  * @author Christian Grothoff
24  *
25  * TODO:
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  */
30 #include "platform.h"
31 #include <gnunet_util_lib.h>
32 #include <microhttpd.h>
33 #include <gnunet_namestore_service.h>
34
35 /**
36  * Invalid method page.
37  */
38 #define METHOD_ERROR "<html><head><title>Illegal request</title></head><body>Go away.</body></html>"
39
40 /**
41  * Front page. (/)
42  */
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>"
44
45 /**
46  * Second page (/S)
47  */
48 #define SUBMIT_PAGE "<html><head><title>%s</title></head><body>%s</body></html>"
49
50 /**
51  * Mime type for HTML pages.
52  */
53 #define MIME_HTML "text/html"
54
55 /**
56  * Name of our cookie.
57  */
58 #define COOKIE_NAME "gns-fcfs"
59
60
61 enum Phase
62   {
63     RP_START = 0,
64     RP_LOOKUP,
65     RP_PUT,
66     RP_SUCCESS,
67     RP_FAIL
68   };
69
70
71 /**
72  * Data kept per request.
73  */
74 struct Request
75 {
76
77   /**
78    * Associated session.
79    */
80   struct Session *session;
81
82   /**
83    * Post processor handling form data (IF this is
84    * a POST request).
85    */
86   struct MHD_PostProcessor *pp;
87
88   /**
89    * URL to serve in response to this POST (if this request 
90    * was a 'POST')
91    */
92   const char *post_url;
93
94   /**
95    * Active request with the namestore.
96    */
97   struct GNUNET_NAMESTORE_QueueEntry *qe;
98   
99   /**
100    * Current processing phase.
101    */
102   enum Phase phase;
103
104   /**
105    * Domain name submitted via form.
106    */
107   char domain_name[64];
108
109   /**
110    * Public key submitted via form.
111    */
112   char public_key[1024];
113
114 };
115
116
117 /**
118  * MHD deamon reference.
119  */
120 static struct MHD_Daemon *httpd;
121
122 /**
123  * Main HTTP task.
124  */
125 static GNUNET_SCHEDULER_TaskIdentifier httpd_task;
126
127 /**
128  * Handle to the namestore.
129  */
130 static struct GNUNET_NAMESTORE_Handle *ns;
131
132 /**
133  * Hash of the public key of the fcfsd zone.
134  */
135 static GNUNET_HashCode fcfsd_zone;
136
137 /**
138  * Private key for the fcfsd zone.
139  */
140 static struct GNUNET_CRYPTO_RsaPrivateKey *fcfs_zone_pkey;
141                         
142
143 /**
144  * Handler that returns a simple static HTTP page.
145  *
146  * @param connection connection to use
147  * @return MHD_YES on success
148  */
149 static int
150 serve_main_page (struct MHD_Connection *connection)
151 {
152   int ret;
153   struct MHD_Response *response;
154
155   /* return static form */
156   response = MHD_create_response_from_buffer (strlen (MAIN_PAGE),
157                                               (void *) MAIN_PAGE,
158                                               MHD_RESPMEM_PERSISTENT);
159   MHD_add_response_header (response,
160                            MHD_HTTP_HEADER_CONTENT_ENCODING,
161                            MIME_HTML);
162   ret = MHD_queue_response (connection, 
163                             MHD_HTTP_OK, 
164                             response);
165   MHD_destroy_response (response);
166   return ret;
167 }
168
169
170 /**
171  * Send the 'SUBMIT_PAGE'.
172  *
173  * @param info information string to send to the user
174  * @param request request information
175  * @param connection connection to use
176  */
177 static int
178 fill_s_reply (const char *info,
179               struct Request *request,
180               struct MHD_Connection *connection)
181 {
182   int ret;
183   char *reply;
184   struct MHD_Response *response;
185
186   GNUNET_asprintf (&reply,
187                    SUBMIT_PAGE,
188                    info,
189                    info);
190   /* return static form */
191   response = MHD_create_response_from_buffer (strlen (reply),
192                                               (void *) reply,
193                                               MHD_RESPMEM_MUST_FREE);
194   MHD_add_response_header (response,
195                            MHD_HTTP_HEADER_CONTENT_ENCODING,
196                            MIME_HTML);
197   ret = MHD_queue_response (connection, 
198                             MHD_HTTP_OK, 
199                             response);
200   MHD_destroy_response (response);
201   return ret;
202 }
203
204
205 /**
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
209  * POST data.
210  *
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
218  *              specified offset
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
223  */
224 static int
225 post_iterator (void *cls,
226                enum MHD_ValueKind kind,
227                const char *key,
228                const char *filename,
229                const char *content_type,
230                const char *transfer_encoding,
231                const char *data, uint64_t off, size_t size)
232 {
233   struct Request *request = cls;
234
235   if (0 == strcmp ("domain", key))
236     {
237       if (size + off > sizeof(request->domain_name))
238         size = sizeof (request->domain_name) - off;
239       memcpy (&request->domain_name[off],
240               data,
241               size);
242       if (size + off < sizeof (request->domain_name))
243         request->domain_name[size+off] = '\0';
244       return MHD_YES;
245     }
246   if (0 == strcmp ("pkey", key))
247     {
248       if (size + off > sizeof(request->public_key))
249         size = sizeof (request->public_key) - off;
250       memcpy (&request->public_key[off],
251               data,
252               size);
253       if (size + off < sizeof (request->public_key))
254         request->public_key[size+off] = '\0';
255       return MHD_YES;
256     }
257   GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
258               "Unsupported form value `%s'\n", key);
259   return MHD_YES;
260 }
261
262
263 /**
264  * Task run whenever HTTP server operations are pending.
265  *
266  * @param cls unused
267  * @param tc scheduler context
268  */
269 static void
270 do_httpd (void *cls,
271           const struct GNUNET_SCHEDULER_TaskContext *tc);
272
273
274 /**
275  * Schedule task to run MHD server now.
276  */
277 static void
278 run_httpd_now ()
279 {
280   if (GNUNET_SCHEDULER_NO_TASK != httpd_task)
281   {
282     GNUNET_SCHEDULER_cancel (httpd_task);
283     httpd_task = GNUNET_SCHEDULER_NO_TASK;
284   }
285   httpd_task = GNUNET_SCHEDULER_add_now (&do_httpd, NULL);
286 }
287
288
289 /**
290  * Continuation called to notify client about result of the
291  * operation.
292  *
293  * @param cls closure
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
298  */
299 static void 
300 put_continuation (void *cls,
301                   int32_t success,
302                   const char *emsg)
303 {
304   struct Request *request = cls;
305
306   request->qe = NULL;
307   if (0 >= success)
308   {
309     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
310                 _("Failed to create record for domain `%s': %s\n"),
311                 request->domain_name,
312                 emsg);
313     request->phase = RP_FAIL;
314   }
315   else
316     request->phase = RP_SUCCESS;
317   run_httpd_now ();
318 }
319
320
321 /**
322  * Process a record that was stored in the namestore.
323  *
324  * @param cls closure
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)
336  */
337 static void 
338 lookup_result_processor (void *cls,
339                          const struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded *zone_key,
340                          struct GNUNET_TIME_Absolute expire,                        
341                          const char *name,
342                          unsigned int rd_len,
343                          const struct GNUNET_NAMESTORE_RecordData *rd,
344                          const struct GNUNET_CRYPTO_RsaSignature *signature)
345 {
346   struct Request *request = cls;
347   struct GNUNET_NAMESTORE_RecordData r;
348
349   request->qe = NULL;
350   if (0 != rd_len)
351   {
352     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
353                 _("Found %u existing records for domain `%s'\n"),
354                 rd_len,
355                 request->domain_name);
356     request->phase = RP_FAIL;
357     run_httpd_now ();
358     return;
359   }
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,
366                                                 fcfs_zone_pkey,
367                                                 request->domain_name,
368                                                 &r,
369                                                 &put_continuation,
370                                                 request);
371 }
372
373
374 /**
375  * Main MHD callback for handling requests.
376  *
377  *
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
389  *        upload_data)
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
406  */
407 static int
408 create_response (void *cls,
409                  struct MHD_Connection *connection,
410                  const char *url,
411                  const char *method,
412                  const char *version,
413                  const char *upload_data, 
414                  size_t *upload_data_size,
415                  void **ptr)
416 {
417   struct MHD_Response *response;
418   struct Request *request;
419   int ret;
420
421   if ( (0 == strcmp (method, MHD_HTTP_METHOD_GET)) ||
422        (0 == strcmp (method, MHD_HTTP_METHOD_HEAD)) )
423     {
424       ret = serve_main_page (connection);
425       if (ret != MHD_YES)
426         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
427                     _("Failed to create page for `%s'\n"),
428                     url);
429       return ret;
430     }
431   if (0 == strcmp (method, MHD_HTTP_METHOD_POST))
432     {   
433       request = *ptr;
434       if (NULL == request)
435       {
436         request = GNUNET_malloc (sizeof (struct Request));
437         *ptr = request;
438         request->pp = MHD_create_post_processor (connection, 1024,
439                                                  &post_iterator, request);
440         if (NULL == request->pp)
441           {
442             GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
443                         _("Failed to setup post processor for `%s'\n"),
444                         url);
445             return MHD_NO; /* internal error */
446           }    
447         return MHD_YES;
448       }
449       if (NULL != request->pp)
450       {
451         /* evaluate POST data */
452         MHD_post_process (request->pp,
453                           upload_data,
454                           *upload_data_size);
455         if (0 != *upload_data_size)
456           {
457             *upload_data_size = 0;
458             return MHD_YES;
459           }
460         /* done with POST data, serve response */
461         MHD_destroy_post_processor (request->pp);
462         request->pp = NULL;
463       }
464       if (strlen (request->public_key) != 2)
465         {
466           /* parse error */
467           return fill_s_reply ("Failed to parse given public key",
468                                request, connection);
469         }
470       fprintf (stderr, "Now in phase %d\n", request->phase);
471       switch (request->phase)
472         {
473         case RP_START:
474           request->phase = RP_LOOKUP;
475           request->qe = GNUNET_NAMESTORE_lookup_record (ns,
476                                                         &fcfsd_zone,
477                                                         request->domain_name,
478                                                         GNUNET_GNS_TYPE_PKEY,
479                                                         &lookup_result_processor,
480                                                         request);
481           break;
482         case RP_LOOKUP:
483           break;
484         case RP_PUT:
485           break;
486         case RP_FAIL:
487           return fill_s_reply ("Request failed, sorry.",
488                                request, connection);
489         case RP_SUCCESS:
490           return fill_s_reply ("Success.",
491                                request, connection);
492         default:
493           GNUNET_break (0);
494           return MHD_NO;
495         }
496         return MHD_YES; /* will have a reply later... */    
497     }
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, 
504                             response);
505   MHD_destroy_response (response);
506   return ret;
507 }
508
509
510 /**
511  * Callback called upon completion of a request.
512  * Decrements session reference counter.
513  *
514  * @param cls not used
515  * @param connection connection that completed
516  * @param con_cls session handle
517  * @param toe status code
518  */
519 static void
520 request_completed_callback (void *cls,
521                             struct MHD_Connection *connection,
522                             void **con_cls,
523                             enum MHD_RequestTerminationCode toe)
524 {
525   struct Request *request = *con_cls;
526
527   if (NULL == request)
528     return;
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);
534 }
535
536
537 /**
538  * Schedule tasks to run MHD server.
539  */
540 static void
541 run_httpd ()
542 {
543   fd_set rs;
544   fd_set ws;
545   fd_set es;
546   struct GNUNET_NETWORK_FDSet *wrs;
547   struct GNUNET_NETWORK_FDSet *wws;
548   struct GNUNET_NETWORK_FDSet *wes;
549   int max;
550   int haveto;
551   unsigned MHD_LONG_LONG timeout;
552   struct GNUNET_TIME_Relative tv;
553
554   FD_ZERO (&rs);
555   FD_ZERO (&ws);
556   FD_ZERO (&es);
557   wrs = GNUNET_NETWORK_fdset_create ();
558   wes = GNUNET_NETWORK_fdset_create ();
559   wws = GNUNET_NETWORK_fdset_create ();
560   max = -1;
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;
565   else
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);
570   httpd_task =
571       GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_HIGH,
572                                    GNUNET_SCHEDULER_NO_TASK, tv, wrs, wws,
573                                    &do_httpd, NULL);
574   GNUNET_NETWORK_fdset_destroy (wrs);
575   GNUNET_NETWORK_fdset_destroy (wws);
576   GNUNET_NETWORK_fdset_destroy (wes);
577 }
578
579
580 /**
581  * Task run whenever HTTP server operations are pending.
582  *
583  * @param cls unused
584  * @param tc scheduler context
585  */
586 static void
587 do_httpd (void *cls,
588           const struct GNUNET_SCHEDULER_TaskContext *tc)
589 {
590   httpd_task = GNUNET_SCHEDULER_NO_TASK;
591   MHD_run (httpd);
592   run_httpd ();
593 }
594
595
596 /**
597  * Task run on shutdown.  Cleans up everything.
598  *
599  * @param cls unused
600  * @param tc scheduler context
601  */
602 static void
603 do_shutdown (void *cls,
604              const struct GNUNET_SCHEDULER_TaskContext *tc)
605 {
606   if (GNUNET_SCHEDULER_NO_TASK != httpd_task)
607   {
608     GNUNET_SCHEDULER_cancel (httpd_task);
609     httpd_task = GNUNET_SCHEDULER_NO_TASK;
610   }
611   if (NULL != ns)
612   {
613     GNUNET_NAMESTORE_disconnect (ns, GNUNET_NO);
614     ns = NULL;
615   }
616   if (NULL != httpd)
617   {
618     MHD_stop_daemon (httpd);
619     httpd = NULL;
620   }
621   if (NULL != fcfs_zone_pkey)
622   {
623     GNUNET_CRYPTO_rsa_key_free (fcfs_zone_pkey);
624     fcfs_zone_pkey = NULL;
625   }
626 }
627
628
629 /**
630  * Main function that will be run.
631  *
632  * @param cls closure
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
636  */
637 static void
638 run (void *cls, char *const *args, const char *cfgfile,
639      const struct GNUNET_CONFIGURATION_Handle *cfg)
640 {
641   char *keyfile;
642   unsigned long long port;
643   struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded pub;
644
645   if (GNUNET_OK !=
646       GNUNET_CONFIGURATION_get_value_number (cfg,
647                                              "fcfsd",
648                                              "HTTPPORT",
649                                              &port))
650   {
651       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
652                   _("Option `%s' not specified in configuration section `%s'\n"),
653                   "HTTPPORT",
654                   "fcfsd");
655       return;
656   }
657   if (GNUNET_OK !=
658       GNUNET_CONFIGURATION_get_value_filename (cfg,
659                                                "fcfsd",
660                                                "ZONEKEY",
661                                                &keyfile))
662   {
663     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
664                 _("Option `%s' not specified in configuration section `%s'\n"),
665                 "ZONEKEY",
666                 "fcfsd");
667     return;
668   }
669   fcfs_zone_pkey = GNUNET_CRYPTO_rsa_key_create_from_file (keyfile);
670   GNUNET_free (keyfile);
671   if (NULL == fcfs_zone_pkey)
672   {
673     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
674                 _("Failed to read or create private zone key\n"));
675     return;
676   }
677   GNUNET_CRYPTO_rsa_key_get_public (fcfs_zone_pkey,
678                                     &pub);
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),
683               port);
684   ns = GNUNET_NAMESTORE_connect (cfg);
685   if (NULL == ns)
686     {
687       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
688                   _("Failed to connect to namestore\n"));
689       return;
690     }
691   httpd = MHD_start_daemon (MHD_USE_DEBUG,
692                             (uint16_t) port,
693                             NULL, NULL, 
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,
700                             MHD_OPTION_END);
701   if (NULL == httpd)
702   {
703     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
704                 _("Failed to start HTTP server\n"));
705     GNUNET_NAMESTORE_disconnect (ns, GNUNET_NO);
706     ns = NULL;
707     return;
708   }
709   run_httpd ();
710   GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL,
711                                 &do_shutdown, NULL);
712 }
713
714
715 /**
716  * The main function for the fcfs daemon.
717  *
718  * @param argc number of arguments from the command line
719  * @param argv command line arguments
720  * @return 0 ok, 1 on error
721  */
722 int
723 main (int argc, char *const *argv)
724 {
725   static const struct GNUNET_GETOPT_CommandLineOption options[] = {
726     GNUNET_GETOPT_OPTION_END
727   };
728
729   int ret;
730
731   GNUNET_log_setup ("fcfsd", "WARNING", NULL);
732   ret =
733       (GNUNET_OK ==
734        GNUNET_PROGRAM_run (argc, argv, "fcfsd",
735                            _("GNUnet GNS first come first serve registration service"), 
736                            options,
737                            &run, NULL)) ? 0 : 1;
738
739   return ret;
740 }
741
742 /* end of gnunet-gns-fcfsd.c */