Browse Source

More on start to try out crystal.

Beoran 7 years ago
parent
commit
e9e86485a0
7 changed files with 1 additions and 1460 deletions
  1. 0 184
      lib/monolog.rb
  2. 0 264
      lib/rfc1143.rb
  3. 0 26
      lib/security.rb
  4. 0 204
      lib/serdes.rb
  5. 0 173
      lib/sitef.rb
  6. 0 608
      lib/telnet.rb
  7. 1 1
      shard.yml

+ 0 - 184
lib/monolog.rb

@@ -1,184 +0,0 @@
-
-# Monolog, an easy to use logger for ruby 
-module Monolog 
-  
-  module Logger
-    attr_reader :data
-    
-    def initialize(data = nil)
-      @data       = data
-    end 
-    
-    def log(file, line, name, level, format, *args)
-    end
-    
-    def close
-      @data.close if @data && @data.respond_to?(:close)
-    end
-    
-  end
-  
-  class FileLogger 
-    include Logger
-    def initialize(filename)
-      @data = File.open(filename, "at")
-    end
-    
-    def log(file, line, name, format, *args)
-      @data.printf("%s: %s: %s: %d: ", Time.now.to_s, name, file, line)
-      @data.printf(format, *args)
-      @data.printf("\n")
-    end
-  end
-  
-  class StdinLogger < FileLogger
-    def initialize
-       @data = $stdin
-    end
-  end
-    
-  class StderrLogger < FileLogger
-    def initialize
-       @data = $stderr
-    end
-  end
-         
-  class Log 
-    attr_reader :loggers
-    attr_reader :levels
-    
-    def initialize
-      @loggers = []
-      @levels  = {} 
-    end
-    
-    def add_logger(logger)  
-      @loggers << logger
-    end
-    
-    def enable_level(name)
-      @levels[name.to_sym] = true 
-    end
-    
-    def disable_level(name)
-      @levels[name.to_sym] = false 
-    end
-    
-    def log_va(file, line, name, format, *args)
-      level = @levels[name.to_sym]
-      return nil unless level   
-      @loggers.each do | logger |
-        logger.log(file, line, name, format, *args)
-      end
-    end
-    
-    def close
-      @loggers.each do | logger |
-        logger.close()
-      end
-    end
-  end
-  
-  def self.setup
-    @log = Log.new
-  end
-  
-  def self.get_log
-    return @log
-  end
-  
-  def self.setup_all(name = nil, err = true, out = false) 
-    setup
-    add_stderr_logger if err
-    add_stdout_logger if out    
-    add_file_logger(name) if name
-    enable_level(:INFO)
-    enable_level(:WARNING)
-    enable_level(:ERROR)
-    enable_level(:FATAL)
-  end
-  
-  def self.enable_level(l)
-    @log ||= nil
-    return unless @log
-    @log.enable_level(l)
-  end
-
-  def self.disable_level(l)      
-    @log ||= nil
-    return unless @log
-    @log.disable_level(l)
-  end
-  
-  def self.add_logger(l)
-    @log ||= nil
-    return unless @log
-    @log.add_logger(l)
-  end
-  
-  def self.add_stdin_logger
-    self.add_logger(StdinLogger.new)
-  end
-  
-  def self.add_stderr_logger
-    self.add_logger(StderrLogger.new)
-  end
-  
-  def self.add_file_logger(filename = "log.log")
-    self.add_logger(FileLogger.new(filename))
-  end
-  
-  def self.close
-    @log ||= nil
-    return unless @log
-    @log.close
-  end
-
-  
-  def self.log_va(file, line, name, format, *args)
-    @log ||= nil
-    return unless @log
-    @log.log_va(file, line, name, format, *args)
-  end
-  
-  def log(name, format, * args)
-    file, line, fun = caller.first.to_s.split(':')
-    Monolog.log_va(file, line, name, format, *args)
-  end
-  
-  def log_error(format, *args)
-    file, line, fun = caller.first.to_s.split(':')
-    Monolog.log_va(file, line, :ERROR, format, *args)
-  end
-
-  def log_warning(format, *args)
-    file, line, fun = caller.first.to_s.split(':')
-    Monolog.log_va(file, line, :WARNING, format, *args)
-  end
-
-  def log_info(format, *args)
-    file, line, fun = caller.first.to_s.split(':')
-    Monolog.log_va(file, line, :INFO, format, *args)
-  end
-
-  def log_debug(format, *args)
-    file, line, fun = caller.first.to_s.split(':')
-    Monolog.log_va(file, line, :DEBUG, format, *args)
-  end
-  
-  def log_fatal(format, *args)
-    file, line, fun = caller.first.to_s.split(':')
-    Monolog.log_va(file, line, :FATAL, format, *args)
-  end
-  
-  alias error log_error
-  alias warn log_warning
-  alias info log_info
-  alias error log_error
-  
-  extend(self)
- end
- 
- 
- 
- 

