fixed clean up and improved logging
[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   tcx.tc = GNUNET_SERVER_transmit_context_create (client);
1075   GNUNET_CONTAINER_multipeermap_iterate (hostmap, &add_to_tc, &tcx);
1076   GNUNET_SERVER_transmit_context_append_data (tcx.tc, NULL, 0,
1077                                               GNUNET_MESSAGE_TYPE_PEERINFO_INFO_END);
1078   GNUNET_SERVER_transmit_context_run (tcx.tc, GNUNET_TIME_UNIT_FOREVER_REL);
1079 }
1080
1081
1082
1083 /**
1084  * Pass the given client the information we have in the respective
1085  * host entry; the client is already in the notification context.
1086  *
1087  * @param cls the `struct GNUNET_SERVER_Client` to notify
1088  * @param key key for the value (unused)
1089  * @param value the `struct HostEntry` to notify the client about
1090  * @return #GNUNET_YES (always, continue to iterate)
1091  */
1092 static int
1093 do_notify_entry (void *cls, const struct GNUNET_PeerIdentity *key, void *value)
1094 {
1095   struct NotificationContext *nc = cls;
1096   struct HostEntry *he = value;
1097   struct InfoMessage *msg;
1098
1099   if ((NULL == he->hello) && (GNUNET_NO == nc->include_friend_only))
1100   {
1101     /* We have no public hello  */
1102     return GNUNET_YES;
1103   }
1104
1105   if ( (NULL == he->friend_only_hello) &&
1106        (GNUNET_YES == nc->include_friend_only) )
1107   {
1108     /* We have no friend hello */
1109     return GNUNET_YES;
1110   }
1111
1112   msg = make_info_message (he, nc->include_friend_only);
1113   GNUNET_SERVER_notification_context_unicast (notify_list,
1114                                               nc->client,
1115                                               &msg->header,
1116                                               GNUNET_NO);
1117   GNUNET_free (msg);
1118   return GNUNET_YES;
1119 }
1120
1121
1122 /**
1123  * Handle NOTIFY-message.
1124  *
1125  * @param cls closure
1126  * @param client identification of the client
1127  * @param message the actual message
1128  */
1129 static void
1130 handle_notify (void *cls, struct GNUNET_SERVER_Client *client,
1131                const struct GNUNET_MessageHeader *message)
1132 {
1133   struct NotifyMessage *nm = (struct NotifyMessage *) message;
1134   struct NotificationContext *nc;
1135
1136   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
1137               "`%s' message received\n",
1138               "NOTIFY");
1139   nc = GNUNET_new (struct NotificationContext);
1140   nc->client = client;
1141   nc->include_friend_only = ntohl (nm->include_friend_only);
1142
1143   GNUNET_CONTAINER_DLL_insert (nc_head, nc_tail, nc);
1144   GNUNET_SERVER_client_mark_monitor (client);
1145         GNUNET_SERVER_notification_context_add (notify_list, client);
1146   GNUNET_CONTAINER_multipeermap_iterate (hostmap, &do_notify_entry, nc);
1147   GNUNET_SERVER_receive_done (client, GNUNET_OK);
1148 }
1149
1150
1151 /**
1152  * Client disconnect callback
1153  *
1154  * @param cls unused
1155  * @param client server client
1156  */
1157 static void
1158 disconnect_cb (void *cls,struct GNUNET_SERVER_Client *client)
1159 {
1160   struct NotificationContext *cur;
1161
1162   for (cur = nc_head; NULL != cur; cur = cur->next)
1163     if (cur->client == client)
1164       break;
1165   if (NULL == cur)
1166     return;
1167   GNUNET_CONTAINER_DLL_remove (nc_head, nc_tail, cur);
1168   GNUNET_free (cur);
1169 }
1170
1171
1172 /**
1173  * Release memory taken by a host entry.
1174  *
1175  * @param cls NULL
1176  * @param key key of the host entry
1177  * @param value the `struct HostEntry` to free
1178  * @return #GNUNET_YES (continue to iterate)
1179  */
1180 static int
1181 free_host_entry (void *cls,
1182                  const struct GNUNET_PeerIdentity *key,
1183                  void *value)
1184 {
1185   struct HostEntry *he = value;
1186
1187   GNUNET_free_non_null (he->hello);
1188   GNUNET_free_non_null (he->friend_only_hello);
1189   GNUNET_free (he);
1190   return GNUNET_YES;
1191 }
1192
1193
1194 /**
1195  * Clean up our state.  Called during shutdown.
1196  *
1197  * @param cls unused
1198  * @param tc scheduler task context, unused
1199  */
1200 static void
1201 shutdown_task (void *cls,
1202                const struct GNUNET_SCHEDULER_TaskContext *tc)
1203 {
1204   struct NotificationContext *cur;
1205   struct NotificationContext *next;
1206
1207   GNUNET_SERVER_notification_context_destroy (notify_list);
1208   notify_list = NULL;
1209
1210   for (cur = nc_head; NULL != cur; cur = next)
1211   {
1212     next = cur->next;
1213     GNUNET_CONTAINER_DLL_remove (nc_head, nc_tail, cur);
1214     GNUNET_free (cur);
1215   }
1216   GNUNET_CONTAINER_multipeermap_iterate (hostmap, &free_host_entry, NULL);
1217   GNUNET_CONTAINER_multipeermap_destroy (hostmap);
1218   if (NULL != stats)
1219   {
1220     GNUNET_STATISTICS_destroy (stats, GNUNET_NO);
1221     stats = NULL;
1222   }
1223 }
1224
1225
1226 /**
1227  * Start up peerinfo service.
1228  *
1229  * @param cls closure
1230  * @param server the initialized server
1231  * @param cfg configuration to use
1232  */
1233 static void
1234 run (void *cls, struct GNUNET_SERVER_Handle *server,
1235      const struct GNUNET_CONFIGURATION_Handle *cfg)
1236 {
1237   static const struct GNUNET_SERVER_MessageHandler handlers[] = {
1238     {&handle_hello, NULL, GNUNET_MESSAGE_TYPE_HELLO, 0},
1239     {&handle_get, NULL, GNUNET_MESSAGE_TYPE_PEERINFO_GET,
1240      sizeof (struct ListPeerMessage)},
1241     {&handle_get_all, NULL, GNUNET_MESSAGE_TYPE_PEERINFO_GET_ALL,
1242      sizeof (struct ListAllPeersMessage)},
1243     {&handle_notify, NULL, GNUNET_MESSAGE_TYPE_PEERINFO_NOTIFY,
1244      sizeof (struct NotifyMessage)},
1245     {NULL, NULL, 0, 0}
1246   };
1247   char *peerdir;
1248   char *ip;
1249   struct DirScanContext dsc;
1250   int noio;
1251   int use_included;
1252
1253   hostmap = GNUNET_CONTAINER_multipeermap_create (1024, GNUNET_YES);
1254   stats = GNUNET_STATISTICS_create ("peerinfo", cfg);
1255   notify_list = GNUNET_SERVER_notification_context_create (server, 0);
1256   noio = GNUNET_CONFIGURATION_get_value_yesno (cfg, "peerinfo", "NO_IO");
1257   use_included = GNUNET_CONFIGURATION_get_value_yesno (cfg, "peerinfo", "USE_INCLUDED_HELLOS");
1258   if (GNUNET_SYSERR == use_included)
1259     use_included = GNUNET_NO;
1260   GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL, &shutdown_task,
1261                                 NULL);
1262   if (GNUNET_YES != noio)
1263   {
1264     GNUNET_assert (GNUNET_OK ==
1265                    GNUNET_CONFIGURATION_get_value_filename (cfg, "peerinfo",
1266                                                             "HOSTS",
1267                                                             &networkIdDirectory));
1268     if (GNUNET_OK !=
1269         GNUNET_DISK_directory_create (networkIdDirectory))
1270     {
1271       GNUNET_SCHEDULER_shutdown ();
1272       return;
1273     }
1274
1275     GNUNET_SCHEDULER_add_with_priority (GNUNET_SCHEDULER_PRIORITY_IDLE,
1276                                         &cron_scan_directory_data_hosts, NULL);
1277
1278     GNUNET_SCHEDULER_add_with_priority (GNUNET_SCHEDULER_PRIORITY_IDLE,
1279                                         &cron_clean_data_hosts, NULL);
1280     if (GNUNET_YES == use_included)
1281     {
1282       ip = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_DATADIR);
1283       GNUNET_asprintf (&peerdir, "%shellos", ip);
1284       GNUNET_free(ip);
1285
1286       GNUNET_log(GNUNET_ERROR_TYPE_INFO, _("Importing HELLOs from `%s'\n"),
1287           peerdir);
1288       dsc.matched = 0;
1289       dsc.remove_files = GNUNET_NO;
1290
1291       GNUNET_DISK_directory_scan (peerdir, &hosts_directory_scan_callback,
1292           &dsc);
1293       GNUNET_free (peerdir);
1294     }
1295     else
1296     {
1297       GNUNET_log(GNUNET_ERROR_TYPE_INFO, _("Skipping import of included HELLOs\n"));
1298     }
1299   }
1300   GNUNET_SERVER_add_handlers (server, handlers);
1301   GNUNET_SERVER_disconnect_notify (server, &disconnect_cb, NULL) ;
1302 }
1303
1304
1305 /**
1306  * The main function for the peerinfo service.
1307  *
1308  * @param argc number of arguments from the command line
1309  * @param argv command line arguments
1310  * @return 0 ok, 1 on error
1311  */
1312 int
1313 main (int argc, char *const *argv)
1314 {
1315   int ret;
1316
1317   ret =
1318       (GNUNET_OK ==
1319        GNUNET_SERVICE_run (argc, argv, "peerinfo", GNUNET_SERVICE_OPTION_NONE,
1320                            &run, NULL)) ? 0 : 1;
1321   GNUNET_free_non_null (networkIdDirectory);
1322   return ret;
1323 }
1324
1325
1326 /* end of gnunet-service-peerinfo.c */