|
@@ -1,608 +0,0 @@
|
|
|
-require 'zlib'
|
|
|
-require_relative 'telnet/codes'
|
|
|
-require_relative 'rfc1143'
|
|
|
-require_relative 'monolog'
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-class Telnet
|
|
|
- include Monolog
|
|
|
- include Telnet::Codes
|
|
|
-
|
|
|
-
|
|
|
- STATES = [:data, :iac, :will, :wont, :do, :dont, :sb, :sb_data, :sb_data_iac]
|
|
|
-
|
|
|
-
|
|
|
- Telopt = Struct.new(:telopt, :us, :him)
|
|
|
-
|
|
|
- attr_reader :telopts
|
|
|
-
|
|
|
- def initialize(client)
|
|
|
- @client = client
|
|
|
- @telopts = {}
|
|
|
- @rfc1143 = {}
|
|
|
- @buffer = ""
|
|
|
- @state = :data
|
|
|
- @sb_telopt = nil;
|
|
|
- @compress = false
|
|
|
- @zdeflate = Zlib::Deflate.new()
|
|
|
- @zinflate = Zlib::Inflate.new()
|
|
|
- end
|
|
|
-
|
|
|
-
|
|
|
- def close
|
|
|
- if @compress
|
|
|
- zbuf = @zdeflate.flush(Zlib::FINISH)
|
|
|
- @client.telnet_send_data(zbuf)
|
|
|
- end
|
|
|
- @zdeflate.close
|
|
|
- @zinflate.close
|
|
|
- end
|
|
|
-
|
|
|
-
|
|
|
- def send_event(type, *data)
|
|
|
- @client.telnet_event(type, *data)
|
|
|
- end
|
|
|
-
|
|
|
-
|
|
|
- def send_raw(buf)
|
|
|
- if @compress
|
|
|
- @zdeflate << buf
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- zbuf = @zdeflate.flush(Zlib::SYNC_FLUSH)
|
|
|
- else
|
|
|
- zbuf = buf
|
|
|
- end
|
|
|
-
|
|
|
- @client.telnet_send_data(zbuf)
|
|
|
- end
|
|
|
-
|
|
|
-
|
|
|
- def send_escaped(buf)
|
|
|
- iac = TELNET_IAC.chr
|
|
|
- self.send_raw(buf.gsub("#{iac}", "#{iac}#{iac}"))
|
|
|
- end
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- def send_negotiate(cmd, telopt)
|
|
|
- bytes = ""
|
|
|
- bytes << TELNET_IAC
|
|
|
- bytes << cmd
|
|
|
- bytes << telopt
|
|
|
- send_raw(bytes)
|
|
|
- end
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- def us_support(telopt)
|
|
|
- have = @rfc1143[telopt]
|
|
|
- return false unless have
|
|
|
- return (have.telopt == telopt) && have.us == :yes
|
|
|
- end
|
|
|
-
|
|
|
-
|
|
|
- def him_support(telopt)
|
|
|
- have = @rfc1143[telopt]
|
|
|
- return false unless have
|
|
|
- return (have.telopt == telopt) && have.him == :yes
|
|
|
- end
|
|
|
-
|
|
|
-
|
|
|
- def set_support(telopt, support=true, us = :no, him = :no)
|
|
|
- rfc1143_set(telopt, support=true, us = :no, him = :no)
|
|
|
- end
|
|
|
-
|
|
|
-
|
|
|
- def rfc1143_get(telopt)
|
|
|
- @rfc1143[telopt]
|
|
|
- end
|
|
|
-
|
|
|
-
|
|
|
- def rfc1143_set(telopt, support=true, us = :no, him = :no)
|
|
|
- agree = support
|
|
|
- @rfc1143[telopt] = RFC1143.new(telopt, us, him, agree)
|
|
|
- return @rfc1143[telopt]
|
|
|
- end
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- def rfc1143_negotiate(telopt)
|
|
|
- q = rfc1143_get(telopt)
|
|
|
- return nil, nil unless q
|
|
|
-
|
|
|
- case @state
|
|
|
- when :will
|
|
|
- return q.handle_will
|
|
|
- when :wont
|
|
|
- return q.handle_wont
|
|
|
- when :do
|
|
|
- return q.handle_do
|
|
|
- when :dont
|
|
|
- return q.handle_dont
|
|
|
- end
|
|
|
- end
|
|
|
-
|
|
|
-
|
|
|
- def do_negotiate(telopt)
|
|
|
- res, arg = rfc1143_negotiate(telopt)
|
|
|
- send_event(@state, telopt, res, arg)
|
|
|
- end
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- def subnegotiate_naws(buffer)
|
|
|
-
|
|
|
- if buffer.nil? || buffer.empty? || buffer.size != 4
|
|
|
- log_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
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- class Environment
|
|
|
- attr_accessor :type
|
|
|
- attr_accessor :value
|
|
|
-
|
|
|
- def initialize(type, value)
|
|
|
- @type = type
|
|
|
- @value = value
|
|
|
- end
|
|
|
- end
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- def subnegotiate_environ(buffer)
|
|
|
- vars = []
|
|
|
- cmd = ""
|
|
|
- arr = buffer.bytes.to_a
|
|
|
- fb = arr.first
|
|
|
-
|
|
|
- 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
|
|
|
-
|
|
|
-
|
|
|
- sb = arr[1]
|
|
|
- if sb != TELNET_ENVIRON_VAR && fb != TELNET_ENVIRON_USEVAR
|
|
|
- log_error("telopt environment subneg missing variable type")
|
|
|
- return false
|
|
|
- end
|
|
|
-
|
|
|
-
|
|
|
- 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
|
|
|
- end
|
|
|
-
|
|
|
- send_event(:environment, fb, vars)
|
|
|
- return false
|
|
|
- end
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-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
|
|
|
-
|
|
|
- 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
|
|
|
- index += 1
|
|
|
- end
|
|
|
-
|
|
|
- send_event(:mssp, vars)
|
|
|
- return false
|
|
|
-end
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-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
|
|
|
-
|
|
|
-
|
|
|
-def subnegotiate_ttype(buffer)
|
|
|
-
|
|
|
- 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
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-def do_subnegotiate(buffer)
|
|
|
- case @sb_telopt
|
|
|
- when TELNET_TELOPT_COMPRESS2
|
|
|
-
|
|
|
-
|
|
|
- @compress = true
|
|
|
- send_event(:compress, @compress)
|
|
|
- return true
|
|
|
-
|
|
|
- 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, @sb_telopt, buffer)
|
|
|
- return false
|
|
|
- end
|
|
|
-end
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- def process_byte(byte)
|
|
|
-
|
|
|
- case @state
|
|
|
-
|
|
|
- when :data
|
|
|
- if byte == TELNET_IAC
|
|
|
-
|
|
|
- send_event(:data, @buffer) unless @buffer.empty?
|
|
|
- @buffer = ""
|
|
|
- @state = :iac
|
|
|
- else
|
|
|
- @buffer << byte
|
|
|
- end
|
|
|
-
|
|
|
- when :iac
|
|
|
- case byte
|
|
|
-
|
|
|
- when TELNET_SB
|
|
|
- @state = :sb
|
|
|
-
|
|
|
- when TELNET_WILL
|
|
|
- @state = :will
|
|
|
- when TELNET_WONT
|
|
|
- @state = :wont
|
|
|
- when TELNET_DO
|
|
|
- @state = :do
|
|
|
- when TELNET_DONT
|
|
|
- @state = :dont
|
|
|
-
|
|
|
- when TELNET_IAC
|
|
|
- @buffer << TELNET_IAC.chr
|
|
|
- send_event(:data, @buffer) unless @buffer.empty?
|
|
|
- @buffer = ""
|
|
|
- @state = :data
|
|
|
-
|
|
|
- else
|
|
|
- send_event(:iac, byte)
|
|
|
- @state = :data
|
|
|
- end
|
|
|
-
|
|
|
-
|
|
|
- when :will, :wont, :do, :dont
|
|
|
- do_negotiate(byte)
|
|
|
- @state = :data
|
|
|
-
|
|
|
- when :sb
|
|
|
- @sb_telopt = byte
|
|
|
- @state = :sb_data
|
|
|
-
|
|
|
- when :sb_data
|
|
|
-
|
|
|
- if (byte == TELNET_IAC)
|
|
|
- @state = :sb_data_iac
|
|
|
- elsif (@sb_telopt == TELNET_TELOPT_COMPRESS && byte == TELNET_WILL)
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- @state = data
|
|
|
- else
|
|
|
- @buffer << byte
|
|
|
- end
|
|
|
-
|
|
|
-
|
|
|
- when :sb_data_iac
|
|
|
- case byte
|
|
|
-
|
|
|
- when TELNET_SE
|
|
|
- @state = :data
|
|
|
-
|
|
|
- compress = do_subnegotiate(@buffer)
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- @buffer = ""
|
|
|
- return true if compress
|
|
|
-
|
|
|
- when TELNET_IAC
|
|
|
-
|
|
|
- @buffer << byte
|
|
|
- @state = :sb_data
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- else
|
|
|
- log_error("Unexpected byte after IAC inside SB: %d", byte)
|
|
|
- @state = :iac
|
|
|
-
|
|
|
- compress = do_subnegotiate(@buffer)
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- @buffer = ""
|
|
|
- return true if compress
|
|
|
- end
|
|
|
- when :data
|
|
|
-
|
|
|
- @buffer << byte
|
|
|
- else
|
|
|
-
|
|
|
- raise "Error in telet state machine!"
|
|
|
- end
|
|
|
-
|
|
|
- return false
|
|
|
- end
|
|
|
-
|
|
|
- def process_bytes(bytes)
|
|
|
-
|
|
|
- arr = bytes.bytes.to_a
|
|
|
- byte = arr.shift
|
|
|
- while byte
|
|
|
- compress = process_byte(byte)
|
|
|
- if compress
|
|
|
-
|
|
|
- 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, @buffer) unless @buffer.empty?
|
|
|
- @buffer = ""
|
|
|
- end
|
|
|
-
|
|
|
-
|
|
|
- def telnet_receive(data)
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- process_bytes(data)
|
|
|
- end
|
|
|
-
|
|
|
-
|
|
|
- def telnet_send_bytes(*bytes)
|
|
|
- s = bytes.pack('C*')
|
|
|
- send_raw(s)
|
|
|
- end
|
|
|
-
|
|
|
-
|
|
|
- def telnet_send_iac(cmd)
|
|
|
- telnet_send_bytes(TELNET_IAC, cmd)
|
|
|
- end
|
|
|
-
|
|
|
-
|
|
|
- def telnet_send_negotiate(cmd, telopt)
|
|
|
-
|
|
|
- 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
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- def telnet_send(buffer)
|
|
|
- send_escaped(buffer)
|
|
|
- end
|
|
|
-
|
|
|
-
|
|
|
- def telnet_begin_sb(telopt)
|
|
|
- telnet_send_bytes(TELNET_IAC, TELNET_SB, telopt)
|
|
|
- end
|
|
|
-
|
|
|
-
|
|
|
- def telnet_end_sb()
|
|
|
- telnet_send_bytes(TELNET_IAC, TELNET_SE)
|
|
|
- end
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- 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
|
|
|
-
|
|
|
-
|
|
|
- def telnet_begin_compress2()
|
|
|
- telnet_send_bytes(TELNET_IAC, TELNET_SB, TELNET_TELOPT_COMPRESS2, TELNET_IAC, TELNET_SE);
|
|
|
- @compress = true
|
|
|
- end
|
|
|
-
|
|
|
-
|
|
|
- def telnet_raw_printf(fmt, *args)
|
|
|
- buf = sprintf(fmt, *args)
|
|
|
- telnet_send(buf)
|
|
|
- end
|
|
|
-
|
|
|
- CRLF = "\r\n"
|
|
|
- CRNUL = "\r\0"
|
|
|
-
|
|
|
-
|
|
|
- def telnet_printf(fmt, *args)
|
|
|
- buf = sprintf(fmt, *args)
|
|
|
- buf.gsub!("\r", CRNUL)
|
|
|
- buf.gsub!("\n", CRLF)
|
|
|
- telnet_send(buf)
|
|
|
- end
|
|
|
-
|
|
|
-
|
|
|
- def telnet_begin_newenviron(cmd)
|
|
|
- telnet_begin_sb(TELNET_TELOPT_NEW_ENVIRON)
|
|
|
- telnet_send_bytes(cmd)
|
|
|
- end
|
|
|
-
|
|
|
-
|
|
|
- def telnet_newenviron_value(type, value)
|
|
|
- telnet_send_bytes(type)
|
|
|
- telnet_send(string)
|
|
|
- end
|
|
|
-
|
|
|
-
|
|
|
- def telnet_ttype_send()
|
|
|
- telnet_send_bytes(TELNET_IAC, TELNET_SB, TELNET_TELOPT_TTYPE, TELNET_TTYPE_SEND, TELNET_IAC, TELNET_SE)
|
|
|
- end
|
|
|
-
|
|
|
-
|
|
|
- def telnet_ttype_is(ttype)
|
|
|
- telnet_send_bytes(TELNET_IAC, TELNET_SB, TELNET_TELOPT_TTYPE, TELNET_TTYPE_IS)
|
|
|
- telnet_send(ttype)
|
|
|
- end
|
|
|
-
|
|
|
-
|
|
|
- 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
|