202 lines
4.8 KiB
Go
202 lines
4.8 KiB
Go
package tclient
|
|
|
|
import (
|
|
"time"
|
|
"fmt"
|
|
"bytes"
|
|
"github.com/pkg/errors"
|
|
"regexp"
|
|
)
|
|
|
|
// ReadUntil reads tcp stream from server until 'waitfor' regex matches.
|
|
// Returns gathered output and error, if any.
|
|
// Any escape sequences are cutted out during reading for providing clean output for parsing/reading.
|
|
func (c *TelnetClient) ReadUntil(waitfor string) (string, error) {
|
|
var err error
|
|
|
|
|
|
// one more dirty hack =\ After dlink paging, it MAY BE, or MAY NOT BE one unnecessary empty string with pages + \n. We should strip it.
|
|
var paged bool
|
|
var b byte
|
|
//var prev byte
|
|
c.buf.Reset()
|
|
//var buf bytes.Buffer
|
|
var lastLine bytes.Buffer
|
|
if waitfor == "" {
|
|
return c.buf.String(), fmt.Errorf(`Empty "waitfor" string given`)
|
|
}
|
|
|
|
rePrompt, err := regexp.Compile(waitfor)
|
|
if err != nil {
|
|
return c.buf.String(), fmt.Errorf(`Cannot compile "waitfor" regexp`)
|
|
}
|
|
|
|
// run reading cycle
|
|
inSequence := false
|
|
//skipCurLine := false
|
|
globalTout := time.After(time.Second * time.Duration(c.TimeoutGlobal))
|
|
for {
|
|
select {
|
|
//case <- c.ctx.Done():
|
|
// return c.cutEscapes(buf), nil
|
|
case <- globalTout:
|
|
return c.buf.String() + lastLine.String(), fmt.Errorf("Operation timeout reached during read")
|
|
default:
|
|
//prev = b
|
|
b, err = c.readByte()
|
|
if err != nil {
|
|
return c.buf.String() + lastLine.String(), errors.Wrap(err, "Error during read")
|
|
}
|
|
|
|
// catch escape sequences
|
|
if b == TELNET_IAC {
|
|
seq := []byte{b}
|
|
|
|
b2, err := c.readByte()
|
|
if err != nil {
|
|
return c.buf.String(), errors.Wrap(err, "Error while reading escape sequence")
|
|
}
|
|
|
|
seq = append(seq, b2)
|
|
if b2 == TELNET_SB { // subnegotiation
|
|
// read all until subneg. end.
|
|
for {
|
|
bn, err := c.readByte()
|
|
if err != nil {
|
|
return c.buf.String(), errors.Wrap(err, "Error while reading escape subnegotiation sequence")
|
|
}
|
|
seq = append(seq, bn)
|
|
if bn == TELNET_SE {
|
|
break
|
|
}
|
|
}
|
|
} else {
|
|
// not subsequence.
|
|
bn, err := c.readByte()
|
|
if err != nil {
|
|
return c.buf.String(), errors.Wrap(err, "Error while reading IAC sequence")
|
|
}
|
|
seq = append(seq, bn)
|
|
}
|
|
|
|
// Sequence finished, do something with it:
|
|
err = c.negotiate(seq)
|
|
if err != nil {
|
|
return c.buf.String(), errors.Wrap(err, "Failed to negotiate connection")
|
|
//c.errChan <- fmt.Sprintf("Failed to negotiate connection: %s", err.Error())
|
|
}
|
|
}
|
|
|
|
// cut out escape sequences
|
|
if b == 27 {
|
|
inSequence = true
|
|
continue
|
|
}
|
|
if inSequence {
|
|
// 2) 0-?, @-~, ' ' - / === 48-63, 32-47, finish with 64-126
|
|
if b == 91 {
|
|
continue
|
|
}
|
|
if b >= 32 && b <= 63 {
|
|
// just skip it
|
|
continue
|
|
}
|
|
if b >= 64 && b <= 126 {
|
|
// finish sequence
|
|
inSequence = false
|
|
continue
|
|
}
|
|
}
|
|
|
|
// not IAC sequence, but IAC char =\
|
|
if b == TELNET_IAC {
|
|
continue
|
|
}
|
|
|
|
// remove \r ; remove backspaces
|
|
if b == 8 {
|
|
if lastLine.Len() > 0 {
|
|
lastLine.Truncate(lastLine.Len() - 1)
|
|
}
|
|
continue
|
|
}
|
|
if b == '\r' {
|
|
continue
|
|
}
|
|
|
|
//fmt.Printf("%s | %d\n", string(b), b)
|
|
//fmt.Printf("%s", string(b))
|
|
|
|
// this is not escape sequence, so write this byte to buffer
|
|
// update: strip '\r'
|
|
/*if b != '\r' {
|
|
c.buf.Write([]byte{b})
|
|
}*/
|
|
|
|
// check for regex matching. Execute callback if matched.
|
|
if len(c.patterns) > 0 {
|
|
for i := range c.patterns {
|
|
if c.patterns[i].Re.Match(lastLine.Bytes()) {
|
|
c.patterns[i].Cb()
|
|
lastLine.Reset()
|
|
// if last 2 chars in buffer are '\n\n', remove last '\n'
|
|
bts := c.buf.Bytes()
|
|
if len(bts) > 2 && bts[len(bts)-1] == '\n' && bts[len(bts)-2] == '\n' {
|
|
c.buf.Truncate(c.buf.Len()-1)
|
|
}
|
|
paged = true
|
|
}
|
|
}
|
|
}
|
|
|
|
// check for CRLF.
|
|
// We need last line to compare with prompt.
|
|
//if b == '\n' && prev == '\r' {
|
|
if b == '\n' {
|
|
lastLine.Write([]byte{b})
|
|
|
|
if paged {
|
|
paged = false
|
|
// only spaces without any other chars, \n at the end
|
|
if match, err := regexp.Match(`(?msi:^[\s^\n]+$)`, lastLine.Bytes()); match && err == nil {
|
|
lastLine.Reset()
|
|
continue
|
|
}
|
|
}
|
|
|
|
c.buf.Write(lastLine.Bytes())
|
|
lastLine.Reset()
|
|
} else {
|
|
lastLine.Write([]byte{b})
|
|
}
|
|
|
|
// After reading, we should check for regexp every time.
|
|
// Unfortunately, we cant wait only CRLF, because prompt usually comes without CRLF.
|
|
if rePrompt.Match(lastLine.Bytes()) {
|
|
return c.buf.String(), nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// read one byte from tcp stream
|
|
func (c *TelnetClient) readByte() (byte, error) {
|
|
var err error
|
|
var buffer [1]byte
|
|
p := buffer[:]
|
|
|
|
err = c.conn.SetReadDeadline(time.Now().Add(time.Second * time.Duration(c.Timeout)))
|
|
if err != nil {
|
|
return p[0], err
|
|
}
|
|
|
|
_, err = c.conn.Read(p)
|
|
// error during read
|
|
if err != nil {
|
|
return p[0], errors.Wrap(err, "Error during readByte")
|
|
}
|
|
|
|
return p[0], nil
|
|
}
|