Browse Source

Working on serialization and the Telnet protocol.

Beoran 8 years ago
parent
commit
9fa91fa423
10 changed files with 690 additions and 876 deletions
  1. 195 0
      lib/rfc1143.rb
  2. 45 20
      lib/serdes.rb
  3. 127 701
      lib/telnet.rb
  4. 94 96
      lib/telnet/codes.rb
  5. 12 3
      lib/woe/server.rb
  6. 71 0
      lib/woe/settings.rb
  7. 29 0
      test/test_rfc1143.rb
  8. 34 0
      test/test_serdes.rb
  9. 35 0
      test/woe/test_settings.rb
  10. 48 56
      woe.geany

+ 195 - 0
lib/rfc1143.rb

@@ -0,0 +1,195 @@
+
+require_relative 'telnet/codes'
+
+
+# rfc1143 state machine for telnet protocol
+class RFC1143
+  include Telnet::Codes
+
+  attr_reader :telopt
+  attr_reader :us
+  attr_reader :him
+  attr_reader :agree
+  
+  def initialize(to, u, h, a)
+    @telopt = to
+    @us     = u
+    @him    = h
+    @agree  = a
+  end
+
+=begin
+ EXAMPLE STATE MACHINE
+ FOR THE Q METHOD OF IMPLEMENTING TELNET OPTION NEGOTIATION
+
+    There are two sides, we (us) and he (him).  We keep four
+    variables:
+
+       us: state of option on our side (NO/WANTNO/WANTYES/YES)
+       usq: a queue bit (EMPTY/OPPOSITE) if us is WANTNO or WANTYES
+       him: state of option on his side
+       himq: a queue bit if him is WANTNO or WANTYES
+
+    An option is enabled if and only if its state is YES.  Note that
+    us/usq and him/himq could be combined into two six-choice states.
+
+    "Error" below means that producing diagnostic information may be a
+    good idea, though it isn't required.
+
+    Upon receipt of WILL, we choose based upon him and himq:
+       NO            If we agree that he should enable, him=YES, send
+                     DO; otherwise, send DONT.
+       YES           Ignore.
+       WANTNO  EMPTY Error: DONT answered by WILL. him=NO.
+            OPPOSITE Error: DONT answered by WILL. him=YES*,
+                     himq=EMPTY.
+       WANTYES EMPTY him=YES.
+            OPPOSITE him=WANTNO, himq=EMPTY, send DONT.
+
+    * This behavior is debatable; DONT will never be answered by WILL
+      over a reliable connection between TELNETs compliant with this
+      RFC, so this was chosen (1) not to generate further messages,
+      because if we know we're dealing with a noncompliant TELNET we
+      shouldn't trust it to be sensible; (2) to empty the queue
+      sensibly.
+
+=end
+  def handle_will
+    case @us
+    when :no
+      if @agree
+        return TELNET_DO, @telopt
+      else
+        return TELNET_DONT, @telopt
+      end
+    when :yes
+      # ignore
+      return nil, nil
+    when :wantno
+      @him = :no
+      return :error, "DONT answered by WILL"
+    when :wantno_opposite
+      @him = :yes
+      return :error, "DONT answered by WILL"
+    when :wantyes
+      @him = :yes
+      return nil, nil
+    when :wantyes_opposite
+      @him = :wantno
+      return TELNET_DONT, @telopt
+    end
+  end
+  
+  
+=begin
+Upon receipt of WONT, we choose based upon him and himq:
+   NO            Ignore.
+   YES           him=NO, send DONT.
+   WANTNO  EMPTY him=NO.
+        OPPOSITE him=WANTYES, himq=NONE, send DO.
+   WANTYES EMPTY him=NO.*
+        OPPOSITE him=NO, himq=NONE.**
+
+* Here is the only spot a length-two queue could be useful; after
+  a WILL negotiation was refused, a queue of WONT WILL would mean
+  to request the option again. This seems of too little utility
+  and too much potential waste; there is little chance that the
+  other side will change its mind immediately.
+
+** Here we don't have to generate another request because we've
+   been "refused into" the correct state anyway.
+=end 
+  def handle_wont
+    case @us
+    when :no
+      return nil, nil
+    when :yes
+      @him = :no
+      return TELNET_DONT, @telopt
+    when :wantno
+      @him = :no
+      return nil, nil
+    when :wantno_opposite
+      @him = :wantyes
+      return TELNET_DO, @telopt
+    when :wantyes
+      @him = :no
+      return nil, nil
+    when :wantyes_opposite
+      @him = :no
+      return nil, nil
+    end
+  end   
+  
+=begin
+
+    If we decide to ask him to enable:
+       NO            him=WANTYES, send DO.
+       YES           Error: Already enabled.
+       WANTNO  EMPTY If we are queueing requests, himq=OPPOSITE;
+                     otherwise, Error: Cannot initiate new request
+                     in the middle of negotiation.
+            OPPOSITE Error: Already queued an enable request.
+       WANTYES EMPTY Error: Already negotiating for enable.
+            OPPOSITE himq=EMPTY.
+            
+    We handle the option on our side by the same procedures, with DO-
+    WILL, DONT-WONT, him-us, himq-usq swapped.         
+=end
+  def handle_do
+    case @him
+    when :no
+      @us = :wantyes
+      return TELNET_WILL, @telopt
+    when :yes
+      return :error, 'Already enabled'
+    when :wantno
+      # us = :wantno_opposite # only if "buffering", whatever that means.
+      return :error, 'Request in the middle of negotiation'
+    when :wantno_opposite
+      return :error, 'Already queued request'
+    when :wantyes
+      return :error, 'Already negotiating for enable'
+    when :wantyes_opposite
+      @us = :wantyes
+      return nil, nil
+    end
+  end   
+  
+=begin
+    If we decide to ask him to disable:
+       NO            Error: Already disabled.
+       YES           him=WANTNO, send DONT.
+       WANTNO  EMPTY Error: Already negotiating for disable.
+            OPPOSITE himq=EMPTY.
+       WANTYES EMPTY If we are queueing requests, himq=OPPOSITE;
+                     otherwise, Error: Cannot initiate new request
+                     in the middle of negotiation.
+            OPPOSITE Error: Already queued a disable request.
+
+    We handle the option on our side by the same procedures, with DO-
+    WILL, DONT-WONT, him-us, himq-usq swapped.
+=end
+  def handle_dont
+    case @him
+    when :no
+      return :error, 'Already disabled'
+    when :yes
+      @us = :wantno
+      return TELNET_WONT, @telopt
+    when :wantno
+      return :error, 'Already negotiating for disable'
+    when :wantno_opposite
+      @us = :wantno
+      return nil, nil
+    when :wantyes
+      # us = :wantno_opposite # only if "buffering", whatever that means.
+      return :error, 'Request in the middle of negotiation'
+    when :wantyes_opposite
+      return :error, 'Already queued disable request'
+    end
+  end
+end
+  
+
+

+ 45 - 20
lib/serdes.rb

@@ -5,7 +5,7 @@ class Dir
     parts = name.split('/').reject { |e| e.empty? }
     parts.each do | part |
       sub <<  "/#{part}"
-      mkdir sub
+      mkdir sub rescue nil
     end
   end
 end
@@ -32,7 +32,7 @@ module Serdes
       attr_writer(name)
     end
     
