indentation
[oweals/gnunet.git] / src / transport / gnunet-service-transport_blacklist.c
index 10eda72b0b1d1effdd9e22d8b6a437b9df2b09f2..7720e467fac2401f7a448418316dc3a6a4e26668 100644 (file)
@@ -1,10 +1,10 @@
 /*
      This file is part of GNUnet.
-     (C) 2010 Christian Grothoff (and other contributing authors)
+     (C) 2010,2011 Christian Grothoff (and other contributing authors)
 
      GNUnet is free software; you can redistribute it and/or modify
      it under the terms of the GNU General Public License as published
-     by the Free Software Foundation; either version 2, or (at your
+     by the Free Software Foundation; either version 3, or (at your
      option) any later version.
 
      GNUnet is distributed in the hope that it will be useful, but
 
 /**
  * @file transport/gnunet-service-transport_blacklist.c
- * @brief low-level P2P messaging
+ * @brief blacklisting implementation
  * @author Christian Grothoff
  */
 #include "platform.h"
-#include "gnunet_protocols.h"
-#include "gnunet_util_lib.h"
-#include "gnunet_service_lib.h"
-#include "transport.h"
+#include "gnunet-service-transport.h"
 #include "gnunet-service-transport_blacklist.h"
+#include "gnunet-service-transport_neighbours.h"
+#include "transport.h"
+
+
+/**
+ * Size of the blacklist hash map.
+ */
+#define TRANSPORT_BLACKLIST_HT_SIZE 64
+
+
+/**
+ * Context we use when performing a blacklist check.
+ */
+struct GST_BlacklistCheck;
+
+
+/**
+ * Information kept for each client registered to perform
+ * blacklisting.
+ */
+struct Blacklisters
+{
+  /**
+   * This is a linked list.
+   */
+  struct Blacklisters *next;
+
+  /**
+   * This is a linked list.
+   */
+  struct Blacklisters *prev;
+
+  /**
+   * Client responsible for this entry.
+   */
+  struct GNUNET_SERVER_Client *client;
+
+  /**
+   * Blacklist check that we're currently performing (or NULL
+   * if we're performing one that has been cancelled).
+   */
+  struct GST_BlacklistCheck *bc;
+
+  /**
+   * Set to GNUNET_YES if we're currently waiting for a reply.
+   */
+  int waiting_for_reply;
+
+};
+
+
+
+/**
+ * Context we use when performing a blacklist check.
+ */
+struct GST_BlacklistCheck
+{
+
+  /**
+   * This is a linked list.
+   */
+  struct GST_BlacklistCheck *next;
+
+  /**
+   * This is a linked list.
+   */
+  struct GST_BlacklistCheck *prev;
+
+  /**
+   * Peer being checked.
+   */
+  struct GNUNET_PeerIdentity peer;
+
+  /**
+   * Continuation to call with the result.
+   */
+  GST_BlacklistTestContinuation cont;
+
+  /**
+   * Closure for cont.
+   */
+  void *cont_cls;
+
+  /**
+   * Current transmission request handle for this client, or NULL if no
+   * request is pending.
+   */
+  struct GNUNET_CONNECTION_TransmitHandle *th;
+
+  /**
+   * Our current position in the blacklisters list.
+   */
+  struct Blacklisters *bl_pos;
+
+  /**
+   * Current task performing the check.
+   */
+  GNUNET_SCHEDULER_TaskIdentifier task;
+
+};
+
+
+/**
+ * Head of DLL of active blacklisting queries.
+ */
+static struct GST_BlacklistCheck *bc_head;
+
+/**
+ * Tail of DLL of active blacklisting queries.
+ */
+static struct GST_BlacklistCheck *bc_tail;
+
+/**
+ * Head of DLL of blacklisting clients.
+ */
+static struct Blacklisters *bl_head;
+
+/**
+ * Tail of DLL of blacklisting clients.
+ */
+static struct Blacklisters *bl_tail;
+
+/**
+ * Hashmap of blacklisted peers.  Values are of type 'char *' (transport names),
+ * can be NULL if we have no static blacklist.
+ */
+static struct GNUNET_CONTAINER_MultiHashMap *blacklist;
+
+
+/**
+ * Perform next action in the blacklist check.
+ *
+ * @param cls the 'struct BlacklistCheck*'
+ * @param tc unused
+ */
+static void
+do_blacklist_check (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc);
+
 
 /**
- * Handle a request to start a blacklist.
+ * Called whenever a client is disconnected.  Frees our
+ * resources associated with that client.
  *
- * @param cls closure (always NULL)
+ * @param cls closure (unused)
  * @param client identification of the client
- * @param message the actual message
+ */
+static void
+client_disconnect_notification (void *cls, struct GNUNET_SERVER_Client *client)
+{
+  struct Blacklisters *bl;
+  struct GST_BlacklistCheck *bc;
+
+  if (client == NULL)
+    return;
+  for (bl = bl_head; bl != NULL; bl = bl->next)
+  {
+    if (bl->client != client)
+      continue;
+    for (bc = bc_head; bc != NULL; bc = bc->next)
+    {
+      if (bc->bl_pos != bl)
+        continue;
+      bc->bl_pos = bl->next;
+      if (bc->th != NULL)
+      {
+        GNUNET_CONNECTION_notify_transmit_ready_cancel (bc->th);
+        bc->th = NULL;
+      }
+      if (bc->task == GNUNET_SCHEDULER_NO_TASK)
+        bc->task = GNUNET_SCHEDULER_add_now (&do_blacklist_check, bc);
+      break;
+    }
+    GNUNET_CONTAINER_DLL_remove (bl_head, bl_tail, bl);
+    GNUNET_SERVER_client_drop (bl->client);
+    GNUNET_free (bl);
+    break;
+  }
+}
+
+
+/**
+ * Read the blacklist file, containing transport:peer entries.
+ * Provided the transport is loaded, set up hashmap with these
+ * entries to blacklist peers by transport.
+ *
+ */
+static void
+read_blacklist_file ()
+{
+  char *fn;
+  char *data;
+  size_t pos;
+  size_t colon_pos;
+  int tsize;
+  struct GNUNET_PeerIdentity pid;
+  struct stat frstat;
+  struct GNUNET_CRYPTO_HashAsciiEncoded enc;
+  unsigned int entries_found;
+  char *transport_name;
+
+  if (GNUNET_OK !=
+      GNUNET_CONFIGURATION_get_value_filename (GST_cfg,
+                                               "TRANSPORT",
+                                               "BLACKLIST_FILE", &fn))
+  {
+#if DEBUG_TRANSPORT
+    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                "Option `%s' in section `%s' not specified!\n",
+                "BLACKLIST_FILE", "TRANSPORT");
+#endif
+    return;
+  }
+  if (GNUNET_OK != GNUNET_DISK_file_test (fn))
+    GNUNET_DISK_fn_write (fn, NULL, 0,
+                          GNUNET_DISK_PERM_USER_READ |
+                          GNUNET_DISK_PERM_USER_WRITE);
+  if (0 != STAT (fn, &frstat))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                _("Could not read blacklist file `%s'\n"), fn);
+    GNUNET_free (fn);
+    return;
+  }
+  if (frstat.st_size == 0)
+  {
+#if DEBUG_TRANSPORT
+    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                _("Blacklist file `%s' is empty.\n"), fn);
+#endif
+    GNUNET_free (fn);
+    return;
+  }
+  /* FIXME: use mmap */
+  data = GNUNET_malloc_large (frstat.st_size);
+  GNUNET_assert (data != NULL);
+  if (frstat.st_size != GNUNET_DISK_fn_read (fn, data, frstat.st_size))
+  {
+    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+                _("Failed to read blacklist from `%s'\n"), fn);
+    GNUNET_free (fn);
+    GNUNET_free (data);
+    return;
+  }
+  entries_found = 0;
+  pos = 0;
+  while ((pos < frstat.st_size) && isspace ((unsigned char) data[pos]))
+    pos++;
+  while ((frstat.st_size >= sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded)) &&
+         (pos <=
+          frstat.st_size - sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded)))
+  {
+    colon_pos = pos;
+    while ((colon_pos < frstat.st_size) &&
+           (data[colon_pos] != ':') &&
+           (!isspace ((unsigned char) data[colon_pos])))
+      colon_pos++;
+    if (colon_pos >= frstat.st_size)
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                  _
+                  ("Syntax error in blacklist file at offset %llu, giving up!\n"),
+                  (unsigned long long) colon_pos);
+      GNUNET_free (fn);
+      GNUNET_free (data);
+      return;
+    }
+
+    if (isspace ((unsigned char) data[colon_pos]))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                  _
+                  ("Syntax error in blacklist file at offset %llu, skipping bytes.\n"),
+                  (unsigned long long) colon_pos);
+      pos = colon_pos;
+      while ((pos < frstat.st_size) && isspace ((unsigned char) data[pos]))
+        pos++;
+      continue;
+    }
+    tsize = colon_pos - pos;
+    if ((pos >= frstat.st_size) || (pos + tsize >= frstat.st_size) ||
+        (tsize == 0))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                  _
+                  ("Syntax error in blacklist file at offset %llu, giving up!\n"),
+                  (unsigned long long) colon_pos);
+      GNUNET_free (fn);
+      GNUNET_free (data);
+      return;
+    }
+
+    if (tsize < 1)
+      continue;
+
+    transport_name = GNUNET_malloc (tsize + 1);
+    memcpy (transport_name, &data[pos], tsize);
+    pos = colon_pos + 1;
+#if DEBUG_TRANSPORT
+    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                "Read transport name `%s' in blacklist file.\n",
+                transport_name);
+#endif
+    memcpy (&enc, &data[pos], sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded));
+    if (!isspace
+        ((unsigned char)
+         enc.encoding[sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded) - 1]))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                  _
+                  ("Syntax error in blacklist file at offset %llu, skipping bytes.\n"),
+                  (unsigned long long) pos);
+      pos++;
+      while ((pos < frstat.st_size) && (!isspace ((unsigned char) data[pos])))
+        pos++;
+      GNUNET_free_non_null (transport_name);
+      continue;
+    }
+    enc.encoding[sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded) - 1] = '\0';
+    if (GNUNET_OK !=
+        GNUNET_CRYPTO_hash_from_string ((char *) &enc, &pid.hashPubKey))
+    {
+      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                  _
+                  ("Syntax error in blacklist file at offset %llu, skipping bytes `%s'.\n"),
+                  (unsigned long long) pos, &enc);
+    }
+    else
+    {
+      if (0 != memcmp (&pid,
+                       &GST_my_identity, sizeof (struct GNUNET_PeerIdentity)))
+      {
+        entries_found++;
+        GST_blacklist_add_peer (&pid, transport_name);
+      }
+      else
+      {
+        GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                    _("Found myself `%s' in blacklist (useless, ignored)\n"),
+                    GNUNET_i2s (&pid));
+      }
+    }
+    pos = pos + sizeof (struct GNUNET_CRYPTO_HashAsciiEncoded);
+    GNUNET_free_non_null (transport_name);
+    while ((pos < frstat.st_size) && isspace ((unsigned char) data[pos]))
+      pos++;
+  }
+  GNUNET_STATISTICS_update (GST_stats,
+                            "# Transport entries blacklisted",
+                            entries_found, GNUNET_NO);
+  GNUNET_free (data);
+  GNUNET_free (fn);
+}
+
+
+/**
+ * Start blacklist subsystem.
+ *
+ * @param server server used to accept clients from
+ */
+void
+GST_blacklist_start (struct GNUNET_SERVER_Handle *server)
+{
+  read_blacklist_file ();
+  GNUNET_SERVER_disconnect_notify (server,
+                                   &client_disconnect_notification, NULL);
+}
+
+
+/**
+ * Free the given entry in the blacklist.
+ *
+ * @param cls unused
+ * @param key host identity (unused)
+ * @param value the blacklist entry
+ * @return GNUNET_OK (continue to iterate)
+ */
+static int
+free_blacklist_entry (void *cls, const GNUNET_HashCode * key, void *value)
+{
+  char *be = value;
+
+  GNUNET_free (be);
+  return GNUNET_OK;
+}
+
+
+/**
+ * Stop blacklist subsystem.
+ */
+void
+GST_blacklist_stop ()
+{
+  if (NULL != blacklist)
+  {
+    GNUNET_CONTAINER_multihashmap_iterate (blacklist,
+                                           &free_blacklist_entry, NULL);
+    GNUNET_CONTAINER_multihashmap_destroy (blacklist);
+    blacklist = NULL;
+  }
+}
+
+
+/**
+ * Transmit blacklist query to the client.
+ *
+ * @param cls the 'struct GST_BlacklistCheck'
+ * @param size number of bytes allowed
+ * @param buf where to copy the message
+ * @return number of bytes copied to buf
+ */
+static size_t
+transmit_blacklist_message (void *cls, size_t size, void *buf)
+{
+  struct GST_BlacklistCheck *bc = cls;
+  struct Blacklisters *bl;
+  struct BlacklistMessage bm;
+
+  bc->th = NULL;
+  if (size == 0)
+  {
+    GNUNET_assert (bc->task == GNUNET_SCHEDULER_NO_TASK);
+    bc->task = GNUNET_SCHEDULER_add_now (&do_blacklist_check, bc);
+    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+                "Failed to send blacklist test for peer `%s' to client\n",
+                GNUNET_i2s (&bc->peer));
+    return 0;
+  }
+#if DEBUG_TRANSPORT
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Sending blacklist test for peer `%s' to client\n",
+              GNUNET_i2s (&bc->peer));
+#endif
+  bl = bc->bl_pos;
+  bm.header.size = htons (sizeof (struct BlacklistMessage));
+  bm.header.type = htons (GNUNET_MESSAGE_TYPE_TRANSPORT_BLACKLIST_QUERY);
+  bm.is_allowed = htonl (0);
+  bm.peer = bc->peer;
+  memcpy (buf, &bm, sizeof (bm));
+  GNUNET_SERVER_receive_done (bl->client, GNUNET_OK);
+  bl->waiting_for_reply = GNUNET_YES;
+  return sizeof (bm);
+}
+
+
+/**
+ * Perform next action in the blacklist check.
+ *
+ * @param cls the 'struct GST_BlacklistCheck*'
+ * @param tc unused
+ */
+static void
+do_blacklist_check (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
+{
+  struct GST_BlacklistCheck *bc = cls;
+  struct Blacklisters *bl;
+
+  bc->task = GNUNET_SCHEDULER_NO_TASK;
+  bl = bc->bl_pos;
+  if (bl == NULL)
+  {
+#if DEBUG_TRANSPORT
+    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                "No other blacklist clients active, will allow neighbour `%s'\n",
+                GNUNET_i2s (&bc->peer));
+#endif
+    bc->cont (bc->cont_cls, &bc->peer, GNUNET_OK);
+    GNUNET_free (bc);
+    return;
+  }
+  if ((bl->bc != NULL) || (bl->waiting_for_reply != GNUNET_NO))
+    return;                     /* someone else busy with this client */
+  bl->bc = bc;
+  bc->th = GNUNET_SERVER_notify_transmit_ready (bl->client,
+                                                sizeof (struct
+                                                        BlacklistMessage),
+                                                GNUNET_TIME_UNIT_FOREVER_REL,
+                                                &transmit_blacklist_message,
+                                                bc);
+}
+
+
+/**
+ * Got the result about an existing connection from a new blacklister.
+ * Shutdown the neighbour if necessary.
+ *
+ * @param cls unused
+ * @param peer the neighbour that was investigated
+ * @param allowed GNUNET_OK if we can keep it,
+ *                GNUNET_NO if we must shutdown the connection
+ */
+static void
+confirm_or_drop_neighbour (void *cls,
+                           const struct GNUNET_PeerIdentity *peer, int allowed)
+{
+  if (GNUNET_OK == allowed)
+    return;                     /* we're done */
+  GNUNET_STATISTICS_update (GST_stats,
+                            gettext_noop ("# disconnects due to blacklist"),
+                            1, GNUNET_NO);
+  GST_neighbours_force_disconnect (peer);
+}
+
+
+/**
+ * Closure for 'test_connection_ok'.
+ */
+struct TestConnectionContext
+{
+  /**
+   * Is this the first neighbour we're checking?
+   */
+  int first;
+
+  /**
+   * Handle to the blacklisting client we need to ask.
+   */
+  struct Blacklisters *bl;
+};
+
+
+/**
+ * Test if an existing connection is still acceptable given a new
+ * blacklisting client.
+ *
+ * @param cls the 'struct TestConnectionContest'
+ * @param pid neighbour's identity
+ * @param ats performance data
+ * @param ats_count number of entries in ats (excluding 0-termination)
+ */
+static void
+test_connection_ok (void *cls,
+                    const struct GNUNET_PeerIdentity *neighbour,
+                    const struct GNUNET_TRANSPORT_ATS_Information *ats,
+                    uint32_t ats_count)
+{
+  struct TestConnectionContext *tcc = cls;
+  struct GST_BlacklistCheck *bc;
+
+  bc = GNUNET_malloc (sizeof (struct GST_BlacklistCheck));
+  GNUNET_CONTAINER_DLL_insert (bc_head, bc_tail, bc);
+  bc->peer = *neighbour;
+  bc->cont = &confirm_or_drop_neighbour;
+  bc->cont_cls = NULL;
+  bc->bl_pos = tcc->bl;
+  if (GNUNET_YES == tcc->first)
+  {
+    /* all would wait for the same client, no need to
+     * create more than just the first task right now */
+    bc->task = GNUNET_SCHEDULER_add_now (&do_blacklist_check, bc);
+    tcc->first = GNUNET_NO;
+  }
+}
+
+
+
+/**
+ * Initialize a blacklisting client.  We got a blacklist-init
+ * message from this client, add him to the list of clients
+ * to query for blacklisting.
+ *
+ * @param cls unused
+ * @param client the client
+ * @param message the blacklist-init message that was sent
  */
 void
