-code cleanup
[oweals/gnunet.git] / src / transport / gnunet-service-transport_blacklist.c
1 /*
2      This file is part of GNUnet.
3      (C) 2010,2011 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 transport/gnunet-service-transport_blacklist.c
23  * @brief blacklisting implementation
24  * @author Christian Grothoff
25  */
26 #include "platform.h"
27 #include "gnunet-service-transport.h"
28 #include "gnunet-service-transport_blacklist.h"
29 #include "gnunet-service-transport_neighbours.h"
30 #include "transport.h"
31
32
33 /**
34  * Size of the blacklist hash map.
35  */
36 #define TRANSPORT_BLACKLIST_HT_SIZE 64
37
38
39 /**
40  * Context we use when performing a blacklist check.
41  */
42 struct GST_BlacklistCheck;
43
44
45 /**
46  * Information kept for each client registered to perform
47  * blacklisting.
48  */
49 struct Blacklisters
50 {
51   /**
52    * This is a linked list.
53    */
54   struct Blacklisters *next;
55
56   /**
57    * This is a linked list.
58    */
59   struct Blacklisters *prev;
60
61   /**
62    * Client responsible for this entry.
63    */
64   struct GNUNET_SERVER_Client *client;
65
66   /**
67    * Blacklist check that we're currently performing (or NULL
68    * if we're performing one that has been cancelled).
69    */
70   struct GST_BlacklistCheck *bc;
71
72   /**
73    * Set to GNUNET_YES if we're currently waiting for a reply.
74    */
75   int waiting_for_reply;
76
77 };
78
79
80
81 /**
82  * Context we use when performing a blacklist check.
83  */
84 struct GST_BlacklistCheck
85 {
86
87   /**
88    * This is a linked list.
89    */
90   struct GST_BlacklistCheck *next;
91
92   /**
93    * This is a linked list.
94    */
95   struct GST_BlacklistCheck *prev;
96
97   /**
98    * Peer being checked.
99    */
100   struct GNUNET_PeerIdentity peer;
101
102   /**
103    * Continuation to call with the result.
104    */
105   GST_BlacklistTestContinuation cont;
106
107   /**
108    * Closure for cont.
109    */
110   void *cont_cls;
111
112   /**
113    * Current transmission request handle for this client, or NULL if no
114    * request is pending.
115    */
116   struct GNUNET_SERVER_TransmitHandle *th;
117
118   /**
119    * Our current position in the blacklisters list.
120    */
121   struct Blacklisters *bl_pos;
122
123   /**
124    * Current task performing the check.
125    */
126   GNUNET_SCHEDULER_TaskIdentifier task;
127
128 };
129
130
131 /**
132  * Head of DLL of active blacklisting queries.
133  */
134 static struct GST_BlacklistCheck *bc_head;
135
136 /**
137  * Tail of DLL of active blacklisting queries.
138  */
139 static struct GST_BlacklistCheck *bc_tail;
140
141 /**
142  * Head of DLL of blacklisting clients.
143  */
144 static struct Blacklisters *bl_head;
145
146 /**
147  * Tail of DLL of blacklisting clients.
148  */
149 static struct Blacklisters *bl_tail;
150
151 /**
152  * Hashmap of blacklisted peers.  Values are of type 'char *' (transport names),
153  * can be NULL if we have no static blacklist.
154  */
155 static struct GNUNET_CONTAINER_MultiHashMap *blacklist;
156
157
158 /**
159  * Perform next action in the blacklist check.
160  *
161  * @param cls the 'struct BlacklistCheck*'
162  * @param tc unused
163  */
164 static void
165 do_blacklist_check (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc);
166
167
168 /**
169  * Called whenever a client is disconnected.  Frees our
170  * resources associated with that client.
171  *
172  * @param cls closure (unused)
173  * @param client identification of the client
174  */
175 static void
176 client_disconnect_notification (void *cls, struct GNUNET_SERVER_Client *client)
177 {
178   struct Blacklisters *bl;
179   struct GST_BlacklistCheck *bc;
180
181   if (client == NULL)
182     return;
183   for (bl = bl_head; bl != NULL; bl = bl->next)
184   {
185     if (bl->client != client)
186       continue;
187     for (bc = bc_head; bc != NULL; bc = bc->next)
188     {
189       if (bc->bl_pos != bl)
190         continue;
191       bc->bl_pos = bl->next;
192       if (bc->th != NULL)
193       {
194         GNUNET_SERVER_notify_transmit_ready_cancel (bc->th);
195         bc->th = NULL;
196       }
197       if (bc->task == GNUNET_SCHEDULER_NO_TASK)
198         bc->task = GNUNET_SCHEDULER_add_now (&do_blacklist_check, bc);
199       break;
200     }
201     GNUNET_CONTAINER_DLL_remove (bl_head, bl_tail, bl);
202     GNUNET_SERVER_client_drop (bl->client);
203     GNUNET_free (bl);
204     break;
205   }
206 }
207
208
209 /**
210  * Function to iterate over options in the blacklisting section for a peer.
211  *
212  * @param cls closure
213  * @param section name of the section
214  * @param option name of the option
215  * @param value value of the option
216  */
217 static void 
218 blacklist_cfg_iter (void *cls, const char *section,
219                     const char *option,
220                     const char *value)
221 {
222   unsigned int *res = cls;
223   struct GNUNET_PeerIdentity peer;
224   char *plugs;
225   char *pos;
226
227   if (GNUNET_OK != GNUNET_CRYPTO_hash_from_string2 (option,
228                                                     strlen (option), 
229                                                     &peer.hashPubKey))
230     return;
231   
232   if ((NULL == value) || (0 == strcmp(value, "")))
233   {
234     /* Blacklist whole peer */
235     GST_blacklist_add_peer (&peer, NULL);
236     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
237                 _("Adding blacklisting entry for peer `%s'\n"), GNUNET_i2s (&peer));
238   }
239   else
240   {
241     plugs = GNUNET_strdup (value);
242     for (pos = strtok (plugs, " "); pos != NULL; pos = strtok (NULL, " "))
243       {
244         GNUNET_log (GNUNET_ERROR_TYPE_INFO,
245                     _("Adding blacklisting entry for peer `%s':`%s'\n"),
246                     GNUNET_i2s (&peer), pos);
247         GST_blacklist_add_peer (&peer, pos);
248       }
249     GNUNET_free (plugs);
250   }
251   (*res)++;
252 }
253
254
255 /**
256  * Read blacklist configuration
257  *
258  * @param cfg the configuration handle
259  * @param my_id my peer identity
260  */
261 static void
262 read_blacklist_configuration (const struct GNUNET_CONFIGURATION_Handle *cfg,
263                               const struct GNUNET_PeerIdentity *my_id)
264 {
265   char cfg_sect[512];
266   unsigned int res = 0;
267
268   GNUNET_snprintf (cfg_sect, 
269                    sizeof (cfg_sect),
270                    "transport-blacklist-%s", 
271                    GNUNET_i2s_full (my_id));
272   GNUNET_CONFIGURATION_iterate_section_values (cfg, cfg_sect, &blacklist_cfg_iter, &res);
273   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
274               "Loaded %u blacklisting entries from configuration\n", res);
275 }
276
277
278 /**
279  * Start blacklist subsystem.
280  *
281  * @param server server used to accept clients from
282  * @param cfg configuration handle
283  * @param my_id my peer id
284  */
285 void
286 GST_blacklist_start (struct GNUNET_SERVER_Handle *server,
287                      const struct GNUNET_CONFIGURATION_Handle *cfg,
288                      const struct GNUNET_PeerIdentity *my_id)
289 {
290   GNUNET_assert (NULL != cfg);
291   GNUNET_assert (NULL != my_id);
292   read_blacklist_configuration (cfg, my_id);
293   GNUNET_SERVER_disconnect_notify (server, &client_disconnect_notification,
294                                    NULL);
295 }
296
297
298 /**
299  * Free the given entry in the blacklist.
300  *
301  * @param cls unused
302  * @param key host identity (unused)
303  * @param value the blacklist entry
304  * @return GNUNET_OK (continue to iterate)
305  */
306 static int
307 free_blacklist_entry (void *cls, const struct GNUNET_HashCode * key, void *value)
308 {
309   char *be = value;
310
311   GNUNET_free_non_null (be);
312   return GNUNET_OK;
313 }
314
315
316 /**
317  * Stop blacklist subsystem.
318  */
319 void
320 GST_blacklist_stop ()
321 {
322   if (NULL != blacklist)
323   {
324     GNUNET_CONTAINER_multihashmap_iterate (blacklist, &free_blacklist_entry,
325                                            NULL);
326     GNUNET_CONTAINER_multihashmap_destroy (blacklist);
327     blacklist = NULL;
328   }
329 }
330
331
332 /**
333  * Transmit blacklist query to the client.
334  *
335  * @param cls the 'struct GST_BlacklistCheck'
336  * @param size number of bytes allowed
337  * @param buf where to copy the message
338  * @return number of bytes copied to buf
339  */
340 static size_t
341 transmit_blacklist_message (void *cls, size_t size, void *buf)
342 {
343   struct GST_BlacklistCheck *bc = cls;
344   struct Blacklisters *bl;
345   struct BlacklistMessage bm;
346
347   bc->th = NULL;
348   if (size == 0)
349   {
350     GNUNET_assert (bc->task == GNUNET_SCHEDULER_NO_TASK);
351     bc->task = GNUNET_SCHEDULER_add_now (&do_blacklist_check, bc);
352     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
353                 "Failed to send blacklist test for peer `%s' to client\n",
354                 GNUNET_i2s (&bc->peer));
355     return 0;
356   }
357   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
358               "Sending blacklist test for peer `%s' to client\n",
359               GNUNET_i2s (&bc->peer));
360   bl = bc->bl_pos;
361   bm.header.size = htons (sizeof (struct BlacklistMessage));
362   bm.header.type = htons (GNUNET_MESSAGE_TYPE_TRANSPORT_BLACKLIST_QUERY);
363   bm.is_allowed = htonl (0);
364   bm.peer = bc->peer;
365   memcpy (buf, &bm, sizeof (bm));
366   GNUNET_SERVER_receive_done (bl->client, GNUNET_OK);
367   bl->waiting_for_reply = GNUNET_YES;
368   return sizeof (bm);
369 }
370
371
372 /**
373  * Perform next action in the blacklist check.
374  *
375  * @param cls the 'struct GST_BlacklistCheck*'
376  * @param tc unused
377  */
378 static void
379 do_blacklist_check (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
380 {
381   struct GST_BlacklistCheck *bc = cls;
382   struct Blacklisters *bl;
383
384   bc->task = GNUNET_SCHEDULER_NO_TASK;
385   bl = bc->bl_pos;
386   if (bl == NULL)
387   {
388     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
389                 "No other blacklist clients active, will allow neighbour `%s'\n",
390                 GNUNET_i2s (&bc->peer));
391     bc->cont (bc->cont_cls, &bc->peer, GNUNET_OK);
392     GNUNET_CONTAINER_DLL_remove(bc_head, bc_tail, bc);
393     GNUNET_free (bc);
394     return;
395   }
396   if ((bl->bc != NULL) || (bl->waiting_for_reply != GNUNET_NO))
397     return;                     /* someone else busy with this client */
398   bl->bc = bc;
399   bc->th =
400       GNUNET_SERVER_notify_transmit_ready (bl->client,
401                                            sizeof (struct BlacklistMessage),
402                                            GNUNET_TIME_UNIT_FOREVER_REL,
403                                            &transmit_blacklist_message, bc);
404 }
405
406
407 /**
408  * Got the result about an existing connection from a new blacklister.
409  * Shutdown the neighbour if necessary.
410  *
411  * @param cls unused
412  * @param peer the neighbour that was investigated
413  * @param allowed GNUNET_OK if we can keep it,
414  *                GNUNET_NO if we must shutdown the connection
415  */
416 static void
417 confirm_or_drop_neighbour (void *cls, const struct GNUNET_PeerIdentity *peer,
418                            int allowed)
419 {
420   if (GNUNET_OK == allowed)
421     return;                     /* we're done */
422   GNUNET_STATISTICS_update (GST_stats,
423                             gettext_noop ("# disconnects due to blacklist"), 1,
424                             GNUNET_NO);
425   GST_neighbours_force_disconnect (peer);
426 }
427
428
429 /**
430  * Closure for 'test_connection_ok'.
431  */
432 struct TestConnectionContext
433 {
434   /**
435    * Is this the first neighbour we're checking?
436    */
437   int first;
438
439   /**
440    * Handle to the blacklisting client we need to ask.
441    */
442   struct Blacklisters *bl;
443 };
444
445
446 /**
447  * Test if an existing connection is still acceptable given a new
448  * blacklisting client.
449  *
450  * @param cls the 'struct TestConnectionContest'
451  * @param neighbour neighbour's identity
452  * @param address the address
453  * @param bandwidth_in inbound quota in NBO
454  * @param bandwidth_out outbound quota in NBO
455  */
456 static void
457 test_connection_ok (void *cls, const struct GNUNET_PeerIdentity *neighbour,
458                     const struct GNUNET_HELLO_Address *address,
459                     struct GNUNET_BANDWIDTH_Value32NBO bandwidth_in,
460                     struct GNUNET_BANDWIDTH_Value32NBO bandwidth_out)
461 {
462   struct TestConnectionContext *tcc = cls;
463   struct GST_BlacklistCheck *bc;
464
465   bc = GNUNET_malloc (sizeof (struct GST_BlacklistCheck));
466   GNUNET_CONTAINER_DLL_insert (bc_head, bc_tail, bc);
467   bc->peer = *neighbour;
468   bc->cont = &confirm_or_drop_neighbour;
469   bc->cont_cls = NULL;
470   bc->bl_pos = tcc->bl;
471   if (GNUNET_YES == tcc->first)
472   {
473     /* all would wait for the same client, no need to
474      * create more than just the first task right now */
475     bc->task = GNUNET_SCHEDULER_add_now (&do_blacklist_check, bc);
476     tcc->first = GNUNET_NO;
477   }
478 }
479
480
481 /**
482  * Initialize a blacklisting client.  We got a blacklist-init
483  * message from this client, add him to the list of clients
484  * to query for blacklisting.
485  *
486  * @param cls unused
487  * @param client the client
488  * @param message the blacklist-init message that was sent
489  */
490 void
491 GST_blacklist_handle_init (void *cls, struct GNUNET_SERVER_Client *client,
492                            const struct GNUNET_MessageHeader *message)
493 {
494   struct Blacklisters *bl;
495   struct TestConnectionContext tcc;
496
497   bl = bl_head;
498   while (bl != NULL)
499   {
500     if (bl->client == client)
501     {
502       GNUNET_break (0);
503       GNUNET_SERVER_receive_done (client, GNUNET_SYSERR);
504       return;
505     }
506     bl = bl->next;
507   }
508   GNUNET_SERVER_client_mark_monitor (client);
509   bl = GNUNET_malloc (sizeof (struct Blacklisters));
510   bl->client = client;
511   GNUNET_SERVER_client_keep (client);
512   GNUNET_CONTAINER_DLL_insert_after (bl_head, bl_tail, bl_tail, bl);
513
514   /* confirm that all existing connections are OK! */
515   tcc.bl = bl;
516   tcc.first = GNUNET_YES;
517   GST_neighbours_iterate (&test_connection_ok, &tcc);
518 }
519
520
521 /**
522  * A blacklisting client has sent us reply. Process it.
523  *
524  * @param cls unused
525  * @param client the client
526  * @param message the blacklist-init message that was sent
527  */
528 void
529 GST_blacklist_handle_reply (void *cls, struct GNUNET_SERVER_Client *client,
530                             const struct GNUNET_MessageHeader *message)
531 {
532   const struct BlacklistMessage *msg =
533       (const struct BlacklistMessage *) message;
534   struct Blacklisters *bl;
535   struct GST_BlacklistCheck *bc;
536
537   bl = bl_head;
538   while ((bl != NULL) && (bl->client != client))
539     bl = bl->next;
540   if (bl == NULL)
541   {
542     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Blacklist client disconnected\n");
543     /* FIXME: other error handling here!? */
544     GNUNET_SERVER_receive_done (client, GNUNET_SYSERR);
545     return;
546   }
547   bc = bl->bc;
548   bl->bc = NULL;
549   bl->waiting_for_reply = GNUNET_NO;
550   if (NULL != bc)
551   {
552     /* only run this if the blacklist check has not been
553      * cancelled in the meantime... */
554     if (ntohl (msg->is_allowed) == GNUNET_SYSERR)
555     {
556       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
557                   "Blacklist check failed, peer not allowed\n");
558       bc->cont (bc->cont_cls, &bc->peer, GNUNET_NO);
559       GNUNET_CONTAINER_DLL_remove (bc_head, bc_tail, bc);
560       GNUNET_free (bc);
561     }
562     else
563     {
564       GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
565                   "Blacklist check succeeded, continuing with checks\n");
566       bc->bl_pos = bc->bl_pos->next;
567       bc->task = GNUNET_SCHEDULER_add_now (&do_blacklist_check, bc);
568     }
569   }
570   /* check if any other bc's are waiting for this blacklister */
571   bc = bc_head;
572   for (bc = bc_head; bc != NULL; bc = bc->next)
573     if ((bc->bl_pos == bl) && (GNUNET_SCHEDULER_NO_TASK == bc->task))
574     {
575       bc->task = GNUNET_SCHEDULER_add_now (&do_blacklist_check, bc);
576       break;
577     }
578 }
579
580
581 /**
582  * Add the given peer to the blacklist (for the given transport).
583  *
584  * @param peer peer to blacklist
585  * @param transport_name transport to blacklist for this peer, NULL for all
586  */
587 void
588 GST_blacklist_add_peer (const struct GNUNET_PeerIdentity *peer,
589                         const char *transport_name)
590 {
591         char * transport = NULL;
592
593   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
594               "Adding peer `%s' with plugin `%s' to blacklist\n",
595               GNUNET_i2s (peer), transport_name);
596   if (blacklist == NULL)
597     blacklist =
598       GNUNET_CONTAINER_multihashmap_create (TRANSPORT_BLACKLIST_HT_SIZE,
599                                             GNUNET_NO);
600   if (NULL != transport_name)
601         transport = GNUNET_strdup ("");
602
603   GNUNET_CONTAINER_multihashmap_put (blacklist, &peer->hashPubKey,
604                                      transport,
605                                      GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
606 }
607
608
609 /**
610  * Test if the given blacklist entry matches.  If so,
611  * abort the iteration.
612  *
613  * @param cls the transport name to match (const char*)
614  * @param key the key (unused)
615  * @param value the 'char *' (name of a blacklisted transport)
616  * @return GNUNET_OK if the entry does not match, GNUNET_NO if it matches
617  */
618 static int
619 test_blacklisted (void *cls, const struct GNUNET_HashCode * key, void *value)
620 {
621   const char *transport_name = cls;
622   char *be = value;
623
624   /* blacklist check for specific no specific transport*/
625   if (transport_name == NULL)
626     return GNUNET_NO;
627   /* all plugins for this peer were blacklisted */
628   if (NULL == value)
629         return GNUNET_NO;
630
631   /* blacklist check for specific transport */
632   if (0 == strcmp (transport_name, be))
633     return GNUNET_NO;           /* abort iteration! */
634   return GNUNET_OK;
635 }
636
637
638 /**
639  * Test if a peer/transport combination is blacklisted.
640  *
641  * @param peer the identity of the peer to test
642  * @param transport_name name of the transport to test, never NULL
643  * @param cont function to call with result
644  * @param cont_cls closure for 'cont'
645  * @return handle to the blacklist check, NULL if the decision
646  *        was made instantly and 'cont' was already called
647  */
648 struct GST_BlacklistCheck *
649 GST_blacklist_test_allowed (const struct GNUNET_PeerIdentity *peer,
650                             const char *transport_name,
651                             GST_BlacklistTestContinuation cont, void *cont_cls)
652 {
653   struct GST_BlacklistCheck *bc;
654
655   GNUNET_assert (peer != NULL);
656
657   if ((blacklist != NULL) &&
658       (GNUNET_SYSERR ==
659        GNUNET_CONTAINER_multihashmap_get_multiple (blacklist, &peer->hashPubKey,
660                                                    &test_blacklisted,
661                                                    (void *) transport_name)))
662   {
663     /* disallowed by config, disapprove instantly */
664     GNUNET_STATISTICS_update (GST_stats,
665                               gettext_noop ("# disconnects due to blacklist"),
666                               1, GNUNET_NO);
667     if (cont != NULL)
668       cont (cont_cls, peer, GNUNET_NO);
669     return NULL;
670   }
671
672   if (bl_head == NULL)
673   {
674     /* no blacklist clients, approve instantly */
675     if (cont != NULL)
676       cont (cont_cls, peer, GNUNET_OK);
677     return NULL;
678   }
679
680   /* need to query blacklist clients */
681   bc = GNUNET_malloc (sizeof (struct GST_BlacklistCheck));
682   GNUNET_CONTAINER_DLL_insert (bc_head, bc_tail, bc);
683   bc->peer = *peer;
684   bc->cont = cont;
685   bc->cont_cls = cont_cls;
686   bc->bl_pos = bl_head;
687   bc->task = GNUNET_SCHEDULER_add_now (&do_blacklist_check, bc);
688   return bc;
689 }
690
691
692 /**
693  * Cancel a blacklist check.
694  *
695  * @param bc check to cancel
696  */
697 void
698 GST_blacklist_test_cancel (struct GST_BlacklistCheck *bc)
699 {
700   GNUNET_CONTAINER_DLL_remove (bc_head, bc_tail, bc);
701   if (bc->bl_pos != NULL)
702   {
703     if (bc->bl_pos->bc == bc)
704     {
705       /* we're at the head of the queue, remove us! */
706       bc->bl_pos->bc = NULL;
707     }
708   }
709   if (GNUNET_SCHEDULER_NO_TASK != bc->task)
710   {
711     GNUNET_SCHEDULER_cancel (bc->task);
712     bc->task = GNUNET_SCHEDULER_NO_TASK;
713   }
714   if (NULL != bc->th)
715   {
716     GNUNET_SERVER_notify_transmit_ready_cancel (bc->th);
717     bc->th = NULL;
718   }
719   GNUNET_free (bc);
720 }
721
722
723 /* end of file gnunet-service-transport_blacklist.c */