+ 0 - 264
lib/rfc1143.rb

@@ -1,264 +0,0 @@
-
-require_relative 'telnet/codes'
-
-
-# rfc1143 state machine for telnet protocol
-# Thehandle_ functions handle input,
-# the send_ functions are for sending negotiations
-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
-  
-  # advertise willingess to support an option 
-  def send_will
-    case @us
-    when :no
-      @us = :wantyes
-      return TELNET_WILL, @telopt
-    when :wantno
-      @us = :wantno_opposite
-    when :wantyes_opposite
-      @us = :wantyes
-    else
-      return nil, nil
-    end
-  end
-  
-  # force turn-off of locally enabled option
-  def send_wont
-    case @us
-    when :yes
-      @us = :wantno
-      return TELNET_WONT, @telopt
-    when :wantno_opposite
-      @us = :wantno
-      return nil, nil
-    when :wantyes
-      @us = :wantyes_opposite
-      return nil, nil
-    else
-      return nil, nil
-    end
-  end   
-
-  # ask remote end to enable an option
-  def send_do
-    case @him
-    when :no
-      @him = :wantyes
-      return TELNET_DO, @telopt
-    when :wantno
-      @him = :wantno_opposite
-      return nil, nil
-    when :wantyes_opposite
-      @us = :wantyes
-      return nil, nil
-    else
-      return nil, nil
-    end
-  end
-
-
-  # demand remote end disable an option
-  def send_dont
-    case @him
-    when :yes
-      @him = :wantno
-      return TELNET_DONT, @telopt
-    when :wantno_opposite
-      @him = :wantno
-      return nil, nil
-    when :wantyes
-      @him = :wantyes_opposite
-    else
-      return nil, nil
-    end
-  end
-end
-  
-
-
-

+ 0 - 26
lib/security.rb

@@ -1,26 +0,0 @@
-#
-# Woe security related helper functions. 
-#
-
-
-CRYPT_MAKE_SALT_AID = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./"
-
-# Generates salt for use by crypt.
-def crypt_make_salt  
-  c1 = CRYPT_MAKE_SALT_AID[rand(CRYPT_MAKE_SALT_AID.length)] 
-  c2 = CRYPT_MAKE_SALT_AID[rand(CRYPT_MAKE_SALT_AID.length)] 
-  return c1 + c2
-end
-
-# Crypt with salt generation.
-def crypt(pass, salt = nil) 
-  salt = crypt_make_salt unless salt
-  return pass.to_s.crypt(salt)
-end
-
-# Challenge crypt password trypass against the hash hash
-def crypt_challenge?(trypass, hash) 
-  salt = hash[0, 2]
-  tryhash = trypass.to_s.crypt(salt)
-  return tryhash == hash
-end

+ 0 - 204
lib/serdes.rb

