[telepathy-gabble/master] SOCKS5: implement proxy support

Guillaume Desmottes guillaume.desmottes at collabora.co.uk
Fri Apr 3 08:41:32 PDT 2009


---
 src/bytestream-socks5.c |  323 +++++++++++++++++++++++++++++++++++++++--------
 1 files changed, 269 insertions(+), 54 deletions(-)

diff --git a/src/bytestream-socks5.c b/src/bytestream-socks5.c
index 01b7c3b..854dc50 100644
--- a/src/bytestream-socks5.c
+++ b/src/bytestream-socks5.c
@@ -84,12 +84,17 @@ enum
 enum _Socks5State
 {
   SOCKS5_STATE_INVALID,
+  SOCKS5_STATE_INITIATOR_OFFER_SENT,
   SOCKS5_STATE_TARGET_TRYING_CONNECT,
   SOCKS5_STATE_TARGET_AUTH_REQUEST_SENT,
   SOCKS5_STATE_TARGET_CONNECT_REQUESTED,
   SOCKS5_STATE_CONNECTED,
   SOCKS5_STATE_INITIATOR_AWAITING_AUTH_REQUEST,
   SOCKS5_STATE_INITIATOR_AWAITING_COMMAND,
+  SOCKS5_STATE_INITIATOR_TRYING_CONNECT,
+  SOCKS5_STATE_INITIATOR_AUTH_REQUEST_SENT,
+  SOCKS5_STATE_INITIATOR_CONNECT_REQUESTED,
+  SOCKS5_STATE_INITIATOR_ACTIVATION_SENT,
   SOCKS5_STATE_ERROR
 };
 
@@ -159,6 +164,7 @@ struct _GabbleBytestreamSocks5Private
   GabbleBytestreamState bytestream_state;
   gchar *peer_jid;
   gchar *self_full_jid;
+  gchar *proxy_jid;
 
   /* List of Streamhost */
   GSList *streamhosts;
@@ -263,6 +269,7 @@ gabble_bytestream_socks5_finalize (GObject *object)
   g_free (priv->peer_resource);
   g_free (priv->peer_jid);
   g_free (priv->self_full_jid);
+  g_free (priv->proxy_jid);
 
   g_slist_foreach (priv->streamhosts, (GFunc) streamhost_free, NULL);
   g_slist_free (priv->streamhosts);
