telnet.go 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. package cli
  2. import (
  3. "bytes"
  4. "net"
  5. )
  6. const (
  7. iac = 255 // Interpret As Command
  8. dont = 254
  9. do = 253
  10. wont = 252
  11. will = 251
  12. sb = 250
  13. se = 240
  14. optEcho = 1
  15. optSuppressGoAhead = 3
  16. optTerminalType = 24
  17. optWindowSize = 31
  18. optLinemode = 34
  19. )
  20. // TelnetConn wraps a net.Conn and handles RFC 854 Telnet negotiations
  21. // while presenting a clean stream to terminal.NewTerminal.
  22. type TelnetConn struct {
  23. net.Conn
  24. subnegotiation bool
  25. }
  26. func NewTelnetConn(conn net.Conn) *TelnetConn {
  27. tc := &TelnetConn{Conn: conn}
  28. tc.initNegotiation()
  29. return tc
  30. }
  31. func (tc *TelnetConn) initNegotiation() {
  32. // Request client to suppress go-ahead and handle character-at-a-time / local echo off
  33. // IAC WILL ECHO, IAC WILL SUPPRESS_GO_AHEAD, IAC DONT LINEMODE
  34. initSeq := []byte{
  35. iac, will, optEcho,
  36. iac, will, optSuppressGoAhead,
  37. iac, dont, optLinemode,
  38. }
  39. _, _ = tc.Conn.Write(initSeq)
  40. }
  41. func (tc *TelnetConn) Read(p []byte) (n int, err error) {
  42. buf := make([]byte, len(p))
  43. rawN, err := tc.Conn.Read(buf)
  44. if err != nil {
  45. return 0, err
  46. }
  47. var clean bytes.Buffer
  48. i := 0
  49. for i < rawN {
  50. b := buf[i]
  51. if b == iac {
  52. if i+1 >= rawN {
  53. break
  54. }
  55. cmd := buf[i+1]
  56. switch cmd {
  57. case iac: // Escaped 255
  58. clean.WriteByte(iac)
  59. i += 2
  60. case will, wont, do, dont:
  61. if i+2 < rawN {
  62. opt := buf[i+2]
  63. tc.handleOption(cmd, opt)
  64. i += 3
  65. } else {
  66. i = rawN
  67. }
  68. case sb: // Subnegotiation start
  69. tc.subnegotiation = true
  70. i += 2
  71. case se: // Subnegotiation end
  72. tc.subnegotiation = false
  73. i += 2
  74. default:
  75. i += 2
  76. }
  77. continue
  78. }
  79. if tc.subnegotiation {
  80. i++
  81. continue
  82. }
  83. // Normalize CR NUL or CR LF into LF for standard terminal handling
  84. if b == '\r' {
  85. if i+1 < rawN && (buf[i+1] == '\n' || buf[i+1] == 0) {
  86. clean.WriteByte('\n')
  87. i += 2
  88. continue
  89. }
  90. clean.WriteByte('\n')
  91. i++
  92. continue
  93. }
  94. clean.WriteByte(b)
  95. i++
  96. }
  97. res := clean.Bytes()
  98. copy(p, res)
  99. return len(res), nil
  100. }
  101. func (tc *TelnetConn) handleOption(cmd, opt byte) {
  102. // Respond to DO ECHO or DO SUPPRESS_GO_AHEAD with WILL
  103. switch cmd {
  104. case do:
  105. if opt == optEcho || opt == optSuppressGoAhead {
  106. _, _ = tc.Conn.Write([]byte{iac, will, opt})
  107. } else {
  108. _, _ = tc.Conn.Write([]byte{iac, wont, opt})
  109. }
  110. case dont:
  111. _, _ = tc.Conn.Write([]byte{iac, wont, opt})
  112. }
  113. }
  114. func (tc *TelnetConn) Write(p []byte) (n int, err error) {
  115. // Ensure \n is transmitted as \r\n for telnet standard compatibility
  116. var out bytes.Buffer
  117. for _, b := range p {
  118. if b == '\n' {
  119. out.WriteByte('\r')
  120. }
  121. out.WriteByte(b)
  122. }
  123. return tc.Conn.Write(out.Bytes())
  124. }