Skip to content

Commit ebc60be

Browse files
committedDec 26, 2017
Initial implementation
1 parent 5910f1f commit ebc60be

11 files changed

+880
-0
lines changed
 

‎README.md

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Package telnet
2+
3+
The [`telnet` package](http://godoc.org/github.com/aprice/telnet) provides basic
4+
telnet client and server implementations for Go, including handling of IACs and
5+
extensible telnet option negotiation.
6+
7+
Currently both the basic client and server are implemented, as well as a NAWS
8+
client/server handler as a working example. Additional handlers may be added to
9+
the core library over time (feel free to submit a PR if you've written one you'd
10+
like to see added!)
11+
12+
## Usage
13+
14+
Running a server:
15+
```go
16+
svr := telnet.NewServer(":9999", telnet.HandleFunc(func(c *telnet.Connection){
17+
log.Printf("Connection received: %s", c.RemoteAddr())
18+
c.Write([]byte("Hello world!\r\n"))
19+
c.Close()
20+
}))
21+
svr.ListenAndServe()
22+
```
23+
24+
The server API is modeled after the `net/http` API, so it should be easy to get
25+
your bearings; of course, telnet and HTTP are very different beasts, so the
26+
similarities are somewhat limited. The server listens on a TCP address for new
27+
connections. Whenever a new connection is received, the connection handler is
28+
called with the connection object. This object is a wrapper for the underlying
29+
TCP connection, which aims to transparently handle IAC. There is a slightly
30+
more complex example located in the `example` package.
31+
32+
Running a client is pretty simple:
33+
```go
34+
conn, err := telnet.Dial("127.0.0.1:9999")
35+
```
36+
37+
This is really straightforward - dial out, get a telnet connection handler back.
38+
Again, this handles IAC transparently, and like the Server, can take a list of
39+
optional IAC handlers. Bear in mind that some handlers - for example, the
40+
included NAWS handler - use different Option functions to register them with a
41+
client versus a server; this is because they may behave differently at each end.
42+
See the documentation for the options for more details.
43+
44+
## Linereader
45+
46+
A sub-package, `linereader`, exposes a simple reader intended to be run in a
47+
Goroutine, which consumes lines from an `io.Reader` and sends them over a
48+
channel for asynchronous handling.

‎ansi.go

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package telnet
2+
3+
// Telnet IAC constants
4+
const (
5+
SE = byte(240)
6+
NOP = byte(241)
7+
BRK = byte(243)
8+
IP = byte(244)
9+
AO = byte(245)
10+
AYT = byte(246)
11+
EC = byte(247)
12+
EL = byte(248)
13+
GA = byte(249)
14+
SB = byte(250)
15+
WILL = byte(251)
16+
WONT = byte(252)
17+
DO = byte(253)
18+
DONT = byte(254)
19+
IAC = byte(255)
20+
)
21+
22+
const Escape = byte('\033')
23+
24+
// ANSI control sequences
25+
var (
26+
// styles
27+
Reset = []byte("\033[0m")
28+
Bold = []byte("\033[1m")
29+
Underline = []byte("\033[4m")
30+
Conceal = []byte("\033[8m")
31+
NormalWeight = []byte("\033[22m")
32+
NoUnderline = []byte("\033[24m")
33+
Reveal = []byte("\033[28m")
34+
// colors - foreground
35+
FGBlack = []byte("\033[30m")
36+
FGRed = []byte("\033[31m")
37+
FGGreen = []byte("\033[32m")
38+
FGYellow = []byte("\033[33m")
39+
FGBlue = []byte("\033[34m")
40+
FGMagenta = []byte("\033[35m")
41+
FGCyan = []byte("\033[36m")
42+
FGWhite = []byte("\033[37m")
43+
FGDefault = []byte("\033[39m")
44+
// background
45+
BGBlack = []byte("\033[40m")
46+
BGRed = []byte("\033[41m")
47+
BGGreen = []byte("\033[42m")
48+
BGYellow = []byte("\033[43m")
49+
BGBlue = []byte("\033[44m")
50+
BGMagenta = []byte("\033[45m")
51+
BGCyan = []byte("\033[46m")
52+
BGWhite = []byte("\033[47m")
53+
BGDefault = []byte("\033[49m")
54+
// xterm
55+
TitleBarFmt = "\033]0;%s\a"
56+
)

‎client.go

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package telnet
2+
3+
import (
4+
"net"
5+
)
6+
7+
// Dial establishes a telnet connection with the remote host specified by addr
8+
// in host:port format. Any specified option handlers will be applied to the
9+
// connection if it is successful.
10+
func Dial(addr string, options ...Option) (conn *Connection, err error) {
11+
c, err := net.Dial("tcp", addr)
12+
if err != nil {
13+
return
14+
}
15+
conn = NewConnection(c, options)
16+
return
17+
}

‎connection.go

+239
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
package telnet
2+
3+
import (
4+
"fmt"
5+
"net"
6+
"time"
7+
)
8+
9+
// Negotiator defines the requirements for a telnet option handler.
10+
type Negotiator interface {
11+
// OptionCode returns the 1-byte option code that indicates this option.
12+
OptionCode() byte
13+
// Offer is called when a new connection is initiated. It offers the handler
14+
// an opportunity to advertise or request an option.
15+
Offer(conn *Connection)
16+
// HandleDo is called when an IAC DO command is received for this option,
17+
// indicating the client is requesting the option to be enabled.
18+
HandleDo(conn *Connection)
19+
// HandleWill is called when an IAC WILL command is received for this
20+
// option, indicating the client is willing to enable this option.
21+
HandleWill(conn *Connection)
22+
// HandleSB is called when a subnegotiation command is received for this
23+
// option. body contains the bytes between `IAC SB <OptionCode>` and `IAC
24+
// SE`.
25+
HandleSB(conn *Connection, body []byte)
26+
}
27+
28+
// Connection to the telnet server. This lightweight TCPConn wrapper handles
29+
// telnet control sequences transparently in reads and writes, and provides
30+
// handling of supported options.
31+
type Connection struct {
32+
// The underlying network connection.
33+
net.Conn
34+
35+
// OptionHandlers handle IAC options; the key is the IAC option code.
36+
OptionHandlers map[byte]Negotiator
37+
38+
// Read buffer
39+
buf []byte
40+
r, w int // buf read and write positions
41+
42+
// IAC handling
43+
iac bool
44+
cmd byte
45+
option byte
46+
47+
// Known client wont/dont
48+
clientWont map[byte]bool
49+
clientDont map[byte]bool
50+
}
51+
52+
// NewConnection initializes a new Connection for this given TCPConn. It will
53+
// register all the given Option handlers and call Offer() on each, in order.
54+
func NewConnection(c net.Conn, options []Option) *Connection {
55+
conn := &Connection{
56+
Conn: c,
57+
OptionHandlers: make(map[byte]Negotiator, len(options)),
58+
buf: make([]byte, 256),
59+
clientWont: make(map[byte]bool),
60+
clientDont: make(map[byte]bool),
61+
}
62+
for _, o := range options {
63+
h := o(conn)
64+
conn.OptionHandlers[h.OptionCode()] = h
65+
h.Offer(conn)
66+
}
67+
return conn
68+
}
69+
70+
// Write to the connection, escaping IAC as necessary.
71+
func (c *Connection) Write(b []byte) (n int, err error) {
72+
var nn, lastWrite int
73+
for i, ch := range b {
74+
if ch == IAC {
75+
if lastWrite < i-1 {
76+
nn, err = c.Conn.Write(b[lastWrite:i])
77+
n += nn
78+
if err != nil {
79+
return
80+
}
81+
}
82+
lastWrite = i + 1
83+
nn, err = c.Conn.Write([]byte{IAC, IAC})
84+
n += nn
85+
if err != nil {
86+
return
87+
}
88+
}
89+
}
90+
if lastWrite < len(b) {
91+
nn, err = c.Conn.Write(b[lastWrite:])
92+
n += nn
93+
}
94+
return
95+
}
96+
97+
// RawWrite writes raw data to the connection, without escaping done by Write.
98+
// Use of RawWrite over Conn.Write allows Connection to do any additional
99+
// handling necessary, so long as it does not modify the raw data sent.
100+
func (c *Connection) RawWrite(b []byte) (n int, err error) {
101+
return c.Conn.Write(b)
102+
}
103+
104+
const maxReadAttempts = 10
105+
106+
// Read from the connection, transparently removing and handling IAC control
107+
// sequences. It may attempt multiple reads against the underlying connection if
108+
// it receives back only IAC which gets stripped out of the stream.
109+
func (c *Connection) Read(b []byte) (n int, err error) {
110+
for i := 0; i < maxReadAttempts && n == 0 && len(b) > 0; i++ {
111+
n, err = c.read(b)
112+
}
113+
return
114+
}
115+
116+
func (c *Connection) read(b []byte) (n int, err error) {
117+
err = c.fill(len(b))
118+
if err != nil {
119+
return
120+
}
121+
var lastWrite, subStart int
122+
var ignoreIAC bool
123+
write := func(end int) int {
124+
if c.r == end {
125+
return 0
126+
}
127+
nn := copy(b[lastWrite:], c.buf[c.r:end])
128+
n += nn
129+
lastWrite += nn
130+
c.r += nn
131+
return nn
132+
}
133+
endIAC := func(i int) {
134+
subStart = 0
135+
c.iac = false
136+
c.cmd = 0
137+
c.option = 0
138+
c.r = i + 1
139+
}
140+
for i := c.r; i < c.w && lastWrite < len(b); i++ {
141+
ch := c.buf[i]
142+
if ch == IAC && !ignoreIAC {
143+
if c.iac && c.cmd == 0 {
144+
// Escaped IAC in text
145+
write(i)
146+
c.r++
147+
c.iac = false
148+
continue
149+
} else if c.iac && c.buf[i-1] == IAC {
150+
// Escaped IAC inside IAC sequence
151+
copy(c.buf[:i], c.buf[i+1:])
152+
i--
153+
ignoreIAC = true
154+
continue
155+
} else if !c.iac {
156+
// Start of IAC sequence
157+
write(i)
158+
c.iac = true
159+
continue
160+
}
161+
}
162+
163+
ignoreIAC = false
164+
165+
if c.iac && c.cmd == 0 {
166+
c.cmd = ch
167+
if ch == SB {
168+
subStart = i + 2
169+
}
170+
continue
171+
} else if c.iac && c.option == 0 {
172+
c.option = ch
173+
if c.cmd != SB {
174+
c.handleNegotiation()
175+
endIAC(i)
176+
}
177+
continue
178+
} else if c.iac && c.cmd == SB && ch == SE && c.buf[i-1] == IAC {
179+
if h, ok := c.OptionHandlers[c.option]; ok {
180+
h.HandleSB(c, c.buf[subStart:i-1])
181+
}
182+
endIAC(i)
183+
continue
184+
}
185+
}
186+
187+
nn := copy(b[lastWrite:], c.buf[c.r:c.w])
188+
n += nn
189+
c.r += nn
190+
return
191+
}
192+
193+
func (c *Connection) fill(requestedBytes int) error {
194+
if c.r > 0 {
195+
copy(c.buf, c.buf[c.r:])
196+
c.w -= c.r
197+
c.r = 0
198+
}
199+
200+
if len(c.buf) < requestedBytes {
201+
buf := make([]byte, requestedBytes)
202+
c.w = copy(buf, c.buf[c.r:c.w])
203+
c.r = 0
204+
c.buf = buf
205+
}
206+
207+
c.SetReadDeadline(time.Now().Add(time.Microsecond))
208+
nn, err := c.Conn.Read(c.buf[c.w:])
209+
c.SetReadDeadline(time.Time{})
210+
c.w += nn
211+
return err
212+
}
213+
214+
// SetWindowTitle attempts to set the client's telnet window title. Clients may
215+
// or may not support this.
216+
func (c *Connection) SetWindowTitle(title string) {
217+
fmt.Fprintf(c, TitleBarFmt, title)
218+
}
219+
220+
func (c *Connection) handleNegotiation() {
221+
switch c.cmd {
222+
case WILL:
223+
if h, ok := c.OptionHandlers[c.option]; ok {
224+
h.HandleWill(c)
225+
} else {
226+
c.Conn.Write([]byte{IAC, DONT, c.option})
227+
}
228+
case WONT:
229+
c.clientWont[c.option] = true
230+
case DO:
231+
if h, ok := c.OptionHandlers[c.option]; ok {
232+
h.HandleDo(c)
233+
} else {
234+
c.Conn.Write([]byte{IAC, WONT, c.option})
235+
}
236+
case DONT:
237+
c.clientDont[c.option] = true
238+
}
239+
}

‎connection_test.go

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package telnet_test
2+
3+
import (
4+
"bytes"
5+
"net"
6+
"testing"
7+
8+
"github.com/aprice/telnet"
9+
)
10+
11+
func TestConnection_Write(t *testing.T) {
12+
tests := []struct {
13+
name string
14+
input []byte
15+
expected []byte
16+
}{
17+
{
18+
name: "plain",
19+
input: []byte("hello world"),
20+
expected: []byte("hello world"),
21+
},
22+
{
23+
name: "iac",
24+
input: []byte("hello \xffworld"),
25+
expected: []byte("hello \xff\xffworld"),
26+
},
27+
{
28+
name: "doubleiac",
29+
input: []byte("hello \xff\xffworld"),
30+
expected: []byte("hello \xff\xff\xff\xffworld"),
31+
},
32+
}
33+
buf := bytes.NewBuffer(nil)
34+
for _, test := range tests {
35+
t.Run(test.name, func(t *testing.T) {
36+
buf.Reset()
37+
client, server := net.Pipe()
38+
go func() {
39+
conn := telnet.NewConnection(server, nil)
40+
_, err := conn.Write(test.input)
41+
if err != nil {
42+
t.Error(err)
43+
}
44+
conn.Close()
45+
}()
46+
buf.ReadFrom(client)
47+
client.Close()
48+
if !bytes.Equal(test.expected, buf.Bytes()) {
49+
t.Errorf("Expected %v, got %v", test.expected, buf.Bytes())
50+
}
51+
})
52+
}
53+
}
54+
55+
func TestConnection_Read(t *testing.T) {
56+
tests := []struct {
57+
name string
58+
input []byte
59+
expected []byte
60+
}{
61+
{
62+
name: "plain",
63+
input: []byte("hello world"),
64+
expected: []byte("hello world"),
65+
},
66+
{
67+
name: "iac",
68+
input: []byte("hello \xff\xffworld"),
69+
expected: []byte("hello \xffworld"),
70+
},
71+
}
72+
for _, test := range tests {
73+
t.Run(test.name, func(t *testing.T) {
74+
client, server := net.Pipe()
75+
go func() {
76+
client.Write(test.input)
77+
t.Log("Finished writing")
78+
client.Close()
79+
t.Log("Closed client")
80+
}()
81+
conn := telnet.NewConnection(server, nil)
82+
b := make([]byte, len(test.expected))
83+
_, err := conn.Read(b)
84+
if err != nil {
85+
t.Error(err)
86+
}
87+
conn.Close()
88+
if !bytes.Equal(test.expected, b) {
89+
t.Errorf("Expected %v, got %v", test.expected, b)
90+
}
91+
})
92+
}
93+
}
94+
95+
type closerBuf struct {
96+
*bytes.Buffer
97+
}
98+
99+
func (c *closerBuf) Close() error { return nil }

‎example/main.go

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package main
2+
3+
import (
4+
"log"
5+
"sync"
6+
"time"
7+
8+
"github.com/aprice/telnet"
9+
"github.com/aprice/telnet/linereader"
10+
)
11+
12+
func main() {
13+
svr := telnet.NewServer(":9999", telnet.HandleFunc(exampleHandler), telnet.NAWSOption)
14+
svr.ListenAndServe()
15+
}
16+
17+
func exampleHandler(c *telnet.Connection) {
18+
log.Printf("Connection received: %s", c.RemoteAddr())
19+
lr := linereader.New()
20+
go lr.ReadLines(c)
21+
22+
wg := new(sync.WaitGroup)
23+
wg.Add(1)
24+
go func() {
25+
defer wg.Done()
26+
for line := range lr.C {
27+
log.Printf("Received line: %v", line)
28+
}
29+
}()
30+
time.Sleep(time.Millisecond)
31+
nh := c.OptionHandlers[telnet.NAWS].(*telnet.NAWSHandler)
32+
log.Printf("Client width: %d, height: %d", nh.Width, nh.Height)
33+
wg.Wait()
34+
log.Printf("Goodbye %s!", c.RemoteAddr())
35+
}

‎linereader/linereader.go

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package linereader
2+
3+
import (
4+
"bufio"
5+
"io"
6+
)
7+
8+
// LineReader reads lines from an io.Reader and passes them to a channel.
9+
type LineReader struct {
10+
C chan []byte
11+
}
12+
13+
// New constructs a new LineReader. While the zero value is usable, it may
14+
// cause a race condition; it is better to call New() to initialize the channel
15+
// before calling ReadLines, to ensure that the channel is not nil when it is
16+
// first read.
17+
func New() *LineReader {
18+
return &LineReader{C: make(chan []byte)}
19+
}
20+
21+
// ReadLines continuously reads lines from the Reader and sends them on C. It
22+
// will not return until it encounters an error (including io.EOF).
23+
func (r *LineReader) ReadLines(in io.Reader) (err error) {
24+
if r.C == nil {
25+
r.C = make(chan []byte)
26+
}
27+
defer close(r.C)
28+
rdr := bufio.NewReader(in)
29+
var line, cont []byte
30+
var prefix bool
31+
for {
32+
line, prefix, err = rdr.ReadLine()
33+
for prefix && err == nil {
34+
cont, prefix, err = rdr.ReadLine()
35+
line = append(line, cont...)
36+
}
37+
if line != nil {
38+
r.C <- line
39+
}
40+
if err == io.EOF {
41+
err = nil
42+
break
43+
}
44+
if err != nil {
45+
break
46+
}
47+
}
48+
return
49+
}

‎options.go

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package telnet
2+
3+
import (
4+
"encoding/binary"
5+
"os"
6+
"time"
7+
8+
"golang.org/x/crypto/ssh/terminal"
9+
)
10+
11+
const (
12+
ECHO = byte(1)
13+
TTYPE = byte(24)
14+
NAWS = byte(31)
15+
ENCRYPT = byte(38)
16+
EOR = byte(239)
17+
)
18+
19+
// NAWSOption enables NAWS negotiation on a Server.
20+
func NAWSOption(c *Connection) Negotiator {
21+
return &NAWSHandler{client: false}
22+
}
23+
24+
// ExposeNAWS enables NAWS negotiation on a Client.
25+
func ExposeNAWS(c *Connection) Negotiator {
26+
width, height, _ := terminal.GetSize(int(os.Stdin.Fd()))
27+
return &NAWSHandler{Width: uint16(width), Height: uint16(height), client: true}
28+
}
29+
30+
// NAWSHandler negotiates NAWS for a specific connection.
31+
type NAWSHandler struct {
32+
Width uint16
33+
Height uint16
34+
35+
client bool
36+
}
37+
38+
func (n *NAWSHandler) OptionCode() byte {
39+
return NAWS
40+
}
41+
42+
func (n *NAWSHandler) Offer(c *Connection) {
43+
if !n.client {
44+
c.Conn.Write([]byte{IAC, DO, n.OptionCode()})
45+
}
46+
}
47+
48+
func (n *NAWSHandler) HandleWill(c *Connection) {}
49+
50+
func (n *NAWSHandler) HandleDo(c *Connection) {
51+
if n.client {
52+
c.Conn.Write([]byte{IAC, WILL, n.OptionCode()})
53+
n.writeSize(c)
54+
go n.monitorTTYSize(c)
55+
} else {
56+
c.Conn.Write([]byte{IAC, WONT, n.OptionCode()})
57+
}
58+
}
59+
60+
func (n *NAWSHandler) monitorTTYSize(c *Connection) {
61+
t := time.NewTicker(time.Second)
62+
for range t.C {
63+
w, h, err := terminal.GetSize(int(os.Stdin.Fd()))
64+
if err != nil {
65+
continue
66+
}
67+
width := uint16(w)
68+
height := uint16(h)
69+
if width != n.Width || height != n.Height {
70+
n.Width = width
71+
n.Height = height
72+
n.writeSize(c)
73+
}
74+
}
75+
}
76+
77+
func (n *NAWSHandler) writeSize(c *Connection) {
78+
c.Conn.Write([]byte{IAC, SB, n.OptionCode()})
79+
payload := make([]byte, 4)
80+
binary.BigEndian.PutUint16(payload, n.Width)
81+
binary.BigEndian.PutUint16(payload[2:], n.Height)
82+
// Normal write - we want inadvertent IACs to be escaped in body
83+
c.Write(payload)
84+
c.Conn.Write([]byte{IAC, SE})
85+
}
86+
87+
func (n *NAWSHandler) HandleSB(c *Connection, b []byte) {
88+
if !n.client {
89+
n.Width = binary.BigEndian.Uint16(b[0:2])
90+
n.Height = binary.BigEndian.Uint16(b[2:4])
91+
}
92+
}

‎options_test.go

+118
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package telnet_test
2+
3+
import (
4+
"bytes"
5+
"io"
6+
"io/ioutil"
7+
"net"
8+
"sync"
9+
"testing"
10+
"time"
11+
12+
"github.com/aprice/telnet"
13+
)
14+
15+
func TestServerNAWS(t *testing.T) {
16+
client, server := net.Pipe()
17+
text := []byte("hello\n")
18+
wg := new(sync.WaitGroup)
19+
wg.Add(1)
20+
go func() {
21+
defer wg.Done()
22+
defer client.Close()
23+
b := make([]byte, 3)
24+
_, err := client.Read(b)
25+
if err != nil {
26+
t.Error(err)
27+
}
28+
if !bytes.Equal(b, []byte{255, 253, 31}) {
29+
t.Errorf("Expected IAC DO NAWS, received %v", b)
30+
}
31+
// IAC WILL NAWS IAC SB NAWS W[1] W[0] H[1] H[0] IAC SE
32+
payload := []byte{255, 251, 31, 255, 250, 31, 0, 80, 0, 20, 255, 240}
33+
payload = append(payload, text...)
34+
_, err = client.Write(payload)
35+
if err != nil {
36+
t.Error(err)
37+
}
38+
}()
39+
conn := telnet.NewConnection(server, []telnet.Option{telnet.NAWSOption})
40+
b := make([]byte, 32)
41+
n, err := conn.Read(b)
42+
if err != nil {
43+
t.Error(err)
44+
}
45+
46+
if !bytes.Equal(text, b[:n]) {
47+
t.Errorf("Expected %q, got %q", text, b[:n])
48+
}
49+
wg.Wait()
50+
conn.Close()
51+
52+
nw := conn.OptionHandlers[31].(*telnet.NAWSHandler)
53+
if nw.Width != 80 || nw.Height != 20 {
54+
t.Logf("%#v", conn)
55+
t.Errorf("Expected w %d, h %d, got w %d, h %d", 80, 20, nw.Width, nw.Height)
56+
}
57+
}
58+
59+
func TestClientNAWS(t *testing.T) {
60+
client, server := net.Pipe()
61+
go func() {
62+
conn := telnet.NewConnection(client, []telnet.Option{telnet.ExposeNAWS})
63+
b := make([]byte, 32)
64+
conn.Read(b)
65+
conn.Close()
66+
}()
67+
68+
_, err := server.Write([]byte{255, 253, 31})
69+
if err != nil {
70+
t.Error(err)
71+
}
72+
expected := []byte{255, 251, 31, 255, 250, 31, 255, 255, 255, 255, 255, 255, 255, 255, 255, 240}
73+
buf := bytes.NewBuffer(nil)
74+
server.SetReadDeadline(time.Now().Add(time.Second))
75+
n, err := io.CopyN(buf, server, int64(len(expected)))
76+
if err != nil {
77+
t.Error(err)
78+
}
79+
b := buf.Bytes()
80+
server.Close()
81+
// IAC WILL NAWS IAC SB NAWS W[1] W[0] H[1] H[0] IAC SE
82+
if !bytes.Equal(b[:n], expected) {
83+
t.Errorf("Expected %v, received %v", expected, b[:n])
84+
}
85+
}
86+
87+
func TestOnlyServerSupportsNAWS(t *testing.T) {
88+
client, server := net.Pipe()
89+
text := []byte("hello\n")
90+
wg := new(sync.WaitGroup)
91+
wg.Add(1)
92+
go func() {
93+
conn := telnet.NewConnection(client, []telnet.Option{})
94+
b := make([]byte, 32)
95+
n, err := conn.Read(b)
96+
if err != nil {
97+
t.Error(err)
98+
}
99+
conn.Close()
100+
101+
if !bytes.Equal(text, b[:n]) {
102+
t.Errorf("Expected %q, got %q", text, b[:n])
103+
}
104+
wg.Done()
105+
}()
106+
wg.Add(1)
107+
go func() {
108+
conn := telnet.NewConnection(server, []telnet.Option{telnet.NAWSOption})
109+
go io.Copy(ioutil.Discard, conn)
110+
_, err := conn.Write(text)
111+
if err != nil {
112+
t.Error(err)
113+
}
114+
conn.Close()
115+
wg.Done()
116+
}()
117+
wg.Wait()
118+
}

‎server.go

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package telnet
2+
3+
import (
4+
"net"
5+
)
6+
7+
// Option functions add handling of a telnet option to a Server. The Option
8+
// function takes a connection (which it can store but needn't) and returns a
9+
// Negotiator; it is up to the Option function whether a single instance of
10+
// the Negotiator is reused or if a new instance is created for each connection.
11+
type Option func(c *Connection) Negotiator
12+
13+
// Handler is a telnet connection handler. The Handler passed to a server will
14+
// be called for all incoming connections.
15+
type Handler interface {
16+
HandleTelnet(conn *Connection)
17+
}
18+
19+
// HandleFunc makes it easy to pass a function as a Handler instead of a full
20+
// type.
21+
type HandleFunc func(conn *Connection)
22+
23+
// HandleTelnet implements Handler, and simply calls the function.
24+
func (f HandleFunc) HandleTelnet(conn *Connection) {
25+
f(conn)
26+
}
27+
28+
// Server listens for telnet connections.
29+
type Server struct {
30+
// Address is the addres the Server listens on.
31+
Address string
32+
handler Handler
33+
options []Option
34+
listener net.Listener
35+
quitting bool
36+
}
37+
38+
// NewServer constructs a new telnet server.
39+
func NewServer(addr string, handler Handler, options ...Option) *Server {
40+
return &Server{Address: addr, handler: handler, options: options}
41+
}
42+
43+
// Serve runs the telnet server. This function does not return and
44+
// should probably be run in a goroutine.
45+
func (s *Server) Serve(l net.Listener) error {
46+
s.listener = l
47+
s.Address = s.listener.Addr().String()
48+
for {
49+
c, err := s.listener.Accept()
50+
if err != nil {
51+
if s.quitting {
52+
return nil
53+
}
54+
return err
55+
}
56+
conn := NewConnection(c, s.options)
57+
go func() {
58+
s.handler.HandleTelnet(conn)
59+
conn.Conn.Close()
60+
}()
61+
}
62+
}
63+
64+
// ListenAndServe runs the telnet server by creating a new Listener using the
65+
// current Server.Address, and then calling Serve().
66+
func (s *Server) ListenAndServe() error {
67+
l, err := net.Listen("tcp", s.Address)
68+
if err != nil {
69+
return err
70+
}
71+
72+
return s.Serve(l)
73+
}
74+
75+
// Stop the telnet server. This stops listening for new connections, but does
76+
// not affect any active connections already opened.
77+
func (s *Server) Stop() {
78+
if s.quitting {
79+
return
80+
}
81+
s.quitting = true
82+
s.listener.Close()
83+
}

‎server_test.go

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package telnet_test
2+
3+
import (
4+
"sync"
5+
"testing"
6+
"time"
7+
8+
"github.com/aprice/telnet"
9+
)
10+
11+
func TestServer_ListenAndServe(t *testing.T) {
12+
wg := new(sync.WaitGroup)
13+
s := telnet.NewServer("127.0.0.1:0", telnet.HandleFunc(func(c *telnet.Connection) {
14+
wg.Add(1)
15+
_, err := c.Write([]byte("Hello!"))
16+
if err != nil {
17+
t.Error(err)
18+
}
19+
err = c.Close()
20+
if err != nil {
21+
t.Error(err)
22+
}
23+
wg.Done()
24+
}))
25+
wg.Add(1)
26+
go func() {
27+
err := s.ListenAndServe()
28+
if err != nil {
29+
t.Error(err)
30+
}
31+
wg.Done()
32+
}()
33+
time.Sleep(time.Millisecond)
34+
client, err := telnet.Dial(s.Address)
35+
if err != nil {
36+
t.Error(err)
37+
}
38+
err = client.Close()
39+
if err != nil {
40+
t.Error(err)
41+
}
42+
s.Stop()
43+
wg.Wait()
44+
}

0 commit comments

Comments
 (0)
Please sign in to comment.