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