class TelnetFilter

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

Public Class Methods

new(pstack, server) click to toggle source

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

Public Instance Methods

desired?(opt) click to toggle source

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
echo(ch) click to toggle source

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
enabled?(opt, who) click to toggle source

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
filter_in(str) click to toggle source

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
filter_out(str) click to toggle source

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
init(args) click to toggle source

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
init_subneg() click to toggle source

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
send_naws() click to toggle source
# 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