pax_global_header00006660000000000000000000000064125436623260014523gustar00rootroot0000000000000052 comment=50b39b746c6ff34bf31977b658848d876ee84fbf 0.5/000077500000000000000000000000001254366232600113715ustar00rootroot000000000000000.5/.gitignore000066400000000000000000000001101254366232600133510ustar00rootroot00000000000000/examples/dummy-client/dummy-client /examples/dummy-server/dummy-server 0.5/COPYING000066400000000000000000000156101254366232600124270ustar00rootroot00000000000000Creative Commons Legal Code CC0 1.0 Universal CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER. Statement of Purpose The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work"). Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others. For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights. 1. Copyright and Related Rights. A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following: i. the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work; ii. moral rights retained by the original author(s) and/or performer(s); iii. publicity and privacy rights pertaining to a person's image or likeness depicted in a Work; iv. rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below; v. rights protecting the extraction, dissemination, use and reuse of data in a Work; vi. database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and vii. other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof. 2. Waiver. To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose. 3. Public License Fallback. Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose. 4. Limitations and Disclaimers. a. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document. b. Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law. c. Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work. d. Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work. 0.5/README000066400000000000000000000022621254366232600122530ustar00rootroot00000000000000goptlib is a library for writing Tor pluggable transports in Go. https://gitweb.torproject.org/torspec.git/tree/pt-spec.txt https://gitweb.torproject.org/torspec.git/tree/proposals/196-transport-control-ports.txt https://gitweb.torproject.org/torspec.git/tree/proposals/217-ext-orport-auth.txt To download a copy of the library into $GOPATH: go get git.torproject.org/pluggable-transports/goptlib.git See the included example programs for examples of how to use the library. To build them, enter their directory and run "go build". examples/dummy-client/dummy-client.go examples/dummy-server/dummy-server.go The recommended way to start writing a new transport plugin is to copy dummy-client or dummy-server and make changes to it. There is browseable documentation here: https://godoc.org/git.torproject.org/pluggable-transports/goptlib.git Report bugs to the tor-dev@lists.torproject.org mailing list or to the bug tracker at https://trac.torproject.org/projects/tor. To the extent possible under law, the authors have dedicated all copyright and related and neighboring rights to this software to the public domain worldwide. This software is distributed without any warranty. See COPYING. 0.5/args.go000066400000000000000000000123051254366232600126550ustar00rootroot00000000000000package pt import ( "bytes" "fmt" "sort" "strings" ) // Key–value mappings for the representation of client and server options. // Args maps a string key to a list of values. It is similar to url.Values. type Args map[string][]string // Get the first value associated with the given key. If there are any values // associated with the key, the value return has the value and ok is set to // true. If there are no values for the given key, value is "" and ok is false. // If you need access to multiple values, use the map directly. func (args Args) Get(key string) (value string, ok bool) { if args == nil { return "", false } vals, ok := args[key] if !ok || len(vals) == 0 { return "", false } return vals[0], true } // Append value to the list of values for key. func (args Args) Add(key, value string) { args[key] = append(args[key], value) } // Return the index of the next unescaped byte in s that is in the term set, or // else the length of the string if no terminators appear. Additionally return // the unescaped string up to the returned index. func indexUnescaped(s string, term []byte) (int, string, error) { var i int unesc := make([]byte, 0) for i = 0; i < len(s); i++ { b := s[i] // A terminator byte? if bytes.IndexByte(term, b) != -1 { break } if b == '\\' { i++ if i >= len(s) { return 0, "", fmt.Errorf("nothing following final escape in %q", s) } b = s[i] } unesc = append(unesc, b) } return i, string(unesc), nil } // Parse a name–value mapping as from an encoded SOCKS username/password. // // "If any [k=v] items are provided, they are configuration parameters for the // proxy: Tor should separate them with semicolons ... If a key or value value // must contain [an equals sign or] a semicolon or a backslash, it is escaped // with a backslash." func parseClientParameters(s string) (args Args, err error) { args = make(Args) if len(s) == 0 { return } i := 0 for { var key, value string var offset, begin int begin = i // Read the key. offset, key, err = indexUnescaped(s[i:], []byte{'=', ';'}) if err != nil { return } i += offset // End of string or no equals sign? if i >= len(s) || s[i] != '=' { err = fmt.Errorf("no equals sign in %q", s[begin:i]) return } // Skip the equals sign. i++ // Read the value. offset, value, err = indexUnescaped(s[i:], []byte{';'}) if err != nil { return } i += offset if len(key) == 0 { err = fmt.Errorf("empty key in %q", s[begin:i]) return } args.Add(key, value) if i >= len(s) { break } // Skip the semicolon. i++ } return args, nil } // Parse a transport–name–value mapping as from TOR_PT_SERVER_TRANSPORT_OPTIONS. // // " is a k=v string value with options that are to be passed to the // transport. Colons, semicolons, equal signs and backslashes must be escaped // with a backslash." // Example: trebuchet:secret=nou;trebuchet:cache=/tmp/cache;ballista:secret=yes func parseServerTransportOptions(s string) (opts map[string]Args, err error) { opts = make(map[string]Args) if len(s) == 0 { return } i := 0 for { var methodName, key, value string var offset, begin int begin = i // Read the method name. offset, methodName, err = indexUnescaped(s[i:], []byte{':', '=', ';'}) if err != nil { return } i += offset // End of string or no colon? if i >= len(s) || s[i] != ':' { err = fmt.Errorf("no colon in %q", s[begin:i]) return } // Skip the colon. i++ // Read the key. offset, key, err = indexUnescaped(s[i:], []byte{'=', ';'}) if err != nil { return } i += offset // End of string or no equals sign? if i >= len(s) || s[i] != '=' { err = fmt.Errorf("no equals sign in %q", s[begin:i]) return } // Skip the equals sign. i++ // Read the value. offset, value, err = indexUnescaped(s[i:], []byte{';'}) if err != nil { return } i += offset if len(methodName) == 0 { err = fmt.Errorf("empty method name in %q", s[begin:i]) return } if len(key) == 0 { err = fmt.Errorf("empty key in %q", s[begin:i]) return } if opts[methodName] == nil { opts[methodName] = make(Args) } opts[methodName].Add(key, value) if i >= len(s) { break } // Skip the semicolon. i++ } return opts, nil } // Escape backslashes and all the bytes that are in set. func backslashEscape(s string, set []byte) string { var buf bytes.Buffer for _, b := range []byte(s) { if b == '\\' || bytes.IndexByte(set, b) != -1 { buf.WriteByte('\\') } buf.WriteByte(b) } return buf.String() } // Encode a name–value mapping so that it is suitable to go in the ARGS option // of an SMETHOD line. The output is sorted by key. The "ARGS:" prefix is not // added. // // "Equal signs and commas [and backslashes] must be escaped with a backslash." func encodeSmethodArgs(args Args) string { if args == nil { return "" } keys := make([]string, 0, len(args)) for key := range args { keys = append(keys, key) } sort.Strings(keys) escape := func(s string) string { return backslashEscape(s, []byte{'=', ','}) } var pairs []string for _, key := range keys { for _, value := range args[key] { pairs = append(pairs, escape(key)+"="+escape(value)) } } return strings.Join(pairs, ",") } 0.5/args_test.go000066400000000000000000000153511254366232600137200ustar00rootroot00000000000000package pt import ( "testing" ) func stringSlicesEqual(a, b []string) bool { if len(a) != len(b) { return false } for i := range a { if a[i] != b[i] { return false } } return true } func argsEqual(a, b Args) bool { for k, av := range a { bv := b[k] if !stringSlicesEqual(av, bv) { return false } } for k, bv := range b { av := a[k] if !stringSlicesEqual(av, bv) { return false } } return true } func TestArgsGet(t *testing.T) { args := Args{ "a": []string{}, "b": []string{"value"}, "c": []string{"v1", "v2", "v3"}, } var uninit Args var v string var ok bool // Get on nil map should be the same as Get on empty map. v, ok = uninit.Get("a") if !(v == "" && !ok) { t.Errorf("unexpected result from Get on nil Args: %q %v", v, ok) } v, ok = args.Get("a") if ok { t.Errorf("Unexpected Get success for %q", "a") } if v != "" { t.Errorf("Get failure returned other than %q: %q", "", v) } v, ok = args.Get("b") if !ok { t.Errorf("Unexpected Get failure for %q", "b") } if v != "value" { t.Errorf("Get(%q) → %q (expected %q)", "b", v, "value") } v, ok = args.Get("c") if !ok { t.Errorf("Unexpected Get failure for %q", "c") } if v != "v1" { t.Errorf("Get(%q) → %q (expected %q)", "c", v, "v1") } v, ok = args.Get("d") if ok { t.Errorf("Unexpected Get success for %q", "d") } } func TestArgsAdd(t *testing.T) { args := make(Args) expected := Args{} if !argsEqual(args, expected) { t.Fatalf("%q != %q", args, expected) } args.Add("k1", "v1") expected = Args{"k1": []string{"v1"}} if !argsEqual(args, expected) { t.Fatalf("%q != %q", args, expected) } args.Add("k2", "v2") expected = Args{"k1": []string{"v1"}, "k2": []string{"v2"}} if !argsEqual(args, expected) { t.Fatalf("%q != %q", args, expected) } args.Add("k1", "v3") expected = Args{"k1": []string{"v1", "v3"}, "k2": []string{"v2"}} if !argsEqual(args, expected) { t.Fatalf("%q != %q", args, expected) } } func TestParseClientParameters(t *testing.T) { badTests := [...]string{ "key", "key\\", "=value", "==value", "==key=value", "key=value\\", "a=b;key=value\\", "a;b=c", ";", "key=value;", ";key=value", "key\\=value", } goodTests := [...]struct { input string expected Args }{ { "", Args{}, }, { "key=", Args{"key": []string{""}}, }, { "key==", Args{"key": []string{"="}}, }, { "key=value", Args{"key": []string{"value"}}, }, { "a=b=c", Args{"a": []string{"b=c"}}, }, { "key=a\nb", Args{"key": []string{"a\nb"}}, }, { "key=value\\;", Args{"key": []string{"value;"}}, }, { "key=\"value\"", Args{"key": []string{"\"value\""}}, }, { "key=\"\"value\"\"", Args{"key": []string{"\"\"value\"\""}}, }, { "\"key=value\"", Args{"\"key": []string{"value\""}}, }, { "key=value;key=value", Args{"key": []string{"value", "value"}}, }, { "key=value1;key=value2", Args{"key": []string{"value1", "value2"}}, }, { "key1=value1;key2=value2;key1=value3", Args{"key1": []string{"value1", "value3"}, "key2": []string{"value2"}}, }, { "\\;=\\;;\\\\=\\;", Args{";": []string{";"}, "\\": []string{";"}}, }, { "a\\=b=c", Args{"a=b": []string{"c"}}, }, { "shared-secret=rahasia;secrets-file=/tmp/blob", Args{"shared-secret": []string{"rahasia"}, "secrets-file": []string{"/tmp/blob"}}, }, { "rocks=20;height=5.6", Args{"rocks": []string{"20"}, "height": []string{"5.6"}}, }, } for _, input := range badTests { _, err := parseClientParameters(input) if err == nil { t.Errorf("%q unexpectedly succeeded", input) } } for _, test := range goodTests { args, err := parseClientParameters(test.input) if err != nil { t.Errorf("%q unexpectedly returned an error: %s", test.input, err) } if !argsEqual(args, test.expected) { t.Errorf("%q → %q (expected %q)", test.input, args, test.expected) } } } func optsEqual(a, b map[string]Args) bool { for k, av := range a { bv, ok := b[k] if !ok || !argsEqual(av, bv) { return false } } for k, bv := range b { av, ok := a[k] if !ok || !argsEqual(av, bv) { return false } } return true } func TestParseServerTransportOptions(t *testing.T) { badTests := [...]string{ "t\\", ":=", "t:=", ":k=", ":=v", "t:=v", "t:=v", "t:k\\", "t:k=v;", "abc", "t:", "key=value", "=value", "t:k=v\\", "t1:k=v;t2:k=v\\", "t:=key=value", "t:==key=value", "t:;key=value", "t:key\\=value", } goodTests := [...]struct { input string expected map[string]Args }{ { "", map[string]Args{}, }, { "t:k=v", map[string]Args{ "t": {"k": []string{"v"}}, }, }, { "t1:k=v1;t2:k=v2;t1:k=v3", map[string]Args{ "t1": {"k": []string{"v1", "v3"}}, "t2": {"k": []string{"v2"}}, }, }, { "t\\:1:k=v;t\\=2:k=v;t\\;3:k=v;t\\\\4:k=v", map[string]Args{ "t:1": {"k": []string{"v"}}, "t=2": {"k": []string{"v"}}, "t;3": {"k": []string{"v"}}, "t\\4": {"k": []string{"v"}}, }, }, { "t:k\\:1=v;t:k\\=2=v;t:k\\;3=v;t:k\\\\4=v", map[string]Args{ "t": { "k:1": []string{"v"}, "k=2": []string{"v"}, "k;3": []string{"v"}, "k\\4": []string{"v"}, }, }, }, { "t:k=v\\:1;t:k=v\\=2;t:k=v\\;3;t:k=v\\\\4", map[string]Args{ "t": {"k": []string{"v:1", "v=2", "v;3", "v\\4"}}, }, }, { "trebuchet:secret=nou;trebuchet:cache=/tmp/cache;ballista:secret=yes", map[string]Args{ "trebuchet": {"secret": []string{"nou"}, "cache": []string{"/tmp/cache"}}, "ballista": {"secret": []string{"yes"}}, }, }, } for _, input := range badTests { _, err := parseServerTransportOptions(input) if err == nil { t.Errorf("%q unexpectedly succeeded", input) } } for _, test := range goodTests { opts, err := parseServerTransportOptions(test.input) if err != nil { t.Errorf("%q unexpectedly returned an error: %s", test.input, err) } if !optsEqual(opts, test.expected) { t.Errorf("%q → %q (expected %q)", test.input, opts, test.expected) } } } func TestEncodeSmethodArgs(t *testing.T) { tests := [...]struct { args Args expected string }{ { nil, "", }, { Args{}, "", }, { Args{"j": []string{"v1", "v2", "v3"}, "k": []string{"v1", "v2", "v3"}}, "j=v1,j=v2,j=v3,k=v1,k=v2,k=v3", }, { Args{"=,\\": []string{"=", ",", "\\"}}, "\\=\\,\\\\=\\=,\\=\\,\\\\=\\,,\\=\\,\\\\=\\\\", }, { Args{"secret": []string{"yes"}}, "secret=yes", }, { Args{"secret": []string{"nou"}, "cache": []string{"/tmp/cache"}}, "cache=/tmp/cache,secret=nou", }, } for _, test := range tests { encoded := encodeSmethodArgs(test.args) if encoded != test.expected { t.Errorf("%q → %q (expected %q)", test.args, encoded, test.expected) } } } 0.5/examples/000077500000000000000000000000001254366232600132075ustar00rootroot000000000000000.5/examples/dummy-client/000077500000000000000000000000001254366232600156165ustar00rootroot000000000000000.5/examples/dummy-client/dummy-client.go000066400000000000000000000052311254366232600205550ustar00rootroot00000000000000// Dummy no-op pluggable transport client. Works only as a managed proxy. // // Usage (in torrc): // UseBridges 1 // Bridge dummy X.X.X.X:YYYY // ClientTransportPlugin dummy exec dummy-client // // Because this transport doesn't do anything to the traffic, you can use the // ORPort of any ordinary bridge (or relay that has DirPort set) in the bridge // line; it doesn't have to declare support for the dummy transport. package main import ( "io" "net" "os" "os/signal" "sync" "syscall" ) import "git.torproject.org/pluggable-transports/goptlib.git" var ptInfo pt.ClientInfo // When a connection handler starts, +1 is written to this channel; when it // ends, -1 is written. var handlerChan = make(chan int) func copyLoop(a, b net.Conn) { var wg sync.WaitGroup wg.Add(2) go func() { io.Copy(b, a) wg.Done() }() go func() { io.Copy(a, b) wg.Done() }() wg.Wait() } func handler(conn *pt.SocksConn) error { handlerChan <- 1 defer func() { handlerChan <- -1 }() defer conn.Close() remote, err := net.Dial("tcp", conn.Req.Target) if err != nil { conn.Reject() return err } defer remote.Close() err = conn.Grant(remote.RemoteAddr().(*net.TCPAddr)) if err != nil { return err } copyLoop(conn, remote) return nil } func acceptLoop(ln *pt.SocksListener) error { defer ln.Close() for { conn, err := ln.AcceptSocks() if err != nil { if e, ok := err.(net.Error); ok && e.Temporary() { continue } return err } go handler(conn) } } func main() { var err error ptInfo, err = pt.ClientSetup([]string{"dummy"}) if err != nil { 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 { case "dummy": ln, err := pt.ListenSocks("tcp", "127.0.0.1:0") if err != nil { pt.CmethodError(methodName, err.Error()) break } go acceptLoop(ln) pt.Cmethod(methodName, ln.Version(), ln.Addr()) listeners = append(listeners, ln) default: pt.CmethodError(methodName, "no such method") } } pt.CmethodsDone() var numHandlers int = 0 var sig os.Signal sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) // wait for first signal sig = nil for sig == nil { select { case n := <-handlerChan: numHandlers += n case sig = <-sigChan: } } for _, ln := range listeners { ln.Close() } if sig == syscall.SIGTERM { return } // wait for second signal or no more handlers sig = nil for sig == nil && numHandlers != 0 { select { case n := <-handlerChan: numHandlers += n case sig = <-sigChan: } } } 0.5/examples/dummy-server/000077500000000000000000000000001254366232600156465ustar00rootroot000000000000000.5/examples/dummy-server/dummy-server.go000066400000000000000000000046551254366232600206460ustar00rootroot00000000000000// Dummy no-op pluggable transport server. Works only as a managed proxy. // // Usage (in torrc): // BridgeRelay 1 // ORPort 9001 // ExtORPort 6669 // ServerTransportPlugin dummy exec dummy-server // // Because the dummy transport doesn't do anything to the traffic, you can // connect to it with any ordinary Tor client; you don't have to use // dummy-client. package main import ( "io" "net" "os" "os/signal" "sync" "syscall" ) import "git.torproject.org/pluggable-transports/goptlib.git" var ptInfo pt.ServerInfo // When a connection handler starts, +1 is written to this channel; when it // ends, -1 is written. var handlerChan = make(chan int) func copyLoop(a, b net.Conn) { var wg sync.WaitGroup wg.Add(2) go func() { io.Copy(b, a) wg.Done() }() go func() { io.Copy(a, b) wg.Done() }() wg.Wait() } func handler(conn net.Conn) error { defer conn.Close() handlerChan <- 1 defer func() { handlerChan <- -1 }() or, err := pt.DialOr(&ptInfo, conn.RemoteAddr().String(), "dummy") if err != nil { return err } defer or.Close() copyLoop(conn, or) return nil } func acceptLoop(ln net.Listener) error { defer ln.Close() for { conn, err := ln.Accept() if err != nil { if e, ok := err.(net.Error); ok && e.Temporary() { continue } return err } go handler(conn) } } func main() { var err error ptInfo, err = pt.ServerSetup([]string{"dummy"}) if err != nil { os.Exit(1) } listeners := make([]net.Listener, 0) for _, bindaddr := range ptInfo.Bindaddrs { switch bindaddr.MethodName { case "dummy": ln, err := net.ListenTCP("tcp", bindaddr.Addr) if err != nil { pt.SmethodError(bindaddr.MethodName, err.Error()) break } go acceptLoop(ln) pt.Smethod(bindaddr.MethodName, ln.Addr()) listeners = append(listeners, ln) default: pt.SmethodError(bindaddr.MethodName, "no such method") } } pt.SmethodsDone() var numHandlers int = 0 var sig os.Signal sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) // wait for first signal sig = nil for sig == nil { select { case n := <-handlerChan: numHandlers += n case sig = <-sigChan: } } for _, ln := range listeners { ln.Close() } if sig == syscall.SIGTERM { return } // wait for second signal or no more handlers sig = nil for sig == nil && numHandlers != 0 { select { case n := <-handlerChan: numHandlers += n case sig = <-sigChan: } } } 0.5/proxy_test.go000066400000000000000000000042251254366232600141430ustar00rootroot00000000000000package 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) } } } 0.5/pt.go000066400000000000000000000633621254366232600123550ustar00rootroot00000000000000// Package pt implements the Tor pluggable transports specification. // // Sample client usage: // var ptInfo pt.ClientInfo // ... // func handler(conn *pt.SocksConn) error { // defer conn.Close() // remote, err := net.Dial("tcp", conn.Req.Target) // if err != nil { // conn.Reject() // return err // } // defer remote.Close() // err = conn.Grant(remote.RemoteAddr().(*net.TCPAddr)) // if err != nil { // return err // } // // do something with conn and remote. // return nil // } // func acceptLoop(ln *pt.SocksListener) error { // defer ln.Close() // for { // conn, err := ln.AcceptSocks() // if err != nil { // if e, ok := err.(net.Error); ok && e.Temporary() { // continue // } // return err // } // go handler(conn) // } // return nil // } // ... // func main() { // var err error // ptInfo, err = pt.ClientSetup([]string{"foo"}) // 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": // ln, err := pt.ListenSocks("tcp", "127.0.0.1:0") // if err != nil { // pt.CmethodError(methodName, err.Error()) // break // } // go acceptLoop(ln) // pt.Cmethod(methodName, ln.Version(), ln.Addr()) // default: // pt.CmethodError(methodName, "no such method") // } // } // pt.CmethodsDone() // } // // Sample server usage: // var ptInfo pt.ServerInfo // ... // func handler(conn net.Conn) error { // defer conn.Close() // or, err := pt.DialOr(&ptInfo, conn.RemoteAddr().String(), "foo") // if err != nil { // return // } // defer or.Close() // // do something with or and conn // return nil // } // func acceptLoop(ln net.Listener) error { // defer ln.Close() // for { // conn, err := ln.Accept() // if err != nil { // if e, ok := err.(net.Error); ok && e.Temporary() { // continue // } // return err // } // go handler(conn) // } // return nil // } // ... // func main() { // var err error // ptInfo, err = pt.ServerSetup([]string{"foo"}) // if err != nil { // os.Exit(1) // } // for _, bindaddr := range ptInfo.Bindaddrs { // switch bindaddr.MethodName { // case "foo": // ln, err := net.ListenTCP("tcp", bindaddr.Addr) // if err != nil { // pt.SmethodError(bindaddr.MethodName, err.Error()) // break // } // go acceptLoop(ln) // pt.Smethod(bindaddr.MethodName, ln.Addr()) // default: // pt.SmethodError(bindaddr.MethodName, "no such method") // } // } // pt.SmethodsDone() // } // // Some additional care is needed to handle SIGINT and shutdown properly. See // the example programs dummy-client and dummy-server. // // Tor pluggable transports specification: // https://gitweb.torproject.org/torspec.git/tree/pt-spec.txt. // // Extended ORPort: // https://gitweb.torproject.org/torspec.git/tree/proposals/196-transport-control-ports.txt. // // 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. // // http://ftp.icm.edu.pl/packages/socks/socks4/SOCKS4.protocol package pt import ( "bytes" "crypto/hmac" "crypto/rand" "crypto/sha256" "crypto/subtle" "encoding/binary" "fmt" "io" "net" "net/url" "os" "strconv" "strings" "time" ) // This type wraps a Write method and calls Sync after each Write. type syncWriter struct { *os.File } // Call File.Write and then Sync. An error is returned if either operation // returns an error. func (w syncWriter) Write(p []byte) (n int, err error) { n, err = w.File.Write(p) if err != nil { return } err = w.Sync() return } // Writer to which pluggable transports negotiation messages are written. It // defaults to a Writer that writes to os.Stdout and calls Sync after each // write. // // You may, for example, log pluggable transports messages by defining a Writer // that logs what is written to it: // type logWriteWrapper struct { // io.Writer // } // // func (w logWriteWrapper) Write(p []byte) (int, error) { // log.Print(string(p)) // return w.Writer.Write(p) // } // and then redefining Stdout: // pt.Stdout = logWriteWrapper{pt.Stdout} var Stdout io.Writer = syncWriter{os.Stdout} // Represents an error that can happen during negotiation, for example // ENV-ERROR. When an error occurs, we print it to stdout and also pass it up // the return chain. type ptErr struct { Keyword string Args []string } // Implements the error interface. func (err *ptErr) Error() string { return formatline(err.Keyword, err.Args...) } func getenv(key string) string { return os.Getenv(key) } // Returns an ENV-ERROR if the environment variable isn't set. func getenvRequired(key string) (string, error) { value := os.Getenv(key) if value == "" { return "", envError(fmt.Sprintf("no %s environment variable", key)) } return value, nil } // Returns true iff keyword contains only bytes allowed in a PT→Tor output line // keyword. // ::= func keywordIsSafe(keyword string) bool { for _, b := range []byte(keyword) { if b >= '0' && b <= '9' { continue } if b >= 'A' && b <= 'Z' { continue } if b >= 'a' && b <= 'z' { continue } if b == '-' || b == '_' { continue } return false } return true } // Returns true iff arg contains only bytes allowed in a PT→Tor output line arg. // ::= func argIsSafe(arg string) bool { for _, b := range []byte(arg) { if b >= '\x80' || b == '\x00' || b == '\n' { return false } } return true } func formatline(keyword string, v ...string) string { var buf bytes.Buffer if !keywordIsSafe(keyword) { panic(fmt.Sprintf("keyword %q contains forbidden bytes", keyword)) } buf.WriteString(keyword) for _, x := range v { if !argIsSafe(x) { panic(fmt.Sprintf("arg %q contains forbidden bytes", x)) } buf.WriteString(" " + x) } return buf.String() } // Print a pluggable transports protocol line to Stdout. The line consists of a // keyword followed by any number of space-separated arg strings. Panics if // there are forbidden bytes in the keyword or the args (pt-spec.txt 2.2.1). func line(keyword string, v ...string) { fmt.Fprintln(Stdout, formatline(keyword, v...)) } // Emit and return the given error as a ptErr. func doError(keyword string, v ...string) *ptErr { line(keyword, v...) return &ptErr{keyword, v} } // Emit an ENV-ERROR line with explanation text. Returns a representation of the // error. func envError(msg string) error { return doError("ENV-ERROR", msg) } // Emit a VERSION-ERROR line with explanation text. Returns a representation of // the error. func versionError(msg string) error { return doError("VERSION-ERROR", msg) } // Emit a CMETHOD-ERROR line with explanation text. Returns a representation of // the error. func CmethodError(methodName, msg string) error { return doError("CMETHOD-ERROR", methodName, msg) } // Emit an SMETHOD-ERROR line with explanation text. Returns a representation of // the error. 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) { line("CMETHOD", name, socks, addr.String()) } // Emit a CMETHODS DONE line. Call this after opening all client listeners. func CmethodsDone() { line("CMETHODS", "DONE") } // Emit an SMETHOD line. Call this once for each listening server port. func Smethod(name string, addr net.Addr) { line("SMETHOD", name, addr.String()) } // Emit an SMETHOD line with an ARGS option. args is a name–value mapping that // will be added to the server's extrainfo document. // // This is an example of how to check for a required option: // secret, ok := bindaddr.Options.Get("shared-secret") // if ok { // args := pt.Args{} // args.Add("shared-secret", secret) // pt.SmethodArgs(bindaddr.MethodName, ln.Addr(), args) // } else { // pt.SmethodError(bindaddr.MethodName, "need a shared-secret option") // } // Or, if you just want to echo back the options provided by Tor from the // TransportServerOptions configuration, // pt.SmethodArgs(bindaddr.MethodName, ln.Addr(), bindaddr.Options) func SmethodArgs(name string, addr net.Addr, args Args) { line("SMETHOD", name, addr.String(), "ARGS:"+encodeSmethodArgs(args)) } // Emit an SMETHODS DONE line. Call this after opening all server listeners. 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. func getManagedTransportVer() (string, error) { const transportVersion = "1" managedTransportVer, err := getenvRequired("TOR_PT_MANAGED_TRANSPORT_VER") if err != nil { return "", err } for _, offered := range strings.Split(managedTransportVer, ",") { if offered == transportVersion { return offered, nil } } return "", versionError("no-version") } // Return the directory name in the TOR_PT_STATE_LOCATION environment variable, // creating it if it doesn't exist. Returns non-nil error if // TOR_PT_STATE_LOCATION is not set or if there is an error creating the // directory. func MakeStateDir() (string, error) { dir, err := getenvRequired("TOR_PT_STATE_LOCATION") if err != nil { return "", err } err = os.MkdirAll(dir, 0700) return dir, err } // Get the intersection of the method names offered by Tor and those in // methodNames. This function reads the environment variable // TOR_PT_CLIENT_TRANSPORTS. func getClientTransports(star []string) ([]string, error) { clientTransports, err := getenvRequired("TOR_PT_CLIENT_TRANSPORTS") if err != nil { return nil, err } if clientTransports == "*" { return star, nil } 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 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 // and returning a non-nil error if any error is encountered. star is the list // of method names to use in case "*" is requested by Tor. Returns a ClientInfo // struct. // // If your program needs to know whether to call ClientSetup or ServerSetup // (i.e., if the same program can be run as either a client or a server), check // whether the TOR_PT_CLIENT_TRANSPORTS environment variable is set: // if os.Getenv("TOR_PT_CLIENT_TRANSPORTS") != "" { // // Client mode; call pt.ClientSetup. // } else { // // Server mode; call pt.ServerSetup. // } func ClientSetup(star []string) (info ClientInfo, err error) { ver, err := getManagedTransportVer() if err != nil { return } line("VERSION", ver) info.MethodNames, err = getClientTransports(star) if err != nil { return } info.ProxyURL, err = getProxyURL() if err != nil { return } return info, nil } // A combination of a method name and an address, as extracted from // TOR_PT_SERVER_BINDADDR. type Bindaddr struct { MethodName string Addr *net.TCPAddr // Options from TOR_PT_SERVER_TRANSPORT_OPTIONS that pertain to this // transport. Options Args } func parsePort(portStr string) (int, error) { port, err := strconv.ParseUint(portStr, 10, 16) return int(port), err } // Resolve an address string into a net.TCPAddr. We are a bit more strict than // net.ResolveTCPAddr; we don't allow an empty host or port, and the host part // must be a literal IP address. func resolveAddr(addrStr string) (*net.TCPAddr, error) { ipStr, portStr, err := net.SplitHostPort(addrStr) if err != nil { // Before the fixing of bug #7011, tor doesn't put brackets around IPv6 // addresses. Split after the last colon, assuming it is a port // separator, and try adding the brackets. parts := strings.Split(addrStr, ":") if len(parts) <= 2 { return nil, err } addrStr := "[" + strings.Join(parts[:len(parts)-1], ":") + "]:" + parts[len(parts)-1] ipStr, portStr, err = net.SplitHostPort(addrStr) } if err != nil { return nil, err } if ipStr == "" { return nil, net.InvalidAddrError(fmt.Sprintf("address string %q lacks a host part", addrStr)) } if portStr == "" { return nil, net.InvalidAddrError(fmt.Sprintf("address string %q lacks a port part", addrStr)) } ip := net.ParseIP(ipStr) if ip == nil { return nil, net.InvalidAddrError(fmt.Sprintf("not an IP string: %q", ipStr)) } port, err := parsePort(portStr) if err != nil { return nil, err } return &net.TCPAddr{IP: ip, Port: port}, nil } // Return a new slice, the members of which are those members of addrs having a // MethodName in methodNames. func filterBindaddrs(addrs []Bindaddr, methodNames []string) []Bindaddr { var result []Bindaddr for _, ba := range addrs { for _, methodName := range methodNames { if ba.MethodName == methodName { result = append(result, ba) break } } } return result } // Return an array of Bindaddrs, being the contents of TOR_PT_SERVER_BINDADDR // with keys filtered by TOR_PT_SERVER_TRANSPORTS. If TOR_PT_SERVER_TRANSPORTS // is "*", then keys are filtered by the entries in star instead. // Transport-specific options from TOR_PT_SERVER_TRANSPORT_OPTIONS are assigned // to the Options member. func getServerBindaddrs(star []string) ([]Bindaddr, error) { var result []Bindaddr // Parse the list of server transport options. serverTransportOptions := getenv("TOR_PT_SERVER_TRANSPORT_OPTIONS") optionsMap, err := parseServerTransportOptions(serverTransportOptions) if err != nil { return nil, envError(fmt.Sprintf("TOR_PT_SERVER_TRANSPORT_OPTIONS: %q: %s", serverTransportOptions, err.Error())) } // Get the list of all requested bindaddrs. serverBindaddr, err := getenvRequired("TOR_PT_SERVER_BINDADDR") if err != nil { return nil, err } for _, spec := range strings.Split(serverBindaddr, ",") { var bindaddr Bindaddr parts := strings.SplitN(spec, "-", 2) if len(parts) != 2 { return nil, envError(fmt.Sprintf("TOR_PT_SERVER_BINDADDR: %q: doesn't contain \"-\"", spec)) } bindaddr.MethodName = parts[0] addr, err := resolveAddr(parts[1]) if err != nil { return nil, envError(fmt.Sprintf("TOR_PT_SERVER_BINDADDR: %q: %s", spec, err.Error())) } bindaddr.Addr = addr bindaddr.Options = optionsMap[bindaddr.MethodName] result = append(result, bindaddr) } // Filter by TOR_PT_SERVER_TRANSPORTS. serverTransports, err := getenvRequired("TOR_PT_SERVER_TRANSPORTS") if err != nil { return nil, err } if serverTransports == "*" { result = filterBindaddrs(result, star) } else { result = filterBindaddrs(result, strings.Split(serverTransports, ",")) } return result, nil } func readAuthCookie(f io.Reader) ([]byte, error) { authCookieHeader := []byte("! Extended ORPort Auth Cookie !\x0a") buf := make([]byte, 64) n, err := io.ReadFull(f, buf) if err != nil { return nil, err } // Check that the file ends here. n, err = f.Read(make([]byte, 1)) if n != 0 { return nil, fmt.Errorf("file is longer than 64 bytes") } else if err != io.EOF { return nil, fmt.Errorf("did not find EOF at end of file") } header := buf[0:32] cookie := buf[32:64] if subtle.ConstantTimeCompare(header, authCookieHeader) != 1 { return nil, fmt.Errorf("missing auth cookie header") } return cookie, nil } // Read and validate the contents of an auth cookie file. Returns the 32-byte // cookie. See section 4.2.1.2 of pt-spec.txt. func readAuthCookieFile(filename string) ([]byte, error) { f, err := os.Open(filename) if err != nil { return nil, err } defer f.Close() return readAuthCookie(f) } // This structure is returned by ServerSetup. It consists of a list of // Bindaddrs, an address for the ORPort, an address for the extended ORPort (if // any), and an authentication cookie (if any). type ServerInfo struct { Bindaddrs []Bindaddr OrAddr *net.TCPAddr ExtendedOrAddr *net.TCPAddr AuthCookiePath string } // Check the server pluggable transports environment, emitting an error message // and returning a non-nil error if any error is encountered. star is the list // of method names to use in case "*" is requested by Tor. Resolves the various // requested bind addresses, the server ORPort and extended ORPort, and reads // the auth cookie file. Returns a ServerInfo struct. // // If your program needs to know whether to call ClientSetup or ServerSetup // (i.e., if the same program can be run as either a client or a server), check // whether the TOR_PT_CLIENT_TRANSPORTS environment variable is set: // if os.Getenv("TOR_PT_CLIENT_TRANSPORTS") != "" { // // Client mode; call pt.ClientSetup. // } else { // // Server mode; call pt.ServerSetup. // } func ServerSetup(star []string) (info ServerInfo, err error) { ver, err := getManagedTransportVer() if err != nil { return } line("VERSION", ver) info.Bindaddrs, err = getServerBindaddrs(star) if err != nil { return } orPort := getenv("TOR_PT_ORPORT") if orPort != "" { info.OrAddr, err = resolveAddr(orPort) if err != nil { err = envError(fmt.Sprintf("cannot resolve TOR_PT_ORPORT %q: %s", orPort, err.Error())) return } } extendedOrPort := getenv("TOR_PT_EXTENDED_SERVER_PORT") if extendedOrPort != "" { info.ExtendedOrAddr, err = resolveAddr(extendedOrPort) if err != nil { err = envError(fmt.Sprintf("cannot resolve TOR_PT_EXTENDED_SERVER_PORT %q: %s", extendedOrPort, err.Error())) return } } info.AuthCookiePath = getenv("TOR_PT_AUTH_COOKIE_FILE") // Need either OrAddr or ExtendedOrAddr. if info.OrAddr == nil && (info.ExtendedOrAddr == nil || info.AuthCookiePath == "") { err = envError("need TOR_PT_ORPORT or TOR_PT_EXTENDED_SERVER_PORT environment variable") return } return info, nil } // See 217-ext-orport-auth.txt section 4.2.1.3. func computeServerHash(authCookie, clientNonce, serverNonce []byte) []byte { h := hmac.New(sha256.New, authCookie) io.WriteString(h, "ExtORPort authentication server-to-client hash") h.Write(clientNonce) h.Write(serverNonce) return h.Sum([]byte{}) } // See 217-ext-orport-auth.txt section 4.2.1.3. func computeClientHash(authCookie, clientNonce, serverNonce []byte) []byte { h := hmac.New(sha256.New, authCookie) io.WriteString(h, "ExtORPort authentication client-to-server hash") h.Write(clientNonce) h.Write(serverNonce) return h.Sum([]byte{}) } func extOrPortAuthenticate(s io.ReadWriter, info *ServerInfo) error { // Read auth types. 217-ext-orport-auth.txt section 4.1. var authTypes [256]bool var count int for count = 0; count < 256; count++ { buf := make([]byte, 1) _, err := io.ReadFull(s, buf) if err != nil { return err } b := buf[0] if b == 0 { break } authTypes[b] = true } if count >= 256 { return fmt.Errorf("read 256 auth types without seeing \\x00") } // We support only type 1, SAFE_COOKIE. if !authTypes[1] { return fmt.Errorf("server didn't offer auth type 1") } _, err := s.Write([]byte{1}) if err != nil { return err } clientNonce := make([]byte, 32) clientHash := make([]byte, 32) serverNonce := make([]byte, 32) serverHash := make([]byte, 32) _, err = io.ReadFull(rand.Reader, clientNonce) if err != nil { return err } _, err = s.Write(clientNonce) if err != nil { return err } _, err = io.ReadFull(s, serverHash) if err != nil { return err } _, err = io.ReadFull(s, serverNonce) if err != nil { return err } // Work around tor bug #15240 where the auth cookie is generated after // pluggable transports are launched, leading to a stale cookie getting // cached forever if it is only read once as part of ServerSetup. authCookie, err := readAuthCookieFile(info.AuthCookiePath) if err != nil { return fmt.Errorf("error reading TOR_PT_AUTH_COOKIE_FILE %q: %s", info.AuthCookiePath, err.Error()) } expectedServerHash := computeServerHash(authCookie, clientNonce, serverNonce) if subtle.ConstantTimeCompare(serverHash, expectedServerHash) != 1 { return fmt.Errorf("mismatch in server hash") } clientHash = computeClientHash(authCookie, clientNonce, serverNonce) _, err = s.Write(clientHash) if err != nil { return err } status := make([]byte, 1) _, err = io.ReadFull(s, status) if err != nil { return err } if status[0] != 1 { return fmt.Errorf("server rejected authentication") } return nil } // See section 3.1 of 196-transport-control-ports.txt. const ( extOrCmdDone = 0x0000 extOrCmdUserAddr = 0x0001 extOrCmdTransport = 0x0002 extOrCmdOkay = 0x1000 extOrCmdDeny = 0x1001 ) func extOrPortSendCommand(s io.Writer, cmd uint16, body []byte) error { var buf bytes.Buffer if len(body) > 65535 { return fmt.Errorf("body length %d exceeds maximum of 65535", len(body)) } err := binary.Write(&buf, binary.BigEndian, cmd) if err != nil { return err } err = binary.Write(&buf, binary.BigEndian, uint16(len(body))) if err != nil { return err } err = binary.Write(&buf, binary.BigEndian, body) if err != nil { return err } _, err = s.Write(buf.Bytes()) if err != nil { return err } return nil } // Send a USERADDR command on s. See section 3.1.2.1 of // 196-transport-control-ports.txt. func extOrPortSendUserAddr(s io.Writer, addr string) error { return extOrPortSendCommand(s, extOrCmdUserAddr, []byte(addr)) } // Send a TRANSPORT command on s. See section 3.1.2.2 of // 196-transport-control-ports.txt. func extOrPortSendTransport(s io.Writer, methodName string) error { return extOrPortSendCommand(s, extOrCmdTransport, []byte(methodName)) } // Send a DONE command on s. See section 3.1 of 196-transport-control-ports.txt. func extOrPortSendDone(s io.Writer) error { return extOrPortSendCommand(s, extOrCmdDone, []byte{}) } func extOrPortRecvCommand(s io.Reader) (cmd uint16, body []byte, err error) { var bodyLen uint16 data := make([]byte, 4) _, err = io.ReadFull(s, data) if err != nil { return } buf := bytes.NewBuffer(data) err = binary.Read(buf, binary.BigEndian, &cmd) if err != nil { return } err = binary.Read(buf, binary.BigEndian, &bodyLen) if err != nil { return } body = make([]byte, bodyLen) _, err = io.ReadFull(s, body) if err != nil { return } return cmd, body, err } // Send USERADDR and TRANSPORT commands followed by a DONE command. Wait for an // OKAY or DENY response command from the server. If addr or methodName is "", // the corresponding command is not sent. Returns nil if and only if OKAY is // received. func extOrPortSetup(s io.ReadWriter, addr, methodName string) error { var err error if addr != "" { err = extOrPortSendUserAddr(s, addr) if err != nil { return err } } if methodName != "" { err = extOrPortSendTransport(s, methodName) if err != nil { return err } } err = extOrPortSendDone(s) if err != nil { return err } cmd, _, err := extOrPortRecvCommand(s) if err != nil { return err } if cmd == extOrCmdDeny { return fmt.Errorf("server returned DENY after our USERADDR and DONE") } else if cmd != extOrCmdOkay { return fmt.Errorf("server returned unknown command 0x%04x after our USERADDR and DONE", cmd) } return nil } // Dial info.ExtendedOrAddr if defined, or else info.OrAddr, and return an open // *net.TCPConn. If connecting to the extended OR port, extended OR port // authentication à la 217-ext-orport-auth.txt is done before returning; an // error is returned if authentication fails. // // The addr and methodName arguments are put in USERADDR and TRANSPORT ExtOrPort // commands, respectively. If either is "", the corresponding command is not // sent. func DialOr(info *ServerInfo, addr, methodName string) (*net.TCPConn, error) { if info.ExtendedOrAddr == nil || info.AuthCookiePath == "" { return net.DialTCP("tcp", nil, info.OrAddr) } s, err := net.DialTCP("tcp", nil, info.ExtendedOrAddr) if err != nil { return nil, err } s.SetDeadline(time.Now().Add(5 * time.Second)) err = extOrPortAuthenticate(s, info) if err != nil { s.Close() return nil, err } err = extOrPortSetup(s, addr, methodName) if err != nil { s.Close() return nil, err } s.SetDeadline(time.Time{}) return s, nil } 0.5/pt_test.go000066400000000000000000000507631254366232600134150ustar00rootroot00000000000000package pt import ( "bytes" "encoding/binary" "fmt" "io" "io/ioutil" "net" "os" "path" "sort" "testing" ) func TestKeywordIsSafe(t *testing.T) { tests := [...]struct { keyword string expected bool }{ {"", true}, {"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_", true}, {"CMETHOD", true}, {"CMETHOD:", false}, {"a b c", false}, {"CMETHOD\x7f", false}, {"CMETHOD\x80", false}, {"CMETHOD\x81", false}, {"CMETHOD\xff", false}, {"CMÉTHOD", false}, } for _, input := range tests { isSafe := keywordIsSafe(input.keyword) if isSafe != input.expected { t.Errorf("keywordIsSafe(%q) → %v (expected %v)", input.keyword, isSafe, input.expected) } } } func TestArgIsSafe(t *testing.T) { tests := [...]struct { arg string expected bool }{ {"", true}, {"abc", true}, {"127.0.0.1:8000", true}, {"étude", false}, {"a\nb", false}, {"a\\b", true}, {"ab\\", true}, {"ab\\\n", false}, {"ab\n\\", false}, {"abc\x7f", true}, {"abc\x80", false}, {"abc\x81", false}, {"abc\xff", false}, {"abc\xff", false}, {"var=GVsbG8\\=", true}, } for _, input := range tests { isSafe := argIsSafe(input.arg) if isSafe != input.expected { t.Errorf("argIsSafe(%q) → %v (expected %v)", input.arg, isSafe, input.expected) } } } func TestGetManagedTransportVer(t *testing.T) { badTests := [...]string{ "", "2", } goodTests := [...]struct { input, expected string }{ {"1", "1"}, {"1,1", "1"}, {"1,2", "1"}, {"2,1", "1"}, } Stdout = ioutil.Discard os.Clearenv() _, err := getManagedTransportVer() if err == nil { t.Errorf("empty environment unexpectedly succeeded") } for _, input := range badTests { os.Setenv("TOR_PT_MANAGED_TRANSPORT_VER", input) _, err := getManagedTransportVer() if err == nil { t.Errorf("TOR_PT_MANAGED_TRANSPORT_VER=%q unexpectedly succeeded", input) } } for _, test := range goodTests { os.Setenv("TOR_PT_MANAGED_TRANSPORT_VER", test.input) output, err := getManagedTransportVer() if err != nil { t.Errorf("TOR_PT_MANAGED_TRANSPORT_VER=%q unexpectedly returned an error: %s", test.input, err) } if output != test.expected { t.Errorf("TOR_PT_MANAGED_TRANSPORT_VER=%q → %q (expected %q)", test.input, output, test.expected) } } } // return true iff the two slices contain the same elements, possibly in a // different order. func stringSetsEqual(a, b []string) bool { ac := make([]string, len(a)) bc := make([]string, len(b)) copy(ac, a) copy(bc, b) sort.Strings(ac) sort.Strings(bc) if len(ac) != len(bc) { return false } for i := 0; i < len(ac); i++ { if ac[i] != bc[i] { return false } } return true } func tcpAddrsEqual(a, b *net.TCPAddr) bool { return a.IP.Equal(b.IP) && a.Port == b.Port } func TestGetClientTransports(t *testing.T) { tests := [...]struct { ptServerClientTransports string star []string expected []string }{ { "*", []string{}, []string{}, }, { "*", []string{"alpha", "beta", "gamma"}, []string{"alpha", "beta", "gamma"}, }, { "alpha,beta,gamma", []string{"alpha", "beta", "gamma"}, []string{"alpha", "beta", "gamma"}, }, { "alpha,beta", []string{"alpha", "beta", "gamma"}, []string{"alpha", "beta"}, }, { "alpha", []string{"alpha", "beta", "gamma"}, []string{"alpha"}, }, { "alpha,beta,gamma", []string{}, []string{"alpha", "beta", "gamma"}, }, { "alpha,beta", []string{}, []string{"alpha", "beta"}, }, { "alpha", []string{}, []string{"alpha"}, }, // my reading of pt-spec.txt says that "*" has special meaning // only when it is the entirety of the environment variable. { "alpha,*,gamma", []string{"alpha", "beta", "gamma"}, []string{"alpha", "*", "gamma"}, }, { "alpha", []string{"beta"}, []string{"alpha"}, }, } Stdout = ioutil.Discard os.Clearenv() _, err := getClientTransports([]string{"alpha", "beta", "gamma"}) if err == nil { t.Errorf("empty environment unexpectedly succeeded") } for _, test := range tests { os.Setenv("TOR_PT_CLIENT_TRANSPORTS", test.ptServerClientTransports) output, err := getClientTransports(test.star) if err != nil { t.Errorf("TOR_PT_CLIENT_TRANSPORTS=%q unexpectedly returned an error: %s", test.ptServerClientTransports, err) } if !stringSetsEqual(output, test.expected) { t.Errorf("TOR_PT_CLIENT_TRANSPORTS=%q %q → %q (expected %q)", test.ptServerClientTransports, test.star, output, test.expected) } } } func TestResolveAddr(t *testing.T) { badTests := [...]string{ "", "1.2.3.4", "1.2.3.4:", "9999", ":9999", "[1:2::3:4]", "[1:2::3:4]:", "[1::2::3:4]", "1:2::3:4::9999", "1:2:3:4::9999", "localhost:9999", "[localhost]:9999", "1.2.3.4:http", "1.2.3.4:0x50", "1.2.3.4:-65456", "1.2.3.4:65536", "1.2.3.4:80\x00", "1.2.3.4:80 ", " 1.2.3.4:80", "1.2.3.4 : 80", } goodTests := [...]struct { input string expected net.TCPAddr }{ {"1.2.3.4:9999", net.TCPAddr{IP: net.ParseIP("1.2.3.4"), Port: 9999}}, {"[1:2::3:4]:9999", net.TCPAddr{IP: net.ParseIP("1:2::3:4"), Port: 9999}}, {"1:2::3:4:9999", net.TCPAddr{IP: net.ParseIP("1:2::3:4"), Port: 9999}}, } for _, input := range badTests { output, err := resolveAddr(input) if err == nil { t.Errorf("%q unexpectedly succeeded: %q", input, output) } } for _, test := range goodTests { output, err := resolveAddr(test.input) if err != nil { t.Errorf("%q unexpectedly returned an error: %s", test.input, err) } if !tcpAddrsEqual(output, &test.expected) { t.Errorf("%q → %q (expected %q)", test.input, output, test.expected) } } } func bindaddrSliceContains(s []Bindaddr, v Bindaddr) bool { for _, sv := range s { if sv.MethodName == v.MethodName && tcpAddrsEqual(sv.Addr, v.Addr) { return true } } return false } func bindaddrSetsEqual(a, b []Bindaddr) bool { for _, v := range a { if !bindaddrSliceContains(b, v) { return false } } for _, v := range b { if !bindaddrSliceContains(a, v) { return false } } return true } func TestGetServerBindaddrs(t *testing.T) { badTests := [...]struct { ptServerBindaddr string ptServerTransports string ptServerTransportOptions string star []string }{ // bad TOR_PT_SERVER_BINDADDR { "alpha", "alpha", "", []string{"alpha", "beta", "gamma"}, }, { "alpha-1.2.3.4", "alpha", "", []string{"alpha", "beta", "gamma"}, }, // missing TOR_PT_SERVER_TRANSPORTS { "alpha-1.2.3.4:1111", "", "alpha:key=value", []string{"alpha"}, }, // bad TOR_PT_SERVER_TRANSPORT_OPTIONS { "alpha-1.2.3.4:1111", "alpha", "key=value", []string{"alpha"}, }, } goodTests := [...]struct { ptServerBindaddr string ptServerTransports string ptServerTransportOptions string star []string expected []Bindaddr }{ { "alpha-1.2.3.4:1111,beta-[1:2::3:4]:2222", "alpha,beta,gamma", "alpha:k1=v1,beta:k2=v2,gamma:k3=v3", []string{"alpha", "beta"}, []Bindaddr{ {"alpha", &net.TCPAddr{IP: net.ParseIP("1.2.3.4"), Port: 1111}, Args{"k1": []string{"v1"}}}, {"beta", &net.TCPAddr{IP: net.ParseIP("1:2::3:4"), Port: 2222}, Args{"k2": []string{"v2"}}}, }, }, { "alpha-1.2.3.4:1111", "xxx", "", []string{"alpha", "beta", "gamma"}, []Bindaddr{}, }, { "alpha-1.2.3.4:1111", "alpha,beta,gamma", "", []string{}, []Bindaddr{ {"alpha", &net.TCPAddr{IP: net.ParseIP("1.2.3.4"), Port: 1111}, Args{}}, }, }, { "alpha-1.2.3.4:1111,beta-[1:2::3:4]:2222", "*", "", []string{"alpha", "beta"}, []Bindaddr{ {"alpha", &net.TCPAddr{IP: net.ParseIP("1.2.3.4"), Port: 1111}, Args{}}, {"beta", &net.TCPAddr{IP: net.ParseIP("1:2::3:4"), Port: 2222}, Args{}}, }, }, { "alpha-1.2.3.4:1111,beta-[1:2::3:4]:2222", "*", "", []string{"alpha", "gamma"}, []Bindaddr{ {"alpha", &net.TCPAddr{IP: net.ParseIP("1.2.3.4"), Port: 1111}, Args{}}, }, }, { "trebuchet-127.0.0.1:1984,ballista-127.0.0.1:4891", "trebuchet,ballista", "trebuchet:secret=nou;trebuchet:cache=/tmp/cache;ballista:secret=yes", []string{"trebuchet", "ballista"}, []Bindaddr{ {"trebuchet", &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 1984}, Args{"secret": []string{"nou"}, "cache": []string{"/tmp/cache"}}}, {"ballista", &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 4891}, Args{"secret": []string{"yes"}}}, }, }, } Stdout = ioutil.Discard os.Clearenv() _, err := getServerBindaddrs([]string{"alpha", "beta", "gamma"}) if err == nil { t.Errorf("empty environment unexpectedly succeeded") } for _, test := range badTests { os.Setenv("TOR_PT_SERVER_BINDADDR", test.ptServerBindaddr) os.Setenv("TOR_PT_SERVER_TRANSPORTS", test.ptServerTransports) os.Setenv("TOR_PT_SERVER_TRANSPORT_OPTIONS", test.ptServerTransportOptions) _, err := getServerBindaddrs(test.star) if err == nil { t.Errorf("TOR_PT_SERVER_BINDADDR=%q TOR_PT_SERVER_TRANSPORTS=%q TOR_PT_SERVER_TRANSPORT_OPTIONS=%q %q unexpectedly succeeded", test.ptServerBindaddr, test.ptServerTransports, test.ptServerTransportOptions, test.star) } } for _, test := range goodTests { os.Setenv("TOR_PT_SERVER_BINDADDR", test.ptServerBindaddr) os.Setenv("TOR_PT_SERVER_TRANSPORTS", test.ptServerTransports) os.Setenv("TOR_PT_SERVER_TRANSPORT_OPTIONS", test.ptServerTransportOptions) output, err := getServerBindaddrs(test.star) if err != nil { t.Errorf("TOR_PT_SERVER_BINDADDR=%q TOR_PT_SERVER_TRANSPORTS=%q TOR_PT_SERVER_TRANSPORT_OPTIONS=%q %q unexpectedly returned an error: %s", test.ptServerBindaddr, test.ptServerTransports, test.ptServerTransportOptions, test.star, err) } if !bindaddrSetsEqual(output, test.expected) { t.Errorf("TOR_PT_SERVER_BINDADDR=%q TOR_PT_SERVER_TRANSPORTS=%q TOR_PT_SERVER_TRANSPORT_OPTIONS=%q %q → %q (expected %q)", test.ptServerBindaddr, test.ptServerTransports, test.ptServerTransportOptions, test.star, output, test.expected) } } } func TestReadAuthCookie(t *testing.T) { badTests := [...][]byte{ []byte(""), // bad header []byte("! Impostor ORPort Auth Cookie !\x0a0123456789ABCDEF0123456789ABCDEF"), // too short []byte("! Extended ORPort Auth Cookie !\x0a0123456789ABCDEF0123456789ABCDE"), // too long []byte("! Extended ORPort Auth Cookie !\x0a0123456789ABCDEF0123456789ABCDEFX"), } goodTests := [...][]byte{ []byte("! Extended ORPort Auth Cookie !\x0a0123456789ABCDEF0123456789ABCDEF"), } for _, input := range badTests { var buf bytes.Buffer buf.Write(input) _, err := readAuthCookie(&buf) if err == nil { t.Errorf("%q unexpectedly succeeded", input) } } for _, input := range goodTests { var buf bytes.Buffer buf.Write(input) cookie, err := readAuthCookie(&buf) if err != nil { t.Errorf("%q unexpectedly returned an error: %s", input, err) } if !bytes.Equal(cookie, input[32:64]) { t.Errorf("%q → %q (expected %q)", input, cookie, input[:32]) } } } func TestComputeServerHash(t *testing.T) { authCookie := make([]byte, 32) clientNonce := make([]byte, 32) serverNonce := make([]byte, 32) // hmac.new("\x00"*32, "ExtORPort authentication server-to-client hash" + "\x00"*64, hashlib.sha256).hexdigest() expected := []byte("\x9e\x22\x19\x19\x98\x2a\x84\xf7\x5f\xaf\x60\xef\x92\x69\x49\x79\x62\x68\xc9\x78\x33\xe0\x69\x60\xff\x26\x53\x69\xa9\x0f\xd6\xd8") hash := computeServerHash(authCookie, clientNonce, serverNonce) if !bytes.Equal(hash, expected) { t.Errorf("%x %x %x → %x (expected %x)", authCookie, clientNonce, serverNonce, hash, expected) } } func TestComputeClientHash(t *testing.T) { authCookie := make([]byte, 32) clientNonce := make([]byte, 32) serverNonce := make([]byte, 32) // hmac.new("\x00"*32, "ExtORPort authentication client-to-server hash" + "\x00"*64, hashlib.sha256).hexdigest() expected := []byte("\x0f\x36\x8b\x1b\xee\x24\xaa\xbc\x54\xa9\x11\x4c\xe0\x6c\x07\x0f\x3e\xd9\x9d\x0d\x36\x8f\x59\x9c\xcc\x6d\xfd\xc8\xbf\x45\x7a\x62") hash := computeClientHash(authCookie, clientNonce, serverNonce) if !bytes.Equal(hash, expected) { t.Errorf("%x %x %x → %x (expected %x)", authCookie, clientNonce, serverNonce, hash, expected) } } // Elide a byte slice in case it's really long. func fmtBytes(s []byte) string { if len(s) > 100 { return fmt.Sprintf("%q...(%d bytes)", s[:5], len(s)) } else { return fmt.Sprintf("%q", s) } } func TestExtOrSendCommand(t *testing.T) { badTests := [...]struct { cmd uint16 body []byte }{ {0x0, make([]byte, 65536)}, {0x1234, make([]byte, 65536)}, } longBody := [65535 + 2 + 2]byte{0x12, 0x34, 0xff, 0xff} goodTests := [...]struct { cmd uint16 body []byte expected []byte }{ {0x0, []byte(""), []byte("\x00\x00\x00\x00")}, {0x5, []byte(""), []byte("\x00\x05\x00\x00")}, {0xfffe, []byte(""), []byte("\xff\xfe\x00\x00")}, {0xffff, []byte(""), []byte("\xff\xff\x00\x00")}, {0x1234, []byte("hello"), []byte("\x12\x34\x00\x05hello")}, {0x1234, make([]byte, 65535), longBody[:]}, } for _, test := range badTests { var buf bytes.Buffer err := extOrPortSendCommand(&buf, test.cmd, test.body) if err == nil { t.Errorf("0x%04x %s unexpectedly succeeded", test.cmd, fmtBytes(test.body)) } } for _, test := range goodTests { var buf bytes.Buffer err := extOrPortSendCommand(&buf, test.cmd, test.body) if err != nil { t.Errorf("0x%04x %s unexpectedly returned an error: %s", test.cmd, fmtBytes(test.body), err) } p := make([]byte, 65535+2+2) n, err := buf.Read(p) if err != nil { t.Fatal(err) } output := p[:n] if !bytes.Equal(output, test.expected) { t.Errorf("0x%04x %s → %s (expected %s)", test.cmd, fmtBytes(test.body), fmtBytes(output), fmtBytes(test.expected)) } } } func TestExtOrSendUserAddr(t *testing.T) { addrs := [...]string{ "0.0.0.0:0", "1.2.3.4:9999", "255.255.255.255:65535", "[::]:0", "[ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255]:63335", } for _, addr := range addrs { var buf bytes.Buffer err := extOrPortSendUserAddr(&buf, addr) if err != nil { t.Errorf("%s unexpectedly returned an error: %s", addr, err) } var cmd, length uint16 binary.Read(&buf, binary.BigEndian, &cmd) if cmd != extOrCmdUserAddr { t.Errorf("%s → cmd 0x%04x (expected 0x%04x)", addr, cmd, extOrCmdUserAddr) } binary.Read(&buf, binary.BigEndian, &length) p := make([]byte, length+1) n, err := buf.Read(p) if n != int(length) { t.Errorf("%s said length %d but had at least %d", addr, length, n) } // test that parsing the address gives something equivalent to // parsing the original. inputAddr, err := resolveAddr(addr) if err != nil { t.Fatal(err) } outputAddr, err := resolveAddr(string(p[:n])) if err != nil { t.Fatal(err) } if !tcpAddrsEqual(inputAddr, outputAddr) { t.Errorf("%s → %s", addr, outputAddr) } } } func TestExtOrPortSendTransport(t *testing.T) { tests := [...]struct { methodName string expected []byte }{ {"", []byte("\x00\x02\x00\x00")}, {"a", []byte("\x00\x02\x00\x01a")}, {"alpha", []byte("\x00\x02\x00\x05alpha")}, } for _, test := range tests { var buf bytes.Buffer err := extOrPortSendTransport(&buf, test.methodName) if err != nil { t.Errorf("%q unexpectedly returned an error: %s", test.methodName, err) } p := make([]byte, 1024) n, err := buf.Read(p) if err != nil { t.Fatal(err) } output := p[:n] if !bytes.Equal(output, test.expected) { t.Errorf("%q → %s (expected %s)", test.methodName, fmtBytes(output), fmtBytes(test.expected)) } } } func TestExtOrPortSendDone(t *testing.T) { expected := []byte("\x00\x00\x00\x00") var buf bytes.Buffer err := extOrPortSendDone(&buf) if err != nil { t.Errorf("unexpectedly returned an error: %s", err) } p := make([]byte, 1024) n, err := buf.Read(p) if err != nil { t.Fatal(err) } output := p[:n] if !bytes.Equal(output, expected) { t.Errorf("→ %s (expected %s)", fmtBytes(output), fmtBytes(expected)) } } func TestExtOrPortRecvCommand(t *testing.T) { badTests := [...][]byte{ []byte(""), []byte("\x12"), []byte("\x12\x34"), []byte("\x12\x34\x00"), []byte("\x12\x34\x00\x01"), } goodTests := [...]struct { input []byte cmd uint16 body []byte leftover []byte }{ { []byte("\x12\x34\x00\x00"), 0x1234, []byte(""), []byte(""), }, { []byte("\x12\x34\x00\x00more"), 0x1234, []byte(""), []byte("more"), }, { []byte("\x12\x34\x00\x04body"), 0x1234, []byte("body"), []byte(""), }, { []byte("\x12\x34\x00\x04bodymore"), 0x1234, []byte("body"), []byte("more"), }, } for _, input := range badTests { var buf bytes.Buffer buf.Write(input) _, _, err := extOrPortRecvCommand(&buf) if err == nil { t.Errorf("%q unexpectedly succeeded", fmtBytes(input)) } } for _, test := range goodTests { var buf bytes.Buffer buf.Write(test.input) cmd, body, err := extOrPortRecvCommand(&buf) if err != nil { t.Errorf("%s unexpectedly returned an error: %s", fmtBytes(test.input), err) } if cmd != test.cmd { t.Errorf("%s → cmd 0x%04x (expected 0x%04x)", fmtBytes(test.input), cmd, test.cmd) } if !bytes.Equal(body, test.body) { t.Errorf("%s → body %s (expected %s)", fmtBytes(test.input), fmtBytes(body), fmtBytes(test.body)) } p := make([]byte, 1024) n, err := buf.Read(p) if err != nil && err != io.EOF { t.Fatal(err) } leftover := p[:n] if !bytes.Equal(leftover, test.leftover) { t.Errorf("%s → leftover %s (expected %s)", fmtBytes(test.input), fmtBytes(leftover), fmtBytes(test.leftover)) } } } // set up so that extOrPortSetup can write to one buffer and read from another. type mockSetupBuf struct { bytes.Buffer ReadBuf bytes.Buffer } func (buf *mockSetupBuf) Read(p []byte) (int, error) { n, err := buf.ReadBuf.Read(p) return n, err } func testExtOrPortSetupIndividual(t *testing.T, addr, methodName string) { var err error var buf mockSetupBuf // fake an OKAY response. err = extOrPortSendCommand(&buf.ReadBuf, extOrCmdOkay, []byte{}) if err != nil { t.Fatal(err) } err = extOrPortSetup(&buf, addr, methodName) if err != nil { t.Fatalf("error in extOrPortSetup: %s", err) } for { cmd, body, err := extOrPortRecvCommand(&buf.Buffer) if err != nil { t.Fatalf("error in extOrPortRecvCommand: %s", err) } if cmd == extOrCmdDone { break } if addr != "" && cmd == extOrCmdUserAddr { if string(body) != addr { t.Errorf("addr=%q methodName=%q got USERADDR with body %q (expected %q)", addr, methodName, body, addr) } continue } if methodName != "" && cmd == extOrCmdTransport { if string(body) != methodName { t.Errorf("addr=%q methodName=%q got TRANSPORT with body %q (expected %q)", addr, methodName, body, methodName) } continue } t.Errorf("addr=%q methodName=%q got unknown cmd %d body %q", addr, methodName, cmd, body) } } func TestExtOrPortSetup(t *testing.T) { const addr = "127.0.0.1:40000" const methodName = "alpha" testExtOrPortSetupIndividual(t, "", "") testExtOrPortSetupIndividual(t, addr, "") testExtOrPortSetupIndividual(t, "", methodName) testExtOrPortSetupIndividual(t, addr, methodName) } func TestMakeStateDir(t *testing.T) { os.Clearenv() // TOR_PT_STATE_LOCATION not set. _, err := MakeStateDir() if err == nil { t.Errorf("empty environment unexpectedly succeeded") } // Setup the scratch directory. tempDir, err := ioutil.TempDir("", "testMakeStateDir") if err != nil { t.Fatalf("ioutil.TempDir failed: %s", err) } defer os.RemoveAll(tempDir) goodTests := [...]string{ // Already existing directory. tempDir, // Nonexistent directory, parent exists. path.Join(tempDir, "parentExists"), // Nonexistent directory, parent doesn't exist. path.Join(tempDir, "missingParent", "parentMissing"), } for _, test := range goodTests { os.Setenv("TOR_PT_STATE_LOCATION", test) dir, err := MakeStateDir() if err != nil { t.Errorf("MakeStateDir unexpectedly failed: %s", err) } if dir != test { t.Errorf("MakeStateDir returned an unexpected path %s (expecting %s)", dir, test) } } // Name already exists, but is an ordinary file. tempFile := path.Join(tempDir, "file") f, err := os.Create(tempFile) if err != nil { t.Fatalf("os.Create failed: %s", err) } defer f.Close() os.Setenv("TOR_PT_STATE_LOCATION", tempFile) _, err = MakeStateDir() if err == nil { t.Errorf("MakeStateDir with a file unexpectedly succeeded") } // Directory name that cannot be created. (Subdir of a file) os.Setenv("TOR_PT_STATE_LOCATION", path.Join(tempFile, "subDir")) _, err = MakeStateDir() if err == nil { t.Errorf("MakeStateDir with a subdirectory of a file unexpectedly succeeded") } } 0.5/socks.go000066400000000000000000000144341254366232600130500ustar00rootroot00000000000000package pt import ( "bufio" "fmt" "io" "net" "time" ) const ( socksVersion = 0x04 socksCmdConnect = 0x01 socksResponseVersion = 0x00 socksRequestGranted = 0x5a socksRequestRejected = 0x5b ) // Put a sanity timeout on how long we wait for a SOCKS request. const socksRequestTimeout = 5 * time.Second // SocksRequest describes a SOCKS request. type SocksRequest struct { // The endpoint requested by the client as a "host:port" string. Target string // The userid string sent by the client. Username string // The parsed contents of Username as a key–value mapping. Args Args } // SocksConn encapsulates a net.Conn and information associated with a SOCKS request. type SocksConn struct { net.Conn Req SocksRequest } // Send a message to the proxy client that access to the given address is // granted. If the IP field inside addr is not an IPv4 address, the IP portion // of the response will be four zero bytes. func (conn *SocksConn) Grant(addr *net.TCPAddr) error { return sendSocks4aResponseGranted(conn, addr) } // Send a message to the proxy client that access was rejected or failed. func (conn *SocksConn) Reject() error { return sendSocks4aResponseRejected(conn) } // SocksListener wraps a net.Listener in order to read a SOCKS request on Accept. // // func handleConn(conn *pt.SocksConn) error { // defer conn.Close() // remote, err := net.Dial("tcp", conn.Req.Target) // if err != nil { // conn.Reject() // return err // } // defer remote.Close() // err = conn.Grant(remote.RemoteAddr().(*net.TCPAddr)) // if err != nil { // return err // } // // do something with conn and remote // return nil // } // ... // ln, err := pt.ListenSocks("tcp", "127.0.0.1:0") // if err != nil { // panic(err.Error()) // } // for { // conn, err := ln.AcceptSocks() // if err != nil { // log.Printf("accept error: %s", err) // if e, ok := err.(net.Error); ok && e.Temporary() { // continue // } // break // } // go handleConn(conn) // } type SocksListener struct { net.Listener } // Open a net.Listener according to network and laddr, and return it as a // SocksListener. func ListenSocks(network, laddr string) (*SocksListener, error) { ln, err := net.Listen(network, laddr) if err != nil { return nil, err } return NewSocksListener(ln), nil } // Create a new SocksListener wrapping the given net.Listener. func NewSocksListener(ln net.Listener) *SocksListener { return &SocksListener{ln} } // Accept is the same as AcceptSocks, except that it returns a generic net.Conn. // It is present for the sake of satisfying the net.Listener interface. func (ln *SocksListener) Accept() (net.Conn, error) { return ln.AcceptSocks() } // Call Accept on the wrapped net.Listener, do SOCKS negotiation, and return a // SocksConn. After accepting, you must call either conn.Grant or conn.Reject // (presumably after trying to connect to conn.Req.Target). // // Errors returned by AcceptSocks may be temporary (for example, EOF while // reading the request, or a badly formatted userid string), or permanent (e.g., // the underlying socket is closed). You can determine whether an error is // temporary and take appropriate action with a type conversion to net.Error. // For example: // // for { // conn, err := ln.AcceptSocks() // if err != nil { // if e, ok := err.(net.Error); ok && e.Temporary() { // log.Printf("temporary accept error; trying again: %s", err) // continue // } // log.Printf("permanent accept error; giving up: %s", err) // break // } // go handleConn(conn) // } func (ln *SocksListener) AcceptSocks() (*SocksConn, error) { retry: c, err := ln.Listener.Accept() if err != nil { return nil, err } conn := new(SocksConn) conn.Conn = c err = conn.SetDeadline(time.Now().Add(socksRequestTimeout)) if err != nil { conn.Close() goto retry } conn.Req, err = readSocks4aConnect(conn) if err != nil { conn.Close() goto retry } err = conn.SetDeadline(time.Time{}) if err != nil { conn.Close() goto retry } return conn, nil } // Returns "socks4", suitable to be included in a call to Cmethod. func (ln *SocksListener) Version() string { return "socks4" } // Read a SOCKS4a connect request. Returns a SocksRequest. func readSocks4aConnect(s io.Reader) (req SocksRequest, err error) { r := bufio.NewReader(s) var h [8]byte _, err = io.ReadFull(r, h[:]) if err != nil { return } if h[0] != socksVersion { err = fmt.Errorf("SOCKS header had version 0x%02x, not 0x%02x", h[0], socksVersion) return } if h[1] != socksCmdConnect { err = fmt.Errorf("SOCKS header had command 0x%02x, not 0x%02x", h[1], socksCmdConnect) return } var usernameBytes []byte usernameBytes, err = r.ReadBytes('\x00') if err != nil { return } req.Username = string(usernameBytes[:len(usernameBytes)-1]) req.Args, err = parseClientParameters(req.Username) if err != nil { return } var port int var host string port = int(h[2])<<8 | int(h[3])<<0 if h[4] == 0 && h[5] == 0 && h[6] == 0 && h[7] != 0 { var hostBytes []byte hostBytes, err = r.ReadBytes('\x00') if err != nil { return } host = string(hostBytes[:len(hostBytes)-1]) } else { host = net.IPv4(h[4], h[5], h[6], h[7]).String() } if r.Buffered() != 0 { err = fmt.Errorf("%d bytes left after SOCKS header", r.Buffered()) return } req.Target = fmt.Sprintf("%s:%d", host, port) return } // Send a SOCKS4a response with the given code and address. If the IP field // inside addr is not an IPv4 address, the IP portion of the response will be // four zero bytes. func sendSocks4aResponse(w io.Writer, code byte, addr *net.TCPAddr) error { var resp [8]byte resp[0] = socksResponseVersion resp[1] = code resp[2] = byte((addr.Port >> 8) & 0xff) resp[3] = byte((addr.Port >> 0) & 0xff) ipv4 := addr.IP.To4() if ipv4 != nil { resp[4] = ipv4[0] resp[5] = ipv4[1] resp[6] = ipv4[2] resp[7] = ipv4[3] } _, err := w.Write(resp[:]) return err } var emptyAddr = net.TCPAddr{IP: net.IPv4(0, 0, 0, 0), Port: 0} // Send a SOCKS4a response code 0x5a. func sendSocks4aResponseGranted(w io.Writer, addr *net.TCPAddr) error { return sendSocks4aResponse(w, socksRequestGranted, addr) } // Send a SOCKS4a response code 0x5b (with an all-zero address). func sendSocks4aResponseRejected(w io.Writer) error { return sendSocks4aResponse(w, socksRequestRejected, &emptyAddr) } 0.5/socks_test.go000066400000000000000000000155071254366232600141110ustar00rootroot00000000000000package pt import ( "bytes" "errors" "io" "net" "testing" "time" ) func TestReadSocks4aConnect(t *testing.T) { badTests := [...][]byte{ []byte(""), // missing userid []byte("\x04\x01\x12\x34\x01\x02\x03\x04"), // missing \x00 after userid []byte("\x04\x01\x12\x34\x01\x02\x03\x04key=value"), // missing hostname []byte("\x04\x01\x12\x34\x00\x00\x00\x01key=value\x00"), // missing \x00 after hostname []byte("\x04\x01\x12\x34\x00\x00\x00\x01key=value\x00hostname"), // bad name–value mapping []byte("\x04\x01\x12\x34\x00\x00\x00\x01userid\x00hostname\x00"), // bad version number []byte("\x03\x01\x12\x34\x01\x02\x03\x04\x00"), // BIND request []byte("\x04\x02\x12\x34\x01\x02\x03\x04\x00"), // SOCKS5 []byte("\x05\x01\x00"), } ipTests := [...]struct { input []byte addr net.TCPAddr userid string }{ { []byte("\x04\x01\x12\x34\x01\x02\x03\x04key=value\x00"), net.TCPAddr{IP: net.ParseIP("1.2.3.4"), Port: 0x1234}, "key=value", }, { []byte("\x04\x01\x12\x34\x01\x02\x03\x04\x00"), net.TCPAddr{IP: net.ParseIP("1.2.3.4"), Port: 0x1234}, "", }, } hostnameTests := [...]struct { input []byte target string userid string }{ { []byte("\x04\x01\x12\x34\x00\x00\x00\x01key=value\x00hostname\x00"), "hostname:4660", "key=value", }, { []byte("\x04\x01\x12\x34\x00\x00\x00\x01\x00hostname\x00"), "hostname:4660", "", }, { []byte("\x04\x01\x12\x34\x00\x00\x00\x01key=value\x00\x00"), ":4660", "key=value", }, { []byte("\x04\x01\x12\x34\x00\x00\x00\x01\x00\x00"), ":4660", "", }, } for _, input := range badTests { var buf bytes.Buffer buf.Write(input) _, err := readSocks4aConnect(&buf) if err == nil { t.Errorf("%q unexpectedly succeeded", input) } } for _, test := range ipTests { var buf bytes.Buffer buf.Write(test.input) req, err := readSocks4aConnect(&buf) if err != nil { t.Errorf("%q unexpectedly returned an error: %s", test.input, err) } addr, err := net.ResolveTCPAddr("tcp", req.Target) if err != nil { t.Errorf("%q → target %q: cannot resolve: %s", test.input, req.Target, err) } if !tcpAddrsEqual(addr, &test.addr) { t.Errorf("%q → address %s (expected %s)", test.input, req.Target, test.addr.String()) } if req.Username != test.userid { t.Errorf("%q → username %q (expected %q)", test.input, req.Username, test.userid) } if req.Args == nil { t.Errorf("%q → unexpected nil Args from username %q", test.input, req.Username) } } for _, test := range hostnameTests { var buf bytes.Buffer buf.Write(test.input) req, err := readSocks4aConnect(&buf) if err != nil { t.Errorf("%q unexpectedly returned an error: %s", test.input, err) } if req.Target != test.target { t.Errorf("%q → target %q (expected %q)", test.input, req.Target, test.target) } if req.Username != test.userid { t.Errorf("%q → username %q (expected %q)", test.input, req.Username, test.userid) } if req.Args == nil { t.Errorf("%q → unexpected nil Args from username %q", test.input, req.Username) } } } func TestSendSocks4aResponse(t *testing.T) { tests := [...]struct { code byte addr net.TCPAddr expected []byte }{ { socksRequestGranted, net.TCPAddr{IP: net.ParseIP("1.2.3.4"), Port: 0x1234}, []byte("\x00\x5a\x12\x34\x01\x02\x03\x04"), }, { socksRequestRejected, net.TCPAddr{IP: net.ParseIP("1:2::3:4"), Port: 0x1234}, []byte("\x00\x5b\x12\x34\x00\x00\x00\x00"), }, } for _, test := range tests { var buf bytes.Buffer err := sendSocks4aResponse(&buf, test.code, &test.addr) if err != nil { t.Errorf("0x%02x %s unexpectedly returned an error: %s", test.code, &test.addr, err) } p := make([]byte, 1024) n, err := buf.Read(p) if err != nil { t.Fatal(err) } output := p[:n] if !bytes.Equal(output, test.expected) { t.Errorf("0x%02x %s → %v (expected %v)", test.code, &test.addr, output, test.expected) } } } var fakeListenerDistinguishedError = errors.New("distinguished error") // fakeListener is a fake dummy net.Listener that returns the given net.Conn and // error the first time Accept is called. After the first call, it returns // (nil, fakeListenerDistinguishedError). type fakeListener struct { c net.Conn err error } func (ln *fakeListener) Accept() (net.Conn, error) { c := ln.c err := ln.err ln.c = nil ln.err = fakeListenerDistinguishedError return c, err } func (ln *fakeListener) Close() error { return nil } func (ln *fakeListener) Addr() net.Addr { return &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 0, Zone: ""} } // A trivial net.Error that lets you control whether it is considered Temporary. type netError struct { errString string temporary bool } func (e *netError) Error() string { return e.errString } func (e *netError) Temporary() bool { return e.temporary } func (e *netError) Timeout() bool { return false } // The purpose of ignoreDeadlineConn is to wrap net.Pipe so that the deadline // functions don't return an error ("net.Pipe does not support deadlines"). type ignoreDeadlineConn struct { net.Conn } func (c *ignoreDeadlineConn) SetDeadline(t time.Time) error { return nil } func (c *ignoreDeadlineConn) SetReadDeadline(t time.Time) error { return nil } func (c *ignoreDeadlineConn) SetWriteDeadline(t time.Time) error { return nil } func TestAcceptErrors(t *testing.T) { // Check that AcceptSocks accurately reflects net.Errors returned by the // underlying call to Accept. This is important for the handling of // Temporary and non-Temporary errors. The loop iterates over // non-net.Error, non-Temporary net.Error, and Temporary net.Error. for _, expectedErr := range []error{io.EOF, &netError{"non-temp", false}, &netError{"temp", true}} { ln := NewSocksListener(&fakeListener{nil, expectedErr}) _, err := ln.AcceptSocks() if expectedNerr, ok := expectedErr.(net.Error); ok { nerr, ok := err.(net.Error) if !ok { t.Errorf("AcceptSocks returned non-net.Error %v", nerr) } else { if expectedNerr.Temporary() != expectedNerr.Temporary() { t.Errorf("AcceptSocks did not keep Temporary status of net.Error: %v", nerr) } } } } c1, c2 := net.Pipe() go func() { // Bogus request: SOCKS 5 then EOF. c2.Write([]byte("\x05\x01\x00")) c2.Close() }() ln := NewSocksListener(&fakeListener{c: &ignoreDeadlineConn{c1}, err: nil}) _, err := ln.AcceptSocks() // The error in parsing the SOCKS request must be either silently // ignored, or else must be a Temporary net.Error. I.e., it must not be // the io.ErrUnexpectedEOF caused by the short request. if err == fakeListenerDistinguishedError { // Was silently ignored. } else if nerr, ok := err.(net.Error); ok { if !nerr.Temporary() { t.Errorf("AcceptSocks returned non-Temporary net.Error: %v", nerr) } } else { t.Errorf("AcceptSocks returned non-net.Error: %v", err) } }