| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849 |
- // 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() {
- // return err
- // }
- // continue
- // }
- // go handler(conn)
- // }
- // return nil
- // }
- // ...
- // func main() {
- // var err error
- // ptInfo, err = pt.ClientSetup([]string{"foo"})
- // if err != nil {
- // 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() {
- // return err
- // }
- // continue
- // }
- // go handler(conn)
- // }
- // return nil
- // }
- // ...
- // func main() {
- // var err error
- // ptInfo, err = pt.ServerSetup([]string{"foo"})
- // 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 SIGINT and shutdown properly. See
- // the example programs dummy-client and dummy-server.
- //
- // Tor pluggable transports specification:
- // https://gitweb.torproject.org/torspec.git/blob/HEAD:/pt-spec.txt.
- //
- // Extended ORPort:
- // https://gitweb.torproject.org/torspec.git/blob/HEAD:/proposals/196-transport-control-ports.txt.
- //
- // Extended ORPort Authentication:
- // https://gitweb.torproject.org/torspec.git/blob/HEAD:/proposals/217-ext-orport-auth.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"
- "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
- }
- // Escape a string so it contains no byte values over 127 and doesn't contain
- // any of the characters '\x00' or '\n'.
- func escape(s string) string {
- var buf bytes.Buffer
- for _, b := range []byte(s) {
- if b == '\n' {
- buf.WriteString("\\n")
- } else if b == '\\' {
- buf.WriteString("\\\\")
- } else if 0 < b && b < 128 {
- buf.WriteByte(b)
- } else {
- fmt.Fprintf(&buf, "\\x%02x", b)
- }
- }
- return buf.String()
- }
- func formatline(keyword string, v ...string) string {
- var buf bytes.Buffer
- buf.WriteString(keyword)
- for _, x := range v {
- buf.WriteString(" " + escape(x))
- }
- return buf.String()
- }
- // Print a pluggable transports protocol line to Stdout. The line consists of an
- // unescaped keyword, followed by any number of escaped strings.
- 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 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")
- }
- // 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 intersection of the method names offered by Tor and those in
- // methodNames. This function reads the environment variable
- // TOR_PT_CLIENT_TRANSPORTS.
- func getClientTransports(star []string) ([]string, error) {
- clientTransports, err := getenvRequired("TOR_PT_CLIENT_TRANSPORTS")
- if err != nil {
- return nil, err
- }
- if clientTransports == "*" {
- return star, nil
- }
- return strings.Split(clientTransports, ","), nil
- }
- // This structure is returned by ClientSetup. It consists of a list of method
- // names.
- type ClientInfo struct {
- MethodNames []string
- }
- // Check the client pluggable transports environment, emitting an error message
- // and returning a non-nil error if any error is encountered. star is the list
- // of method names to use in case "*" is requested by Tor. 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.
- // }
- func ClientSetup(star []string) (info ClientInfo, err error) {
- ver, err := getManagedTransportVer()
- if err != nil {
- return
- }
- line("VERSION", ver)
- info.MethodNames, err = getClientTransports(star)
- 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. If TOR_PT_SERVER_TRANSPORTS
- // is "*", then keys are filtered by the entries in star instead.
- // Transport-specific options from TOR_PT_SERVER_TRANSPORT_OPTIONS are assigned
- // to the Options member.
- func getServerBindaddrs(star []string) ([]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
- }
- if serverTransports == "*" {
- result = filterBindaddrs(result, star)
- } else {
- 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
- AuthCookie []byte
- }
- // Check the server pluggable transports environment, emitting an error message
- // and returning a non-nil error if any error is encountered. star is the list
- // of method names to use in case "*" is requested by Tor. 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.
- // }
- func ServerSetup(star []string) (info ServerInfo, err error) {
- ver, err := getManagedTransportVer()
- if err != nil {
- return
- }
- line("VERSION", ver)
- info.Bindaddrs, err = getServerBindaddrs(star)
- 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
- }
- }
- extendedOrPort := getenv("TOR_PT_EXTENDED_SERVER_PORT")
- if extendedOrPort != "" {
- 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
- }
- }
- authCookieFilename := getenv("TOR_PT_AUTH_COOKIE_FILE")
- if authCookieFilename != "" {
- info.AuthCookie, err = readAuthCookieFile(authCookieFilename)
- if err != nil {
- err = envError(fmt.Sprintf("error reading TOR_PT_AUTH_COOKIE_FILE %q: %s", authCookieFilename, err.Error()))
- return
- }
- }
- // Need either OrAddr or ExtendedOrAddr.
- if info.OrAddr == nil && (info.ExtendedOrAddr == nil || info.AuthCookie == 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
- }
- expectedServerHash := computeServerHash(info.AuthCookie, clientNonce, serverNonce)
- if subtle.ConstantTimeCompare(serverHash, expectedServerHash) != 1 {
- return fmt.Errorf("mismatch in server hash")
- }
- clientHash = computeClientHash(info.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.AuthCookie == nil {
- 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
- }
|