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