[tor-commits] [goptlib/master] Add support for TOR_PT_PROXY.

dcf at torproject.org dcf at torproject.org
Sun Jun 28 03:00:59 UTC 2015


commit d433318f1635b9d14b29236df7556cb42378932b
Author: David Fifield <david at bamsoftware.com>
Date:   Wed Jun 24 13:25:06 2015 -0700

    Add support for TOR_PT_PROXY.
    
    https://trac.torproject.org/projects/tor/ticket/12125
    
    Adds two new functions:
    	ProxyError
    	ProxyDone
    and one new member to ClientInfo:
    	ProxyURL *url.URL
---
 examples/dummy-client/dummy-client.go |    5 +++
 proxy_test.go                         |   80 +++++++++++++++++++++++++++++++++
 pt.go                                 |   66 ++++++++++++++++++++++++++-
 3 files changed, 150 insertions(+), 1 deletion(-)

diff --git a/examples/dummy-client/dummy-client.go b/examples/dummy-client/dummy-client.go
index cbb2d38..5d99b0c 100644
--- a/examples/dummy-client/dummy-client.go
+++ b/examples/dummy-client/dummy-client.go
@@ -88,6 +88,11 @@ func main() {
 		os.Exit(1)
 	}
 
+	if ptInfo.ProxyURL != nil {
+		pt.ProxyError("proxy is not supported")
+		os.Exit(1)
+	}
+
 	listeners := make([]net.Listener, 0)
 	for _, methodName := range ptInfo.MethodNames {
 		switch methodName {
diff --git a/proxy_test.go b/proxy_test.go
new file mode 100644
index 0000000..c7a113b
--- /dev/null
+++ b/proxy_test.go
@@ -0,0 +1,80 @@
+package pt
+
+import (
+	"os"
+	"testing"
+)
+
+func TestGetProxyURL(t *testing.T) {
+	badTests := [...]string{
+		"bogus",
+		"http:",
+		"://127.0.0.1",
+		"//127.0.0.1",
+		"http:127.0.0.1",
+		"://[::1]",
+		"//[::1]",
+		"http:[::1]",
+		"://localhost",
+		"//localhost",
+		"http:localhost",
+		// No port in these.
+		"http://127.0.0.1",
+		"socks4a://127.0.0.1",
+		"socks5://127.0.0.1",
+		"http://127.0.0.1:",
+		"http://[::1]",
+		"http://localhost",
+		"unknown://localhost/whatever",
+		// No host in these.
+		"http://:8080",
+		"socks4a://:1080",
+		"socks5://:1080",
+	}
+	goodTests := [...]struct {
+		input, expected string
+	}{
+		{"http://127.0.0.1:8080", "http://127.0.0.1:8080"},
+		{"http://127.0.0.1:8080/", "http://127.0.0.1:8080/"},
+		{"http://127.0.0.1:8080/path", "http://127.0.0.1:8080/path"},
+		{"http://[::1]:8080", "http://[::1]:8080"},
+		{"http://[::1]:8080/", "http://[::1]:8080/"},
+		{"http://[::1]:8080/path", "http://[::1]:8080/path"},
+		{"http://localhost:8080", "http://localhost:8080"},
+		{"http://localhost:8080/", "http://localhost:8080/"},
+		{"http://localhost:8080/path", "http://localhost:8080/path"},
+		{"http://user@localhost:8080", "http://user@localhost:8080"},
+		{"http://user:password@localhost:8080", "http://user:password@localhost:8080"},
+		{"socks5://localhost:1080", "socks5://localhost:1080"},
+		{"socks4a://localhost:1080", "socks4a://localhost:1080"},
+		{"unknown://localhost:9999/whatever", "unknown://localhost:9999/whatever"},
+	}
+
+	os.Clearenv()
+	u, err := getProxyURL()
+	if err != nil {
+		t.Errorf("empty environment unexpectedly returned an error: %s", err)
+	}
+	if u != nil {
+		t.Errorf("empty environment returned %q", u)
+	}
+
+	for _, input := range badTests {
+		os.Setenv("TOR_PT_PROXY", input)
+		u, err = getProxyURL()
+		if err == nil {
+			t.Errorf("TOR_PT_PROXY=%q unexpectedly succeeded and returned %q", input, u)
+		}
+	}
+
+	for _, test := range goodTests {
+		os.Setenv("TOR_PT_PROXY", test.input)
+		u, err := getProxyURL()
+		if err != nil {
+			t.Errorf("TOR_PT_PROXY=%q unexpectedly returned an error: %s", test.input, err)
+		}
+		if u == nil || u.String() != test.expected {
+			t.Errorf("TOR_PT_PROXY=%q → %q (expected %q)", test.input, u, test.expected)
+		}
+	}
+}
diff --git a/pt.go b/pt.go
index d2e7dc1..fc2141e 100644
--- a/pt.go
+++ b/pt.go
@@ -39,6 +39,12 @@
 // 		if err != nil {
 // 			os.Exit(1)
 // 		}
+// 		if ptInfo.ProxyURL != nil {
+// 			// you need to interpret the proxy URL yourself
+// 			// call pt.ProxyDone instead if it's a type you understand
+// 			pt.ProxyError("proxy %s is not supported")
+// 			os.Exit(1)
+// 		}
 // 		for _, methodName := range ptInfo.MethodNames {
 // 			switch methodName {
 // 			case "foo":
@@ -119,6 +125,9 @@
 // Extended ORPort Authentication:
 // https://gitweb.torproject.org/torspec.git/tree/proposals/217-ext-orport-auth.txt.
 //
+// Pluggable Transport through SOCKS proxy:
+// https://gitweb.torproject.org/torspec.git/tree/proposals/232-pluggable-transports-through-proxy.txt
+//
 // The package implements a SOCKS4a server sufficient for a Tor client transport
 // plugin.
 //
@@ -135,6 +144,7 @@ import (
 	"fmt"
 	"io"
 	"net"
+	"net/url"
 	"os"
 	"strconv"
 	"strings"
@@ -286,6 +296,12 @@ func SmethodError(methodName, msg string) error {
 	return doError("SMETHOD-ERROR", methodName, msg)
 }
 
+// Emit a PROXY-ERROR line with explanation text. Returns a representation of
+// the error.
+func ProxyError(msg string) error {
+	return doError("PROXY-ERROR %s\n", msg)
+}
+
 // Emit a CMETHOD line. socks must be "socks4" or "socks5". Call this once for
 // each listening client SOCKS port.
 func Cmethod(name string, socks string, addr net.Addr) {
@@ -326,6 +342,11 @@ func SmethodsDone() {
 	line("SMETHODS", "DONE")
 }
 
+// Emit a PROXY DONE line. Call this after parsing ClientInfo.ProxyURL.
+func ProxyDone() {
+	fmt.Fprintf(Stdout, "PROXY DONE\n")
+}
+
 // Get a pluggable transports version offered by Tor and understood by us, if
 // any. The only version we understand is "1". This function reads the
 // environment variable TOR_PT_MANAGED_TRANSPORT_VER.
@@ -370,10 +391,48 @@ func getClientTransports(star []string) ([]string, error) {
 	return strings.Split(clientTransports, ","), nil
 }
 
+// Get the upstream proxy URL. Returns nil if no proxy is requested. The
+// function ensures that the Scheme and Host fields are set; i.e., that the URL
+// is absolute. It additionally checks that the Host field contains both a host
+// and a port part. This function reads the environment variable TOR_PT_PROXY.
+//
+// This function doesn't check that the scheme is one of Tor's supported proxy
+// schemes; that is, one of "http", "socks5", or "socks4a". The caller must be
+// able to handle any returned scheme (which may be by calling ProxyError if
+// it doesn't know how to handle the scheme).
+func getProxyURL() (*url.URL, error) {
+	rawurl := os.Getenv("TOR_PT_PROXY")
+	if rawurl == "" {
+		return nil, nil
+	}
+	u, err := url.Parse(rawurl)
+	if err != nil {
+		return nil, err
+	}
+	if u.Scheme == "" {
+		return nil, fmt.Errorf("missing scheme")
+	}
+	if u.Host == "" {
+		return nil, fmt.Errorf("missing authority")
+	}
+	host, port, err := net.SplitHostPort(u.Host)
+	if err != nil {
+		return nil, err
+	}
+	if host == "" {
+		return nil, fmt.Errorf("missing host")
+	}
+	if port == "" {
+		return nil, fmt.Errorf("missing port")
+	}
+	return u, nil
+}
+
 // This structure is returned by ClientSetup. It consists of a list of method
-// names.
+// names and the upstream proxy URL, if any.
 type ClientInfo struct {
 	MethodNames []string
+	ProxyURL    *url.URL
 }
 
 // Check the client pluggable transports environment, emitting an error message
@@ -401,6 +460,11 @@ func ClientSetup(star []string) (info ClientInfo, err error) {
 		return
 	}
 
+	info.ProxyURL, err = getProxyURL()
+	if err != nil {
+		return
+	}
+
 	return info, nil
 }
 



More information about the tor-commits mailing list