diff options
author | kali kaneko (leap communications) <kali@leap.se> | 2021-04-14 16:54:42 +0200 |
---|---|---|
committer | kali kaneko (leap communications) <kali@leap.se> | 2021-04-14 16:54:42 +0200 |
commit | 67a0eb7111d3f89e4a0cb21e43aefe6d87d37e04 (patch) | |
tree | c9b18e0da6e06ac165a485ee957b7850adb12e86 /vendor/github.com/OperatorFoundation/shapeshifter-ipc/pt.go | |
parent | 2e8f2a2e8e83fd89f956cdde886d5d9d808132da (diff) |
[pkg] go mod vendor to build debian/ubuntu packages
Diffstat (limited to 'vendor/github.com/OperatorFoundation/shapeshifter-ipc/pt.go')
-rw-r--r-- | vendor/github.com/OperatorFoundation/shapeshifter-ipc/pt.go | 941 |
1 files changed, 941 insertions, 0 deletions
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 +} |