-GNUNET_TRANSPORT_handle_blacklist_init (void *cls,
-                                       struct GNUNET_SERVER_Client *client,
-                                       const struct GNUNET_MessageHeader *message)
+GST_blacklist_handle_init (void *cls,
+                           struct GNUNET_SERVER_Client *client,
+                           const struct GNUNET_MessageHeader *message)
 {
   struct Blacklisters *bl;
+  struct TestConnectionContext tcc;
 
+  bl = bl_head;
+  while (bl != NULL)
+  {
+    if (bl->client == client)
+    {
+      GNUNET_break (0);
+      GNUNET_SERVER_receive_done (client, GNUNET_SYSERR);
+      return;
+    }
+    bl = bl->next;
+  }
   bl = GNUNET_malloc (sizeof (struct Blacklisters));
   bl->client = client;
   GNUNET_SERVER_client_keep (client);
-  GNUNET_CONTAINER_DLL_insert (bl_head, bl_tail, bl);
-  /* FIXME: confirm that all existing connections are OK! */
+  GNUNET_CONTAINER_DLL_insert_after (bl_head, bl_tail, bl_tail, bl);
+
+  /* confirm that all existing connections are OK! */
+  tcc.bl = bl;
+  tcc.first = GNUNET_YES;
+  GST_neighbours_iterate (&test_connection_ok, &tcc);
 }
 
 
 /**
- * Handle a request to blacklist a peer.
+ * A blacklisting client has sent us reply. Process it.
  *
- * @param cls closure (always NULL)
- * @param client identification of the client
- * @param message the actual message
+ * @param cls unused
+ * @param client the client
+ * @param message the blacklist-init message that was sent
  */
 void
