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