use enum GNUNET_ATS_Network_Type instead of uint32_t where appropriate
[oweals/gnunet.git] / src / peerinfo / gnunet-service-peerinfo.c
1 /*
2      This file is part of GNUnet.
3      (C) 2001, 2002, 2004, 2005, 2007, 2009, 2010, 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 /**
22  * @file peerinfo/gnunet-service-peerinfo.c
23  * @brief maintains list of known peers
24  *
25  * Code to maintain the list of currently known hosts (in memory
26  * structure of data/hosts/).
27  *
28  * @author Christian Grothoff
29  *
30  * TODO:
31  * - notify clients when addresses in HELLO expire (#1933)
32  */
33
34 #include "platform.h"
35 #include "gnunet_util_lib.h"
36 #include "gnunet_hello_lib.h"
37 #include "gnunet_protocols.h"
38 #include "gnunet_statistics_service.h"
39 #include "peerinfo.h"
40
41 /**
42  * How often do we scan the HOST_DIR for new entries?
43  */
44 #define DATA_HOST_FREQ GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 15)
45
46 /**
47  * How often do we discard old entries in data/hosts/?
48  */
49 #define DATA_HOST_CLEAN_FREQ GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 60)
50
51
52 /**
53  * In-memory cache of known hosts.
54  */
55 struct HostEntry
56 {
57
58   /**
59    * Identity of the peer.
60    */
61   struct GNUNET_PeerIdentity identity;
62
63   /**
64    * Hello for the peer (can be NULL)
65    */
66   struct GNUNET_HELLO_Message *hello;
67
68   /**
69    * Friend only hello for the peer (can be NULL)
70    */
71   struct GNUNET_HELLO_Message *friend_only_hello;
72
73 };
74
75 /**
76  * Transmit context for GET requests
77  */
78 struct TransmitContext
79 {
80   /**
81    * Server transmit context
82    */
83   struct GNUNET_SERVER_TransmitContext *tc;
84
85   /**
86    * Include friend only HELLOs GNUNET_YES or _NO
87    */
88   int friend_only;
89 };
90
91 /**
92  * Result of reading a file
93  */
94 struct ReadHostFileContext
95 {
96   /**
97    * Hello for the peer (can be NULL)
98    */
99   struct GNUNET_HELLO_Message *hello;
100
101   /**
102    * Friend only hello for the peer (can be NULL)
103    */
104   struct GNUNET_HELLO_Message *friend_only_hello;
105 };
106
107
108 /**
109  * Client notification context
110  */
111 struct NotificationContext
112 {
113         /**
114          * Next in DLL
115          */
116         struct NotificationContext *prev;
117
118         /**
119          * Previous in DLL
120          */
121         struct NotificationContext *next;
122
123         /**
124          * Server client
125          */
126         struct GNUNET_SERVER_Client *client;
127
128         /**
129          * Interested in friend only HELLO?
130          */
131         int include_friend_only;
132 };
133
134
135 /**
136  * The in-memory list of known hosts, mapping of
137  * host IDs to 'struct HostEntry*' values.
138  */
139 static struct GNUNET_CONTAINER_MultiPeerMap *hostmap;
140
141 /**
142  * Clients to immediately notify about all changes.
143  */
144 static struct GNUNET_SERVER_NotificationContext *notify_list;
145
146 /**
147  * Directory where the hellos are stored in (peerinfo/)
148  */
149 static char *networkIdDirectory;
150
151 /**
152  * Handle for reporting statistics.
153  */
154 static struct GNUNET_STATISTICS_Handle *stats;
155
156 /**
157  * DLL of notification contexts: head
158  */
159 static struct NotificationContext *nc_head;
160
161 /**
162  * DLL of notification contexts: tail
163  */
164 static struct NotificationContext *nc_tail;
165
166
167 /**
168  * Notify all clients in the notify list about the
169  * given host entry changing.
170  *
171  * @param he entry of the host for which we generate a notification
172  * @param include_friend_only create public of friend-only message
173  * @return generated notification message
174  */
175 static struct InfoMessage *
176 make_info_message (const struct HostEntry *he, int include_friend_only)
177 {
178   struct InfoMessage *im;
179   struct GNUNET_HELLO_Message *src;
180   size_t hs;
181
182   if (GNUNET_YES == include_friend_only)
183         src = he->friend_only_hello;
184   else
185         src = he->hello;
186
187   hs = (NULL == src) ? 0 : GNUNET_HELLO_size (src);
188   im = GNUNET_malloc (sizeof (struct InfoMessage) + hs);
189   im->header.size = htons (hs + sizeof (struct InfoMessage));
190   im->header.type = htons (GNUNET_MESSAGE_TYPE_PEERINFO_INFO);
191   im->peer = he->identity;
192   if (NULL != src)
193     memcpy (&im[1], src, hs);
194   return im;
195 }
196
197
198 /**
199  * Address iterator that causes expired entries to be discarded.
200  *
201  * @param cls pointer to the current time
202  * @param address the address
203  * @param expiration expiration time for the address
204  * @return GNUNET_NO if expiration smaller than the current time
205  */
206 static int
207 discard_expired (void *cls, const struct GNUNET_HELLO_Address *address,
208                  struct GNUNET_TIME_Absolute expiration)
209 {
210   const struct GNUNET_TIME_Absolute *now = cls;
211
212   if (now->abs_value_us > expiration.abs_value_us)
213   {
214     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
215                 _("Removing expired address of transport `%s'\n"),
216                 address->transport_name);
217     return GNUNET_NO;
218   }
219   return GNUNET_OK;
220 }
221
222
223 /**
224  * Address iterator that counts the remaining addresses.
225  *
226  * @param cls pointer to the counter
227  * @param address the address
228  * @param expiration expiration time for the address
229  * @return GNUNET_OK (always)
230  */
231 static int
232 count_addresses (void *cls, const struct GNUNET_HELLO_Address *address,
233                  struct GNUNET_TIME_Absolute expiration)
234 {
235   unsigned int *cnt = cls;
236
237   (*cnt)++;
238   return GNUNET_OK;
239 }
240
241
242 /**
243  * Get the filename under which we would store the GNUNET_HELLO_Message
244  * for the given host and protocol.
245  *
246  * @param id peer for which we need the filename for the HELLO
247  * @return filename of the form DIRECTORY/HOSTID
248  */
249 static char *
250 get_host_filename (const struct GNUNET_PeerIdentity *id)
251 {
252   char *fn;
253
254   if (NULL == networkIdDirectory)
255     return NULL;
256   GNUNET_asprintf (&fn, "%s%s%s", networkIdDirectory, DIR_SEPARATOR_STR,
257                    GNUNET_i2s_full (id));
258   return fn;
259 }
260
261
262 /**
263  * Broadcast information about the given entry to all
264  * clients that care.
265  *
266  * @param entry entry to broadcast about
267  */
268 static void
269 notify_all (struct HostEntry *entry)
270 {
271   struct InfoMessage *msg_pub;
272   struct InfoMessage *msg_friend;
273   struct NotificationContext *cur;
274
275   msg_pub = make_info_message (entry, GNUNET_NO);
276   msg_friend = make_info_message (entry, GNUNET_YES);
277   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
278               "Notifying all clients about peer `%s'\n",
279               GNUNET_i2s(&entry->identity));
280   for (cur = nc_head; NULL != cur; cur = cur->next)
281   {
282     if (GNUNET_NO == cur->include_friend_only)
283       {
284         GNUNET_SERVER_notification_context_unicast (notify_list,
285                                                     cur->client,
286                                                     &msg_pub->header,
287                                                     GNUNET_NO);
288       }
289     if (GNUNET_YES == cur->include_friend_only)
290     {
291       GNUNET_SERVER_notification_context_unicast (notify_list,
292                                                   cur->client,
293                                                   &msg_friend->header,
294                                                   GNUNET_NO);
295     }
296   }
297   GNUNET_free (msg_pub);
298   GNUNET_free (msg_friend);
299 }
300
301
302 /**
303  * Bind a host address (hello) to a hostId.
304  *
305  * @param peer the peer for which this is a hello
306  * @param hello the verified (!) hello message
307  */
308 static void
309 update_hello (const struct GNUNET_PeerIdentity *peer,
310               const struct GNUNET_HELLO_Message *hello);
311
312
313 /**
314  * Try to read the HELLOs in the given filename and discard expired
315  * addresses.  Removes the file if one the HELLO is mal-formed.  If all
316  * addresses are expired, the HELLO is also removed (but the HELLO
317  * with the public key is still returned if it was found and valid).
318  *
319  * The file can contain multiple HELLO messages, but onlu a public and a friend only
320  * HELLO should be included
321  *
322  * @param fn name of the file
323  * @param unlink_garbage if #GNUNET_YES, try to remove useless files
324  * @param r ReadHostFileContext to store the resutl
325  */
326 static void
327 read_host_file (const char *fn,
328                 int unlink_garbage,
329                 struct ReadHostFileContext *r)
330 {
331   char buffer[GNUNET_SERVER_MAX_MESSAGE_SIZE - 1] GNUNET_ALIGN;
332   unsigned int size_total;
333   struct GNUNET_TIME_Absolute now;
334   unsigned int left;
335
336   const struct GNUNET_HELLO_Message *hello;
337   struct GNUNET_HELLO_Message *hello_clean;
338   unsigned read_pos;
339   int size_hello;
340
341   r->friend_only_hello = NULL;
342   r->hello = NULL;
343
344   if (GNUNET_YES != GNUNET_DISK_file_test (fn))
345     return;
346   size_total = GNUNET_DISK_fn_read (fn, buffer, sizeof (buffer));
347   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
348               "Read %u bytes from `%s'\n",
349               size_total,
350               fn);
351   if (size_total < sizeof (struct GNUNET_MessageHeader))
352   {
353     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
354                 _("Failed to parse HELLO in file `%s': %s\n"),
355                 fn, "Fail has invalid size");
356     if ( (GNUNET_YES == unlink_garbage) &&
357          (0 != UNLINK (fn)) &&
358          (ENOENT != errno) )
359       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
360                                 "unlink",
361                                 fn);
362     return;
363   }
364
365   read_pos = 0;
366   while (read_pos < size_total)
367   {
368     hello = (const struct GNUNET_HELLO_Message *) &buffer[read_pos];
369     size_hello = GNUNET_HELLO_size (hello);
370     if (0 == size_hello)
371       {
372         GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
373                     _("Failed to parse HELLO in file `%s': %s %u \n"),
374                     fn,
375                     "HELLO is invalid and has size of ",
376                     size_hello);
377         if ((GNUNET_YES == unlink_garbage) &&
378             (0 != UNLINK (fn)) &&
379             (ENOENT != errno) )
380           GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
381                                     "unlink",
382                                     fn);
383         return;
384       }
385
386     now = GNUNET_TIME_absolute_get ();
387     hello_clean = GNUNET_HELLO_iterate_addresses (hello, GNUNET_YES,
388                                                   &discard_expired, &now);
389     left = 0;
390     (void) GNUNET_HELLO_iterate_addresses (hello_clean, GNUNET_NO,
391                                            &count_addresses, &left);
392
393     if (0 == left)
394     {
395       GNUNET_free (hello_clean);
396       break;
397     }
398
399     if (GNUNET_NO == GNUNET_HELLO_is_friend_only (hello_clean))
400     {
401       if (NULL == r->hello)
402         r->hello = hello_clean;
403       else
404       {
405         GNUNET_break (0);
406         GNUNET_free (r->hello);
407         r->hello = hello_clean;
408       }
409     }
410     else
411     {
412       if (NULL == r->friend_only_hello)
413         r->friend_only_hello = hello_clean;
414       else
415       {
416         GNUNET_break (0);
417         GNUNET_free (r->friend_only_hello);
418         r->friend_only_hello = hello_clean;
419       }
420     }
421     read_pos += size_hello;
422   }
423
424   if (0 == left)
425   {
426     /* no addresses left, remove from disk */
427     if ( (GNUNET_YES == unlink_garbage) &&
428          (0 != UNLINK (fn)) )
429       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING,
430                                 "unlink",
431                                 fn);
432   }
433   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
434               "Found `%s' and `%s' HELLO message in file\n",
435               (NULL != r->hello) ? "public" : "NO public",
436               (NULL != r->friend_only_hello) ? "friend only" : "NO friend only");
437 }
438
439
440 /**
441  * Add a host to the list and notify clients about this event
442  *
443  * @param identity the identity of the host
444  * @return the HostEntry
445  */
446 static struct HostEntry *
447 add_host_to_known_hosts (const struct GNUNET_PeerIdentity *identity)
448 {
449   struct HostEntry *entry;
450   struct ReadHostFileContext r;
451   char *fn;
452
453   entry = GNUNET_CONTAINER_multipeermap_get (hostmap, identity);
454   if (NULL == entry)
455   {
456     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Adding new peer `%s'\n", GNUNET_i2s (identity));
457     GNUNET_STATISTICS_update (stats, gettext_noop ("# peers known"), 1,
458                               GNUNET_NO);
459     entry = GNUNET_new (struct HostEntry);
460     entry->identity = *identity;
461     GNUNET_assert (GNUNET_OK ==
462                    GNUNET_CONTAINER_multipeermap_put (hostmap, &entry->identity, entry,
463                                                       GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
464     notify_all (entry);
465     fn = get_host_filename (identity);
466     if (NULL != fn)
467     {
468       read_host_file (fn, GNUNET_YES, &r);
469       if (NULL != r.hello)
470         update_hello (identity, r.hello);
471       if (NULL != r.friend_only_hello)
472         update_hello (identity, r.friend_only_hello);
473       GNUNET_free_non_null (r.hello);
474       GNUNET_free_non_null (r.friend_only_hello);
475       GNUNET_free (fn);
476     }
477   }
478   return entry;
479 }
480
481
482 /**
483  * Remove a file that should not be there.  LOG
484  * success or failure.
485  *
486  * @param fullname name of the file to remove
487  */
488 static void
489 remove_garbage (const char *fullname)
490 {
491   if (0 == UNLINK (fullname))
492     GNUNET_log (GNUNET_ERROR_TYPE_WARNING | GNUNET_ERROR_TYPE_BULK,
493                 _
494                 ("File `%s' in directory `%s' does not match naming convention. "
495                  "Removed.\n"), fullname, networkIdDirectory);
496   else
497     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK,
498                               "unlink", fullname);
499 }
500
501
502 /**
503  * Closure for 'hosts_directory_scan_callback'.
504  */
505 struct DirScanContext
506 {
507   /**
508    * GNUNET_YES if we should remove files that are broken,
509    * GNUNET_NO if the directory we are iterating over should
510    * be treated as read-only by us.
511    */
512   int remove_files;
513
514   /**
515    * Counter for the number of (valid) entries found, incremented
516    * by one for each match.
517    */
518   unsigned int matched;
519 };
520
521
522 /**
523  * Function that is called on each HELLO file in a particular directory.
524  * Try to parse the file and add the HELLO to our list.
525  *
526  * @param cls pointer to 'unsigned int' to increment for each file, or NULL
527  *            if the file is from a read-only, read-once resource directory
528  * @param fullname name of the file to parse
529  * @return #GNUNET_OK (continue iteration)
530  */
531 static int
532 hosts_directory_scan_callback (void *cls, const char *fullname)
533 {
534   struct DirScanContext *dsc = cls;
535   struct GNUNET_PeerIdentity identity;
536   struct ReadHostFileContext r;
537   const char *filename;
538   struct GNUNET_PeerIdentity id_public;
539   struct GNUNET_PeerIdentity id_friend;
540   struct GNUNET_PeerIdentity id;
541
542   if (GNUNET_YES != GNUNET_DISK_file_test (fullname))
543     return GNUNET_OK;           /* ignore non-files */
544
545   filename = strrchr (fullname, DIR_SEPARATOR);
546   if ((NULL == filename) || (1 > strlen (filename)))
547     filename = fullname;
548   else
549     filename ++;
550
551   read_host_file (fullname, dsc->remove_files, &r);
552   if ( (NULL == r.hello) && (NULL == r.friend_only_hello))
553     return GNUNET_OK;
554   if (NULL != r.friend_only_hello)
555   {
556     if (GNUNET_OK != GNUNET_HELLO_get_id (r.friend_only_hello, &id_friend))
557       if (GNUNET_YES == dsc->remove_files)
558       {
559         remove_garbage (fullname);
560         return GNUNET_OK;
561       }
562     id = id_friend;
563   }
564   if (NULL != r.hello)
565   {
566     if (GNUNET_OK != GNUNET_HELLO_get_id (r.hello, &id_public))
567       if (GNUNET_YES == dsc->remove_files)
568       {
569         remove_garbage (fullname);
570         return GNUNET_OK;
571       }
572     id = id_public;
573   }
574
575   if ( (NULL != r.hello) && (NULL != r.friend_only_hello) &&
576        (0 != memcmp (&id_friend, &id_public, sizeof (id_friend))) )
577   {
578     /* HELLOs are not for the same peer */
579     GNUNET_break (0);
580     if (GNUNET_YES == dsc->remove_files)
581       remove_garbage (fullname);
582     return GNUNET_OK;
583   }
584   if (GNUNET_OK == GNUNET_CRYPTO_eddsa_public_key_from_string (filename,
585                                                                   strlen (filename),
586                                                                   &identity.public_key))
587   {
588     if (0 != memcmp (&id, &identity, sizeof (id_friend)))
589     {
590       /* HELLOs are not for the same peer */
591       GNUNET_break (0);
592       if (GNUNET_YES == dsc->remove_files)
593         remove_garbage (fullname);
594       return GNUNET_OK;
595     }
596   }
597
598   /* ok, found something valid, remember HELLO */
599   add_host_to_known_hosts (&id);
600   if (NULL != r.hello)
601   {
602     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Updating peer `%s' public HELLO \n",
603                 GNUNET_i2s (&id));
604     update_hello (&id, r.hello);
605     GNUNET_free (r.hello);
606   }
607   if (NULL != r.friend_only_hello)
608   {
609     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Updating peer `%s' friend only HELLO \n",
610                 GNUNET_i2s (&id));
611     update_hello (&id, r.friend_only_hello);
612     GNUNET_free (r.friend_only_hello);
613   }
614   dsc->matched++;
615   return GNUNET_OK;
616 }
617
618
619 /**
620  * Call this method periodically to scan data/hosts for new hosts.
621  *
622  * @param cls unused
623  * @param tc scheduler context, aborted if reason is shutdown
624  */
625 static void
626 cron_scan_directory_data_hosts (void *cls,
627                                 const struct GNUNET_SCHEDULER_TaskContext *tc)
628 {
629   static unsigned int retries;
630   struct DirScanContext dsc;
631
632   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
633     return;
634   if (GNUNET_SYSERR == GNUNET_DISK_directory_create (networkIdDirectory))
635   {
636     GNUNET_SCHEDULER_add_delayed_with_priority (DATA_HOST_FREQ,
637                                                 GNUNET_SCHEDULER_PRIORITY_IDLE,
638                                                 &cron_scan_directory_data_hosts, NULL);
639     return;
640   }
641   dsc.matched = 0;
642   dsc.remove_files = GNUNET_YES;
643   GNUNET_log (GNUNET_ERROR_TYPE_INFO | GNUNET_ERROR_TYPE_BULK,
644               _("Scanning directory `%s'\n"), networkIdDirectory);
645   GNUNET_DISK_directory_scan (networkIdDirectory,
646                               &hosts_directory_scan_callback, &dsc);
647   if ((0 == dsc.matched) && (0 == (++retries & 31)))
648     GNUNET_log (GNUNET_ERROR_TYPE_WARNING | GNUNET_ERROR_TYPE_BULK,
649                 _("Still no peers found in `%s'!\n"), networkIdDirectory);
650   GNUNET_SCHEDULER_add_delayed_with_priority (DATA_HOST_FREQ,
651                                               GNUNET_SCHEDULER_PRIORITY_IDLE,
652                                               &cron_scan_directory_data_hosts,
653                                               NULL);
654 }
655
656
657 static struct GNUNET_HELLO_Message *
658 update_friend_hello (const struct GNUNET_HELLO_Message *hello,
659                      const struct GNUNET_HELLO_Message *friend_hello)
660 {
661   struct GNUNET_HELLO_Message * res;
662   struct GNUNET_HELLO_Message * tmp;
663   struct GNUNET_CRYPTO_EddsaPublicKey pk;
664
665   if (NULL != friend_hello)
666   {
667     res = GNUNET_HELLO_merge (hello, friend_hello);
668     GNUNET_assert (GNUNET_YES == GNUNET_HELLO_is_friend_only (res));
669     return res;
670   }
671
672   if (GNUNET_OK !=
673       GNUNET_HELLO_get_key (hello, &pk))
674   {
675     GNUNET_break (0);
676     return NULL;
677   }
678   tmp = GNUNET_HELLO_create (&pk, NULL, NULL, GNUNET_YES);
679   res = GNUNET_HELLO_merge (hello, tmp);
680   GNUNET_free (tmp);
681   GNUNET_assert (GNUNET_YES == GNUNET_HELLO_is_friend_only (res));
682   return res;
683 }
684
685
686 /**
687  * Bind a host address (hello) to a hostId.
688  *
689  * @param peer the peer for which this is a hello
690  * @param hello the verified (!) hello message
691  */
692 static void
693 update_hello (const struct GNUNET_PeerIdentity *peer,
694               const struct GNUNET_HELLO_Message *hello)
695 {
696   char *fn;
697   struct HostEntry *host;
698   struct GNUNET_HELLO_Message *mrg;
699   struct GNUNET_HELLO_Message **dest;
700   struct GNUNET_TIME_Absolute delta;
701   unsigned int cnt;
702   unsigned int size;
703   int friend_hello_type;
704   int store_hello;
705   int store_friend_hello;
706   int pos;
707   char *buffer;
708
709   host = GNUNET_CONTAINER_multipeermap_get (hostmap, peer);
710   GNUNET_assert (NULL != host);
711
712   friend_hello_type = GNUNET_HELLO_is_friend_only (hello);
713         GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Updating %s HELLO for `%s'\n",
714                         (GNUNET_YES == friend_hello_type) ? "friend-only" : "public",
715                         GNUNET_i2s (peer));
716
717   dest = NULL;
718   if (GNUNET_YES == friend_hello_type)
719   {
720     dest = &host->friend_only_hello;
721   }
722   else
723   {
724     dest = &host->hello;
725   }
726
727   if (NULL == (*dest))
728   {
729     (*dest) = GNUNET_malloc (GNUNET_HELLO_size (hello));
730     memcpy ((*dest), hello, GNUNET_HELLO_size (hello));
731   }
732   else
733   {
734     mrg = GNUNET_HELLO_merge ((*dest), hello);
735     delta = GNUNET_HELLO_equals (mrg, (*dest), GNUNET_TIME_absolute_get ());
736     if (delta.abs_value_us == GNUNET_TIME_UNIT_FOREVER_ABS.abs_value_us)
737     {
738       /* no differences, just ignore the update */
739         GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "No change in %s HELLO for `%s'\n",
740                         (GNUNET_YES == friend_hello_type) ? "friend-only" : "public",
741                         GNUNET_i2s (peer));
742       GNUNET_free (mrg);
743       return;
744     }
745     GNUNET_free ((*dest));
746     (*dest) = mrg;
747   }
748
749   if ((NULL != (host->hello)) && (GNUNET_NO == friend_hello_type))
750   {
751     /* Update friend only hello */
752     mrg = update_friend_hello (host->hello, host->friend_only_hello);
753     if (NULL != host->friend_only_hello)
754       GNUNET_free (host->friend_only_hello);
755     host->friend_only_hello = mrg;
756   }
757
758   if (NULL != host->hello)
759     GNUNET_assert ((GNUNET_NO == GNUNET_HELLO_is_friend_only (host->hello)));
760   if (NULL != host->friend_only_hello)
761     GNUNET_assert ((GNUNET_YES == GNUNET_HELLO_is_friend_only(host->friend_only_hello)));
762
763   fn = get_host_filename (peer);
764   if ( (NULL != fn) &&
765        (GNUNET_OK == GNUNET_DISK_directory_create_for_file (fn)) )
766   {
767     store_hello = GNUNET_NO;
768     size = 0;
769     cnt = 0;
770     if (NULL != host->hello)
771       (void) GNUNET_HELLO_iterate_addresses (host->hello,
772                                              GNUNET_NO, &count_addresses, &cnt);
773     if (cnt > 0)
774     {
775       store_hello = GNUNET_YES;
776       size += GNUNET_HELLO_size (host->hello);
777     }
778     cnt = 0;
779     if (NULL != host->friend_only_hello)
780       (void) GNUNET_HELLO_iterate_addresses (host->friend_only_hello, GNUNET_NO,
781                                              &count_addresses, &cnt);
782     store_friend_hello = GNUNET_NO;
783     if (0 < cnt)
784     {
785       store_friend_hello = GNUNET_YES;
786       size += GNUNET_HELLO_size (host->friend_only_hello);
787     }
788
789     if ((GNUNET_NO == store_hello) && (GNUNET_NO == store_friend_hello))
790     {
791       /* no valid addresses, don't put HELLO on disk; in fact,
792          if one exists on disk, remove it */
793       (void) UNLINK (fn);
794     }
795     else
796     {
797       buffer = GNUNET_malloc (size);
798       pos = 0;
799
800       if (GNUNET_YES == store_hello)
801       {
802         memcpy (buffer, host->hello, GNUNET_HELLO_size (host->hello));
803         pos += GNUNET_HELLO_size (host->hello);
804       }
805       if (GNUNET_YES == store_friend_hello)
806       {
807         memcpy (&buffer[pos], host->friend_only_hello, GNUNET_HELLO_size (host->friend_only_hello));
808         pos += GNUNET_HELLO_size (host->friend_only_hello);
809       }
810       GNUNET_assert (pos == size);
811
812       if (GNUNET_SYSERR == GNUNET_DISK_fn_write (fn, buffer, size,
813                                                  GNUNET_DISK_PERM_USER_READ |
814                                                  GNUNET_DISK_PERM_USER_WRITE |
815                                                  GNUNET_DISK_PERM_GROUP_READ |
816                                                  GNUNET_DISK_PERM_OTHER_READ))
817         GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, "write", fn);
818       else
819         GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Stored %s %s HELLO in %s  with total size %u\n",
820                     (GNUNET_YES == store_friend_hello) ? "friend-only": "",
821                     (GNUNET_YES == store_hello) ? "public": "",
822                     fn, size);
823       GNUNET_free (buffer);
824     }
825   }
826   GNUNET_free_non_null (fn);
827   notify_all (host);
828 }
829
830
831 /**
832  * Do transmit info about peer to given host.
833  *
834  * @param cls NULL to hit all hosts, otherwise specifies a particular target
835  * @param key hostID
836  * @param value information to transmit
837  * @return GNUNET_YES (continue to iterate)
838  */
839 static int
840 add_to_tc (void *cls, const struct GNUNET_PeerIdentity *key, void *value)
841 {
842   struct TransmitContext *tc = cls;
843   struct HostEntry *pos = value;
844   struct InfoMessage *im;
845   uint16_t hs;
846   char buf[GNUNET_SERVER_MAX_MESSAGE_SIZE - 1] GNUNET_ALIGN;
847
848   hs = 0;
849   im = (struct InfoMessage *) buf;
850
851   if ((pos->hello != NULL) && (GNUNET_NO == tc->friend_only))
852   {
853         /* Copy public HELLO */
854     hs = GNUNET_HELLO_size (pos->hello);
855     GNUNET_assert (hs < GNUNET_SERVER_MAX_MESSAGE_SIZE -
856                    sizeof (struct InfoMessage));
857     memcpy (&im[1], pos->hello, hs);
858     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
859                 "Sending public HELLO with size %u for peer `%4s'\n",
860                 hs, GNUNET_i2s (key));
861   }
862   else if ((pos->friend_only_hello != NULL) && (GNUNET_YES == tc->friend_only))
863   {
864         /* Copy friend only HELLO */
865     hs = GNUNET_HELLO_size (pos->friend_only_hello);
866     GNUNET_assert (hs < GNUNET_SERVER_MAX_MESSAGE_SIZE -
867                    sizeof (struct InfoMessage));
868     memcpy (&im[1], pos->friend_only_hello, hs);
869     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
870                 "Sending friend-only HELLO with size %u for peer `%4s'\n",
871                 hs, GNUNET_i2s (key));
872   }
873   else
874   {
875       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
876                   "Adding no HELLO for peer `%s'\n",
877                  GNUNET_i2s (key));
878   }
879
880   im->header.type = htons (GNUNET_MESSAGE_TYPE_PEERINFO_INFO);
881   im->header.size = htons (sizeof (struct InfoMessage) + hs);
882   im->reserved = htonl (0);
883   im->peer = pos->identity;
884   GNUNET_SERVER_transmit_context_append_message (tc->tc, &im->header);
885   return GNUNET_YES;
886 }
887
888
889 /**
890  * @brief delete expired HELLO entries in directory
891  *
892  * @param cls pointer to current time (struct GNUNET_TIME_Absolute)
893  * @param fn filename to test to see if the HELLO expired
894  * @return GNUNET_OK (continue iteration)
895  */
896 static int
897 discard_hosts_helper (void *cls, const char *fn)
898 {
899   struct GNUNET_TIME_Absolute *now = cls;
900   char buffer[GNUNET_SERVER_MAX_MESSAGE_SIZE - 1] GNUNET_ALIGN;
901   const struct GNUNET_HELLO_Message *hello;
902   struct GNUNET_HELLO_Message *new_hello;
903   int read_size;
904   unsigned int cur_hello_size;
905   unsigned int new_hello_size;
906   int read_pos;
907   int write_pos;
908   unsigned int cnt;
909   char *writebuffer;
910
911
912   read_size = GNUNET_DISK_fn_read (fn, buffer, sizeof (buffer));
913   if (read_size < sizeof (struct GNUNET_MessageHeader))
914   {
915     if (0 != UNLINK (fn))
916       GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING |
917                                 GNUNET_ERROR_TYPE_BULK, "unlink", fn);
918     return GNUNET_OK;
919   }
920
921   writebuffer = GNUNET_malloc (read_size);
922   read_pos = 0;
923   write_pos = 0;
924   while (read_pos < read_size)
925   {
926     /* Check each HELLO */
927     hello = (const struct GNUNET_HELLO_Message *) &buffer[read_pos];
928     cur_hello_size = GNUNET_HELLO_size (hello);
929     if (0 == cur_hello_size)
930     {
931       /* Invalid data, discard */
932       if (0 != UNLINK (fn))
933         GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING |
934                                   GNUNET_ERROR_TYPE_BULK, "unlink", fn);
935       return GNUNET_OK;
936     }
937     new_hello = GNUNET_HELLO_iterate_addresses (hello, GNUNET_YES, &discard_expired, now);
938     cnt = 0;
939     if (NULL != new_hello)
940       (void) GNUNET_HELLO_iterate_addresses (hello, GNUNET_NO, &count_addresses, &cnt);
941     if ( (NULL != new_hello) && (0 < cnt) )
942     {
943       /* Store new HELLO to write it when done */
944       new_hello_size = GNUNET_HELLO_size (new_hello);
945       memcpy (&writebuffer[write_pos], new_hello, new_hello_size);
946       write_pos += new_hello_size;
947     }
948     read_pos += cur_hello_size;
949     GNUNET_free_non_null (new_hello);
950   }
951
952   if (0 < write_pos)
953   {
954       GNUNET_DISK_fn_write (fn, writebuffer,write_pos,
955                             GNUNET_DISK_PERM_USER_READ |
956                             GNUNET_DISK_PERM_USER_WRITE |
957                             GNUNET_DISK_PERM_GROUP_READ |
958                             GNUNET_DISK_PERM_OTHER_READ);
959   }
960   else if (0 != UNLINK (fn))
961     GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING |
962                               GNUNET_ERROR_TYPE_BULK, "unlink", fn);
963
964   GNUNET_free (writebuffer);
965   return GNUNET_OK;
966 }
967
968
969 /**
970  * Call this method periodically to scan peerinfo/ for ancient
971  * HELLOs to expire.
972  *
973  * @param cls unused
974  * @param tc scheduler context, aborted if reason is shutdown
975  */
976 static void
977 cron_clean_data_hosts (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
978 {
979   struct GNUNET_TIME_Absolute now;
980
981   if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN))
982     return;
983   now = GNUNET_TIME_absolute_get ();
984   GNUNET_log (GNUNET_ERROR_TYPE_INFO | GNUNET_ERROR_TYPE_BULK,
985               _("Cleaning up directory `%s'\n"), networkIdDirectory);
986   GNUNET_DISK_directory_scan (networkIdDirectory, &discard_hosts_helper, &now);
987   GNUNET_SCHEDULER_add_delayed (DATA_HOST_CLEAN_FREQ, &cron_clean_data_hosts,
988                                 NULL);
989 }
990
991
992 /**
993  * Handle HELLO-message.
994  *
995  * @param cls closure
996  * @param client identification of the client
997  * @param message the actual message
998  */
999 static void
1000 handle_hello (void *cls, struct GNUNET_SERVER_Client *client,
1001               const struct GNUNET_MessageHeader *message)
1002 {
1003   const struct GNUNET_HELLO_Message *hello;
1004   struct GNUNET_PeerIdentity pid;
1005
1006   hello = (const struct GNUNET_HELLO_Message *) message;
1007   if (GNUNET_OK != GNUNET_HELLO_get_id (hello, &pid))
1008   {
1009     GNUNET_break (0);
1010     GNUNET_SERVER_receive_done (client, GNUNET_SYSERR);
1011     return;
1012   }
1013   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "`%s' message received for peer `%4s'\n",
1014               "HELLO", GNUNET_i2s (&pid));
1015   add_host_to_known_hosts (&pid);
1016   update_hello (&pid, hello);
1017   GNUNET_SERVER_receive_done (client, GNUNET_OK);
1018 }
1019
1020
1021 /**
1022  * Handle GET-message.
1023  *
1024  * @param cls closure
1025  * @param client identification of the client
1026  * @param message the actual message
1027  */
1028 static void
1029 handle_get (void *cls, struct GNUNET_SERVER_Client *client,
1030             const struct GNUNET_MessageHeader *message)
1031 {
1032   const struct ListPeerMessage *lpm;
1033   struct TransmitContext tcx;
1034
1035   lpm = (const struct ListPeerMessage *) message;
1036   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "`%s' message received for peer `%4s'\n",
1037               "GET", GNUNET_i2s (&lpm->peer));
1038   tcx.friend_only = ntohl (lpm->include_friend_only);
1039   tcx.tc = GNUNET_SERVER_transmit_context_create (client);
1040   GNUNET_CONTAINER_multipeermap_get_multiple (hostmap, &lpm->peer,
1041                                               &add_to_tc, &tcx);
1042   GNUNET_SERVER_transmit_context_append_data (tcx.tc, NULL, 0,
1043                                               GNUNET_MESSAGE_TYPE_PEERINFO_INFO_END);
1044   GNUNET_SERVER_transmit_context_run (tcx.tc, GNUNET_TIME_UNIT_FOREVER_REL);
1045 }
1046
1047
1048 /**
1049  * Handle GET-ALL-message.
1050  *
1051  * @param cls closure
1052  * @param client identification of the client
1053  * @param message the actual message
1054  */
1055 static void
1056 handle_get_all (void *cls, struct GNUNET_SERVER_Client *client,
1057                 const struct GNUNET_MessageHeader *message)
1058 {
1059   const struct ListAllPeersMessage *lapm;
1060   struct TransmitContext tcx;
1061
1062   lapm = (const struct ListAllPeersMessage *) message;
1063   tcx.friend_only = ntohl (lapm->include_friend_only);
1064   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "`%s' message received\n", "GET_ALL");
1065   tcx.tc = GNUNET_SERVER_transmit_context_create (client);
1066   GNUNET_CONTAINER_multipeermap_iterate (hostmap, &add_to_tc, &tcx);
1067   GNUNET_SERVER_transmit_context_append_data (tcx.tc, NULL, 0,
1068                                               GNUNET_MESSAGE_TYPE_PEERINFO_INFO_END);
1069   GNUNET_SERVER_transmit_context_run (tcx.tc, GNUNET_TIME_UNIT_FOREVER_REL);
1070 }
1071
1072
1073
1074 /**
1075  * Pass the given client the information we have in the respective
1076  * host entry; the client is already in the notification context.
1077  *
1078  * @param cls the 'struct GNUNET_SERVER_Client' to notify
1079  * @param key key for the value (unused)
1080  * @param value the 'struct HostEntry' to notify the client about
1081  * @return GNUNET_YES (always, continue to iterate)
1082  */
1083 static int
1084 do_notify_entry (void *cls, const struct GNUNET_PeerIdentity *key, void *value)
1085 {
1086   struct NotificationContext *nc = cls;
1087   struct HostEntry *he = value;
1088   struct InfoMessage *msg;
1089
1090   if ((NULL == he->hello) && (GNUNET_NO == nc->include_friend_only))
1091   {
1092     /* We have no public hello  */
1093     return GNUNET_YES;
1094   }
1095
1096   if ( (NULL == he->friend_only_hello) &&
1097        (GNUNET_YES == nc->include_friend_only) )
1098   {
1099     /* We have no friend hello */
1100     return GNUNET_YES;
1101   }
1102
1103   msg = make_info_message (he, nc->include_friend_only);
1104   GNUNET_SERVER_notification_context_unicast (notify_list,
1105                                               nc->client,
1106                                               &msg->header,
1107                                               GNUNET_NO);
1108   GNUNET_free (msg);
1109   return GNUNET_YES;
1110 }
1111
1112
1113 /**
1114  * Handle NOTIFY-message.
1115  *
1116  * @param cls closure
1117  * @param client identification of the client
1118  * @param message the actual message
1119  */
1120 static void
1121 handle_notify (void *cls, struct GNUNET_SERVER_Client *client,
1122                const struct GNUNET_MessageHeader *message)
1123 {
1124   struct NotifyMessage *nm = (struct NotifyMessage *) message;
1125   struct NotificationContext *nc;
1126
1127   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1128               "`%s' message received\n",
1129               "NOTIFY");
1130   nc = GNUNET_malloc (sizeof (struct NotificationContext));
1131   nc->client = client;
1132   nc->include_friend_only = ntohl (nm->include_friend_only);
1133
1134   GNUNET_CONTAINER_DLL_insert (nc_head, nc_tail, nc);
1135   GNUNET_SERVER_client_mark_monitor (client);
1136         GNUNET_SERVER_notification_context_add (notify_list, client);
1137   GNUNET_CONTAINER_multipeermap_iterate (hostmap, &do_notify_entry, nc);
1138   GNUNET_SERVER_receive_done (client, GNUNET_OK);
1139 }
1140
1141
1142 /**
1143  * Client disconnect callback
1144  *
1145  * @param cls unused
1146  * @param client server client
1147  */
1148 static void
1149 disconnect_cb (void *cls,struct GNUNET_SERVER_Client *client)
1150 {
1151   struct NotificationContext *cur;
1152
1153   for (cur = nc_head; NULL != cur; cur = cur->next)
1154     if (cur->client == client)
1155       break;
1156   if (NULL == cur)
1157     return;
1158   GNUNET_CONTAINER_DLL_remove (nc_head, nc_tail, cur);
1159   GNUNET_free (cur);
1160 }
1161
1162
1163 /**
1164  * Release memory taken by a host entry.
1165  *
1166  * @param cls NULL
1167  * @param key key of the host entry
1168  * @param value the 'struct HostEntry' to free
1169  * @return GNUNET_YES (continue to iterate)
1170  */
1171 static int
1172 free_host_entry (void *cls, const struct GNUNET_PeerIdentity *key, void *value)
1173 {
1174   struct HostEntry *he = value;
1175
1176   GNUNET_free_non_null (he->hello);
1177   GNUNET_free_non_null (he->friend_only_hello);
1178   GNUNET_free (he);
1179   return GNUNET_YES;
1180 }
1181
1182
1183 /**
1184  * Clean up our state.  Called during shutdown.
1185  *
1186  * @param cls unused
1187  * @param tc scheduler task context, unused
1188  */
1189 static void
1190 shutdown_task (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
1191 {
1192   struct NotificationContext *cur;
1193   struct NotificationContext *next;
1194
1195   GNUNET_SERVER_notification_context_destroy (notify_list);
1196   notify_list = NULL;
1197
1198   for (cur = nc_head; NULL != cur; cur = next)
1199   {
1200     next = cur->next;
1201     GNUNET_CONTAINER_DLL_remove (nc_head, nc_tail, cur);
1202     GNUNET_free (cur);
1203   }
1204   GNUNET_CONTAINER_multipeermap_iterate (hostmap, &free_host_entry, NULL);
1205   GNUNET_CONTAINER_multipeermap_destroy (hostmap);
1206   if (NULL != stats)
1207   {
1208     GNUNET_STATISTICS_destroy (stats, GNUNET_NO);
1209     stats = NULL;
1210   }
1211 }
1212
1213
1214 /**
1215  * Start up peerinfo service.
1216  *
1217  * @param cls closure
1218  * @param server the initialized server
1219  * @param cfg configuration to use
1220  */
1221 static void
1222 run (void *cls, struct GNUNET_SERVER_Handle *server,
1223      const struct GNUNET_CONFIGURATION_Handle *cfg)
1224 {
1225   static const struct GNUNET_SERVER_MessageHandler handlers[] = {
1226     {&handle_hello, NULL, GNUNET_MESSAGE_TYPE_HELLO, 0},
1227     {&handle_get, NULL, GNUNET_MESSAGE_TYPE_PEERINFO_GET,
1228      sizeof (struct ListPeerMessage)},
1229     {&handle_get_all, NULL, GNUNET_MESSAGE_TYPE_PEERINFO_GET_ALL,
1230      sizeof (struct ListAllPeersMessage)},
1231     {&handle_notify, NULL, GNUNET_MESSAGE_TYPE_PEERINFO_NOTIFY,
1232      sizeof (struct NotifyMessage)},
1233     {NULL, NULL, 0, 0}
1234   };
1235   char *peerdir;
1236   char *ip;
1237   struct DirScanContext dsc;
1238   int noio;
1239   int use_included;
1240
1241   hostmap = GNUNET_CONTAINER_multipeermap_create (1024, GNUNET_YES);
1242   stats = GNUNET_STATISTICS_create ("peerinfo", cfg);
1243   notify_list = GNUNET_SERVER_notification_context_create (server, 0);
1244   noio = GNUNET_CONFIGURATION_get_value_yesno (cfg, "peerinfo", "NO_IO");
1245   use_included = GNUNET_CONFIGURATION_get_value_yesno (cfg, "peerinfo", "USE_INCLUDED_HELLOS");
1246   GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL, &shutdown_task,
1247                                 NULL);
1248   if (GNUNET_YES != noio)
1249   {
1250     GNUNET_assert (GNUNET_OK ==
1251                    GNUNET_CONFIGURATION_get_value_filename (cfg, "peerinfo",
1252                                                             "HOSTS",
1253                                                             &networkIdDirectory));
1254     if (GNUNET_OK !=
1255         GNUNET_DISK_directory_create (networkIdDirectory))
1256     {
1257       GNUNET_SCHEDULER_shutdown ();
1258       return;
1259     }
1260
1261     GNUNET_SCHEDULER_add_with_priority (GNUNET_SCHEDULER_PRIORITY_IDLE,
1262                                         &cron_scan_directory_data_hosts, NULL);
1263
1264     GNUNET_SCHEDULER_add_with_priority (GNUNET_SCHEDULER_PRIORITY_IDLE,
1265                                         &cron_clean_data_hosts, NULL);
1266     if (GNUNET_YES == use_included)
1267     {
1268         ip = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_DATADIR);
1269         GNUNET_asprintf (&peerdir,
1270                      "%shellos",
1271                      ip);
1272         GNUNET_free (ip);
1273
1274                         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1275                         _("Importing HELLOs from `%s'\n"),
1276                         peerdir);
1277                         dsc.matched = 0;
1278                         dsc.remove_files = GNUNET_NO;
1279
1280                         GNUNET_DISK_directory_scan (peerdir,
1281                                         &hosts_directory_scan_callback, &dsc);
1282
1283                         GNUNET_free (peerdir);
1284     }
1285   }
1286   GNUNET_SERVER_add_handlers (server, handlers);
1287   GNUNET_SERVER_disconnect_notify (server, &disconnect_cb, NULL) ;
1288 }
1289
1290
1291 /**
1292  * The main function for the peerinfo service.
1293  *
1294  * @param argc number of arguments from the command line
1295  * @param argv command line arguments
1296  * @return 0 ok, 1 on error
1297  */
1298 int
1299 main (int argc, char *const *argv)
1300 {
1301   int ret;
1302
1303   ret =
1304       (GNUNET_OK ==
1305        GNUNET_SERVICE_run (argc, argv, "peerinfo", GNUNET_SERVICE_OPTION_NONE,
1306                            &run, NULL)) ? 0 : 1;
1307   GNUNET_free_non_null (networkIdDirectory);
1308   return ret;
1309 }
1310
1311
1312 /* end of gnunet-service-peerinfo.c */