From f0d8923a9a864f70b658dd86e67feb373d8f879f Mon Sep 17 00:00:00 2001
From: Yawning Angel <yawning@schwanenlied.me>
Date: Sun, 25 May 2014 11:00:25 +0000
Subject: [PATCH] Move the SOCKS4 code off into it's own little getto.

Part of issue #7.
---
 obfs4proxy/proxy_extras.go | 107 ------------------------
 obfs4proxy/proxy_socks4.go | 164 +++++++++++++++++++++++++++++++++++++
 2 files changed, 164 insertions(+), 107 deletions(-)
 create mode 100644 obfs4proxy/proxy_socks4.go

diff --git a/obfs4proxy/proxy_extras.go b/obfs4proxy/proxy_extras.go
index 080e6b0..27b638b 100644
--- a/obfs4proxy/proxy_extras.go
+++ b/obfs4proxy/proxy_extras.go
@@ -28,11 +28,7 @@
 package main
 
 import (
-	"errors"
-	"io"
-	"net"
 	"net/url"
-	"strconv"
 
 	"code.google.com/p/go.net/proxy"
 
@@ -53,106 +49,3 @@ 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)
-}
diff --git a/obfs4proxy/proxy_socks4.go b/obfs4proxy/proxy_socks4.go
new file mode 100644
index 0000000..9d6bd4d
--- /dev/null
+++ b/obfs4proxy/proxy_socks4.go
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 2014, Yawning Angel <yawning at torproject dot org>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *  * Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ *
+ *  * Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This is inspired by go.net/proxy/socks5.go:
+ *
+ * Copyright 2011 The Go Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the LICENSE file.
+ */
+
+package main
+
+import (
+	"errors"
+	"fmt"
+	"io"
+	"net"
+	"net/url"
+	"strconv"
+
+	"code.google.com/p/go.net/proxy"
+)
+
+// socks4Proxy is a SOCKS4 proxy.
+type socks4Proxy struct {
+	hostPort string
+	username string
+	forward  proxy.Dialer
+}
+
+const (
+	socks4Version        = 0x04
+	socks4CommandConnect = 0x01
+	socks4Null           = 0x00
+	socks4ReplyVersion   = 0x00
+
+	socks4Granted                = 0x5a
+	socks4Rejected               = 0x5b
+	socks4RejectedIdentdFailed   = 0x5c
+	socks4RejectedIdentdMismatch = 0x5d
+)
+
+func newSOCKS4(uri *url.URL, forward proxy.Dialer) (proxy.Dialer, error) {
+	s := new(socks4Proxy)
+	s.hostPort = uri.Host
+	s.forward = forward
+	if uri.User != nil {
+		s.username = uri.User.Username()
+	}
+	return s, nil
+}
+
+func (s *socks4Proxy) 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, fmt.Errorf("proxy error: %s", socks4ErrorToString(resp[1]))
+	}
+
+	return c, nil
+}
+
+func socks4ErrorToString(code byte) string {
+	switch code {
+	case socks4Rejected:
+		return "request rejected or failed"
+	case socks4RejectedIdentdFailed:
+		return "request rejected becasue SOCKS server cannot connect to identd on the client"
+	case socks4RejectedIdentdMismatch:
+		return "request rejected because the client program and identd report different user-ids"
+	default:
+		return fmt.Sprintf("unknown failure code %x", code)
+	}
+}
+
+func init() {
+	// Despite the scheme name, this really is SOCKS4.
+	proxy.RegisterDialerType("socks4a", newSOCKS4)
+}
-- 
GitLab