-documenting todo, minor 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_len,
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_len)
375   {
376     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
377                 _("Found %u existing records for domain `%s'\n"),
378                 rd_len,
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  *
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
413  *        upload_data)
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
430  */
431 static int
432 create_response (void *cls,
433                  struct MHD_Connection *connection,
434                  const char *url,
435                  const char *method,
436                  const char *version,
437                  const char *upload_data, 
438                  size_t *upload_data_size,
439                  void **ptr)
440 {
441   struct MHD_Response *response;
442   struct Request *request;
443   int ret;
444
445   if ( (0 == strcmp (method, MHD_HTTP_METHOD_GET)) ||
446        (0 == strcmp (method, MHD_HTTP_METHOD_HEAD)) )
447     {
448       ret = serve_main_page (connection);
449       if (ret != MHD_YES)
450         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
451                     _("Failed to create page for `%s'\n"),
452                     url);
453       return ret;
454     }
455   if (0 == strcmp (method, MHD_HTTP_METHOD_POST))
456     {   
457       request = *ptr;
458       if (NULL == request)
459       {
460         request = GNUNET_malloc (sizeof (struct Request));
461         *ptr = request;
462         request->pp = MHD_create_post_processor (connection, 1024,
463                                                  &post_iterator, request);
464         if (NULL == request->pp)
465           {
466             GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
467                         _("Failed to setup post processor for `%s'\n"),
468                         url);
469             return MHD_NO; /* internal error */
470           }    
471         return MHD_YES;
472       }
473       if (NULL != request->pp)
474       {
475         /* evaluate POST data */
476         MHD_post_process (request->pp,
477                           upload_data,
478                           *upload_data_size);
479         if (0 != *upload_data_size)
480           {
481             *upload_data_size = 0;
482             return MHD_YES;
483           }
484         /* done with POST data, serve response */
485         MHD_destroy_post_processor (request->pp);
486         request->pp = NULL;
487       }
488       if (strlen (request->public_key) != 2)
489         {
490           /* parse error */
491           return fill_s_reply ("Failed to parse given public key",
492                                request, connection);
493         }
494       switch (request->phase)
495         {
496         case RP_START:
497           request->phase = RP_LOOKUP;
498           request->qe = GNUNET_NAMESTORE_lookup_record (ns,
499                                                         &fcfsd_zone,
500                                                         request->domain_name,
501                                                         GNUNET_GNS_TYPE_PKEY,
502                                                         &lookup_result_processor,
503                                                         request);
504           break;
505         case RP_LOOKUP:
506           break;
507         case RP_PUT:
508           break;
509         case RP_FAIL:
510           return fill_s_reply ("Request failed, sorry.",
511                                request, connection);
512         case RP_SUCCESS:
513           return fill_s_reply ("Success.",
514                                request, connection);
515         default:
516           GNUNET_break (0);
517           return MHD_NO;
518         }
519         return MHD_YES; /* will have a reply later... */    
520     }
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, 
527                             response);
528   MHD_destroy_response (response);
529   return ret;
530 }
531
532
533 /**
534  * Callback called upon completion of a request.
535  * Decrements session reference counter.
536  *
537  * @param cls not used
538  * @param connection connection that completed
539  * @param con_cls session handle
540  * @param toe status code
541  */
542 static void
543 request_completed_callback (void *cls,
544                             struct MHD_Connection *connection,
545                             void **con_cls,
546                             enum MHD_RequestTerminationCode toe)
547 {
548   struct Request *request = *con_cls;
549
550   if (NULL == request)
551     return;
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);
557 }
558
559
560 /**
561  * Schedule tasks to run MHD server.
562  */
563 static void
564 run_httpd ()
565 {
566   fd_set rs;
567   fd_set ws;
568   fd_set es;
569   struct GNUNET_NETWORK_FDSet *wrs;
570   struct GNUNET_NETWORK_FDSet *wws;
571   struct GNUNET_NETWORK_FDSet *wes;
572   int max;
573   int haveto;
574   unsigned MHD_LONG_LONG timeout;
575   struct GNUNET_TIME_Relative tv;
576
577   FD_ZERO (&rs);
578   FD_ZERO (&ws);
579   FD_ZERO (&es);
580   wrs = GNUNET_NETWORK_fdset_create ();
581   wes = GNUNET_NETWORK_fdset_create ();
582   wws = GNUNET_NETWORK_fdset_create ();
583   max = -1;
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;
588   else
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);
593   httpd_task =
594       GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_HIGH,
595                                    GNUNET_SCHEDULER_NO_TASK, tv, wrs, wws,
596                                    &do_httpd, NULL);
597   GNUNET_NETWORK_fdset_destroy (wrs);
598   GNUNET_NETWORK_fdset_destroy (wws);
599   GNUNET_NETWORK_fdset_destroy (wes);
600 }
601
602
603 /**
604  * Task run whenever HTTP server operations are pending.
605  *
606  * @param cls unused
607  * @param tc scheduler context
608  */
609 static void
610 do_httpd (void *cls,
611           const struct GNUNET_SCHEDULER_TaskContext *tc)
612 {
613   httpd_task = GNUNET_SCHEDULER_NO_TASK;
614   MHD_run (httpd);
615   run_httpd ();
616 }
617
618
619 /**
620  * Task run on shutdown.  Cleans up everything.
621  *
622  * @param cls unused
623  * @param tc scheduler context
624  */
625 static void
626 do_shutdown (void *cls,
627              const struct GNUNET_SCHEDULER_TaskContext *tc)
628 {
629   if (GNUNET_SCHEDULER_NO_TASK != httpd_task)
630   {
631     GNUNET_SCHEDULER_cancel (httpd_task);
632     httpd_task = GNUNET_SCHEDULER_NO_TASK;
633   }
634   if (NULL != ns)
635   {
636     GNUNET_NAMESTORE_disconnect (ns, GNUNET_NO);
637     ns = NULL;
638   }
639   if (NULL != httpd)
640   {
641     MHD_stop_daemon (httpd);
642     httpd = NULL;
643   }
644   if (NULL != fcfs_zone_pkey)
645   {
646     GNUNET_CRYPTO_rsa_key_free (fcfs_zone_pkey);
647     fcfs_zone_pkey = NULL;
648   }
649 }
650
651
652 /**
653  * Main function that will be run.
654  *
655  * @param cls closure
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
659  */
660 static void
661 run (void *cls, char *const *args, const char *cfgfile,
662      const struct GNUNET_CONFIGURATION_Handle *cfg)
663 {
664   char *keyfile;
665   unsigned long long port;
666   struct GNUNET_CRYPTO_RsaPublicKeyBinaryEncoded pub;
667
668   if (GNUNET_OK !=
669       GNUNET_CONFIGURATION_get_value_number (cfg,
670                                              "fcfsd",
671                                              "HTTPPORT",
672                                              &port))
673   {
674       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
675                   _("Option `%s' not specified in configuration section `%s'\n"),
676                   "HTTPPORT",
677                   "fcfsd");
678       return;
679   }
680   if (GNUNET_OK !=
681       GNUNET_CONFIGURATION_get_value_filename (cfg,
682                                                "fcfsd",
683                                                "ZONEKEY",
684                                                &keyfile))
685   {
686     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
687                 _("Option `%s' not specified in configuration section `%s'\n"),
688                 "ZONEKEY",
689                 "fcfsd");
690     return;
691   }
692   fcfs_zone_pkey = GNUNET_CRYPTO_rsa_key_create_from_file (keyfile);
693   GNUNET_free (keyfile);
694   if (NULL == fcfs_zone_pkey)
695   {
696     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
697                 _("Failed to read or create private zone key\n"));
698     return;
699   }
700   GNUNET_CRYPTO_rsa_key_get_public (fcfs_zone_pkey,
701                                     &pub);
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),
706               port);
707   ns = GNUNET_NAMESTORE_connect (cfg);
708   if (NULL == ns)
709     {
710       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
711                   _("Failed to connect to namestore\n"));
712       return;
713     }
714   httpd = MHD_start_daemon (MHD_USE_DEBUG,
715                             (uint16_t) port,
716                             NULL, NULL, 
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,
723                             MHD_OPTION_END);
724   if (NULL == httpd)
725   {
726     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
727                 _("Failed to start HTTP server\n"));
728     GNUNET_NAMESTORE_disconnect (ns, GNUNET_NO);
729     ns = NULL;
730     return;
731   }
732   run_httpd ();
733   GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL,
734                                 &do_shutdown, NULL);
735 }
736
737
738 /**
739  * The main function for the fcfs daemon.
740  *
741  * @param argc number of arguments from the command line
742  * @param argv command line arguments
743  * @return 0 ok, 1 on error
744  */
745 int
746 main (int argc, char *const *argv)
747 {
748   static const struct GNUNET_GETOPT_CommandLineOption options[] = {
749     GNUNET_GETOPT_OPTION_END
750   };
751
752   int ret;
753
754   GNUNET_log_setup ("fcfsd", "WARNING", NULL);
755   ret =
756       (GNUNET_OK ==
757        GNUNET_PROGRAM_run (argc, argv, "fcfsd",
758                            _("GNUnet GNS first come first serve registration service"), 
759                            options,
760                            &run, NULL)) ? 0 : 1;
761
762   return ret;
763 }
764
765 /* end of gnunet-gns-fcfsd.c */