pax_global_header00006660000000000000000000000064141204574410014514gustar00rootroot0000000000000052 comment=13b7b3552e1eef32e4d8a2a7813f22488f91dc09 v1.2.0/000077500000000000000000000000001412045744100117045ustar00rootroot00000000000000v1.2.0/.gitignore000066400000000000000000000001101412045744100136640ustar00rootroot00000000000000/examples/dummy-client/dummy-client /examples/dummy-server/dummy-server v1.2.0/COPYING000066400000000000000000000156101412045744100127420ustar00rootroot00000000000000Creative 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. v1.2.0/ChangeLog000066400000000000000000000040071412045744100134570ustar00rootroot00000000000000== v1.2.0 The default and development branch is now "main" rather than "master". The master branch will no longer be updated. https://lists.torproject.org/pipermail/anti-censorship-team/2021-May/000168.html If you have an existing clone of the master branch, run these commands to update it: git fetch origin git remote set-head origin -a git branch --move master main git branch --set-upstream-to=origin/main main Added a go.mod file. https://bugs.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/40065 == v1.1.0 Added the Log function. https://bugs.torproject.org/28940 == v1.0.0 Changed the tag naming scheme to work better with Go modules. https://github.com/golang/go/wiki/Modules#semantic-import-versioning == 0.7 Fixed the ProxyError function; previously it would always panic. Repeated transport names in TOR_PT_SERVER_BINDADDR now result in an ENV-ERROR. https://bugs.torproject.org/21261 == 0.6 Remove all support for the "*" transport specification. The argument to the ClientSetup and ServerSetup functions is now unused. https://bugs.torproject.org/15612 Replaced SOCKS4a with SOCKS5. https://bugs.torproject.org/12535 == 0.5 The AcceptSocks function no longer reports non-permanent errors, such as those caused by a faulty SOCKS handshake. Added support for an upstream proxy (TOR_PT_PROXY). The two new functions are ProxyError and ProxyDone. The ClientInfo struct has a new ProxyURL member. https://bugs.torproject.org/12125 == 0.4 Read the ExtORPort cookie file on every call to DialOr, instead of reading it once and caching the result. This is to work around a tor bug where tor doesn't ensure a new cookie file is written before starting pluggable transports. https://bugs.torproject.org/15240 == 0.3 Made output functions panic intead of backslash-escaping. Escaping of invalid bytes is not specified by pt-spec, and backslashes conflicted with the specified escaping of SMETHOD ARGS. https://bugs.torproject.org/13370 == 0.2 Added the MakeStateDir function. == 0.1 == 0.0 Initial release. v1.2.0/README000066400000000000000000000023771412045744100125750ustar00rootroot00000000000000goptlib is a library for writing Tor pluggable transports in Go. https://spec.torproject.org/pt-spec 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 https://gitweb.torproject.org/torspec.git/tree/proposals/232-pluggable-transports-through-proxy.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. v1.2.0/args.go000066400000000000000000000122661412045744100131760ustar00rootroot00000000000000package 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. // // "First the '=' formatted arguments MUST be escaped, such that all // backslash, equal sign, and semicolon characters are 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. // // "...a semicolon-separated list of : pairs, where is a PT // name and 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: scramblesuit:key=banana;automata:rule=110;automata:depth=3 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, ",") } v1.2.0/args_test.go000066400000000000000000000157051412045744100142360ustar00rootroot00000000000000package 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"}}, }, { "a=bc==", Args{"a": []string{"bc=="}}, }, { "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"}}, }, }, { "t:k=v=v", map[string]Args{ "t": {"k": []string{"v=v"}}, }, }, { "t:k=vv==", map[string]Args{ "t": {"k": []string{"vv=="}}, }, }, { "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) } } } v1.2.0/examples/000077500000000000000000000000001412045744100135225ustar00rootroot00000000000000v1.2.0/examples/dummy-client/000077500000000000000000000000001412045744100161315ustar00rootroot00000000000000v1.2.0/examples/dummy-client/dummy-client.go000066400000000000000000000045641412045744100211000ustar00rootroot00000000000000// 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" "io/ioutil" "net" "os" "os/signal" "sync" "syscall" ) import "git.torproject.org/pluggable-transports/goptlib.git" var ptInfo pt.ClientInfo 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 { 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(nil) 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() sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGTERM) if os.Getenv("TOR_PT_EXIT_ON_STDIN_CLOSE") == "1" { // This environment variable means we should treat EOF on stdin // just like SIGTERM: https://bugs.torproject.org/15435. go func() { io.Copy(ioutil.Discard, os.Stdin) sigChan <- syscall.SIGTERM }() } // wait for a signal <-sigChan // signal received, shut down for _, ln := range listeners { ln.Close() } } v1.2.0/examples/dummy-server/000077500000000000000000000000001412045744100161615ustar00rootroot00000000000000v1.2.0/examples/dummy-server/dummy-server.go000066400000000000000000000042101412045744100211440ustar00rootroot00000000000000// 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" "io/ioutil" "net" "os" "os/signal" "sync" "syscall" ) import "git.torproject.org/pluggable-transports/goptlib.git" var ptInfo pt.ServerInfo 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() 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(nil) 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() sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGTERM) if os.Getenv("TOR_PT_EXIT_ON_STDIN_CLOSE") == "1" { // This environment variable means we should treat EOF on stdin // just like SIGTERM: https://bugs.torproject.org/15435. go func() { io.Copy(ioutil.Discard, os.Stdin) sigChan <- syscall.SIGTERM }() } // wait for a signal <-sigChan // signal received, shut down for _, ln := range listeners { ln.Close() } } v1.2.0/go.mod000066400000000000000000000001041412045744100130050ustar00rootroot00000000000000module git.torproject.org/pluggable-transports/goptlib.git go 1.11 v1.2.0/proxy_test.go000066400000000000000000000042251412045744100144560ustar00rootroot00000000000000package 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) } } } v1.2.0/pt.go000066400000000000000000000730101412045744100126570ustar00rootroot00000000000000// 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() { // pt.Log(pt.LogSeverityError, "accept error: " + err.Error()) // continue // } // return err // } // go handler(conn) // } // return nil // } // ... // func main() { // var err error // ptInfo, err = pt.ClientSetup(nil) // 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(fmt.Sprintf("proxy %s is not supported", ptInfo.ProxyURL)) // 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 // } // pt.Log(pt.LogSeverityError, "accept error: " + err.Error()) // return err // } // go handler(conn) // } // return nil // } // ... // func main() { // var err error // ptInfo, err = pt.ServerSetup(nil) // 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 signals and shutdown properly. See // the example programs dummy-client and dummy-server. // // Tor pluggable transports specification: // https://spec.torproject.org/pt-spec // // 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 SOCKS5 server sufficient for a Tor client transport // plugin. // // https://www.ietf.org/rfc/rfc1928.txt // https://www.ietf.org/rfc/rfc1929.txt 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) { switch { case '0' <= b && b <= '9': continue case 'A' <= b && b <= 'Z': continue case 'a' <= b && b <= 'z': continue case b == '-' || b == '_': continue default: 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", 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") } // Unexported type to represent log severities, preventing external callers from // inventing new severity strings that may violate quoting rules. // // pt-spec.txt section 3.3.4 specifies quoting for MESSAGE, but not for // SEVERITY, and the example shows an unquoted "SEVERITY=debug". While we know // tor's parser permits quoting of SEVERITY, it's not actually specified. // Therefore we we need to guard against callers passing a string that violates // the global protocol constraint of "any US-ASCII character but NUL or NL." So // here, we instantiate exactly the five supported severities, using a type that // cannot be constructed outside the package. type logSeverity struct { string } // Severity levels for the Log function. var ( LogSeverityError = logSeverity{"error"} LogSeverityWarning = logSeverity{"warning"} LogSeverityNotice = logSeverity{"notice"} LogSeverityInfo = logSeverity{"info"} LogSeverityDebug = logSeverity{"debug"} ) // Encode a string according to the CString rules of section 2.1.1 in // control-spec.txt. // CString = DQUOTE *qcontent DQUOTE // "...in a CString, the escapes '\n', '\t', '\r', and the octal escapes '\0' // ... '\377' represent newline, tab, carriage return, and the 256 possible // octet values respectively." // RFC 2822 section 3.2.5 in turn defines what byte values we need to escape: // everything but // NO-WS-CTL / ; Non white space controls // %d33 / ; The rest of the US-ASCII // %d35-91 / ; characters not including "\" // %d93-126 ; or the quote character // Technically control-spec.txt requires us to escape the space character (32), // but it is an error in the spec: https://bugs.torproject.org/29432. // // We additionally need to ensure that whatever we return passes argIsSafe, // because strings encoded by this function are printed verbatim by Log. func encodeCString(s string) string { result := bytes.NewBuffer([]byte{}) result.WriteByte('"') for _, c := range []byte(s) { if c == 32 || c == 33 || (35 <= c && c <= 91) || (93 <= c && c <= 126) { result.WriteByte(c) } else { fmt.Fprintf(result, "\\%03o", c) } } result.WriteByte('"') return result.String() } // Emit a LOG message with the given severity (one of LogSeverityError, // LogSeverityWarning, LogSeverityNotice, LogSeverityInfo, or LogSeverityDebug). func Log(severity logSeverity, message string) { // " contains the log message which can be a String or CString..." // encodeCString always makes the string safe to emit; i.e., it // satisfies argIsSafe. line("LOG", "SEVERITY="+severity.string, "MESSAGE="+encodeCString(message)) } // 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 list of method names requested by Tor. This function reads the // environment variable TOR_PT_CLIENT_TRANSPORTS. func getClientTransports() ([]string, error) { clientTransports, err := getenvRequired("TOR_PT_CLIENT_TRANSPORTS") if err != nil { return nil, err } 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. 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. // } // // Always pass nil for the unused single parameter. In the past, the parameter // was a list of transport names to use in case Tor requested "*". That feature // was never implemented and has been removed from the pluggable transports // specification. // https://bugs.torproject.org/15612 func ClientSetup(_ []string) (info ClientInfo, err error) { ver, err := getManagedTransportVer() if err != nil { return } line("VERSION", ver) info.MethodNames, err = getClientTransports() 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. // https://bugs.torproject.org/7011 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. Transport-specific options // from TOR_PT_SERVER_TRANSPORT_OPTIONS are assigned to the Options member. func getServerBindaddrs() ([]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 } seenMethods := make(map[string]bool) 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] // Check for duplicate method names: "Applications MUST NOT set // more than one
: pair per PT name." if seenMethods[bindaddr.MethodName] { return nil, envError(fmt.Sprintf("TOR_PT_SERVER_BINDADDR: %q: duplicate method name %q", spec, bindaddr.MethodName)) } seenMethods[bindaddr.MethodName] = true 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 } 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 217-ext-orport-auth.txt. func readAuthCookieFile(filename string) (cookie []byte, err error) { f, err := os.Open(filename) if err != nil { return nil, err } defer func() { closeErr := f.Close() if err == nil { err = closeErr } }() 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. 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. // } // // Always pass nil for the unused single parameter. In the past, the parameter // was a list of transport names to use in case Tor requested "*". That feature // was never implemented and has been removed from the pluggable transports // specification. // https://bugs.torproject.org/15612 func ServerSetup(_ []string) (info ServerInfo, err error) { ver, err := getManagedTransportVer() if err != nil { return } line("VERSION", ver) info.Bindaddrs, err = getServerBindaddrs() 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 } } info.AuthCookiePath = getenv("TOR_PT_AUTH_COOKIE_FILE") extendedOrPort := getenv("TOR_PT_EXTENDED_SERVER_PORT") if extendedOrPort != "" { if info.AuthCookiePath == "" { err = envError("need TOR_PT_AUTH_COOKIE_FILE environment variable with TOR_PT_EXTENDED_SERVER_PORT") return } 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 } } // Need either OrAddr or ExtendedOrAddr. if info.OrAddr == nil && info.ExtendedOrAddr == nil { 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. // https://bugs.torproject.org/15240 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.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 extOrPortSetMetadata(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 } func extOrPortSetup(s net.Conn, timeout time.Duration, info *ServerInfo, addr, methodName string) error { err := s.SetDeadline(time.Now().Add(5 * time.Second)) if err != nil { return err } err = extOrPortAuthenticate(s, info) if err != nil { return err } err = extOrPortSetMetadata(s, addr, methodName) if err != nil { return err } err = s.SetDeadline(time.Time{}) if err != nil { return err } 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 } err = extOrPortSetup(s, 5*time.Second, info, addr, methodName) if err != nil { s.Close() return nil, err } return s, nil } v1.2.0/pt_test.go000066400000000000000000000652351412045744100137300ustar00rootroot00000000000000package pt import ( "bytes" "encoding/binary" "fmt" "io" "io/ioutil" "net" "os" "path" "sort" "testing" "time" ) const testAuthCookiePath = "test_authcookie" func TestErrors(t *testing.T) { Stdout = ioutil.Discard var err error err = envError("XYZ") if err.Error() != "ENV-ERROR XYZ" { t.Errorf("unexpected string %q from envError", err.Error()) } err = versionError("XYZ") if err.Error() != "VERSION-ERROR XYZ" { t.Errorf("unexpected string %q from versionError", err.Error()) } err = CmethodError("method", "XYZ") if err.Error() != "CMETHOD-ERROR method XYZ" { t.Errorf("unexpected string %q from CmethodError", err.Error()) } err = SmethodError("method", "XYZ") if err.Error() != "SMETHOD-ERROR method XYZ" { t.Errorf("unexpected string %q from SmethodError", err.Error()) } err = ProxyError("XYZ") if err.Error() != "PROXY-ERROR XYZ" { t.Errorf("unexpected string %q from ProxyError", err.Error()) } } 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}, {"\xffCMETHOD", 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 { ptClientTransports string expected []string }{ { "alpha", []string{"alpha"}, }, { "alpha,beta", []string{"alpha", "beta"}, }, { "alpha,beta,gamma", []string{"alpha", "beta", "gamma"}, }, // In the past, "*" meant to return all known transport names. // But now it has no special meaning. // https://bugs.torproject.org/15612 { "*", []string{"*"}, }, { "alpha,*,gamma", []string{"alpha", "*", "gamma"}, }, // No escaping is defined for TOR_PT_CLIENT_TRANSPORTS. { `alpha\,beta`, []string{`alpha\`, `beta`}, }, } Stdout = ioutil.Discard os.Clearenv() _, err := getClientTransports() if err == nil { t.Errorf("empty environment unexpectedly succeeded") } for _, test := range tests { os.Setenv("TOR_PT_CLIENT_TRANSPORTS", test.ptClientTransports) output, err := getClientTransports() if err != nil { t.Errorf("TOR_PT_CLIENT_TRANSPORTS=%q unexpectedly returned an error: %s", test.ptClientTransports, err) } if !stringSetsEqual(output, test.expected) { t.Errorf("TOR_PT_CLIENT_TRANSPORTS=%q → %q (expected %q)", test.ptClientTransports, 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 }{ // bad TOR_PT_SERVER_BINDADDR { "alpha", "alpha", "", }, { "alpha-1.2.3.4", "alpha", "", }, // missing TOR_PT_SERVER_TRANSPORTS { "alpha-1.2.3.4:1111", "", "alpha:key=value", }, // bad TOR_PT_SERVER_TRANSPORT_OPTIONS { "alpha-1.2.3.4:1111", "alpha", "key=value", }, // no escaping is defined for TOR_PT_SERVER_TRANSPORTS or // TOR_PT_SERVER_BINDADDR. { `alpha\,beta-1.2.3.4:1111`, `alpha\,beta`, "", }, // duplicates in TOR_PT_SERVER_BINDADDR // https://bugs.torproject.org/21261 { `alpha-0.0.0.0:1234,alpha-[::]:1234`, `alpha`, "", }, { `alpha-0.0.0.0:1234,alpha-0.0.0.0:1234`, `alpha`, "", }, } goodTests := [...]struct { ptServerBindaddr string ptServerTransports string ptServerTransportOptions 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", []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", "", []Bindaddr{}, }, { "alpha-1.2.3.4:1111", "alpha,beta,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", []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"}}}, }, }, // In the past, "*" meant to return all known transport names. // But now it has no special meaning. // https://bugs.torproject.org/15612 { "alpha-1.2.3.4:1111,beta-[1:2::3:4]:2222", "*", "", []Bindaddr{}, }, } Stdout = ioutil.Discard os.Clearenv() _, err := getServerBindaddrs() 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() if err == nil { t.Errorf("TOR_PT_SERVER_BINDADDR=%q TOR_PT_SERVER_TRANSPORTS=%q TOR_PT_SERVER_TRANSPORT_OPTIONS=%q unexpectedly succeeded", test.ptServerBindaddr, test.ptServerTransports, test.ptServerTransportOptions) } } 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() if err != nil { t.Errorf("TOR_PT_SERVER_BINDADDR=%q TOR_PT_SERVER_TRANSPORTS=%q TOR_PT_SERVER_TRANSPORT_OPTIONS=%q unexpectedly returned an error: %s", test.ptServerBindaddr, test.ptServerTransports, test.ptServerTransportOptions, 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 (expected %q)", test.ptServerBindaddr, test.ptServerTransports, test.ptServerTransportOptions, 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 extOrPortSetMetadata can write to one buffer and read from another. type mockSetMetadataBuf struct { ReadBuf bytes.Buffer WriteBuf bytes.Buffer } func (buf *mockSetMetadataBuf) Read(p []byte) (int, error) { return buf.ReadBuf.Read(p) } func (buf *mockSetMetadataBuf) Write(p []byte) (int, error) { return buf.WriteBuf.Write(p) } func testExtOrPortSetMetadataIndividual(t *testing.T, addr, methodName string) { var err error var buf mockSetMetadataBuf // fake an OKAY response. err = extOrPortSendCommand(&buf.ReadBuf, extOrCmdOkay, []byte{}) if err != nil { panic(err) } err = extOrPortSetMetadata(&buf, addr, methodName) if err != nil { t.Fatalf("error in extOrPortSetMetadata: %s", err) } for { cmd, body, err := extOrPortRecvCommand(&buf.WriteBuf) 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 TestExtOrPortSetMetadata(t *testing.T) { const addr = "127.0.0.1:40000" const methodName = "alpha" testExtOrPortSetMetadataIndividual(t, "", "") testExtOrPortSetMetadataIndividual(t, addr, "") testExtOrPortSetMetadataIndividual(t, "", methodName) testExtOrPortSetMetadataIndividual(t, addr, methodName) } func simulateServerExtOrPortAuth(r io.Reader, w io.Writer, authCookie []byte) error { // send auth types _, err := w.Write([]byte{1, 0}) if err != nil { return err } // read client auth type buf := make([]byte, 1) _, err = io.ReadFull(r, buf) if err != nil { return err } if buf[0] != 1 { return fmt.Errorf("didn't get client auth type 1") } // read client nonce clientNonce := make([]byte, 32) _, err = io.ReadFull(r, clientNonce) if err != nil { return err } // send server hash and nonce serverNonce := make([]byte, 32) serverHash := computeServerHash(authCookie, clientNonce, serverNonce) _, err = w.Write(serverHash) if err != nil { return err } _, err = w.Write(serverNonce) if err != nil { return err } // read client hash clientHash := make([]byte, 32) _, err = io.ReadFull(r, clientHash) if err != nil { return err } // send success status _, err = w.Write([]byte{1}) if err != nil { return err } return nil } type failSetDeadlineAfter struct { n int err error } func (c *failSetDeadlineAfter) try() error { if c.n > 0 { c.n-- return nil } return c.err } func (c *failSetDeadlineAfter) SetDeadline(_ time.Time) error { return c.try() } func (c *failSetDeadlineAfter) SetReadDeadline(_ time.Time) error { return c.try() } func (c *failSetDeadlineAfter) SetWriteDeadline(_ time.Time) error { return c.try() } // a fake Conn whose Set*Deadline functions fail after a certain number of // calls. type connFailSetDeadline struct { io.Reader io.Writer failSetDeadlineAfter } func (c *connFailSetDeadline) Close() error { return nil } func (c *connFailSetDeadline) LocalAddr() net.Addr { return &net.IPAddr{net.IPv4(0, 0, 0, 0), ""} } func (c *connFailSetDeadline) RemoteAddr() net.Addr { return &net.IPAddr{net.IPv4(0, 0, 0, 0), ""} } // Test that a failure of SetDeadline is reported. func TestExtOrPortSetupFailSetDeadline(t *testing.T) { authCookie, err := readAuthCookieFile(testAuthCookiePath) if err != nil { panic(err) } // extOrPortSetup calls SetDeadline twice, so try failing the call after // differing delays. expectedErr := fmt.Errorf("distinguished error") for _, delay := range []int{0, 1, 2} { upstreamR, upstreamW := io.Pipe() downstreamR, downstreamW := io.Pipe() // mock ExtORPort to talk to go func() { // handle auth err := simulateServerExtOrPortAuth(upstreamR, downstreamW, authCookie) if err != nil { return } // discard succeeding client data go func() { io.Copy(ioutil.Discard, upstreamR) }() // fake an OKAY response. err = extOrPortSendCommand(downstreamW, extOrCmdOkay, []byte{}) if err != nil { return } }() // make a Conn that will fail SetDeadline after a certain number // of calls. s := &connFailSetDeadline{downstreamR, upstreamW, failSetDeadlineAfter{delay, expectedErr}} serverInfo := &ServerInfo{AuthCookiePath: testAuthCookiePath} err = extOrPortSetup(s, 1*time.Second, serverInfo, "", "") if delay < 2 && err != expectedErr { t.Fatalf("delay %v: expected error %v, got %v", delay, expectedErr, err) } else if delay >= 2 && err != nil { t.Fatalf("delay %v: got error %v", delay, err) } } } 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") } } // Compare with unescape_string in tor's src/lib/encoding/cstring.c. That // function additionally allows hex escapes, but control-spec.txt's CString // doesn't say anything about that. func decodeCString(enc string) (string, error) { var result bytes.Buffer b := []byte(enc) state := "^" number := 0 i := 0 for i < len(b) { c := b[i] switch state { case "^": if c != '"' { return "", fmt.Errorf("missing start quote") } state = "." case ".": switch c { case '\\': state = "\\" case '"': state = "$" default: result.WriteByte(c) } case "\\": switch c { case 'n': result.WriteByte('\n') state = "." case 't': result.WriteByte('\t') state = "." case 'r': result.WriteByte('\r') state = "." case '"', '\\': result.WriteByte(c) state = "." case '0', '1', '2', '3', '4', '5', '6', '7': number = int(c - '0') state = "o1" default: return "", fmt.Errorf("unknown escape \\%c", c) } case "o1": // 1 octal digit read switch c { case '0', '1', '2', '3', '4', '5', '6', '7': number = number*8 + int(c-'0') state = "o2" default: if number > 255 { return "", fmt.Errorf("invalid octal escape") } result.WriteByte(byte(number)) state = "." continue // process the current byte again } case "o2": // 2 octal digits read switch c { case '0', '1', '2', '3', '4', '5', '6', '7': number = number*8 + int(c-'0') if number > 255 { return "", fmt.Errorf("invalid octal escape") } result.WriteByte(byte(number)) state = "." default: if number > 255 { return "", fmt.Errorf("invalid octal escape") } result.WriteByte(byte(number)) state = "." continue // process the current byte again } case "$": return "", fmt.Errorf("trailing garbage") } i++ } if state != "$" { return "", fmt.Errorf("unexpected end of string") } return result.String(), nil } func roundtripCString(src string) (string, error) { enc := encodeCString(src) dec, err := decodeCString(enc) if err != nil { return enc, fmt.Errorf("failed to decode: %+q → %+q: %v", src, enc, err) } if dec != src { return enc, fmt.Errorf("roundtrip failed: %+q → %+q → %+q", src, enc, dec) } return enc, nil } func TestEncodeCString(t *testing.T) { tests := []string{ "", "\"", "\"\"", "abc\"def", "\\", "\\\\", "\x0123abc", // trap here is if you encode '\x01' as "\\1"; it would join with the following digits: "\\123abc". "\n\r\t\x7f", "\\377", } allBytes := make([]byte, 256) for i := 0; i < len(allBytes); i++ { allBytes[i] = byte(i) } tests = append(tests, string(allBytes)) for _, test := range tests { enc, err := roundtripCString(test) if err != nil { t.Error(err) } if !argIsSafe(enc) { t.Errorf("escaping %+q resulted in non-safe %+q", test, enc) } } } v1.2.0/socks.go000066400000000000000000000323571412045744100133670ustar00rootroot00000000000000package pt import ( "bufio" "fmt" "io" "net" "time" ) const ( socksVersion = 0x05 socksAuthNoneRequired = 0x00 socksAuthUsernamePassword = 0x02 socksAuthNoAcceptableMethods = 0xff socksCmdConnect = 0x01 socksRsv = 0x00 socksAtypeV4 = 0x01 socksAtypeDomainName = 0x03 socksAtypeV6 = 0x04 socksAuthRFC1929Ver = 0x01 socksAuthRFC1929Success = 0x00 socksAuthRFC1929Fail = 0x01 socksRepSucceeded = 0x00 // "general SOCKS server failure" SocksRepGeneralFailure = 0x01 // "connection not allowed by ruleset" SocksRepConnectionNotAllowed = 0x02 // "Network unreachable" SocksRepNetworkUnreachable = 0x03 // "Host unreachable" SocksRepHostUnreachable = 0x04 // "Connection refused" SocksRepConnectionRefused = 0x05 // "TTL expired" SocksRepTTLExpired = 0x06 // "Command not supported" SocksRepCommandNotSupported = 0x07 // "Address type not supported" SocksRepAddressNotSupported = 0x08 ) // 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 password string sent by the client. Password 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. Addr is ignored, and "0.0.0.0:0" is always sent back for // BND.ADDR/BND.PORT in the SOCKS response. func (conn *SocksConn) Grant(addr *net.TCPAddr) error { return sendSocks5ResponseGranted(conn) } // Send a message to the proxy client that access was rejected or failed. This // sends back a "General Failure" error code. RejectReason should be used if // more specific error reporting is desired. func (conn *SocksConn) Reject() error { return conn.RejectReason(SocksRepGeneralFailure) } // Send a message to the proxy client that access was rejected, with the // specific error code indicating the reason behind the rejection. func (conn *SocksConn) RejectReason(reason byte) error { return sendSocks5ResponseRejected(conn, reason) } // 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 = socks5Handshake(conn) if err != nil { conn.Close() goto retry } err = conn.SetDeadline(time.Time{}) if err != nil { conn.Close() goto retry } return conn, nil } // Returns "socks5", suitable to be included in a call to Cmethod. func (ln *SocksListener) Version() string { return "socks5" } // socks5handshake conducts the SOCKS5 handshake up to the point where the // client command is read and the proxy must open the outgoing connection. // Returns a SocksRequest. func socks5Handshake(s io.ReadWriter) (req SocksRequest, err error) { rw := bufio.NewReadWriter(bufio.NewReader(s), bufio.NewWriter(s)) // Negotiate the authentication method. var method byte if method, err = socksNegotiateAuth(rw); err != nil { return } // Authenticate the client. if err = socksAuthenticate(rw, method, &req); err != nil { return } // Read the command. err = socksReadCommand(rw, &req) return } // socksNegotiateAuth negotiates the authentication method and returns the // selected method as a byte. On negotiation failures an error is returned. func socksNegotiateAuth(rw *bufio.ReadWriter) (method byte, err error) { // Validate the version. if err = socksReadByteVerify(rw, "version", socksVersion); err != nil { return } // Read the number of methods. var nmethods byte if nmethods, err = socksReadByte(rw); err != nil { return } // Read the methods. var methods []byte if methods, err = socksReadBytes(rw, int(nmethods)); err != nil { return } // Pick the most "suitable" method. method = socksAuthNoAcceptableMethods for _, m := range methods { switch m { case socksAuthNoneRequired: // Pick Username/Password over None if the client happens to // send both. if method == socksAuthNoAcceptableMethods { method = m } case socksAuthUsernamePassword: method = m } } // Send the negotiated method. var msg [2]byte msg[0] = socksVersion msg[1] = method if _, err = rw.Writer.Write(msg[:]); err != nil { return } if err = socksFlushBuffers(rw); err != nil { return } return } // socksAuthenticate authenticates the client via the chosen authentication // mechanism. func socksAuthenticate(rw *bufio.ReadWriter, method byte, req *SocksRequest) (err error) { switch method { case socksAuthNoneRequired: // Straight into reading the connect. case socksAuthUsernamePassword: if err = socksAuthRFC1929(rw, req); err != nil { return } case socksAuthNoAcceptableMethods: err = fmt.Errorf("SOCKS method select had no compatible methods") return default: err = fmt.Errorf("SOCKS method select picked a unsupported method 0x%02x", method) return } if err = socksFlushBuffers(rw); err != nil { return } return } // socksAuthRFC1929 authenticates the client via RFC 1929 username/password // auth. As a design decision any valid username/password is accepted as this // field is primarily used as an out-of-band argument passing mechanism for // pluggable transports. func socksAuthRFC1929(rw *bufio.ReadWriter, req *SocksRequest) (err error) { sendErrResp := func() { // Swallow the write/flush error here, we are going to close the // connection and the original failure is more useful. resp := []byte{socksAuthRFC1929Ver, socksAuthRFC1929Fail} rw.Write(resp[:]) socksFlushBuffers(rw) } // Validate the fixed parts of the command message. if err = socksReadByteVerify(rw, "auth version", socksAuthRFC1929Ver); err != nil { sendErrResp() return } // Read the username. var ulen byte if ulen, err = socksReadByte(rw); err != nil { return } if ulen < 1 { sendErrResp() err = fmt.Errorf("RFC1929 username with 0 length") return } var uname []byte if uname, err = socksReadBytes(rw, int(ulen)); err != nil { return } req.Username = string(uname) // Read the password. var plen byte if plen, err = socksReadByte(rw); err != nil { return } if plen < 1 { sendErrResp() err = fmt.Errorf("RFC1929 password with 0 length") return } var passwd []byte if passwd, err = socksReadBytes(rw, int(plen)); err != nil { return } if !(plen == 1 && passwd[0] == 0x00) { // tor will set the password to 'NUL' if there are no arguments. req.Password = string(passwd) } // Mash the username/password together and parse it as a pluggable // transport argument string. if req.Args, err = parseClientParameters(req.Username + req.Password); err != nil { sendErrResp() } else { resp := []byte{socksAuthRFC1929Ver, socksAuthRFC1929Success} _, err = rw.Write(resp[:]) } return } // socksReadCommand reads a SOCKS5 client command and parses out the relevant // fields into a SocksRequest. Only CMD_CONNECT is supported. func socksReadCommand(rw *bufio.ReadWriter, req *SocksRequest) (err error) { sendErrResp := func(reason byte) { // Swallow errors that occur when writing/flushing the response, // connection will be closed anyway. sendSocks5ResponseRejected(rw, reason) socksFlushBuffers(rw) } // Validate the fixed parts of the command message. if err = socksReadByteVerify(rw, "version", socksVersion); err != nil { sendErrResp(SocksRepGeneralFailure) return } if err = socksReadByteVerify(rw, "command", socksCmdConnect); err != nil { sendErrResp(SocksRepCommandNotSupported) return } if err = socksReadByteVerify(rw, "reserved", socksRsv); err != nil { sendErrResp(SocksRepGeneralFailure) return } // Read the destination address/port. // XXX: This should probably eventually send socks 5 error messages instead // of rudely closing connections on invalid addresses. var atype byte if atype, err = socksReadByte(rw); err != nil { return } var host string switch atype { case socksAtypeV4: var addr []byte if addr, err = socksReadBytes(rw, net.IPv4len); err != nil { return } host = net.IPv4(addr[0], addr[1], addr[2], addr[3]).String() case socksAtypeDomainName: var alen byte if alen, err = socksReadByte(rw); err != nil { return } if alen == 0 { err = fmt.Errorf("SOCKS request had domain name with 0 length") return } var addr []byte if addr, err = socksReadBytes(rw, int(alen)); err != nil { return } host = string(addr) case socksAtypeV6: var rawAddr []byte if rawAddr, err = socksReadBytes(rw, net.IPv6len); err != nil { return } addr := make(net.IP, net.IPv6len) copy(addr[:], rawAddr[:]) host = fmt.Sprintf("[%s]", addr.String()) default: sendErrResp(SocksRepAddressNotSupported) err = fmt.Errorf("SOCKS request had unsupported address type 0x%02x", atype) return } var rawPort []byte if rawPort, err = socksReadBytes(rw, 2); err != nil { return } port := int(rawPort[0])<<8 | int(rawPort[1])<<0 if err = socksFlushBuffers(rw); err != nil { return } req.Target = fmt.Sprintf("%s:%d", host, port) return } // Send a SOCKS5 response with the given code. BND.ADDR/BND.PORT is always the // IPv4 address/port "0.0.0.0:0". func sendSocks5Response(w io.Writer, code byte) error { resp := make([]byte, 4+4+2) resp[0] = socksVersion resp[1] = code resp[2] = socksRsv resp[3] = socksAtypeV4 // BND.ADDR/BND.PORT should be the address and port that the outgoing // connection is bound to on the proxy, but Tor does not use this // information, so all zeroes are sent. _, err := w.Write(resp[:]) return err } // Send a SOCKS5 response code 0x00. func sendSocks5ResponseGranted(w io.Writer) error { return sendSocks5Response(w, socksRepSucceeded) } // Send a SOCKS5 response with the provided failure reason. func sendSocks5ResponseRejected(w io.Writer, reason byte) error { return sendSocks5Response(w, reason) } func socksFlushBuffers(rw *bufio.ReadWriter) error { if err := rw.Writer.Flush(); err != nil { return err } if rw.Reader.Buffered() > 0 { return fmt.Errorf("%d bytes left after SOCKS message", rw.Reader.Buffered()) } return nil } func socksReadByte(rw *bufio.ReadWriter) (byte, error) { return rw.Reader.ReadByte() } func socksReadBytes(rw *bufio.ReadWriter, n int) ([]byte, error) { ret := make([]byte, n) if _, err := io.ReadFull(rw.Reader, ret); err != nil { return nil, err } return ret, nil } func socksReadByteVerify(rw *bufio.ReadWriter, descr string, expected byte) error { val, err := socksReadByte(rw) if err != nil { return err } if val != expected { return fmt.Errorf("SOCKS message field %s was 0x%02x, not 0x%02x", descr, val, expected) } return nil } var _ net.Listener = (*SocksListener)(nil) v1.2.0/socks_test.go000066400000000000000000000343501412045744100144210ustar00rootroot00000000000000package pt import ( "bufio" "bytes" "encoding/hex" "errors" "io" "net" "testing" "time" ) // testReadWriter is a bytes.Buffer backed io.ReadWriter used for testing. The // Read and Write routines are to be used by the component being tested. Data // can be written to and read back via the writeHex and readHex routines. type testReadWriter struct { readBuf bytes.Buffer writeBuf bytes.Buffer } func (c *testReadWriter) Read(buf []byte) (n int, err error) { return c.readBuf.Read(buf) } func (c *testReadWriter) Write(buf []byte) (n int, err error) { return c.writeBuf.Write(buf) } func (c *testReadWriter) writeHex(str string) (n int, err error) { var buf []byte if buf, err = hex.DecodeString(str); err != nil { return } return c.readBuf.Write(buf) } func (c *testReadWriter) readHex() string { return hex.EncodeToString(c.writeBuf.Bytes()) } func (c *testReadWriter) toBufio() *bufio.ReadWriter { return bufio.NewReadWriter(bufio.NewReader(c), bufio.NewWriter(c)) } func (c *testReadWriter) reset() { c.readBuf.Reset() c.writeBuf.Reset() } // TestAuthInvalidVersion tests auth negotiation with an invalid version. func TestAuthInvalidVersion(t *testing.T) { c := new(testReadWriter) // VER = 03, NMETHODS = 01, METHODS = [00] c.writeHex("030100") if _, err := socksNegotiateAuth(c.toBufio()); err == nil { t.Error("socksNegotiateAuth(InvalidVersion) succeded") } } // TestAuthInvalidNMethods tests auth negotiaton with no methods. func TestAuthInvalidNMethods(t *testing.T) { c := new(testReadWriter) var err error var method byte // VER = 05, NMETHODS = 00 c.writeHex("0500") if method, err = socksNegotiateAuth(c.toBufio()); err != nil { t.Error("socksNegotiateAuth(No Methods) failed:", err) } if method != socksAuthNoAcceptableMethods { t.Error("socksNegotiateAuth(No Methods) picked unexpected method:", method) } if msg := c.readHex(); msg != "05ff" { t.Error("socksNegotiateAuth(No Methods) invalid response:", msg) } } // TestAuthNoneRequired tests auth negotiaton with NO AUTHENTICATION REQUIRED. func TestAuthNoneRequired(t *testing.T) { c := new(testReadWriter) var err error var method byte // VER = 05, NMETHODS = 01, METHODS = [00] c.writeHex("050100") if method, err = socksNegotiateAuth(c.toBufio()); err != nil { t.Error("socksNegotiateAuth(None) failed:", err) } if method != socksAuthNoneRequired { t.Error("socksNegotiateAuth(None) unexpected method:", method) } if msg := c.readHex(); msg != "0500" { t.Error("socksNegotiateAuth(None) invalid response:", msg) } } // TestAuthUsernamePassword tests auth negotiation with USERNAME/PASSWORD. func TestAuthUsernamePassword(t *testing.T) { c := new(testReadWriter) var err error var method byte // VER = 05, NMETHODS = 01, METHODS = [02] c.writeHex("050102") if method, err = socksNegotiateAuth(c.toBufio()); err != nil { t.Error("socksNegotiateAuth(UsernamePassword) failed:", err) } if method != socksAuthUsernamePassword { t.Error("socksNegotiateAuth(UsernamePassword) unexpected method:", method) } if msg := c.readHex(); msg != "0502" { t.Error("socksNegotiateAuth(UsernamePassword) invalid response:", msg) } } 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) } } // TestAuthBoth tests auth negotiation containing both NO AUTHENTICATION // REQUIRED and USERNAME/PASSWORD. func TestAuthBoth(t *testing.T) { c := new(testReadWriter) var err error var method byte // VER = 05, NMETHODS = 02, METHODS = [00, 02] c.writeHex("05020002") if method, err = socksNegotiateAuth(c.toBufio()); err != nil { t.Error("socksNegotiateAuth(Both) failed:", err) } if method != socksAuthUsernamePassword { t.Error("socksNegotiateAuth(Both) unexpected method:", method) } if msg := c.readHex(); msg != "0502" { t.Error("socksNegotiateAuth(Both) invalid response:", msg) } } // TestAuthUnsupported tests auth negotiation with a unsupported method. func TestAuthUnsupported(t *testing.T) { c := new(testReadWriter) var err error var method byte // VER = 05, NMETHODS = 01, METHODS = [01] (GSSAPI) c.writeHex("050101") if method, err = socksNegotiateAuth(c.toBufio()); err != nil { t.Error("socksNegotiateAuth(Unknown) failed:", err) } if method != socksAuthNoAcceptableMethods { t.Error("socksNegotiateAuth(Unknown) picked unexpected method:", method) } if msg := c.readHex(); msg != "05ff" { t.Error("socksNegotiateAuth(Unknown) invalid response:", msg) } } // TestAuthUnsupported2 tests auth negotiation with supported and unsupported // methods. func TestAuthUnsupported2(t *testing.T) { c := new(testReadWriter) var err error var method byte // VER = 05, NMETHODS = 03, METHODS = [00,01,02] c.writeHex("0503000102") if method, err = socksNegotiateAuth(c.toBufio()); err != nil { t.Error("socksNegotiateAuth(Unknown2) failed:", err) } if method != socksAuthUsernamePassword { t.Error("socksNegotiateAuth(Unknown2) picked unexpected method:", method) } if msg := c.readHex(); msg != "0502" { t.Error("socksNegotiateAuth(Unknown2) invalid response:", msg) } } // TestRFC1929InvalidVersion tests RFC1929 auth with an invalid version. func TestRFC1929InvalidVersion(t *testing.T) { c := new(testReadWriter) var req SocksRequest // VER = 03, ULEN = 5, UNAME = "ABCDE", PLEN = 5, PASSWD = "abcde" c.writeHex("03054142434445056162636465") if err := socksAuthenticate(c.toBufio(), socksAuthUsernamePassword, &req); err == nil { t.Error("socksAuthenticate(InvalidVersion) succeded") } if msg := c.readHex(); msg != "0101" { t.Error("socksAuthenticate(InvalidVersion) invalid response:", msg) } } // TestRFC1929InvalidUlen tests RFC1929 auth with an invalid ULEN. func TestRFC1929InvalidUlen(t *testing.T) { c := new(testReadWriter) var req SocksRequest // VER = 01, ULEN = 0, UNAME = "", PLEN = 5, PASSWD = "abcde" c.writeHex("0100056162636465") if err := socksAuthenticate(c.toBufio(), socksAuthUsernamePassword, &req); err == nil { t.Error("socksAuthenticate(InvalidUlen) succeded") } if msg := c.readHex(); msg != "0101" { t.Error("socksAuthenticate(InvalidUlen) invalid response:", msg) } } // TestRFC1929InvalidPlen tests RFC1929 auth with an invalid PLEN. func TestRFC1929InvalidPlen(t *testing.T) { c := new(testReadWriter) var req SocksRequest // VER = 01, ULEN = 5, UNAME = "ABCDE", PLEN = 0, PASSWD = "" c.writeHex("0105414243444500") if err := socksAuthenticate(c.toBufio(), socksAuthUsernamePassword, &req); err == nil { t.Error("socksAuthenticate(InvalidPlen) succeded") } if msg := c.readHex(); msg != "0101" { t.Error("socksAuthenticate(InvalidPlen) invalid response:", msg) } } // TestRFC1929InvalidArgs tests RFC1929 auth with invalid pt args. func TestRFC1929InvalidPTArgs(t *testing.T) { c := new(testReadWriter) var req SocksRequest // VER = 01, ULEN = 5, UNAME = "ABCDE", PLEN = 5, PASSWD = "abcde" c.writeHex("01054142434445056162636465") if err := socksAuthenticate(c.toBufio(), socksAuthUsernamePassword, &req); err == nil { t.Error("socksAuthenticate(InvalidArgs) succeded") } if msg := c.readHex(); msg != "0101" { t.Error("socksAuthenticate(InvalidArgs) invalid response:", msg) } } // TestRFC1929Success tests RFC1929 auth with valid pt args. func TestRFC1929Success(t *testing.T) { c := new(testReadWriter) var req SocksRequest // VER = 01, ULEN = 9, UNAME = "key=value", PLEN = 1, PASSWD = "\0" c.writeHex("01096b65793d76616c75650100") if err := socksAuthenticate(c.toBufio(), socksAuthUsernamePassword, &req); err != nil { t.Error("socksAuthenticate(Success) failed:", err) } if msg := c.readHex(); msg != "0100" { t.Error("socksAuthenticate(Success) invalid response:", msg) } v, ok := req.Args.Get("key") if v != "value" || !ok { t.Error("RFC1929 k,v parse failure:", v) } } // TestRequestInvalidHdr tests SOCKS5 requests with invalid VER/CMD/RSV/ATYPE func TestRequestInvalidHdr(t *testing.T) { c := new(testReadWriter) var req SocksRequest // VER = 03, CMD = 01, RSV = 00, ATYPE = 01, DST.ADDR = 127.0.0.1, DST.PORT = 9050 c.writeHex("030100017f000001235a") if err := socksReadCommand(c.toBufio(), &req); err == nil { t.Error("socksReadCommand(InvalidVer) succeded") } if msg := c.readHex(); msg != "05010001000000000000" { t.Error("socksReadCommand(InvalidVer) invalid response:", msg) } c.reset() // VER = 05, CMD = 05, RSV = 00, ATYPE = 01, DST.ADDR = 127.0.0.1, DST.PORT = 9050 c.writeHex("050500017f000001235a") if err := socksReadCommand(c.toBufio(), &req); err == nil { t.Error("socksReadCommand(InvalidCmd) succeded") } if msg := c.readHex(); msg != "05070001000000000000" { t.Error("socksReadCommand(InvalidCmd) invalid response:", msg) } c.reset() // VER = 05, CMD = 01, RSV = 30, ATYPE = 01, DST.ADDR = 127.0.0.1, DST.PORT = 9050 c.writeHex("050130017f000001235a") if err := socksReadCommand(c.toBufio(), &req); err == nil { t.Error("socksReadCommand(InvalidRsv) succeded") } if msg := c.readHex(); msg != "05010001000000000000" { t.Error("socksReadCommand(InvalidRsv) invalid response:", msg) } c.reset() // VER = 05, CMD = 01, RSV = 01, ATYPE = 05, DST.ADDR = 127.0.0.1, DST.PORT = 9050 c.writeHex("050100057f000001235a") if err := socksReadCommand(c.toBufio(), &req); err == nil { t.Error("socksReadCommand(InvalidAtype) succeded") } if msg := c.readHex(); msg != "05080001000000000000" { t.Error("socksAuthenticate(InvalidAtype) invalid response:", msg) } c.reset() } // TestRequestIPv4 tests IPv4 SOCKS5 requests. func TestRequestIPv4(t *testing.T) { c := new(testReadWriter) var req SocksRequest // VER = 05, CMD = 01, RSV = 00, ATYPE = 01, DST.ADDR = 127.0.0.1, DST.PORT = 9050 c.writeHex("050100017f000001235a") if err := socksReadCommand(c.toBufio(), &req); err != nil { t.Error("socksReadCommand(IPv4) failed:", err) } addr, err := net.ResolveTCPAddr("tcp", req.Target) if err != nil { t.Error("net.ResolveTCPAddr failed:", err) } if !tcpAddrsEqual(addr, &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 9050}) { t.Error("Unexpected target:", addr) } } // TestRequestIPv6 tests IPv4 SOCKS5 requests. func TestRequestIPv6(t *testing.T) { c := new(testReadWriter) var req SocksRequest // VER = 05, CMD = 01, RSV = 00, ATYPE = 04, DST.ADDR = 0102:0304:0506:0708:090a:0b0c:0d0e:0f10, DST.PORT = 9050 c.writeHex("050100040102030405060708090a0b0c0d0e0f10235a") if err := socksReadCommand(c.toBufio(), &req); err != nil { t.Error("socksReadCommand(IPv6) failed:", err) } addr, err := net.ResolveTCPAddr("tcp", req.Target) if err != nil { t.Error("net.ResolveTCPAddr failed:", err) } if !tcpAddrsEqual(addr, &net.TCPAddr{IP: net.ParseIP("0102:0304:0506:0708:090a:0b0c:0d0e:0f10"), Port: 9050}) { t.Error("Unexpected target:", addr) } } // TestRequestFQDN tests FQDN (DOMAINNAME) SOCKS5 requests. func TestRequestFQDN(t *testing.T) { c := new(testReadWriter) var req SocksRequest // VER = 05, CMD = 01, RSV = 00, ATYPE = 04, DST.ADDR = example.com, DST.PORT = 9050 c.writeHex("050100030b6578616d706c652e636f6d235a") if err := socksReadCommand(c.toBufio(), &req); err != nil { t.Error("socksReadCommand(FQDN) failed:", err) } if req.Target != "example.com:9050" { t.Error("Unexpected target:", req.Target) } } // TestResponseNil tests nil address SOCKS5 responses. func TestResponseNil(t *testing.T) { c := new(testReadWriter) b := c.toBufio() if err := sendSocks5ResponseGranted(b); err != nil { t.Error("sendSocks5ResponseGranted() failed:", err) } b.Flush() if msg := c.readHex(); msg != "05000001000000000000" { t.Error("sendSocks5ResponseGranted(nil) invalid response:", msg) } } var _ io.ReadWriter = (*testReadWriter)(nil) v1.2.0/test_authcookie000066400000000000000000000001001412045744100150100ustar00rootroot00000000000000! Extended ORPort Auth Cookie ! this file is used in test code.