-GNUNET_TRANSPORT_handle_blacklist_reply (void *cls,
-                                        struct GNUNET_SERVER_Client *client,
-                                        const struct GNUNET_MessageHeader *message)
+GST_blacklist_handle_reply (void *cls,
+                            struct GNUNET_SERVER_Client *client,
+                            const struct GNUNET_MessageHeader *message)
 {
+  const struct BlacklistMessage *msg =
+      (const struct BlacklistMessage *) message;
   struct Blacklisters *bl;
-  const struct BlacklistMessage *msg = (const struct BlacklistMessage*) message;
+  struct GST_BlacklistCheck *bc;
 
   bl = bl_head;
-  while ( (bl != NULL) &&
-         (bl->client != client) )
+  while ((bl != NULL) && (bl->client != client))
     bl = bl->next;
   if (bl == NULL)
+  {
+#if DEBUG_TRANSPORT
+    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Blacklist client disconnected\n");
+#endif
+    /* FIXME: other error handling here!? */
+    GNUNET_SERVER_receive_done (client, GNUNET_SYSERR);
+    return;
+  }
+  bc = bl->bc;
+  bl->bc = NULL;
+  bl->waiting_for_reply = GNUNET_NO;
+  if (NULL != bc)
+  {
+    /* only run this if the blacklist check has not been 
+     * cancelled in the meantime... */
+    if (ntohl (msg->is_allowed) == GNUNET_SYSERR)
     {
-      GNUNET_SERVER_client_done (client, GNUNET_SYSERR);
-      return;
+#if DEBUG_TRANSPORT
+      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                  "Blacklist check failed, peer not allowed\n");
+#endif
+      bc->cont (bc->cont_cls, &bc->peer, GNUNET_NO);
+      GNUNET_CONTAINER_DLL_remove (bc_head, bc_tail, bc);
+      GNUNET_free (bc);
+    }
+    else
+    {
+#if DEBUG_TRANSPORT
+      GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+                  "Blacklist check succeeded, continuing with checks\n");
+#endif
+      bc->bl_pos = bc->bl_pos->next;
+      bc->task = GNUNET_SCHEDULER_add_now (&do_blacklist_check, bc);
     }
