indentation
[oweals/gnunet.git] / src / transport / gnunet-service-transport_blacklist.c
index 623f8c3f26b095f608e6e3c500d53f01bb11edb6..7720e467fac2401f7a448418316dc3a6a4e26668 100644 (file)
@@ -1,10 +1,10 @@
 /*
      This file is part of GNUnet.
 /*
      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
 
      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
      option) any later version.
 
      GNUnet is distributed in the hope that it will be useful, but
 
 /**
  * @file transport/gnunet-service-transport_blacklist.c
 
 /**
  * @file transport/gnunet-service-transport_blacklist.c
- * @brief low-level P2P messaging
+ * @brief blacklisting implementation
  * @author Christian Grothoff
  */
 #include "platform.h"
  * @author Christian Grothoff
  */
 #include "platform.h"
-#include "gnunet_protocols.h"
-#include "gnunet_service_lib.h"
-#include "transport.h"
+#include "gnunet-service-transport.h"
 #include "gnunet-service-transport_blacklist.h"
 #include "gnunet-service-transport_blacklist.h"
+#include "gnunet-service-transport_neighbours.h"
+#include "transport.h"
 
 
 /**
 
 
 /**
- * Information kept for each blacklisted peer.
+ * Size of the blacklist hash map.
  */
  */
-struct BlacklistEntry
-{
-  /**
-   * How long until this entry times out?
-   */
-  struct GNUNET_TIME_Absolute until;
+#define TRANSPORT_BLACKLIST_HT_SIZE 64
 
 
-  /**
-   * Task scheduled to run the moment the time does run out.
-   */
-  GNUNET_SCHEDULER_TaskIdentifier timeout_task;
-};
+
+/**
+ * Context we use when performing a blacklist check.
+ */
+struct GST_BlacklistCheck;
 
 
 /**
 
 
 /**
- * Entry in list of notifications still to transmit to
- * a client.
+ * Information kept for each client registered to perform
+ * blacklisting.
  */
  */
-struct PendingNotificationList 
+struct Blacklisters
 {
 {
+  /**
+   * This is a linked list.
+   */
+  struct Blacklisters *next;
 
   /**
    * This is a linked list.
    */
 
   /**
    * This is a linked list.
    */
-  struct PendingNotificationList *next;
+  struct Blacklisters *prev;
 
   /**
 
   /**
-   * Identity of the peer to send notification about.
+   * Client responsible for this entry.
    */
    */
-  struct GNUNET_PeerIdentity peer;
+  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;
 
 };
 
 
 
 };
 
 
+
 /**
 /**
- * List of clients to notify whenever the blacklist changes.
+ * Context we use when performing a blacklist check.
  */
  */
-struct BlacklistNotificationList
+struct GST_BlacklistCheck
 {
 
   /**
    * This is a linked list.
    */
 {
 
   /**
    * This is a linked list.
    */
-  struct BlacklistNotificationList *next;
+  struct GST_BlacklistCheck *next;
 
   /**
 
   /**
-   * Client to notify.
+   * This is a linked list.
    */
    */
-  struct GNUNET_SERVER_Client *client;
+  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;
 
   /**
 
   /**
-   * Pending request for transmission to client, or NULL.
-   */ 
-  struct GNUNET_CONNECTION_TransmitHandle *req;
+   * 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;
 
   /**
 
   /**
-   * Blacklist entries that still need to be submitted.
+   * Current task performing the check.
    */
    */
-  struct PendingNotificationList *pending;
-  
+  GNUNET_SCHEDULER_TaskIdentifier task;
+
 };
 
 
 /**
 };
 
 
 /**
- * Map of blacklisted peers (maps from peer identities
- * to 'struct BlacklistEntry*' values).
+ * 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;
 
  */
 static struct GNUNET_CONTAINER_MultiHashMap *blacklist;
 
+
 /**
 /**
- * Linked list of clients to notify whenever the blacklist changes.
+ * Perform next action in the blacklist check.
+ *
+ * @param cls the 'struct BlacklistCheck*'
+ * @param tc unused
  */
  */
-static struct BlacklistNotificationList *blacklist_notifiers;
+static void
+do_blacklist_check (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc);
+
 
 /**
 
 /**
- * Our scheduler.
+ * Called whenever a client is disconnected.  Frees our
+ * resources associated with that client.
+ *
+ * @param cls closure (unused)
+ * @param client identification of the client
  */
  */
-static struct GNUNET_SCHEDULER_Handle *sched;
+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;
+  }
+}
 
 
 /**
 
 
 /**
- * Free the entries in the blacklist hash map.
+ * Read the blacklist file, containing transport:peer entries.
+ * Provided the transport is loaded, set up hashmap with these
+ * entries to blacklist peers by transport.
  *
  *
- * @param cls closure, unused
- * @param key current key code
- * @param value value in the hash map
- * @return GNUNET_YES if we should continue to
- *         iterate,
- *         GNUNET_NO if not.
+ */
+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
  */
 static int
