-doxygen fixes
[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  * - 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!?)
32  */
33 #include "platform.h"
34 #include <gnunet_util_lib.h>
35 #include <microhttpd.h>
36 #include <gnunet_namestore_service.h>
37
38 /**
39  * Invalid method page.
40  */
41 #define METHOD_ERROR "<html><head><title>Illegal request</title></head><body>Go away.</body></html>"
42
43 /**
44  * Front page. (/)
45  */
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>"
47
48 /**
49  * Second page (/S)
50  */
51 #define SUBMIT_PAGE "<html><head><title>%s</title></head><body>%s</body></html>"
52
53 /**
54  * Mime type for HTML pages.
55  */
56 #define MIME_HTML "text/html"
57
58 /**
59  * Name of our cookie.
60  */
61 #define COOKIE_NAME "gns-fcfs"
62
63
64 /**
65  * Phases a request goes through.
66  */
67 enum Phase
68   {
69     /**
70      * Start phase (parsing POST, checking).
71      */
72     RP_START = 0,
73
74     /**
75      * Lookup to see if the domain name is taken.
76      */
77     RP_LOOKUP,
78
79     /**
80      * Storing of the record.
81      */
82     RP_PUT,
83
84     /**
85      * We're done with success.
86      */
87     RP_SUCCESS,
88
89     /**
90      * Send failure message.
91      */
92     RP_FAIL
93   };
94
95
96 /**
97  * Data kept per request.
98  */
99 struct Request
100 {
101
102   /**
103    * Associated session.
104    */
105   struct Session *session;
106
107   /**
108    * Post processor handling form data (IF this is
109    * a POST request).
110    */
111   struct MHD_PostProcessor *pp;
112
113   /**
114    * URL to serve in response to this POST (if this request 
115    * was a 'POST')
116    */
117   const char *post_url;
118
119   /**
120    * Active request with the namestore.
121    */
122   struct GNUNET_NAMESTORE_QueueEntry *qe;
123   
124   /**
125    * Current processing phase.
126    */
127   enum Phase phase;
128
129   /**
130    * Domain name submitted via form.
131    */
132   char domain_name[64];
133
134   /**
135    * Public key submitted via form.
136    */
137   char public_key[1024];
138
139 };
140
141
142 /**
143  * MHD deamon reference.
144  */
145 static struct MHD_Daemon *httpd;
146
147 /**
148  * Main HTTP task.
149  */
150 static GNUNET_SCHEDULER_TaskIdentifier httpd_task;
151
152 /**
153  * Handle to the namestore.
154  */
155 static struct GNUNET_NAMESTORE_Handle *ns;
156
157 /**
158  * Hash of the public key of the fcfsd zone.
159  */
160 static GNUNET_HashCode fcfsd_zone;
161
162 /**
163  * Private key for the fcfsd zone.
164  */
165 static struct GNUNET_CRYPTO_RsaPrivateKey *fcfs_zone_pkey;
166                         
167
168 /**
169  * Handler that returns a simple static HTTP page.
170  *
171  * @param connection connection to use
172  * @return MHD_YES on success
173  */
174 static int
175 serve_main_page (struct MHD_Connection *connection)
176 {
177   int ret;
178   struct MHD_Response *response;
179
180   /* return static form */
181   response = MHD_create_response_from_buffer (strlen (MAIN_PAGE),
182                                               (void *) MAIN_PAGE,
183                                               MHD_RESPMEM_PERSISTENT);
184   MHD_add_response_header (response,
185                            MHD_HTTP_HEADER_CONTENT_ENCODING,
186                            MIME_HTML);
187   ret = MHD_queue_response (connection, 
188                             MHD_HTTP_OK, 
189                             response);
190   MHD_destroy_response (response);
191   return ret;
192 }
193
194
195 /**
196  * Send the 'SUBMIT_PAGE'.
197  *
198  * @param info information string to send to the user
199  * @param request request information
200  * @param connection connection to use
201  */
202 static int
203 fill_s_reply (const char *info,
204               struct Request *request,
205               struct MHD_Connection *connection)
206 {
207   int ret;
208   char *reply;
209   struct MHD_Response *response;
210
211   GNUNET_asprintf (&reply,
212                    SUBMIT_PAGE,
213                    info,
214                    info);
215   /* return static form */
216   response = MHD_create_response_from_buffer (strlen (reply),
217                                               (void *) reply,
218                                               MHD_RESPMEM_MUST_FREE);
219   MHD_add_response_header (response,
220                            MHD_HTTP_HEADER_CONTENT_ENCODING,
221                            MIME_HTML);
222   ret = MHD_queue_response (connection, 
223                             MHD_HTTP_OK, 
224                             response);
225   MHD_destroy_response (response);
226   return ret;
227 }
228
229
230 /**
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
234  * POST data.
235  *
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
243  *              specified offset
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
248  */
249 static int
250 post_iterator (void *cls,
251                enum MHD_ValueKind kind,
252                const char *key,
253                const char *filename,
254                const char *content_type,
255                const char *transfer_encoding,
256                const char *data, uint64_t off, size_t size)
257 {
258   struct Request *request = cls;
259
260   if (0 == strcmp ("domain", key))
261     {
262       if (size + off >= sizeof(request->domain_name))
263         size = sizeof (request->domain_name) - off - 1;
264       memcpy (&request->domain_name[off],
265               data,
266               size);
267       request->domain_name[size+off] = '\0';
268       return MHD_YES;
269     }
270   if (0 == strcmp ("pkey", key))
271     {
272       if (size + off >= sizeof(request->public_key))
273         size = sizeof (request->public_key) - off - 1;
274       memcpy (&request->public_key[off],
275               data,
276               size);
277       request->public_key[size+off] = '\0';
278       return MHD_YES;
279     }
280   GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
281               _("Unsupported form value `%s'\n"),
282               key);
283   return MHD_YES;
284 }
285
286
287 /**
288  * Task run whenever HTTP server operations are pending.
289  *
290  * @param cls unused
291  * @param tc scheduler context
292  */
293 static void
294 do_httpd (void *cls,
295           const struct GNUNET_SCHEDULER_TaskContext *tc);
296
297
298 /**
299  * Schedule task to run MHD server now.
300  */
301 static void
302 run_httpd_now ()
303 {
304   if (GNUNET_SCHEDULER_NO_TASK != httpd_task)
305   {
306     GNUNET_SCHEDULER_cancel (httpd_task);
307     httpd_task = GNUNET_SCHEDULER_NO_TASK;
308   }
309   httpd_task = GNUNET_SCHEDULER_add_now (&do_httpd, NULL);
310 }
311
312
313 /**
314  * Continuation called to notify client about result of the
315  * operation.
316  *
317  * @param cls closure
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
322  */
323 static void 
324 put_continuation (void *cls,
325                   int32_t success,
326                   const char *emsg)
327 {
328   struct Request *request = cls;
329
330   request->qe = NULL;
331   if (0 >= success)
332   {
333     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
334                 _("Failed to create record for domain `%s': %s\n"),
335                 request->domain_name,
336                 emsg);
337     request->phase = RP_FAIL;
338   }
339   else
340     request->phase = RP_SUCCESS;
341   run_httpd_now ();
342 }
343
344
345 /**
346  * Process a record that was stored in the namestore.
347  *
348  * @param cls closure
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)
360  */
361 static void 
362 lookup_result_processor (void *cls,
363                          const struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded *zone_key,
364                          struct GNUNET_TIME_Absolute expire,                        
365                          const char *name,
366                          unsigned int rd_count,
367                          const struct GNUNET_NAMESTORE_RecordData *rd,
368                          const struct GNUNET_CRYPTO_RsaSignature *signature)
369 {
370   struct Request *request = cls;
371   struct GNUNET_NAMESTORE_RecordData r;
372
373   request->qe = NULL;
374   if (0 != rd_count)
375   {
376     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
377                 _("Found %u existing records for domain `%s'\n"),
378                 rd_count,
379                 request->domain_name);
380     request->phase = RP_FAIL;
381     run_httpd_now ();
382     return;
383   }
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,
390                                                 fcfs_zone_pkey,
391                                                 request->domain_name,
392                                                 &r,
393                                                 &put_continuation,
394                                                 request);
395 }
396
397
398 /**
399  * Main MHD callback for handling requests.
400  *
401  * @param cls unused
402  * @param connection MHD connection handle
403  * @param url the requested url
404  * @param method the HTTP method used ("GET", "PUT", etc.)
405  * @param version the HTTP version string (i.e. "HTTP/1.1")
406  * @param upload_data the data being uploaded (excluding HEADERS,
407  *        for a POST that fits into memory and that is encoded
408  *        with a supported encoding, the POST data will NOT be
409  *        given in upload_data and is instead available as
410  *        part of MHD_get_connection_values; very large POST
411  *        data *will* be made available incrementally in
412  *        upload_data)
413  * @param upload_data_size set initially to the size of the
414  *        upload_data provided; the method must update this
415  *        value to the number of bytes NOT processed;
416  * @param ptr pointer to location where we store the 'struct Request'
417  * @return MHS_YES if the connection was handled successfully,
418  *         MHS_NO if the socket must be closed due to a serios
419  *         error while handling the request
420  */
421 static int
422 create_response (void *cls,
423                  struct MHD_Connection *connection,
424                  const char *url,
425                  const char *method,
426                  const char *version,
427                  const char *upload_data, 
428                  size_t *upload_data_size,
429                  void **ptr)
430 {
431   struct MHD_Response *response;
432   struct Request *request;
433   int ret;
434
435   if ( (0 == strcmp (method, MHD_HTTP_METHOD_GET)) ||
436        (0 == strcmp (method, MHD_HTTP_METHOD_HEAD)) )
437     {
438       ret = serve_main_page (connection);
439       if (ret != MHD_YES)
440         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
441                     _("Failed to create page for `%s'\n"),
442                     url);
443       return ret;
444     }
445   if (0 == strcmp (method, MHD_HTTP_METHOD_POST))
446     {   
447       request = *ptr;
448       if (NULL == request)
449       {
450         request = GNUNET_malloc (sizeof (struct Request));
451         *ptr = request;
452         request->pp = MHD_create_post_processor (connection, 1024,
453                                                  &post_iterator, request);
454         if (NULL == request->pp)
455           {
456             GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
457                         _("Failed to setup post processor for `%s'\n"),
458                         url);
459             return MHD_NO; /* internal error */
460           }    
461         return MHD_YES;
462       }
463       if (NULL != request->pp)
464       {
465         /* evaluate POST data */
466         MHD_post_process (request->pp,
467                           upload_data,
468                           *upload_data_size);
469         if (0 != *upload_data_size)
470           {
471             *upload_data_size = 0;
472             return MHD_YES;
473           }
474         /* done with POST data, serve response */
475         MHD_destroy_post_processor (request->pp);
476         request->pp = NULL;
477       }
478       if (strlen (request->public_key) != 2)
479         {
480           /* parse error */
481           return fill_s_reply ("Failed to parse given public key",
482                                request, connection);
483         }
484       switch (request->phase)
485         {
486         case RP_START:
487           request->phase = RP_LOOKUP;
488           request->qe = GNUNET_NAMESTORE_lookup_record (ns,
489                                                         &fcfsd_zone,
490                                                         request->domain_name,
491                                                         GNUNET_GNS_TYPE_PKEY,
492                                                         &lookup_result_processor,
493                                                         request);
494           break;
495         case RP_LOOKUP:
496           break;
497         case RP_PUT:
498           break;
499         case RP_FAIL:
500           return fill_s_reply ("Request failed, sorry.",
501                                request, connection);
502         case RP_SUCCESS:
503           return fill_s_reply ("Success.",
504                                request, connection);
505         default:
506           GNUNET_break (0);
507           return MHD_NO;
508         }
509         return MHD_YES; /* will have a reply later... */    
510     }
511   /* unsupported HTTP method */
512   response = MHD_create_response_from_buffer (strlen (METHOD_ERROR),
513                                               (void *) METHOD_ERROR,
514                                               MHD_RESPMEM_PERSISTENT);
515   ret = MHD_queue_response (connection, 
516                             MHD_HTTP_METHOD_NOT_ACCEPTABLE, 
517                             response);
518   MHD_destroy_response (response);
519   return ret;
520 }
521
522
523 /**
524  * Callback called upon completion of a request.
525  * Decrements session reference counter.
526  *
527  * @param cls not used
528  * @param connection connection that completed
529  * @param con_cls session handle
530  * @param toe status code
531  */
532 static void
533 request_completed_callback (void *cls,
534                             struct MHD_Connection *connection,
535                             void **con_cls,
536                             enum MHD_RequestTerminationCode toe)
537 {
538   struct Request *request = *con_cls;
539
540   if (NULL == request)
541     return;
542   if (NULL != request->pp)
543     MHD_destroy_post_processor (request->pp);
544   if (NULL != request->qe)
545     GNUNET_NAMESTORE_cancel (request->qe);
546   GNUNET_free (request);
547 }
548
549
550 /**
551  * Schedule tasks to run MHD server.
552  */
553 static void
554 run_httpd ()
555 {
556   fd_set rs;
557   fd_set ws;
558   fd_set es;
559   struct GNUNET_NETWORK_FDSet *wrs;
560   struct GNUNET_NETWORK_FDSet *wws;
561   struct GNUNET_NETWORK_FDSet *wes;
562   int max;
563   int haveto;
564   unsigned MHD_LONG_LONG timeout;
565   struct GNUNET_TIME_Relative tv;
566
567   FD_ZERO (&rs);
568   FD_ZERO (&ws);
569   FD_ZERO (&es);
570   wrs = GNUNET_NETWORK_fdset_create ();
571   wes = GNUNET_NETWORK_fdset_create ();
572   wws = GNUNET_NETWORK_fdset_create ();
573   max = -1;
574   GNUNET_assert (MHD_YES == MHD_get_fdset (httpd, &rs, &ws, &es, &max));
575   haveto = MHD_get_timeout (httpd, &timeout);
576   if (haveto == MHD_YES)
577     tv.rel_value = (uint64_t) timeout;
578   else
579     tv = GNUNET_TIME_UNIT_FOREVER_REL;
580   GNUNET_NETWORK_fdset_copy_native (wrs, &rs, max + 1);
581   GNUNET_NETWORK_fdset_copy_native (wws, &ws, max + 1);
582   GNUNET_NETWORK_fdset_copy_native (wes, &es, max + 1);
583   httpd_task =
584       GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_HIGH,
585                                    GNUNET_SCHEDULER_NO_TASK, tv, wrs, wws,
586                                    &do_httpd, NULL);
587   GNUNET_NETWORK_fdset_destroy (wrs);
588   GNUNET_NETWORK_fdset_destroy (wws);
589   GNUNET_NETWORK_fdset_destroy (wes);
590 }
591
592
593 /**
594  * Task run whenever HTTP server operations are pending.
595  *
596  * @param cls unused
597  * @param tc scheduler context
598  */
599 static void
600 do_httpd (void *cls,
601           const struct GNUNET_SCHEDULER_TaskContext *tc)
602 {
603   httpd_task = GNUNET_SCHEDULER_NO_TASK;
604   MHD_run (httpd);
605   run_httpd ();
606 }
607
608
609 /**
610  * Task run on shutdown.  Cleans up everything.
611  *
612  * @param cls unused
613  * @param tc scheduler context
614  */
615 static void
616 do_shutdown (void *cls,
617              const struct GNUNET_SCHEDULER_TaskContext *tc)
618 {
619   if (GNUNET_SCHEDULER_NO_TASK != httpd_task)
620   {
621     GNUNET_SCHEDULER_cancel (httpd_task);
622     httpd_task = GNUNET_SCHEDULER_NO_TASK;
623   }
624   if (NULL != ns)
625   {
626     GNUNET_NAMESTORE_disconnect (ns, GNUNET_NO);
627     ns = NULL;
628   }
629   if (NULL != httpd)
630   {
631     MHD_stop_daemon (httpd);
632     httpd = NULL;
633   }
634   if (NULL != fcfs_zone_pkey)
635   {
636     GNUNET_CRYPTO_rsa_key_free (fcfs_zone_pkey);
637     fcfs_zone_pkey = NULL;
638   }
639 }
640
641
642 /**
643  * Main function that will be run.
644  *
645  * @param cls closure
646  * @param args remaining command-line arguments
647  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
648  * @param cfg configuration
649  */
650 static void
651 run (void *cls, char *const *args, const char *cfgfile,
652      const struct GNUNET_CONFIGURATION_Handle *cfg)
653 {
654   char *keyfile;
655   unsigned long long port;
656   struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded pub;
657
658   if (GNUNET_OK !=
659       GNUNET_CONFIGURATION_get_value_number (cfg,
660                                              "fcfsd",
661                                              "HTTPPORT",
662                                              &port))
663   {
664       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
665                   _("Option `%s' not specified in configuration section `%s'\n"),
666                   "HTTPPORT",
667                   "fcfsd");
668       return;
669   }
670   if (GNUNET_OK !=
671       GNUNET_CONFIGURATION_get_value_filename (cfg,
672                                                "fcfsd",
673                                                "ZONEKEY",
674                                                &keyfile))
675   {
676     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
677                 _("Option `%s' not specified in configuration section `%s'\n"),
678                 "ZONEKEY",
679                 "fcfsd");
680     return;
681   }
682   fcfs_zone_pkey = GNUNET_CRYPTO_rsa_key_create_from_file (keyfile);
683   GNUNET_free (keyfile);
684   if (NULL == fcfs_zone_pkey)
685   {
686     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
687                 _("Failed to read or create private zone key\n"));
688     return;
689   }
690   GNUNET_CRYPTO_rsa_key_get_public (fcfs_zone_pkey,
691                                     &pub);
692   GNUNET_CRYPTO_hash (&pub, sizeof (pub), &fcfsd_zone);
693   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
694               _("Managing `%s' as FCFS zone on port %llu\n"),
695               GNUNET_h2s_full (&fcfsd_zone),
696               port);
697   ns = GNUNET_NAMESTORE_connect (cfg);
698   if (NULL == ns)
699     {
700       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
701                   _("Failed to connect to namestore\n"));
702       return;
703     }
704   httpd = MHD_start_daemon (MHD_USE_DEBUG,
705                             (uint16_t) port,
706                             NULL, NULL, 
707                             &create_response, NULL, 
708                             MHD_OPTION_CONNECTION_LIMIT, (unsigned int) 128,
709                             MHD_OPTION_PER_IP_CONNECTION_LIMIT, (unsigned int) 1,
710                             MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 16,
711                             MHD_OPTION_CONNECTION_MEMORY_LIMIT, (size_t) (4 * 1024), 
712                             MHD_OPTION_NOTIFY_COMPLETED, &request_completed_callback, NULL,
713                             MHD_OPTION_END);
714   if (NULL == httpd)
715   {
716     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
717                 _("Failed to start HTTP server\n"));
718     GNUNET_NAMESTORE_disconnect (ns, GNUNET_NO);
719     ns = NULL;
720     return;
721   }
722   run_httpd ();
723   GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL,
724                                 &do_shutdown, NULL);
725 }
726
727
728 /**
729  * The main function for the fcfs daemon.
730  *
731  * @param argc number of arguments from the command line
732  * @param argv command line arguments
733  * @return 0 ok, 1 on error
734  */
735 int
736 main (int argc, char *const *argv)
737 {
738   static const struct GNUNET_GETOPT_CommandLineOption options[] = {
739     GNUNET_GETOPT_OPTION_END
740   };
741
742   int ret;
743
744   GNUNET_log_setup ("fcfsd", "WARNING", NULL);
745   ret =
746       (GNUNET_OK ==
747        GNUNET_PROGRAM_run (argc, argv, "fcfsd",
748                            _("GNUnet GNS first come first serve registration service"), 
749                            options,
750                            &run, NULL)) ? 0 : 1;
751
752   return ret;
753 }
754
755 /* end of gnunet-gns-fcfsd.c */