├── reader.go ├── writer.go ├── go.mod ├── go.sum ├── logger.go ├── context.go ├── handler.go ├── caller.go ├── echo_handler.go ├── discard_logger.go ├── telsh ├── discard_logger.go ├── producer.go ├── telnet_handler_test.go ├── help.go ├── handler.go ├── doc.go └── telnet_handler.go ├── LICENSE ├── client.go ├── standard_caller.go ├── data_writer_test.go ├── tls.go ├── conn.go ├── data_writer.go ├── data_reader.go ├── server.go ├── README.md ├── echo_handler_test.go ├── data_reader_test.go ├── doc.go └── standard_caller_test.go /reader.go: -------------------------------------------------------------------------------- 1 | package telnet 2 | 3 | 4 | type Reader interface { 5 | Read([]byte) (int, error) 6 | } 7 | -------------------------------------------------------------------------------- /writer.go: -------------------------------------------------------------------------------- 1 | package telnet 2 | 3 | 4 | type Writer interface { 5 | Write([]byte) (int, error) 6 | } 7 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/reiver/go-telnet 2 | 3 | go 1.22 4 | 5 | require github.com/reiver/go-oi v1.0.0 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/reiver/go-oi v1.0.0 h1:nvECWD7LF+vOs8leNGV/ww+F2iZKf3EYjYZ527turzM= 2 | github.com/reiver/go-oi v1.0.0/go.mod h1:RrDBct90BAhoDTxB1fenZwfykqeGvhI6LsNfStJoEkI= 3 | -------------------------------------------------------------------------------- /logger.go: -------------------------------------------------------------------------------- 1 | package telnet 2 | 3 | 4 | type Logger interface{ 5 | Debug(...interface{}) 6 | Debugf(string, ...interface{}) 7 | 8 | Error(...interface{}) 9 | Errorf(string, ...interface{}) 10 | 11 | Trace(...interface{}) 12 | Tracef(string, ...interface{}) 13 | 14 | Warn(...interface{}) 15 | Warnf(string, ...interface{}) 16 | } 17 | -------------------------------------------------------------------------------- /context.go: -------------------------------------------------------------------------------- 1 | package telnet 2 | 3 | 4 | type Context interface { 5 | Logger() Logger 6 | 7 | InjectLogger(Logger) Context 8 | } 9 | 10 | 11 | type internalContext struct { 12 | logger Logger 13 | } 14 | 15 | 16 | func NewContext() Context { 17 | ctx := internalContext{} 18 | 19 | return &ctx 20 | } 21 | 22 | 23 | func (ctx *internalContext) Logger() Logger { 24 | return ctx.logger 25 | } 26 | 27 | func (ctx *internalContext) InjectLogger(logger Logger) Context { 28 | ctx.logger = logger 29 | 30 | return ctx 31 | } 32 | -------------------------------------------------------------------------------- /handler.go: -------------------------------------------------------------------------------- 1 | package telnet 2 | 3 | 4 | // A Handler serves a TELNET (or TELNETS) connection. 5 | // 6 | // Writing data to the Writer passed as an argument to the ServeTELNET method 7 | // will send data to the TELNET (or TELNETS) client. 8 | // 9 | // Reading data from the Reader passed as an argument to the ServeTELNET method 10 | // will receive data from the TELNET client. 11 | // 12 | // The Writer's Write method sends "escaped" TELNET (and TELNETS) data. 13 | // 14 | // The Reader's Read method "un-escapes" TELNET (and TELNETS) data, and filters 15 | // out TELNET (and TELNETS) command sequences. 16 | type Handler interface { 17 | ServeTELNET(Context, Writer, Reader) 18 | } 19 | -------------------------------------------------------------------------------- /caller.go: -------------------------------------------------------------------------------- 1 | package telnet 2 | 3 | 4 | // A Caller represents the client end of a TELNET (or TELNETS) connection. 5 | // 6 | // Writing data to the Writer passed as an argument to the CallTELNET method 7 | // will send data to the TELNET (or TELNETS) server. 8 | // 9 | // Reading data from the Reader passed as an argument to the CallTELNET method 10 | // will receive data from the TELNET server. 11 | // 12 | // The Writer's Write method sends "escaped" TELNET (and TELNETS) data. 13 | // 14 | // The Reader's Read method "un-escapes" TELNET (and TELNETS) data, and filters 15 | // out TELNET (and TELNETS) command sequences. 16 | type Caller interface { 17 | CallTELNET(Context, Writer, Reader) 18 | } 19 | -------------------------------------------------------------------------------- /echo_handler.go: -------------------------------------------------------------------------------- 1 | package telnet 2 | 3 | 4 | import ( 5 | "github.com/reiver/go-oi" 6 | ) 7 | 8 | 9 | // EchoHandler is a simple TELNET server which "echos" back to the client any (non-command) 10 | // data back to the TELNET client, it received from the TELNET client. 11 | var EchoHandler Handler = internalEchoHandler{} 12 | 13 | 14 | type internalEchoHandler struct{} 15 | 16 | 17 | func (handler internalEchoHandler) ServeTELNET(ctx Context, w Writer, r Reader) { 18 | 19 | var buffer [1]byte // Seems like the length of the buffer needs to be small, otherwise will have to wait for buffer to fill up. 20 | p := buffer[:] 21 | 22 | for { 23 | n, err := r.Read(p) 24 | 25 | if n > 0 { 26 | oi.LongWrite(w, p[:n]) 27 | } 28 | 29 | if nil != err { 30 | break 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /discard_logger.go: -------------------------------------------------------------------------------- 1 | package telnet 2 | 3 | 4 | type internalDiscardLogger struct{} 5 | 6 | func (internalDiscardLogger) Debug(...interface{}) {} 7 | func (internalDiscardLogger) Debugf(string, ...interface{}) {} 8 | func (internalDiscardLogger) Debugln(...interface{}) {} 9 | 10 | func (internalDiscardLogger) Error(...interface{}) {} 11 | func (internalDiscardLogger) Errorf(string, ...interface{}) {} 12 | func (internalDiscardLogger) Errorln(...interface{}) {} 13 | 14 | func (internalDiscardLogger) Trace(...interface{}) {} 15 | func (internalDiscardLogger) Tracef(string, ...interface{}) {} 16 | func (internalDiscardLogger) Traceln(...interface{}) {} 17 | 18 | func (internalDiscardLogger) Warn(...interface{}) {} 19 | func (internalDiscardLogger) Warnf(string, ...interface{}) {} 20 | func (internalDiscardLogger) Warnln(...interface{}) {} 21 | 22 | -------------------------------------------------------------------------------- /telsh/discard_logger.go: -------------------------------------------------------------------------------- 1 | package telsh 2 | 3 | 4 | type internalDiscardLogger struct{} 5 | 6 | func (internalDiscardLogger) Debug(...interface{}) {} 7 | func (internalDiscardLogger) Debugf(string, ...interface{}) {} 8 | func (internalDiscardLogger) Debugln(...interface{}) {} 9 | 10 | func (internalDiscardLogger) Error(...interface{}) {} 11 | func (internalDiscardLogger) Errorf(string, ...interface{}) {} 12 | func (internalDiscardLogger) Errorln(...interface{}) {} 13 | 14 | func (internalDiscardLogger) Trace(...interface{}) {} 15 | func (internalDiscardLogger) Tracef(string, ...interface{}) {} 16 | func (internalDiscardLogger) Traceln(...interface{}) {} 17 | 18 | func (internalDiscardLogger) Warn(...interface{}) {} 19 | func (internalDiscardLogger) Warnf(string, ...interface{}) {} 20 | func (internalDiscardLogger) Warnln(...interface{}) {} 21 | 22 | -------------------------------------------------------------------------------- /telsh/producer.go: -------------------------------------------------------------------------------- 1 | package telsh 2 | 3 | 4 | import ( 5 | "github.com/reiver/go-telnet" 6 | ) 7 | 8 | 9 | // A Producer provides a Produce method which creates a Handler. 10 | // 11 | // Producer is an abstraction that represents a shell "command". 12 | // 13 | // Contrast this with a Handler, which is is an abstraction that 14 | // represents a "running" shell "command". 15 | // 16 | // To use a metaphor, the differences between a Producer and a Handler, 17 | // is like the difference between a program executable and actually running 18 | // the program executable. 19 | type Producer interface { 20 | Produce(telnet.Context, string, ...string) Handler 21 | } 22 | 23 | 24 | // ProducerFunc is an adaptor, that can be used to turn a func with the 25 | // signature: 26 | // 27 | // func(telnet.Context, string, ...string) Handler 28 | // 29 | // Into a Producer 30 | type ProducerFunc func(telnet.Context, string, ...string) Handler 31 | 32 | 33 | // Produce makes ProducerFunc fit the Producer interface. 34 | func (fn ProducerFunc) Produce(ctx telnet.Context, name string, args ...string) Handler { 35 | return fn(ctx, name, args...) 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Charles Iliya Krempeaux :: http://changelog.ca/ 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package telnet 2 | 3 | 4 | import ( 5 | "crypto/tls" 6 | ) 7 | 8 | 9 | func DialAndCall(caller Caller) error { 10 | conn, err := Dial() 11 | if nil != err { 12 | return err 13 | } 14 | 15 | client := &Client{Caller:caller} 16 | 17 | return client.Call(conn) 18 | } 19 | 20 | 21 | func DialToAndCall(srvAddr string, caller Caller) error { 22 | conn, err := DialTo(srvAddr) 23 | if nil != err { 24 | return err 25 | } 26 | 27 | client := &Client{Caller:caller} 28 | 29 | return client.Call(conn) 30 | } 31 | 32 | 33 | func DialAndCallTLS(caller Caller, tlsConfig *tls.Config) error { 34 | conn, err := DialTLS(tlsConfig) 35 | if nil != err { 36 | return err 37 | } 38 | 39 | client := &Client{Caller:caller} 40 | 41 | return client.Call(conn) 42 | } 43 | 44 | func DialToAndCallTLS(srvAddr string, caller Caller, tlsConfig *tls.Config) error { 45 | conn, err := DialToTLS(srvAddr, tlsConfig) 46 | if nil != err { 47 | return err 48 | } 49 | 50 | client := &Client{Caller:caller} 51 | 52 | return client.Call(conn) 53 | } 54 | 55 | 56 | type Client struct { 57 | Caller Caller 58 | 59 | Logger Logger 60 | } 61 | 62 | 63 | func (client *Client) Call(conn *Conn) error { 64 | 65 | logger := client.logger() 66 | 67 | 68 | caller := client.Caller 69 | if nil == caller { 70 | logger.Debug("Defaulted caller to StandardCaller.") 71 | caller = StandardCaller 72 | } 73 | 74 | 75 | var ctx Context = NewContext().InjectLogger(logger) 76 | 77 | var w Writer = conn 78 | var r Reader = conn 79 | 80 | caller.CallTELNET(ctx, w, r) 81 | conn.Close() 82 | 83 | 84 | return nil 85 | } 86 | 87 | 88 | func (client *Client) logger() Logger { 89 | logger := client.Logger 90 | if nil == logger { 91 | logger = internalDiscardLogger{} 92 | } 93 | 94 | return logger 95 | } 96 | 97 | 98 | func (client *Client) SetAuth(username string) { 99 | //@TODO: ################################################# 100 | } 101 | -------------------------------------------------------------------------------- /telsh/telnet_handler_test.go: -------------------------------------------------------------------------------- 1 | package telsh 2 | 3 | 4 | import ( 5 | "github.com/reiver/go-telnet" 6 | 7 | "bytes" 8 | "strings" 9 | 10 | "testing" 11 | ) 12 | 13 | 14 | func TestServeTELNETCommandNotFound(t *testing.T) { 15 | 16 | tests := []struct{ 17 | ClientSends string 18 | Expected string 19 | }{ 20 | { 21 | ClientSends: "\r\n", 22 | Expected: "", 23 | }, 24 | 25 | 26 | 27 | { 28 | ClientSends: "apple\r\n", 29 | Expected: "apple: command not found\r\n", 30 | }, 31 | { 32 | ClientSends: "banana\r\n", 33 | Expected: "banana: command not found\r\n", 34 | }, 35 | { 36 | ClientSends: "cherry\r\n", 37 | Expected: "cherry: command not found\r\n", 38 | }, 39 | 40 | 41 | 42 | { 43 | ClientSends: "\t\r\n", 44 | Expected: "", 45 | }, 46 | { 47 | ClientSends: "\t\t\r\n", 48 | Expected: "", 49 | }, 50 | { 51 | ClientSends: "\t\t\t\r\n", 52 | Expected: "", 53 | }, 54 | 55 | 56 | 57 | { 58 | ClientSends: " \r\n", 59 | Expected: "", 60 | }, 61 | { 62 | ClientSends: " \r\n", 63 | Expected: "", 64 | }, 65 | { 66 | ClientSends: " \r\n", 67 | Expected: "", 68 | }, 69 | 70 | 71 | 72 | { 73 | ClientSends: " \t\r\n", 74 | Expected: "", 75 | }, 76 | { 77 | ClientSends: "\t \r\n", 78 | Expected: "", 79 | }, 80 | 81 | 82 | 83 | { 84 | ClientSends: "ls -alF\r\n", 85 | Expected: "ls: command not found\r\n", 86 | }, 87 | } 88 | 89 | 90 | for testNumber, test := range tests { 91 | 92 | shellHandler := NewShellHandler() 93 | if nil == shellHandler { 94 | t.Errorf("For test #%d, did not expect to get nil, but actually got it: %v; for client sent: %q", testNumber, shellHandler, test.ClientSends) 95 | continue 96 | } 97 | 98 | 99 | ctx := telnet.NewContext() 100 | 101 | var buffer bytes.Buffer 102 | 103 | shellHandler.ServeTELNET(ctx, &buffer, strings.NewReader(test.ClientSends)) 104 | 105 | if expected, actual := shellHandler.WelcomeMessage+shellHandler.Prompt+test.Expected+shellHandler.Prompt+shellHandler.ExitMessage, buffer.String(); expected != actual { 106 | t.Errorf("For test #%d, expect %q, but actually got %q; for client sent: %q", testNumber, expected, actual, test.ClientSends) 107 | continue 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /standard_caller.go: -------------------------------------------------------------------------------- 1 | package telnet 2 | 3 | 4 | import ( 5 | "github.com/reiver/go-oi" 6 | 7 | "bufio" 8 | "bytes" 9 | "fmt" 10 | "io" 11 | "os" 12 | 13 | "time" 14 | ) 15 | 16 | 17 | // StandardCaller is a simple TELNET client which sends to the server any data it gets from os.Stdin 18 | // as TELNET (and TELNETS) data, and writes any TELNET (or TELNETS) data it receives from 19 | // the server to os.Stdout, and writes any error it has to os.Stderr. 20 | var StandardCaller Caller = internalStandardCaller{} 21 | 22 | 23 | type internalStandardCaller struct{} 24 | 25 | 26 | func (caller internalStandardCaller) CallTELNET(ctx Context, w Writer, r Reader) { 27 | standardCallerCallTELNET(os.Stdin, os.Stdout, os.Stderr, ctx, w, r) 28 | } 29 | 30 | 31 | func standardCallerCallTELNET(stdin io.ReadCloser, stdout io.WriteCloser, stderr io.WriteCloser, ctx Context, w Writer, r Reader) { 32 | 33 | go func(writer io.Writer, reader io.Reader) { 34 | 35 | var buffer [1]byte // Seems like the length of the buffer needs to be small, otherwise will have to wait for buffer to fill up. 36 | p := buffer[:] 37 | 38 | for { 39 | // Read 1 byte. 40 | n, err := reader.Read(p) 41 | if n <= 0 && nil == err { 42 | continue 43 | } else if n <= 0 && nil != err { 44 | break 45 | } 46 | 47 | oi.LongWrite(writer, p) 48 | } 49 | }(stdout, r) 50 | 51 | 52 | 53 | var buffer bytes.Buffer 54 | var p []byte 55 | 56 | var crlfBuffer [2]byte = [2]byte{'\r','\n'} 57 | crlf := crlfBuffer[:] 58 | 59 | scanner := bufio.NewScanner(stdin) 60 | scanner.Split(scannerSplitFunc) 61 | 62 | for scanner.Scan() { 63 | buffer.Write(scanner.Bytes()) 64 | buffer.Write(crlf) 65 | 66 | p = buffer.Bytes() 67 | 68 | n, err := oi.LongWrite(w, p) 69 | if nil != err { 70 | break 71 | } 72 | if expected, actual := int64(len(p)), n; expected != actual { 73 | err := fmt.Errorf("Transmission problem: tried sending %d bytes, but actually only sent %d bytes.", expected, actual) 74 | fmt.Fprint(stderr, err.Error()) 75 | return 76 | } 77 | 78 | 79 | buffer.Reset() 80 | } 81 | 82 | // Wait a bit to receive data from the server (that we would send to io.Stdout). 83 | time.Sleep(3 * time.Millisecond) 84 | } 85 | 86 | 87 | func scannerSplitFunc(data []byte, atEOF bool) (advance int, token []byte, err error) { 88 | if atEOF { 89 | return 0, nil, nil 90 | } 91 | 92 | return bufio.ScanLines(data, atEOF) 93 | } 94 | -------------------------------------------------------------------------------- /telsh/help.go: -------------------------------------------------------------------------------- 1 | package telsh 2 | 3 | 4 | import ( 5 | "github.com/reiver/go-oi" 6 | "github.com/reiver/go-telnet" 7 | 8 | "io" 9 | "sort" 10 | ) 11 | 12 | 13 | type internalHelpProducer struct { 14 | shellHandler *ShellHandler 15 | } 16 | 17 | 18 | func Help(shellHandler *ShellHandler) Producer { 19 | producer := internalHelpProducer{ 20 | shellHandler:shellHandler, 21 | } 22 | 23 | return &producer 24 | } 25 | 26 | 27 | func (producer *internalHelpProducer) Produce(telnet.Context, string, ...string) Handler { 28 | return newHelpHandler(producer) 29 | } 30 | 31 | 32 | type internalHelpHandler struct { 33 | helpProducer *internalHelpProducer 34 | 35 | err error 36 | 37 | stdin io.ReadCloser 38 | stdout io.WriteCloser 39 | stderr io.WriteCloser 40 | 41 | stdinPipe io.WriteCloser 42 | stdoutPipe io.ReadCloser 43 | stderrPipe io.ReadCloser 44 | } 45 | 46 | 47 | func newHelpHandler(helpProducer *internalHelpProducer) *internalHelpHandler { 48 | stdin, stdinPipe := io.Pipe() 49 | stdoutPipe, stdout := io.Pipe() 50 | stderrPipe, stderr := io.Pipe() 51 | 52 | handler := internalHelpHandler{ 53 | helpProducer:helpProducer, 54 | 55 | err:nil, 56 | 57 | stdin:stdin, 58 | stdout:stdout, 59 | stderr:stderr, 60 | 61 | stdinPipe:stdinPipe, 62 | stdoutPipe:stdoutPipe, 63 | stderrPipe:stderrPipe, 64 | } 65 | 66 | return &handler 67 | } 68 | 69 | 70 | 71 | 72 | func (handler *internalHelpHandler) Run() error { 73 | if nil != handler.err { 74 | return handler.err 75 | } 76 | 77 | //@TODO: Should this be reaching inside of ShellHandler? Maybe there should be ShellHandler public methods instead. 78 | keys := make([]string, 1+len(handler.helpProducer.shellHandler.producers)) 79 | i:=0 80 | for key,_ := range handler.helpProducer.shellHandler.producers { 81 | keys[i] = key 82 | i++ 83 | } 84 | keys[i] = handler.helpProducer.shellHandler.ExitCommandName 85 | sort.Strings(keys) 86 | for _, key := range keys { 87 | oi.LongWriteString(handler.stdout, key) 88 | oi.LongWriteString(handler.stdout, "\r\n") 89 | } 90 | 91 | handler.stdin.Close() 92 | handler.stdout.Close() 93 | handler.stderr.Close() 94 | 95 | return handler.err 96 | } 97 | 98 | func (handler *internalHelpHandler) StdinPipe() (io.WriteCloser, error) { 99 | if nil != handler.err { 100 | return nil, handler.err 101 | } 102 | 103 | return handler.stdinPipe, nil 104 | } 105 | 106 | func (handler *internalHelpHandler) StdoutPipe() (io.ReadCloser, error) { 107 | if nil != handler.err { 108 | return nil, handler.err 109 | } 110 | 111 | return handler.stdoutPipe, nil 112 | } 113 | 114 | func (handler *internalHelpHandler) StderrPipe() (io.ReadCloser, error) { 115 | if nil != handler.err { 116 | return nil, handler.err 117 | } 118 | 119 | return handler.stderrPipe, nil 120 | } 121 | -------------------------------------------------------------------------------- /data_writer_test.go: -------------------------------------------------------------------------------- 1 | package telnet 2 | 3 | 4 | import ( 5 | "bytes" 6 | 7 | "testing" 8 | ) 9 | 10 | 11 | func TestDataWriter(t *testing.T) { 12 | 13 | tests := []struct{ 14 | Bytes []byte 15 | Expected []byte 16 | }{ 17 | { 18 | Bytes: []byte{}, 19 | Expected: []byte{}, 20 | }, 21 | 22 | 23 | 24 | { 25 | Bytes: []byte("apple"), 26 | Expected: []byte("apple"), 27 | }, 28 | { 29 | Bytes: []byte("banana"), 30 | Expected: []byte("banana"), 31 | }, 32 | { 33 | Bytes: []byte("cherry"), 34 | Expected: []byte("cherry"), 35 | }, 36 | 37 | 38 | 39 | { 40 | Bytes: []byte("apple banana cherry"), 41 | Expected: []byte("apple banana cherry"), 42 | }, 43 | 44 | 45 | 46 | { 47 | Bytes: []byte{255}, 48 | Expected: []byte{255,255}, 49 | }, 50 | { 51 | Bytes: []byte{255,255}, 52 | Expected: []byte{255,255,255,255}, 53 | }, 54 | { 55 | Bytes: []byte{255,255,255}, 56 | Expected: []byte{255,255,255,255,255,255}, 57 | }, 58 | { 59 | Bytes: []byte{255,255,255,255}, 60 | Expected: []byte{255,255,255,255,255,255,255,255}, 61 | }, 62 | { 63 | Bytes: []byte{255,255,255,255,255}, 64 | Expected: []byte{255,255,255,255,255,255,255,255,255,255}, 65 | }, 66 | 67 | 68 | 69 | { 70 | Bytes: []byte("apple\xffbanana\xffcherry"), 71 | Expected: []byte("apple\xff\xffbanana\xff\xffcherry"), 72 | }, 73 | { 74 | Bytes: []byte("\xffapple\xffbanana\xffcherry\xff"), 75 | Expected: []byte("\xff\xffapple\xff\xffbanana\xff\xffcherry\xff\xff"), 76 | }, 77 | 78 | 79 | 80 | 81 | { 82 | Bytes: []byte("apple\xff\xffbanana\xff\xffcherry"), 83 | Expected: []byte("apple\xff\xff\xff\xffbanana\xff\xff\xff\xffcherry"), 84 | }, 85 | { 86 | Bytes: []byte("\xff\xffapple\xff\xffbanana\xff\xffcherry\xff\xff"), 87 | Expected: []byte("\xff\xff\xff\xffapple\xff\xff\xff\xffbanana\xff\xff\xff\xffcherry\xff\xff\xff\xff"), 88 | }, 89 | } 90 | 91 | //@TODO: Add random tests. 92 | 93 | 94 | for testNumber, test := range tests { 95 | 96 | subWriter := new(bytes.Buffer) 97 | 98 | writer := newDataWriter(subWriter) 99 | 100 | n, err := writer.Write(test.Bytes) 101 | if nil != err { 102 | t.Errorf("For test #%d, did not expected an error, but actually got one: (%T) %v; for %q -> %q.", testNumber, err, err, string(test.Bytes), string(test.Expected)) 103 | continue 104 | } 105 | 106 | if expected, actual := len(test.Bytes), n; expected != actual { 107 | t.Errorf("For test #%d, expected %d, but actually got %d; for %q -> %q.", testNumber, expected, actual, string(test.Bytes), string(test.Expected)) 108 | continue 109 | } 110 | 111 | if expected, actual := string(test.Expected), subWriter.String(); expected != actual { 112 | t.Errorf("For test #%d, expected %q, but actually got %q; for %q -> %q.", testNumber, expected, actual, string(test.Bytes), string(test.Expected)) 113 | continue 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /telsh/handler.go: -------------------------------------------------------------------------------- 1 | package telsh 2 | 3 | 4 | import ( 5 | "io" 6 | ) 7 | 8 | 9 | // Hander is an abstraction that represents a "running" shell "command". 10 | // 11 | // Contrast this with a Producer, which is is an abstraction that 12 | // represents a shell "command". 13 | // 14 | // To use a metaphor, the differences between a Producer and a Handler, 15 | // is like the difference between a program executable and actually running 16 | // the program executable. 17 | // 18 | // Conceptually, anything that implements the Hander, and then has its Producer 19 | // registered with ShellHandler.Register() will be available as a command. 20 | // 21 | // Note that Handler was intentionally made to be compatible with 22 | // "os/exec", which is part of the Go standard library. 23 | type Handler interface { 24 | Run() error 25 | 26 | StdinPipe() (io.WriteCloser, error) 27 | StdoutPipe() (io.ReadCloser, error) 28 | StderrPipe() (io.ReadCloser, error) 29 | } 30 | 31 | 32 | // HandlerFunc is useful to write inline Producers, and provides an alternative to 33 | // creating something that implements Handler directly. 34 | // 35 | // For example: 36 | // 37 | // shellHandler := telsh.NewShellHandler() 38 | // 39 | // shellHandler.Register("five", telsh.ProducerFunc( 40 | // 41 | // func(ctx telnet.Context, name string, args ...string) telsh.Handler{ 42 | // 43 | // return telsh.PromoteHandlerFunc( 44 | // 45 | // func(stdin io.ReadCloser, stdout io.WriteCloser, stderr io.WriteCloser, args ...string) error { 46 | // oi.LongWrite(stdout, []byte{'5', '\r', '\n'}) 47 | // 48 | // return nil 49 | // }, 50 | // ) 51 | // }, 52 | // )) 53 | // 54 | // Note that PromoteHandlerFunc is used to turn a HandlerFunc into a Handler. 55 | type HandlerFunc func(stdin io.ReadCloser, stdout io.WriteCloser, stderr io.WriteCloser, args ...string)error 56 | 57 | 58 | type internalPromotedHandlerFunc struct { 59 | err error 60 | fn HandlerFunc 61 | stdin io.ReadCloser 62 | stdout io.WriteCloser 63 | stderr io.WriteCloser 64 | 65 | stdinPipe io.WriteCloser 66 | stdoutPipe io.ReadCloser 67 | stderrPipe io.ReadCloser 68 | 69 | args []string 70 | } 71 | 72 | 73 | // PromoteHandlerFunc turns a HandlerFunc into a Handler. 74 | func PromoteHandlerFunc(fn HandlerFunc, args ...string) Handler { 75 | stdin, stdinPipe := io.Pipe() 76 | stdoutPipe, stdout := io.Pipe() 77 | stderrPipe, stderr := io.Pipe() 78 | 79 | argsCopy := make([]string, len(args)) 80 | for i, datum := range args { 81 | argsCopy[i] = datum 82 | } 83 | 84 | handler := internalPromotedHandlerFunc{ 85 | err:nil, 86 | 87 | fn:fn, 88 | 89 | stdin:stdin, 90 | stdout:stdout, 91 | stderr:stderr, 92 | 93 | stdinPipe:stdinPipe, 94 | stdoutPipe:stdoutPipe, 95 | stderrPipe:stderrPipe, 96 | 97 | args:argsCopy, 98 | } 99 | 100 | return &handler 101 | } 102 | 103 | 104 | func (handler *internalPromotedHandlerFunc) Run() error { 105 | if nil != handler.err { 106 | return handler.err 107 | } 108 | 109 | handler.err = handler.fn(handler.stdin, handler.stdout, handler.stderr, handler.args...) 110 | 111 | handler.stdin.Close() 112 | handler.stdout.Close() 113 | handler.stderr.Close() 114 | 115 | return handler.err 116 | } 117 | 118 | func (handler *internalPromotedHandlerFunc) StdinPipe() (io.WriteCloser, error) { 119 | if nil != handler.err { 120 | return nil, handler.err 121 | } 122 | 123 | return handler.stdinPipe, nil 124 | } 125 | 126 | func (handler *internalPromotedHandlerFunc) StdoutPipe() (io.ReadCloser, error) { 127 | if nil != handler.err { 128 | return nil, handler.err 129 | } 130 | 131 | return handler.stdoutPipe, nil 132 | } 133 | 134 | func (handler *internalPromotedHandlerFunc) StderrPipe() (io.ReadCloser, error) { 135 | if nil != handler.err { 136 | return nil, handler.err 137 | } 138 | 139 | return handler.stderrPipe, nil 140 | } 141 | -------------------------------------------------------------------------------- /tls.go: -------------------------------------------------------------------------------- 1 | package telnet 2 | 3 | 4 | import ( 5 | "crypto/tls" 6 | "net" 7 | ) 8 | 9 | 10 | // ListenAndServeTLS acts identically to ListenAndServe, except that it 11 | // uses the TELNET protocol over TLS. 12 | // 13 | // From a TELNET protocol point-of-view, it allows for 'secured telnet', also known as TELNETS, 14 | // which by default listens to port 992. 15 | // 16 | // Of course, this port can be overridden using the 'addr' argument. 17 | // 18 | // For a very simple example: 19 | // 20 | // package main 21 | // 22 | // import ( 23 | // "github.com/reiver/go-telnet" 24 | // ) 25 | // 26 | // func main() { 27 | // 28 | // //@TODO: In your code, you would probably want to use a different handler. 29 | // var handler telnet.Handler = telnet.EchoHandler 30 | // 31 | // err := telnet.ListenAndServeTLS(":5555", "cert.pem", "key.pem", handler) 32 | // if nil != err { 33 | // //@TODO: Handle this error better. 34 | // panic(err) 35 | // } 36 | // } 37 | func ListenAndServeTLS(addr string, certFile string, keyFile string, handler Handler) error { 38 | server := &Server{Addr: addr, Handler: handler} 39 | return server.ListenAndServeTLS(certFile, keyFile) 40 | } 41 | 42 | 43 | 44 | // ListenAndServeTLS acts identically to ListenAndServe, except that it 45 | // uses the TELNET protocol over TLS. 46 | // 47 | // From a TELNET protocol point-of-view, it allows for 'secured telnet', also known as TELNETS, 48 | // which by default listens to port 992. 49 | func (server *Server) ListenAndServeTLS(certFile string, keyFile string) error { 50 | 51 | addr := server.Addr 52 | if "" == addr { 53 | addr = ":telnets" 54 | } 55 | 56 | 57 | listener, err := net.Listen("tcp", addr) 58 | if nil != err { 59 | return err 60 | } 61 | 62 | 63 | // Apparently have to make a copy of the TLS config this way, rather than by 64 | // simple assignment, to prevent some unexported fields from being copied over. 65 | // 66 | // It would be nice if tls.Config had a method that would do this "safely". 67 | // (I.e., what happens if in the future more exported fields are added to 68 | // tls.Config?) 69 | var tlsConfig *tls.Config = nil 70 | if nil == server.TLSConfig { 71 | tlsConfig = &tls.Config{} 72 | } else { 73 | tlsConfig = &tls.Config{ 74 | Rand: server.TLSConfig.Rand, 75 | Time: server.TLSConfig.Time, 76 | Certificates: server.TLSConfig.Certificates, 77 | NameToCertificate: server.TLSConfig.NameToCertificate, 78 | GetCertificate: server.TLSConfig.GetCertificate, 79 | RootCAs: server.TLSConfig.RootCAs, 80 | NextProtos: server.TLSConfig.NextProtos, 81 | ServerName: server.TLSConfig.ServerName, 82 | ClientAuth: server.TLSConfig.ClientAuth, 83 | ClientCAs: server.TLSConfig.ClientCAs, 84 | InsecureSkipVerify: server.TLSConfig.InsecureSkipVerify, 85 | CipherSuites: server.TLSConfig.CipherSuites, 86 | PreferServerCipherSuites: server.TLSConfig.PreferServerCipherSuites, 87 | SessionTicketsDisabled: server.TLSConfig.SessionTicketsDisabled, 88 | SessionTicketKey: server.TLSConfig.SessionTicketKey, 89 | ClientSessionCache: server.TLSConfig.ClientSessionCache, 90 | MinVersion: server.TLSConfig.MinVersion, 91 | MaxVersion: server.TLSConfig.MaxVersion, 92 | CurvePreferences: server.TLSConfig.CurvePreferences, 93 | } 94 | } 95 | 96 | 97 | tlsConfigHasCertificate := len(tlsConfig.Certificates) > 0 || nil != tlsConfig.GetCertificate 98 | if "" == certFile || "" == keyFile || !tlsConfigHasCertificate { 99 | tlsConfig.Certificates = make([]tls.Certificate, 1) 100 | 101 | var err error 102 | tlsConfig.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile) 103 | if nil != err { 104 | return err 105 | } 106 | } 107 | 108 | 109 | tlsListener := tls.NewListener(listener, tlsConfig) 110 | 111 | 112 | return server.Serve(tlsListener) 113 | } 114 | -------------------------------------------------------------------------------- /conn.go: -------------------------------------------------------------------------------- 1 | package telnet 2 | 3 | 4 | import ( 5 | "crypto/tls" 6 | "net" 7 | ) 8 | 9 | 10 | type Conn struct { 11 | conn interface { 12 | Read(b []byte) (n int, err error) 13 | Write(b []byte) (n int, err error) 14 | Close() error 15 | LocalAddr() net.Addr 16 | RemoteAddr() net.Addr 17 | } 18 | dataReader *internalDataReader 19 | dataWriter *internalDataWriter 20 | } 21 | 22 | 23 | // Dial makes a (un-secure) TELNET client connection to the system's 'loopback address' 24 | // (also known as "localhost" or 127.0.0.1). 25 | // 26 | // If a secure connection is desired, use `DialTLS` instead. 27 | func Dial() (*Conn, error) { 28 | return DialTo("") 29 | } 30 | 31 | // DialTo makes a (un-secure) TELNET client connection to the the address specified by 32 | // 'addr'. 33 | // 34 | // If a secure connection is desired, use `DialToTLS` instead. 35 | func DialTo(addr string) (*Conn, error) { 36 | 37 | const network = "tcp" 38 | 39 | if "" == addr { 40 | addr = "127.0.0.1:telnet" 41 | } 42 | 43 | conn, err := net.Dial(network, addr) 44 | if nil != err { 45 | return nil, err 46 | } 47 | 48 | dataReader := newDataReader(conn) 49 | dataWriter := newDataWriter(conn) 50 | 51 | clientConn := Conn{ 52 | conn:conn, 53 | dataReader:dataReader, 54 | dataWriter:dataWriter, 55 | } 56 | 57 | return &clientConn, nil 58 | } 59 | 60 | 61 | // DialTLS makes a (secure) TELNETS client connection to the system's 'loopback address' 62 | // (also known as "localhost" or 127.0.0.1). 63 | func DialTLS(tlsConfig *tls.Config) (*Conn, error) { 64 | return DialToTLS("", tlsConfig) 65 | } 66 | 67 | // DialToTLS makes a (secure) TELNETS client connection to the the address specified by 68 | // 'addr'. 69 | func DialToTLS(addr string, tlsConfig *tls.Config) (*Conn, error) { 70 | 71 | const network = "tcp" 72 | 73 | if "" == addr { 74 | addr = "127.0.0.1:telnets" 75 | } 76 | 77 | conn, err := tls.Dial(network, addr, tlsConfig) 78 | if nil != err { 79 | return nil, err 80 | } 81 | 82 | dataReader := newDataReader(conn) 83 | dataWriter := newDataWriter(conn) 84 | 85 | clientConn := Conn{ 86 | conn:conn, 87 | dataReader:dataReader, 88 | dataWriter:dataWriter, 89 | } 90 | 91 | return &clientConn, nil 92 | } 93 | 94 | 95 | 96 | // Close closes the client connection. 97 | // 98 | // Typical usage might look like: 99 | // 100 | // telnetsClient, err = telnet.DialToTLS(addr, tlsConfig) 101 | // if nil != err { 102 | // //@TODO: Handle error. 103 | // return err 104 | // } 105 | // defer telnetsClient.Close() 106 | func (clientConn *Conn) Close() error { 107 | return clientConn.conn.Close() 108 | } 109 | 110 | 111 | // Read receives `n` bytes sent from the server to the client, 112 | // and "returns" into `p`. 113 | // 114 | // Note that Read can only be used for receiving TELNET (and TELNETS) data from the server. 115 | // 116 | // TELNET (and TELNETS) command codes cannot be received using this method, as Read deals 117 | // with TELNET (and TELNETS) "unescaping", and (when appropriate) filters out TELNET (and TELNETS) 118 | // command codes. 119 | // 120 | // Read makes Client fit the io.Reader interface. 121 | func (clientConn *Conn) Read(p []byte) (n int, err error) { 122 | return clientConn.dataReader.Read(p) 123 | } 124 | 125 | 126 | // Write sends `n` bytes from 'p' to the server. 127 | // 128 | // Note that Write can only be used for sending TELNET (and TELNETS) data to the server. 129 | // 130 | // TELNET (and TELNETS) command codes cannot be sent using this method, as Write deals with 131 | // TELNET (and TELNETS) "escaping", and will properly "escape" anything written with it. 132 | // 133 | // Write makes Conn fit the io.Writer interface. 134 | func (clientConn *Conn) Write(p []byte) (n int, err error) { 135 | return clientConn.dataWriter.Write(p) 136 | } 137 | 138 | 139 | // LocalAddr returns the local network address. 140 | func (clientConn *Conn) LocalAddr() net.Addr { 141 | return clientConn.conn.LocalAddr() 142 | } 143 | 144 | 145 | // RemoteAddr returns the remote network address. 146 | func (clientConn *Conn) RemoteAddr() net.Addr { 147 | return clientConn.conn.RemoteAddr() 148 | } 149 | -------------------------------------------------------------------------------- /data_writer.go: -------------------------------------------------------------------------------- 1 | package telnet 2 | 3 | 4 | import ( 5 | "github.com/reiver/go-oi" 6 | 7 | "bytes" 8 | "errors" 9 | "io" 10 | ) 11 | 12 | 13 | var iaciac []byte = []byte{255, 255} 14 | 15 | var errOverflow = errors.New("Overflow") 16 | var errPartialIACIACWrite = errors.New("Partial IAC IAC write.") 17 | 18 | 19 | // An internalDataWriter deals with "escaping" according to the TELNET (and TELNETS) protocol. 20 | // 21 | // In the TELNET (and TELNETS) protocol byte value 255 is special. 22 | // 23 | // The TELNET (and TELNETS) protocol calls byte value 255: "IAC". Which is short for "interpret as command". 24 | // 25 | // The TELNET (and TELNETS) protocol also has a distinction between 'data' and 'commands'. 26 | // 27 | //(DataWriter is targetted toward TELNET (and TELNETS) 'data', not TELNET (and TELNETS) 'commands'.) 28 | // 29 | // If a byte with value 255 (=IAC) appears in the data, then it must be escaped. 30 | // 31 | // Escaping byte value 255 (=IAC) in the data is done by putting 2 of them in a row. 32 | // 33 | // So, for example: 34 | // 35 | // []byte{255} -> []byte{255, 255} 36 | // 37 | // Or, for a more complete example, if we started with the following: 38 | // 39 | // []byte{1, 55, 2, 155, 3, 255, 4, 40, 255, 30, 20} 40 | // 41 | // ... TELNET escaping would produce the following: 42 | // 43 | // []byte{1, 55, 2, 155, 3, 255, 255, 4, 40, 255, 255, 30, 20} 44 | // 45 | // (Notice that each "255" in the original byte array became 2 "255"s in a row.) 46 | // 47 | // internalDataWriter takes care of all this for you, so you do not have to do it. 48 | type internalDataWriter struct { 49 | wrapped io.Writer 50 | } 51 | 52 | 53 | // newDataWriter creates a new internalDataWriter writing to 'w'. 54 | // 55 | // 'w' receives what is written to the *internalDataWriter but escaped according to 56 | // the TELNET (and TELNETS) protocol. 57 | // 58 | // I.e., byte 255 (= IAC) gets encoded as 255, 255. 59 | // 60 | // For example, if the following it written to the *internalDataWriter's Write method: 61 | // 62 | // []byte{1, 55, 2, 155, 3, 255, 4, 40, 255, 30, 20} 63 | // 64 | // ... then (conceptually) the following is written to 'w's Write method: 65 | // 66 | // []byte{1, 55, 2, 155, 3, 255, 255, 4, 40, 255, 255, 30, 20} 67 | // 68 | // (Notice that each "255" in the original byte array became 2 "255"s in a row.) 69 | // 70 | // *internalDataWriter takes care of all this for you, so you do not have to do it. 71 | func newDataWriter(w io.Writer) *internalDataWriter { 72 | writer := internalDataWriter{ 73 | wrapped:w, 74 | } 75 | 76 | return &writer 77 | } 78 | 79 | 80 | // Write writes the TELNET (and TELNETS) escaped data for of the data in 'data' to the wrapped io.Writer. 81 | func (w *internalDataWriter) Write(data []byte) (n int, err error) { 82 | var n64 int64 83 | 84 | n64, err = w.write64(data) 85 | n = int(n64) 86 | if int64(n) != n64 { 87 | panic(errOverflow) 88 | } 89 | 90 | return n, err 91 | } 92 | 93 | 94 | func (w *internalDataWriter) write64(data []byte) (n int64, err error) { 95 | 96 | if len(data) <= 0 { 97 | return 0, nil 98 | } 99 | 100 | const IAC = 255 101 | 102 | var buffer bytes.Buffer 103 | for _, datum := range data { 104 | 105 | if IAC == datum { 106 | 107 | if buffer.Len() > 0 { 108 | var numWritten int64 109 | 110 | numWritten, err = oi.LongWrite(w.wrapped, buffer.Bytes()) 111 | n += numWritten 112 | if nil != err { 113 | return n, err 114 | } 115 | buffer.Reset() 116 | } 117 | 118 | 119 | var numWritten int64 120 | //@TODO: Should we worry about "iaciac" potentially being modified by the .Write()? 121 | numWritten, err = oi.LongWrite(w.wrapped, iaciac) 122 | if int64(len(iaciac)) != numWritten { 123 | //@TODO: Do we really want to panic() here? 124 | panic(errPartialIACIACWrite) 125 | } 126 | n += 1 127 | if nil != err { 128 | return n, err 129 | } 130 | } else { 131 | buffer.WriteByte(datum) // The returned error is always nil, so we ignore it. 132 | } 133 | } 134 | 135 | if buffer.Len() > 0 { 136 | var numWritten int64 137 | numWritten, err = oi.LongWrite(w.wrapped, buffer.Bytes()) 138 | n += numWritten 139 | } 140 | 141 | return n, err 142 | } 143 | -------------------------------------------------------------------------------- /data_reader.go: -------------------------------------------------------------------------------- 1 | package telnet 2 | 3 | 4 | import ( 5 | "bufio" 6 | "errors" 7 | "io" 8 | ) 9 | 10 | 11 | var ( 12 | errCorrupted = errors.New("Corrupted") 13 | ) 14 | 15 | 16 | // An internalDataReader deals with "un-escaping" according to the TELNET protocol. 17 | // 18 | // In the TELNET protocol byte value 255 is special. 19 | // 20 | // The TELNET protocol calls byte value 255: "IAC". Which is short for "interpret as command". 21 | // 22 | // The TELNET protocol also has a distinction between 'data' and 'commands'. 23 | // 24 | //(DataReader is targetted toward TELNET 'data', not TELNET 'commands'.) 25 | // 26 | // If a byte with value 255 (=IAC) appears in the data, then it must be escaped. 27 | // 28 | // Escaping byte value 255 (=IAC) in the data is done by putting 2 of them in a row. 29 | // 30 | // So, for example: 31 | // 32 | // []byte{255} -> []byte{255, 255} 33 | // 34 | // Or, for a more complete example, if we started with the following: 35 | // 36 | // []byte{1, 55, 2, 155, 3, 255, 4, 40, 255, 30, 20} 37 | // 38 | // ... TELNET escaping would produce the following: 39 | // 40 | // []byte{1, 55, 2, 155, 3, 255, 255, 4, 40, 255, 255, 30, 20} 41 | // 42 | // (Notice that each "255" in the original byte array became 2 "255"s in a row.) 43 | // 44 | // DataReader deals with "un-escaping". In other words, it un-does what was shown 45 | // in the examples. 46 | // 47 | // So, for example, it does this: 48 | // 49 | // []byte{255, 255} -> []byte{255} 50 | // 51 | // And, for example, goes from this: 52 | // 53 | // []byte{1, 55, 2, 155, 3, 255, 255, 4, 40, 255, 255, 30, 20} 54 | // 55 | // ... to this: 56 | // 57 | // []byte{1, 55, 2, 155, 3, 255, 4, 40, 255, 30, 20} 58 | type internalDataReader struct { 59 | wrapped io.Reader 60 | buffered *bufio.Reader 61 | } 62 | 63 | 64 | // newDataReader creates a new DataReader reading from 'r'. 65 | func newDataReader(r io.Reader) *internalDataReader { 66 | buffered := bufio.NewReader(r) 67 | 68 | reader := internalDataReader{ 69 | wrapped:r, 70 | buffered:buffered, 71 | } 72 | 73 | return &reader 74 | } 75 | 76 | 77 | // Read reads the TELNET escaped data from the wrapped io.Reader, and "un-escapes" it into 'data'. 78 | func (r *internalDataReader) Read(data []byte) (n int, err error) { 79 | 80 | const IAC = 255 81 | 82 | const SB = 250 83 | const SE = 240 84 | 85 | const WILL = 251 86 | const WONT = 252 87 | const DO = 253 88 | const DONT = 254 89 | 90 | p := data 91 | 92 | for len(p) > 0 { 93 | var b byte 94 | 95 | b, err = r.buffered.ReadByte() 96 | if nil != err { 97 | return n, err 98 | } 99 | 100 | if IAC == b { 101 | var peeked []byte 102 | 103 | peeked, err = r.buffered.Peek(1) 104 | if nil != err { 105 | return n, err 106 | } 107 | 108 | switch peeked[0] { 109 | case WILL, WONT, DO, DONT: 110 | _, err = r.buffered.Discard(2) 111 | if nil != err { 112 | return n, err 113 | } 114 | case IAC: 115 | p[0] = IAC 116 | n++ 117 | p = p[1:] 118 | 119 | _, err = r.buffered.Discard(1) 120 | if nil != err { 121 | return n, err 122 | } 123 | case SB: 124 | for { 125 | var b2 byte 126 | b2, err = r.buffered.ReadByte() 127 | if nil != err { 128 | return n, err 129 | } 130 | 131 | if IAC == b2 { 132 | peeked, err = r.buffered.Peek(1) 133 | if nil != err { 134 | return n, err 135 | } 136 | 137 | if IAC == peeked[0] { 138 | _, err = r.buffered.Discard(1) 139 | if nil != err { 140 | return n, err 141 | } 142 | } 143 | 144 | if SE == peeked[0] { 145 | _, err = r.buffered.Discard(1) 146 | if nil != err { 147 | return n, err 148 | } 149 | break 150 | } 151 | } 152 | } 153 | case SE: 154 | _, err = r.buffered.Discard(1) 155 | if nil != err { 156 | return n, err 157 | } 158 | default: 159 | // If we get in here, this is not following the TELNET protocol. 160 | //@TODO: Make a better error. 161 | err = errCorrupted 162 | return n, err 163 | } 164 | } else { 165 | 166 | p[0] = b 167 | n++ 168 | p = p[1:] 169 | } 170 | } 171 | 172 | return n, nil 173 | } 174 | -------------------------------------------------------------------------------- /telsh/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package telsh provides "middleware" (for the telnet package) that can be used to implement a TELNET or TELNETS server 3 | that provides a "shell" interface (also known as a "command-line interface" or "CLI"). 4 | 5 | Shell interfaces you may be familiar with include: "bash", "csh", "sh", "zsk", etc. 6 | 7 | 8 | TELNET Server 9 | 10 | Here is an example usage: 11 | 12 | package main 13 | 14 | import ( 15 | "github.com/reiver/go-oi" 16 | "github.com/reiver/go-telnet" 17 | "github.com/reiver/go-telnet/telsh" 18 | 19 | "io" 20 | ) 21 | 22 | func main() { 23 | 24 | telnetHandler := telsh.NewShellHandler() 25 | 26 | if err := telnetHandler.RegisterElse( 27 | telsh.ProducerFunc( 28 | func(ctx telnet.Context, name string, args ...string) telsh.Handler { 29 | return telsh.PromoteHandlerFunc( 30 | func(stdin io.ReadCloser, stdout io.WriteCloser, stderr io.WriteCloser, args ...string) error { 31 | oi.LongWrite(stdout, []byte{'w','a','t','?', '\r','\n'}) 32 | 33 | return nil 34 | }, 35 | ) 36 | }, 37 | ), 38 | ); nil != err { 39 | panic(err) 40 | } 41 | 42 | if err := telnetHandler.Register("help", 43 | telsh.ProducerFunc( 44 | func(ctx telnet.Context, name string, args ...string) telsh.Handler { 45 | return telsh.PromoteHandlerFunc( 46 | func(stdin io.ReadCloser, stdout io.WriteCloser, stderr io.WriteCloser, args ...string) error { 47 | oi.LongWrite(stdout, []byte{'r','t','f','m','!', '\r','\n'}) 48 | 49 | return nil 50 | }, 51 | ) 52 | }, 53 | ), 54 | ); nil != err { 55 | panic(err) 56 | } 57 | 58 | err := telnet.ListenAndServe(":5555", telnetHandler) 59 | if nil != err { 60 | //@TODO: Handle this error better. 61 | panic(err) 62 | } 63 | } 64 | 65 | Here is a more "unpacked" example: 66 | 67 | package main 68 | 69 | 70 | import ( 71 | "github.com/reiver/go-oi" 72 | "github.com/reiver/go-telnet" 73 | "github.com/reiver/go-telnet/telsh" 74 | 75 | "io" 76 | "time" 77 | ) 78 | 79 | 80 | var ( 81 | shellHandler = telsh.NewShellHandler() 82 | ) 83 | 84 | 85 | func init() { 86 | 87 | shellHandler.Register("dance", telsh.ProducerFunc(producer)) 88 | 89 | 90 | shellHandler.WelcomeMessage = ` 91 | __ __ ______ _ _____ ____ __ __ ______ 92 | \ \ / /| ____|| | / ____| / __ \ | \/ || ____| 93 | \ \ /\ / / | |__ | | | | | | | || \ / || |__ 94 | \ \/ \/ / | __| | | | | | | | || |\/| || __| 95 | \ /\ / | |____ | |____ | |____ | |__| || | | || |____ 96 | \/ \/ |______||______| \_____| \____/ |_| |_||______| 97 | 98 | ` 99 | } 100 | 101 | 102 | func producer(ctx telnet.Context, name string, args ...string) telsh.Handler{ 103 | return telsh.PromoteHandlerFunc(handler) 104 | } 105 | 106 | 107 | func handler(stdin io.ReadCloser, stdout io.WriteCloser, stderr io.WriteCloser, args ...string) error { 108 | for i:=0; i<20; i++ { 109 | oi.LongWriteString(stdout, "\r⠋") 110 | time.Sleep(50*time.Millisecond) 111 | 112 | oi.LongWriteString(stdout, "\r⠙") 113 | time.Sleep(50*time.Millisecond) 114 | 115 | oi.LongWriteString(stdout, "\r⠹") 116 | time.Sleep(50*time.Millisecond) 117 | 118 | oi.LongWriteString(stdout, "\r⠸") 119 | time.Sleep(50*time.Millisecond) 120 | 121 | oi.LongWriteString(stdout, "\r⠼") 122 | time.Sleep(50*time.Millisecond) 123 | 124 | oi.LongWriteString(stdout, "\r⠴") 125 | time.Sleep(50*time.Millisecond) 126 | 127 | oi.LongWriteString(stdout, "\r⠦") 128 | time.Sleep(50*time.Millisecond) 129 | 130 | oi.LongWriteString(stdout, "\r⠧") 131 | time.Sleep(50*time.Millisecond) 132 | 133 | oi.LongWriteString(stdout, "\r⠇") 134 | time.Sleep(50*time.Millisecond) 135 | 136 | oi.LongWriteString(stdout, "\r⠏") 137 | time.Sleep(50*time.Millisecond) 138 | } 139 | oi.LongWriteString(stdout, "\r \r\n") 140 | 141 | return nil 142 | } 143 | 144 | 145 | func main() { 146 | 147 | addr := ":5555" 148 | if err := telnet.ListenAndServe(addr, shellHandler); nil != err { 149 | panic(err) 150 | } 151 | } 152 | 153 | */ 154 | package telsh 155 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | package telnet 2 | 3 | 4 | import ( 5 | "crypto/tls" 6 | "net" 7 | ) 8 | 9 | 10 | // ListenAndServe listens on the TCP network address `addr` and then spawns a call to the ServeTELNET 11 | // method on the `handler` to serve each incoming connection. 12 | // 13 | // For a very simple example: 14 | // 15 | // package main 16 | // 17 | // import ( 18 | // "github.com/reiver/go-telnet" 19 | // ) 20 | // 21 | // func main() { 22 | // 23 | // //@TODO: In your code, you would probably want to use a different handler. 24 | // var handler telnet.Handler = telnet.EchoHandler 25 | // 26 | // err := telnet.ListenAndServe(":5555", handler) 27 | // if nil != err { 28 | // //@TODO: Handle this error better. 29 | // panic(err) 30 | // } 31 | // } 32 | func ListenAndServe(addr string, handler Handler) error { 33 | server := &Server{Addr: addr, Handler: handler} 34 | return server.ListenAndServe() 35 | } 36 | 37 | 38 | // Serve accepts an incoming TELNET or TELNETS client connection on the net.Listener `listener`. 39 | func Serve(listener net.Listener, handler Handler) error { 40 | 41 | server := &Server{Handler: handler} 42 | return server.Serve(listener) 43 | } 44 | 45 | 46 | // A Server defines parameters of a running TELNET server. 47 | // 48 | // For a simple example: 49 | // 50 | // package main 51 | // 52 | // import ( 53 | // "github.com/reiver/go-telnet" 54 | // ) 55 | // 56 | // func main() { 57 | // 58 | // var handler telnet.Handler = telnet.EchoHandler 59 | // 60 | // server := &telnet.Server{ 61 | // Addr:":5555", 62 | // Handler:handler, 63 | // } 64 | // 65 | // err := server.ListenAndServe() 66 | // if nil != err { 67 | // //@TODO: Handle this error better. 68 | // panic(err) 69 | // } 70 | // } 71 | type Server struct { 72 | Addr string // TCP address to listen on; ":telnet" or ":telnets" if empty (when used with ListenAndServe or ListenAndServeTLS respectively). 73 | Handler Handler // handler to invoke; telnet.EchoServer if nil 74 | 75 | TLSConfig *tls.Config // optional TLS configuration; used by ListenAndServeTLS. 76 | 77 | Logger Logger 78 | } 79 | 80 | // ListenAndServe listens on the TCP network address 'server.Addr' and then spawns a call to the ServeTELNET 81 | // method on the 'server.Handler' to serve each incoming connection. 82 | // 83 | // For a simple example: 84 | // 85 | // package main 86 | // 87 | // import ( 88 | // "github.com/reiver/go-telnet" 89 | // ) 90 | // 91 | // func main() { 92 | // 93 | // var handler telnet.Handler = telnet.EchoHandler 94 | // 95 | // server := &telnet.Server{ 96 | // Addr:":5555", 97 | // Handler:handler, 98 | // } 99 | // 100 | // err := server.ListenAndServe() 101 | // if nil != err { 102 | // //@TODO: Handle this error better. 103 | // panic(err) 104 | // } 105 | // } 106 | func (server *Server) ListenAndServe() error { 107 | 108 | addr := server.Addr 109 | if "" == addr { 110 | addr = ":telnet" 111 | } 112 | 113 | 114 | listener, err := net.Listen("tcp", addr) 115 | if nil != err { 116 | return err 117 | } 118 | 119 | 120 | return server.Serve(listener) 121 | } 122 | 123 | 124 | // Serve accepts an incoming TELNET client connection on the net.Listener `listener`. 125 | func (server *Server) Serve(listener net.Listener) error { 126 | 127 | defer listener.Close() 128 | 129 | 130 | logger := server.logger() 131 | 132 | 133 | handler := server.Handler 134 | if nil == handler { 135 | //@TODO: Should this be a "ShellHandler" instead, that gives a shell-like experience by default 136 | // If this is changd, then need to change the comment in the "type Server struct" definition. 137 | logger.Debug("Defaulted handler to EchoHandler.") 138 | handler = EchoHandler 139 | } 140 | 141 | 142 | for { 143 | // Wait for a new TELNET client connection. 144 | logger.Debugf("Listening at %q.", listener.Addr()) 145 | conn, err := listener.Accept() 146 | if err != nil { 147 | //@TODO: Could try to recover from certain kinds of errors. Maybe waiting a while before trying again. 148 | return err 149 | } 150 | logger.Debugf("Received new connection from %q.", conn.RemoteAddr()) 151 | 152 | // Handle the new TELNET client connection by spawning 153 | // a new goroutine. 154 | go server.handle(conn, handler) 155 | logger.Debugf("Spawned handler to handle connection from %q.", conn.RemoteAddr()) 156 | } 157 | } 158 | 159 | func (server *Server) handle(c net.Conn, handler Handler) { 160 | defer c.Close() 161 | 162 | logger := server.logger() 163 | 164 | 165 | defer func(){ 166 | if r := recover(); nil != r { 167 | if nil != logger { 168 | logger.Errorf("Recovered from: (%T) %v", r, r) 169 | } 170 | } 171 | }() 172 | 173 | var ctx Context = NewContext().InjectLogger(logger) 174 | 175 | var w Writer = newDataWriter(c) 176 | var r Reader = newDataReader(c) 177 | 178 | handler.ServeTELNET(ctx, w, r) 179 | c.Close() 180 | } 181 | 182 | 183 | 184 | func (server *Server) logger() Logger { 185 | logger := server.Logger 186 | if nil == logger { 187 | logger = internalDiscardLogger{} 188 | } 189 | 190 | return logger 191 | } 192 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-telnet 2 | 3 | Package **telnet** provides TELNET and TELNETS client and server implementations, for the Go programming language. 4 | 5 | 6 | The **telnet** package provides an API in a style similar to the "net/http" library that is part of the Go standard library, including support for "middleware". 7 | 8 | 9 | (TELNETS is *secure TELNET*, with the TELNET protocol over a secured TLS (or SSL) connection.) 10 | 11 | 12 | ## Documention 13 | 14 | Online documentation, which includes examples, can be found at: http://godoc.org/github.com/reiver/go-telnet 15 | 16 | [![GoDoc](https://godoc.org/github.com/reiver/go-telnet?status.svg)](https://godoc.org/github.com/reiver/go-telnet) 17 | 18 | 19 | ## Very Simple TELNET Server Example 20 | 21 | A very very simple TELNET server is shown in the following code. 22 | 23 | This particular TELNET server just echos back to the user anything they "submit" to the server. 24 | 25 | (By default, a TELNET client does *not* send anything to the server until the [Enter] key is pressed. 26 | "Submit" means typing something and then pressing the [Enter] key.) 27 | 28 | ``` 29 | package main 30 | 31 | import ( 32 | "github.com/reiver/go-telnet" 33 | ) 34 | 35 | func main() { 36 | 37 | var handler telnet.Handler = telnet.EchoHandler 38 | 39 | err := telnet.ListenAndServe(":5555", handler) 40 | if nil != err { 41 | //@TODO: Handle this error better. 42 | panic(err) 43 | } 44 | } 45 | 46 | ``` 47 | 48 | If you wanted to test out this very very simple TELNET server, if you were on the same computer it was 49 | running, you could connect to it using the bash command: 50 | ``` 51 | telnet localhost 5555 52 | ``` 53 | (Note that we use the same TCP port number -- "5555" -- as we had in our code. That is important, as the 54 | value used by your TELNET server and the value used by your TELNET client **must** match.) 55 | 56 | 57 | ## Very Simple (Secure) TELNETS Server Example 58 | 59 | TELNETS is the secure version of TELNET. 60 | 61 | The code to make a TELNETS server is very similar to the code to make a TELNET server. 62 | (The difference between we use the `telnet.ListenAndServeTLS` func instead of the 63 | `telnet.ListenAndServe` func.) 64 | 65 | ``` 66 | package main 67 | 68 | import ( 69 | "github.com/reiver/go-telnet" 70 | ) 71 | 72 | func main() { 73 | 74 | var handler telnet.Handler = telnet.EchoHandler 75 | 76 | err := telnet.ListenAndServeTLS(":5555", "cert.pem", "key.pem", handler) 77 | if nil != err { 78 | //@TODO: Handle this error better. 79 | panic(err) 80 | } 81 | } 82 | 83 | ``` 84 | 85 | If you wanted to test out this very very simple TELNETS server, get the `telnets` client program from here: 86 | https://github.com/reiver/telnets 87 | 88 | 89 | ## TELNET Client Example: 90 | ``` 91 | package main 92 | 93 | import ( 94 | "github.com/reiver/go-telnet" 95 | ) 96 | 97 | func main() { 98 | var caller telnet.Caller = telnet.StandardCaller 99 | 100 | //@TODO: replace "example.net:5555" with address you want to connect to. 101 | telnet.DialToAndCall("example.net:5555", caller) 102 | } 103 | ``` 104 | 105 | 106 | ## TELNETS Client Example: 107 | ``` 108 | package main 109 | 110 | import ( 111 | "github.com/reiver/go-telnet" 112 | 113 | "crypto/tls" 114 | ) 115 | 116 | func main() { 117 | //@TODO: Configure the TLS connection here, if you need to. 118 | tlsConfig := &tls.Config{} 119 | 120 | var caller telnet.Caller = telnet.StandardCaller 121 | 122 | //@TODO: replace "example.net:5555" with address you want to connect to. 123 | telnet.DialToAndCallTLS("example.net:5555", caller, tlsConfig) 124 | } 125 | ``` 126 | 127 | 128 | ## TELNET Shell Server Example 129 | 130 | A more useful TELNET servers can be made using the `"github.com/reiver/go-telnet/telsh"` sub-package. 131 | 132 | For example: 133 | ``` 134 | package main 135 | 136 | 137 | import ( 138 | "github.com/reiver/go-oi" 139 | "github.com/reiver/go-telnet" 140 | "github.com/reiver/go-telnet/telsh" 141 | 142 | "io" 143 | "time" 144 | ) 145 | 146 | 147 | 148 | func fiveHandler(stdin io.ReadCloser, stdout io.WriteCloser, stderr io.WriteCloser, args ...string) error { 149 | oi.LongWriteString(stdout, "The number FIVE looks like this: 5\r\n") 150 | 151 | return nil 152 | } 153 | 154 | func fiveProducer(ctx telnet.Context, name string, args ...string) telsh.Handler{ 155 | return telsh.PromoteHandlerFunc(fiveHandler) 156 | } 157 | 158 | 159 | 160 | func danceHandler(stdin io.ReadCloser, stdout io.WriteCloser, stderr io.WriteCloser, args ...string) error { 161 | for i:=0; i<20; i++ { 162 | oi.LongWriteString(stdout, "\r⠋") 163 | time.Sleep(50*time.Millisecond) 164 | 165 | oi.LongWriteString(stdout, "\r⠙") 166 | time.Sleep(50*time.Millisecond) 167 | 168 | oi.LongWriteString(stdout, "\r⠹") 169 | time.Sleep(50*time.Millisecond) 170 | 171 | oi.LongWriteString(stdout, "\r⠸") 172 | time.Sleep(50*time.Millisecond) 173 | 174 | oi.LongWriteString(stdout, "\r⠼") 175 | time.Sleep(50*time.Millisecond) 176 | 177 | oi.LongWriteString(stdout, "\r⠴") 178 | time.Sleep(50*time.Millisecond) 179 | 180 | oi.LongWriteString(stdout, "\r⠦") 181 | time.Sleep(50*time.Millisecond) 182 | 183 | oi.LongWriteString(stdout, "\r⠧") 184 | time.Sleep(50*time.Millisecond) 185 | 186 | oi.LongWriteString(stdout, "\r⠇") 187 | time.Sleep(50*time.Millisecond) 188 | 189 | oi.LongWriteString(stdout, "\r⠏") 190 | time.Sleep(50*time.Millisecond) 191 | } 192 | oi.LongWriteString(stdout, "\r \r\n") 193 | 194 | return nil 195 | } 196 | 197 | func danceProducer(ctx telnet.Context, name string, args ...string) telsh.Handler{ 198 | 199 | return telsh.PromoteHandlerFunc(danceHandler) 200 | } 201 | 202 | 203 | func main() { 204 | 205 | shellHandler := telsh.NewShellHandler() 206 | 207 | shellHandler.WelcomeMessage = ` 208 | __ __ ______ _ _____ ____ __ __ ______ 209 | \ \ / /| ____|| | / ____| / __ \ | \/ || ____| 210 | \ \ /\ / / | |__ | | | | | | | || \ / || |__ 211 | \ \/ \/ / | __| | | | | | | | || |\/| || __| 212 | \ /\ / | |____ | |____ | |____ | |__| || | | || |____ 213 | \/ \/ |______||______| \_____| \____/ |_| |_||______| 214 | 215 | ` 216 | 217 | 218 | // Register the "five" command. 219 | commandName := "five" 220 | commandProducer := telsh.ProducerFunc(fiveProducer) 221 | 222 | shellHandler.Register(commandName, commandProducer) 223 | 224 | 225 | 226 | // Register the "dance" command. 227 | commandName = "dance" 228 | commandProducer = telsh.ProducerFunc(danceProducer) 229 | 230 | shellHandler.Register(commandName, commandProducer) 231 | 232 | 233 | 234 | shellHandler.Register("dance", telsh.ProducerFunc(danceProducer)) 235 | 236 | addr := ":5555" 237 | if err := telnet.ListenAndServe(addr, shellHandler); nil != err { 238 | panic(err) 239 | } 240 | } 241 | ``` 242 | 243 | TELNET servers made using the `"github.com/reiver/go-telnet/telsh"` sub-package will often be more useful 244 | as it makes it easier for you to create a *shell* interface. 245 | 246 | 247 | # More Information 248 | 249 | There is a lot more information about documentation on all this here: http://godoc.org/github.com/reiver/go-telnet 250 | 251 | (You should really read those.) 252 | 253 | -------------------------------------------------------------------------------- /telsh/telnet_handler.go: -------------------------------------------------------------------------------- 1 | package telsh 2 | 3 | 4 | import ( 5 | "github.com/reiver/go-oi" 6 | "github.com/reiver/go-telnet" 7 | 8 | "bytes" 9 | "io" 10 | "strings" 11 | "sync" 12 | ) 13 | 14 | 15 | const ( 16 | defaultExitCommandName = "exit" 17 | defaultPrompt = "§ " 18 | defaultWelcomeMessage = "\r\nWelcome!\r\n" 19 | defaultExitMessage = "\r\nGoodbye!\r\n" 20 | ) 21 | 22 | 23 | type ShellHandler struct { 24 | muxtex sync.RWMutex 25 | producers map[string]Producer 26 | elseProducer Producer 27 | 28 | ExitCommandName string 29 | Prompt string 30 | WelcomeMessage string 31 | ExitMessage string 32 | } 33 | 34 | 35 | func NewShellHandler() *ShellHandler { 36 | producers := map[string]Producer{} 37 | 38 | telnetHandler := ShellHandler{ 39 | producers:producers, 40 | 41 | Prompt: defaultPrompt, 42 | ExitCommandName: defaultExitCommandName, 43 | WelcomeMessage: defaultWelcomeMessage, 44 | ExitMessage: defaultExitMessage, 45 | } 46 | 47 | return &telnetHandler 48 | } 49 | 50 | 51 | func (telnetHandler *ShellHandler) Register(name string, producer Producer) error { 52 | 53 | telnetHandler.muxtex.Lock() 54 | telnetHandler.producers[name] = producer 55 | telnetHandler.muxtex.Unlock() 56 | 57 | return nil 58 | } 59 | 60 | func (telnetHandler *ShellHandler) MustRegister(name string, producer Producer) *ShellHandler { 61 | if err := telnetHandler.Register(name, producer); nil != err { 62 | panic(err) 63 | } 64 | 65 | return telnetHandler 66 | } 67 | 68 | 69 | func (telnetHandler *ShellHandler) RegisterHandlerFunc(name string, handlerFunc HandlerFunc) error { 70 | 71 | produce := func(ctx telnet.Context, name string, args ...string) Handler { 72 | return PromoteHandlerFunc(handlerFunc, args...) 73 | } 74 | 75 | producer := ProducerFunc(produce) 76 | 77 | return telnetHandler.Register(name, producer) 78 | } 79 | 80 | func (telnetHandler *ShellHandler) MustRegisterHandlerFunc(name string, handlerFunc HandlerFunc) *ShellHandler { 81 | if err := telnetHandler.RegisterHandlerFunc(name, handlerFunc); nil != err { 82 | panic(err) 83 | } 84 | 85 | return telnetHandler 86 | } 87 | 88 | 89 | func (telnetHandler *ShellHandler) RegisterElse(producer Producer) error { 90 | 91 | telnetHandler.muxtex.Lock() 92 | telnetHandler.elseProducer = producer 93 | telnetHandler.muxtex.Unlock() 94 | 95 | return nil 96 | } 97 | 98 | func (telnetHandler *ShellHandler) MustRegisterElse(producer Producer) *ShellHandler { 99 | if err := telnetHandler.RegisterElse(producer); nil != err { 100 | panic(err) 101 | } 102 | 103 | return telnetHandler 104 | } 105 | 106 | 107 | func (telnetHandler *ShellHandler) ServeTELNET(ctx telnet.Context, writer telnet.Writer, reader telnet.Reader) { 108 | 109 | logger := ctx.Logger() 110 | if nil == logger { 111 | logger = internalDiscardLogger{} 112 | } 113 | 114 | 115 | colonSpaceCommandNotFoundEL := []byte(": command not found\r\n") 116 | 117 | 118 | var prompt bytes.Buffer 119 | var exitCommandName string 120 | var welcomeMessage string 121 | var exitMessage string 122 | 123 | prompt.WriteString(telnetHandler.Prompt) 124 | 125 | promptBytes := prompt.Bytes() 126 | 127 | exitCommandName = telnetHandler.ExitCommandName 128 | welcomeMessage = telnetHandler.WelcomeMessage 129 | exitMessage = telnetHandler.ExitMessage 130 | 131 | 132 | if _, err := oi.LongWriteString(writer, welcomeMessage); nil != err { 133 | logger.Errorf("Problem long writing welcome message: %v", err) 134 | return 135 | } 136 | logger.Debugf("Wrote welcome message: %q.", welcomeMessage) 137 | if _, err := oi.LongWrite(writer, promptBytes); nil != err { 138 | logger.Errorf("Problem long writing prompt: %v", err) 139 | return 140 | } 141 | logger.Debugf("Wrote prompt: %q.", promptBytes) 142 | 143 | 144 | var buffer [1]byte // Seems like the length of the buffer needs to be small, otherwise will have to wait for buffer to fill up. 145 | p := buffer[:] 146 | 147 | var line bytes.Buffer 148 | 149 | for { 150 | // Read 1 byte. 151 | n, err := reader.Read(p) 152 | if n <= 0 && nil == err { 153 | continue 154 | } else if n <= 0 && nil != err { 155 | break 156 | } 157 | 158 | 159 | line.WriteByte(p[0]) 160 | //logger.Tracef("Received: %q (%d).", p[0], p[0]) 161 | 162 | 163 | if '\n' == p[0] { 164 | lineString := line.String() 165 | 166 | if "\r\n" == lineString { 167 | line.Reset() 168 | if _, err := oi.LongWrite(writer, promptBytes); nil != err { 169 | return 170 | } 171 | continue 172 | } 173 | 174 | 175 | //@TODO: support piping. 176 | fields := strings.Fields(lineString) 177 | logger.Debugf("Have %d tokens.", len(fields)) 178 | logger.Tracef("Tokens: %v", fields) 179 | if len(fields) <= 0 { 180 | line.Reset() 181 | if _, err := oi.LongWrite(writer, promptBytes); nil != err { 182 | return 183 | } 184 | continue 185 | } 186 | 187 | 188 | field0 := fields[0] 189 | 190 | if exitCommandName == field0 { 191 | oi.LongWriteString(writer, exitMessage) 192 | return 193 | } 194 | 195 | 196 | var producer Producer 197 | 198 | telnetHandler.muxtex.RLock() 199 | var ok bool 200 | producer, ok = telnetHandler.producers[field0] 201 | telnetHandler.muxtex.RUnlock() 202 | 203 | if !ok { 204 | telnetHandler.muxtex.RLock() 205 | producer = telnetHandler.elseProducer 206 | telnetHandler.muxtex.RUnlock() 207 | } 208 | 209 | if nil == producer { 210 | //@TODO: Don't convert that to []byte! think this creates "garbage" (for collector). 211 | oi.LongWrite(writer, []byte(field0)) 212 | oi.LongWrite(writer, colonSpaceCommandNotFoundEL) 213 | line.Reset() 214 | if _, err := oi.LongWrite(writer, promptBytes); nil != err { 215 | return 216 | } 217 | continue 218 | } 219 | 220 | handler := producer.Produce(ctx, field0, fields[1:]...) 221 | if nil == handler { 222 | oi.LongWrite(writer, []byte(field0)) 223 | //@TODO: Need to use a different error message. 224 | oi.LongWrite(writer, colonSpaceCommandNotFoundEL) 225 | line.Reset() 226 | oi.LongWrite(writer, promptBytes) 227 | continue 228 | } 229 | 230 | //@TODO: Wire up the stdin, stdout, stderr of the handler. 231 | 232 | if stdoutPipe, err := handler.StdoutPipe(); nil != err { 233 | //@TODO: 234 | } else if nil == stdoutPipe { 235 | //@TODO: 236 | } else { 237 | connect(ctx, writer, stdoutPipe) 238 | } 239 | 240 | 241 | if stderrPipe, err := handler.StderrPipe(); nil != err { 242 | //@TODO: 243 | } else if nil == stderrPipe { 244 | //@TODO: 245 | } else { 246 | connect(ctx, writer, stderrPipe) 247 | } 248 | 249 | 250 | if err := handler.Run(); nil != err { 251 | //@TODO: 252 | } 253 | line.Reset() 254 | if _, err := oi.LongWrite(writer, promptBytes); nil != err { 255 | return 256 | } 257 | } 258 | 259 | 260 | //@TODO: Are there any special errors we should be dealing with separately? 261 | if nil != err { 262 | break 263 | } 264 | } 265 | 266 | 267 | oi.LongWriteString(writer, exitMessage) 268 | return 269 | } 270 | 271 | 272 | 273 | func connect(ctx telnet.Context, writer io.Writer, reader io.Reader) { 274 | 275 | logger := ctx.Logger() 276 | 277 | go func(logger telnet.Logger){ 278 | 279 | var buffer [1]byte // Seems like the length of the buffer needs to be small, otherwise will have to wait for buffer to fill up. 280 | p := buffer[:] 281 | 282 | for { 283 | // Read 1 byte. 284 | n, err := reader.Read(p) 285 | if n <= 0 && nil == err { 286 | continue 287 | } else if n <= 0 && nil != err { 288 | break 289 | } 290 | 291 | //logger.Tracef("Sending: %q.", p) 292 | //@TODO: Should we be checking for errors? 293 | oi.LongWrite(writer, p) 294 | //logger.Tracef("Sent: %q.", p) 295 | } 296 | }(logger) 297 | } 298 | -------------------------------------------------------------------------------- /echo_handler_test.go: -------------------------------------------------------------------------------- 1 | package telnet 2 | 3 | 4 | import ( 5 | "bytes" 6 | 7 | "testing" 8 | ) 9 | 10 | 11 | func TestEchoHandler(t *testing.T) { 12 | 13 | tests := []struct{ 14 | Bytes []byte 15 | Expected []byte 16 | }{ 17 | { 18 | Bytes: []byte{}, 19 | Expected: []byte{}, 20 | }, 21 | 22 | 23 | 24 | { 25 | Bytes: []byte("a"), 26 | Expected: []byte("a"), 27 | }, 28 | { 29 | Bytes: []byte("b"), 30 | Expected: []byte("b"), 31 | }, 32 | { 33 | Bytes: []byte("c"), 34 | Expected: []byte("c"), 35 | }, 36 | 37 | 38 | 39 | { 40 | Bytes: []byte("apple"), 41 | Expected: []byte("apple"), 42 | }, 43 | { 44 | Bytes: []byte("banana"), 45 | Expected: []byte("banana"), 46 | }, 47 | { 48 | Bytes: []byte("cherry"), 49 | Expected: []byte("cherry"), 50 | }, 51 | 52 | 53 | 54 | { 55 | Bytes: []byte("apple banana cherry"), 56 | Expected: []byte("apple banana cherry"), 57 | }, 58 | 59 | 60 | 61 | { 62 | Bytes: []byte{255, 255}, 63 | Expected: []byte{255, 255}, 64 | }, 65 | { 66 | Bytes: []byte{255, 255, 255, 255}, 67 | Expected: []byte{255, 255, 255, 255}, 68 | }, 69 | { 70 | Bytes: []byte{255, 255, 255, 255, 255, 255}, 71 | Expected: []byte{255, 255, 255, 255, 255, 255}, 72 | }, 73 | { 74 | Bytes: []byte{255, 255, 255, 255, 255, 255, 255, 255}, 75 | Expected: []byte{255, 255, 255, 255, 255, 255, 255, 255}, 76 | }, 77 | { 78 | Bytes: []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, 79 | Expected: []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, 80 | }, 81 | 82 | 83 | 84 | { 85 | Bytes: []byte("apple\xff\xffbanana\xff\xffcherry"), 86 | Expected: []byte("apple\xff\xffbanana\xff\xffcherry"), 87 | }, 88 | { 89 | Bytes: []byte("\xff\xffapple\xff\xffbanana\xff\xffcherry\xff\xff"), 90 | Expected: []byte("\xff\xffapple\xff\xffbanana\xff\xffcherry\xff\xff"), 91 | }, 92 | 93 | 94 | 95 | { 96 | Bytes: []byte("apple\xff\xff\xff\xffbanana\xff\xff\xff\xffcherry"), 97 | Expected: []byte("apple\xff\xff\xff\xffbanana\xff\xff\xff\xffcherry"), 98 | }, 99 | { 100 | Bytes: []byte("\xff\xff\xff\xffapple\xff\xff\xff\xffbanana\xff\xff\xff\xffcherry\xff\xff\xff\xff"), 101 | Expected: []byte("\xff\xff\xff\xffapple\xff\xff\xff\xffbanana\xff\xff\xff\xffcherry\xff\xff\xff\xff"), 102 | }, 103 | 104 | 105 | 106 | { 107 | Bytes: []byte{255,251,24}, // IAC WILL TERMINAL-TYPE 108 | Expected: []byte{}, 109 | }, 110 | { 111 | Bytes: []byte{255,252,24}, // IAC WON'T TERMINAL-TYPE 112 | Expected: []byte{}, 113 | }, 114 | { 115 | Bytes: []byte{255,253,24}, // IAC DO TERMINAL-TYPE 116 | Expected: []byte{}, 117 | }, 118 | { 119 | Bytes: []byte{255,254,24}, // IAC DON'T TERMINAL-TYPE 120 | Expected: []byte{}, 121 | }, 122 | 123 | 124 | 125 | { 126 | Bytes: []byte{67, 255,251,24}, // 'C' IAC WILL TERMINAL-TYPE 127 | Expected: []byte{67}, 128 | }, 129 | { 130 | Bytes: []byte{67, 255,252,24}, // 'C' IAC WON'T TERMINAL-TYPE 131 | Expected: []byte{67}, 132 | }, 133 | { 134 | Bytes: []byte{67, 255,253,24}, // 'C' IAC DO TERMINAL-TYPE 135 | Expected: []byte{67}, 136 | }, 137 | { 138 | Bytes: []byte{67, 255,254,24}, // 'C' IAC DON'T TERMINAL-TYPE 139 | Expected: []byte{67}, 140 | }, 141 | 142 | 143 | 144 | { 145 | Bytes: []byte{255,251,24, 68}, // IAC WILL TERMINAL-TYPE 'D' 146 | Expected: []byte{68}, 147 | }, 148 | { 149 | Bytes: []byte{255,252,24, 68}, // IAC WON'T TERMINAL-TYPE 'D' 150 | Expected: []byte{68}, 151 | }, 152 | { 153 | Bytes: []byte{255,253,24, 68}, // IAC DO TERMINAL-TYPE 'D' 154 | Expected: []byte{68}, 155 | }, 156 | { 157 | Bytes: []byte{255,254,24, 68}, // IAC DON'T TERMINAL-TYPE 'D' 158 | Expected: []byte{68}, 159 | }, 160 | 161 | 162 | { 163 | Bytes: []byte{67, 255,251,24, 68}, // 'C' IAC WILL TERMINAL-TYPE 'D' 164 | Expected: []byte{67,68}, 165 | }, 166 | { 167 | Bytes: []byte{67, 255,252,24, 68}, // 'C' IAC WON'T TERMINAL-TYPE 'D' 168 | Expected: []byte{67,68}, 169 | }, 170 | { 171 | Bytes: []byte{67, 255,253,24, 68}, // 'C' IAC DO TERMINAL-TYPE 'D' 172 | Expected: []byte{67,68}, 173 | }, 174 | { 175 | Bytes: []byte{67, 255,254,24, 68}, // 'C' IAC DON'T TERMINAL-TYPE 'D' 176 | Expected: []byte{67,68}, 177 | }, 178 | 179 | 180 | 181 | { 182 | Bytes: []byte{255, 250, 24, 1, 255, 240}, // IAC SB TERMINAL-TYPE SEND IAC SE 183 | Expected: []byte{}, 184 | }, 185 | { 186 | Bytes: []byte{255, 250, 24, 0, 68,69,67,45,86,84,53,50 ,255, 240}, // IAC SB TERMINAL-TYPE IS "DEC-VT52" IAC SE 187 | Expected: []byte{}, 188 | }, 189 | 190 | 191 | 192 | { 193 | Bytes: []byte{67, 255, 250, 24, 1, 255, 240}, // 'C' IAC SB TERMINAL-TYPE SEND IAC SE 194 | Expected: []byte{67}, 195 | }, 196 | { 197 | Bytes: []byte{67, 255, 250, 24, 0, 68,69,67,45,86,84,53,50 ,255, 240}, // 'C' IAC SB TERMINAL-TYPE IS "DEC-VT52" IAC SE 198 | Expected: []byte{67}, 199 | }, 200 | 201 | 202 | 203 | { 204 | Bytes: []byte{255, 250, 24, 1, 255, 240, 68}, // IAC SB TERMINAL-TYPE SEND IAC SE 'D' 205 | Expected: []byte{68}, 206 | }, 207 | { 208 | Bytes: []byte{255, 250, 24, 0, 68,69,67,45,86,84,53,50 ,255, 240, 68}, // IAC SB TERMINAL-TYPE IS "DEC-VT52" IAC SE 'D' 209 | Expected: []byte{68}, 210 | }, 211 | 212 | 213 | 214 | { 215 | Bytes: []byte{67, 255, 250, 24, 1, 255, 240, 68}, // 'C' IAC SB TERMINAL-TYPE SEND IAC SE 'D' 216 | Expected: []byte{67, 68}, 217 | }, 218 | { 219 | Bytes: []byte{67, 255, 250, 24, 0, 68,69,67,45,86,84,53,50 ,255, 240, 68}, // 'C' IAC SB TERMINAL-TYPE IS "DEC-VT52" IAC SE 'D' 220 | Expected: []byte{67, 68}, 221 | }, 222 | 223 | 224 | 225 | { 226 | Bytes: []byte{255,250, 0,1,2,3,4,5,6,7,8,9,10,11,12,13 ,255,240}, // IAC SB 0 1 2 3 4 5 6 7 8 9 10 11 12 13 IAC SE 227 | Expected: []byte{}, 228 | }, 229 | { 230 | Bytes: []byte{67, 255,250, 0,1,2,3,4,5,6,7,8,9,10,11,12,13 ,255,240}, // 'C' IAC SB 0 1 2 3 4 5 6 7 8 9 10 11 12 13 IAC SE 231 | Expected: []byte{67}, 232 | }, 233 | { 234 | Bytes: []byte{255,250, 0,1,2,3,4,5,6,7,8,9,10,11,12,13 ,255,240, 68}, // IAC SB 0 1 2 3 4 5 6 7 8 9 10 11 12 13 IAC SE 'D' 235 | Expected: []byte{68}, 236 | }, 237 | { 238 | Bytes: []byte{67, 255,250, 0,1,2,3,4,5,6,7,8,9,10,11,12,13 ,255,240, 68}, // 'C' IAC SB 0 1 2 3 4 5 6 7 8 9 10 11 12 13 IAC SE 'D' 239 | Expected: []byte{67,68}, 240 | }, 241 | 242 | 243 | 244 | //@TODO: Is this correct? Can IAC appear between thee 'IAC SB' and ''IAC SE'?... and if "yes", do escaping rules apply? 245 | { 246 | Bytes: []byte{ 255,250, 255,255,240 ,255,240}, // IAC SB 255 255 240 IAC SE = IAC SB IAC IAC SE IAC SE 247 | Expected: []byte{}, 248 | }, 249 | { 250 | Bytes: []byte{67, 255,250, 255,255,240 ,255,240}, // 'C' IAC SB 255 255 240 IAC SE = IAC SB IAC IAC SE IAC SE 251 | Expected: []byte{67}, 252 | }, 253 | { 254 | Bytes: []byte{ 255,250, 255,255,240 ,255,240, 68}, // IAC SB 255 255 240 IAC SE = IAC SB IAC IAC SE IAC SE 'D' 255 | Expected: []byte{68}, 256 | }, 257 | { 258 | Bytes: []byte{67, 255,250, 255,255,240 ,255,240, 68}, // 'C' IAC SB 255 255 240 IAC SE = IAC SB IAC IAC SE IAC SE 'D' 259 | Expected: []byte{67,68}, 260 | }, 261 | 262 | 263 | 264 | //@TODO: Is this correct? Can IAC appear between thee 'IAC SB' and ''IAC SE'?... and if "yes", do escaping rules apply? 265 | { 266 | Bytes: []byte{ 255,250, 71,255,255,240 ,255,240}, // IAC SB 'G' 255 255 240 IAC SE = IAC SB 'G' IAC IAC SE IAC SE 267 | Expected: []byte{}, 268 | }, 269 | { 270 | Bytes: []byte{67, 255,250, 71,255,255,240 ,255,240}, // 'C' IAC SB 'G' 255 255 240 IAC SE = IAC SB 'G' IAC IAC SE IAC SE 271 | Expected: []byte{67}, 272 | }, 273 | { 274 | Bytes: []byte{ 255,250, 71,255,255,240 ,255,240, 68}, // IAC SB 'G' 255 255 240 IAC SE = IAC SB 'G' IAC IAC SE IAC SE 'D' 275 | Expected: []byte{68}, 276 | }, 277 | { 278 | Bytes: []byte{67, 255,250, 71,255,255,240 ,255,240, 68}, // 'C' IAC SB 'G' 255 255 240 IAC SE = IAC 'G' SB IAC IAC SE IAC SE 'D' 279 | Expected: []byte{67,68}, 280 | }, 281 | 282 | 283 | 284 | //@TODO: Is this correct? Can IAC appear between thee 'IAC SB' and ''IAC SE'?... and if "yes", do escaping rules apply? 285 | { 286 | Bytes: []byte{ 255,250, 255,255,240,72 ,255,240}, // IAC SB 255 255 240 'H' IAC SE = IAC SB IAC IAC SE 'H' IAC SE 287 | Expected: []byte{}, 288 | }, 289 | { 290 | Bytes: []byte{67, 255,250, 255,255,240,72 ,255,240}, // 'C' IAC SB 255 255 240 'H' IAC SE = IAC SB IAC IAC SE 'H' IAC SE 291 | Expected: []byte{67}, 292 | }, 293 | { 294 | Bytes: []byte{ 255,250, 255,255,240,72 ,255,240, 68}, // IAC SB 255 255 240 'H' IAC SE = IAC SB IAC IAC SE 'H' IAC SE 'D' 295 | Expected: []byte{68}, 296 | }, 297 | { 298 | Bytes: []byte{67, 255,250, 255,255,240,72 ,255,240, 68}, // 'C' IAC SB 255 255 240 'H' IAC SE = IAC SB IAC IAC SE 'H' IAC SE 'D' 299 | Expected: []byte{67,68}, 300 | }, 301 | 302 | 303 | 304 | //@TODO: Is this correct? Can IAC appear between thee 'IAC SB' and ''IAC SE'?... and if "yes", do escaping rules apply? 305 | { 306 | Bytes: []byte{ 255,250, 71,255,255,240,72 ,255,240}, // IAC SB 'G' 255 255 240 'H' IAC SE = IAC SB 'G' IAC IAC SE 'H' IAC SE 307 | Expected: []byte{}, 308 | }, 309 | { 310 | Bytes: []byte{67, 255,250, 71,255,255,240,72 ,255,240}, // 'C' IAC SB 'G' 255 255 240 'H' IAC SE = IAC SB 'G' IAC IAC SE 'H' IAC SE 311 | Expected: []byte{67}, 312 | }, 313 | { 314 | Bytes: []byte{ 255,250, 71,255,255,240,72 ,255,240, 68}, // IAC SB 'G' 255 255 240 'H' IAC SE = IAC SB 'G' IAC IAC SE 'H' IAC SE 'D' 315 | Expected: []byte{68}, 316 | }, 317 | { 318 | Bytes: []byte{67, 255,250, 71,255,255,240,72 ,255,240, 68}, // 'C' IAC SB 'G' 255 255 240 'H' IAC SE = IAC 'G' SB IAC IAC SE 'H' IAC SE 'D' 319 | Expected: []byte{67,68}, 320 | }, 321 | } 322 | 323 | 324 | for testNumber, test := range tests { 325 | var ctx Context = nil 326 | 327 | var buffer bytes.Buffer 328 | 329 | writer := newDataWriter(&buffer) 330 | reader := newDataReader( bytes.NewReader(test.Bytes) ) 331 | 332 | EchoHandler.ServeTELNET(ctx, writer, reader) 333 | 334 | if expected, actual := string(test.Expected), buffer.String(); expected != actual { 335 | t.Errorf("For test #%d, expected %q, but actually got %q.", testNumber, expected, actual) 336 | continue 337 | } 338 | } 339 | } 340 | -------------------------------------------------------------------------------- /data_reader_test.go: -------------------------------------------------------------------------------- 1 | package telnet 2 | 3 | 4 | import ( 5 | "bytes" 6 | "io" 7 | 8 | "testing" 9 | ) 10 | 11 | 12 | func TestDataReader(t *testing.T) { 13 | 14 | tests := []struct{ 15 | Bytes []byte 16 | Expected []byte 17 | }{ 18 | { 19 | Bytes: []byte{}, 20 | Expected: []byte{}, 21 | }, 22 | 23 | 24 | 25 | { 26 | Bytes: []byte("a"), 27 | Expected: []byte("a"), 28 | }, 29 | { 30 | Bytes: []byte("b"), 31 | Expected: []byte("b"), 32 | }, 33 | { 34 | Bytes: []byte("c"), 35 | Expected: []byte("c"), 36 | }, 37 | 38 | 39 | 40 | { 41 | Bytes: []byte("apple"), 42 | Expected: []byte("apple"), 43 | }, 44 | { 45 | Bytes: []byte("banana"), 46 | Expected: []byte("banana"), 47 | }, 48 | { 49 | Bytes: []byte("cherry"), 50 | Expected: []byte("cherry"), 51 | }, 52 | 53 | 54 | 55 | { 56 | Bytes: []byte("apple banana cherry"), 57 | Expected: []byte("apple banana cherry"), 58 | }, 59 | 60 | 61 | 62 | { 63 | Bytes: []byte{255,255}, 64 | Expected: []byte{255}, 65 | }, 66 | { 67 | Bytes: []byte{255,255,255,255}, 68 | Expected: []byte{255,255}, 69 | }, 70 | { 71 | Bytes: []byte{255,255,255,255,255,255}, 72 | Expected: []byte{255,255,255}, 73 | }, 74 | { 75 | Bytes: []byte{255,255,255,255,255,255,255,255}, 76 | Expected: []byte{255,255,255,255}, 77 | }, 78 | { 79 | Bytes: []byte{255,255,255,255,255,255,255,255,255,255}, 80 | Expected: []byte{255,255,255,255,255}, 81 | }, 82 | 83 | 84 | 85 | { 86 | Bytes: []byte("apple\xff\xffbanana\xff\xffcherry"), 87 | Expected: []byte("apple\xffbanana\xffcherry"), 88 | }, 89 | { 90 | Bytes: []byte("\xff\xffapple\xff\xffbanana\xff\xffcherry\xff\xff"), 91 | Expected: []byte("\xffapple\xffbanana\xffcherry\xff"), 92 | }, 93 | 94 | 95 | 96 | 97 | { 98 | Bytes: []byte("apple\xff\xff\xff\xffbanana\xff\xff\xff\xffcherry"), 99 | Expected: []byte("apple\xff\xffbanana\xff\xffcherry"), 100 | }, 101 | { 102 | Bytes: []byte("\xff\xff\xff\xffapple\xff\xff\xff\xffbanana\xff\xff\xff\xffcherry\xff\xff\xff\xff"), 103 | Expected: []byte("\xff\xffapple\xff\xffbanana\xff\xffcherry\xff\xff"), 104 | }, 105 | 106 | 107 | 108 | { 109 | Bytes: []byte{255,251,24}, // IAC WILL TERMINAL-TYPE 110 | Expected: []byte{}, 111 | }, 112 | { 113 | Bytes: []byte{255,252,24}, // IAC WON'T TERMINAL-TYPE 114 | Expected: []byte{}, 115 | }, 116 | { 117 | Bytes: []byte{255,253,24}, // IAC DO TERMINAL-TYPE 118 | Expected: []byte{}, 119 | }, 120 | { 121 | Bytes: []byte{255,254,24}, // IAC DON'T TERMINAL-TYPE 122 | Expected: []byte{}, 123 | }, 124 | 125 | 126 | 127 | { 128 | Bytes: []byte{67, 255,251,24}, // 'C' IAC WILL TERMINAL-TYPE 129 | Expected: []byte{67}, 130 | }, 131 | { 132 | Bytes: []byte{67, 255,252,24}, // 'C' IAC WON'T TERMINAL-TYPE 133 | Expected: []byte{67}, 134 | }, 135 | { 136 | Bytes: []byte{67, 255,253,24}, // 'C' IAC DO TERMINAL-TYPE 137 | Expected: []byte{67}, 138 | }, 139 | { 140 | Bytes: []byte{67, 255,254,24}, // 'C' IAC DON'T TERMINAL-TYPE 141 | Expected: []byte{67}, 142 | }, 143 | 144 | 145 | 146 | { 147 | Bytes: []byte{255,251,24, 68}, // IAC WILL TERMINAL-TYPE 'D' 148 | Expected: []byte{68}, 149 | }, 150 | { 151 | Bytes: []byte{255,252,24, 68}, // IAC WON'T TERMINAL-TYPE 'D' 152 | Expected: []byte{68}, 153 | }, 154 | { 155 | Bytes: []byte{255,253,24, 68}, // IAC DO TERMINAL-TYPE 'D' 156 | Expected: []byte{68}, 157 | }, 158 | { 159 | Bytes: []byte{255,254,24, 68}, // IAC DON'T TERMINAL-TYPE 'D' 160 | Expected: []byte{68}, 161 | }, 162 | 163 | 164 | { 165 | Bytes: []byte{67, 255,251,24, 68}, // 'C' IAC WILL TERMINAL-TYPE 'D' 166 | Expected: []byte{67,68}, 167 | }, 168 | { 169 | Bytes: []byte{67, 255,252,24, 68}, // 'C' IAC WON'T TERMINAL-TYPE 'D' 170 | Expected: []byte{67,68}, 171 | }, 172 | { 173 | Bytes: []byte{67, 255,253,24, 68}, // 'C' IAC DO TERMINAL-TYPE 'D' 174 | Expected: []byte{67,68}, 175 | }, 176 | { 177 | Bytes: []byte{67, 255,254,24, 68}, // 'C' IAC DON'T TERMINAL-TYPE 'D' 178 | Expected: []byte{67,68}, 179 | }, 180 | 181 | 182 | 183 | { 184 | Bytes: []byte{255, 250, 24, 1, 255, 240}, // IAC SB TERMINAL-TYPE SEND IAC SE 185 | Expected: []byte{}, 186 | }, 187 | { 188 | Bytes: []byte{255, 250, 24, 0, 68,69,67,45,86,84,53,50 ,255, 240}, // IAC SB TERMINAL-TYPE IS "DEC-VT52" IAC SE 189 | Expected: []byte{}, 190 | }, 191 | 192 | 193 | 194 | { 195 | Bytes: []byte{67, 255, 250, 24, 1, 255, 240}, // 'C' IAC SB TERMINAL-TYPE SEND IAC SE 196 | Expected: []byte{67}, 197 | }, 198 | { 199 | Bytes: []byte{67, 255, 250, 24, 0, 68,69,67,45,86,84,53,50 ,255, 240}, // 'C' IAC SB TERMINAL-TYPE IS "DEC-VT52" IAC SE 200 | Expected: []byte{67}, 201 | }, 202 | 203 | 204 | 205 | { 206 | Bytes: []byte{255, 250, 24, 1, 255, 240, 68}, // IAC SB TERMINAL-TYPE SEND IAC SE 'D' 207 | Expected: []byte{68}, 208 | }, 209 | { 210 | Bytes: []byte{255, 250, 24, 0, 68,69,67,45,86,84,53,50 ,255, 240, 68}, // IAC SB TERMINAL-TYPE IS "DEC-VT52" IAC SE 'D' 211 | Expected: []byte{68}, 212 | }, 213 | 214 | 215 | 216 | { 217 | Bytes: []byte{67, 255, 250, 24, 1, 255, 240, 68}, // 'C' IAC SB TERMINAL-TYPE SEND IAC SE 'D' 218 | Expected: []byte{67, 68}, 219 | }, 220 | { 221 | Bytes: []byte{67, 255, 250, 24, 0, 68,69,67,45,86,84,53,50 ,255, 240, 68}, // 'C' IAC SB TERMINAL-TYPE IS "DEC-VT52" IAC SE 'D' 222 | Expected: []byte{67, 68}, 223 | }, 224 | 225 | 226 | 227 | { 228 | Bytes: []byte{255,250, 0,1,2,3,4,5,6,7,8,9,10,11,12,13 ,255,240}, // IAC SB 0 1 2 3 4 5 6 7 8 9 10 11 12 13 IAC SE 229 | Expected: []byte{}, 230 | }, 231 | { 232 | Bytes: []byte{67, 255,250, 0,1,2,3,4,5,6,7,8,9,10,11,12,13 ,255,240}, // 'C' IAC SB 0 1 2 3 4 5 6 7 8 9 10 11 12 13 IAC SE 233 | Expected: []byte{67}, 234 | }, 235 | { 236 | Bytes: []byte{255,250, 0,1,2,3,4,5,6,7,8,9,10,11,12,13 ,255,240, 68}, // IAC SB 0 1 2 3 4 5 6 7 8 9 10 11 12 13 IAC SE 'D' 237 | Expected: []byte{68}, 238 | }, 239 | { 240 | Bytes: []byte{67, 255,250, 0,1,2,3,4,5,6,7,8,9,10,11,12,13 ,255,240, 68}, // 'C' IAC SB 0 1 2 3 4 5 6 7 8 9 10 11 12 13 IAC SE 'D' 241 | Expected: []byte{67,68}, 242 | }, 243 | 244 | 245 | 246 | //@TODO: Is this correct? Can IAC appear between thee 'IAC SB' and ''IAC SE'?... and if "yes", do escaping rules apply? 247 | { 248 | Bytes: []byte{ 255,250, 255,255,240 ,255,240}, // IAC SB 255 255 240 IAC SE = IAC SB IAC IAC SE IAC SE 249 | Expected: []byte{}, 250 | }, 251 | { 252 | Bytes: []byte{67, 255,250, 255,255,240 ,255,240}, // 'C' IAC SB 255 255 240 IAC SE = IAC SB IAC IAC SE IAC SE 253 | Expected: []byte{67}, 254 | }, 255 | { 256 | Bytes: []byte{ 255,250, 255,255,240 ,255,240, 68}, // IAC SB 255 255 240 IAC SE = IAC SB IAC IAC SE IAC SE 'D' 257 | Expected: []byte{68}, 258 | }, 259 | { 260 | Bytes: []byte{67, 255,250, 255,255,240 ,255,240, 68}, // 'C' IAC SB 255 255 240 IAC SE = IAC SB IAC IAC SE IAC SE 'D' 261 | Expected: []byte{67,68}, 262 | }, 263 | 264 | 265 | 266 | //@TODO: Is this correct? Can IAC appear between thee 'IAC SB' and ''IAC SE'?... and if "yes", do escaping rules apply? 267 | { 268 | Bytes: []byte{ 255,250, 71,255,255,240 ,255,240}, // IAC SB 'G' 255 255 240 IAC SE = IAC SB 'G' IAC IAC SE IAC SE 269 | Expected: []byte{}, 270 | }, 271 | { 272 | Bytes: []byte{67, 255,250, 71,255,255,240 ,255,240}, // 'C' IAC SB 'G' 255 255 240 IAC SE = IAC SB 'G' IAC IAC SE IAC SE 273 | Expected: []byte{67}, 274 | }, 275 | { 276 | Bytes: []byte{ 255,250, 71,255,255,240 ,255,240, 68}, // IAC SB 'G' 255 255 240 IAC SE = IAC SB 'G' IAC IAC SE IAC SE 'D' 277 | Expected: []byte{68}, 278 | }, 279 | { 280 | Bytes: []byte{67, 255,250, 71,255,255,240 ,255,240, 68}, // 'C' IAC SB 'G' 255 255 240 IAC SE = IAC 'G' SB IAC IAC SE IAC SE 'D' 281 | Expected: []byte{67,68}, 282 | }, 283 | 284 | 285 | 286 | //@TODO: Is this correct? Can IAC appear between thee 'IAC SB' and ''IAC SE'?... and if "yes", do escaping rules apply? 287 | { 288 | Bytes: []byte{ 255,250, 255,255,240,72 ,255,240}, // IAC SB 255 255 240 'H' IAC SE = IAC SB IAC IAC SE 'H' IAC SE 289 | Expected: []byte{}, 290 | }, 291 | { 292 | Bytes: []byte{67, 255,250, 255,255,240,72 ,255,240}, // 'C' IAC SB 255 255 240 'H' IAC SE = IAC SB IAC IAC SE 'H' IAC SE 293 | Expected: []byte{67}, 294 | }, 295 | { 296 | Bytes: []byte{ 255,250, 255,255,240,72 ,255,240, 68}, // IAC SB 255 255 240 'H' IAC SE = IAC SB IAC IAC SE 'H' IAC SE 'D' 297 | Expected: []byte{68}, 298 | }, 299 | { 300 | Bytes: []byte{67, 255,250, 255,255,240,72 ,255,240, 68}, // 'C' IAC SB 255 255 240 'H' IAC SE = IAC SB IAC IAC SE 'H' IAC SE 'D' 301 | Expected: []byte{67,68}, 302 | }, 303 | 304 | 305 | 306 | //@TODO: Is this correct? Can IAC appear between thee 'IAC SB' and ''IAC SE'?... and if "yes", do escaping rules apply? 307 | { 308 | Bytes: []byte{ 255,250, 71,255,255,240,72 ,255,240}, // IAC SB 'G' 255 255 240 'H' IAC SE = IAC SB 'G' IAC IAC SE 'H' IAC SE 309 | Expected: []byte{}, 310 | }, 311 | { 312 | Bytes: []byte{67, 255,250, 71,255,255,240,72 ,255,240}, // 'C' IAC SB 'G' 255 255 240 'H' IAC SE = IAC SB 'G' IAC IAC SE 'H' IAC SE 313 | Expected: []byte{67}, 314 | }, 315 | { 316 | Bytes: []byte{ 255,250, 71,255,255,240,72 ,255,240, 68}, // IAC SB 'G' 255 255 240 'H' IAC SE = IAC SB 'G' IAC IAC SE 'H' IAC SE 'D' 317 | Expected: []byte{68}, 318 | }, 319 | { 320 | Bytes: []byte{67, 255,250, 71,255,255,240,72 ,255,240, 68}, // 'C' IAC SB 'G' 255 255 240 'H' IAC SE = IAC 'G' SB IAC IAC SE 'H' IAC SE 'D' 321 | Expected: []byte{67,68}, 322 | }, 323 | 324 | 325 | } 326 | 327 | //@TODO: Add random tests. 328 | 329 | 330 | for testNumber, test := range tests { 331 | 332 | subReader := bytes.NewReader(test.Bytes) 333 | 334 | reader := newDataReader(subReader) 335 | 336 | buffer := make([]byte, 2*len(test.Bytes)) 337 | n, err := reader.Read(buffer) 338 | if nil != err && io.EOF != err { 339 | t.Errorf("For test #%d, did not expected an error, but actually got one: (%T) %v; for %q -> %q.", testNumber, err, err, string(test.Bytes), string(test.Expected)) 340 | continue 341 | } 342 | 343 | if expected, actual := len(test.Expected), n; expected != actual { 344 | t.Errorf("For test #%d, expected %d, but actually got %d (and %q); for %q -> %q.", testNumber, expected, actual, string(buffer[:n]), string(test.Bytes), string(test.Expected)) 345 | continue 346 | } 347 | 348 | if expected, actual := string(test.Expected), string(buffer[:n]); expected != actual { 349 | t.Errorf("For test #%d, expected %q, but actually got %q; for %q -> %q.", testNumber, expected, actual, string(test.Bytes), string(test.Expected)) 350 | continue 351 | } 352 | } 353 | } 354 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package telnet provides TELNET and TELNETS client and server implementations 3 | in a style similar to the "net/http" library that is part of the Go standard library, 4 | including support for "middleware"; TELNETS is secure TELNET, with the TELNET protocol 5 | over a secured TLS (or SSL) connection. 6 | 7 | 8 | Example TELNET Server 9 | 10 | ListenAndServe starts a (un-secure) TELNET server with a given address and handler. 11 | 12 | handler := telnet.EchoHandler 13 | 14 | err := telnet.ListenAndServe(":23", handler) 15 | if nil != err { 16 | panic(err) 17 | } 18 | 19 | 20 | Example TELNETS Server 21 | 22 | ListenAndServeTLS starts a (secure) TELNETS server with a given address and handler, 23 | using the specified "cert.pem" and "key.pem" files. 24 | 25 | handler := telnet.EchoHandler 26 | 27 | err := telnet.ListenAndServeTLS(":992", "cert.pem", "key.pem", handler) 28 | if nil != err { 29 | panic(err) 30 | } 31 | 32 | 33 | Example TELNET Client: 34 | 35 | DialToAndCall creates a (un-secure) TELNET client, which connects to a given address using the specified caller. 36 | 37 | package main 38 | 39 | import ( 40 | "github.com/reiver/go-telnet" 41 | ) 42 | 43 | func main() { 44 | var caller telnet.Caller = telnet.StandardCaller 45 | 46 | //@TOOD: replace "example.net:23" with address you want to connect to. 47 | telnet.DialToAndCall("example.net:23", caller) 48 | } 49 | 50 | 51 | Example TELNETS Client: 52 | 53 | DialToAndCallTLS creates a (secure) TELNETS client, which connects to a given address using the specified caller. 54 | 55 | package main 56 | 57 | import ( 58 | "github.com/reiver/go-telnet" 59 | 60 | "crypto/tls" 61 | ) 62 | 63 | func main() { 64 | //@TODO: Configure the TLS connection here, if you need to. 65 | tlsConfig := &tls.Config{} 66 | 67 | var caller telnet.Caller = telnet.StandardCaller 68 | 69 | //@TOOD: replace "example.net:992" with address you want to connect to. 70 | telnet.DialToAndCallTLS("example.net:992", caller, tlsConfig) 71 | } 72 | 73 | 74 | TELNET vs TELNETS 75 | 76 | If you are communicating over the open Internet, you should be using (the secure) TELNETS protocol and ListenAndServeTLS. 77 | 78 | If you are communicating just on localhost, then using just (the un-secure) TELNET protocol and telnet.ListenAndServe may be OK. 79 | 80 | If you are not sure which to use, use TELNETS and ListenAndServeTLS. 81 | 82 | 83 | Example TELNET Shell Server 84 | 85 | The previous 2 exaple servers were very very simple. Specifically, they just echoed back whatever 86 | you submitted to it. 87 | 88 | If you typed: 89 | 90 | Apple Banana Cherry\r\n 91 | 92 | ... it would send back: 93 | 94 | Apple Banana Cherry\r\n 95 | 96 | (Exactly the same data you sent it.) 97 | 98 | A more useful TELNET server can be made using the "github.com/reiver/go-telnet/telsh" sub-package. 99 | 100 | The `telsh` sub-package provides "middleware" that enables you to create a "shell" interface (also 101 | called a "command line interface" or "CLI") which most people would expect when using TELNET OR TELNETS. 102 | 103 | For example: 104 | 105 | 106 | package main 107 | 108 | 109 | import ( 110 | "github.com/reiver/go-oi" 111 | "github.com/reiver/go-telnet" 112 | "github.com/reiver/go-telnet/telsh" 113 | 114 | "time" 115 | ) 116 | 117 | 118 | func main() { 119 | 120 | shellHandler := telsh.NewShellHandler() 121 | 122 | commandName := "date" 123 | shellHandler.Register(commandName, danceProducer) 124 | 125 | commandName = "animate" 126 | shellHandler.Register(commandName, animateProducer) 127 | 128 | addr := ":23" 129 | if err := telnet.ListenAndServe(addr, shellHandler); nil != err { 130 | panic(err) 131 | } 132 | } 133 | 134 | Note that in the example, so far, we have registered 2 commands: `date` and `animate`. 135 | 136 | For this to actually work, we need to have code for the `date` and `animate` commands. 137 | 138 | The actual implemenation for the `date` command could be done like the following: 139 | 140 | func dateHandlerFunc(stdin io.ReadCloser, stdout io.WriteCloser, stderr io.WriteCloser, args ...string) error { 141 | const layout = "Mon Jan 2 15:04:05 -0700 MST 2006" 142 | s := time.Now().Format(layout) 143 | 144 | if _, err := oi.LongWriteString(stdout, s); nil != err { 145 | return err 146 | } 147 | 148 | return nil 149 | } 150 | 151 | 152 | func dateProducerFunc(ctx telnet.Context, name string, args ...string) telsh.Handler{ 153 | return telsh.PromoteHandlerFunc(dateHandler) 154 | } 155 | 156 | 157 | var dateProducer = ProducerFunc(dateProducerFunc) 158 | 159 | Note that your "real" work is in the `dateHandlerFunc` func. 160 | 161 | And the actual implementation for the `animate` command could be done as follows: 162 | 163 | func animateHandlerFunc(stdin io.ReadCloser, stdout io.WriteCloser, stderr io.WriteCloser, args ...string) error { 164 | 165 | for i:=0; i<20; i++ { 166 | oi.LongWriteString(stdout, "\r⠋") 167 | time.Sleep(50*time.Millisecond) 168 | 169 | oi.LongWriteString(stdout, "\r⠙") 170 | time.Sleep(50*time.Millisecond) 171 | 172 | oi.LongWriteString(stdout, "\r⠹") 173 | time.Sleep(50*time.Millisecond) 174 | 175 | oi.LongWriteString(stdout, "\r⠸") 176 | time.Sleep(50*time.Millisecond) 177 | 178 | oi.LongWriteString(stdout, "\r⠼") 179 | time.Sleep(50*time.Millisecond) 180 | 181 | oi.LongWriteString(stdout, "\r⠴") 182 | time.Sleep(50*time.Millisecond) 183 | 184 | oi.LongWriteString(stdout, "\r⠦") 185 | time.Sleep(50*time.Millisecond) 186 | 187 | oi.LongWriteString(stdout, "\r⠧") 188 | time.Sleep(50*time.Millisecond) 189 | 190 | oi.LongWriteString(stdout, "\r⠇") 191 | time.Sleep(50*time.Millisecond) 192 | 193 | oi.LongWriteString(stdout, "\r⠏") 194 | time.Sleep(50*time.Millisecond) 195 | } 196 | oi.LongWriteString(stdout, "\r \r\n") 197 | 198 | return nil 199 | } 200 | 201 | 202 | func animateProducerFunc(ctx telnet.Context, name string, args ...string) telsh.Handler{ 203 | return telsh.PromoteHandlerFunc(animateHandler) 204 | } 205 | 206 | 207 | var animateProducer = ProducerFunc(animateProducerFunc) 208 | 209 | Again, note that your "real" work is in the `animateHandlerFunc` func. 210 | 211 | Generating PEM Files 212 | 213 | If you are using the telnet.ListenAndServeTLS func or the telnet.Server.ListenAndServeTLS method, you will need to 214 | supply "cert.pem" and "key.pem" files. 215 | 216 | If you do not already have these files, the Go soure code contains a tool for generating these files for you. 217 | 218 | It can be found at: 219 | 220 | $GOROOT/src/crypto/tls/generate_cert.go 221 | 222 | So, for example, if your `$GOROOT` is the "/usr/local/go" directory, then it would be at: 223 | 224 | /usr/local/go/src/crypto/tls/generate_cert.go 225 | 226 | If you run the command: 227 | 228 | go run $GOROOT/src/crypto/tls/generate_cert.go --help 229 | 230 | ... then you get the help information for "generate_cert.go". 231 | 232 | Of course, you would replace or set `$GOROOT` with whatever your path actually is. Again, for example, 233 | if your `$GOROOT` is the "/usr/local/go" directory, then it would be: 234 | 235 | go run /usr/local/go/src/crypto/tls/generate_cert.go --help 236 | 237 | To demonstrate the usage of "generate_cert.go", you might run the following to generate certificates 238 | that were bound to the hosts `127.0.0.1` and `localhost`: 239 | 240 | go run /usr/local/go/src/crypto/tls/generate_cert.go --ca --host='127.0.0.1,localhost' 241 | 242 | 243 | If you are not sure where "generate_cert.go" is on your computer, on Linux and Unix based systems, you might 244 | be able to find the file with the command: 245 | 246 | locate /src/crypto/tls/generate_cert.go 247 | 248 | (If it finds it, it should output the full path to this file.) 249 | 250 | 251 | Example TELNET Client 252 | 253 | You can make a simple (un-secure) TELNET client with code like the following: 254 | 255 | package main 256 | 257 | 258 | import ( 259 | "github.com/reiver/go-telnet" 260 | ) 261 | 262 | 263 | func main() { 264 | var caller telnet.Caller = telnet.StandardCaller 265 | 266 | //@TOOD: replace "example.net:5555" with address you want to connect to. 267 | telnet.DialToAndCall("example.net:5555", caller) 268 | } 269 | 270 | 271 | Example TELNETS Client 272 | 273 | You can make a simple (secure) TELNETS client with code like the following: 274 | 275 | package main 276 | 277 | 278 | import ( 279 | "github.com/reiver/go-telnet" 280 | ) 281 | 282 | 283 | func main() { 284 | var caller telnet.Caller = telnet.StandardCaller 285 | 286 | //@TOOD: replace "example.net:5555" with address you want to connect to. 287 | telnet.DialToAndCallTLS("example.net:5555", caller) 288 | } 289 | 290 | 291 | TELNET Story 292 | 293 | The TELNET protocol is best known for providing a means of connecting to a remote computer, using a (text-based) shell interface, and being able to interact with it, (more or less) as if you were sitting at that computer. 294 | 295 | (Shells are also known as command-line interfaces or CLIs.) 296 | 297 | Although this was the original usage of the TELNET protocol, it can be (and is) used for other purposes as well. 298 | 299 | 300 | The Era 301 | 302 | The TELNET protocol came from an era in computing when text-based shell interface where the common way of interacting with computers. 303 | 304 | The common interface for computers during this era was a keyboard and a monochromatic (i.e., single color) text-based monitors called "video terminals". 305 | 306 | (The word "video" in that era of computing did not refer to things such as movies. But instead was meant to contrast it with paper. In particular, the teletype machines, which were typewriter like devices that had a keyboard, but instead of having a monitor had paper that was printed onto.) 307 | 308 | 309 | Early Office Computers 310 | 311 | In that era, in the early days of office computers, it was rare that an individual would have a computer at their desk. (A single computer was much too expensive.) 312 | 313 | Instead, there would be a single central computer that everyone would share. The style of computer used (for the single central shared computer) was called a "mainframe". 314 | 315 | What individuals would have at their desks, instead of their own compuer, would be some type of video terminal. 316 | 317 | The different types of video terminals had named such as: 318 | 319 | • VT52 320 | 321 | • VT100 322 | 323 | • VT220 324 | 325 | • VT240 326 | 327 | ("VT" in those named was short for "video terminal".) 328 | 329 | 330 | Teletype 331 | 332 | To understand this era, we need to go back a bit in time to what came before it: teletypes. 333 | 334 | 335 | Terminal Codes 336 | 337 | Terminal codes (also sometimes called 'terminal control codes') are used to issue various kinds of commands 338 | to the terminal. 339 | 340 | (Note that 'terminal control codes' are a completely separate concept for 'TELNET commands', 341 | and the two should NOT be conflated or confused.) 342 | 343 | The most common types of 'terminal codes' are the 'ANSI escape codes'. (Although there are other types too.) 344 | 345 | 346 | ANSI Escape Codes 347 | 348 | ANSI escape codes (also sometimes called 'ANSI escape sequences') are a common type of 'terminal code' used 349 | to do things such as: 350 | 351 | • moving the cursor, 352 | 353 | • erasing the display, 354 | 355 | • erasing the line, 356 | 357 | • setting the graphics mode, 358 | 359 | • setting the foregroup color, 360 | 361 | • setting the background color, 362 | 363 | • setting the screen resolution, and 364 | 365 | • setting keyboard strings. 366 | 367 | 368 | Setting The Foreground Color With ANSI Escape Codes 369 | 370 | One of the abilities of ANSI escape codes is to set the foreground color. 371 | 372 | Here is a table showing codes for this: 373 | 374 | | ANSI Color | Go string | Go []byte | 375 | | ------------ | ---------- | ----------------------------- | 376 | | Black | "\x1b[30m" | []byte{27, '[', '3','0', 'm'} | 377 | | Red | "\x1b[31m" | []byte{27, '[', '3','1', 'm'} | 378 | | Green | "\x1b[32m" | []byte{27, '[', '3','2', 'm'} | 379 | | Brown/Yellow | "\x1b[33m" | []byte{27, '[', '3','3', 'm'} | 380 | | Blue | "\x1b[34m" | []byte{27, '[', '3','4', 'm'} | 381 | | Magenta | "\x1b[35m" | []byte{27, '[', '3','5', 'm'} | 382 | | Cyan | "\x1b[36m" | []byte{27, '[', '3','6', 'm'} | 383 | | Gray/White | "\x1b[37m" | []byte{27, '[', '3','7', 'm'} | 384 | 385 | (Note that in the `[]byte` that the first `byte` is the number `27` (which 386 | is the "escape" character) where the third and fouth characters are the 387 | **not** number literals, but instead character literals `'3'` and whatever.) 388 | 389 | 390 | Setting The Background Color With ANSI Escape Codes 391 | 392 | Another of the abilities of ANSI escape codes is to set the background color. 393 | 394 | | ANSI Color | Go string | Go []byte | 395 | | ------------ | ---------- | ----------------------------- | 396 | | Black | "\x1b[40m" | []byte{27, '[', '4','0', 'm'} | 397 | | Red | "\x1b[41m" | []byte{27, '[', '4','1', 'm'} | 398 | | Green | "\x1b[42m" | []byte{27, '[', '4','2', 'm'} | 399 | | Brown/Yellow | "\x1b[43m" | []byte{27, '[', '4','3', 'm'} | 400 | | Blue | "\x1b[44m" | []byte{27, '[', '4','4', 'm'} | 401 | | Magenta | "\x1b[45m" | []byte{27, '[', '4','5', 'm'} | 402 | | Cyan | "\x1b[46m" | []byte{27, '[', '4','6', 'm'} | 403 | | Gray/White | "\x1b[47m" | []byte{27, '[', '4','7', 'm'} | 404 | 405 | (Note that in the `[]byte` that the first `byte` is the number `27` (which 406 | is the "escape" character) where the third and fouth characters are the 407 | **not** number literals, but instead character literals `'4'` and whatever.) 408 | 409 | Using ANSI Escape Codes 410 | 411 | In Go code, if I wanted to use an ANSI escape code to use a blue background, 412 | a white foreground, and bold, I could do that with the ANSI escape code: 413 | 414 | "\x1b[44;37;1m" 415 | 416 | Note that that start with byte value 27, which we have encoded as hexadecimal 417 | as \x1b. Followed by the '[' character. 418 | 419 | Coming after that is the sub-string "44", which is the code that sets our background color to blue. 420 | 421 | We follow that with the ';' character (which separates codes). 422 | 423 | And the after that comes the sub-string "37", which is the code that set our foreground color to white. 424 | 425 | After that, we follow with another ";" character (which, again, separates codes). 426 | 427 | And then we follow it the sub-string "1", which is the code that makes things bold. 428 | 429 | And finally, the ANSI escape sequence is finished off with the 'm' character. 430 | 431 | To show this in a more complete example, our `dateHandlerFunc` from before could incorporate ANSI escape sequences as follows: 432 | 433 | func dateHandlerFunc(stdin io.ReadCloser, stdout io.WriteCloser, stderr io.WriteCloser, args ...string) error { 434 | const layout = "Mon Jan 2 15:04:05 -0700 MST 2006" 435 | s := "\x1b[44;37;1m" + time.Now().Format(layout) + "\x1b[0m" 436 | 437 | if _, err := oi.LongWriteString(stdout, s); nil != err { 438 | return err 439 | } 440 | 441 | return nil 442 | } 443 | 444 | Note that in that example, in addition to using the ANSI escape sequence "\x1b[44;37;1m" 445 | to set the background color to blue, set the foreground color to white, and make it bold, 446 | we also used the ANSI escape sequence "\x1b[0m" to reset the background and foreground colors 447 | and boldness back to "normal". 448 | 449 | */ 450 | package telnet 451 | -------------------------------------------------------------------------------- /standard_caller_test.go: -------------------------------------------------------------------------------- 1 | package telnet 2 | 3 | 4 | import ( 5 | "github.com/reiver/go-oi" 6 | 7 | "bytes" 8 | "io/ioutil" 9 | 10 | "testing" 11 | ) 12 | 13 | 14 | func TestStandardCallerFromClientToServer(t *testing.T) { 15 | 16 | tests := []struct{ 17 | Bytes []byte 18 | Expected []byte 19 | }{ 20 | { 21 | Bytes: []byte{}, 22 | Expected: []byte{}, 23 | }, 24 | 25 | 26 | 27 | 28 | 29 | 30 | { 31 | Bytes: []byte("a"), 32 | Expected: []byte(""), 33 | }, 34 | { 35 | Bytes: []byte("b"), 36 | Expected: []byte(""), 37 | }, 38 | { 39 | Bytes: []byte("c"), 40 | Expected: []byte(""), 41 | }, 42 | 43 | 44 | 45 | { 46 | Bytes: []byte("a\n"), 47 | Expected: []byte("a\r\n"), 48 | }, 49 | { 50 | Bytes: []byte("b\n"), 51 | Expected: []byte("b\r\n"), 52 | }, 53 | { 54 | Bytes: []byte("c\n"), 55 | Expected: []byte("c\r\n"), 56 | }, 57 | 58 | 59 | 60 | { 61 | Bytes: []byte("a\nb\nc"), 62 | Expected: []byte("a\r\nb\r\n"), 63 | }, 64 | 65 | 66 | 67 | { 68 | Bytes: []byte("a\nb\nc\n"), 69 | Expected: []byte("a\r\nb\r\nc\r\n"), 70 | }, 71 | 72 | 73 | 74 | 75 | 76 | 77 | { 78 | Bytes: []byte("apple"), 79 | Expected: []byte(""), 80 | }, 81 | { 82 | Bytes: []byte("banana"), 83 | Expected: []byte(""), 84 | }, 85 | { 86 | Bytes: []byte("cherry"), 87 | Expected: []byte(""), 88 | }, 89 | 90 | 91 | 92 | { 93 | Bytes: []byte("apple\n"), 94 | Expected: []byte("apple\r\n"), 95 | }, 96 | { 97 | Bytes: []byte("banana\n"), 98 | Expected: []byte("banana\r\n"), 99 | }, 100 | { 101 | Bytes: []byte("cherry\n"), 102 | Expected: []byte("cherry\r\n"), 103 | }, 104 | 105 | 106 | 107 | 108 | 109 | 110 | { 111 | Bytes: []byte("apple\nbanana\ncherry"), 112 | Expected: []byte("apple\r\nbanana\r\n"), 113 | }, 114 | 115 | 116 | 117 | { 118 | Bytes: []byte("apple\nbanana\ncherry\n"), 119 | Expected: []byte("apple\r\nbanana\r\ncherry\r\n"), 120 | }, 121 | 122 | 123 | 124 | 125 | 126 | 127 | { 128 | Bytes: []byte("apple banana cherry"), 129 | Expected: []byte(""), 130 | }, 131 | 132 | 133 | 134 | { 135 | Bytes: []byte("apple banana cherry\n"), 136 | Expected: []byte("apple banana cherry\r\n"), 137 | }, 138 | 139 | 140 | 141 | 142 | 143 | 144 | { 145 | Bytes: []byte{255}, 146 | Expected: []byte{}, 147 | }, 148 | { 149 | Bytes: []byte{255, 255}, 150 | Expected: []byte{}, 151 | }, 152 | { 153 | Bytes: []byte{255, 255, 255}, 154 | Expected: []byte{}, 155 | }, 156 | { 157 | Bytes: []byte{255, 255, 255, 255}, 158 | Expected: []byte{}, 159 | }, 160 | { 161 | Bytes: []byte{255, 255, 255, 255, 255}, 162 | Expected: []byte{}, 163 | }, 164 | { 165 | Bytes: []byte{255, 255, 255, 255, 255, 255}, 166 | Expected: []byte{}, 167 | }, 168 | { 169 | Bytes: []byte{255, 255, 255, 255, 255, 255, 255}, 170 | Expected: []byte{}, 171 | }, 172 | { 173 | Bytes: []byte{255, 255, 255, 255, 255, 255, 255, 255}, 174 | Expected: []byte{}, 175 | }, 176 | { 177 | Bytes: []byte{255, 255, 255, 255, 255, 255, 255, 255, 255}, 178 | Expected: []byte{}, 179 | }, 180 | { 181 | Bytes: []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255}, 182 | Expected: []byte{}, 183 | }, 184 | 185 | 186 | 187 | { 188 | Bytes: []byte{255, '\n'}, 189 | Expected: []byte{255, 255, '\r', '\n'}, 190 | }, 191 | { 192 | Bytes: []byte{255, 255, '\n'}, 193 | Expected: []byte{255, 255, 255, 255, '\r', '\n'}, 194 | }, 195 | { 196 | Bytes: []byte{255, 255, 255, '\n'}, 197 | Expected: []byte{255, 255, 255, 255, 255, 255, '\r', '\n'}, 198 | }, 199 | { 200 | Bytes: []byte{255, 255, 255, 255, '\n'}, 201 | Expected: []byte{255, 255, 255, 255, 255, 255, 255, 255, '\r', '\n'}, 202 | }, 203 | { 204 | Bytes: []byte{255, 255, 255, 255, 255, '\n'}, 205 | Expected: []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, '\r', '\n'}, 206 | }, 207 | { 208 | Bytes: []byte{255, 255, 255, 255, 255, 255, '\n'}, 209 | Expected: []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, '\r', '\n'}, 210 | }, 211 | { 212 | Bytes: []byte{255, 255, 255, 255, 255, 255, 255, '\n'}, 213 | Expected: []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, '\r', '\n'}, 214 | }, 215 | { 216 | Bytes: []byte{255, 255, 255, 255, 255, 255, 255, 255, '\n'}, 217 | Expected: []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, '\r', '\n'}, 218 | }, 219 | { 220 | Bytes: []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, '\n'}, 221 | Expected: []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, '\r', '\n'}, 222 | }, 223 | { 224 | Bytes: []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, '\n'}, 225 | Expected: []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, '\r', '\n'}, 226 | }, 227 | 228 | 229 | 230 | 231 | 232 | 233 | { 234 | Bytes: []byte("apple\xff\xffbanana\xff\xffcherry"), 235 | Expected: []byte(""), 236 | }, 237 | { 238 | Bytes: []byte("\xff\xffapple\xff\xffbanana\xff\xffcherry\xff\xff"), 239 | Expected: []byte(""), 240 | }, 241 | 242 | 243 | 244 | { 245 | Bytes: []byte("apple\xffbanana\xffcherry\n"), 246 | Expected: []byte("apple\xff\xffbanana\xff\xffcherry\r\n"), 247 | }, 248 | { 249 | Bytes: []byte("\xffapple\xffbanana\xffcherry\xff\n"), 250 | Expected: []byte("\xff\xffapple\xff\xffbanana\xff\xffcherry\xff\xff\r\n"), 251 | }, 252 | 253 | 254 | 255 | { 256 | Bytes: []byte("apple\xff\xff\xff\xffbanana\xff\xff\xff\xffcherry"), 257 | Expected: []byte(""), 258 | }, 259 | { 260 | Bytes: []byte("\xff\xff\xff\xffapple\xff\xff\xff\xffbanana\xff\xff\xff\xffcherry\xff\xff\xff\xff"), 261 | Expected: []byte(""), 262 | }, 263 | 264 | 265 | 266 | { 267 | Bytes: []byte("apple\xff\xffbanana\xff\xffcherry\n"), 268 | Expected: []byte("apple\xff\xff\xff\xffbanana\xff\xff\xff\xffcherry\r\n"), 269 | }, 270 | { 271 | Bytes: []byte("\xff\xffapple\xff\xffbanana\xff\xffcherry\xff\xff\n"), 272 | Expected: []byte("\xff\xff\xff\xffapple\xff\xff\xff\xffbanana\xff\xff\xff\xffcherry\xff\xff\xff\xff\r\n"), 273 | }, 274 | 275 | 276 | 277 | 278 | 279 | 280 | { 281 | Bytes: []byte{255,251,24}, // IAC WILL TERMINAL-TYPE 282 | Expected: []byte{}, 283 | }, 284 | { 285 | Bytes: []byte{255,252,24}, // IAC WON'T TERMINAL-TYPE 286 | Expected: []byte{}, 287 | }, 288 | { 289 | Bytes: []byte{255,253,24}, // IAC DO TERMINAL-TYPE 290 | Expected: []byte{}, 291 | }, 292 | { 293 | Bytes: []byte{255,254,24}, // IAC DON'T TERMINAL-TYPE 294 | Expected: []byte{}, 295 | }, 296 | 297 | 298 | 299 | { 300 | Bytes: []byte{ 255,251,24, '\n'}, // IAC WILL TERMINAL-TYPE '\n' 301 | Expected: []byte{255,255,251,24,'\r','\n'}, 302 | }, 303 | { 304 | Bytes: []byte{ 255,252,24, '\n'}, // IAC WON'T TERMINAL-TYPE '\n' 305 | Expected: []byte{255,255,252,24,'\r','\n'}, 306 | }, 307 | { 308 | Bytes: []byte{ 255,253,24, '\n'}, // IAC DO TERMINAL-TYPE '\n' 309 | Expected: []byte{255,255,253,24,'\r','\n'}, 310 | }, 311 | { 312 | Bytes: []byte{ 255,254,24, '\n'}, // IAC DON'T TERMINAL-TYPE '\n' 313 | Expected: []byte{255,255,254,24,'\r','\n'}, 314 | }, 315 | 316 | 317 | 318 | 319 | 320 | 321 | { 322 | Bytes: []byte{67, 255,251,24}, // 'C' IAC WILL TERMINAL-TYPE 323 | Expected: []byte{}, 324 | }, 325 | { 326 | Bytes: []byte{67, 255,252,24}, // 'C' IAC WON'T TERMINAL-TYPE 327 | Expected: []byte{}, 328 | }, 329 | { 330 | Bytes: []byte{67, 255,253,24}, // 'C' IAC DO TERMINAL-TYPE 331 | Expected: []byte{}, 332 | }, 333 | { 334 | Bytes: []byte{67, 255,254,24}, // 'C' IAC DON'T TERMINAL-TYPE 335 | Expected: []byte{}, 336 | }, 337 | 338 | 339 | 340 | { 341 | Bytes: []byte{67, 255,251,24, '\n'}, // 'C' IAC WILL TERMINAL-TYPE '\n' 342 | Expected: []byte{67, 255,255,251,24, '\r','\n'}, 343 | }, 344 | { 345 | Bytes: []byte{67, 255,252,24, '\n'}, // 'C' IAC WON'T TERMINAL-TYPE '\n' 346 | Expected: []byte{67, 255,255,252,24, '\r','\n'}, 347 | }, 348 | { 349 | Bytes: []byte{67, 255,253,24, '\n'}, // 'C' IAC DO TERMINAL-TYPE '\n' 350 | Expected: []byte{67, 255,255,253,24, '\r','\n'}, 351 | }, 352 | { 353 | Bytes: []byte{67, 255,254,24, '\n'}, // 'C' IAC DON'T TERMINAL-TYPE '\n' 354 | Expected: []byte{67, 255,255,254,24, '\r','\n'}, 355 | }, 356 | 357 | 358 | 359 | 360 | 361 | 362 | { 363 | Bytes: []byte{255,251,24, 68}, // IAC WILL TERMINAL-TYPE 'D' 364 | Expected: []byte{}, 365 | }, 366 | { 367 | Bytes: []byte{255,252,24, 68}, // IAC WON'T TERMINAL-TYPE 'D' 368 | Expected: []byte{}, 369 | }, 370 | { 371 | Bytes: []byte{255,253,24, 68}, // IAC DO TERMINAL-TYPE 'D' 372 | Expected: []byte{}, 373 | }, 374 | { 375 | Bytes: []byte{255,254,24, 68}, // IAC DON'T TERMINAL-TYPE 'D' 376 | Expected: []byte{}, 377 | }, 378 | 379 | 380 | 381 | { 382 | Bytes: []byte{ 255,251,24, 68, '\n'}, // IAC WILL TERMINAL-TYPE 'D' '\n' 383 | Expected: []byte{255,255,251,24, 68, '\r','\n'}, 384 | }, 385 | { 386 | Bytes: []byte{ 255,252,24, 68, '\n'}, // IAC WON'T TERMINAL-TYPE 'D' '\n' 387 | Expected: []byte{255,255,252,24, 68, '\r','\n'}, 388 | }, 389 | { 390 | Bytes: []byte{ 255,253,24, 68, '\n'}, // IAC DO TERMINAL-TYPE 'D' '\n' 391 | Expected: []byte{255,255,253,24, 68, '\r','\n'}, 392 | }, 393 | { 394 | Bytes: []byte{ 255,254,24, 68, '\n'}, // IAC DON'T TERMINAL-TYPE 'D' '\n' 395 | Expected: []byte{255,255,254,24, 68, '\r','\n'}, 396 | }, 397 | 398 | 399 | 400 | 401 | 402 | 403 | { 404 | Bytes: []byte{67, 255,251,24, 68}, // 'C' IAC WILL TERMINAL-TYPE 'D' 405 | Expected: []byte{}, 406 | }, 407 | { 408 | Bytes: []byte{67, 255,252,24, 68}, // 'C' IAC WON'T TERMINAL-TYPE 'D' 409 | Expected: []byte{}, 410 | }, 411 | { 412 | Bytes: []byte{67, 255,253,24, 68}, // 'C' IAC DO TERMINAL-TYPE 'D' 413 | Expected: []byte{}, 414 | }, 415 | { 416 | Bytes: []byte{67, 255,254,24, 68}, // 'C' IAC DON'T TERMINAL-TYPE 'D' 417 | Expected: []byte{}, 418 | }, 419 | 420 | 421 | 422 | { 423 | Bytes: []byte{67, 255,251,24, 68, '\n'}, // 'C' IAC WILL TERMINAL-TYPE 'D' '\n' 424 | Expected: []byte{67, 255,255,251,24, 68, '\r','\n'}, 425 | }, 426 | { 427 | Bytes: []byte{67, 255,252,24, 68, '\n'}, // 'C' IAC WON'T TERMINAL-TYPE 'D' '\n' 428 | Expected: []byte{67, 255,255,252,24, 68, '\r','\n'}, 429 | }, 430 | { 431 | Bytes: []byte{67, 255,253,24, 68, '\n'}, // 'C' IAC DO TERMINAL-TYPE 'D' '\n' 432 | Expected: []byte{67, 255,255,253,24, 68, '\r','\n'}, 433 | }, 434 | { 435 | Bytes: []byte{67, 255,254,24, 68, '\n'}, // 'C' IAC DON'T TERMINAL-TYPE 'D' '\n' 436 | Expected: []byte{67, 255,255,254,24, 68, '\r','\n'}, 437 | }, 438 | 439 | 440 | 441 | 442 | 443 | 444 | { 445 | Bytes: []byte{255, 250, 24, 1, 255, 240}, // IAC SB TERMINAL-TYPE SEND IAC SE 446 | Expected: []byte{}, 447 | }, 448 | { 449 | Bytes: []byte{255, 250, 24, 0, 68,69,67,45,86,84,53,50 ,255, 240}, // IAC SB TERMINAL-TYPE IS "DEC-VT52" IAC SE 450 | Expected: []byte{}, 451 | }, 452 | 453 | 454 | 455 | { 456 | Bytes: []byte{ 255, 250, 24, 1, 255, 240, '\n'}, // IAC SB TERMINAL-TYPE SEND IAC SE '\n' 457 | Expected: []byte{255, 255, 250, 24, 1, 255, 255, 240, '\r','\n'}, 458 | }, 459 | { 460 | Bytes: []byte{ 255, 250, 24, 0, 68,69,67,45,86,84,53,50, 255, 240, '\n'}, // IAC SB TERMINAL-TYPE IS "DEC-VT52" IAC SE '\n' 461 | Expected: []byte{255, 255, 250, 24, 0, 68,69,67,45,86,84,53,50, 255, 255, 240, '\r','\n'}, 462 | }, 463 | 464 | 465 | 466 | 467 | 468 | 469 | { 470 | Bytes: []byte{67, 255, 250, 24, 1, 255, 240}, // 'C' IAC SB TERMINAL-TYPE SEND IAC SE 471 | Expected: []byte{}, 472 | }, 473 | { 474 | Bytes: []byte{67, 255, 250, 24, 0, 68,69,67,45,86,84,53,50 ,255, 240}, // 'C' IAC SB TERMINAL-TYPE IS "DEC-VT52" IAC SE 475 | Expected: []byte{}, 476 | }, 477 | 478 | 479 | 480 | { 481 | Bytes: []byte{67, 255, 250, 24, 1, 255, 240, '\n'}, // 'C' IAC SB TERMINAL-TYPE SEND IAC SE '\n' 482 | Expected: []byte{67, 255, 255, 250, 24, 1, 255, 255, 240, '\r','\n'}, 483 | }, 484 | { 485 | Bytes: []byte{67, 255, 250, 24, 0, 68,69,67,45,86,84,53,50, 255, 240, '\n'}, // 'C' IAC SB TERMINAL-TYPE IS "DEC-VT52" IAC SE '\n' 486 | Expected: []byte{67, 255, 255, 250, 24, 0, 68,69,67,45,86,84,53,50, 255, 255, 240, '\r','\n'}, 487 | }, 488 | 489 | 490 | 491 | 492 | 493 | 494 | { 495 | Bytes: []byte{255, 250, 24, 1, 255, 240, 68}, // IAC SB TERMINAL-TYPE SEND IAC SE 'D' 496 | Expected: []byte{}, 497 | }, 498 | { 499 | Bytes: []byte{255, 250, 24, 0, 68,69,67,45,86,84,53,50 ,255, 240, 68}, // IAC SB TERMINAL-TYPE IS "DEC-VT52" IAC SE 'D' 500 | Expected: []byte{}, 501 | }, 502 | 503 | 504 | 505 | { 506 | Bytes: []byte{ 255, 250, 24, 1, 255, 240, 68, '\n'}, // IAC SB TERMINAL-TYPE SEND IAC SE 'D' '\n' 507 | Expected: []byte{255, 255, 250, 24, 1, 255, 255, 240, 68, '\r','\n'}, 508 | }, 509 | { 510 | Bytes: []byte{ 255, 250, 24, 0, 68,69,67,45,86,84,53,50, 255, 240, 68, '\n'}, // IAC SB TERMINAL-TYPE IS "DEC-VT52" IAC SE 'D' '\n' 511 | Expected: []byte{255, 255, 250, 24, 0, 68,69,67,45,86,84,53,50, 255, 255, 240, 68, '\r','\n'}, 512 | }, 513 | 514 | 515 | 516 | 517 | 518 | 519 | { 520 | Bytes: []byte{67, 255, 250, 24, 1, 255, 240, 68}, // 'C' IAC SB TERMINAL-TYPE SEND IAC SE 'D' 521 | Expected: []byte{}, 522 | }, 523 | { 524 | Bytes: []byte{67, 255, 250, 24, 0, 68,69,67,45,86,84,53,50 ,255, 240, 68}, // 'C' IAC SB TERMINAL-TYPE IS "DEC-VT52" IAC SE 'D' 525 | Expected: []byte{}, 526 | }, 527 | 528 | 529 | 530 | { 531 | Bytes: []byte{67, 255, 250, 24, 1, 255, 240, 68, '\n'}, // 'C' IAC SB TERMINAL-TYPE SEND IAC SE 'D' '\n' 532 | Expected: []byte{67, 255, 255, 250, 24, 1, 255, 255, 240, 68, '\r','\n'}, 533 | }, 534 | { 535 | Bytes: []byte{67, 255, 250, 24, 0, 68,69,67,45,86,84,53,50, 255, 240, 68, '\n'}, // 'C' IAC SB TERMINAL-TYPE IS "DEC-VT52" IAC SE 'D' '\n' 536 | Expected: []byte{67, 255, 255, 250, 24, 0, 68,69,67,45,86,84,53,50, 255, 255, 240, 68, '\r','\n'}, 537 | }, 538 | 539 | 540 | 541 | 542 | 543 | 544 | { 545 | Bytes: []byte{ 255,250, 0,1,2,3,4,5,6,7,8,9, 10,11,12,13 ,255,240}, // IAC SB 0 1 2 3 4 5 6 7 8 9 10 11 12 13 IAC SE 546 | Expected: []byte{255,255,250, 0,1,2,3,4,5,6,7,8,9,'\r',10}, 547 | }, 548 | { 549 | Bytes: []byte{67, 255,250, 0,1,2,3,4,5,6,7,8,9, 10, 11,12,13 ,255,240}, // 'C' IAC SB 0 1 2 3 4 5 6 7 8 9 10 11 12 13 IAC SE 550 | Expected: []byte{67, 255,255,250, 0,1,2,3,4,5,6,7,8,9,'\r',10}, 551 | }, 552 | { 553 | Bytes: []byte{ 255,250, 0,1,2,3,4,5,6,7,8,9, 10,11,12,13 ,255,240, 68}, // IAC SB 0 1 2 3 4 5 6 7 8 9 10 11 12 13 IAC SE 'D' 554 | Expected: []byte{255,255,250, 0,1,2,3,4,5,6,7,8,9,'\r',10}, 555 | }, 556 | { 557 | Bytes: []byte{67, 255,250, 0,1,2,3,4,5,6,7,8,9, 10,11,12,13 ,255,240, 68}, // 'C' IAC SB 0 1 2 3 4 5 6 7 8 9 10 11 12 13 IAC SE 'D' 558 | Expected: []byte{67, 255,255,250, 0,1,2,3,4,5,6,7,8,9,'\r',10}, 559 | }, 560 | 561 | 562 | 563 | { 564 | Bytes: []byte{ 255,250, 0,1,2,3,4,5,6,7,8,9, 10,11,12,13, 255,240, '\n'}, // IAC SB 0 1 2 3 4 5 6 7 8 9 10 11 12 13 IAC SE '\n' 565 | Expected: []byte{255,255,250, 0,1,2,3,4,5,6,7,8,9,'\r',10,11,12,13, 255,255,240, '\r','\n'}, 566 | }, 567 | { 568 | Bytes: []byte{67, 255,250, 0,1,2,3,4,5,6,7,8,9, 10,11,12, 13, 255,240, '\n'}, // 'C' IAC SB 0 1 2 3 4 5 6 7 8 9 10 11 12 13 IAC SE '\n' 569 | Expected: []byte{67, 255,255,250, 0,1,2,3,4,5,6,7,8,9,'\r',10,11,12,13, 255,255,240, '\r','\n'}, 570 | }, 571 | { 572 | Bytes: []byte{ 255,250, 0,1,2,3,4,5,6,7,8,9, 10,11,12,13, 255,240, 68, '\n'}, // IAC SB 0 1 2 3 4 5 6 7 8 9 10 11 12 13 IAC SE 'D' '\n' 573 | Expected: []byte{255,255,250, 0,1,2,3,4,5,6,7,8,9,'\r',10,11,12,13, 255,255,240, 68, '\r','\n'}, 574 | }, 575 | { 576 | Bytes: []byte{67, 255,250, 0,1,2,3,4,5,6,7,8,9, 10,11,12,13, 255,240, 68, '\n'}, // 'C' IAC SB 0 1 2 3 4 5 6 7 8 9 10 11 12 13 IAC SE 'D' '\n' 577 | Expected: []byte{67, 255,255,250, 0,1,2,3,4,5,6,7,8,9,'\r',10,11,12,13, 255,255,240, 68, '\r','\n'}, 578 | }, 579 | } 580 | 581 | 582 | for testNumber, test := range tests { 583 | var stdinBuffer bytes.Buffer 584 | var stdoutBuffer bytes.Buffer 585 | var stderrBuffer bytes.Buffer 586 | 587 | stdinBuffer.Write(test.Bytes) // <----------------- The important difference between the 2 loops. 588 | 589 | stdin := ioutil.NopCloser(&stdinBuffer) 590 | stdout := oi.WriteNopCloser(&stdoutBuffer) 591 | stderr := oi.WriteNopCloser(&stderrBuffer) 592 | 593 | var ctx Context = nil 594 | 595 | var dataWriterBuffer bytes.Buffer 596 | dataWriter := newDataWriter(&dataWriterBuffer) 597 | 598 | dataReader := newDataReader( bytes.NewReader([]byte{}) ) // <----------------- The important difference between the 2 loops. 599 | 600 | standardCallerCallTELNET(stdin, stdout, stderr, ctx, dataWriter, dataReader) 601 | 602 | 603 | if expected, actual := string(test.Expected), dataWriterBuffer.String(); expected != actual { 604 | t.Errorf("For test #%d, expected %q, but actually got %q; for %q.", testNumber, expected, actual, test.Bytes) 605 | continue 606 | } 607 | 608 | if expected, actual := "", stdoutBuffer.String(); expected != actual { 609 | t.Errorf("For test #%d, expected %q, but actually got %q.", testNumber, expected, actual) 610 | continue 611 | } 612 | 613 | if expected, actual := "", stderrBuffer.String(); expected != actual { 614 | t.Errorf("For test #%d, expected %q, but actually got %q.", testNumber, expected, actual) 615 | continue 616 | } 617 | } 618 | } 619 | 620 | 621 | 622 | func TestStandardCallerFromServerToClient(t *testing.T) { 623 | 624 | tests := []struct{ 625 | Bytes []byte 626 | Expected []byte 627 | }{ 628 | { 629 | Bytes: []byte{}, 630 | Expected: []byte{}, 631 | }, 632 | 633 | 634 | 635 | { 636 | Bytes: []byte("a"), 637 | Expected: []byte("a"), 638 | }, 639 | { 640 | Bytes: []byte("b"), 641 | Expected: []byte("b"), 642 | }, 643 | { 644 | Bytes: []byte("c"), 645 | Expected: []byte("c"), 646 | }, 647 | 648 | 649 | 650 | { 651 | Bytes: []byte("apple"), 652 | Expected: []byte("apple"), 653 | }, 654 | { 655 | Bytes: []byte("banana"), 656 | Expected: []byte("banana"), 657 | }, 658 | { 659 | Bytes: []byte("cherry"), 660 | Expected: []byte("cherry"), 661 | }, 662 | 663 | 664 | 665 | { 666 | Bytes: []byte("apple banana cherry"), 667 | Expected: []byte("apple banana cherry"), 668 | }, 669 | 670 | 671 | 672 | { 673 | Bytes: []byte{255,255}, 674 | Expected: []byte{255}, 675 | }, 676 | { 677 | Bytes: []byte{255,255,255,255}, 678 | Expected: []byte{255,255}, 679 | }, 680 | { 681 | Bytes: []byte{255,255,255,255,255,255}, 682 | Expected: []byte{255,255,255}, 683 | }, 684 | { 685 | Bytes: []byte{255,255,255,255,255,255,255,255}, 686 | Expected: []byte{255,255,255,255}, 687 | }, 688 | { 689 | Bytes: []byte{255,255,255,255,255,255,255,255,255,255}, 690 | Expected: []byte{255,255,255,255,255}, 691 | }, 692 | 693 | 694 | 695 | { 696 | Bytes: []byte("apple\xff\xffbanana\xff\xffcherry"), 697 | Expected: []byte("apple\xffbanana\xffcherry"), 698 | }, 699 | { 700 | Bytes: []byte("\xff\xffapple\xff\xffbanana\xff\xffcherry\xff\xff"), 701 | Expected: []byte("\xffapple\xffbanana\xffcherry\xff"), 702 | }, 703 | 704 | 705 | 706 | 707 | { 708 | Bytes: []byte("apple\xff\xff\xff\xffbanana\xff\xff\xff\xffcherry"), 709 | Expected: []byte("apple\xff\xffbanana\xff\xffcherry"), 710 | }, 711 | { 712 | Bytes: []byte("\xff\xff\xff\xffapple\xff\xff\xff\xffbanana\xff\xff\xff\xffcherry\xff\xff\xff\xff"), 713 | Expected: []byte("\xff\xffapple\xff\xffbanana\xff\xffcherry\xff\xff"), 714 | }, 715 | 716 | 717 | 718 | { 719 | Bytes: []byte{255,251,24}, // IAC WILL TERMINAL-TYPE 720 | Expected: []byte{}, 721 | }, 722 | { 723 | Bytes: []byte{255,252,24}, // IAC WON'T TERMINAL-TYPE 724 | Expected: []byte{}, 725 | }, 726 | { 727 | Bytes: []byte{255,253,24}, // IAC DO TERMINAL-TYPE 728 | Expected: []byte{}, 729 | }, 730 | { 731 | Bytes: []byte{255,254,24}, // IAC DON'T TERMINAL-TYPE 732 | Expected: []byte{}, 733 | }, 734 | 735 | 736 | 737 | { 738 | Bytes: []byte{67, 255,251,24}, // 'C' IAC WILL TERMINAL-TYPE 739 | Expected: []byte{67}, 740 | }, 741 | { 742 | Bytes: []byte{67, 255,252,24}, // 'C' IAC WON'T TERMINAL-TYPE 743 | Expected: []byte{67}, 744 | }, 745 | { 746 | Bytes: []byte{67, 255,253,24}, // 'C' IAC DO TERMINAL-TYPE 747 | Expected: []byte{67}, 748 | }, 749 | { 750 | Bytes: []byte{67, 255,254,24}, // 'C' IAC DON'T TERMINAL-TYPE 751 | Expected: []byte{67}, 752 | }, 753 | 754 | 755 | 756 | { 757 | Bytes: []byte{255,251,24, 68}, // IAC WILL TERMINAL-TYPE 'D' 758 | Expected: []byte{68}, 759 | }, 760 | { 761 | Bytes: []byte{255,252,24, 68}, // IAC WON'T TERMINAL-TYPE 'D' 762 | Expected: []byte{68}, 763 | }, 764 | { 765 | Bytes: []byte{255,253,24, 68}, // IAC DO TERMINAL-TYPE 'D' 766 | Expected: []byte{68}, 767 | }, 768 | { 769 | Bytes: []byte{255,254,24, 68}, // IAC DON'T TERMINAL-TYPE 'D' 770 | Expected: []byte{68}, 771 | }, 772 | 773 | 774 | { 775 | Bytes: []byte{67, 255,251,24, 68}, // 'C' IAC WILL TERMINAL-TYPE 'D' 776 | Expected: []byte{67,68}, 777 | }, 778 | { 779 | Bytes: []byte{67, 255,252,24, 68}, // 'C' IAC WON'T TERMINAL-TYPE 'D' 780 | Expected: []byte{67,68}, 781 | }, 782 | { 783 | Bytes: []byte{67, 255,253,24, 68}, // 'C' IAC DO TERMINAL-TYPE 'D' 784 | Expected: []byte{67,68}, 785 | }, 786 | { 787 | Bytes: []byte{67, 255,254,24, 68}, // 'C' IAC DON'T TERMINAL-TYPE 'D' 788 | Expected: []byte{67,68}, 789 | }, 790 | 791 | 792 | 793 | { 794 | Bytes: []byte{255, 250, 24, 1, 255, 240}, // IAC SB TERMINAL-TYPE SEND IAC SE 795 | Expected: []byte{}, 796 | }, 797 | { 798 | Bytes: []byte{255, 250, 24, 0, 68,69,67,45,86,84,53,50 ,255, 240}, // IAC SB TERMINAL-TYPE IS "DEC-VT52" IAC SE 799 | Expected: []byte{}, 800 | }, 801 | 802 | 803 | 804 | { 805 | Bytes: []byte{67, 255, 250, 24, 1, 255, 240}, // 'C' IAC SB TERMINAL-TYPE SEND IAC SE 806 | Expected: []byte{67}, 807 | }, 808 | { 809 | Bytes: []byte{67, 255, 250, 24, 0, 68,69,67,45,86,84,53,50 ,255, 240}, // 'C' IAC SB TERMINAL-TYPE IS "DEC-VT52" IAC SE 810 | Expected: []byte{67}, 811 | }, 812 | 813 | 814 | 815 | { 816 | Bytes: []byte{255, 250, 24, 1, 255, 240, 68}, // IAC SB TERMINAL-TYPE SEND IAC SE 'D' 817 | Expected: []byte{68}, 818 | }, 819 | { 820 | Bytes: []byte{255, 250, 24, 0, 68,69,67,45,86,84,53,50 ,255, 240, 68}, // IAC SB TERMINAL-TYPE IS "DEC-VT52" IAC SE 'D' 821 | Expected: []byte{68}, 822 | }, 823 | 824 | 825 | 826 | { 827 | Bytes: []byte{67, 255, 250, 24, 1, 255, 240, 68}, // 'C' IAC SB TERMINAL-TYPE SEND IAC SE 'D' 828 | Expected: []byte{67, 68}, 829 | }, 830 | { 831 | Bytes: []byte{67, 255, 250, 24, 0, 68,69,67,45,86,84,53,50 ,255, 240, 68}, // 'C' IAC SB TERMINAL-TYPE IS "DEC-VT52" IAC SE 'D' 832 | Expected: []byte{67, 68}, 833 | }, 834 | 835 | 836 | 837 | { 838 | Bytes: []byte{255,250, 0,1,2,3,4,5,6,7,8,9,10,11,12,13 ,255,240}, // IAC SB 0 1 2 3 4 5 6 7 8 9 10 11 12 13 IAC SE 839 | Expected: []byte{}, 840 | }, 841 | { 842 | Bytes: []byte{67, 255,250, 0,1,2,3,4,5,6,7,8,9,10,11,12,13 ,255,240}, // 'C' IAC SB 0 1 2 3 4 5 6 7 8 9 10 11 12 13 IAC SE 843 | Expected: []byte{67}, 844 | }, 845 | { 846 | Bytes: []byte{255,250, 0,1,2,3,4,5,6,7,8,9,10,11,12,13 ,255,240, 68}, // IAC SB 0 1 2 3 4 5 6 7 8 9 10 11 12 13 IAC SE 'D' 847 | Expected: []byte{68}, 848 | }, 849 | { 850 | Bytes: []byte{67, 255,250, 0,1,2,3,4,5,6,7,8,9,10,11,12,13 ,255,240, 68}, // 'C' IAC SB 0 1 2 3 4 5 6 7 8 9 10 11 12 13 IAC SE 'D' 851 | Expected: []byte{67,68}, 852 | }, 853 | 854 | 855 | 856 | //@TODO: Is this correct? Can IAC appear between thee 'IAC SB' and ''IAC SE'?... and if "yes", do escaping rules apply? 857 | { 858 | Bytes: []byte{ 255,250, 255,255,240 ,255,240}, // IAC SB 255 255 240 IAC SE = IAC SB IAC IAC SE IAC SE 859 | Expected: []byte{}, 860 | }, 861 | { 862 | Bytes: []byte{67, 255,250, 255,255,240 ,255,240}, // 'C' IAC SB 255 255 240 IAC SE = IAC SB IAC IAC SE IAC SE 863 | Expected: []byte{67}, 864 | }, 865 | { 866 | Bytes: []byte{ 255,250, 255,255,240 ,255,240, 68}, // IAC SB 255 255 240 IAC SE = IAC SB IAC IAC SE IAC SE 'D' 867 | Expected: []byte{68}, 868 | }, 869 | { 870 | Bytes: []byte{67, 255,250, 255,255,240 ,255,240, 68}, // 'C' IAC SB 255 255 240 IAC SE = IAC SB IAC IAC SE IAC SE 'D' 871 | Expected: []byte{67,68}, 872 | }, 873 | 874 | 875 | 876 | //@TODO: Is this correct? Can IAC appear between thee 'IAC SB' and ''IAC SE'?... and if "yes", do escaping rules apply? 877 | { 878 | Bytes: []byte{ 255,250, 71,255,255,240 ,255,240}, // IAC SB 'G' 255 255 240 IAC SE = IAC SB 'G' IAC IAC SE IAC SE 879 | Expected: []byte{}, 880 | }, 881 | { 882 | Bytes: []byte{67, 255,250, 71,255,255,240 ,255,240}, // 'C' IAC SB 'G' 255 255 240 IAC SE = IAC SB 'G' IAC IAC SE IAC SE 883 | Expected: []byte{67}, 884 | }, 885 | { 886 | Bytes: []byte{ 255,250, 71,255,255,240 ,255,240, 68}, // IAC SB 'G' 255 255 240 IAC SE = IAC SB 'G' IAC IAC SE IAC SE 'D' 887 | Expected: []byte{68}, 888 | }, 889 | { 890 | Bytes: []byte{67, 255,250, 71,255,255,240 ,255,240, 68}, // 'C' IAC SB 'G' 255 255 240 IAC SE = IAC 'G' SB IAC IAC SE IAC SE 'D' 891 | Expected: []byte{67,68}, 892 | }, 893 | 894 | 895 | 896 | //@TODO: Is this correct? Can IAC appear between thee 'IAC SB' and ''IAC SE'?... and if "yes", do escaping rules apply? 897 | { 898 | Bytes: []byte{ 255,250, 255,255,240,72 ,255,240}, // IAC SB 255 255 240 'H' IAC SE = IAC SB IAC IAC SE 'H' IAC SE 899 | Expected: []byte{}, 900 | }, 901 | { 902 | Bytes: []byte{67, 255,250, 255,255,240,72 ,255,240}, // 'C' IAC SB 255 255 240 'H' IAC SE = IAC SB IAC IAC SE 'H' IAC SE 903 | Expected: []byte{67}, 904 | }, 905 | { 906 | Bytes: []byte{ 255,250, 255,255,240,72 ,255,240, 68}, // IAC SB 255 255 240 'H' IAC SE = IAC SB IAC IAC SE 'H' IAC SE 'D' 907 | Expected: []byte{68}, 908 | }, 909 | { 910 | Bytes: []byte{67, 255,250, 255,255,240,72 ,255,240, 68}, // 'C' IAC SB 255 255 240 'H' IAC SE = IAC SB IAC IAC SE 'H' IAC SE 'D' 911 | Expected: []byte{67,68}, 912 | }, 913 | 914 | 915 | 916 | //@TODO: Is this correct? Can IAC appear between thee 'IAC SB' and ''IAC SE'?... and if "yes", do escaping rules apply? 917 | { 918 | Bytes: []byte{ 255,250, 71,255,255,240,72 ,255,240}, // IAC SB 'G' 255 255 240 'H' IAC SE = IAC SB 'G' IAC IAC SE 'H' IAC SE 919 | Expected: []byte{}, 920 | }, 921 | { 922 | Bytes: []byte{67, 255,250, 71,255,255,240,72 ,255,240}, // 'C' IAC SB 'G' 255 255 240 'H' IAC SE = IAC SB 'G' IAC IAC SE 'H' IAC SE 923 | Expected: []byte{67}, 924 | }, 925 | { 926 | Bytes: []byte{ 255,250, 71,255,255,240,72 ,255,240, 68}, // IAC SB 'G' 255 255 240 'H' IAC SE = IAC SB 'G' IAC IAC SE 'H' IAC SE 'D' 927 | Expected: []byte{68}, 928 | }, 929 | { 930 | Bytes: []byte{67, 255,250, 71,255,255,240,72 ,255,240, 68}, // 'C' IAC SB 'G' 255 255 240 'H' IAC SE = IAC 'G' SB IAC IAC SE 'H' IAC SE 'D' 931 | Expected: []byte{67,68}, 932 | }, 933 | 934 | 935 | } 936 | 937 | 938 | 939 | for testNumber, test := range tests { 940 | var stdinBuffer bytes.Buffer 941 | var stdoutBuffer bytes.Buffer 942 | var stderrBuffer bytes.Buffer 943 | 944 | stdin := ioutil.NopCloser(&stdinBuffer) 945 | stdout := oi.WriteNopCloser(&stdoutBuffer) 946 | stderr := oi.WriteNopCloser(&stderrBuffer) 947 | 948 | var ctx Context = nil 949 | 950 | var dataWriterBuffer bytes.Buffer 951 | dataWriter := newDataWriter(&dataWriterBuffer) 952 | 953 | dataReader := newDataReader( bytes.NewReader(test.Bytes) ) // <----------------- The important difference between the 2 loops. 954 | 955 | standardCallerCallTELNET(stdin, stdout, stderr, ctx, dataWriter, dataReader) 956 | 957 | 958 | if expected, actual := "", dataWriterBuffer.String(); expected != actual { 959 | t.Errorf("For test #%d, expected %q, but actually got %q; for %q.", testNumber, expected, actual, test.Bytes) 960 | continue 961 | } 962 | 963 | if expected, actual := string(test.Expected), stdoutBuffer.String(); expected != actual { 964 | t.Errorf("For test #%d, expected %q, but actually got %q.", testNumber, expected, actual) 965 | continue 966 | } 967 | 968 | if expected, actual := "", stderrBuffer.String(); expected != actual { 969 | t.Errorf("For test #%d, expected %q, but actually got %q.", testNumber, expected, actual) 970 | continue 971 | } 972 | 973 | } 974 | } 975 | --------------------------------------------------------------------------------