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