-free_blacklist_entry (void *cls,
-                     const GNUNET_HashCode *key,
-                     void *value)
+free_blacklist_entry (void *cls, const GNUNET_HashCode * key, void *value)
 {
 {
-  struct BlacklistEntry *be = value;
+  char *be = value;
 
 
-  GNUNET_SCHEDULER_cancel (sched,
-                          be->timeout_task);
   GNUNET_free (be);
   GNUNET_free (be);
-  return GNUNET_YES;
+  return GNUNET_OK;
 }
 
 
 /**
 }
 
 
 /**
- * Task run when we are shutting down.  Cleans up.
+ * 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 closure (unused)
- * @param tc scheduler context (unused)
+ * @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 void 
-shutdown_task (void *cls,
-              const struct GNUNET_SCHEDULER_TaskContext *tc)
+static size_t
+transmit_blacklist_message (void *cls, size_t size, void *buf)
 {
 {
-  GNUNET_CONTAINER_multihashmap_iterate (blacklist,
-                                        &free_blacklist_entry,
-                                        NULL);
-  GNUNET_CONTAINER_multihashmap_destroy (blacklist);
+  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);
 }
 
 
 /**
 }
 
 
 /**
- * Handle a request to blacklist a peer.
+ * Perform next action in the blacklist check.
  *
  *
- * @param cls closure (always NULL)
- * @param client identification of the client
- * @param message the actual message
+ * @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
  */
 void
-GNUNET_TRANSPORT_handle_blacklist (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)
 {
 {
-  /* FIXME */
-  GNUNET_SERVER_receive_done (client, GNUNET_OK);
+  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_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 for notification of blacklist changes.
+ * 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
  */
 void
-GNUNET_TRANSPORT_handle_blacklist_notify (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)
 {
 {
-  /* FIXME */
-  GNUNET_SERVER_receive_done (client, GNUNET_OK);
+  const struct BlacklistMessage *msg =
+      (const struct BlacklistMessage *) message;
+  struct Blacklisters *bl;
+  struct GST_BlacklistCheck *bc;
+
+  bl = bl_head;
+  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)
+    {
+#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);
+    }
+  }
+  /* 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;
+    }
 }
 
 
 /**
 }
 
 
 /**
- * Is the given peer currently blacklisted?
+ * 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
+ */
+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);
+}
+
+
+/**
+ * Test if the given blacklist entry matches.  If so,
+ * abort the iteration.
  *
  *
- * @param id identity of the peer
- * @return GNUNET_YES if the peer is blacklisted, GNUNET_NO if not
+ * @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
  */
  */
-int
-GNUNET_TRANSPORT_blacklist_check (const struct GNUNET_PeerIdentity *id)
+static int
+test_blacklisted (void *cls, const GNUNET_HashCode * key, void *value)
 {
 {
-  return GNUNET_CONTAINER_multihashmap_contains (blacklist, &id->hashPubKey);
+  const char *transport_name = cls;
+  char *be = value;
+
+  if (0 == strcmp (transport_name, be))
+    return GNUNET_NO;           /* abort iteration! */
+  return GNUNET_OK;
 }
 
 
 /**
 }
 
 
 /**
- * Initialize the blacklisting subsystem.
+ * Test if a peer/transport combination is blacklisted.
  *
  *
- * @param s scheduler to use
+ * @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
  */
  */
-void 
-GNUNET_TRANSPORT_blacklist_init (struct GNUNET_SCHEDULER_Handle *s)
+struct GST_BlacklistCheck *
+GST_blacklist_test_allowed (const struct GNUNET_PeerIdentity *peer,
+                            const char *transport_name,
+                            GST_BlacklistTestContinuation cont, void *cont_cls)
 {
 {
-  sched = s;
-  blacklist = GNUNET_CONTAINER_multihashmap_create (4);
-  GNUNET_SCHEDULER_add_delayed (sched,
-                               GNUNET_TIME_UNIT_FOREVER_REL,
-                               &shutdown_task,
-                               NULL);
+  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;
 }
 
 }
 
-/* end of gnunet-service-transport_blacklist.c */
+
+/**
+ * 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 file gnunet-service-transport_blacklist.c */