@@ -493,7 +500,8 @@ transport_connected_cb (GibberTransport *transport,
 
   stop_timer (self);
 
-  if (priv->socks5_state == SOCKS5_STATE_TARGET_TRYING_CONNECT)
+  if (priv->socks5_state == SOCKS5_STATE_TARGET_TRYING_CONNECT ||
+      priv->socks5_state == SOCKS5_STATE_INITIATOR_TRYING_CONNECT)
     {
       gchar msg[3];
 
@@ -507,7 +515,10 @@ transport_connected_cb (GibberTransport *transport,
 
       write_to_transport (self, msg, 3, NULL);
 
-      priv->socks5_state = SOCKS5_STATE_TARGET_AUTH_REQUEST_SENT;
+      if (priv->socks5_state == SOCKS5_STATE_TARGET_TRYING_CONNECT)
+        priv->socks5_state = SOCKS5_STATE_TARGET_AUTH_REQUEST_SENT;
+      else
+        priv->socks5_state = SOCKS5_STATE_INITIATOR_AUTH_REQUEST_SENT;
     }
 }
 
@@ -715,6 +726,121 @@ start_timer (GabbleBytestreamSocks5 *self,
   priv->timer_id = g_timeout_add_seconds (seconds, socks5_timer_cb, self);
 }
 
+static void
+target_got_connect_reply (GabbleBytestreamSocks5 *self)
+{
+  GabbleBytestreamSocks5Private *priv = GABBLE_BYTESTREAM_SOCKS5_GET_PRIVATE (
+      self);
+  LmMessage *iq_result;
+
+  DEBUG ("Received CONNECT reply. Socks5 stream connected. "
+      "Bytestream is now open");
+  priv->socks5_state = SOCKS5_STATE_CONNECTED;
+  g_object_set (self, "state", GABBLE_BYTESTREAM_STATE_OPEN, NULL);
+
+  /* Acknowledge the connection */
+  iq_result = lm_iq_message_make_result (
+      priv->msg_for_acknowledge_connection);
+  if (NULL != iq_result)
+    {
+      LmMessageNode *node;
+      Streamhost *current_streamhost;
+
+      node = lm_message_node_add_child (iq_result->node, "query", "");
+      lm_message_node_set_attribute (node, "xmlns", NS_BYTESTREAMS);
+
+      /* streamhost-used informs the other end of the streamhost we
+       * decided to use. In case of a direct connetion this is useless
+       * but if we are using an external proxy we need to know which
+       * one was selected */
+      node = lm_message_node_add_child (node, "streamhost-used", "");
+      current_streamhost = priv->streamhosts->data;
+      lm_message_node_set_attribute (node, "jid",
+          current_streamhost->jid);
+
+      _gabble_connection_send (priv->conn, iq_result, NULL);
+      lm_message_unref (iq_result);
+    }
+
+  if (priv->read_blocked)
+    {
+      DEBUG ("reading has been blocked. Blocking now as the socks5 "
+          "negotiation is done");
+      gibber_transport_block_receiving (priv->transport, TRUE);
+    }
+}
+
+static LmHandlerResult
+socks5_activation_reply_cb (GabbleConnection *conn,
+                            LmMessage *sent_msg,
+                            LmMessage *reply_msg,
+                            GObject *obj,
+                            gpointer user_data)
+{
+  GabbleBytestreamSocks5 *self = GABBLE_BYTESTREAM_SOCKS5 (user_data);
+  GabbleBytestreamSocks5Private *priv = GABBLE_BYTESTREAM_SOCKS5_GET_PRIVATE (
+      self);
+
+  if (lm_message_get_sub_type (reply_msg) != LM_MESSAGE_SUB_TYPE_RESULT)
+    {
+      DEBUG ("Activation failed");
+      goto activation_failed;
+    }
+
+  if (priv->socks5_state != SOCKS5_STATE_INITIATOR_ACTIVATION_SENT)
+    {
+      DEBUG ("We are not waiting for an activation reply (state: %u)",
+          priv->socks5_state);
+      goto activation_failed;
+    }
+
+  DEBUG ("Proxy activated the bytestream. It's now open");
+
+  priv->socks5_state = SOCKS5_STATE_CONNECTED;
+  g_object_set (self, "state", GABBLE_BYTESTREAM_STATE_OPEN, NULL);
+  /* We can read data from the sock5 socket now */
+  gibber_transport_block_receiving (priv->transport, FALSE);
+
+  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+activation_failed:
+  g_signal_emit_by_name (self, "connection-error");
+  g_object_set (self, "state", GABBLE_BYTESTREAM_STATE_CLOSED, NULL);
+  return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+}
+
+static void
+initiator_got_connect_reply (GabbleBytestreamSocks5 *self)
+{
+  GabbleBytestreamSocks5Private *priv = GABBLE_BYTESTREAM_SOCKS5_GET_PRIVATE (
+      self);
+  LmMessage *iq;
+
+  DEBUG ("SOCKS5 negotiation with proxy is done. Sending activation IQ");
+
+  iq = lm_message_build (priv->proxy_jid, LM_MESSAGE_TYPE_IQ,
+      '@', "type", "set",
+      '(', "query", "",
+        '@', "xmlns", NS_BYTESTREAMS,
+        '@', "sid", priv->stream_id,
+        '(', "activate", priv->peer_jid, ')',
+      ')', NULL);
+
+  priv->socks5_state = SOCKS5_STATE_INITIATOR_ACTIVATION_SENT;
+
+  /* Block reading while waiting for the activation reply */
+  gibber_transport_block_receiving (priv->transport, TRUE);
+
+  if (!_gabble_connection_send_with_reply (priv->conn, iq,
+      socks5_activation_reply_cb, G_OBJECT (self), self, NULL))
+    {
+      DEBUG ("Sending activation IQ failed");
+
+      g_signal_emit_by_name (self, "connection-error");
+      g_object_set (self, "state", GABBLE_BYTESTREAM_STATE_CLOSED, NULL);
+      return;
+    }
+}
+
 /* Process the received data and returns the number of bytes that have been
  * used */
 static gsize
@@ -727,13 +853,13 @@ socks5_handle_received_data (GabbleBytestreamSocks5 *self,
   guint auth_len;
   guint i;
   gchar *domain;
-  LmMessage *iq_result;
   guint8 domain_len;
   gsize len;
 
   switch (priv->socks5_state)
     {
       case SOCKS5_STATE_TARGET_AUTH_REQUEST_SENT:
+      case SOCKS5_STATE_INITIATOR_AUTH_REQUEST_SENT:
         /* We sent an authorization request and we are awaiting for a
          * response, the response is 2 bytes-long */
         if (string->len < 2)
@@ -752,8 +878,16 @@ socks5_handle_received_data (GabbleBytestreamSocks5 *self,
 
         DEBUG ("Received auth reply. Sending CONNECT command");
 
-        domain = compute_domain (priv->stream_id, priv->peer_jid,
-            priv->self_full_jid);
+        if (priv->socks5_state == SOCKS5_STATE_TARGET_AUTH_REQUEST_SENT)
+          {
+            domain = compute_domain(priv->stream_id, priv->peer_jid,
+                priv->self_full_jid);
+          }
+        else
+          {
+            domain = compute_domain(priv->stream_id, priv->self_full_jid,
+                priv->peer_jid);
+          }
 
         msg[0] = SOCKS5_VERSION;
         msg[1] = SOCKS5_CMD_CONNECT;
@@ -771,7 +905,10 @@ socks5_handle_received_data (GabbleBytestreamSocks5 *self,
 
         write_to_transport (self, msg, SOCKS5_CONNECT_LENGTH, NULL);
 
-        priv->socks5_state = SOCKS5_STATE_TARGET_CONNECT_REQUESTED;
+        if (priv->socks5_state == SOCKS5_STATE_TARGET_AUTH_REQUEST_SENT)
+          priv->socks5_state = SOCKS5_STATE_TARGET_CONNECT_REQUESTED;
+        else
+          priv->socks5_state = SOCKS5_STATE_INITIATOR_CONNECT_REQUESTED;
 
         /* Older version of Gabble (pre 0.7.22) are bugged and just send 2
          * bytes as CONNECT reply. We set a timer to not wait the full reply
@@ -783,6 +920,7 @@ socks5_handle_received_data (GabbleBytestreamSocks5 *self,
         return 2;
 
       case SOCKS5_STATE_TARGET_CONNECT_REQUESTED:
+      case SOCKS5_STATE_INITIATOR_CONNECT_REQUESTED:
         /* We sent a CONNECT request and are awaiting for the response */
         if (string->len < SOCKS5_MIN_LENGTH)
           return 0;
@@ -809,8 +947,16 @@ socks5_handle_received_data (GabbleBytestreamSocks5 *self,
             return string->len;
           }
 
-        domain = compute_domain (priv->stream_id, priv->peer_jid,
-            priv->self_full_jid);
+        if (priv->socks5_state == SOCKS5_STATE_TARGET_AUTH_REQUEST_SENT)
+          {
+            domain = compute_domain(priv->stream_id, priv->peer_jid,
+                priv->self_full_jid);
+          }
+        else
+          {
+            domain = compute_domain(priv->stream_id, priv->self_full_jid,
+                priv->peer_jid);
+          }
 
         if (!check_domain (&string->str[5], domain_len, domain))
           {
@@ -820,41 +966,10 @@ socks5_handle_received_data (GabbleBytestreamSocks5 *self,
 
         g_free (domain);
 
-        DEBUG ("Received CONNECT reply. Socks5 stream connected. "
-            "Bytestream is now open");
-        priv->socks5_state = SOCKS5_STATE_CONNECTED;
-        g_object_set (self, "state", GABBLE_BYTESTREAM_STATE_OPEN, NULL);
-
-        /* Acknowledge the connection */
-        iq_result = lm_iq_message_make_result (
-            priv->msg_for_acknowledge_connection);
-        if (NULL != iq_result)
-          {
-            LmMessageNode *node;
-            Streamhost *current_streamhost;
-
-            node = lm_message_node_add_child (iq_result->node, "query", "");
-            lm_message_node_set_attribute (node, "xmlns", NS_BYTESTREAMS);
-
-            /* streamhost-used informs the other end of the streamhost we
-             * decided to use. In case of a direct connetion this is useless
-             * but if we are using an external proxy we need to know which
-             * one was selected */
-            node = lm_message_node_add_child (node, "streamhost-used", "");
-            current_streamhost = priv->streamhosts->data;
-            lm_message_node_set_attribute (node, "jid",
-                current_streamhost->jid);
-
-            _gabble_connection_send (priv->conn, iq_result, NULL);
-            lm_message_unref (iq_result);
-          }
-
-        if (priv->read_blocked)
-          {
-            DEBUG ("reading has been blocked. Blocking now as the socks5 "
-                "negotiation is done");
-            gibber_transport_block_receiving (priv->transport, TRUE);
-          }
+        if (priv->socks5_state == SOCKS5_STATE_TARGET_CONNECT_REQUESTED)
+          target_got_connect_reply (self);
+        else
+          initiator_got_connect_reply (self);
 
         return SOCKS5_MIN_LENGTH + domain_len;
 
@@ -992,10 +1107,19 @@ socks5_handle_received_data (GabbleBytestreamSocks5 *self,
         return string->len;
 
       case SOCKS5_STATE_TARGET_TRYING_CONNECT:
+      case SOCKS5_STATE_INITIATOR_TRYING_CONNECT:
         DEBUG ("Impossible to receive data when not yet connected to the "
             "socket");
         break;
 
+      case SOCKS5_STATE_INITIATOR_OFFER_SENT:
+        DEBUG ("Shouldn't receive data when we just sent the offer");
+        break;
+
+      case SOCKS5_STATE_INITIATOR_ACTIVATION_SENT:
+        DEBUG ("Shouldn't receive data before we received activation reply");
+        break;
+
       case SOCKS5_STATE_INVALID:
         DEBUG ("Invalid SOCKS5 state");
         break;
@@ -1335,6 +1459,47 @@ gabble_bytestream_socks5_close (GabbleBytestreamIface *iface,
     }
 }
 
+static void
+initiator_connected_to_proxy (GabbleBytestreamSocks5 *self)
+{
+  GabbleBytestreamSocks5Private *priv = GABBLE_BYTESTREAM_SOCKS5_GET_PRIVATE (
+      self);
+  const GSList *proxies;
+  GSList *l;
+  GabbleSocks5Proxy *proxy = NULL;
+  GibberTCPTransport *transport;
+
+  proxies = gabble_bytestream_factory_get_socks_proxies(
+      priv->conn->bytestream_factory);
+  for (l = (GSList *) proxies; l != NULL; l = g_slist_next (l))
+     {
+       proxy = (GabbleSocks5Proxy *) l->data;
+
+       if (!tp_strdiff (proxy->jid, priv->proxy_jid))
+         break;
+
+       proxy = NULL;
+     }
+
+  if (proxy == NULL)
+    {
+      DEBUG ("Uknown proxy: %s. Closing the bytestream", priv->proxy_jid);
+      g_signal_emit_by_name (self, "connection-error");
+      g_object_set (self, "state", GABBLE_BYTESTREAM_STATE_CLOSED, NULL);
+      return;
+    }
+
+  DEBUG ("connect to proxy: %s (%s;%s)", proxy->jid, proxy->host, proxy->port);
+  priv->socks5_state = SOCKS5_STATE_INITIATOR_TRYING_CONNECT;
+
+  transport = gibber_tcp_transport_new ();
+  set_transport (self, GIBBER_TRANSPORT (transport));
+  g_object_unref (transport);
+
+  gibber_tcp_transport_connect (transport, proxy->host,
+      proxy->port);
+}
+
 static LmHandlerResult
 socks5_init_reply_cb (GabbleConnection *conn,
                       LmMessage *sent_msg,
@@ -1349,14 +1514,7 @@ socks5_init_reply_cb (GabbleConnection *conn,
   if (lm_message_get_sub_type (reply_msg) == LM_MESSAGE_SUB_TYPE_RESULT)
     {
       LmMessageNode *query, *streamhost = NULL;
-
-      if (priv->socks5_state != SOCKS5_STATE_CONNECTED)
-        {
-          DEBUG ("Target claims that the bytestream is open but SOCKS5 is not "
-              "connected (state: %u). Closing the bytestream",
-              priv->socks5_state);
-          goto socks5_init_error;
-        }
+      const gchar *jid;
 
       query = lm_message_node_get_child_with_namespace (reply_msg->node,
           "query", NS_BYTESTREAMS);
@@ -1370,9 +1528,42 @@ socks5_init_reply_cb (GabbleConnection *conn,
           goto socks5_init_error;
         }
 
+      jid = lm_message_node_get_attribute (streamhost, "jid");
+      if (jid == NULL)
+        {
+          DEBUG ("no jid attribute in streamhost. Closing the bytestream");
+          goto socks5_init_error;
+        }
+
+      if (tp_strdiff (jid, priv->self_full_jid))
+        {
+          DEBUG ("Target is connected to proxy: %s", jid);
+
+          if (priv->socks5_state != SOCKS5_STATE_INITIATOR_OFFER_SENT)
+            {
+              DEBUG ("We are already in the negotation process (state: %u). "
+                  "Closing the bytestream", priv->socks5_state);
+              goto socks5_init_error;
+            }
+
+          priv->proxy_jid = g_strdup (jid);
+          initiator_connected_to_proxy (self);
+          return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+        }
+
+      /* No proxy used */
+      DEBUG ("Target is connected to us");
+
+      if (priv->socks5_state != SOCKS5_STATE_CONNECTED)
+        {
+          DEBUG ("Target claims that the bytestream is open but SOCKS5 is not "
+              "connected (state: %u). Closing the bytestream",
+              priv->socks5_state);
+          goto socks5_init_error;
+        }
+
       /* yeah, stream initiated */
-      DEBUG ("Socks5 stream initiated using stream: %s",
-          lm_message_node_get_attribute (streamhost, "jid"));
+      DEBUG ("Socks5 stream initiated using stream: %s", jid);
       g_object_set (self, "state", GABBLE_BYTESTREAM_STATE_OPEN, NULL);
       /* We can read data from the sock5 socket now */
       gibber_transport_block_receiving (priv->transport, FALSE);
@@ -1558,6 +1749,8 @@ gabble_bytestream_socks5_initiate (GabbleBytestreamIface *iface)
   LmMessage *msg;
   GList *ips;
   GList *ip;
+  const GSList *proxies;
+  GSList *l;
 
   if (priv->bytestream_state != GABBLE_BYTESTREAM_STATE_INITIATING)
     {
@@ -1593,7 +1786,9 @@ gabble_bytestream_socks5_initiate (GabbleBytestreamIface *iface)
   ip = ips;
   while (ip)
     {
-      LmMessageNode *node = lm_message_node_add_child (msg->node->children,
+      LmMessageNode *node;
+
+      node = lm_message_node_add_child (msg->node->children,
           "streamhost", "");
       lm_message_node_set_attributes (node,
           "jid", priv->self_full_jid,
@@ -1611,6 +1806,26 @@ gabble_bytestream_socks5_initiate (GabbleBytestreamIface *iface)
    * add support for external proxies to have more chances to make the
    * bytestream work */
 
+  proxies = gabble_bytestream_factory_get_socks_proxies(
+      priv->conn->bytestream_factory);
+
+  for (l = (GSList *) proxies; l != NULL; l = g_slist_next (l))
+    {
+      LmMessageNode *node;
+      GabbleSocks5Proxy *proxy = (GabbleSocks5Proxy *) l->data;
+
+      node = lm_message_node_add_child (msg->node->children,
+          "streamhost", "");
+
+      lm_message_node_set_attributes (node,
+          "jid", proxy->jid,
+          "host", proxy->host,
+          "port", proxy->port,
+          NULL);
+    }
+
+  priv->socks5_state = SOCKS5_STATE_INITIATOR_OFFER_SENT;
+
   if (!_gabble_connection_send_with_reply (priv->conn, msg,
       socks5_init_reply_cb, G_OBJECT (self), NULL, NULL))
     {
-- 
1.5.6.5




More information about the telepathy-commits mailing list