From 3c64e04e0237e7b730c277d7977ac1cf18a483b9 Mon Sep 17 00:00:00 2001
From: Yawning Angel <yawning@schwanenlied.me>
Date: Sun, 25 May 2014 10:28:32 +0000
Subject: [PATCH] Add support for SOCKS4.

Despite the unfortunate scheme name, this really is SOCKS4, and not 4a,
as the torrc Socks4Proxy option only supports addresses and not FQDNs.

Part of issue #7.
---
 obfs4proxy/obfs4proxy.go   |   2 +-
 obfs4proxy/proxy_extras.go | 107 +++++++++++++++++++++++++++++++++++++
 2 files changed, 108 insertions(+), 1 deletion(-)

diff --git a/obfs4proxy/obfs4proxy.go b/obfs4proxy/obfs4proxy.go
index c00ee88..2d292ce 100644
--- a/obfs4proxy/obfs4proxy.go
+++ b/obfs4proxy/obfs4proxy.go
@@ -310,7 +310,7 @@ func clientSetup() (launched bool) {
 	}
 	if ptClientProxy != nil {
 		// XXX: Limit this to SOCKS5 for now.
-		if ptClientProxy.Scheme != "socks5" {
+		if ptClientProxy.Scheme == "http" {
 			ptProxyError(fmt.Sprintf("proxy scheme not supported: %s",
 				ptClientProxy.Scheme))
 			return
diff --git a/obfs4proxy/proxy_extras.go b/obfs4proxy/proxy_extras.go
index 27b638b..080e6b0 100644
--- a/obfs4proxy/proxy_extras.go
+++ b/obfs4proxy/proxy_extras.go
@@ -28,7 +28,11 @@
 package main
 
 import (
+	"errors"
+	"io"
+	"net"
 	"net/url"
+	"strconv"
 
 	"code.google.com/p/go.net/proxy"
 
@@ -49,3 +53,106 @@ func getProxyDialer(uri *url.URL) (obfs4.DialFn, error) {
 
 	return dialer.Dial, nil
 }
+
+// socks4 is a SOCKSv4 proxy.
+type socks4 struct {
+	hostPort string
+	username string
+	forward  proxy.Dialer
+}
+
+const (
+	socks4Version        = 0x04
+	socks4CommandConnect = 0x01
+	socks4Null           = 0x00
+	socks4ReplyVersion   = 0x00
+
+	socks4Granted = 0x5a
+)
+
+func newSOCKS4(uri *url.URL, forward proxy.Dialer) (proxy.Dialer, error) {
+	s := new(socks4)
+	s.hostPort = uri.Host
+	s.forward = forward
+	if uri.User != nil {
+		s.username = uri.User.Username()
+	}
+	return s, nil
+}
+
+func (s *socks4) Dial(network, addr string) (net.Conn, error) {
+	if network != "tcp" && network != "tcp4" {
+		return nil, errors.New("invalid network type")
+	}
+
+	// Deal with the destination address/string.
+	ipStr, portStr, err := net.SplitHostPort(addr)
+	if err != nil {
+		return nil, err
+	}
+	ip := net.ParseIP(ipStr)
+	if ip == nil {
+		return nil, errors.New("failed to parse destination IP")
+	}
+	ip4 := ip.To4()
+	if ip4 == nil {
+		return nil, errors.New("destination address is not IPv4")
+	}
+	port, err := strconv.ParseUint(portStr, 10, 16)
+	if err != nil {
+		return nil, err
+	}
+
+	// Connect to the proxy.
+	c, err := s.forward.Dial("tcp", s.hostPort)
+	if err != nil {
+		return nil, err
+	}
+
+	// Make/write the request:
+	//  +----+----+----+----+----+----+----+----+----+----+....+----+
+	//  | VN | CD | DSTPORT |      DSTIP        | USERID       |NULL|
+	//  +----+----+----+----+----+----+----+----+----+----+....+----+
+
+	req := make([]byte, 0, 9+len(s.username))
+	req = append(req, socks4Version)
+	req = append(req, socks4CommandConnect)
+	req = append(req, byte(port>>8), byte(port))
+	req = append(req, ip4...)
+	if s.username != "" {
+		req = append(req, s.username...)
+	}
+	req = append(req, socks4Null)
+	_, err = c.Write(req)
+	if err != nil {
+		c.Close()
+		return nil, err
+	}
+
+	// Read the response:
+	// +----+----+----+----+----+----+----+----+
+	// | VN | CD | DSTPORT |      DSTIP        |
+	// +----+----+----+----+----+----+----+----+
+
+	var resp [8]byte
+	_, err = io.ReadFull(c, resp[:])
+	if err != nil {
+		c.Close()
+		return nil, err
+	}
+	if resp[0] != socks4ReplyVersion {
+		c.Close()
+		return nil, errors.New("proxy returned invalid SOCKS4 version")
+	}
+	if resp[1] != socks4Granted {
+		c.Close()
+		return nil, errors.New("proxy rejected the connect request")
+	}
+
+	return c, nil
+}
+
+func init() {
+	// Despite the scheme name, this really is SOCKS4.
+	proxy.RegisterDialerType("socks4a", newSOCKS4)
+}
-- 
GitLab