(no commit message)
[oweals/gnunet.git] / src / hostlist / hostlist-client.c
1 /*
2      This file is part of GNUnet.
3      (C) 2001, 2002, 2003, 2004, 2005, 2006, 2009, 2010 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 2, 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 /**
22  * @file hostlist/hostlist-client.c
23  * @brief hostlist support.  Downloads HELLOs via HTTP.
24  * @author Christian Grothoff
25  * @author Matthias Wachs
26  */
27
28 #include "platform.h"
29 #include "hostlist-client.h"
30 #include "gnunet_core_service.h"
31 #include "gnunet_hello_lib.h"
32 #include "gnunet_statistics_service.h"
33 #include "gnunet_transport_service.h"
34 #include "gnunet-daemon-hostlist.h"
35 #include <curl/curl.h>
36 #include "gnunet_common.h"
37 #include "gnunet_bio_lib.h"
38
39 #define DEBUG_HOSTLIST_CLIENT GNUNET_YES
40
41 #define MAX_URL_LEN 1000
42
43 /**
44  * Number of connections that we must have to NOT download
45  * hostlists anymore.
46  */
47 #define MIN_CONNECTIONS 4
48
49 /**
50  * A single hostlist obtained by hostlist advertisements
51  */
52 struct Hostlist
53 {
54   /**
55    * previous entry, used to manage entries in a double linked list
56    */
57   struct Hostlist * prev;
58
59   /**
60    * next entry, used to manage entries in a double linked list
61    */
62   struct Hostlist * next;
63
64   /**
65    * URI where hostlist can be obtained
66    */
67   const char *hostlist_uri;
68
69   /**
70    * Value describing the quality of the hostlist, the bigger the better but (should) never < 0
71    * used for deciding which hostlist is replaced if MAX_NUMBER_HOSTLISTS in data structure is reached
72    * intial value = HOSTLIST_INITIAL
73    * increased every successful download by HOSTLIST_SUCCESSFULL_DOWNLOAD
74    * increased every successful download by number of obtained HELLO messages
75    * decreased every failed download by HOSTLIST_SUCCESSFULL_DOWNLOAD
76    */
77   uint64_t quality;
78
79   /**
80    * Time the hostlist advertisement was recieved and the entry was created
81    */
82   struct GNUNET_TIME_Absolute time_creation;
83
84   /**
85    * Last time the hostlist was obtained
86    */
87   struct GNUNET_TIME_Absolute time_last_usage;
88
89   /**
90    * Number of HELLO messages obtained during last download
91    */
92   uint32_t hello_count;
93
94   /**
95    * Number of times the hostlist was successfully obtained
96    */
97   uint32_t times_used;
98
99 };
100
101
102 /**
103  * Our configuration.
104  */
105 static const struct GNUNET_CONFIGURATION_Handle *cfg;
106
107 /**
108  * Our scheduler.
109  */
110 static struct GNUNET_SCHEDULER_Handle *sched;
111
112 /**
113  * Statistics handle.
114  */
115 struct GNUNET_STATISTICS_Handle *stats; 
116
117 /**
118  * Transport handle.
119  */
120 struct GNUNET_TRANSPORT_Handle *transport;
121                        
122 /**
123  * Proxy that we are using (can be NULL).
124  */
125 static char *proxy;
126
127 /**
128  * Buffer for data downloaded via HTTP.
129  */
130 static char download_buffer[GNUNET_SERVER_MAX_MESSAGE_SIZE];
131
132 /**
133  * Number of bytes valid in 'download_buffer'.
134  */
135 static size_t download_pos;
136
137 /**
138  * Current URL that we are using.
139  */
140 static char *current_url;
141
142 /**
143  * Current CURL handle.
144  */
145 static CURL *curl;
146
147 /**
148  * Current multi-CURL handle.
149  */
150 static CURLM *multi;
151
152 /**
153  * ID of the current task scheduled.
154  */
155 static GNUNET_SCHEDULER_TaskIdentifier current_task;
156
157 /**
158  * ID of the current hostlist saving task scheduled.
159  */
160 static GNUNET_SCHEDULER_TaskIdentifier saving_task;
161
162 /**
163  * Amount of time we wait between hostlist downloads.
164  */
165 static struct GNUNET_TIME_Relative hostlist_delay;
166
167 /**
168  * Set to GNUNET_YES if the current URL had some problems.
169  */ 
170 static int bogus_url;
171
172 /**
173  * Number of active connections (according to core service).
174  */
175 static unsigned int connection_count;
176
177 /**
178  * At what time MUST the current hostlist request be done?
179  */
180 static struct GNUNET_TIME_Absolute end_time;
181
182 /**
183  * Head of the linked list used to store hostlists
184  */
185 static struct Hostlist * linked_list_head;
186
187 /**
188  *  Tail of the linked list used to store hostlists
189  */
190 static struct Hostlist * linked_list_tail;
191
192 /**
193  *  Current hostlist used for downloading
194  */
195 static struct Hostlist * current_hostlist;
196
197 /*
198  *  Size of the linke list  used to store hostlists
199  */
200 static unsigned int linked_list_size;
201
202 /**
203  * Value saying if preconfigured  is used
204  */
205 static unsigned int use_preconfigured_list;
206
207 /**
208  * Set if we are allowed to learn new hostlists and use them
209  */
210 static int learning;
211
212 /**
213  * Value saying how many valid HELLO messages were obtained during download
214  */
215 static unsigned int hellos_obtained;
216
217 /**
218  * Value saying if hostlist download was successful
219  */
220 static unsigned int download_successful;
221
222 /**
223  * Process downloaded bits by calling callback on each HELLO.
224  *
225  * @param ptr buffer with downloaded data
226  * @param size size of a record
227  * @param nmemb number of records downloaded
228  * @param ctx unused
229  * @return number of bytes that were processed (always size*nmemb)
230  */
231 static size_t
232 download_hostlist_processor (void *ptr, 
233                              size_t size, 
234                              size_t nmemb, 
235                              void *ctx)
236 {
237   const char * cbuf = ptr;
238   const struct GNUNET_MessageHeader *msg;
239   size_t total;
240   size_t cpy;
241   size_t left;
242   uint16_t msize;
243
244   total = size * nmemb;
245   if ( (total == 0) || (bogus_url) )
246     {
247       return total;  /* ok, no data or bogus data */
248     }
249   GNUNET_STATISTICS_update (stats, 
250                             gettext_noop ("# bytes downloaded from hostlist servers"), 
251                             (int64_t) total, 
252                             GNUNET_NO);  
253   left = total;
254   while ( (left > 0) ||
255           (download_pos > 0) )
256     {
257       cpy = GNUNET_MIN (left, GNUNET_SERVER_MAX_MESSAGE_SIZE - download_pos);
258       memcpy (&download_buffer[download_pos],
259               cbuf,
260               cpy);      
261       cbuf += cpy;
262       download_pos += cpy;
263       left -= cpy;
264       if (download_pos < sizeof(struct GNUNET_MessageHeader))
265         {
266           GNUNET_assert (left == 0);
267           break;
268         }
269       msg = (const struct GNUNET_MessageHeader *) download_buffer;
270       msize = ntohs(msg->size);
271       if (msize < sizeof(struct GNUNET_MessageHeader))
272         {        
273           GNUNET_STATISTICS_update (stats, 
274                                     gettext_noop ("# invalid HELLOs downloaded from hostlist servers"), 
275                                     1, 
276                                     GNUNET_NO);  
277           GNUNET_log (GNUNET_ERROR_TYPE_INFO,
278                       _("Invalid `%s' message received from hostlist at `%s'\n"),
279                       "HELLO",
280                       current_url); 
281           bogus_url = 1;
282           return total;
283         }
284       if (download_pos < msize)
285         {
286           GNUNET_assert (left == 0);
287           break;
288         }
289       if (GNUNET_HELLO_size ((const struct GNUNET_HELLO_Message*)msg) == msize)
290         {
291 #if DEBUG_HOSTLIST_CLIENT
292           GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
293                       "Received valid `%s' message from hostlist server.\n",
294                       "HELLO");
295 #endif
296           GNUNET_STATISTICS_update (stats, 
297                                     gettext_noop ("# valid HELLOs downloaded from hostlist servers"), 
298                                     1, 
299                                     GNUNET_NO);
300           hellos_obtained++;
301           GNUNET_TRANSPORT_offer_hello (transport, msg);
302         }
303       else
304         {
305           GNUNET_STATISTICS_update (stats, 
306                                     gettext_noop ("# invalid HELLOs downloaded from hostlist servers"), 
307                                     1, 
308                                     GNUNET_NO);  
309           GNUNET_log (GNUNET_ERROR_TYPE_INFO,
310                       _("Invalid `%s' message received from hostlist at `%s'\n"),
311                       "HELLO",
312                       current_url);
313           bogus_url = GNUNET_YES;
314           return total;
315         }
316       memmove (download_buffer,
317                &download_buffer[msize],
318                download_pos - msize);
319       download_pos -= msize;
320     }
321   return total;
322 }
323
324
325 /**
326  * Obtain a hostlist URL that we should use.
327  *
328  * @return NULL if there is no URL available
329  */
330 static char *
331 get_bootstrap_url ()
332 {
333   char *servers;
334   char *ret;
335   size_t urls;
336   size_t pos;
337
338   if (GNUNET_OK != 
339       GNUNET_CONFIGURATION_get_value_string (cfg,
340                                              "HOSTLIST",
341                                              "SERVERS",
342                                              &servers))
343     {
344       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
345                   _("No `%s' specified in `%s' configuration, will not bootstrap.\n"),
346                   "SERVERS", "HOSTLIST");
347       return NULL;
348     }
349
350   urls = 0;
351   if (strlen (servers) > 0)
352     {
353       urls++;
354       pos = strlen (servers) - 1;
355       while (pos > 0)
356         {
357           if (servers[pos] == ' ')
358             urls++;
359           pos--;
360         }
361     }
362   if (urls == 0)
363     {
364       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
365                   _("No `%s' specified in `%s' configuration, will not bootstrap.\n"),
366                   "SERVERS", "HOSTLIST");
367       GNUNET_free (servers);
368       return NULL;
369     }
370
371   urls = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, urls) + 1;
372   pos = strlen (servers) - 1;
373   while (pos > 0)
374     {
375       if (servers[pos] == ' ')
376         {
377           urls--;
378           servers[pos] = '\0';
379         }
380       if (urls == 0)
381         {
382           pos++;
383           break;
384         }
385       pos--;    
386     }
387   ret = GNUNET_strdup (&servers[pos]);
388   GNUNET_free (servers);
389   return ret;
390 }
391
392 /**
393  * Method deciding if a preconfigured or advertisied hostlist is used on a 50:50 ratio
394  * @return uri to use, NULL if there is no URL available
395  */
396 static char *
397 get_list_url ()
398 {
399   uint32_t index;
400   unsigned int counter;
401   struct Hostlist * pos;
402
403   if ( GNUNET_NO == learning)
404   {
405     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
406                 "Using preconfigured bootstrap server\n");
407     current_hostlist = NULL;
408     return get_bootstrap_url();
409   }
410   if ( (GNUNET_YES == use_preconfigured_list) ||
411        (linked_list_size == 0) )
412   {
413     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
414                 "Using preconfigured bootstrap server\n");
415     current_hostlist = NULL;
416     return get_bootstrap_url();
417   }
418   index = GNUNET_CRYPTO_random_u32 ( GNUNET_CRYPTO_QUALITY_WEAK, linked_list_size);
419   counter = 0;
420   pos = linked_list_head;
421   while ( counter < index )
422     {
423       pos = pos->next;
424       counter ++;
425     }
426   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
427               "Using learned hostlist `%s'\n", pos->hostlist_uri);
428   current_hostlist = pos;
429   return strdup(pos->hostlist_uri);
430 }
431
432
433 #define CURL_EASY_SETOPT(c, a, b) do { ret = curl_easy_setopt(c, a, b); if (ret != CURLE_OK) GNUNET_log(GNUNET_ERROR_TYPE_WARNING, _("%s failed at %s:%d: `%s'\n"), "curl_easy_setopt", __FILE__, __LINE__, curl_easy_strerror(ret)); } while (0);
434
435
436 /**
437  * Schedule the background task that will (possibly)
438  * download a hostlist.
439  */
440 static void
441 schedule_hostlist_task (void);
442
443 /**
444  * Method to load persistent hostlist file during hostlist client shutdown
445  * @param shutdown set if called because of shutdown, entries in linked list will be destroyed
446  */
447 static void save_hostlist_file ( int shutdown );
448
449 /**
450  * add val2 to val1 with overflow check
451  * @param val1 value 1
452  * @param val2 value 2
453  * @return result
454  */
455 static uint64_t checked_add (uint64_t val1, uint64_t val2)
456 {
457   static uint64_t temp;
458   static uint64_t maxv;
459
460   maxv = 0;
461   maxv--;
462
463   temp = val1+val2;
464   if ( temp < val1)
465     return maxv;
466   else
467     return temp;
468 }
469
470 /**
471  * Subtract val2 from val1 with underflow check
472  * @param val1 value 1
473  * @param val2 value 2
474  * @return result
475  */
476 static uint64_t checked_sub (uint64_t val1, uint64_t val2)
477 {
478   if ( val1 <= val2)
479     return 0;
480   else
481     return (val1-val2);
482 }
483
484
485 /**
486  * Method updating hostlist statistics
487  */
488 static void update_hostlist ( )
489 {
490   if ( (use_preconfigured_list == GNUNET_NO) && ( NULL != current_hostlist ) )
491   {
492     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
493                 "Updating hostlist statics for URI `%s'\n",current_hostlist->hostlist_uri );
494      current_hostlist->hello_count = hellos_obtained;
495      current_hostlist->time_last_usage = GNUNET_TIME_absolute_get();
496      current_hostlist->quality = checked_add ( current_hostlist->quality, (hellos_obtained * HOSTLIST_SUCCESSFUL_HELLO));
497      if ( GNUNET_YES == download_successful )
498      {
499        current_hostlist->times_used++;
500        current_hostlist->quality = checked_add ( current_hostlist->quality, HOSTLIST_SUCCESSFUL_DOWNLOAD);
501      }
502      else
503        current_hostlist->quality = checked_sub ( current_hostlist->quality, HOSTLIST_FAILED_DOWNLOAD );
504   }
505   current_hostlist = NULL;
506   /* Alternating the usage of preconfigured and learned hostlists */
507
508   if ( GNUNET_YES == learning)
509     {
510     if (use_preconfigured_list == GNUNET_YES)
511       use_preconfigured_list = GNUNET_NO;
512     else
513       use_preconfigured_list = GNUNET_YES;
514     }
515   else
516     use_preconfigured_list = GNUNET_YES;
517 }
518
519 /**
520  * Clean up the state from the task that downloaded the
521  * hostlist and schedule the next task.
522  */
523 static void 
524 clean_up ()
525 {
526   CURLMcode mret;
527
528   if (multi != NULL)
529     {
530       mret = curl_multi_remove_handle (multi, curl);
531       if (mret != CURLM_OK)
532         {
533           GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
534                       _("%s failed at %s:%d: `%s'\n"),
535                       "curl_multi_remove_handle", __FILE__, __LINE__,
536                       curl_multi_strerror (mret));
537         }
538       mret = curl_multi_cleanup (multi);
539       if (mret != CURLM_OK)
540         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
541                     _("%s failed at %s:%d: `%s'\n"),
542                     "curl_multi_cleanup", __FILE__, __LINE__,
543                     curl_multi_strerror (mret));
544       multi = NULL;
545     }
546   if (curl != NULL)
547     {
548       curl_easy_cleanup (curl);
549       curl = NULL;
550     }  
551   GNUNET_free_non_null (current_url);
552   current_url = NULL;
553
554   schedule_hostlist_task ();
555 }
556
557
558 /**
559  * Ask CURL for the select set and then schedule the
560  * receiving task with the scheduler.
561  */
562 static void
563 run_multi (void);
564
565
566 /**
567  * Task that is run when we are ready to receive more data from the hostlist
568  * server. 
569  *
570  * @param cls closure, unused
571  * @param tc task context, unused
572  */
573 static void
574 multi_ready (void *cls,
575              const struct GNUNET_SCHEDULER_TaskContext *tc)
576 {
577   int running;
578   struct CURLMsg *msg;
579   CURLMcode mret;
580   
581   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
582     {
583 #if DEBUG_HOSTLIST_CLIENT
584       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
585                   "Shutdown requested while trying to download hostlist from `%s'\n",
586                   current_url);
587 #endif
588       update_hostlist();
589       clean_up ();
590       return;
591     }
592   if (GNUNET_TIME_absolute_get_remaining (end_time).value == 0)
593     {
594       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
595                   _("Timeout trying to download hostlist from `%s'\n"),
596                   current_url);
597       update_hostlist();
598       clean_up ();
599       return;
600     }
601 #if DEBUG_HOSTLIST_CLIENT
602   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
603               "Ready for processing hostlist client request\n");
604 #endif
605   do 
606     {
607       running = 0;
608       mret = curl_multi_perform (multi, &running);
609       if (running == 0)
610         {
611           do
612             {
613               msg = curl_multi_info_read (multi, &running);
614               GNUNET_break (msg != NULL);
615               if (msg == NULL)
616                 break;
617               switch (msg->msg)
618                 {
619                 case CURLMSG_DONE:
620                   if ( (msg->data.result != CURLE_OK) &&
621                        (msg->data.result != CURLE_GOT_NOTHING) )                       
622                     GNUNET_log(GNUNET_ERROR_TYPE_INFO,
623                                _("%s failed for `%s' at %s:%d: `%s'\n"),
624                                "curl_multi_perform", 
625                                current_url,
626                                __FILE__,
627                                __LINE__,
628                                curl_easy_strerror (msg->data.result));            
629                   else
630                     {
631                     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
632                                 _("Download of hostlist `%s' completed.\n"),
633                                 current_url);
634                     download_successful = GNUNET_YES;
635                     update_hostlist();
636                     }
637                   clean_up ();
638                   return;
639                 default:
640                   break;
641                 }
642             }
643           while (running > 0);
644         }
645     }
646   while (mret == CURLM_CALL_MULTI_PERFORM);
647   if (mret != CURLM_OK)
648     {
649       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
650                   _("%s failed at %s:%d: `%s'\n"),
651                   "curl_multi_perform", __FILE__, __LINE__,
652                   curl_multi_strerror (mret));
653       clean_up ();
654     }
655   run_multi ();
656 }
657
658
659 /**
660  * Ask CURL for the select set and then schedule the
661  * receiving task with the scheduler.
662  */
663 static void
664 run_multi () 
665 {
666   CURLMcode mret;
667   fd_set rs;
668   fd_set ws;
669   fd_set es;
670   int max;
671   struct GNUNET_NETWORK_FDSet *grs;
672   struct GNUNET_NETWORK_FDSet *gws;
673   long timeout;
674   struct GNUNET_TIME_Relative rtime;
675   
676   max = -1;
677   FD_ZERO (&rs);
678   FD_ZERO (&ws);
679   FD_ZERO (&es);
680   mret = curl_multi_fdset (multi, &rs, &ws, &es, &max);
681   if (mret != CURLM_OK)
682     {
683       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
684                   _("%s failed at %s:%d: `%s'\n"),
685                   "curl_multi_fdset", __FILE__, __LINE__,
686                   curl_multi_strerror (mret));
687       clean_up ();
688       return;
689     }
690   mret = curl_multi_timeout (multi, &timeout);
691   if (mret != CURLM_OK)
692     {
693       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
694                   _("%s failed at %s:%d: `%s'\n"),
695                   "curl_multi_timeout", __FILE__, __LINE__,
696                   curl_multi_strerror (mret));
697       clean_up ();
698       return;
699     }
700   rtime = GNUNET_TIME_relative_min (GNUNET_TIME_absolute_get_remaining (end_time),
701                                     GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
702                                                                    timeout));
703   grs = GNUNET_NETWORK_fdset_create ();
704   gws = GNUNET_NETWORK_fdset_create ();
705   GNUNET_NETWORK_fdset_copy_native (grs, &rs, max + 1);
706   GNUNET_NETWORK_fdset_copy_native (gws, &ws, max + 1);  
707 #if DEBUG_HOSTLIST_CLIENT
708   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
709               "Scheduling task for hostlist download using cURL\n");
710 #endif
711   current_task 
712     = GNUNET_SCHEDULER_add_select (sched,
713                                    GNUNET_SCHEDULER_PRIORITY_DEFAULT,
714                                    GNUNET_SCHEDULER_NO_TASK,
715                                    rtime,
716                                    grs,
717                                    gws,
718                                    &multi_ready,
719                                    multi);
720   GNUNET_NETWORK_fdset_destroy (gws);
721   GNUNET_NETWORK_fdset_destroy (grs);
722 }
723
724
725 /**
726  * Main function that will download a hostlist and process its
727  * data.
728  */
729 static void
730 download_hostlist () 
731 {
732   CURLcode ret;
733   CURLMcode mret;
734
735   current_url = get_list_url ();
736   if (current_url == NULL)
737     return;
738   curl = curl_easy_init ();
739   multi = NULL;
740   if (curl == NULL)
741     {
742       GNUNET_break (0);
743       clean_up ();
744       return;
745     }
746   GNUNET_log (GNUNET_ERROR_TYPE_INFO | GNUNET_ERROR_TYPE_BULK,
747               _("Bootstrapping using hostlist at `%s'.\n"), 
748               current_url);
749   hellos_obtained = 0;
750   download_successful = GNUNET_NO;
751   GNUNET_STATISTICS_update (stats, 
752                             gettext_noop ("# hostlist downloads initiated"), 
753                             1, 
754                             GNUNET_NO);  
755   if (proxy != NULL)
756     CURL_EASY_SETOPT (curl, CURLOPT_PROXY, proxy);    
757   download_pos = 0;
758   bogus_url = 0;
759   CURL_EASY_SETOPT (curl,
760                     CURLOPT_WRITEFUNCTION, 
761                     &download_hostlist_processor);
762   if (ret != CURLE_OK)
763     {
764       clean_up ();
765       return;
766     }
767   CURL_EASY_SETOPT (curl,
768                     CURLOPT_WRITEDATA, 
769                     NULL);
770   if (ret != CURLE_OK)
771     {
772       clean_up ();
773       return;
774     }
775   CURL_EASY_SETOPT (curl, CURLOPT_FOLLOWLOCATION, 1);
776   CURL_EASY_SETOPT (curl, CURLOPT_MAXREDIRS, 4);
777   /* no need to abort if the above failed */
778   CURL_EASY_SETOPT (curl, 
779                     CURLOPT_URL, 
780                     current_url);
781   if (ret != CURLE_OK)
782     {
783       clean_up ();
784       return;
785     }
786   CURL_EASY_SETOPT (curl, 
787                     CURLOPT_FAILONERROR, 
788                     1);
789 #if 0
790   CURL_EASY_SETOPT (curl, 
791                     CURLOPT_VERBOSE, 
792                     1);
793 #endif
794   CURL_EASY_SETOPT (curl, 
795                     CURLOPT_BUFFERSIZE, 
796                     GNUNET_SERVER_MAX_MESSAGE_SIZE);
797   if (0 == strncmp (current_url, "http", 4))
798     CURL_EASY_SETOPT (curl, CURLOPT_USERAGENT, "GNUnet");
799   CURL_EASY_SETOPT (curl, 
800                     CURLOPT_CONNECTTIMEOUT, 
801                     60L);
802   CURL_EASY_SETOPT (curl, 
803                     CURLOPT_TIMEOUT, 
804                     60L);
805 #if 0
806   /* this should no longer be needed; we're now single-threaded! */
807   CURL_EASY_SETOPT (curl,
808                     CURLOPT_NOSIGNAL, 
809                     1);
810 #endif
811   multi = curl_multi_init ();
812   if (multi == NULL)
813     {
814       GNUNET_break (0);
815       clean_up ();
816       return;
817     }
818   mret = curl_multi_add_handle (multi, curl);
819   if (mret != CURLM_OK)
820     {
821       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
822                   _("%s failed at %s:%d: `%s'\n"),
823                   "curl_multi_add_handle", __FILE__, __LINE__,
824                   curl_multi_strerror (mret));
825       mret = curl_multi_cleanup (multi);
826       if (mret != CURLM_OK)
827         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
828                     _("%s failed at %s:%d: `%s'\n"),
829                     "curl_multi_cleanup", __FILE__, __LINE__,
830                     curl_multi_strerror (mret));
831       multi = NULL;
832       clean_up ();
833       return;
834     }
835   end_time = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_MINUTES);
836   run_multi ();
837 }  
838
839
840 /**
841  * Task that checks if we should try to download a hostlist.
842  * If so, we initiate the download, otherwise we schedule
843  * this task again for a later time.
844  */
845 static void
846 check_task (void *cls,
847             const struct GNUNET_SCHEDULER_TaskContext *tc)
848 {
849   current_task = GNUNET_SCHEDULER_NO_TASK;
850   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
851     return;
852   if (connection_count < MIN_CONNECTIONS)
853     download_hostlist ();
854   else
855     schedule_hostlist_task ();
856 }
857
858
859 /**
860  * Compute when we should check the next time about downloading
861  * a hostlist; then schedule the task accordingly.
862  */
863 static void
864 schedule_hostlist_task ()
865 {
866   static int once;
867   struct GNUNET_TIME_Relative delay;
868
869   if (stats == NULL)
870     {
871       curl_global_cleanup ();
872       return; /* in shutdown */
873     }
874   delay = hostlist_delay;
875   if (hostlist_delay.value == 0)
876     hostlist_delay = GNUNET_TIME_UNIT_SECONDS;
877   else
878     hostlist_delay = GNUNET_TIME_relative_multiply (hostlist_delay, 2);
879   if (hostlist_delay.value > GNUNET_TIME_UNIT_HOURS.value * (1 + connection_count))
880     hostlist_delay = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_HOURS,
881                                                     (1 + connection_count));
882   GNUNET_STATISTICS_set (stats,
883                          gettext_noop("# seconds between hostlist downloads"),
884                          hostlist_delay.value,
885                          GNUNET_YES);
886   if (0 == once)
887     {
888       delay = GNUNET_TIME_UNIT_ZERO;
889       once = 1;
890     }  
891   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
892               _("Have %u/%u connections.  Will consider downloading hostlist in %llums\n"),
893               connection_count,
894               MIN_CONNECTIONS,
895               (unsigned long long) delay.value);
896   current_task = GNUNET_SCHEDULER_add_delayed (sched,
897                                                delay,
898                                                &check_task,
899                                                NULL);
900 }
901
902 /**
903  * Task that writes hostlist entries to a file on a regular base
904  * cls closure
905  * tc TaskContext
906  */
907 static void
908 hostlist_saving_task (void *cls,
909             const struct GNUNET_SCHEDULER_TaskContext *tc)
910 {
911   saving_task = GNUNET_SCHEDULER_NO_TASK;
912   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
913     return;
914   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
915               _("Scheduled saving of hostlists\n"));
916   save_hostlist_file ( GNUNET_NO );
917
918   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
919               _("Hostlists will be saved to file again in %llums\n"),
920               (unsigned long long) SAVING_INTERVALL.value);
921   saving_task = GNUNET_SCHEDULER_add_delayed (sched,
922                                                SAVING_INTERVALL,
923                                                &hostlist_saving_task,
924                                                NULL);
925 }
926
927 /**
928  * Method called whenever a given peer connects.
929  *
930  * @param cls closure
931  * @param peer peer identity this notification is about
932  * @param latency reported latency of the connection with 'other'
933  * @param distance reported distance (DV) to 'other' 
934  */
935 static void
936 connect_handler (void *cls,
937                  const struct
938                  GNUNET_PeerIdentity * peer,
939                  struct GNUNET_TIME_Relative latency,
940                  uint32_t distance)
941 {
942   connection_count++;
943   GNUNET_STATISTICS_update (stats, 
944                             gettext_noop ("# active connections"), 
945                             1, 
946                             GNUNET_NO);  
947 }
948
949
950 /**
951  * Method called whenever a given peer disconnects.
952  *
953  * @param cls closure
954  * @param peer peer identity this notification is about
955  */
956 static void
957 disconnect_handler (void *cls,
958                     const struct
959                     GNUNET_PeerIdentity * peer)
960 {
961   connection_count--;
962   GNUNET_STATISTICS_update (stats, 
963                             gettext_noop ("# active connections"), 
964                             -1, 
965                             GNUNET_NO);  
966 }
967
968
969 /**
970  * Method to check if URI is in hostlist linked list
971  * @param uri uri to check
972  * @return GNUNET_YES if existing in linked list, GNUNET_NO if not
973  */
974 static int 
975 linked_list_contains (const char * uri)
976 {
977   struct Hostlist * pos;
978
979   pos = linked_list_head;
980   while (pos != NULL)
981     {
982       if (0 == strcmp(pos->hostlist_uri, uri) ) 
983         return GNUNET_YES;
984       pos = pos->next;
985     }
986   return GNUNET_NO;
987 }
988
989
990 /* linked_list_? */
991 static struct Hostlist *
992 linked_list_get_lowest_quality ( )
993 {
994   struct Hostlist * pos;
995   struct Hostlist * lowest;
996
997   if (linked_list_size == 0)
998     return NULL;
999   lowest = linked_list_head;
1000   pos = linked_list_head->next;
1001   while (pos != NULL)
1002     {
1003       if (pos->quality < lowest->quality) 
1004         lowest = pos;
1005       pos = pos->next;
1006     }
1007   return lowest;
1008 }
1009
1010
1011 /**
1012  * Method called whenever an advertisement message arrives.
1013  *
1014  * @param cls closure (always NULL)
1015  * @param peer the peer sending the message
1016  * @param message the actual message
1017  * @param latency latency
1018  * @param distance distance
1019  * @return GNUNET_OK to keep the connection open,
1020  *         GNUNET_SYSERR to close it (signal serious error)
1021  */
1022 static int
1023 advertisement_handler (void *cls,
1024     const struct GNUNET_PeerIdentity * peer,
1025     const struct GNUNET_MessageHeader * message,
1026     struct GNUNET_TIME_Relative latency,
1027     uint32_t distance)
1028 {
1029   size_t size;
1030   size_t uri_size;
1031   const struct GNUNET_MessageHeader * incoming;
1032   const char *uri;
1033   struct Hostlist * hostlist;
1034
1035   GNUNET_assert (ntohs (message->type) == GNUNET_MESSAGE_TYPE_HOSTLIST_ADVERTISEMENT);
1036   size = ntohs (message->size);
1037   if (size <= sizeof(struct GNUNET_MessageHeader))
1038     {
1039       GNUNET_break_op (0);
1040       return GNUNET_SYSERR;
1041     }
1042   incoming = (const struct GNUNET_MessageHeader *) message;
1043   uri = (const char*) &incoming[1];
1044   uri_size = size - sizeof (struct GNUNET_MessageHeader);
1045   if (uri [uri_size - 1] != '\0')
1046     {
1047       GNUNET_break_op (0);
1048       return GNUNET_SYSERR;
1049     }
1050   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1051               "Hostlist client recieved advertisement from `%s' containing URI `%s'\n", 
1052               GNUNET_i2s (peer), 
1053               uri);
1054   if (GNUNET_NO != linked_list_contains (uri))
1055     {
1056       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1057                 "URI `%s' is already known\n",
1058                 uri);
1059       return GNUNET_OK;
1060     }
1061   hostlist = GNUNET_malloc (sizeof (struct Hostlist) + uri_size);
1062   hostlist->hostlist_uri = (const char*) &hostlist[1];
1063   memcpy (&hostlist[1], uri, uri_size);
1064   hostlist->time_creation = GNUNET_TIME_absolute_get();
1065   hostlist->time_last_usage = GNUNET_TIME_absolute_get_zero();
1066   hostlist->quality = HOSTLIST_INITIAL;  
1067
1068   GNUNET_CONTAINER_DLL_insert(linked_list_head, linked_list_tail, hostlist);
1069   linked_list_size++;
1070   
1071   if (MAX_NUMBER_HOSTLISTS >= linked_list_size)
1072     return GNUNET_OK;
1073
1074   /* No free entries available, replace existing entry  */
1075   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1076               "Removing lowest quality entry\n" );  
1077   struct Hostlist * lowest_quality = linked_list_get_lowest_quality();
1078   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1079               "Hostlist with URI `%s' has the worst quality of all with value %llu\n", 
1080               lowest_quality->hostlist_uri,
1081               (unsigned long long) lowest_quality->quality);
1082   GNUNET_CONTAINER_DLL_remove (linked_list_head, linked_list_tail, lowest_quality);
1083   linked_list_size--;
1084   GNUNET_free (lowest_quality);
1085   return GNUNET_OK;
1086 }
1087
1088
1089 /**
1090  * Continuation called by the statistics code once 
1091  * we go the stat.  Initiates hostlist download scheduling.
1092  *
1093  * @param cls closure
1094  * @param success GNUNET_OK if statistics were
1095  *        successfully obtained, GNUNET_SYSERR if not.
1096  */
1097 static void
1098 primary_task (void *cls, int success)
1099 {
1100   if (stats == NULL)
1101     return; /* in shutdown */
1102 #if DEBUG_HOSTLIST_CLIENT
1103   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1104               "Statistics request done, scheduling hostlist download\n");
1105 #endif
1106   schedule_hostlist_task ();
1107 }
1108
1109
1110 static int
1111 process_stat (void *cls,
1112               const char *subsystem,
1113               const char *name,
1114               uint64_t value,
1115               int is_persistent)
1116 {
1117   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1118               _("Initial time between hostlist downloads is %llums\n"),
1119               (unsigned long long) value);
1120   hostlist_delay.value = value;
1121   return GNUNET_OK;
1122 }
1123
1124 /**
1125  * Method to load persistent hostlist file during hostlist client startup
1126  */
1127 static void 
1128 load_hostlist_file ()
1129 {
1130   char *filename;
1131   char *uri;
1132   char *emsg;
1133   struct Hostlist * hostlist;
1134   uri = NULL;
1135   uint32_t times_used;
1136   uint32_t hellos_returned;
1137   uint64_t quality;
1138   uint64_t last_used;
1139   uint64_t created;
1140   uint32_t counter;
1141
1142   if (GNUNET_OK !=
1143       GNUNET_CONFIGURATION_get_value_string (cfg,
1144                                              "HOSTLIST",
1145                                              "HOSTLISTFILE",
1146                                              &filename))
1147     {
1148       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1149                   _("No `%s' specified in `%s' configuration, cannot load hostlists from file.\n"),
1150                   "HOSTLISTFILE", "HOSTLIST");
1151       return;
1152     }
1153
1154   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1155               _("Loading saved hostlist entries from file `%s' \n"), filename);
1156
1157   struct GNUNET_BIO_ReadHandle * rh = GNUNET_BIO_read_open (filename);
1158   if (NULL == rh)
1159     {
1160       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1161                   _("Could not open file `%s' for reading to load hostlists: %s\n"), 
1162                   filename,
1163                   STRERROR (errno));
1164       GNUNET_free (filename);
1165       return;
1166     }
1167
1168   counter = 0;
1169   while ( (GNUNET_OK == GNUNET_BIO_read_string (rh, "url" , &uri, MAX_URL_LEN)) &&
1170           (GNUNET_OK == GNUNET_BIO_read_int32 (rh, &times_used)) &&
1171           (GNUNET_OK == GNUNET_BIO_read_int64 (rh, &quality)) &&
1172           (GNUNET_OK == GNUNET_BIO_read_int64 (rh, &last_used)) &&
1173           (GNUNET_OK == GNUNET_BIO_read_int64 (rh, &created)) &&
1174           (GNUNET_OK == GNUNET_BIO_read_int32 (rh, &hellos_returned)) )
1175     {
1176       hostlist = GNUNET_malloc (sizeof (struct Hostlist) + strlen (uri) + 1);
1177       hostlist->hello_count = hellos_returned;
1178       hostlist->hostlist_uri = (const char *) &hostlist[1];
1179       memcpy (&hostlist[1], uri, strlen(uri)+1);
1180       hostlist->quality = quality;
1181       hostlist->time_creation.value = created;
1182       hostlist->time_last_usage.value = last_used;
1183       GNUNET_CONTAINER_DLL_insert(linked_list_head, linked_list_tail, hostlist);
1184       linked_list_size++;
1185       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1186                   "Added hostlist entry eith URI `%s' \n", hostlist->hostlist_uri);
1187       GNUNET_free (uri);
1188       uri = NULL;
1189       counter++;
1190       if ( counter >= MAX_NUMBER_HOSTLISTS ) break;
1191     }
1192
1193   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1194               _("%u hostlist URIs loaded from file\n"), counter);
1195   GNUNET_STATISTICS_set (stats,
1196                          gettext_noop("# hostlis URIs read from file"),
1197                          counter,
1198                          GNUNET_YES);
1199
1200   GNUNET_free_non_null (uri);
1201   emsg = NULL;
1202   GNUNET_BIO_read_close (rh, &emsg);
1203   if (emsg != NULL)
1204     GNUNET_free (emsg);
1205   GNUNET_free (filename);
1206 }
1207
1208
1209 /**
1210  * Method to load persistent hostlist file during hostlist client shutdown
1211  * @param shutdown set if called because of shutdown, entries in linked list will be destroyed
1212  */
1213 static void save_hostlist_file ( int shutdown )
1214 {
1215   char *filename;
1216   struct Hostlist *pos;
1217   struct GNUNET_BIO_WriteHandle * wh;
1218   int ok;
1219   uint32_t counter;
1220
1221   if (GNUNET_OK !=
1222       GNUNET_CONFIGURATION_get_value_string (cfg,
1223                                              "HOSTLIST",
1224                                              "HOSTLISTFILE",
1225                                              &filename))
1226     {
1227       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1228                   _("No `%s' specified in `%s' configuration, cannot save hostlists to file.\n"),
1229                   "HOSTLISTFILE", "HOSTLIST");
1230                   GNUNET_free (filename);
1231       return;
1232     }
1233   wh = GNUNET_BIO_write_open (filename);
1234   if ( NULL == wh)
1235     {
1236       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1237                   _("Could not open file `%s' for writing to save hostlists: %s\n"),
1238                   filename,
1239                   STRERROR (errno));
1240                   GNUNET_free (filename);
1241       return;
1242     }
1243   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1244               _("Writing %u hostlist URIs to `%s'\n" ),
1245               linked_list_size, filename);
1246
1247   /* add code to write hostlists to file using bio */
1248   ok = GNUNET_YES;
1249   counter = 0;
1250   while (NULL != (pos = linked_list_head))
1251     {
1252       if ( GNUNET_YES == shutdown)
1253       {
1254         GNUNET_CONTAINER_DLL_remove (linked_list_head, linked_list_tail, pos);
1255         linked_list_size--;
1256       }
1257       if (GNUNET_YES == ok)
1258         {
1259           if ( (GNUNET_OK !=
1260                 GNUNET_BIO_write_string (wh, pos->hostlist_uri)) ||
1261                (GNUNET_OK !=
1262                 GNUNET_BIO_write_int32 (wh, pos->times_used)) ||
1263                (GNUNET_OK !=
1264                 GNUNET_BIO_write_int64 (wh, pos->quality)) ||
1265                (GNUNET_OK !=
1266                 GNUNET_BIO_write_int64 (wh, pos->time_last_usage.value)) ||
1267                (GNUNET_OK !=
1268                 GNUNET_BIO_write_int64 (wh, pos->time_creation.value)) ||
1269                (GNUNET_OK !=
1270                 GNUNET_BIO_write_int32 (wh, pos->hello_count)))
1271             {
1272               GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1273                           _("Error writing hostlist URIs to file `%s'\n"),
1274                           filename);
1275               ok = GNUNET_NO;
1276             }
1277         }
1278
1279       if ( GNUNET_YES == shutdown)
1280         GNUNET_free (pos);
1281       counter ++;
1282       if ( counter >= MAX_NUMBER_HOSTLISTS) break;
1283     }  
1284   GNUNET_STATISTICS_set (stats,
1285                          gettext_noop("# hostlist URIs written to file"),
1286                          counter,
1287                          GNUNET_YES);
1288
1289   if ( GNUNET_OK != GNUNET_BIO_write_close ( wh ) )
1290     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
1291                 _("Error writing hostlist URIs to file `%s'\n"),
1292                 filename);
1293   GNUNET_free (filename);
1294 }
1295
1296 /**
1297  * Start downloading hostlists from hostlist servers as necessary.
1298  */
1299 int
1300 GNUNET_HOSTLIST_client_start (const struct GNUNET_CONFIGURATION_Handle *c,
1301                               struct GNUNET_SCHEDULER_Handle *s,
1302                               struct GNUNET_STATISTICS_Handle *st,
1303                               GNUNET_CORE_ConnectEventHandler *ch,
1304                               GNUNET_CORE_DisconnectEventHandler *dh,
1305                               GNUNET_CORE_MessageCallback *msgh,
1306                               int learn)
1307 {
1308   char *filename;
1309   int result;
1310
1311   if (0 != curl_global_init (CURL_GLOBAL_WIN32))
1312     {
1313       GNUNET_break (0);
1314       return GNUNET_SYSERR;
1315     }
1316   transport = GNUNET_TRANSPORT_connect (s, c, NULL, NULL, NULL, NULL);
1317   if (NULL == transport)
1318     {
1319       curl_global_cleanup ();
1320       return GNUNET_SYSERR;
1321     }
1322   cfg = c;
1323   sched = s;
1324   stats = st;
1325   if (GNUNET_OK !=
1326       GNUNET_CONFIGURATION_get_value_string (cfg,
1327                                              "HOSTLIST",
1328                                              "HTTP-PROXY", 
1329                                              &proxy))
1330     proxy = NULL;
1331   *ch = &connect_handler;
1332   *dh = &disconnect_handler;
1333   if (learn)
1334     *msgh = &advertisement_handler;
1335   else
1336     *msgh = NULL;
1337   linked_list_head = NULL;
1338   linked_list_tail = NULL;
1339   use_preconfigured_list = GNUNET_YES;
1340   learning = learn;
1341   if ( GNUNET_YES == learning )
1342   {
1343     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1344               _("Learning is enabled on this peer\n"));
1345     load_hostlist_file ();
1346     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1347               _("Hostlists will be saved to file again in  %llums\n"),
1348               (unsigned long long) SAVING_INTERVALL.value);
1349     saving_task = GNUNET_SCHEDULER_add_delayed (sched,
1350                                                SAVING_INTERVALL,
1351                                                &hostlist_saving_task,
1352                                                NULL);
1353   }
1354   else
1355   {
1356     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1357               _("Learning is not enabled on this peer\n"));
1358     if (GNUNET_OK == GNUNET_CONFIGURATION_get_value_string (cfg,
1359                                                             "HOSTLIST",
1360                                                             "HOSTLISTFILE",
1361                                                             &filename))
1362     {
1363     if ( GNUNET_YES == GNUNET_DISK_file_test (filename) )
1364       {
1365         result = remove (filename);
1366         if (result == 0)
1367         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1368               _("Since learning is not enabled on this peer, hostlist file `%s' was removed\n"),filename);
1369         else
1370           GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1371                 _("Hostlist file `%s' could not be removed\n"),filename);
1372       }
1373     }
1374     GNUNET_free ( filename );
1375   }
1376   GNUNET_STATISTICS_get (stats,
1377                          "hostlist",
1378                          gettext_noop("# seconds between hostlist downloads"),
1379                          GNUNET_TIME_UNIT_MINUTES,
1380                          &primary_task,
1381                          &process_stat,
1382                          NULL);
1383   return GNUNET_OK;
1384 }
1385
1386
1387 /**
1388  * Stop downloading hostlists from hostlist servers as necessary.
1389  */
1390 void
1391 GNUNET_HOSTLIST_client_stop ()
1392 {
1393 #if DEBUG_HOSTLIST_CLIENT
1394   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1395               "Hostlist client shutdown\n");
1396 #endif
1397   save_hostlist_file ( GNUNET_YES );
1398
1399   if (current_task != GNUNET_SCHEDULER_NO_TASK)
1400     {
1401       GNUNET_SCHEDULER_cancel (sched,
1402                                current_task);
1403       curl_global_cleanup ();
1404     }
1405   if (transport != NULL)
1406     {
1407       GNUNET_TRANSPORT_disconnect (transport);
1408       transport = NULL;
1409     }
1410   GNUNET_assert (NULL == transport);
1411   GNUNET_free_non_null (proxy);
1412   proxy = NULL;
1413   cfg = NULL;
1414   sched = NULL;
1415 }
1416
1417 /* end of hostlist-client.c */