The TelnetFilter class implements the Telnet protocol.
This implements most of basic Telnet as per RFCs 854/855/1129/1143 and options in RFCs 857/858/1073/1091
Initialize state of filter
pstack
The ProtocolStack associated with this filter
server
An optional hash of desired initial options
# File lib/network/protocol/telnetfilter.rb, line 37 def initialize(pstack, server) super(pstack) @server = server @wopts = {} getopts(@server.service_negotiation) @mode = :normal # Parse mode :normal, :cmd, :cr @state = {} @sc = nil @sneg_opts = [ TTYPE, ZMP ] # supported options which imply an initial # sub negotiation of options @ttype = [] @init_tries = 0 # Number of tries at negotitating sub options @synch = false log.debug "telnet filter initialized - #{@init_tries}" end
Test to see which state we prefer this option to be in
opt
The Telnet option code
# File lib/network/protocol/telnetfilter.rb, line 286 def desired?(opt) st = @wopts[opt] st = false if st.nil? st end
Handle server-side echo
ch
character string to echo
# File lib/network/protocol/telnetfilter.rb, line 294 def echo(ch) return if @server.service_type == :client # Never echo for server when client # Remove this if it makes sense for peer to peer if @pstack.echo_on if @pstack.hide_on && ch.getbyte(0) != CR @pstack.conn.sock.send('*',0) else @pstack.conn.sock.send(ch,0) end end end
Test to see if option is enabled
opt
The Telnet option code
who
The side to check :us or :him
# File lib/network/protocol/telnetfilter.rb, line 278 def enabled?(opt, who) option(opt) e = @state[opt].send(who) e == :yes ? true : false end
The #filter_in method filters input data
str
The string to be processed
return
The filtered data
# File lib/network/protocol/telnetfilter.rb, line 84 def filter_in(str) # init_subneg return "" if str.nil? || str.empty? buf = "" @sc ? @sc.concat(str) : @sc = StringScanner.new(str) while b = @sc.get_byte # OOB sync data if @pstack.urgent_on || b.getbyte(0) == DM log.debug("(#{@pstack.conn.object_id}) Sync mode on") @pstack.urgent_on = false @synch = true break end case mode? when :normal case b.getbyte(0) when CR next if @synch set_mode(:cr) if !@pstack.binary_on when LF # LF or LF/CR may be issued by broken mud servers and clients next if @synch set_mode(:lf) if !@pstack.binary_on buf << LF.chr echo(CR.chr + LF.chr) when IAC set_mode(:cmd) when NUL # ignore NULs in stream when in normal mode next if @synch if @pstack.binary_on buf << b echo(b) else log.debug("(#{@pstack.conn.object_id}) unexpected NUL found in stream") end when BS, DEL next if @synch # Leaves BS, DEL in input stream for higher filter to deal with. buf << b echo(BS.chr) else next if @synch ### NOTE - we will allow 8-bit NVT against RFC 1123 recommendation "should not" ### # Only let 7-bit values through in normal mode #if (b[0] & 0x80 == 0) && !@pstack.binary_on buf << b echo(b) #else # log.debug("(#{@pstack.conn.object_id}) unexpected 8-bit byte found in stream '#{b[0]}'") #end end when :cr # handle CRLF and CRNUL by insertion of LF into buffer case b.getbyte(0) when LF buf << LF.chr echo(CR.chr + LF.chr) when NUL if @server.service_type == :client # Don't xlate CRNUL when client buf << CR.chr echo(CR.chr) else buf << LF.chr echo(CR.chr + LF.chr) end else # eat lone CR buf << b echo(b) end set_mode(:normal) when :lf # liberally handle LF, LFCR for clients that aren't telnet correct case b.getbyte(0) when CR # Handle LFCR by swallowing CR else # Handle other stuff that follows - single LF buf << b echo(b) end set_mode(:normal) when :cmd case b.getbyte(0) when IAC # IAC escapes IAC buf << IAC.chr set_mode(:normal) when AYT log.debug("(#{@pstack.conn.object_id}) AYT sent - Msg returned") @pstack.conn.sock.send("TeensyMUD is here.\n",0) set_mode(:normal) when AO log.debug("(#{@pstack.conn.object_id}) AO sent - Synch returned") @pstack.conn.sockio.write_flush @pstack.conn.sock.send(IAC.chr + DM.chr, 0) @pstack.conn.sockio.write_urgent(DM.chr) set_mode(:normal) when IP @pstack.conn.sockio.read_flush @pstack.conn.sockio.write_flush log.debug("(#{@pstack.conn.object_id}) IP sent") set_mode(:normal) when GA, NOP, BRK # not implemented or ignored log.debug("(#{@pstack.conn.object_id}) GA, NOP or BRK sent") set_mode(:normal) when DM log.debug("(#{@pstack.conn.object_id}) Synch mode off") @synch = false set_mode(:normal) when EC next if @synch log.debug("(#{@pstack.conn.object_id}) EC sent") if buf.size > 1 buf.slice!(-1) elsif @pstack.conn.inbuffer.size > 0 @pstack.conn.inbuffer.slice(-1) end set_mode(:normal) when EL next if @synch log.debug("(#{@pstack.conn.object_id}) EL sent") p = buf.rindex("\n") if p buf.slice!(p+1..-1) else buf = "" p = @pstack.conn.inbuffer.rindex("\n") if p @pstack.conn.inbuffer.slice!(p+1..-1) end end set_mode(:normal) when DO, DONT, WILL, WONT if @sc.eos? @sc.unscan break end opt = @sc.get_byte case b.getbyte(0) when WILL replies_him(opt.getbyte(0),true) when WONT replies_him(opt.getbyte(0),false) when DO requests_us(opt.getbyte(0),true) when DONT requests_us(opt.getbyte(0),false) end # Update interesting things in ProtocolStack after negotiation case opt.getbyte(0) when ECHO @pstack.echo_on = enabled?(ECHO, :us) when BINARY @pstack.binary_on = enabled?(BINARY, :us) when ZMP @pstack.zmp_on = enabled?(ZMP, :us) end set_mode(:normal) when SB @sc.unscan break if @sc.check_until(%r#{IAC.chr}#{SE.chr}/).nil? @sc.get_byte opt = @sc.get_byte data = @sc.scan_until(%r#{IAC.chr}#{SE.chr}/).chop.chop parse_subneg(opt.getbyte(0),data) set_mode(:normal) else log.debug("(#{@pstack.conn.object_id}) Unknown Telnet command - #{b.getbyte(0)}") set_mode(:normal) end end end # while b @sc = nil if @sc.eos? buf end
The #filter_out method filters output data
str
The string to be processed
return
The filtered data
# File lib/network/protocol/telnetfilter.rb, line 265 def filter_out(str) return '' if str.nil? || str.empty? if !@pstack.binary_on str.gsub!(%r\n/, "\r\n") end str end
Negotiate starting wanted options
args
Optional initial options
# File lib/network/protocol/telnetfilter.rb, line 56 def init(args) if @server.service_type == :client # let server offer and ask for client # several sorts of options here - server offer, ask client or both @wopts.each do |key,val| case key when ECHO, SGA, BINARY, ZMP, EOREC ask_him(key,val) else offer_us(key,val) end end else # several sorts of options here - server offer, ask client or both @wopts.each do |key,val| case key when ECHO, SGA, BINARY, ZMP, EOREC offer_us(key,val) else ask_him(key,val) end end end true end
Negotiate starting wanted options that imply subnegotation So far only terminal type
# File lib/network/protocol/telnetfilter.rb, line 308 def init_subneg return if @init_tries > 20 @init_tries += 1 @wopts.each_key do |opt| next if !@sneg_opts.include?(opt) log.debug("(#{@pstack.conn.object_id}) Subnegotiation attempt for option #{opt}.") case opt when TTYPE who = :him else who = :us end if desired?(opt) == enabled?(opt, who) case opt when TTYPE @pstack.conn.sendmsg(IAC.chr + SB.chr + TTYPE.chr + 1.chr + IAC.chr + SE.chr) when ZMP log.info("(#{@pstack.conn.object_id}) ZMP successfully negotiated." ) @pstack.conn.sendmsg("#{IAC.chr}#{SB.chr}#{ZMP.chr}" + "zmp.check#{NUL.chr}color.#{NUL.chr}" + "#{IAC.chr}#{SE.chr}") @pstack.conn.sendmsg("#{IAC.chr}#{SB.chr}#{ZMP.chr}" + "zmp.ident#{NUL.chr}TeensyMUD#{NUL.chr}#{Version}#{NUL.chr}A sexy mud server#{NUL.chr}" + "#{IAC.chr}#{SE.chr}") @pstack.conn.sendmsg("#{IAC.chr}#{SB.chr}#{ZMP.chr}" + "zmp.ping#{NUL.chr}" + "#{IAC.chr}#{SE.chr}") @pstack.conn.sendmsg("#{IAC.chr}#{SB.chr}#{ZMP.chr}" + "zmp.input#{NUL.chr}\n I see you support...\n ZMP protocol\n#{NUL.chr}" + "#{IAC.chr}#{SE.chr}") end @sneg_opts.delete(opt) end end if @init_tries > 20 log.debug("(#{@pstack.conn.object_id}) Telnet init_subneg option - Timed out after #{@init_tries} tries.") @sneg_opts = [] @pstack.conn.set_initdone if !@pstack.terminal or @pstack.terminal.empty? @pstack.terminal = "dumb" end end end
# File lib/network/protocol/telnetfilter.rb, line 353 def send_naws return if !enabled?(NAWS, :us) ts = @pstack.query(:termsize) data = [ts[0]].pack('n') + [ts[1]].pack('n') data.gsub!(%r#{IAC}/, IAC.chr + IAC.chr) # 255 needs to be doubled @pstack.conn.sendmsg(IAC.chr + SB.chr + NAWS.chr + data + IAC.chr + SE.chr) end