blob: 5cedeeb75e733aaefad76adb143cb2ae8eea660b [file] [log] [blame]
// Package telnet provides simple interface for interacting with Telnet
// connection.
package telnet
import (
"bufio"
"bytes"
"fmt"
"net"
"time"
"unicode"
)
const (
CR = byte('\r')
LF = byte('\n')
)
const (
cmdSE = 240
cmdNOP = 241
cmdData = 242
cmdBreak = 243
cmdGA = 249
cmdSB = 250
cmdWill = 251
cmdWont = 252
cmdDo = 253
cmdDont = 254
cmdIAC = 255
)
const (
optEcho = 1
optSuppressGoAhead = 3
// optTerminalType = 24
optNAWS = 31
)
// Conn implements net.Conn interface for Telnet protocol plus some set of
// Telnet specific methods.
type Conn struct {
net.Conn
r *bufio.Reader
unixWriteMode bool
cliSuppressGoAhead bool
cliEcho bool
}
func NewConn(conn net.Conn) (*Conn, error) {
c := Conn{
Conn: conn,
r: bufio.NewReaderSize(conn, 256),
}
return &c, nil
}
func Dial(network, addr string) (*Conn, error) {
conn, err := net.Dial(network, addr)
if err != nil {
return nil, err
}
return NewConn(conn)
}
func DialTimeout(network, addr string, timeout time.Duration) (*Conn, error) {
conn, err := net.DialTimeout(network, addr, timeout)
if err != nil {
return nil, err
}
return NewConn(conn)
}
// SetUnixWriteMode sets flag that applies only to the Write method.
// If set, Write converts any '\n' (LF) to '\r\n' (CR LF).
func (c *Conn) SetUnixWriteMode(uwm bool) {
c.unixWriteMode = uwm
}
func (c *Conn) do(option byte) error {
//log.Println("do:", option)
_, err := c.Conn.Write([]byte{cmdIAC, cmdDo, option})
return err
}
func (c *Conn) dont(option byte) error {
//log.Println("dont:", option)
_, err := c.Conn.Write([]byte{cmdIAC, cmdDont, option})
return err
}
func (c *Conn) will(option byte) error {
//log.Println("will:", option)
_, err := c.Conn.Write([]byte{cmdIAC, cmdWill, option})
return err
}
func (c *Conn) wont(option byte) error {
//log.Println("wont:", option)
_, err := c.Conn.Write([]byte{cmdIAC, cmdWont, option})
return err
}
func (c *Conn) sub(opt byte, data ...byte) error {
if _, err := c.Conn.Write([]byte{cmdIAC, cmdSB, opt}); err != nil {
return err
}
if _, err := c.Conn.Write(data); err != nil {
return err
}
_, err := c.Conn.Write([]byte{cmdIAC, cmdSE})
return err
}
func (c *Conn) deny(cmd, opt byte) (err error) {
switch cmd {
case cmdDo:
err = c.wont(opt)
case cmdDont:
// nop
case cmdWill, cmdWont:
err = c.dont(opt)
}
return
}
func (c *Conn) skipSubneg() error {
for {
if b, err := c.r.ReadByte(); err != nil {
return err
} else if b == cmdIAC {
if b, err = c.r.ReadByte(); err != nil {
return err
} else if b == cmdSE {
return nil
}
}
}
}
func (c *Conn) cmd(cmd byte) error {
switch cmd {
case cmdGA:
return nil
case cmdDo, cmdDont, cmdWill, cmdWont:
// Process cmd after this switch.
case cmdSB:
return c.skipSubneg()
default:
return fmt.Errorf("unknown command: %d", cmd)
}
// Read an option
o, err := c.r.ReadByte()
if err != nil {
return err
}
//log.Println("received cmd:", cmd, o)
switch o {
case optEcho:
// Accept any echo configuration.
switch cmd {
case cmdDo:
if !c.cliEcho {
c.cliEcho = true
err = c.will(o)
}
case cmdDont:
if c.cliEcho {
c.cliEcho = false
err = c.wont(o)
}
case cmdWill:
if !c.cliEcho {
c.cliEcho = true
err = c.do(o)
}
case cmdWont:
if c.cliEcho {
c.cliEcho = false
err = c.dont(o)
}
}
case optSuppressGoAhead:
// We don't use GA so can allways accept every configuration
switch cmd {
case cmdDo:
if !c.cliSuppressGoAhead {
c.cliSuppressGoAhead = true
err = c.will(o)
}
case cmdDont:
if c.cliSuppressGoAhead {
c.cliSuppressGoAhead = false
err = c.wont(o)
}
case cmdWill:
if !c.cliSuppressGoAhead {
c.cliSuppressGoAhead = true
err = c.do(o)
}
case cmdWont:
if c.cliSuppressGoAhead {
c.cliSuppressGoAhead = false
err = c.dont(o)
}
}
case optNAWS:
if cmd != cmdDo {
err = c.deny(cmd, o)
break
}
if err = c.will(o); err != nil {
break
}
// Reply with max window size: 65535x65535
err = c.sub(o, 255, 255, 255, 255)
default:
// Deny any other option
err = c.deny(cmd, o)
}
return err
}
func (c *Conn) tryReadByte() (b byte, retry bool, err error) {
b, err = c.r.ReadByte()
if err != nil || b != cmdIAC {
return
}
b, err = c.r.ReadByte()
if err != nil {
return
}
if b != cmdIAC {
err = c.cmd(b)
if err != nil {
return
}
retry = true
}
return
}
// SetEcho tries to enable/disable echo on server side. Typically telnet
// servers doesn't support this.
func (c *Conn) SetEcho(echo bool) error {
if echo {
return c.do(optEcho)
}
return c.dont(optEcho)
}
// ReadByte works like bufio.ReadByte
func (c *Conn) ReadByte() (b byte, err error) {
retry := true
for retry && err == nil {
b, retry, err = c.tryReadByte()
}
return
}
// ReadRune works like bufio.ReadRune
func (c *Conn) ReadRune() (r rune, size int, err error) {
loop:
r, size, err = c.r.ReadRune()
if err != nil {
return
}
if r != unicode.ReplacementChar || size != 1 {
// Properly readed rune
return
}
// Bad rune
err = c.r.UnreadRune()
if err != nil {
return
}
// Read telnet command or escaped IAC
_, retry, err := c.tryReadByte()
if err != nil {
return
}
if retry {
// This bad rune was a begining of telnet command. Try read next rune.
goto loop
}
// Return escaped IAC as unicode.ReplacementChar
return
}
// Read is for implement an io.Reader interface
func (c *Conn) Read(buf []byte) (int, error) {
var n int
for n < len(buf) {
b, retry, err := c.tryReadByte()
if err != nil {
return n, err
}
if !retry {
buf[n] = b
n++
}
if n > 0 && c.r.Buffered() == 0 {
// Don't block if can't return more data.
return n, err
}
}
return n, nil
}
// ReadBytes works like bufio.ReadBytes
func (c *Conn) ReadBytes(delim byte) ([]byte, error) {
var line []byte
for {
b, err := c.ReadByte()
if err != nil {
return nil, err
}
line = append(line, b)
if b == delim {
break
}
}
return line, nil
}
// SkipBytes works like ReadBytes but skips all read data.
func (c *Conn) SkipBytes(delim byte) error {
for {
b, err := c.ReadByte()
if err != nil {
return err
}
if b == delim {
break
}
}
return nil
}
// ReadString works like bufio.ReadString
func (c *Conn) ReadString(delim byte) (string, error) {
bytes, err := c.ReadBytes(delim)
return string(bytes), err
}
func (c *Conn) readUntil(read bool, delims ...string) ([]byte, int, error) {
if len(delims) == 0 {
return nil, 0, nil
}
p := make([]string, len(delims))
for i, s := range delims {
if len(s) == 0 {
return nil, 0, nil
}
p[i] = s
}
var line []byte
for {
b, err := c.ReadByte()
if err != nil {
return nil, 0, err
}
if read {
line = append(line, b)
}
for i, s := range p {
if s[0] == b {
if len(s) == 1 {
return line, i, nil
}
p[i] = s[1:]
} else {
p[i] = delims[i]
}
}
}
panic(nil)
}
// ReadUntilIndex reads from connection until one of delimiters occurs. Returns
// read data and an index of delimiter or error.
func (c *Conn) ReadUntilIndex(delims ...string) ([]byte, int, error) {
return c.readUntil(true, delims...)
}
// ReadUntil works like ReadUntilIndex but don't return a delimiter index.
func (c *Conn) ReadUntil(delims ...string) ([]byte, error) {
d, _, err := c.readUntil(true, delims...)
return d, err
}
// SkipUntilIndex works like ReadUntilIndex but skips all read data.
func (c *Conn) SkipUntilIndex(delims ...string) (int, error) {
_, i, err := c.readUntil(false, delims...)
return i, err
}
// SkipUntil works like ReadUntil but skips all read data.
func (c *Conn) SkipUntil(delims ...string) error {
_, _, err := c.readUntil(false, delims...)
return err
}
// Write is for implement an io.Writer interface
func (c *Conn) Write(buf []byte) (int, error) {
search := "\xff"
if c.unixWriteMode {
search = "\xff\n"
}
var (
n int
err error
)
for len(buf) > 0 {
var k int
i := bytes.IndexAny(buf, search)
if i == -1 {
k, err = c.Conn.Write(buf)
n += k
break
}
k, err = c.Conn.Write(buf[:i])
n += k
if err != nil {
break
}
switch buf[i] {
case LF:
k, err = c.Conn.Write([]byte{CR, LF})
case cmdIAC:
k, err = c.Conn.Write([]byte{cmdIAC, cmdIAC})
}
n += k
if err != nil {
break
}
buf = buf[i+1:]
}
return n, err
}