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 }