-  if (ntohl (msg->is_allowed) == GNUNET_SYSERR)
-    {    
-      be = GNUNET_malloc (sizeof (struct BlacklistEntry));
-      be->peer = msg->peer;
-      be->client = client;
-      GNUNET_CONTAINER_multihashmap_put (blacklist,
-                                        &msg->peer.hashPubKey,
-                                        be,
-                                        GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
+  }
+  /* check if any other bc's are waiting for this blacklister */
+  bc = bc_head;
+  for (bc = bc_head; bc != NULL; bc = bc->next)
+    if ((bc->bl_pos == bl) && (GNUNET_SCHEDULER_NO_TASK == bc->task))
+    {
+      bc->task = GNUNET_SCHEDULER_add_now (&do_blacklist_check, bc);
+      break;
     }
-  /* FIXME: trigger continuation... */
 }
 
 
 /**
- * Notify the given client about all entries in the blacklist.
- *
- * @param cls closure, refers to the 'struct GNUNET_SERVER_Client' to notify
- * @param key current key code (peer identity, not used)
- * @param value value in the hash map, the 'struct BlacklistEntry*'
- * @return GNUNET_YES (continue to iterate)
+ * Add the given peer to the blacklist (for the given transport).
+ * 
+ * @param peer peer to blacklist
+ * @param transport_name transport to blacklist for this peer, NULL for all
  */
