123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653 |
- package telnet
- import "bytes"
- import "compression/zlib"
- import "github.com/beoran/woe/monolog"
- // This Telnet struct implements a subset of the Telnet protocol.
- // Telnet states
- type TelnetState int
- const (
- data_state TelnetState = iota,
- iac_state = iota,
- will_state = iota,
- wont_state = iota,
- do_state = iota,
- dont_state = iota
- sb_state = iota,
- sb_data_state = iota,
- sb_data_iac_state = iota
- )
- // Telnet event types
- type Event interface {
- isEvent()
- }
- type DataEvent struct {
- Data [] byte
- }
- func (me DataEvent) isEvent() {}
- type NAWSEvent struct {
- W int
- H int
- }
- func (me NAWSEvent) isEvent() {}
- type TTypeEvent struct {
- Telopt byte
- Name string
- }
- func (me TTypeEvent) isEvent() {}
- type SubnegotioateEvent struct {
- Telopt byte
- Buffer [] byte
- }
- func (me SubnegotiateEvent) isEvent() {}
- type IACEvent struct {
- Telopt byte
- }
- func (me IACEvent) isEvent() {}
- type CompressionEvent struct {
- Compress bool
- }
- func (me CompressionEvent) isEvent() {}
- type EnvironmentEvent struct {
- Telopt byte
- Vars map[string] string
- }
- func (me EnvironmentEvent) isEvent() {}
- type MSSPEvent struct {
- Telopt byte
- Vars map[string] string
- }
- func (me MSSPEvent) isEvent() {}
- type ZMPEvent struct {
- Telopt byte
- Vars map[string] string
- }
- func (me ZMPEvent) isEvent() {}
- type EventChannel chan[Event]
- type Telopt struct {
- telopt byte
- us byte
- him byte
- }
-
- type Telnet struct {
- Events EventChannel
- telopts map[byte] Telopt
- state TelnetState
- compress bool
- zwriter Writer
- zreader Reader
- buffer []byte
- sb_telopt byte
- }
- func New() telnet * Telnet {
- events := make(EventChannel, 64)
- telopts := make (map[byte] Telopt)
- state := data_state
- compress := false
- zwriter := nil
- zreader := nil
- buffer := make([]byte, 1024, 1024)
- sb_telopt := 0
- telnet = &Telnet { events, telopts, state, compress,
- zwriter, zreader, buffer, sb_telopt
- }
- return telnet
- }
- // Starts compresssion
- func (me * Telnet) StartCompression() {
- // var zwbuf bytes.Buffer
- // me.zwriter = zlib.NewWriter(&zwbuf);
- }
-
- // Closes the telnet connection, send last compressed data if needed.
- func (me * Telnet) Close() {
- if me.compress {
- me.zwriter.Close()
- me.zreader.Close()
- }
- }
- // Filters raw text, only compressing it if needed.
- func (me * Telnet) FilterRaw(in []byte, out chan []byte) {
- // XXX Handle compression here later
- out <- in
- }
- // Filters text, escaping IAC bytes.
- func (me * Telnet) FilterRaw(in []byte, out chan []byte) {
- buffer := make([]byte, len(in) * 2, len(in) * 2)
- outdex := 0
- /* Double IAC characters to escape them. */
- for index := 0; index < len(in) ; index++ {
- now := in[index]
- if now == TELNET_IAC {
- buffer[outdex] = TELNET_IAC;
- outdex++;
- }
- buffer[outdex] = TELNET_IAC;
- outdex++;
- }
- out <- buffer
- }
- // Send negotiation bytes
- func (me * Telnet) SendNegotiate(cmd byte, telopt byte, out chan []byte) {
- buffer := make([]byte, 3)
- buffer[0] = TELNET_IAC
- buffer[1] = cmd
- buffer[2] = telopt
- me.FilterRaw(buffer, out)
- }
-
- // Parse a subnegotiation buffer for a naws event
- func (me * Telnet) SubnegotiateNAWS(buffer []byte, )
- // Some clients, like Gnome-Mud can't even get this right. Grrr!
- // XXx continue here
- if buffer.nil? || buffer.empty? || buffer.size != 4
- monolog.Info("Bad NAWS negotiation: #{buffer}")
- return nil
- end
- arr = buffer.bytes.to_a
- w = (arr[0] << 8) + arr[1]
- h = (arr[2] << 8) + arr[3]
- send_event(:naws, w, h)
- end
-
- # Storage for environment values
- class Environment
- attr_accessor :type
- attr_accessor :value
-
- def initialize(type, value)
- me.type = type
- me.value = value
- end
- end
- # process an ENVIRON/NEW-ENVIRON subnegotiation buffer
- def subnegotiate_environ(buffer)
- vars = []
- cmd = ""
- arr = buffer.bytes.to_a
- fb = arr.first
- # first byte must be a valid command
- if fb != TELNET_ENVIRON_SEND && fb != TELNET_ENVIRON_IS && fb != TELNET_ENVIRON_INFO
- log_error("telopt environment subneg command not valid")
- return 0
- end
-
- cmd << fb
-
- if (buffer.size == 1)
- send_event(:environment, fb, vars)
- return false
- end
-
- # Second byte must be VAR or USERVAR, if present
- sb = arr[1]
- if sb != TELNET_ENVIRON_VAR && fb != TELNET_ENVIRON_USEVAR
- log_error("telopt environment subneg missing variable type")
- return false
- end
-
- # ensure last byte is not an escape byte (makes parsing later easier)
- lb = arr.last
- if lb == TELNET_ENVIRON_ESC
- log_error("telopt environment subneg ends with ESC")
- return false
- end
- var = nil
- index = 1
- escape = false
-
- arr.shift
-
- arr.each do | c |
- case c
- when TELNET_ENVIRON_VAR
- when TELNET_ENVIRON_VALUE
- when TELNET_ENVIRON_USERVAR
- if escape
- escape = false
- var.value << c
- elsif var
- vars << var
- var = Environment.new(c, "")
- else
- var = Environment.new(c, "")
- end
- when TELNET_ENVIRON_ESC
- escape = true
- else
- var.value << c
- end # case
- end # each
-
- send_event(:environment, fb, vars)
- return false
- end
- # process an MSSP subnegotiation buffer
- def subnegotiate_mssp(buffer)
- telnet_event_t ev;
- struct telnet_environ_t *values;
- char *var = 0;
- char *c, *last, *out;
- size_t i, count;
- unsigned char next_type;
-
- if buffer.size < 1
- return 0
- end
-
- arr = buffer.bytes.to_a
- fb = arr.first
- # first byte must be a valid command
- if fb != TELNET_MSSSP_VAR
- log_error("telopt MSSP subneg data not valid")
- return false
- end
-
- vars = {}
- var = ""
- val = ""
- mstate = :var
- while index < arr.size
- c = arr[index]
- case c
- when TELNET_MSSP_VAR
- mstate = :var
- if mstate == :val
- vars[var] = val
- var = ""
- val = ""
- end
- when TELNET_MSSP_VAL
- mstate = :val
- else
- if mstate == :var
- var << c
- elsif mstate == :val
- val << c
- end
- end # case
- index += 1
- end # while
-
- send_event(:mssp, vars)
- return false
- end
- # parse ZMP command subnegotiation buffers
- def subnegotiate_zmp(buffer)
- args = []
- arg = ""
-
- buffer.each_byte do |b|
- if b == 0
- args << arg
- arg = ""
- else
- arg << byte
- end
- end
- send_event(:zmp, vars)
- return false
- end
- # parse TERMINAL-TYPE command subnegotiation buffers
- def subnegotiate_ttype(buffer)
- # make sure request is not empty
- if buffer.size == 0
- log_error("Incomplete TERMINAL-TYPE request");
- return 0
- end
-
- arr = buffer.bytes
- fb = arr.first
- term = nil
-
- if fb == TELNET_TTYPE_IS
- term = buffer[1, buffer.size]
- send_event(:ttype_is, term)
- elsif fb == TELNET_TTYPE_SEND
- term = buffer[1, buffer.size]
- send_event(:ttype_send, term)
- else
- log_error("TERMINAL-TYPE request has invalid type")
- return false
- end
- return false
- end
- # process a subnegotiation buffer; returns true if the current buffer
- # must be aborted and reprocessed due to COMPRESS2 being activated
- def do_subnegotiate(buffer)
- case me.sb_telopt
- when TELNET_TELOPT_COMPRESS2
- # received COMPRESS2 begin marker, setup our zlib box and
- # start handling the compressed stream if it's not already.
- me.compress = true
- send_event(:compress, me.compress)
- return true
- # specially handled subnegotiation telopt types
- when TELNET_TELOPT_ZMP
- return subnegotiate_zmp(buffer)
- when TELNET_TELOPT_TTYPE
- return subnegotiate_ttype(buffer)
- when TELNET_TELOPT_ENVIRON
- return subnegotiate_environ(buffer)
- when TELNET_TELOPT_NEW_ENVIRON
- return subnegotiate_environ(buffer)
- when TELNET_TELOPT_MSSP
- return subnegotiate_mssp(buffer)
- when TELNET_TELOPT_NAWS
- return subnegotiate_naws(buffer)
- else
- send_event(:subnegotiate, me.sb_telopt, buffer)
- return false
- end
- end
-
- def process_byte(byte)
- # p "process_byte, #{me.state} #{byte}"
- case me.state
- # regular data
- when :data
- if byte == TELNET_IAC
- # receive buffered bytes as data and go to IAC state if it's notempty
- send_event(:data, me.buffer) unless me.buffer.empty?
- me.buffer = ""
- me.state = :iac
- else
- me.buffer << byte
- end
- # IAC received before
- when :iac
- case byte
- # subnegotiation
- when TELNET_SB
- me.state = :sb
- # negotiation commands
- when TELNET_WILL
- me.state = :will
- when TELNET_WONT
- me.state = :wont
- when TELNET_DO
- me.state = :do
- when TELNET_DONT
- me.state = :dont
- # IAC escaping
- when TELNET_IAC
- me.buffer << TELNET_IAC.chr
- send_event(:data, me.buffer) unless me.buffer.empty?
- me.buffer = ""
- me.state = :data
- # some other command
- else
- send_event(:iac, byte)
- me.state = :data
- end
- # negotiation received before
- when :will, :wont, :do, :dont
- do_negotiate(byte)
- me.state = :data
- # subnegotiation started, determine option to subnegotiate
- when :sb
- me.sb_telopt = byte
- me.state = :sb_data
- # subnegotiation data, buffer bytes until the end request
- when :sb_data
- # IAC command in subnegotiation -- either IAC SE or IAC IAC
- if (byte == TELNET_IAC)
- me.state = :sb_data_iac
- elsif (me.sb_telopt == TELNET_TELOPT_COMPRESS && byte == TELNET_WILL)
- # MCCPv1 defined an invalid subnegotiation sequence (IAC SB 85 WILL SE)
- # to start compression. Catch and discard this case, only support
- # MMCPv2.
- me.state = data
- else
- me.buffer << byte
- end
- # IAC received inside a subnegotiation
- when :sb_data_iac
- case byte
- # end subnegotiation
- when TELNET_SE
- me.state = :data
- # process subnegotiation
- compress = do_subnegotiate(me.buffer)
- # if compression was negotiated, the rest of the stream is compressed
- # and processing it requires decompressing it. Return true to signal
- # this.
- me.buffer = ""
- return true if compress
- # escaped IAC byte
- when TELNET_IAC
- # push IAC into buffer */
- me.buffer << byte
- me.state = :sb_data
- # something else -- protocol error. attempt to process
- # content in subnegotiation buffer, then evaluate the
- # given command as an IAC code.
- else
- log_error("Unexpected byte after IAC inside SB: %d", byte)
- me.state = :iac
- # subnegotiate with the buffer anyway, even though it's an error
- compress = do_subnegotiate(me.buffer)
- # if compression was negotiated, the rest of the stream is compressed
- # and processing it requires decompressing it. Return true to signal
- # this.
- me.buffer = ""
- return true if compress
- end
- when :data
- # buffer any other bytes
- me.buffer << byte
- else
- # programing error, shouldn't happen
- raise "Error in telet state machine!"
- end
- # return false to signal compression needn't start
- return false
- end
-
- def process_bytes(bytes)
- # I have a feeling this way of handling strings isn't very efficient.. :p
- arr = bytes.bytes.to_a
- byte = arr.shift
- while byte
- compress = process_byte(byte)
- if compress
- # paper over this for a while...
- new_bytes = Zlib.inflate(arr.pack('c*')) rescue nil
- if new_bytes
- arr = new_bytes.bytes.to_a
- end
- end
- byte = arr.shift
- end
- send_event(:data, me.buffer) unless me.buffer.empty?
- me.buffer = ""
- end
-
- # Call this when the server receives data from the client
- def telnet_receive(data)
- # the COMPRESS2 protocol seems to be half-duplex in that only
- # the server's data stream is compressed (unless maybe if the client
- # is asked to also compress with a DO command ?)
- process_bytes(data)
- end
-
- # Send a bytes array (raw) to the client
- def telnet_send_bytes(*bytes)
- s = bytes.pack('C*')
- send_raw(s)
- end
-
- # send an iac command
- def telnet_send_iac(cmd)
- telnet_send_bytes(TELNET_IAC, cmd)
- end
- # send negotiation
- def telnet_send_negotiate(cmd, telopt)
- # get current option states
- q = rfc1143_get(telopt)
- unless q
- rfc1143_set(telopt)
- q = rfc1143_get(telopt)
- end
-
- act, arg = nil, nil
- case cmd
- when TELNET_WILL
- act, arg = q.send_will
- when TELNET_WONT
- act, arg = q.send_wont
- when TELNET_DO
- act, arg = q.send_do
- when TELNET_DONT
- act, arg = q.send_dont
- end
-
- return false unless act
- telnet_send_bytes(TELNET_IAC, act, telopt)
- end
-
- # send non-command data (escapes IAC bytes)
- def telnet_send(buffer)
- send_escaped(buffer)
- end
-
- # send subnegotiation header
- def telnet_begin_sb(telopt)
- telnet_send_bytes(TELNET_IAC, TELNET_SB, telopt)
- end
- # send subnegotiation ending
- def telnet_end_sb()
- telnet_send_bytes(TELNET_IAC, TELNET_SE)
- end
- # send complete subnegotiation
- def telnet_subnegotiation(telopt, buffer = nil)
- telnet_send_bytes(TELNET_IAC, TELNET_SB, telopt)
- telnet_send(buffer) if buffer;
- telnet_send_bytes(TELNET_IAC, TELNET_SE)
- end
-
- # start compress2 compression
- def telnet_begin_compress2()
- telnet_send_bytes(TELNET_IAC, TELNET_SB, TELNET_TELOPT_COMPRESS2, TELNET_IAC, TELNET_SE);
- me.compress = true
- end
-
- # send formatted data
- def telnet_raw_printf(fmt, *args)
- buf = sprintf(fmt, *args)
- telnet_send(buf)
- end
- CRLF = "\r\n"
- CRNUL = "\r\0"
-
- # send formatted data with \r and \n translation in addition to IAC IAC
- def telnet_printf(fmt, *args)
- buf = sprintf(fmt, *args)
- buf.gsub!("\r", CRNUL)
- buf.gsub!("\n", CRLF)
- telnet_send(buf)
- end
- # begin NEW-ENVIRON subnegotation
- def telnet_begin_newenviron(cmd)
- telnet_begin_sb(TELNET_TELOPT_NEW_ENVIRON)
- telnet_send_bytes(cmd)
- end
-
- # send a NEW-ENVIRON value
- def telnet_newenviron_value(type, value)
- telnet_send_bytes(type)
- telnet_send(string)
- end
-
- # send TERMINAL-TYPE SEND command
- def telnet_ttype_send()
- telnet_send_bytes(TELNET_IAC, TELNET_SB, TELNET_TELOPT_TTYPE, TELNET_TTYPE_SEND, TELNET_IAC, TELNET_SE)
- end
-
- # send TERMINAL-TYPE IS command
- def telnet_ttype_is(ttype)
- telnet_send_bytes(TELNET_IAC, TELNET_SB, TELNET_TELOPT_TTYPE, TELNET_TTYPE_IS)
- telnet_send(ttype)
- end
-
- # send MSSP data
- def telnet_send_mssp(mssp)
- buf = ""
- mssp.each do | key, val|
- buf << TELNET_MSSP_VAR.chr
- buf << key
- buf << TELNET_MSSP_VAL.chr
- buf << val
- end
- telnet_subnegotiation(TELNET_TELOPT_MSSP, buf)
- end
- end
|