diff options
Diffstat (limited to 'vendor/github.com/OperatorFoundation/shapeshifter-ipc')
6 files changed, 1842 insertions, 0 deletions
diff --git a/vendor/github.com/OperatorFoundation/shapeshifter-ipc/.gitignore b/vendor/github.com/OperatorFoundation/shapeshifter-ipc/.gitignore new file mode 100644 index 0000000..d4d5132 --- /dev/null +++ b/vendor/github.com/OperatorFoundation/shapeshifter-ipc/.gitignore @@ -0,0 +1,2 @@ +/examples/dummy-client/dummy-client +/examples/dummy-server/dummy-server diff --git a/vendor/github.com/OperatorFoundation/shapeshifter-ipc/COPYING b/vendor/github.com/OperatorFoundation/shapeshifter-ipc/COPYING new file mode 100644 index 0000000..0e259d4 --- /dev/null +++ b/vendor/github.com/OperatorFoundation/shapeshifter-ipc/COPYING @@ -0,0 +1,121 @@ +Creative 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. diff --git a/vendor/github.com/OperatorFoundation/shapeshifter-ipc/README b/vendor/github.com/OperatorFoundation/shapeshifter-ipc/README new file mode 100644 index 0000000..1e36247 --- /dev/null +++ b/vendor/github.com/OperatorFoundation/shapeshifter-ipc/README @@ -0,0 +1,27 @@ +goptlib 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 github.com/OperatorFoundation/shapeshifter-ipc + +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/github.com/OperatorFoundation/shapeshifter-ipc + +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. diff --git a/vendor/github.com/OperatorFoundation/shapeshifter-ipc/args.go b/vendor/github.com/OperatorFoundation/shapeshifter-ipc/args.go new file mode 100644 index 0000000..295efa4 --- /dev/null +++ b/vendor/github.com/OperatorFoundation/shapeshifter-ipc/args.go @@ -0,0 +1,265 @@ +package pt + +import ( + "bytes" + "encoding/json" + "fmt" + "sort" + "strings" +) + +// Key–value mappings for the representation of client and server options. + +// Args maps a string key to a list of values. It is similar to url.Values. +type Args map[string][]string + +// Get the first value associated with the given key. If there are any values +// associated with the key, the value return has the value and ok is set to +// true. If there are no values for the given key, value is "" and ok is false. +// If you need access to multiple values, use the map directly. +func (args Args) Get(key string) (value string, ok bool) { + if args == nil { + return "", false + } + vals, ok := args[key] + if !ok || len(vals) == 0 { + return "", false + } + return vals[0], true +} + +// Append value to the list of values for key. +func (args Args) Add(key, value string) { + args[key] = append(args[key], value) +} + +// Return the index of the next unescaped byte in s that is in the term set, or +// else the length of the string if no terminators appear. Additionally return +// the unescaped string up to the returned index. +func indexUnescaped(s string, term []byte) (int, string, error) { + var i int + unesc := make([]byte, 0) + for i = 0; i < len(s); i++ { + b := s[i] + // A terminator byte? + if bytes.IndexByte(term, b) != -1 { + break + } + if b == '\\' { + i++ + if i >= len(s) { + return 0, "", fmt.Errorf("nothing following final escape in %q", s) + } + b = s[i] + } + unesc = append(unesc, b) + } + return i, string(unesc), nil +} + +// Parse a name–value mapping as from an encoded SOCKS username/password. +// +// "If any [k=v] items are provided, they are configuration parameters for the +// proxy: Tor should separate them with semicolons ... If a key or value value +// must contain [an equals sign or] a semicolon or a backslash, it is escaped +// with a backslash." +func parseClientParameters(s string) (args Args, err error) { + args = make(Args) + if len(s) == 0 { + return + } + i := 0 + for { + var key, value string + var offset, begin int + + begin = i + // Read the key. + offset, key, err = indexUnescaped(s[i:], []byte{'=', ';'}) + if err != nil { + return + } + i += offset + // End of string or no equals sign? + if i >= len(s) || s[i] != '=' { + err = fmt.Errorf("no equals sign in %q", s[begin:i]) + return + } + // Skip the equals sign. + i++ + // Read the value. + offset, value, err = indexUnescaped(s[i:], []byte{';'}) + if err != nil { + return + } + i += offset + if len(key) == 0 { + err = fmt.Errorf("empty key in %q", s[begin:i]) + return + } + args.Add(key, value) + if i >= len(s) { + break + } + // Skip the semicolon. + i++ + } + return args, nil +} + +func ParsePT2ClientParameters(s string) (args Args, err error) { + args = make(Args) + if len(s) == 0 { + return + } + + decoder := json.NewDecoder(strings.NewReader(s)) + var result map[string]string + if err := decoder.Decode(&result); err != nil { + fmt.Errorf("Error decoding JSON %q", err) + return nil, err + } + + for key, value := range result { + args.Add(key, value) + } + + return args, nil +} + +func ParsePT2ServerParameters(s string) (params map[string]Args, err error) { + opts := make(map[string]Args) + if len(s) == 0 { + return opts, nil + } + + decoder := json.NewDecoder(strings.NewReader(s)) + var result map[string]map[string]string + if err := decoder.Decode(&result); err != nil { + fmt.Errorf("Error decoding JSON %q", err) + return nil, err + } + + for key, sub := range result { + args := make(Args) + for key2, value := range sub { + args.Add(key2, value) + } + + opts[key] = args + } + + return opts, nil +} + +// Parse a transport–name–value mapping as from TOR_PT_SERVER_TRANSPORT_OPTIONS. +// +// "<value> is a k=v string value with options that are to be passed to the +// transport. Colons, semicolons, equal signs and backslashes must be escaped +// with a backslash." +// Example: trebuchet:secret=nou;trebuchet:cache=/tmp/cache;ballista:secret=yes +func ParseServerTransportOptions(s string) (opts map[string]Args, err error) { + opts = make(map[string]Args) + if len(s) == 0 { + return + } + i := 0 + for { + var methodName, key, value string + var offset, begin int + + begin = i + // Read the method name. + offset, methodName, err = indexUnescaped(s[i:], []byte{':', '=', ';'}) + if err != nil { + return + } + i += offset + // End of string or no colon? + if i >= len(s) || s[i] != ':' { + err = fmt.Errorf("no colon in %q", s[begin:i]) + return + } + // Skip the colon. + i++ + // Read the key. + offset, key, err = indexUnescaped(s[i:], []byte{'=', ';'}) + if err != nil { + return + } + i += offset + // End of string or no equals sign? + if i >= len(s) || s[i] != '=' { + err = fmt.Errorf("no equals sign in %q", s[begin:i]) + return + } + // Skip the equals sign. + i++ + // Read the value. + offset, value, err = indexUnescaped(s[i:], []byte{';'}) + if err != nil { + return + } + i += offset + if len(methodName) == 0 { + err = fmt.Errorf("empty method name in %q", s[begin:i]) + return + } + if len(key) == 0 { + err = fmt.Errorf("empty key in %q", s[begin:i]) + return + } + if opts[methodName] == nil { + opts[methodName] = make(Args) + } + opts[methodName].Add(key, value) + if i >= len(s) { + break + } + // Skip the semicolon. + i++ + } + return opts, nil +} + +// Escape backslashes and all the bytes that are in set. +func backslashEscape(s string, set []byte) string { + var buf bytes.Buffer + for _, b := range []byte(s) { + if b == '\\' || bytes.IndexByte(set, b) != -1 { + buf.WriteByte('\\') + } + buf.WriteByte(b) + } + return buf.String() +} + +// Encode a name–value mapping so that it is suitable to go in the ARGS option +// of an SMETHOD line. The output is sorted by key. The "ARGS:" prefix is not +// added. +// +// "Equal signs and commas [and backslashes] must be escaped with a backslash." +func encodeSmethodArgs(args Args) string { + if args == nil { + return "" + } + + keys := make([]string, 0, len(args)) + for key := range args { + keys = append(keys, key) + } + sort.Strings(keys) + + escape := func(s string) string { + return backslashEscape(s, []byte{'=', ','}) + } + + var pairs []string + for _, key := range keys { + for _, value := range args[key] { + pairs = append(pairs, escape(key)+"="+escape(value)) + } + } + + return strings.Join(pairs, ",") +} diff --git a/vendor/github.com/OperatorFoundation/shapeshifter-ipc/pt.go b/vendor/github.com/OperatorFoundation/shapeshifter-ipc/pt.go new file mode 100644 index 0000000..aedbbb6 --- /dev/null +++ b/vendor/github.com/OperatorFoundation/shapeshifter-ipc/pt.go @@ -0,0 +1,941 @@ +// Package pt implements the Tor pluggable transports specification. +// +// Sample client usage: +// var ptInfo pt.ClientInfo +// ... +// func handler(conn *pt.SocksConn) error { +// defer conn.Close() +// remote, err := net.Dial("tcp", conn.Req.Target) +// if err != nil { +// conn.Reject() +// return err +// } +// defer remote.Close() +// err = conn.Grant(remote.RemoteAddr().(*net.TCPAddr)) +// if err != nil { +// return err +// } +// // do something with conn and remote. +// return nil +// } +// func acceptLoop(ln *pt.SocksListener) error { +// defer ln.Close() +// for { +// conn, err := ln.AcceptSocks() +// if err != nil { +// if e, ok := err.(net.Error); ok && e.Temporary() { +// continue +// } +// return err +// } +// go handler(conn) +// } +// return nil +// } +// ... +// func main() { +// var err error +// ptInfo, err = pt.ClientSetup(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("proxy %s is not supported") +// os.Exit(1) +// } +// for _, methodName := range ptInfo.MethodNames { +// switch methodName { +// case "foo": +// ln, err := pt.ListenSocks("tcp", "127.0.0.1:0") +// if err != nil { +// pt.CmethodError(methodName, err.Error()) +// break +// } +// go acceptLoop(ln) +// pt.Cmethod(methodName, ln.Version(), ln.Addr()) +// default: +// pt.CmethodError(methodName, "no such method") +// } +// } +// pt.CmethodsDone() +// } +// +// Sample server usage: +// var ptInfo pt.ServerInfo +// ... +// func handler(conn net.Conn) error { +// defer conn.Close() +// or, err := pt.DialOr(&ptInfo, conn.RemoteAddr().String(), "foo") +// if err != nil { +// return +// } +// defer or.Close() +// // do something with or and conn +// return nil +// } +// func acceptLoop(ln net.Listener) error { +// defer ln.Close() +// for { +// conn, err := ln.Accept() +// if err != nil { +// if e, ok := err.(net.Error); ok && e.Temporary() { +// continue +// } +// return err +// } +// go handler(conn) +// } +// return nil +// } +// ... +// func main() { +// var err error +// ptInfo, err = pt.ServerSetup(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. +// <KeywordChar> ::= <any US-ASCII alphanumeric, dash, and underscore> +func keywordIsSafe(keyword string) bool { + for _, b := range []byte(keyword) { + if b >= '0' && b <= '9' { + continue + } + if b >= 'A' && b <= 'Z' { + continue + } + if b >= 'a' && b <= 'z' { + continue + } + if b == '-' || b == '_' { + continue + } + return false + } + return true +} + +// Returns true iff arg contains only bytes allowed in a PT→Tor output line arg. +// <ArgChar> ::= <any US-ASCII character but NUL or NL> +func argIsSafe(arg string) bool { + for _, b := range []byte(arg) { + if b >= '\x80' || b == '\x00' || b == '\n' { + return false + } + } + return true +} + +func formatline(keyword string, v ...string) string { + var buf bytes.Buffer + if !keywordIsSafe(keyword) { + panic(fmt.Sprintf("keyword %q contains forbidden bytes", keyword)) + } + buf.WriteString(keyword) + for _, x := range v { + if !argIsSafe(x) { + panic(fmt.Sprintf("arg %q contains forbidden bytes", x)) + } + buf.WriteString(" " + x) + } + return buf.String() +} + +// Print a pluggable transports protocol line to Stdout. The line consists of a +// keyword followed by any number of space-separated arg strings. Panics if +// there are forbidden bytes in the keyword or the args (pt-spec.txt 2.2.1). +func line(keyword string, v ...string) { + fmt.Fprintln(Stdout, formatline(keyword, v...)) +} + +// Emit and return the given error as a ptErr. +func doError(keyword string, v ...string) *ptErr { + line(keyword, v...) + return &ptErr{keyword, v} +} + +// Emit an ENV-ERROR line with explanation text. Returns a representation of the +// error. +func envError(msg string) error { + return doError("ENV-ERROR", msg) +} + +// Emit a VERSION-ERROR line with explanation text. Returns a representation of +// the error. +func versionError(msg string) error { + return doError("VERSION-ERROR", msg) +} + +// Emit a CMETHOD-ERROR line with explanation text. Returns a representation of +// the error. +func CmethodError(methodName, msg string) error { + return doError("CMETHOD-ERROR", methodName, msg) +} + +// Emit an SMETHOD-ERROR line with explanation text. Returns a representation of +// the error. +func SmethodError(methodName, msg string) error { + return doError("SMETHOD-ERROR", methodName, msg) +} + +// Emit a PROXY-ERROR line with explanation text. Returns a representation of +// the error. +func ProxyError(msg string) error { + return doError("PROXY-ERROR %s\n", msg) +} + +// Emit a CMETHOD line. socks must be "socks4" or "socks5". Call this once for +// each listening client SOCKS port. +func Cmethod(name string, socks string, addr net.Addr) { + line("CMETHOD", name, socks, addr.String()) +} + +// Emit a CMETHODS DONE line. Call this after opening all client listeners. +func CmethodsDone() { + line("CMETHODS", "DONE") +} + +// Emit an SMETHOD line. Call this once for each listening server port. +func Smethod(name string, addr net.Addr) { + line("SMETHOD", name, addr.String()) +} + +// Emit an SMETHOD line with an ARGS option. args is a name–value mapping that +// will be added to the server's extrainfo document. +// +// This is an example of how to check for a required option: +// secret, ok := bindaddr.Options.Get("shared-secret") +// if ok { +// args := pt.Args{} +// args.Add("shared-secret", secret) +// pt.SmethodArgs(bindaddr.MethodName, ln.Addr(), args) +// } else { +// pt.SmethodError(bindaddr.MethodName, "need a shared-secret option") +// } +// Or, if you just want to echo back the options provided by Tor from the +// TransportServerOptions configuration, +// pt.SmethodArgs(bindaddr.MethodName, ln.Addr(), bindaddr.Options) +func SmethodArgs(name string, addr net.Addr, args Args) { + line("SMETHOD", name, addr.String(), "ARGS:"+encodeSmethodArgs(args)) +} + +// Emit an SMETHODS DONE line. Call this after opening all server listeners. +func SmethodsDone() { + line("SMETHODS", "DONE") +} + +// Emit a PROXY DONE line. Call this after parsing ClientInfo.ProxyURL. +func ProxyDone() { + fmt.Fprintf(Stdout, "PROXY DONE\n") +} + +// Get a pluggable transports version offered by Tor and understood by us, if +// any. The only version we understand is "1". This function reads the +// environment variable TOR_PT_MANAGED_TRANSPORT_VER. +func getManagedTransportVer() (string, error) { + const transportVersion = "1" + managedTransportVer, err := GetenvRequired("TOR_PT_MANAGED_TRANSPORT_VER") + if err != nil { + return "", err + } + for _, offered := range strings.Split(managedTransportVer, ",") { + if offered == transportVersion { + return offered, nil + } + } + return "", versionError("no-version") +} + +// Return the directory name in the TOR_PT_STATE_LOCATION environment variable, +// creating it if it doesn't exist. Returns non-nil error if +// TOR_PT_STATE_LOCATION is not set or if there is an error creating the +// directory. +func MakeStateDir() (string, error) { + dir, err := GetenvRequired("TOR_PT_STATE_LOCATION") + if err != nil { + return "", err + } + err = os.MkdirAll(dir, 0700) + return dir, err +} + +// Get the 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://trac.torproject.org/projects/tor/ticket/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. + 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 + } + for _, spec := range strings.Split(serverBindaddr, ",") { + var bindaddr Bindaddr + + parts := strings.SplitN(spec, "-", 2) + if len(parts) != 2 { + return nil, envError(fmt.Sprintf("TOR_PT_SERVER_BINDADDR: %q: doesn't contain \"-\"", spec)) + } + bindaddr.MethodName = parts[0] + addr, err := ResolveAddr(parts[1]) + if err != nil { + return nil, envError(fmt.Sprintf("TOR_PT_SERVER_BINDADDR: %q: %s", spec, err.Error())) + } + bindaddr.Addr = addr + bindaddr.Options = optionsMap[bindaddr.MethodName] + result = append(result, bindaddr) + } + + // Filter by TOR_PT_SERVER_TRANSPORTS. + serverTransports, err := GetenvRequired("TOR_PT_SERVER_TRANSPORTS") + if err != nil { + return nil, err + } + result = FilterBindaddrs(result, strings.Split(serverTransports, ",")) + + return result, nil +} + +func readAuthCookie(f io.Reader) ([]byte, error) { + authCookieHeader := []byte("! Extended ORPort Auth Cookie !\x0a") + buf := make([]byte, 64) + + n, err := io.ReadFull(f, buf) + if err != nil { + return nil, err + } + // Check that the file ends here. + n, err = f.Read(make([]byte, 1)) + if n != 0 { + return nil, fmt.Errorf("file is longer than 64 bytes") + } else if err != io.EOF { + return nil, fmt.Errorf("did not find EOF at end of file") + } + header := buf[0:32] + cookie := buf[32:64] + if subtle.ConstantTimeCompare(header, authCookieHeader) != 1 { + return nil, fmt.Errorf("missing auth cookie header") + } + + return cookie, nil +} + +// Read and validate the contents of an auth cookie file. Returns the 32-byte +// cookie. See section 4.2.1.2 of pt-spec.txt. +func readAuthCookieFile(filename string) ([]byte, error) { + f, err := os.Open(filename) + if err != nil { + return nil, err + } + defer f.Close() + + return readAuthCookie(f) +} + +// This structure is returned by ServerSetup. It consists of a list of +// Bindaddrs, an address for the ORPort, an address for the extended ORPort (if +// any), and an authentication cookie (if any). +type ServerInfo struct { + Bindaddrs []Bindaddr + OrAddr *net.TCPAddr + ExtendedOrAddr *net.TCPAddr + AuthCookiePath string +} + +// Check the server pluggable transports environment, emitting an error message +// and returning a non-nil error if any error is encountered. 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://trac.torproject.org/projects/tor/ticket/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. + authCookie, err := readAuthCookieFile(info.AuthCookiePath) + if err != nil { + return fmt.Errorf("error reading TOR_PT_AUTH_COOKIE_FILE %q: %s", info.AuthCookiePath, err.Error()) + } + + expectedServerHash := computeServerHash(authCookie, clientNonce, serverNonce) + if subtle.ConstantTimeCompare(serverHash, expectedServerHash) != 1 { + return fmt.Errorf("mismatch in server hash") + } + + clientHash = computeClientHash(authCookie, clientNonce, serverNonce) + _, err = s.Write(clientHash) + if err != nil { + return err + } + + status := make([]byte, 1) + _, err = io.ReadFull(s, status) + if err != nil { + return err + } + if status[0] != 1 { + return fmt.Errorf("server rejected authentication") + } + + return nil +} + +// See section 3.1 of 196-transport-control-ports.txt. +const ( + extOrCmdDone = 0x0000 + extOrCmdUserAddr = 0x0001 + extOrCmdTransport = 0x0002 + extOrCmdOkay = 0x1000 + extOrCmdDeny = 0x1001 +) + +func extOrPortSendCommand(s io.Writer, cmd uint16, body []byte) error { + var buf bytes.Buffer + if len(body) > 65535 { + return fmt.Errorf("body length %d exceeds maximum of 65535", len(body)) + } + err := binary.Write(&buf, binary.BigEndian, cmd) + if err != nil { + return err + } + err = binary.Write(&buf, binary.BigEndian, uint16(len(body))) + if err != nil { + return err + } + err = binary.Write(&buf, binary.BigEndian, body) + if err != nil { + return err + } + _, err = s.Write(buf.Bytes()) + if err != nil { + return err + } + + return nil +} + +// Send a USERADDR command on s. See section 3.1.2.1 of +// 196-transport-control-ports.txt. +func extOrPortSendUserAddr(s io.Writer, addr string) error { + return extOrPortSendCommand(s, extOrCmdUserAddr, []byte(addr)) +} + +// Send a TRANSPORT command on s. See section 3.1.2.2 of +// 196-transport-control-ports.txt. +func extOrPortSendTransport(s io.Writer, methodName string) error { + return extOrPortSendCommand(s, extOrCmdTransport, []byte(methodName)) +} + +// Send a DONE command on s. See section 3.1 of 196-transport-control-ports.txt. +func extOrPortSendDone(s io.Writer) error { + return extOrPortSendCommand(s, extOrCmdDone, []byte{}) +} + +func extOrPortRecvCommand(s io.Reader) (cmd uint16, body []byte, err error) { + var bodyLen uint16 + data := make([]byte, 4) + + _, err = io.ReadFull(s, data) + if err != nil { + return + } + buf := bytes.NewBuffer(data) + err = binary.Read(buf, binary.BigEndian, &cmd) + if err != nil { + return + } + err = binary.Read(buf, binary.BigEndian, &bodyLen) + if err != nil { + return + } + body = make([]byte, bodyLen) + _, err = io.ReadFull(s, body) + if err != nil { + return + } + + return cmd, body, err +} + +// Send USERADDR and TRANSPORT commands followed by a DONE command. Wait for an +// OKAY or DENY response command from the server. If addr or methodName is "", +// the corresponding command is not sent. Returns nil if and only if OKAY is +// received. +func extOrPortSetup(s io.ReadWriter, addr, methodName string) error { + var err error + + if addr != "" { + err = extOrPortSendUserAddr(s, addr) + if err != nil { + return err + } + } + if methodName != "" { + err = extOrPortSendTransport(s, methodName) + if err != nil { + return err + } + } + err = extOrPortSendDone(s) + if err != nil { + return err + } + cmd, _, err := extOrPortRecvCommand(s) + if err != nil { + return err + } + if cmd == extOrCmdDeny { + return fmt.Errorf("server returned DENY after our USERADDR and DONE") + } else if cmd != extOrCmdOkay { + return fmt.Errorf("server returned unknown command 0x%04x after our USERADDR and DONE", cmd) + } + + return nil +} + +// Dial info.ExtendedOrAddr if defined, or else info.OrAddr, and return an open +// *net.TCPConn. If connecting to the extended OR port, extended OR port +// authentication à la 217-ext-orport-auth.txt is done before returning; an +// error is returned if authentication fails. +// +// The addr and methodName arguments are put in USERADDR and TRANSPORT ExtOrPort +// commands, respectively. If either is "", the corresponding command is not +// sent. +func DialOr(info *ServerInfo, addr, methodName string) (*net.TCPConn, error) { + if info.ExtendedOrAddr == nil || info.AuthCookiePath == "" { + return net.DialTCP("tcp", nil, info.OrAddr) + } + + s, err := net.DialTCP("tcp", nil, info.ExtendedOrAddr) + if err != nil { + return nil, err + } + s.SetDeadline(time.Now().Add(5 * time.Second)) + err = extOrPortAuthenticate(s, info) + if err != nil { + s.Close() + return nil, err + } + err = extOrPortSetup(s, addr, methodName) + if err != nil { + s.Close() + return nil, err + } + s.SetDeadline(time.Time{}) + + return s, nil +} diff --git a/vendor/github.com/OperatorFoundation/shapeshifter-ipc/pt2_socks.go b/vendor/github.com/OperatorFoundation/shapeshifter-ipc/pt2_socks.go new file mode 100644 index 0000000..eab74c6 --- /dev/null +++ b/vendor/github.com/OperatorFoundation/shapeshifter-ipc/pt2_socks.go @@ -0,0 +1,486 @@ +package pt + +import ( + "bufio" + "encoding/binary" + "fmt" + "io" + "net" + "time" +) + +const ( + socksVersion = 0x05 + + socksAuthNoneRequired = 0x00 + socksAuthPrivateMethodPT2 = 0x80 + socksAuthNoAcceptableMethods = 0xff + + socksCmdConnect = 0x01 + socksRsv = 0x00 + + socksAtypeV4 = 0x01 + socksAtypeDomainName = 0x03 + socksAtypeV6 = 0x04 + + 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 parsed contents of private authentication method 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, false) + 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, needOptions bool) (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, needOptions); 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, needOptions bool) (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 { + if needOptions { + switch m { + case socksAuthNoneRequired: + // Pick PT 2.0 private authentication method over None if the client happens to + // send both and SOCKS header options are needed. + if method == socksAuthNoAcceptableMethods { + method = m + } + + case socksAuthPrivateMethodPT2: + method = m + } + } else { + switch m { + case socksAuthPrivateMethodPT2: + // Pick None over PT 2.0 private authentication method if the client happens to + // send both and SOCKS header options are not needed. + if method == socksAuthNoAcceptableMethods { + method = m + } + + case socksAuthNoneRequired: + 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 socksAuthPrivateMethodPT2: + if err = socksAuthPT2(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 Pluggable Transports 2.0, draft 1 +// private auth method. +func socksAuthPT2(rw *bufio.ReadWriter, req *SocksRequest) (err error) { + // Read the authentication data. + var len uint32 + if len, err = socksReadUint32(rw); err != nil { + return + } + if len == 0 { + err = fmt.Errorf("PT 2.0 authentication data with 0 length") + return + } + var data []byte + if data, err = socksReadBytes(rw, int(len)); err != nil { + return + } + + var result string = string(data) + + // Parse the authentication data according to the PT 2.0 specification + if req.Args, err = ParsePT2ClientParameters(result); err != nil { + fmt.Println("Error parsing PT2 client parameters", err) + return + } + + 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 socksReadUint32(rw *bufio.ReadWriter) (uint32, error) { + buffer, err := socksReadBytes(rw, 4) + if err != nil { + return 0, err + } + + return binary.BigEndian.Uint32(buffer), nil +} + +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) |