| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138 |
- package cli
- import (
- "bytes"
- "net"
- )
- const (
- iac = 255 // Interpret As Command
- dont = 254
- do = 253
- wont = 252
- will = 251
- sb = 250
- se = 240
- optEcho = 1
- optSuppressGoAhead = 3
- optTerminalType = 24
- optWindowSize = 31
- optLinemode = 34
- )
- // TelnetConn wraps a net.Conn and handles RFC 854 Telnet negotiations
- // while presenting a clean stream to terminal.NewTerminal.
- type TelnetConn struct {
- net.Conn
- subnegotiation bool
- }
- func NewTelnetConn(conn net.Conn) *TelnetConn {
- tc := &TelnetConn{Conn: conn}
- tc.initNegotiation()
- return tc
- }
- func (tc *TelnetConn) initNegotiation() {
- // Request client to suppress go-ahead and handle character-at-a-time / local echo off
- // IAC WILL ECHO, IAC WILL SUPPRESS_GO_AHEAD, IAC DONT LINEMODE
- initSeq := []byte{
- iac, will, optEcho,
- iac, will, optSuppressGoAhead,
- iac, dont, optLinemode,
- }
- _, _ = tc.Conn.Write(initSeq)
- }
- func (tc *TelnetConn) Read(p []byte) (n int, err error) {
- buf := make([]byte, len(p))
- rawN, err := tc.Conn.Read(buf)
- if err != nil {
- return 0, err
- }
- var clean bytes.Buffer
- i := 0
- for i < rawN {
- b := buf[i]
- if b == iac {
- if i+1 >= rawN {
- break
- }
- cmd := buf[i+1]
- switch cmd {
- case iac: // Escaped 255
- clean.WriteByte(iac)
- i += 2
- case will, wont, do, dont:
- if i+2 < rawN {
- opt := buf[i+2]
- tc.handleOption(cmd, opt)
- i += 3
- } else {
- i = rawN
- }
- case sb: // Subnegotiation start
- tc.subnegotiation = true
- i += 2
- case se: // Subnegotiation end
- tc.subnegotiation = false
- i += 2
- default:
- i += 2
- }
- continue
- }
- if tc.subnegotiation {
- i++
- continue
- }
- // Normalize CR NUL or CR LF into LF for standard terminal handling
- if b == '\r' {
- if i+1 < rawN && (buf[i+1] == '\n' || buf[i+1] == 0) {
- clean.WriteByte('\n')
- i += 2
- continue
- }
- clean.WriteByte('\n')
- i++
- continue
- }
- clean.WriteByte(b)
- i++
- }
- res := clean.Bytes()
- copy(p, res)
- return len(res), nil
- }
- func (tc *TelnetConn) handleOption(cmd, opt byte) {
- // Respond to DO ECHO or DO SUPPRESS_GO_AHEAD with WILL
- switch cmd {
- case do:
- if opt == optEcho || opt == optSuppressGoAhead {
- _, _ = tc.Conn.Write([]byte{iac, will, opt})
- } else {
- _, _ = tc.Conn.Write([]byte{iac, wont, opt})
- }
- case dont:
- _, _ = tc.Conn.Write([]byte{iac, wont, opt})
- }
- }
- func (tc *TelnetConn) Write(p []byte) (n int, err error) {
- // Ensure \n is transmitted as \r\n for telnet standard compatibility
- var out bytes.Buffer
- for _, b := range p {
- if b == '\n' {
- out.WriteByte('\r')
- }
- out.WriteByte(b)
- }
- return tc.Conn.Write(out.Bytes())
- }
|