@@ -1,204 +0,0 @@
-
-
-
-class Dir
-  def self.mkdir_p(name)
-    sub   = ""
-    parts = name.split('/').reject { |e| e.empty? }
-    parts.each do | part |
-      sub <<  "/#{part}"
-      mkdir sub rescue nil
-    end
-  end
-end
-
-
-
-# Module to help with serialization and deserialization of any type of data
-module Serdes
-  
-  module ClassMethods
-    def serdes_add_to_fields(name, type = nil)
-      @serdes_fields ||= []
-      info = { :name => name, :type => type }
-      @serdes_fields << info
-    end
-    
-    def serdes_reader(name, type = nil)
-      serdes_add_to_fields(name, type)
-      attr_reader(name)
-    end
-    
-    def serdes_writer(name)
-      serdes_add_to_fields(name, type = nil)
-      attr_writer(name)
-    end
-    
-    def serdes_accessor(name, type = nil)
-      serdes_add_to_fields(name, type)
-      attr_accessor(name)
-    end
-    
-    def serdes_fields()
-      @serdes_fields ||= []
-      return @serdes_fields
-    end
-    
-    
-    def serdes_register(obj)
-      @serdes_loaded ||= {}
-      @serdes_loaded[obj.id] = obj
-    end
-    
-    def serdes_forget(id)
-      @serdes_loaded ||= {}
-      @serdes_loaded.delete(id)
-    end
-    
-    def serdes_loaded()
-      @serdes_loaded ||= {}
-      return @serdes_loaded
-    end
-    
-    def serdes_get(id)
-      @serdes_loaded ||= {}
-      return @serdes_loaded[id.to_sym]
-    end
-
-    def serdes_load_one(id)
-      return nil unless id && !id.empty?
-      
-      full_name = Serdes.serdes_full_for(self, id)
-      data, errors  = Sitef.load_filename(full_name)
-      unless data
-        # log_error(errors.join(", "))
-        return nil
-      end
-      
-      eldat = data.select do |el|
-        el['id'].to_s == id.to_s
-      end
-      return nil unless eldat
-
-      eldat = eldat.first
-      return nil unless eldat
-      
-      typedat = {}
-      self.serdes_fields.each do |info|
-        name  = info[:name]
-        type  = info[:type]
-        value = eldat[name.to_s]
-        
-        typevalue = 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
-      
-        typedat[name] = typevalue
-      end
-      
-      obj = self.new(typedat)
-      return obj
-    end
-    
-    def serdes_fetch(id)
-      res = serdes_get(id)
-      return res if res
-      return serdes_load_one(id)
-    end
-    
-    alias :fetch    :serdes_fetch
-    alias :load_one :serdes_load_one
-    alias :get      :serdes_get
-    
-    def from_serdes(id)
-      return serdes_fetch(id)
-    end
-    
-    def to_serdes(value)
-      return value.id.to_s
-    end
-    
-    
-  end
-
-  # include callback, be sure to extend the class with the ClassMethods
-  def self.included(klass)
-    klass.extend(ClassMethods)
-  end
-  
-  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_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 = {}
-    self.class.serdes_fields.each do |info|
-      name  = info[:name]
-      type  = info[:type]
-      type||= String
-      key   = "#{name}" 
-      value = "#{self.send(name.to_sym)}"
-      if type.respond_to?(:to_serdes)
-         wrapvalue = type.to_serdes(value)
-      else 
-         wrapvalue = value.to_s
-      end
-      data[key]    = wrapvalue
-    end
-    return data
-  end
-  
-  def save_one
-    Dir.mkdir_p Serdes.serdes_dir_for(self.class)
-    data = serdes_data
-    full_name = Serdes.serdes_full_for(self.class, self.id)
-    Sitef.save_filename(full_name, [ data ] )
-  end
-  
-  def initialize(fields = {}) 
-    fields.each  do |key, value|
-      p "Setting #{key} #{value}"
-      instance_variable_set("@#{key}", value)
-    end
-    self.class.serdes_register(self)
-  end
-
-end
-

+ 0 - 173
lib/sitef.rb