-static int
-notify_blacklist_entry (void *cls,
-                       const GNUNET_HashCode *key,
-                       void *value)
-{
-  struct GNUNET_SERVER_Client *client = cls;
-  struct BlacklistEntry *be = value;
-  struct BlacklistMessage msg;
-
-  msg.header.type = htons (GNUNET_MESSAGE_TYPE_TRANSPORT_BLACKLIST);
-  msg.header.size = htons (sizeof (struct BlacklistMessage));
-  msg.reserved = htonl (0);
-  msg.peer = be->peer;
-  msg.until = GNUNET_TIME_absolute_hton (be->until);
-  GNUNET_SERVER_notification_context_unicast (blacklist_notifiers,
-                                             client,
-                                             &msg.header,
-                                             GNUNET_NO);
-  return GNUNET_YES;
+void
+GST_blacklist_add_peer (const struct GNUNET_PeerIdentity *peer,
+                        const char *transport_name)
+{
+#if DEBUG_TRANSPORT
+  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+              "Adding peer `%s' with plugin `%s' to blacklist\n",
+              GNUNET_i2s (peer), transport_name);
+#endif
+  if (blacklist == NULL)
+    blacklist =
+        GNUNET_CONTAINER_multihashmap_create (TRANSPORT_BLACKLIST_HT_SIZE);
+  GNUNET_CONTAINER_multihashmap_put (blacklist, &peer->hashPubKey,
+                                     GNUNET_strdup (transport_name),
+                                     GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
 }
 
 
 /**
- * Handle a request for notification of blacklist changes.
+ * Test if the given blacklist entry matches.  If so,
+ * abort the iteration.
  *
- * @param cls closure (always NULL)
- * @param client identification of the client
- * @param message the actual message
+ * @param cls the transport name to match (const char*)
+ * @param key the key (unused)
+ * @param value the 'char *' (name of a blacklisted transport)
+ * @return GNUNET_OK if the entry does not match, GNUNET_NO if it matches
  */
-void
-GNUNET_TRANSPORT_handle_blacklist_notify (void *cls,
-                                         struct GNUNET_SERVER_Client *client,
-                                         const struct GNUNET_MessageHeader *message)
-{
-  GNUNET_SERVER_notification_context_add (blacklist_notifiers, client);
-  GNUNET_CONTAINER_multihashmap_iterate (blacklist,
-                                        &notify_blacklist_entry,
-                                        client);
+static int
+test_blacklisted (void *cls, const GNUNET_HashCode * key, void *value)
+{
+  const char *transport_name = cls;
+  char *be = value;
+
+  if (0 == strcmp (transport_name, be))
+    return GNUNET_NO;           /* abort iteration! */
+  return GNUNET_OK;
 }
 
 
 /**
- * Is the given peer currently blacklisted?
+ * Test if a peer/transport combination is blacklisted.
  *
- * @param id identity of the peer
- * @return GNUNET_YES if the peer is blacklisted, GNUNET_NO if not
+ * @param peer the identity of the peer to test
+ * @param transport_name name of the transport to test, never NULL
+ * @param cont function to call with result
+ * @param cont_cls closure for 'cont'
+ * @return handle to the blacklist check, NULL if the decision
+ *        was made instantly and 'cont' was already called
  */
-int
-GNUNET_TRANSPORT_blacklist_check (const struct GNUNET_PeerIdentity *id)
+struct GST_BlacklistCheck *
+GST_blacklist_test_allowed (const struct GNUNET_PeerIdentity *peer,
+                            const char *transport_name,
+                            GST_BlacklistTestContinuation cont, void *cont_cls)
 {
-  if (GNUNET_CONTAINER_multihashmap_contains (blacklist, &id->hashPubKey))    
-    return GNUNET_YES;
-  
+  struct GST_BlacklistCheck *bc;
+
+  if ((blacklist != NULL) &&
+      (GNUNET_SYSERR ==
+       GNUNET_CONTAINER_multihashmap_get_multiple (blacklist,
+                                                   &peer->hashPubKey,
+                                                   &test_blacklisted,
+                                                   (void *) transport_name)))
+  {
+    /* disallowed by config, disapprove instantly */
+    GNUNET_STATISTICS_update (GST_stats,
+                              gettext_noop ("# disconnects due to blacklist"),
+                              1, GNUNET_NO);
+    if (cont != NULL)
+      cont (cont_cls, peer, GNUNET_NO);
+    return NULL;
+  }
+
+  if (bl_head == NULL)
+  {
+    /* no blacklist clients, approve instantly */
+    if (cont != NULL)
+      cont (cont_cls, peer, GNUNET_OK);
+    return NULL;
+  }
+
+  /* need to query blacklist clients */
+  bc = GNUNET_malloc (sizeof (struct GST_BlacklistCheck));
+  GNUNET_CONTAINER_DLL_insert (bc_head, bc_tail, bc);
+  bc->peer = *peer;
+  bc->cont = cont;
+  bc->cont_cls = cont_cls;
+  bc->bl_pos = bl_head;
+  bc->task = GNUNET_SCHEDULER_add_now (&do_blacklist_check, bc);
+  return bc;
 }
 
 
 /**
- * Initialize the blacklisting subsystem.
- *
- * @param server server of the transport service
- * @param s scheduler to use
- */
-void 
-GNUNET_TRANSPORT_blacklist_init (struct GNUNET_SERVER_Handle *server,
-                                struct GNUNET_SCHEDULER_Handle *s)
-{
-  sched = s;
-  blacklist = GNUNET_CONTAINER_multihashmap_create (4);
-  GNUNET_SCHEDULER_add_delayed (sched,
-                               GNUNET_TIME_UNIT_FOREVER_REL,
-                               &shutdown_task,
-                               NULL);
+ * Cancel a blacklist check.
+ * 
+ * @param bc check to cancel
+ */
+void
+GST_blacklist_test_cancel (struct GST_BlacklistCheck *bc)
+{
+  GNUNET_CONTAINER_DLL_remove (bc_head, bc_tail, bc);
+  if (bc->bl_pos != NULL)
+  {
+    if (bc->bl_pos->bc == bc)
+    {
+      /* we're at the head of the queue, remove us! */
+      bc->bl_pos->bc = NULL;
+    }
+  }
+  if (GNUNET_SCHEDULER_NO_TASK != bc->task)
+  {
+    GNUNET_SCHEDULER_cancel (bc->task);
+    bc->task = GNUNET_SCHEDULER_NO_TASK;
+  }
+  if (NULL != bc->th)
+  {
+    GNUNET_CONNECTION_notify_transmit_ready_cancel (bc->th);
+    bc->th = NULL;
+  }
+  GNUNET_free (bc);
 }
 
 
-/* end of gnunet-service-transport_blacklist.c */
+/* end of file gnunet-service-transport_blacklist.c */