-    def serdes_accessor(name)
+    def serdes_accessor(name, type = nil)
       serdes_add_to_fields(name, type)
       attr_accessor(name)
     end
@@ -63,10 +63,10 @@ module Serdes
       return @serdes_loaded[id.to_sym]
     end
 
-    def serdes_load(id)
+    def serdes_load_one(id)
       return nil unless id && !id.empty?
       
-      full_name = Serdes.serdes_dir_name(self, id) + '/' + Serdes.serdes_file_name(self, id)
+      full_name = Serdes.serdes_full_for(self, id)
       data, errors  = Sitef.load_filename(full_name)
       unless data
         log errors.join(", ")
@@ -89,10 +89,16 @@ module Serdes
         
         typevalue = nil
         
-        if type.respond_to?(:serdes_load)
-          typevalue = type.serdes_load(value)
-        elsif type && Kernel.respond_to?(type.to_sym)
-          typevalue = Kernel.send(type.to_sym, value) rescue nil 
+        if type
+          if type.respond_to?(:serdes_load)
+            typevalue = type.serdes_load(value)
+          elsif Kernel.respond_to?(type.to_sym)
+            typevalue = Kernel.send(type.to_sym, value) rescue nil 
+          elsif type.respond_to(:new)
+            typevalue = type.new(value)
+          else 
+            typevalue = value
+          end
         else
           typevalue = value
         end
@@ -107,12 +113,12 @@ module Serdes
     def serdes_fetch(id)
       res = serdes_get(id)
       return res if res
-      return serdes_load(id)
+      return serdes_load_one(id)
     end
     
-    alias :fetch :serdes_fetch
-    alias :load  :serdes_load
-    alias :get   :serdes_get
+    alias :fetch    :serdes_fetch
+    alias :load_one :serdes_load_one
+    alias :get      :serdes_get
     
     def from_serdes(id)
       return serdes_fetch(id)
@@ -120,7 +126,9 @@ module Serdes
     
     def to_serdes(value)
       return value.id.to_s
-    end  
+    end
+    
+    
   end
 
   # include callback, be sure to extend the class with the ClassMethods
@@ -128,16 +136,34 @@ module Serdes
     klass.extend(ClassMethods)
   end
   
-  def self.serdes_dir_name(klass)
-    top = klass.to_s.gsub('::', '/').downcase
+  def self.serdes_dir=(dir)
+    @serdes_dir = dir
+  end
+  
+  def self.serdes_dir
+    @serdes_dir ||= File.join(Dir.pwd, 'data', 'var')
+    @serdes_dir
+  end
+
+  
+  def self.serdes_dir_for(klass)
+    top = File.join(Serdes.serdes_dir,
+      klass.to_s.gsub('::', '/').downcase)
     return top
   end
   
-  def self.serdes_file_name(id)
+  def self.serdes_file_for(id)
     top = id.to_s.dup    
     top << '.sitef'
     return top 
   end
+  
+  def self.serdes_full_for(klass, id)
+    tdir = serdes_dir_for(klass)
+    tfil = serdes_file_for(id)
+    return File.join(tdir, tfil)
+  end
+  
 
   def serdes_data
     data = {}
@@ -157,11 +183,10 @@ module Serdes
     return data
   end
   
-  def save
-    Dir.mkdir_p Serdes.serdes_dir_name(self.class, self.id)
+  def save_one
+    Dir.mkdir_p Serdes.serdes_dir_for(self.class)
     data = serdes_data
-    full_name = Serdes.serdes_dir_name(self.class, self.id) + 
-               '/' + Serdes.serdes_file_name(self.class, self.id)
+    full_name = Serdes.serdes_full_for(self.class, self.id)
     Sitef.save_filename(full_name, [ data ] )
   end
   

+ 127 - 701
lib/telnet.rb

@@ -1,43 +1,32 @@
+require 'zlib'
+require_relative 'telnet/codes'
+require_relative 'rfc1143'
+require_relative 'monolog'
 
-# file::    telnetfilter.rb
-# author::  Jon A. Lambert
-# version:: 2.8.0
-# date::    01/19/2006
-#
-# This source code copyright (C) 2005, 2006 by Jon A. Lambert
-# All rights reserved.
-#
-# Released under the terms of the TeensyMUD Public License
-# See LICENSE file for additional information.
-#
-$:.unshift "lib" if !$:.include? "lib"
-$:.unshift "vendor" if !$:.include? "vendor"
 
-require 'strscan'
-require 'ostruct'
-require 'network/protocol/filter'
-require 'network/protocol/telnetcodes'
-require 'network/protocol/asciicodes'
 
-# 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
+# This Telnet class implements a subset of the Telnet protocol.
 #
 class Telnet
+  include Monolog
   include Telnet::Codes
 
-  # Initialize state of filter
-  def initialize(server)
-    @server     = server
-    @mode       = :normal #  Parse mode :normal, :cmd, :cr
-    @state      = {}
-    @sc         = nil
-    @sneg_opts  = [ TTYPE, ZMP ]                                
-    @ttype      = []
-    @init_tries = 0   # Number of tries at negotitating sub options
-    @synch      = false
-    log.debug "telnet filter initialized - #{@init_tries}"
+  # Allowed telnet state codes
+  STATES = [:data, :iac, :will, :wont, :do, :dont, :sb, :sb_data, :sb_data_iac]
+  
+  # Helper structs
+  Telopt = Struct.new(:telopt, :us, :him)
+
+  
+  
+  def initialize(client)
+    @client     = client
+    @telopts    = {}    # Telopt support.
+    @rfc1143    = {}    # RFC1143 support.
+    @buffer     = ""    # Subrequest buffer
+    @state      = :data # state of telnet protocol parser.
+    @sb_telopt  = nil;  # current subnegotiation
+    @compress   = false # compression state
   end
   
   # Wait for input from the server 
@@ -47,693 +36,130 @@ class Telnet
   
   # Called when client data should be filtered before being passed to the server
   def client_to_server(data)
