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