@@ -1,173 +0,0 @@
-
-# Sitef is a simple text format for serializing data to
-# It's intent is to be human readable and easy to 
-# use for multi line text.
-
-module Sitef
-  # All Sitef data is stored in files with one or more records.
-  # Records are separated by separated by at least 2 dashes on a line.
-  # Records contain key/value fields. The key starts in the first column
-  # with a : and is followed by a : the value starts after the second :
-  # A multiline key / value needs a key that starts with . and ends with .
-  # the end of the value is a  pair of dots .. by itself 
-  # Keys may not be nested, however, you could use spaces or dots, 
-  # or array indexes to emulate nexted keys. 
-  # A # at the start optionally after whitespace is a comment
-  # 
-  def self.parse_file(file)
-    lineno   = 0
-    results  = []
-    errors   = []
-    
-    record   = {}
-    key      = nil
-    value    = nil
-    until file.eof?
-      lineno     += 1 
-      line        = file.gets(256)
-      break if line.nil?
-      next if line.empty? 
-      # new record
-      if line[0,2] == '--' 
-        # Store last key used if any.
-        if key          
-          record[key] = value.chomp
-          key = nil
-        end  
-        results << record
-        record = {}
-      elsif line[0] == '#'
-      # Comments start with #
-      elsif line[0] == ':'
-      # a key/value pair
-      key, value  = line[1,line.size].split(':', 2)
-      record[key] = value.chomp
-      key = value = nil
-      elsif line[0, 2] == '..'
-      # end of multiline value 
-      record[key] = value.chomp
-      key = value = nil
-      elsif (line[0] == '.') && key.nil?
-      # Multiline key/value starts here (but is ignored 
-      # until .. is encountered)
-      key   = line[1, line.size]
-      key.chomp!
-      value = ""
-      # multiline value
-      elsif key
-          if line[0] == '\\'
-            # remove any escapes
-            line.slice!(0)
-          end
-          # continue the value
-          value << line
-      else
-          # Not in a key, sntax error.
-          errors << "#{lineno}: Don't know how to process line"
-      end      
-    end
-    # Store last key used if any.
-    if key      
-      record[key] = value.chomp
-    end  
-    # store last record 
-    results << record unless record.empty?
-    return results, errors
-  end  
-  
-  def self.load_filename(filename)
-    results, errors = nil, nil, nil;
-    file = File.open(filename, 'rt') rescue nil
-    return nil, ["Could not open #{filename}"] unless file
-    begin 
-      results, errors = parse_file(file)
-    ensure
-      file.close
-    end
-    return results, errors
-  end
-  
-  # Loads a Sitef fileas obejcts. Uses the ruby_klass atribute to load the object
-  # If that is missing, uses defklass
-  def self.load_objects(filename, defklass=nil)
-    results, errors = load_filename(filename)
-    p filename, results, errors
-    unless errors.nil? || errors.empty?
-      return nil, errors 
-    end
-    
-    objres = [] 
-    results.each do | result |
-      klassname = result['ruby_class'] || defklass
-      return nil unless klassname
-      klass = klassname.split('::').inject(Kernel) { |klass, name| klass.const_get(name) rescue nil } 
-      return nil unless klass
-      if klass.respond_to? :from_sitef
-        objres << klass.from_sitef(result)
-      else
-        objres << klass.new(result)
-      end      
-    end
-    return objres, errors    
-  end
-  
-  
-  # Saves a single field to a file in Sitef format.
-  def self.save_field(file, key, value)
-    if value.is_a? String
-      sval = value.dup
-    else
-      sval = value.to_s
-    end
-    if sval["\n"]
-      file.puts(".#{key}\n")
-      # Escape everything that could be misinterpreted with a \\
-      sval.gsub!(/\n([\.\-\:\#\\]+)/, "\n\\\\\\1")
-      sval.gsub!(/\A([\.\-\:\#\\]+)/, "\\\\\\1")
-      file.printf("%s", sval)
-      file.printf("\n..\n")
-    else
-      file.printf(":#{key}:#{sval}\n")
-    end
-  end
-  
-  def self.save_object(file, object, *fields)
-    save_field(file, :ruby_class, object.class.to_s)
-    fields.each do | field |
-      value = object.send(field.to_sym)
-      save_field(file, field, value)
-    end
-  end
-  
-  def self.save_record(file, record, *fields)
-    record.each do | key, value |
-      next if fields && !fields.empty? && !fields.member?(key)
-      save_field(file, key, value)
-    end
-  end
-
-  def self.save_file(file, records, *fields)
-    records.each do | record |
-      if record.is_a? Hash
-        save_record(file, record, *fields)
-      else 
-        save_object(file, record, *fields)
-      end
-      file.puts("--\n")
-    end
-  end
-  
-  def self.save_filename(filename, records, *fields)
-    results , errors = nil, nil
-    file = File.open(filename, 'wt')
-    return false, ["Could not open #{filename}"] unless file
-    begin 
-      save_file(file, records, *fields)
-    ensure
-      file.close
-    end
-    return true, []
-  end
-  
-end
-

+ 0 - 608
lib/telnet.rb

@@ -1,608 +0,0 @@
-require 'zlib'
-require_relative 'telnet/codes'
-require_relative 'rfc1143'
-require_relative 'monolog'
-
-
-
-# This Telnet class implements a subset of the Telnet protocol.
-#
-class Telnet
-  include Monolog
-  include Telnet::Codes
-
-  # 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)
-
-  attr_reader :telopts
-  
-  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
-    @zdeflate   = Zlib::Deflate.new() # Deflate stream for compression2 support.
-    @zinflate   = Zlib::Inflate.new() # Inflate stream for compression2 support.
-  end
-  
-  # Closes the telnet connection, send last compressed data if needed.
-  def close
-    if @compress 
-      zbuf = @zdeflate.flush(Zlib::FINISH)
-      @client.telnet_send_data(zbuf)
-    end
-    @zdeflate.close
-    @zinflate.close    
-  end
-  
-  # Send an event to the client to notify it of a state change or of data
-  def send_event(type, *data)
-    @client.telnet_event(type, *data)
-  end
-  
-  # Sends unescaped data to client, possibly compressing it if needed
-  def send_raw(buf)
-    if @compress
-      @zdeflate << buf
-      # for short messages the "compressed" stream wil actually be 
-      # bigger than the uncompressed one, but that's unavoidable
-      # due to the streaming nature of network connections.
-      zbuf = @zdeflate.flush(Zlib::SYNC_FLUSH)
-    else
-      zbuf = buf
-    end
-    # Don't use send_event here, since that's only for events received
-    @client.telnet_send_data(zbuf)
-  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
-  
-  # Send negotiation bytes
-  
-  # negotiation bytes 
-  def send_negotiate(cmd, telopt)
-    bytes = ""
-    bytes << TELNET_IAC
-    bytes << cmd
-    bytes << telopt
-    send_raw(bytes)
-  end  
-  
-  # 
-  
-  # Check if we support a particular telsopt using the RFC1143 state
-  def us_support(telopt)
-    have = @rfc1143[telopt]
-    return false unless have
-    return (have.telopt == telopt) && have.us == :yes 
-  end
-  
-  # Check if the remote supports a telopt (and it is enabled)
-  def him_support(telopt)
-    have = @rfc1143[telopt]
-    return false unless have
-    return (have.telopt == telopt) && have.him == :yes 
-  end
-  
-  # Set that we support an option (using the RFC1143 state)
-  def set_support(telopt, support=true, us = :no, him = :no)
-    rfc1143_set(telopt, support=true, us = :no, him = :no)
-  end
-   
-  # retrieve RFC1143 option state
-  def rfc1143_get(telopt)
-    @rfc1143[telopt]
-  end
-    
-  # save RFC1143 option state
-  def rfc1143_set(telopt, support=true, us = :no, him = :no)
-    agree = support
-    @rfc1143[telopt] = RFC1143.new(telopt, us, him, agree)
-    return @rfc1143[telopt]
-  end
-  
-  
-  # RFC1143 telnet option negotiation helper
-  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
-  
-  # Performs a telnet negotiation
-  def do_negotiate(telopt)
-    res, arg = rfc1143_negotiate(telopt)
-    send_event(@state, telopt, res, arg)
-  end
-  
-  
-  # Process a subnegotiation buffer for a naws event
-  def subnegotiate_naws(buffer)
-    # Some clients, like Gnome-Mud can't even get this right. Grrr!
-    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
-  
-
-  # Storage for environment values
-  class Environment 
-    attr_accessor :type
-    attr_accessor :value
-    
-    def initialize(type, value)
-      @type   = type
-      @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 @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.
-    @compress = true
-    send_event(:compress, @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, @sb_telopt, buffer)
-    return false
-  end
-end
-
-
-  
-  def process_byte(byte) 
-    # p "process_byte, #{@state} #{byte}"
-    case @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, @buffer) unless @buffer.empty?
-        @buffer = ""
-        @state = :iac
-      else
-        @buffer << byte
-      end
-    # IAC received before
-    when :iac
-      case byte
-      # subnegotiation
-      when TELNET_SB
-        @state = :sb
-      # negotiation commands
-      when TELNET_WILL
-        @state = :will
-      when TELNET_WONT
-        @state = :wont
-      when TELNET_DO
-        @state = :do
-      when TELNET_DONT
-        @state = :dont
-      # IAC escaping 
-      when TELNET_IAC
-        @buffer << TELNET_IAC.chr
-        send_event(:data, @buffer) unless @buffer.empty?
-        @buffer = ""
-        @state = :data
-      # some other command
-      else
-        send_event(:iac, byte)
-        @state = :data
-      end
-
-    # negotiation received before
-    when :will, :wont, :do, :dont
-      do_negotiate(byte)
-      @state = :data
-    # subnegotiation started, determine option to subnegotiate
-    when :sb
-      @sb_telopt = byte
-      @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)
-        @state = :sb_data_iac
-      elsif (@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.
-        @state = data
-      else 
-        @buffer << byte
-      end
-
-    # IAC received inside a subnegotiation
-    when :sb_data_iac
-      case byte
-        # end subnegotiation
-        when TELNET_SE
-          @state = :data
-          # process subnegotiation
-          compress = do_subnegotiate(@buffer)
-          # if compression was negotiated, the rest of the stream is compressed
-          # and processing it requires decompressing it. Return true to signal 
-          # this.
-          @buffer = ""
-          return true if compress
-        # escaped IAC byte
-        when TELNET_IAC
-        # push IAC into buffer */
-          @buffer << byte
-          @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)
-          @state = :iac
-          # subnegotiate with the buffer anyway, even though it's an error
-          compress = do_subnegotiate(@buffer)
-          # if compression was negotiated, the rest of the stream is compressed
-          # and processing it requires decompressing it. Return true to signal 
-          # this.
-          @buffer = ""
-          return true if compress
-        end
-    when :data  
-      # buffer any other bytes
-      @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, @buffer) unless @buffer.empty?
-    @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);
-    @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

+ 1 - 1
shard.yml

@@ -2,6 +2,6 @@ name: woe
 version: 0.1.0
 
 authors:
-  -  <>
+  -  <beoran@gmail.com>
 
 license: MIT