-    
-  end
-  
-  # Called when server data should be filtered before being passed to the client
-  def server_to_client(data)
-  
-  end
-  
-  
-  
-
-  # Negotiate starting wanted options
-  #
-  # [+args+] Optional initial options
-  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
-
-  # The filter_in method filters input data
-  # [+str+]    The string to be processed
-  # [+return+] The filtered data
-  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)
+    result = ""
+    data.each_byte do | b |
+    iac    = TELNET_IAC.chr  
+      
+      case @buffer
+        when /\A#{iac}#{iac}\Z/
+        
+        # ongoing negotiation
+        when /\A#{iac}\Z/
+          return nil
         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("WOE 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(/#{IAC.chr}#{SE.chr}/).nil?
-          @sc.get_byte
-          opt = @sc.get_byte
-          data = @sc.scan_until(/#{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
-  def filter_out(str)
-    return '' if str.nil? || str.empty?
-    if !@pstack.binary_on
-      str.gsub!(/\n/, "\r\n")
-    end
-    str
-  end
-
-  ###### Custom public methods
-
-  # Test to see if option is enabled
-  # [+opt+] The Telnet option code
-  # [+who+] The side to check :us or :him
-  def enabled?(opt, who)
-    option(opt)
-    e = @state[opt].send(who)
-    e == :yes ? true : false
-  end
-
-  # Test to see which state we prefer this option to be in
-  # [+opt+] The Telnet option code
-  def desired?(opt)
-    st = @wopts[opt]
-    st = false if st.nil?
-    st
-  end
-
-  # Handle server-side echo
-  # [+ch+] character string to echo
-  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
-
-  # Negotiate starting wanted options that imply subnegotation
-  # So far only terminal type
-  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}WOE#{NUL.chr}#{Version}#{NUL.chr}A mud based on the TeensyMUD 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
+  
+  # Sends unescaped data to client, possibly compressing it if needed
+  def send_raw(buf)
+    if @compress
+      zbuf = Zlib.deflate(buf)
+    else
+      zbuf = buf
     end
+    @client.send_data(zbuf)
   end
-
-  def send_naws
-    return if !enabled?(NAWS, :us)
-    ts = @pstack.query(:termsize)
-    data = [ts[0]].pack('n') + [ts[1]].pack('n')
-    data.gsub!(/#{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
-
-private
-  ###### Private methods
-
-  def getopts(wopts)
-    # supported options
-    wopts.each do |op|
-      case op
-      when :ttype
-        @wopts[TTYPE] = true
-      when :echo
-        @wopts[ECHO] = true
-      when :sga
-        @wopts[SGA] = true
-      when :naws
-        @wopts[NAWS] = true
-      when :eorec
-        @wopts[EOREC] = true
-      when :binary
-        @wopts[BINARY] = true
-      when :zmp
-        @wopts[ZMP] = true
-      end
-    end
+  
+  # Send data to client (escapes IAC bytes) 
+  def send_escaped(buf)
+    iac = TELNET_IAC.chr
+    self.send_raw(buf.gsub("#{iac}", "#{iac}#{iac}")
   end
-
-  # parse the subnegotiation data and save it
-  # [+opt+] The Telnet option found
-  # [+data+] The data found between SB OPTION and IAC SE
-  def parse_subneg(opt,data)
-    data.gsub!(/#{IAC}#{IAC}/, IAC.chr) # 255 needs to be undoubled from all data
-    case opt
-    when NAWS
-      @pstack.twidth = data[0..1].unpack('n')[0]
-      @pstack.theight = data[2..3].unpack('n')[0]
-      @pstack.conn.publish(:termsize)
-      log.debug("(#{@pstack.conn.object_id}) Terminal width #{@pstack.twidth} / height #{@pstack.theight}")
-    when TTYPE
-      if data.getbyte(0) == 0
-        log.debug("(#{@pstack.conn.object_id}) Terminal type - #{data[1..-1]}")
-        if !@ttype.include?(data[1..-1])
-          # short-circuit choice because of Zmud
-          if data[1..-1].downcase == 'zmud'
-            @ttype << data[1..-1]
-            @pstack.terminal = 'zmud'
-            log.debug("(#{@pstack.conn.object_id}) Terminal choice - #{@pstack.terminal} in list #{@ttype.inspect}")
-          end
-          # short-circuit choice because of Windows telnet client
-          if data[1..-1].downcase == 'vt100'
-            @ttype << data[1..-1]
-            @pstack.terminal = 'vt100'
-            log.debug("(#{@pstack.conn.object_id}) Terminal choice - #{@pstack.terminal} in list #{@ttype.inspect}")
-          end
-          return if @pstack.terminal
-          @ttype << data[1..-1]
-          @pstack.conn.sendmsg(IAC.chr + SB.chr + TTYPE.chr + 1.chr + IAC.chr + SE.chr)
-        else
-          return if @pstack.terminal
-          choose_terminal
-        end
-      elsif data.getbyte(0) == 1  # send - should only be called by :client
-        return if !@pstack.terminal
-        @pstack.conn.sendmsg(IAC.chr + SB.chr + TTYPE.chr + 0.chr + @pstack.terminal + IAC.chr + SE.chr)
-      end
-    when ZMP
-      args = data.split("\0")
-      cmd = args.shift
-      handle_zmp(cmd,args)
-    end
+  
+  # Send negotiation bytes
+  
+/* send negotiation bytes */
+  def send_negotiate(cmd, telopt)
+    bytes = ""
+    bytes << TELNET_IAC
+    bytes << cmd
+    bytes << telopt
+    send_raw(bytes)
   end
-
-  # Pick a preferred terminal
-  # Order is vt100, vt999, ansi, xterm, or a recognized custom client
-  # Should not pick vtnt as we dont handle it
-  def choose_terminal
-    if @ttype.empty?
-      @pstack.terminal = "dumb"
-    end
-
-    # Pick most capable from list of terminals
-    @pstack.terminal = @ttype.find {|t| t =~ /mushclient/i } if !@pstack.terminal
-    @pstack.terminal = @ttype.find {|t| t =~ /simplemu/i } if !@pstack.terminal
-    @pstack.terminal = @ttype.find {|t| t =~ /(zmud).*/i } if !@pstack.terminal
-    @pstack.terminal = @ttype.find {|t| t =~ /linux/i } if !@pstack.terminal
-    @pstack.terminal = @ttype.find {|t| t =~ /cygwin/i } if !@pstack.terminal
-    @pstack.terminal = @ttype.find {|t| t =~ /(cons25).*/i } if !@pstack.terminal
-    @pstack.terminal = @ttype.find {|t| t =~ /(xterm).*/i } if !@pstack.terminal
-    @pstack.terminal = @ttype.find {|t| t =~  /(vt)[-]?100/i } if !@pstack.terminal
-    @pstack.terminal = @ttype.find {|t| t =~ /(vt)[-]?\d+/i } if !@pstack.terminal
-    @pstack.terminal = @ttype.find {|t| t =~ /(ansi).*/i } if !@pstack.terminal
-
-    if @pstack.terminal && @ttype.last != @pstack.terminal # short circuit retraversal of options
-      @ttype.each do |t|
-        @pstack.conn.sendmsg(IAC.chr + SB.chr + TTYPE.chr + 1.chr + IAC.chr + SE.chr)
-        break if t == @pstack.terminal
-      end
-    elsif @ttype.last != @pstack.terminal
-      @pstack.terminal = 'dumb'
-    end
-
-    @pstack.terminal.downcase!
-
-    # translate certain terminals to something meaningful
-    case @pstack.terminal
-    when /cygwin/i, /cons25/i, /linux/i, /dec-vt/i
-      @pstack.terminal = 'vt100'
-    when /ansis/i then
-      @pstack.terminal = 'ansi'
-    end
-    log.debug("(#{@pstack.conn.object_id}) Terminal set to - #{@pstack.terminal} from list #{@ttype.inspect}")
+  
+  # Check if we support a particular telopt;
+  def us_support(telopt)
+    have = @telopts[telopt] 
+    return false unless have
+    return (have.telopt == telopt) && have.us 
   end
-
-  # Get current parse mode
-  # [+return+] The current parse mode
-  def mode?
-    return @mode
+  
+  # Check if the remote supports a telopt
+  def him_support(telopt)
+    have = @telopts[telopt] 
+    return false unless have
+    return (have.telopt == telopt) && have.him 
   end
-
-  # set current parse mode
-  # [+m+] Mode to set it to
-  def set_mode(m)
-    @mode = m
+ 
+  
+  # retrieve RFC1143 option state
+  def rfc1143_get(telopt)
+    @rfc1143[telopt] 
   end
-
-  # Creates an option entry in our state table and sets its initial state
-  def option(opt)
-    return if @state.key?(opt)
-    o = OpenStruct.new
-    o.us = :no
-    o.him = :no
-    o.usq = :empty
-    o.himq = :empty
-    @state[opt] = o
+  
+  
+  # save RFC1143 option state
+  def rfc1143_set(telopt, us, him)
+    agree = we_support(telopt)
+    @rfc1143[telopt] = RFC1143.new(telopt, us, him, agree)
+    return @rfc1143[telopt]
   end
-
-  # Ask the client to enable or disable an option.
-  #
-  # [+opt+]   The option code
-  # [+enable+] true for enable, false for disable
-  def ask_him(opt, enable)
-    log.debug("(#{@pstack.conn.object_id}) Requested Telnet option #{opt.to_s} set to #{enable.to_s}")
-    initiate(opt, enable, :him)
+  
+  
+  # RFC1143 telnet option negotiation helper
+  def rfc1143_negotiate_will(rfc1143)
+    return rfc1143.handle_will
   end
-
-  # Offer the server to enable or disable an option
-  #
-  # [+opt+]   The option code
-  # [+enable+] true for enable, false for disable
-  def offer_us(opt, enable)
-    log.debug("(#{@pstack.conn.object_id}) Offered Telnet option #{opt.to_s} set to #{enable.to_s}")
-    initiate(opt, enable, :us)
+  
+  # RFC1143 telnet option negotiation helper
+  def rfc1143_negotiate_wont(rfc1143)
+     return rfc1143.handle_wont
   end
-
-  # Initiate a request to client.  Called by ask_him or offer_us.
-  #
-  # [+opt+]   The option code
-  # [+enable+] true for enable, false for disable
-  # [+who+] :him if asking client, :us if server offering
-  def initiate(opt, enable, who)
-    option(opt)
-
-    case who
-    when :him
-      willdo = DO.chr
-      wontdont = DONT.chr
-      whoq = :himq
-    when :us
-      willdo = WILL.chr
-      wontdont = WONT.chr
-      whoq = :usq
-    else
-      # Error
-    end
-
-    case @state[opt].send(who)
-    when :no
-      if enable
-        @state[opt].send("#{who}=", :wantyes)
-        @pstack.conn.sendmsg(IAC.chr + willdo + opt.chr)
-      else
-        # Error already disabled
-        log.error("(#{@pstack.conn.object_id}) Telnet negotiation: option #{opt.to_s} already disabled")
-      end
-    when :yes
-      if enable
-        # Error already enabled
-        log.error("(#{@pstack.conn.object_id}) Telnet negotiation: option #{opt.to_s} already enabled")
-      else
-        @state[opt].send("#{who}=", :wantno)
-        @pstack.conn.sendmsg(IAC.chr + wontdont + opt.chr)
-      end
-    when :wantno
-      if enable
-        case @state[opt].send(whoq)
-        when :empty
-          @state[opt].send("#{whoq}=", :opposite)
-        when :opposite
-          # Error already queued enable request
-          log.error("(#{@pstack.conn.object_id}) Telnet negotiation: option #{opt.to_s} already queued enable request")
-        end
-      else
-        case @state[opt].send(whoq)
-        when :empty
-          # Error already negotiating for disable
-          log.error("(#{@pstack.conn.object_id}) Telnet negotiation: option #{opt.to_s} already negotiating for disable")
-        when :opposite
-          @state[opt].send("#{whoq}=", :empty)
-        end
-      end
-    when :wantyes
-      if enable
-        case @state[opt].send(whoq)
-        when :empty
-          #Error already negotiating for enable
-          log.error("(#{@pstack.conn.object_id}) Telnet negotiation: option #{opt.to_s} already negotiating for enable")
-        when :opposite
-          @state[opt].send("#{whoq}=", :empty)
-        end
-      else
-        case @state[opt].send(whoq)
-        when :empty
-          @state[opt].send("#{whoq}=", :opposite)
-        when :opposite
-          #Error already queued for disable request
-          log.error("(#{@pstack.conn.object_id}) Telnet negotiation: option #{opt.to_s} already queued for disable request")
-        end
-      end
-    end
+  
+  # RFC1143 telnet option negotiation helper
+  def rfc1143_negotiate_do(rfc1143)
+    return rfc1143.handle_do
   end
-
-  # Client replies WILL or WONT
-  #
-  # [+opt+]   The option code
-  # [+enable+] true for WILL answer, false for WONT answer
-  def replies_him(opt, enable)
-    log.debug("(#{@pstack.conn.object_id}) Client replies to Telnet option #{opt.to_s} set to #{enable.to_s}")
-    response(opt, enable, :him)
+  
+  # RFC1143 telnet option negotiation helper
+  def rfc1143_negotiate_dont(rfc1143)
+    return rfc1143.handle_dont
   end
-
-  # Client requests DO or DONT
-  #
-  # [+opt+]   The option code
-  # [+enable+] true for DO request, false for DONT request
-  def requests_us(opt, enable)
-    log.debug("(#{@pstack.conn.object_id}) Client requests Telnet option #{opt.to_s} set to #{enable.to_s}")
-    response(opt, enable, :us)
+    
+  # RFC1143 telnet option negotiation helper
+  def rfc1143_negotiate(telopt)
+    q = rfc1143_get(telopt)
+    return nil, nil unless q
+    
+    case @state
+    when :will
+      return rfc1143_negotiate_will(q)    
+    when :wont
+      return rfc1143_negotiate_wont(q)    
+    when :do
+      return rfc1143_negotiate_do(q)    
+    when :dont
+      return rfc1143_negotiate_dont(q)    
+    end  
   end
-
-  # Handle client response.  Called by requests_us or replies_him
-  #
-  # [+opt+]   The option code
-  # [+enable+] true for WILL answer, false for WONT answer
-  # [+who+] :him if client replies, :us if client requests
-  def response(opt, enable, who)
-    option(opt)
-
-    case who
-    when :him
-      willdo = DO.chr
-      wontdont = DONT.chr
-      whoq = :himq
-    when :us
-      willdo = WILL.chr
-      wontdont = WONT.chr
-      whoq = :usq
+  
+  def do_negotiate(telopt)
+    res, arg = rfc1143_negotiate(telopt)
+    return unless res
+    if res == :error
+      log_error(arg)
     else
-      # Error
-    end
-
-    case @state[opt].send(who)
-    when :no
-      if enable
-        if desired?(opt)
-        # If we agree
-          @state[opt].send("#{who}=", :yes)
-          @pstack.conn.sendmsg(IAC.chr + willdo + opt.chr)
-          log.debug("(#{@pstack.conn.object_id}) Telnet negotiation: agreed to enable option #{opt}")
-        else
-        # If we disagree
-          @pstack.conn.sendmsg(IAC.chr + wontdont + opt.chr)
-          log.debug("(#{@pstack.conn.object_id}) Telnet negotiation: disagreed to enable option #{opt}")
-        end
-      else
-        # Ignore
-      end
-    when :yes
-      if enable
-        # Ignore
-      else
-        @state[opt].send("#{who}=", :no)
-        @pstack.conn.sendmsg(IAC.chr + wontdont + opt.chr)
-      end
-    when :wantno
-      if enable
-        case @state[opt].send(whoq)
-        when :empty
-          #Error DONT/WONT answered by WILL/DO
-          @state[opt].send("#{who}=", :no)
-        when :opposite
-          #Error DONT/WONT answered by WILL/DO
-          @state[opt].send("#{who}=", :yes)
-          @state[opt].send("#{whoq}=", :empty)
-        end
-        log.error("(#{@pstack.conn.object_id}) Telnet negotiation: option #{opt.to_s} DONT/WONT answered by WILL/DO")
-      else
-        case @state[opt].send(whoq)
-        when :empty
-          @state[opt].send("#{who}=", :no)
-          log.debug("(#{@pstack.conn.object_id}) Telnet negotiation: agreed to disable option #{opt}")
-        when :opposite
-          @state[opt].send("#{who}=", :wantyes)
-          @state[opt].send("#{whoq}=", :empty)
-          @pstack.conn.sendmsg(IAC.chr + willdo + opt.chr)
-        end
-      end
-    when :wantyes
-      if enable
-        case @state[opt].send(whoq)
-        when :empty
-          @state[opt].send("#{who}=", :yes)
-          log.debug("(#{@pstack.conn.object_id}) Telnet negotiation: agreed to enable option #{opt}")
-        when :opposite
-          @state[opt].send("#{who}=", :wantno)
-          @state[opt].send("#{whoq}=", :empty)
-          @pstack.conn.sendmsg(IAC.chr + wontdont + opt.chr)
-        end
-      else
-        case @state[opt].send(whoq)
-        when :empty
-          @state[opt].send("#{who}=", :no)
-          log.debug("(#{@pstack.conn.object_id}) Telnet negotiation: agreed to disable option #{opt}")
-        when :opposite
-          @state[opt].send("#{who}=", :no)
-          @state[opt].send("#{whoq}=", :empty)
-        end
-      end
+      send_negotiate(res, arg)
     end
   end
-
-  def handle_zmp(cmd,args)
-    log.debug("(#{@pstack.conn.object_id}) ZMP command recieved - '#{cmd}' args: #{args.inspect}" )
-    case cmd
-    when "zmp.ping"
-      @pstack.conn.sendmsg("#{IAC.chr}#{SB.chr}#{ZMP.chr}" +
-        "zmp.time#{NUL.chr}#{Time.now.utc.strftime("%Y-%m-%d %H:%M:%S")}#{NUL.chr}" +
-        "#{IAC.chr}#{SE.chr}")
-    when "zmp.time"
-    when "zmp.ident"
-      # That's nice
-    when "zmp.check"
-      case args[0]
-      when /zmp.*/
-      # We support all 'zmp.' package and commands so..
-        @pstack.conn.sendmsg("#{IAC.chr}#{SB.chr}#{ZMP.chr}" +
-          "zmp.support#{NUL.chr}#{args[0]}{NUL.chr}" +
-          "#{IAC.chr}#{SE.chr}")
-      else
-        @pstack.conn.sendmsg("#{IAC.chr}#{SB.chr}#{ZMP.chr}" +
-          "zmp.no-support#{NUL.chr}#{args[0]}#{NUL.chr}" +
-          "#{IAC.chr}#{SE.chr}")
-      end
-    when "zmp.support"
-    when "zmp.no-support"
-    when "zmp.input"
-      # Now we just simply pass this whole load to the Character.parse
-      # WARN: This means there is a possibility of out-of-order processing
-      #       of @inbuffer, though extremely unlikely.
-      @pstack.conn.publish(args[0])
-    end
+  
+  
+  
+  # Called when server data should be filtered before being passed to the client
+  def server_to_client(data)
+  
   end
-
 end

+ 94 - 96
lib/telnet/codes.rb

@@ -2,109 +2,107 @@
 # Based on code by Jon A. Lambert,  under the Zlib license.
  
 module Telnet
+  module Codes
+    # Telnet commands
+    TELNET_IAC = 255
+    TELNET_DONT = 254
+    TELNET_DO = 253
+    TELNET_WONT = 252
+    TELNET_WILL = 251
+    TELNET_SB = 250
+    TELNET_GA = 249
+    TELNET_EL = 248
+    TELNET_EC = 247
+    TELNET_AYT = 246
+    TELNET_AO = 245
+    TELNET_IP = 244
+    TELNET_BREAK = 243
+    TELNET_DM = 242
+    TELNET_NOP = 241
+    TELNET_SE = 240
+    TELNET_EOR = 239
+    TELNET_ABORT = 238
+    TELNET_SUSP = 237
+    TELNET_EOF = 236
 
-module Codes
+    # Telnet options.
+    TELNET_TELOPT_BINARY = 0
+    TELNET_TELOPT_ECHO = 1
+    TELNET_TELOPT_RCP = 2
+    TELNET_TELOPT_SGA = 3
+    TELNET_TELOPT_NAMS = 4
+    TELNET_TELOPT_STATUS = 5
+    TELNET_TELOPT_TM = 6
+    TELNET_TELOPT_RCTE = 7
+    TELNET_TELOPT_NAOL = 8
+    TELNET_TELOPT_NAOP = 9
+    TELNET_TELOPT_NAOCRD = 10
+    TELNET_TELOPT_NAOHTS = 11
+    TELNET_TELOPT_NAOHTD = 12
+    TELNET_TELOPT_NAOFFD = 13
+    TELNET_TELOPT_NAOVTS = 14
+    TELNET_TELOPT_NAOVTD = 15
+    TELNET_TELOPT_NAOLFD = 16
+    TELNET_TELOPT_XASCII = 17
+    TELNET_TELOPT_LOGOUT = 18
+    TELNET_TELOPT_BM = 19
+    TELNET_TELOPT_DET = 20
+    TELNET_TELOPT_SUPDUP = 21
+    TELNET_TELOPT_SUPDUPOUTPUT = 22
+    TELNET_TELOPT_SNDLOC = 23
+    TELNET_TELOPT_TTYPE = 24
+    TELNET_TELOPT_EOR = 25
+    TELNET_TELOPT_TUID = 26
+    TELNET_TELOPT_OUTMRK = 27
+    TELNET_TELOPT_TTYLOC = 28
+    TELNET_TELOPT_3270REGIME = 29
+    TELNET_TELOPT_X3PAD = 30
+    TELNET_TELOPT_NAWS = 31
+    TELNET_TELOPT_TSPEED = 32
+    TELNET_TELOPT_LFLOW = 33
+    TELNET_TELOPT_LINEMODE = 34
+    TELNET_TELOPT_XDISPLOC = 35
+    TELNET_TELOPT_ENVIRON = 36
+    TELNET_TELOPT_AUTHENTICATION = 37
+    TELNET_TELOPT_ENCRYPT = 38
+    TELNET_TELOPT_NEW_ENVIRON = 39
+    TELNET_TELOPT_MSDP = 69
+    TELNET_TELOPT_MSSP = 70
+    TELNET_TELOPT_COMPRESS = 85
+    TELNET_TELOPT_COMPRESS2 = 86
+    TELNET_TELOPT_MSP = 90
+    TELNET_TELOPT_MXP = 91
+    TELNET_TELOPT_MSP2 = 92
+    TELNET_TELOPT_MSP2_MUSIC = 0
+    TELNET_TELOPT_MSP2_SOUND = 1
 
-  IAC = 255  # Command  - RFC 854, 855, 1123, 1143
 
-  # 2 byte commands
-  WILL = 251  # Will do option
-  WONT = 252  # Wont do option
-  DO   = 253  # Do option
-  DONT = 254  # Dont do option
 
+    TELNET_TELOPT_ZMP = 93
+    TELNET_TELOPT_EXOPL = 255
 
-  SB = 250  # Subnegotiation begin # IAC SB <option> <parameters> IAC SE
-  SE = 240  # Subnegotiation end
+    TELNET_TELOPT_MCCP2 = 86
 
-  # 1 byte commands
-  GA    = 249  # Go Ahead
-  NOP   = 241  # No-op
-  BRK   = 243  # Break
+    # TERMINAL-TYPE codes. 
+    TELNET_TTYPE_IS = 0
+    TELNET_TTYPE_SEND = 1
 
-  # In RFC 854
-  AYT   = 246  # Are you there?
-  AO    = 245  # abort output
-  IP    = 244  # interrupt
-  EL    = 248  # erase current line
-  EC    = 247  # erase current character
+    # NEW-ENVIRON/ENVIRON codes. 
+    TELNET_ENVIRON_IS = 0
+    TELNET_ENVIRON_SEND = 1
+    TELNET_ENVIRON_INFO = 2
+    TELNET_ENVIRON_VAR = 0
+    TELNET_ENVIRON_VALUE = 1
+    TELNET_ENVIRON_ESC = 2
+    TELNET_ENVIRON_USERVAR = 3
 
-  DM    = 242  # data mark - sent to demarcate end of urgent commands
+    # MSSP codes. 
+    TELNET_MSSP_VAR = 1
+    TELNET_MSSP_VAL = 2
 
-  EOR   = 239 # end of record (transparent mode)
-  ABORT = 238 # Abort process
-  SUSP  = 237 # Suspend process
-  EOF   = 236 # End of file
-
-  # Options
-  BINARY         =   0 # Transmit Binary - RFC 856
-  ECHO           =   1 # Echo - RFC 857
-  RCP            =   2 # Reconnection
-  SGA            =   3 # Suppress Go Ahead - RFC 858
-  NAMS           =   4 # Approx Message Size Negotiation
-  STATUS         =   5 # Status - RFC 859
-  TM             =   6 # Timing Mark - RFC 860
-  RCTE           =   7 # Remote Controlled Trans and Echo - RFC 563, 726
-  NAOL           =   8 # Output Line Width
-  NAOP           =   9 # Output Page Size
-  NAOCRD         =  10 # Output Carriage-Return Disposition - RFC 652
-  NAOHTS         =  11 # Output Horizontal Tab Stops - RFC 653
-  NAOHTD         =  12 # Output Horizontal Tab Disposition - RFC 654
-  NAOFFD         =  13 # Output Formfeed Disposition - RFC 655
-  NAOVTS         =  14 # Output Vertical Tabstops - RFC 656
-  NAOVTD         =  15 # Output Vertical Tab Disposition - RFC 657
-  NAOLFD         =  16 # Output Linefeed Disposition - RFC 658
-  XASCII         =  17 # Extended ASCII - RFC 698
-  LOGOUT         =  18 # Logout - RFC 727
-  BM             =  19 # Byte Macro - RFC 735
-  DET            =  20 # Data Entry Terminal - RFC 732, 1043
-  SUPDUP         =  21 # SUPDUP - RFC 734, 736
-  SUPDUPOUTPUT   =  22 # SUPDUP Output - RFC 749
-  SNDLOC         =  23 # Send Location - RFC 779
-  TTYPE          =  24 # Terminal Type - RFC 1091
-  EOREC          =  25 # End of Record - RFC 885
-  TUID           =  26 # TACACS User Identification - RFC 927
-  OUTMRK         =  27 # Output Marking - RFC 933
-  TTYLOC         =  28 # Terminal Location Number - RFC 946
-  REGIME3270     =  29 # Telnet 3270 Regime - RFC 1041
-  X3PAD          =  30 # X.3 PAD - RFC 1053
-  NAWS           =  31 # Negotiate About Window Size - RFC 1073
-  TSPEED         =  32 # Terminal Speed - RFC 1079
-  LFLOW          =  33 # Remote Flow Control - RFC 1372
-  LINEMODE       =  34 # Linemode - RFC 1184
-  XDISPLOC       =  35 # X Display Location - RFC 1096
-  ENVIRON        =  36 # Environment Option - RFC 1408
-  AUTHENTICATION =  37 # Authentication Option - RFC 1416, 2941, 2942, 2943, 2951
-  ENCRYPT        =  38 # Encryption Option - RFC 2946
-  NEW_ENVIRON    =  39 # New Environment Option - RFC 1572
-  TN3270         =  40 # TN3270 Terminal Entry - RFC 2355
-  XAUTH          =  41 # XAUTH
-  CHARSET        =  42 # Charset option - RFC 2066
-  RSP            =  43 # Remote Serial Port
-  CPCO           =  44 # COM port Control Option - RFC 2217
-  SUPLECHO       =  45 # Suppress Local Echo
-  TLS            =  46 # Telnet Start TLS
-  KERMIT         =  47 # Kermit tranfer Option - RFC 2840
-  SENDURL        =  48 # Send URL
-  FORWARDX       =  49 # Forward X
-  PLOGON         = 138 # Telnet Pragma Logon
-  SSPI           = 139 # Telnet SSPI Logon
-  PHEARTBEAT     = 140 # Telnat Pragma Heartbeat
-  EXOPL          = 255 # Extended-Options-List - RFC 861
-
-  MSDP     = 69  # Mud Server Data Protocol
-  MSSP     = 70  # MUD Server Status Protocol
-  COMPRESS =  85 # MCCP 1 support (broken deprecated)
-  COMPRESS2 = 86 # MCCP 2 support
-  MSP  = 90 # MSP  support
-  MXP  = 91 # MUD eXtension Protocol (MXP)
-  MSP2 = 92 # MSP2 support
-    MUSIC = 0
-    SOUND = 1
-
-  ZMP = 93 # ZMP support
-  AARD = 102 # Aardwolf client protocol (deprecated?)
-  MULTIPLEX = 112 # Crystal client telnet multiplex 
-  ATCP = 200 # Achaea telnet client protocol
-  GMCP = 201 # GMCP/ATCP2 client protocol
+    # newline, cr and nul
+    TELNET_CR = 13
+    TELNET_NL = 10
+    TELNET_NUL = 0
+  end
 end

+ 12 - 3
lib/woe/server.rb

@@ -19,13 +19,22 @@ module Woe
       @fiber     = nil
     end
     
+    def get_free_client_id
+      cli = 0
+      @clients.each do |client|
+        return cli if client.nil?
+        cli += 1
+      end
+      return cli
+    end
+    
     def start() 
       log_info("Server listening on port #@port")
       @signature = EventMachine.start_server("0.0.0.0", @port, Client) do |client|
-        @client_id          += 1
-        client.id            = @client_id
+        client_id            = get_free_client_id
+        client.id            = client_id
         client.server        = self   
-        @clients[@client_id] = client
+        @clients[client_id]  = client
       end
       EventMachine.add_periodic_timer(1) do 
         @tick_id            += 1

+ 71 - 0
lib/woe/settings.rb

@@ -0,0 +1,71 @@
+
+require 'singleton'
+require 'optparse'
+
+VERSION = '0.1.0'
+
+module Woe
+  class Settings
+    include Singleton
+    
+    attr_reader :data_dir
+    attr_reader :port
+    
+    def initialize
+      @data_dir = 'data'
+      @port     = 7000
+    end
+    
+    def var_dir
+      File.join(@data_dir, 'var')
+    end
+    
+    def script_dir
+      File.join(@data_dir, 'script')
+    end
+    
+    def parse_args    
+      oparser = OptionParser.new do |opt|
+        opt.banner = "Usage woe [options]"
+        opt.on('-p', '--port [PORT]',  "Port at which the WOE server should listen.") do |  port |
+          @port = port.to_i
+        end 
+        opt.on('-d', '--data [DIR]', "Directory where the data and scripts of the server are installed.") do | dir |
+          @data_dir = dir
+        end   
+        opt.on('-h', '--help', "Show this message.") do
+          puts opt
+          exit
+        end
+        opt.on('-v', '--version', "Show the version of WOE.") do
+          puts VERSION      
+          exit
+        end  
+      end      
+      oparser.parse!    
+    end
+    
+    def self.parse_args
+      self.instance.parse_args
+    end
+    
+    def self.port
+      self.instance.port
+    end
+    
+    def self.data_dir
+      self.instance.data_dir
+    end
+    
+    def self.var_dir
+      self.instance.var_dir
+    end
+    
+    def self.script_dir
+      self.instance.script_dir
+    end    
+  end  # class
+end # module Woe
+  
+  
+

+ 29 - 0
test/test_rfc1143.rb

@@ -0,0 +1,29 @@
+require 'atto'
+include Atto::Test
+
+require_relative '../lib/rfc1143' 
+
+assert { RFC1143 }
+
+sm = RFC1143.new(:echo, :no, :no, true)
+
+assert { sm }
+assert { sm.telopt  == :echo }
+assert { sm.us      == :no }
+assert { sm.him     == :no }
+assert { sm.agree   == true }
+
+assert do 
+  sm = RFC1143.new(:echo, :no, :no, true)
+  res, arg = sm.handle_will
+  res == :send_do
+  arg == :echo
+end
+
+assert do 
+  sm = RFC1143.new(:echo, :no, :no, false)
+  res, arg = sm.handle_will
+  res == :send_dont
+  arg == :echo
+end
+

+ 34 - 0
test/test_serdes.rb

@@ -0,0 +1,34 @@
+
+require 'atto'
+include Atto::Test
+
+require_relative '../lib/sitef' 
+require_relative '../lib/serdes' 
+
+class Try
+  include Serdes
+  
+  serdes_reader   :id  
+  serdes_accessor :foo
+  serdes_reader   :bar
+  serdes_reader   :hp, :Integer
+  
+end
+
+@t = nil
+
+assert do 
+  @t = Try.new(:id => 'try79', :foo => 'fooo', :bar => "bar\nbar\bar", :hp => 45) 
+  @t
+end
+
+assert do
+  @t.save_one
+end
+
+assert do 
+  @tl = Try.load_one('try79')  
+  @tl && @tl.id == 'try79' && @tl.foo == @t.foo && @tl.bar == @t.bar && @tl.hp == @t.hp
+end
+
+

+ 35 - 0
test/woe/test_settings.rb

@@ -0,0 +1,35 @@
+require 'atto'
+include Atto::Test
+
+require_relative '../../lib/woe/settings' 
+
+assert { Woe::Settings }
+
+assert { Woe::Settings.port         == 7000           }
+assert { Woe::Settings.data_dir     == 'data'         }
+assert { Woe::Settings.var_dir      == 'data/var'     }
+assert { Woe::Settings.script_dir   == 'data/script'  }
+
+ARGV << '-p'
+ARGV << '7777'
+
+ARGV << '-d'
+ARGV << '/var/woe/data'
+
+p ARGV
+
+assert { Woe::Settings.parse_args }
+
+
+assert { Woe::Settings.port         == 7777           }
+assert { Woe::Settings.data_dir     == '/var/woe/data'         }
+assert { Woe::Settings.var_dir      == '/var/woe/data/var'     }
+assert { Woe::Settings.script_dir   == '/var/woe/data/script'  }
+
+
+# Undefining class methods
+# ec = class << Kernel ; self; end
+# (Kernel.methods - BasicObject.methods).each do |m| ; ec.class_eval { remove_method m } ; end
+# Removing constants
+# Object.instance_eval { remove_const :ARGF }
+

+ 48 - 56
woe.geany

@@ -23,62 +23,54 @@ long_line_behaviour=1
 long_line_column=80
 
 [files]
-current_page=38
-FILE_NAME_0=32;C;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fsrc%2Fmain.c;0;2
-FILE_NAME_1=1652;C;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fsrc%2Fcallrb.c;0;2
-FILE_NAME_2=10930;C;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fsrc%2Ftoruby.c;0;2
-FILE_NAME_3=1133;C;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fsrc%2Ftr_file.c;0;2
-FILE_NAME_4=11178;C;0;EUTF-8;0;1;0;%2Fusr%2Flocal%2Finclude%2Fmruby.h;0;2
-FILE_NAME_5=15634;C;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fsrc%2Fserver.c;0;2
-FILE_NAME_6=4816;C;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fsrc%2Flibtelnet.c;0;2
-FILE_NAME_7=2640;C;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fsrc%2Fclient.c;0;2
-FILE_NAME_8=8011;C;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Flibtelnet%2Futil%2Ftelnet-chatd.c;0;2
-FILE_NAME_9=158;C;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fsrc%2Fconfig.c;0;2
-FILE_NAME_10=154;C;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Finclude%2Fclient.h;0;2
-FILE_NAME_11=240;C;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Finclude%2Fconfig.h;0;2
-FILE_NAME_12=3599;C;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Finclude%2Fesh.h;0;2
-FILE_NAME_13=3217;C;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fsrc%2Fesh.c;0;2
-FILE_NAME_14=697;None;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2FTupfile;0;2
-FILE_NAME_15=1810;C;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Finclude%2Frh.h;0;2
-FILE_NAME_16=7445;C;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Finclude%2Ftr_macro.h;0;2
-FILE_NAME_17=1250;C;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fsrc%2Frh.c;0;2
-FILE_NAME_18=235;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fscript%2Fmain.rb;0;2
-FILE_NAME_19=602;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fscript%2Fsecurity.rb;0;2
-FILE_NAME_20=1315;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fscript%2Fserdes.rb;0;2
-FILE_NAME_21=521;None;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fvar%2Ftry.sitef;0;2
-FILE_NAME_22=143;Markdown;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fureqma%2FREADME.md;0;2
-FILE_NAME_23=341;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fureqma%2Fureqma;0;2
-FILE_NAME_24=348;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fscript%2Faccount.rb;0;2
-FILE_NAME_25=3275;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fscript%2Fsitef.rb;0;2
-FILE_NAME_26=152;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fscript%2Fmotd.rb;0;2
-FILE_NAME_27=35;None;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fvar%2Faccount%2FD%2FDyon%2FDyon.account;0;2
-FILE_NAME_28=40;None;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fvar%2Faccount%2FB%2FBeoran%2FBeoran.account;0;2
-FILE_NAME_29=29;None;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fvar%2Faccount%2Fa%2Faxl%2Faxl.account;0;2
-FILE_NAME_30=18;None;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fvar%2Fmotd;0;2
-FILE_NAME_31=0;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fscript%2Fcharacter.rb;0;2
-FILE_NAME_32=137;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fscript%2Flog.rb;0;2
-FILE_NAME_33=382;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fscript%2Fclient.rb;0;2
-FILE_NAME_34=245;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fscript%2Fmode.rb;0;2
-FILE_NAME_35=414;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fscript%2Fmode%2Fcharacter.rb;0;2
-FILE_NAME_36=669;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fscript%2Fmode%2Fsetup.rb;0;2
-FILE_NAME_37=6413;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Farch%2Fdl%2Fmud%2Frelease%2Ftmud-3.0.0%2Ftclient.rb;0;2
-FILE_NAME_38=2622;YAML;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Farch%2Fdl%2Fmud%2Frelease%2Ftmud-3.0.0%2Fconfig.yaml;0;2
-FILE_NAME_39=2069;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fscript%2Fmode%2Flogin.rb;0;2
-FILE_NAME_40=0;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fscript%2Fmode%2Fnormal.rb;0;2
-FILE_NAME_41=3438;Sh;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fwork%2Febp2git%2Febp_make_gitignore;0;2
-FILE_NAME_42=643;Sh;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fwork%2Febp2git%2Febp_make_all_gitignores;0;2
-FILE_NAME_43=1452;None;0;EUTF-8;0;1;0;%2Fusers%2Fnmbs_ebp%2Fr17%2F.gitignore;0;2
-FILE_NAME_44=867;C;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Finclude%2Ftimer.h;0;2
-FILE_NAME_45=2864;C;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fsrc%2Ftimer.c;0;2
-FILE_NAME_46=60;C;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Finclude%2Fmem.h;0;2
-FILE_NAME_47=426;C;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Finclude%2Fstate.h;0;2
-FILE_NAME_48=61;C;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fsrc%2Fmonolog.c;0;2
-FILE_NAME_49=17892;C;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Finclude%2Flibtelnet.h;0;2
-FILE_NAME_50=1774;C;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Finclude%2Fmonolog.h;0;2
-FILE_NAME_51=235;C;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Finclude%2Fdynar.h;0;2
-FILE_NAME_52=103;C;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fsrc%2Fmem.c;0;2
-FILE_NAME_53=5343;C;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fsrc%2Fdynar.c;0;2
-FILE_NAME_54=2919;C;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Finclude%2Fserver.h;0;2
+current_page=33
+FILE_NAME_0=11178;C;0;EUTF-8;0;1;0;%2Fusr%2Flocal%2Finclude%2Fmruby.h;0;2
+FILE_NAME_1=8011;C;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Flibtelnet%2Futil%2Ftelnet-chatd.c;0;2
+FILE_NAME_2=697;None;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2FTupfile;0;2
+FILE_NAME_3=235;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fscript%2Fmain.rb;0;2
+FILE_NAME_4=602;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fscript%2Fsecurity.rb;0;2
+FILE_NAME_5=1315;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fscript%2Fserdes.rb;0;2
+FILE_NAME_6=521;None;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fvar%2Ftry.sitef;0;2
+FILE_NAME_7=143;Markdown;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fureqma%2FREADME.md;0;2
+FILE_NAME_8=341;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fureqma%2Fureqma;0;2
+FILE_NAME_9=348;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fscript%2Faccount.rb;0;2
+FILE_NAME_10=3275;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fscript%2Fsitef.rb;0;2
+FILE_NAME_11=152;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fscript%2Fmotd.rb;0;2
+FILE_NAME_12=35;None;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fvar%2Faccount%2FD%2FDyon%2FDyon.account;0;2
+FILE_NAME_13=40;None;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fvar%2Faccount%2FB%2FBeoran%2FBeoran.account;0;2
+FILE_NAME_14=29;None;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fvar%2Faccount%2Fa%2Faxl%2Faxl.account;0;2
+FILE_NAME_15=18;None;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fvar%2Fmotd;0;2
+FILE_NAME_16=0;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fscript%2Fcharacter.rb;0;2
+FILE_NAME_17=137;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fscript%2Flog.rb;0;2
+FILE_NAME_18=382;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fscript%2Fclient.rb;0;2
+FILE_NAME_19=245;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fscript%2Fmode.rb;0;2
+FILE_NAME_20=414;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fscript%2Fmode%2Fcharacter.rb;0;2
+FILE_NAME_21=669;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fscript%2Fmode%2Fsetup.rb;0;2
+FILE_NAME_22=6413;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Farch%2Fdl%2Fmud%2Frelease%2Ftmud-3.0.0%2Ftclient.rb;0;2
+FILE_NAME_23=2622;YAML;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Farch%2Fdl%2Fmud%2Frelease%2Ftmud-3.0.0%2Fconfig.yaml;0;2
+FILE_NAME_24=0;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fdata%2Fscript%2Fmode%2Fnormal.rb;0;2
+FILE_NAME_25=814;Sh;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fwork%2Febp2git%2Febp_make_all_gitignores;0;2
+FILE_NAME_26=3707;Sh;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fwork%2Febp2git%2Febp_make_gitignore;0;2
+FILE_NAME_27=1452;None;0;EUTF-8;0;1;0;%2Fusers%2Fnmbs_ebp%2Fr17%2F.gitignore;0;2
+FILE_NAME_28=0;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Flib%2Fmonolog.rb;0;2
+FILE_NAME_29=0;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Flib%2Fsitef.rb;0;2
+FILE_NAME_30=1;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Flib%2Ftelnet.rb;0;2
+FILE_NAME_31=3786;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Flib%2Fserdes.rb;0;2
+FILE_NAME_32=2505;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fwork%2Fatw%2Fscrapeme%2Fatw_logs_scraper.rb;0;2
+FILE_NAME_33=1362;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Flib%2Fwoe%2Fsettings.rb;0;2
+FILE_NAME_34=30175567;None;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fwork%2Fatw%2Fscrapeme%2Fatw2-prod_20150622093131.csv;0;2
+FILE_NAME_35=252;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Flib%2Fwoe%2Faccount.rb;0;2
+FILE_NAME_36=0;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Flib%2Fwoe%2Fansi.rb;0;2
+FILE_NAME_37=3492;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Flib%2Fwoe%2Fclient.rb;0;2
+FILE_NAME_38=0;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Flib%2Fwoe%2Fgserver.rb;0;2
+FILE_NAME_39=0;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Flib%2Fwoe%2Flog.rb;0;2
+FILE_NAME_40=902;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Flib%2Fwoe%2Fserver.rb;0;2
+FILE_NAME_41=0;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Ftest%2Ftest_monolog.rb;0;2
+FILE_NAME_42=0;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Ftest%2Ftest_sitef.rb;0;2
+FILE_NAME_43=352;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Ftest%2Fwoe%2Ftest_server.rb;0;2
+FILE_NAME_44=11;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fatto%2Flib%2Fatto%2Frun.rb;0;2
+FILE_NAME_45=1597;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fatto%2Flib%2Fatto%2Ftest.rb;0;2
+FILE_NAME_46=0;Ruby;0;EUTF-8;0;1;0;%2Fhome%2Fbjorn%2Fsrc%2Fwoe%2Fbin%2Fwoe;0;2
 
 [VTE]
 last_dir=/home/bjorn/src/woe