github.com/ooni/psiphon/tunnel-core@v0.0.0-20230105123940-fe12a24c96ee/oovendor/goptlib/pt.go (about)

     1  // Package pt implements the Tor pluggable transports specification.
     2  //
     3  // Sample client usage:
     4  // 	var ptInfo pt.ClientInfo
     5  // 	...
     6  // 	func handler(conn *pt.SocksConn) error {
     7  // 		defer conn.Close()
     8  // 		remote, err := net.Dial("tcp", conn.Req.Target)
     9  // 		if err != nil {
    10  // 			conn.Reject()
    11  // 			return err
    12  // 		}
    13  // 		defer remote.Close()
    14  // 		err = conn.Grant(remote.RemoteAddr().(*net.TCPAddr))
    15  // 		if err != nil {
    16  // 			return err
    17  // 		}
    18  // 		// do something with conn and remote.
    19  // 		return nil
    20  // 	}
    21  // 	func acceptLoop(ln *pt.SocksListener) error {
    22  // 		defer ln.Close()
    23  // 		for {
    24  // 			conn, err := ln.AcceptSocks()
    25  // 			if err != nil {
    26  // 				if e, ok := err.(net.Error); ok && !e.Temporary() {
    27  // 					return err
    28  // 				}
    29  // 				continue
    30  // 			}
    31  // 			go handler(conn)
    32  // 		}
    33  // 		return nil
    34  // 	}
    35  // 	...
    36  // 	func main() {
    37  // 		var err error
    38  // 		ptInfo, err = pt.ClientSetup([]string{"foo"})
    39  // 		if err != nil {
    40  // 			os.Exit(1)
    41  // 		}
    42  // 		for _, methodName := range ptInfo.MethodNames {
    43  // 			switch methodName {
    44  // 			case "foo":
    45  // 				ln, err := pt.ListenSocks("tcp", "127.0.0.1:0")
    46  // 				if err != nil {
    47  // 					pt.CmethodError(methodName, err.Error())
    48  // 					break
    49  // 				}
    50  // 				go acceptLoop(ln)
    51  // 				pt.Cmethod(methodName, ln.Version(), ln.Addr())
    52  // 			default:
    53  // 				pt.CmethodError(methodName, "no such method")
    54  // 			}
    55  // 		}
    56  // 		pt.CmethodsDone()
    57  // 	}
    58  //
    59  // Sample server usage:
    60  // 	var ptInfo pt.ServerInfo
    61  // 	...
    62  // 	func handler(conn net.Conn) error {
    63  // 		defer conn.Close()
    64  // 		or, err := pt.DialOr(&ptInfo, conn.RemoteAddr().String(), "foo")
    65  // 		if err != nil {
    66  // 			return
    67  // 		}
    68  // 		defer or.Close()
    69  // 		// do something with or and conn
    70  // 		return nil
    71  // 	}
    72  // 	func acceptLoop(ln net.Listener) error {
    73  // 		defer ln.Close()
    74  // 		for {
    75  // 			conn, err := ln.Accept()
    76  // 			if err != nil {
    77  // 				if e, ok := err.(net.Error); ok && !e.Temporary() {
    78  // 					return err
    79  // 				}
    80  // 				continue
    81  // 			}
    82  // 			go handler(conn)
    83  // 		}
    84  // 		return nil
    85  // 	}
    86  // 	...
    87  // 	func main() {
    88  // 		var err error
    89  // 		ptInfo, err = pt.ServerSetup([]string{"foo"})
    90  // 		if err != nil {
    91  // 			os.Exit(1)
    92  // 		}
    93  // 		for _, bindaddr := range ptInfo.Bindaddrs {
    94  // 			switch bindaddr.MethodName {
    95  // 			case "foo":
    96  // 				ln, err := net.ListenTCP("tcp", bindaddr.Addr)
    97  // 				if err != nil {
    98  // 					pt.SmethodError(bindaddr.MethodName, err.Error())
    99  // 					break
   100  // 				}
   101  // 				go acceptLoop(ln)
   102  // 				pt.Smethod(bindaddr.MethodName, ln.Addr())
   103  // 			default:
   104  // 				pt.SmethodError(bindaddr.MethodName, "no such method")
   105  // 			}
   106  // 		}
   107  // 		pt.SmethodsDone()
   108  // 	}
   109  //
   110  // Some additional care is needed to handle SIGINT and shutdown properly. See
   111  // the example programs dummy-client and dummy-server.
   112  //
   113  // Tor pluggable transports specification:
   114  // https://gitweb.torproject.org/torspec.git/blob/HEAD:/pt-spec.txt.
   115  //
   116  // Extended ORPort:
   117  // https://gitweb.torproject.org/torspec.git/blob/HEAD:/proposals/196-transport-control-ports.txt.
   118  //
   119  // Extended ORPort Authentication:
   120  // https://gitweb.torproject.org/torspec.git/blob/HEAD:/proposals/217-ext-orport-auth.txt.
   121  //
   122  // The package implements a SOCKS5 server sufficient for a Tor client transport
   123  // plugin.
   124  //
   125  // https://www.ietf.org/rfc/rfc1928.txt
   126  // https://www.ietf.org/rfc/rfc1929.txt
   127  package pt
   128  
   129  import (
   130  	"bytes"
   131  	"crypto/hmac"
   132  	"crypto/rand"
   133  	"crypto/sha256"
   134  	"crypto/subtle"
   135  	"encoding/binary"
   136  	"fmt"
   137  	"io"
   138  	"net"
   139  	"os"
   140  	"strconv"
   141  	"strings"
   142  	"time"
   143  )
   144  
   145  // This type wraps a Write method and calls Sync after each Write.
   146  type syncWriter struct {
   147  	*os.File
   148  }
   149  
   150  // Call File.Write and then Sync. An error is returned if either operation
   151  // returns an error.
   152  func (w syncWriter) Write(p []byte) (n int, err error) {
   153  	n, err = w.File.Write(p)
   154  	if err != nil {
   155  		return
   156  	}
   157  	err = w.Sync()
   158  	return
   159  }
   160  
   161  // Writer to which pluggable transports negotiation messages are written. It
   162  // defaults to a Writer that writes to os.Stdout and calls Sync after each
   163  // write.
   164  //
   165  // You may, for example, log pluggable transports messages by defining a Writer
   166  // that logs what is written to it:
   167  // 	type logWriteWrapper struct {
   168  // 		io.Writer
   169  // 	}
   170  //
   171  // 	func (w logWriteWrapper) Write(p []byte) (int, error) {
   172  // 		log.Print(string(p))
   173  // 		return w.Writer.Write(p)
   174  // 	}
   175  // and then redefining Stdout:
   176  // 	pt.Stdout = logWriteWrapper{pt.Stdout}
   177  var Stdout io.Writer = syncWriter{os.Stdout}
   178  
   179  // Represents an error that can happen during negotiation, for example
   180  // ENV-ERROR. When an error occurs, we print it to stdout and also pass it up
   181  // the return chain.
   182  type ptErr struct {
   183  	Keyword string
   184  	Args    []string
   185  }
   186  
   187  // Implements the error interface.
   188  func (err *ptErr) Error() string {
   189  	return formatline(err.Keyword, err.Args...)
   190  }
   191  
   192  func getenv(key string) string {
   193  	return os.Getenv(key)
   194  }
   195  
   196  // Returns an ENV-ERROR if the environment variable isn't set.
   197  func getenvRequired(key string) (string, error) {
   198  	value := os.Getenv(key)
   199  	if value == "" {
   200  		return "", envError(fmt.Sprintf("no %s environment variable", key))
   201  	}
   202  	return value, nil
   203  }
   204  
   205  // Escape a string so it contains no byte values over 127 and doesn't contain
   206  // any of the characters '\x00' or '\n'.
   207  func escape(s string) string {
   208  	var buf bytes.Buffer
   209  	for _, b := range []byte(s) {
   210  		if b == '\n' {
   211  			buf.WriteString("\\n")
   212  		} else if b == '\\' {
   213  			buf.WriteString("\\\\")
   214  		} else if 0 < b && b < 128 {
   215  			buf.WriteByte(b)
   216  		} else {
   217  			fmt.Fprintf(&buf, "\\x%02x", b)
   218  		}
   219  	}
   220  	return buf.String()
   221  }
   222  
   223  func formatline(keyword string, v ...string) string {
   224  	var buf bytes.Buffer
   225  	buf.WriteString(keyword)
   226  	for _, x := range v {
   227  		buf.WriteString(" " + escape(x))
   228  	}
   229  	return buf.String()
   230  }
   231  
   232  // Print a pluggable transports protocol line to Stdout. The line consists of an
   233  // unescaped keyword, followed by any number of escaped strings.
   234  func line(keyword string, v ...string) {
   235  	fmt.Fprintln(Stdout, formatline(keyword, v...))
   236  }
   237  
   238  // Emit and return the given error as a ptErr.
   239  func doError(keyword string, v ...string) *ptErr {
   240  	line(keyword, v...)
   241  	return &ptErr{keyword, v}
   242  }
   243  
   244  // Emit an ENV-ERROR line with explanation text. Returns a representation of the
   245  // error.
   246  func envError(msg string) error {
   247  	return doError("ENV-ERROR", msg)
   248  }
   249  
   250  // Emit a VERSION-ERROR line with explanation text. Returns a representation of
   251  // the error.
   252  func versionError(msg string) error {
   253  	return doError("VERSION-ERROR", msg)
   254  }
   255  
   256  // Emit a CMETHOD-ERROR line with explanation text. Returns a representation of
   257  // the error.
   258  func CmethodError(methodName, msg string) error {
   259  	return doError("CMETHOD-ERROR", methodName, msg)
   260  }
   261  
   262  // Emit an SMETHOD-ERROR line with explanation text. Returns a representation of
   263  // the error.
   264  func SmethodError(methodName, msg string) error {
   265  	return doError("SMETHOD-ERROR", methodName, msg)
   266  }
   267  
   268  // Emit a CMETHOD line. socks must be "socks4" or "socks5". Call this once for
   269  // each listening client SOCKS port.
   270  func Cmethod(name string, socks string, addr net.Addr) {
   271  	line("CMETHOD", name, socks, addr.String())
   272  }
   273  
   274  // Emit a CMETHODS DONE line. Call this after opening all client listeners.
   275  func CmethodsDone() {
   276  	line("CMETHODS", "DONE")
   277  }
   278  
   279  // Emit an SMETHOD line. Call this once for each listening server port.
   280  func Smethod(name string, addr net.Addr) {
   281  	line("SMETHOD", name, addr.String())
   282  }
   283  
   284  // Emit an SMETHOD line with an ARGS option. args is a name–value mapping that
   285  // will be added to the server's extrainfo document.
   286  //
   287  // This is an example of how to check for a required option:
   288  // 	secret, ok := bindaddr.Options.Get("shared-secret")
   289  // 	if ok {
   290  // 		args := pt.Args{}
   291  // 		args.Add("shared-secret", secret)
   292  // 		pt.SmethodArgs(bindaddr.MethodName, ln.Addr(), args)
   293  // 	} else {
   294  // 		pt.SmethodError(bindaddr.MethodName, "need a shared-secret option")
   295  // 	}
   296  // Or, if you just want to echo back the options provided by Tor from the
   297  // TransportServerOptions configuration,
   298  // 	pt.SmethodArgs(bindaddr.MethodName, ln.Addr(), bindaddr.Options)
   299  func SmethodArgs(name string, addr net.Addr, args Args) {
   300  	line("SMETHOD", name, addr.String(), "ARGS:"+encodeSmethodArgs(args))
   301  }
   302  
   303  // Emit an SMETHODS DONE line. Call this after opening all server listeners.
   304  func SmethodsDone() {
   305  	line("SMETHODS", "DONE")
   306  }
   307  
   308  // Get a pluggable transports version offered by Tor and understood by us, if
   309  // any. The only version we understand is "1". This function reads the
   310  // environment variable TOR_PT_MANAGED_TRANSPORT_VER.
   311  func getManagedTransportVer() (string, error) {
   312  	const transportVersion = "1"
   313  	managedTransportVer, err := getenvRequired("TOR_PT_MANAGED_TRANSPORT_VER")
   314  	if err != nil {
   315  		return "", err
   316  	}
   317  	for _, offered := range strings.Split(managedTransportVer, ",") {
   318  		if offered == transportVersion {
   319  			return offered, nil
   320  		}
   321  	}
   322  	return "", versionError("no-version")
   323  }
   324  
   325  // Return the directory name in the TOR_PT_STATE_LOCATION environment variable,
   326  // creating it if it doesn't exist. Returns non-nil error if
   327  // TOR_PT_STATE_LOCATION is not set or if there is an error creating the
   328  // directory.
   329  func MakeStateDir() (string, error) {
   330  	dir, err := getenvRequired("TOR_PT_STATE_LOCATION")
   331  	if err != nil {
   332  		return "", err
   333  	}
   334  	err = os.MkdirAll(dir, 0700)
   335  	return dir, err
   336  }
   337  
   338  // Get the intersection of the method names offered by Tor and those in
   339  // methodNames. This function reads the environment variable
   340  // TOR_PT_CLIENT_TRANSPORTS.
   341  func getClientTransports(star []string) ([]string, error) {
   342  	clientTransports, err := getenvRequired("TOR_PT_CLIENT_TRANSPORTS")
   343  	if err != nil {
   344  		return nil, err
   345  	}
   346  	if clientTransports == "*" {
   347  		return star, nil
   348  	}
   349  	return strings.Split(clientTransports, ","), nil
   350  }
   351  
   352  // This structure is returned by ClientSetup. It consists of a list of method
   353  // names.
   354  type ClientInfo struct {
   355  	MethodNames []string
   356  }
   357  
   358  // Check the client pluggable transports environment, emitting an error message
   359  // and returning a non-nil error if any error is encountered. star is the list
   360  // of method names to use in case "*" is requested by Tor. Returns a ClientInfo
   361  // struct.
   362  //
   363  // If your program needs to know whether to call ClientSetup or ServerSetup
   364  // (i.e., if the same program can be run as either a client or a server), check
   365  // whether the TOR_PT_CLIENT_TRANSPORTS environment variable is set:
   366  // 	if os.Getenv("TOR_PT_CLIENT_TRANSPORTS") != "" {
   367  // 		// Client mode; call pt.ClientSetup.
   368  // 	} else {
   369  // 		// Server mode; call pt.ServerSetup.
   370  // 	}
   371  func ClientSetup(star []string) (info ClientInfo, err error) {
   372  	ver, err := getManagedTransportVer()
   373  	if err != nil {
   374  		return
   375  	}
   376  	line("VERSION", ver)
   377  
   378  	info.MethodNames, err = getClientTransports(star)
   379  	if err != nil {
   380  		return
   381  	}
   382  
   383  	return info, nil
   384  }
   385  
   386  // A combination of a method name and an address, as extracted from
   387  // TOR_PT_SERVER_BINDADDR.
   388  type Bindaddr struct {
   389  	MethodName string
   390  	Addr       *net.TCPAddr
   391  	// Options from TOR_PT_SERVER_TRANSPORT_OPTIONS that pertain to this
   392  	// transport.
   393  	Options Args
   394  }
   395  
   396  func parsePort(portStr string) (int, error) {
   397  	port, err := strconv.ParseUint(portStr, 10, 16)
   398  	return int(port), err
   399  }
   400  
   401  // Resolve an address string into a net.TCPAddr. We are a bit more strict than
   402  // net.ResolveTCPAddr; we don't allow an empty host or port, and the host part
   403  // must be a literal IP address.
   404  func resolveAddr(addrStr string) (*net.TCPAddr, error) {
   405  	ipStr, portStr, err := net.SplitHostPort(addrStr)
   406  	if err != nil {
   407  		// Before the fixing of bug #7011, tor doesn't put brackets around IPv6
   408  		// addresses. Split after the last colon, assuming it is a port
   409  		// separator, and try adding the brackets.
   410  		parts := strings.Split(addrStr, ":")
   411  		if len(parts) <= 2 {
   412  			return nil, err
   413  		}
   414  		addrStr := "[" + strings.Join(parts[:len(parts)-1], ":") + "]:" + parts[len(parts)-1]
   415  		ipStr, portStr, err = net.SplitHostPort(addrStr)
   416  	}
   417  	if err != nil {
   418  		return nil, err
   419  	}
   420  	if ipStr == "" {
   421  		return nil, net.InvalidAddrError(fmt.Sprintf("address string %q lacks a host part", addrStr))
   422  	}
   423  	if portStr == "" {
   424  		return nil, net.InvalidAddrError(fmt.Sprintf("address string %q lacks a port part", addrStr))
   425  	}
   426  	ip := net.ParseIP(ipStr)
   427  	if ip == nil {
   428  		return nil, net.InvalidAddrError(fmt.Sprintf("not an IP string: %q", ipStr))
   429  	}
   430  	port, err := parsePort(portStr)
   431  	if err != nil {
   432  		return nil, err
   433  	}
   434  	return &net.TCPAddr{IP: ip, Port: port}, nil
   435  }
   436  
   437  // Return a new slice, the members of which are those members of addrs having a
   438  // MethodName in methodNames.
   439  func filterBindaddrs(addrs []Bindaddr, methodNames []string) []Bindaddr {
   440  	var result []Bindaddr
   441  
   442  	for _, ba := range addrs {
   443  		for _, methodName := range methodNames {
   444  			if ba.MethodName == methodName {
   445  				result = append(result, ba)
   446  				break
   447  			}
   448  		}
   449  	}
   450  
   451  	return result
   452  }
   453  
   454  // Return an array of Bindaddrs, being the contents of TOR_PT_SERVER_BINDADDR
   455  // with keys filtered by TOR_PT_SERVER_TRANSPORTS. If TOR_PT_SERVER_TRANSPORTS
   456  // is "*", then keys are filtered by the entries in star instead.
   457  // Transport-specific options from TOR_PT_SERVER_TRANSPORT_OPTIONS are assigned
   458  // to the Options member.
   459  func getServerBindaddrs(star []string) ([]Bindaddr, error) {
   460  	var result []Bindaddr
   461  
   462  	// Parse the list of server transport options.
   463  	serverTransportOptions := getenv("TOR_PT_SERVER_TRANSPORT_OPTIONS")
   464  	optionsMap, err := parseServerTransportOptions(serverTransportOptions)
   465  	if err != nil {
   466  		return nil, envError(fmt.Sprintf("TOR_PT_SERVER_TRANSPORT_OPTIONS: %q: %s", serverTransportOptions, err.Error()))
   467  	}
   468  
   469  	// Get the list of all requested bindaddrs.
   470  	serverBindaddr, err := getenvRequired("TOR_PT_SERVER_BINDADDR")
   471  	if err != nil {
   472  		return nil, err
   473  	}
   474  	for _, spec := range strings.Split(serverBindaddr, ",") {
   475  		var bindaddr Bindaddr
   476  
   477  		parts := strings.SplitN(spec, "-", 2)
   478  		if len(parts) != 2 {
   479  			return nil, envError(fmt.Sprintf("TOR_PT_SERVER_BINDADDR: %q: doesn't contain \"-\"", spec))
   480  		}
   481  		bindaddr.MethodName = parts[0]
   482  		addr, err := resolveAddr(parts[1])
   483  		if err != nil {
   484  			return nil, envError(fmt.Sprintf("TOR_PT_SERVER_BINDADDR: %q: %s", spec, err.Error()))
   485  		}
   486  		bindaddr.Addr = addr
   487  		bindaddr.Options = optionsMap[bindaddr.MethodName]
   488  		result = append(result, bindaddr)
   489  	}
   490  
   491  	// Filter by TOR_PT_SERVER_TRANSPORTS.
   492  	serverTransports, err := getenvRequired("TOR_PT_SERVER_TRANSPORTS")
   493  	if err != nil {
   494  		return nil, err
   495  	}
   496  	if serverTransports == "*" {
   497  		result = filterBindaddrs(result, star)
   498  	} else {
   499  		result = filterBindaddrs(result, strings.Split(serverTransports, ","))
   500  	}
   501  
   502  	return result, nil
   503  }
   504  
   505  func readAuthCookie(f io.Reader) ([]byte, error) {
   506  	authCookieHeader := []byte("! Extended ORPort Auth Cookie !\x0a")
   507  	buf := make([]byte, 64)
   508  
   509  	n, err := io.ReadFull(f, buf)
   510  	if err != nil {
   511  		return nil, err
   512  	}
   513  	// Check that the file ends here.
   514  	n, err = f.Read(make([]byte, 1))
   515  	if n != 0 {
   516  		return nil, fmt.Errorf("file is longer than 64 bytes")
   517  	} else if err != io.EOF {
   518  		return nil, fmt.Errorf("did not find EOF at end of file")
   519  	}
   520  	header := buf[0:32]
   521  	cookie := buf[32:64]
   522  	if subtle.ConstantTimeCompare(header, authCookieHeader) != 1 {
   523  		return nil, fmt.Errorf("missing auth cookie header")
   524  	}
   525  
   526  	return cookie, nil
   527  }
   528  
   529  // Read and validate the contents of an auth cookie file. Returns the 32-byte
   530  // cookie. See section 4.2.1.2 of pt-spec.txt.
   531  func readAuthCookieFile(filename string) ([]byte, error) {
   532  	f, err := os.Open(filename)
   533  	if err != nil {
   534  		return nil, err
   535  	}
   536  	defer f.Close()
   537  
   538  	return readAuthCookie(f)
   539  }
   540  
   541  // This structure is returned by ServerSetup. It consists of a list of
   542  // Bindaddrs, an address for the ORPort, an address for the extended ORPort (if
   543  // any), and an authentication cookie (if any).
   544  type ServerInfo struct {
   545  	Bindaddrs      []Bindaddr
   546  	OrAddr         *net.TCPAddr
   547  	ExtendedOrAddr *net.TCPAddr
   548  	AuthCookie     []byte
   549  }
   550  
   551  // Check the server pluggable transports environment, emitting an error message
   552  // and returning a non-nil error if any error is encountered. star is the list
   553  // of method names to use in case "*" is requested by Tor. Resolves the various
   554  // requested bind addresses, the server ORPort and extended ORPort, and reads
   555  // the auth cookie file. Returns a ServerInfo struct.
   556  //
   557  // If your program needs to know whether to call ClientSetup or ServerSetup
   558  // (i.e., if the same program can be run as either a client or a server), check
   559  // whether the TOR_PT_CLIENT_TRANSPORTS environment variable is set:
   560  // 	if os.Getenv("TOR_PT_CLIENT_TRANSPORTS") != "" {
   561  // 		// Client mode; call pt.ClientSetup.
   562  // 	} else {
   563  // 		// Server mode; call pt.ServerSetup.
   564  // 	}
   565  func ServerSetup(star []string) (info ServerInfo, err error) {
   566  	ver, err := getManagedTransportVer()
   567  	if err != nil {
   568  		return
   569  	}
   570  	line("VERSION", ver)
   571  
   572  	info.Bindaddrs, err = getServerBindaddrs(star)
   573  	if err != nil {
   574  		return
   575  	}
   576  
   577  	orPort := getenv("TOR_PT_ORPORT")
   578  	if orPort != "" {
   579  		info.OrAddr, err = resolveAddr(orPort)
   580  		if err != nil {
   581  			err = envError(fmt.Sprintf("cannot resolve TOR_PT_ORPORT %q: %s", orPort, err.Error()))
   582  			return
   583  		}
   584  	}
   585  
   586  	extendedOrPort := getenv("TOR_PT_EXTENDED_SERVER_PORT")
   587  	if extendedOrPort != "" {
   588  		info.ExtendedOrAddr, err = resolveAddr(extendedOrPort)
   589  		if err != nil {
   590  			err = envError(fmt.Sprintf("cannot resolve TOR_PT_EXTENDED_SERVER_PORT %q: %s", extendedOrPort, err.Error()))
   591  			return
   592  		}
   593  	}
   594  	authCookieFilename := getenv("TOR_PT_AUTH_COOKIE_FILE")
   595  	if authCookieFilename != "" {
   596  		info.AuthCookie, err = readAuthCookieFile(authCookieFilename)
   597  		if err != nil {
   598  			err = envError(fmt.Sprintf("error reading TOR_PT_AUTH_COOKIE_FILE %q: %s", authCookieFilename, err.Error()))
   599  			return
   600  		}
   601  	}
   602  
   603  	// Need either OrAddr or ExtendedOrAddr.
   604  	if info.OrAddr == nil && (info.ExtendedOrAddr == nil || info.AuthCookie == nil) {
   605  		err = envError("need TOR_PT_ORPORT or TOR_PT_EXTENDED_SERVER_PORT environment variable")
   606  		return
   607  	}
   608  
   609  	return info, nil
   610  }
   611  
   612  // See 217-ext-orport-auth.txt section 4.2.1.3.
   613  func computeServerHash(authCookie, clientNonce, serverNonce []byte) []byte {
   614  	h := hmac.New(sha256.New, authCookie)
   615  	io.WriteString(h, "ExtORPort authentication server-to-client hash")
   616  	h.Write(clientNonce)
   617  	h.Write(serverNonce)
   618  	return h.Sum([]byte{})
   619  }
   620  
   621  // See 217-ext-orport-auth.txt section 4.2.1.3.
   622  func computeClientHash(authCookie, clientNonce, serverNonce []byte) []byte {
   623  	h := hmac.New(sha256.New, authCookie)
   624  	io.WriteString(h, "ExtORPort authentication client-to-server hash")
   625  	h.Write(clientNonce)
   626  	h.Write(serverNonce)
   627  	return h.Sum([]byte{})
   628  }
   629  
   630  func extOrPortAuthenticate(s io.ReadWriter, info *ServerInfo) error {
   631  	// Read auth types. 217-ext-orport-auth.txt section 4.1.
   632  	var authTypes [256]bool
   633  	var count int
   634  	for count = 0; count < 256; count++ {
   635  		buf := make([]byte, 1)
   636  		_, err := io.ReadFull(s, buf)
   637  		if err != nil {
   638  			return err
   639  		}
   640  		b := buf[0]
   641  		if b == 0 {
   642  			break
   643  		}
   644  		authTypes[b] = true
   645  	}
   646  	if count >= 256 {
   647  		return fmt.Errorf("read 256 auth types without seeing \\x00")
   648  	}
   649  
   650  	// We support only type 1, SAFE_COOKIE.
   651  	if !authTypes[1] {
   652  		return fmt.Errorf("server didn't offer auth type 1")
   653  	}
   654  	_, err := s.Write([]byte{1})
   655  	if err != nil {
   656  		return err
   657  	}
   658  
   659  	clientNonce := make([]byte, 32)
   660  	clientHash := make([]byte, 32)
   661  	serverNonce := make([]byte, 32)
   662  	serverHash := make([]byte, 32)
   663  
   664  	_, err = io.ReadFull(rand.Reader, clientNonce)
   665  	if err != nil {
   666  		return err
   667  	}
   668  	_, err = s.Write(clientNonce)
   669  	if err != nil {
   670  		return err
   671  	}
   672  
   673  	_, err = io.ReadFull(s, serverHash)
   674  	if err != nil {
   675  		return err
   676  	}
   677  	_, err = io.ReadFull(s, serverNonce)
   678  	if err != nil {
   679  		return err
   680  	}
   681  
   682  	expectedServerHash := computeServerHash(info.AuthCookie, clientNonce, serverNonce)
   683  	if subtle.ConstantTimeCompare(serverHash, expectedServerHash) != 1 {
   684  		return fmt.Errorf("mismatch in server hash")
   685  	}
   686  
   687  	clientHash = computeClientHash(info.AuthCookie, clientNonce, serverNonce)
   688  	_, err = s.Write(clientHash)
   689  	if err != nil {
   690  		return err
   691  	}
   692  
   693  	status := make([]byte, 1)
   694  	_, err = io.ReadFull(s, status)
   695  	if err != nil {
   696  		return err
   697  	}
   698  	if status[0] != 1 {
   699  		return fmt.Errorf("server rejected authentication")
   700  	}
   701  
   702  	return nil
   703  }
   704  
   705  // See section 3.1 of 196-transport-control-ports.txt.
   706  const (
   707  	extOrCmdDone      = 0x0000
   708  	extOrCmdUserAddr  = 0x0001
   709  	extOrCmdTransport = 0x0002
   710  	extOrCmdOkay      = 0x1000
   711  	extOrCmdDeny      = 0x1001
   712  )
   713  
   714  func extOrPortSendCommand(s io.Writer, cmd uint16, body []byte) error {
   715  	var buf bytes.Buffer
   716  	if len(body) > 65535 {
   717  		return fmt.Errorf("body length %d exceeds maximum of 65535", len(body))
   718  	}
   719  	err := binary.Write(&buf, binary.BigEndian, cmd)
   720  	if err != nil {
   721  		return err
   722  	}
   723  	err = binary.Write(&buf, binary.BigEndian, uint16(len(body)))
   724  	if err != nil {
   725  		return err
   726  	}
   727  	err = binary.Write(&buf, binary.BigEndian, body)
   728  	if err != nil {
   729  		return err
   730  	}
   731  	_, err = s.Write(buf.Bytes())
   732  	if err != nil {
   733  		return err
   734  	}
   735  
   736  	return nil
   737  }
   738  
   739  // Send a USERADDR command on s. See section 3.1.2.1 of
   740  // 196-transport-control-ports.txt.
   741  func extOrPortSendUserAddr(s io.Writer, addr string) error {
   742  	return extOrPortSendCommand(s, extOrCmdUserAddr, []byte(addr))
   743  }
   744  
   745  // Send a TRANSPORT command on s. See section 3.1.2.2 of
   746  // 196-transport-control-ports.txt.
   747  func extOrPortSendTransport(s io.Writer, methodName string) error {
   748  	return extOrPortSendCommand(s, extOrCmdTransport, []byte(methodName))
   749  }
   750  
   751  // Send a DONE command on s. See section 3.1 of 196-transport-control-ports.txt.
   752  func extOrPortSendDone(s io.Writer) error {
   753  	return extOrPortSendCommand(s, extOrCmdDone, []byte{})
   754  }
   755  
   756  func extOrPortRecvCommand(s io.Reader) (cmd uint16, body []byte, err error) {
   757  	var bodyLen uint16
   758  	data := make([]byte, 4)
   759  
   760  	_, err = io.ReadFull(s, data)
   761  	if err != nil {
   762  		return
   763  	}
   764  	buf := bytes.NewBuffer(data)
   765  	err = binary.Read(buf, binary.BigEndian, &cmd)
   766  	if err != nil {
   767  		return
   768  	}
   769  	err = binary.Read(buf, binary.BigEndian, &bodyLen)
   770  	if err != nil {
   771  		return
   772  	}
   773  	body = make([]byte, bodyLen)
   774  	_, err = io.ReadFull(s, body)
   775  	if err != nil {
   776  		return
   777  	}
   778  
   779  	return cmd, body, err
   780  }
   781  
   782  // Send USERADDR and TRANSPORT commands followed by a DONE command. Wait for an
   783  // OKAY or DENY response command from the server. If addr or methodName is "",
   784  // the corresponding command is not sent. Returns nil if and only if OKAY is
   785  // received.
   786  func extOrPortSetup(s io.ReadWriter, addr, methodName string) error {
   787  	var err error
   788  
   789  	if addr != "" {
   790  		err = extOrPortSendUserAddr(s, addr)
   791  		if err != nil {
   792  			return err
   793  		}
   794  	}
   795  	if methodName != "" {
   796  		err = extOrPortSendTransport(s, methodName)
   797  		if err != nil {
   798  			return err
   799  		}
   800  	}
   801  	err = extOrPortSendDone(s)
   802  	if err != nil {
   803  		return err
   804  	}
   805  	cmd, _, err := extOrPortRecvCommand(s)
   806  	if err != nil {
   807  		return err
   808  	}
   809  	if cmd == extOrCmdDeny {
   810  		return fmt.Errorf("server returned DENY after our USERADDR and DONE")
   811  	} else if cmd != extOrCmdOkay {
   812  		return fmt.Errorf("server returned unknown command 0x%04x after our USERADDR and DONE", cmd)
   813  	}
   814  
   815  	return nil
   816  }
   817  
   818  // Dial info.ExtendedOrAddr if defined, or else info.OrAddr, and return an open
   819  // *net.TCPConn. If connecting to the extended OR port, extended OR port
   820  // authentication à la 217-ext-orport-auth.txt is done before returning; an
   821  // error is returned if authentication fails.
   822  //
   823  // The addr and methodName arguments are put in USERADDR and TRANSPORT ExtOrPort
   824  // commands, respectively. If either is "", the corresponding command is not
   825  // sent.
   826  func DialOr(info *ServerInfo, addr, methodName string) (*net.TCPConn, error) {
   827  	if info.ExtendedOrAddr == nil || info.AuthCookie == nil {
   828  		return net.DialTCP("tcp", nil, info.OrAddr)
   829  	}
   830  
   831  	s, err := net.DialTCP("tcp", nil, info.ExtendedOrAddr)
   832  	if err != nil {
   833  		return nil, err
   834  	}
   835  	s.SetDeadline(time.Now().Add(5 * time.Second))
   836  	err = extOrPortAuthenticate(s, info)
   837  	if err != nil {
   838  		s.Close()
   839  		return nil, err
   840  	}
   841  	err = extOrPortSetup(s, addr, methodName)
   842  	if err != nil {
   843  		s.Close()
   844  		return nil, err
   845  	}
   846  	s.SetDeadline(time.Time{})
   847  
   848  	return s, nil
   849  }