summaryrefslogtreecommitdiff
path: root/vendor/github.com/OperatorFoundation/shapeshifter-ipc
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/OperatorFoundation/shapeshifter-ipc')
-rw-r--r--vendor/github.com/OperatorFoundation/shapeshifter-ipc/.gitignore2
-rw-r--r--vendor/github.com/OperatorFoundation/shapeshifter-ipc/COPYING121
-rw-r--r--vendor/github.com/OperatorFoundation/shapeshifter-ipc/README27
-rw-r--r--vendor/github.com/OperatorFoundation/shapeshifter-ipc/args.go265
-rw-r--r--vendor/github.com/OperatorFoundation/shapeshifter-ipc/pt.go941
-rw-r--r--vendor/github.com/OperatorFoundation/shapeshifter-ipc/pt2_socks.go486
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)