commit d433318f1635b9d14b29236df7556cb42378932b Author: David Fifield david@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://%5B::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%22%7D, + {"http://127.0.0.1:8080/", "http://127.0.0.1:8080/%22%7D, + {"http://127.0.0.1:8080/path", "http://127.0.0.1:8080/path%22%7D, + {"http://%5B::1%5D:8080", "http://%5B::1%5D:8080%22%7D, + {"http://%5B::1%5D:8080/", "http://%5B::1%5D:8080/%22%7D, + {"http://%5B::1%5D:8080/path", "http://%5B::1%5D:8080/path%22%7D, + {"http://localhost:8080", "http://localhost:8080%22%7D, + {"http://localhost:8080/", "http://localhost:8080/%22%7D, + {"http://localhost:8080/path", "http://localhost:8080/path%22%7D, + {"http://user@localhost:8080", "http://user@localhost:8080%22%7D, + {"http://user:password@localhost:8080", "http://user:password@localhost:8080%22%7D, + {"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.... // +// Pluggable Transport through SOCKS proxy: +// https://gitweb.torproject.org/torspec.git/tree/proposals/232-pluggable-trans... +// // 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 }
tor-commits@lists.torproject.org