浏览代码

Telnet negotiation works now do the acount login and creation.

Beoran 8 年之前
父节点
当前提交
931912cc7b
共有 7 个文件被更改,包括 265 次插入57 次删除
  1. 3 1
      lib/serdes.rb
  2. 1 1
      lib/sitef.rb
  3. 11 4
      lib/telnet.rb
  4. 20 1
      lib/telnet/codes.rb
  5. 10 3
      lib/woe/account.rb
  6. 215 44
      lib/woe/client.rb
  7. 5 3
      lib/woe/server.rb

+ 3 - 1
lib/serdes.rb

@@ -1,4 +1,6 @@
 
+
+
 class Dir
   def self.mkdir_p(name)
     sub   = ""
@@ -69,7 +71,7 @@ module Serdes
       full_name = Serdes.serdes_full_for(self, id)
       data, errors  = Sitef.load_filename(full_name)
       unless data
-        log errors.join(", ")
+        # log_error(errors.join(", "))
         return nil
       end
       

+ 1 - 1
lib/sitef.rb

@@ -77,7 +77,7 @@ module Sitef
   
   def self.load_filename(filename)
     results, errors = nil, nil, nil;
-    file = File.open(filename, 'rt')
+    file = File.open(filename, 'rt') rescue nil
     return nil, ["Could not open #{filename}"] unless file
     begin 
       results, errors = parse_file(file)

+ 11 - 4
lib/telnet.rb

@@ -50,8 +50,10 @@ class Telnet
   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)
-      p "Deflating #{buf} -> #{zbuf}"
     else
       zbuf = buf
     end
@@ -136,6 +138,11 @@ class Telnet
   
   # 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]
@@ -320,7 +327,7 @@ end
 # must be aborted and reprocessed due to COMPRESS2 being activated
 
 def do_subnegotiate(buffer)
-  case @sb_teloptTELNET_MSSSP_VAR
+  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.
@@ -378,8 +385,8 @@ end
         @state = :dont
       # IAC escaping 
       when TELNET_IAC
-        @buffer << byte
-        send_raw(@buffer)
+        @buffer << TELNET_IAC.chr
+        send_event(:data, @buffer) unless @buffer.empty?
         @buffer = ""
         @state = :data
       # some other command

+ 20 - 1
lib/telnet/codes.rb

@@ -86,6 +86,17 @@ class Telnet
     # TERMINAL-TYPE codes. 
     TELNET_TTYPE_IS = 0
     TELNET_TTYPE_SEND = 1
+    
+    # MTTS standard codes
+    TELNET_MTTS_ANSI                = 1
+    TELNET_MTTS_VT100               = 2
+    TELNET_MTTS_UTF8                = 4
+    TELNET_MTTS_256_COLORS          = 8
+    TELNET_MTTS_MOUSE_TRACKING      = 16
+    TELNET_MTTS_OSC_COLOR_PALETTE   = 32
+    TELNET_MTTS_SCREEN_READER       = 64
+    TELNET_MTTS_PROXY               = 128
+    
 
     # NEW-ENVIRON/ENVIRON codes. 
     TELNET_ENVIRON_IS = 0
@@ -99,7 +110,15 @@ class Telnet
     # MSSP codes. 
     TELNET_MSSP_VAR = 1
     TELNET_MSSP_VAL = 2
-
+    
+    # MSDP values.
+    TELNET_MSDP_VAR         = 1
+    TELNET_MSDP_VAL         = 2
+    TELNET_MSDP_TABLE_OPEN  = 3
+    TELNET_MSDP_TABLE_CLOSE = 4
+    TELNET_MSDP_ARRAY_OPEN  = 5
+    TELNET_MSDP_ARRAY_CLOSE = 6
+    
     # newline, cr and nul
     TELNET_CR = 13
     TELNET_NL = 10

+ 10 - 3
lib/woe/account.rb

@@ -1,12 +1,19 @@
-script "serdes.rb"
+require_relative "../sitef.rb"
+require_relative "../serdes.rb"
+require_relative "../monolog.rb"
 
 
+module Woe
 class Account
   include Serdes
+  include Monolog
 
   serdes_reader :id
   serdes_reader :pass
   serdes_reader :algo
+  serdes_reader :email
+  serdes_reader :woe_points
+  
   
   def inspect
     "Account #{@id} #{@pass} #{algo}"
@@ -27,8 +34,8 @@ class Account
       return false
     end
   end
-  
-end
+end # class Account
+end # module Woe
 
 
 

+ 215 - 44
lib/woe/client.rb

@@ -4,6 +4,8 @@ require 'timeout'
 
 require_relative '../monolog'
 require_relative '../telnet'
+require_relative 'account'
+
 
 module Woe
 
@@ -39,6 +41,12 @@ class Client
     @io.close
   end
   
+  # Is the client in read timeout state
+  def timeout?
+    return false unless @timeout_at
+    return Time.now >= @timeout_at
+  end
+  
   def alive?
     @fiber.alive? && @busy
   end
@@ -49,6 +57,7 @@ class Client
   
   def write_raw(data)
     @io.write(data)
+    @io.flush
   end
   
   
@@ -56,7 +65,10 @@ class Client
     @telnet.send_escaped(data)
   end
   
- 
+  def printf(fmt, *args)
+    @telnet.printf(fmt, *args)
+  end
+  
   def on_start
      p "Starting client fiber"
      return nil
@@ -85,12 +97,10 @@ class Client
   def telnet_event(type, *data)
     # store in the event queue
     @telnet_events << TelnetEvent.new(type, data)
-    p "Received tenet event #{@telnet_events}."
+    log_debug("Received tenet event #{@telnet_events}.")
   end
   
   def telnet_send_data(buf)
-    # @telnet_events << TelnetEvent.new(:command, buf)
-    p "Sending telnet data."
     self.write_raw(buf)
   end
   
@@ -124,6 +134,7 @@ class Client
       end
       
       unless @telnet_events.empty?
+        @timeout_at = nil
         return @telnet_events.shift
       end
 
@@ -139,12 +150,13 @@ class Client
         data = on_read
         # all data ends up in he telnet_events queue
         unless @telnet_events.empty?
+          @timeout_at = nil
           return @telnet_events.shift
         end
       when :write 
         on_write(arg)
       else
-        p "Unknown command #{cmd}" 
+        log_warning("Unknown command #{cmd}") 
       end
     end
   end
@@ -173,41 +185,10 @@ class Client
   end
           
   
-  def ask_login
-    @login = nil
-    while  @login.nil? || @login.empty?
-      write("Login:")
-      @login = wait_for_command
-    end
-    @login.chomp!
-    true
-  end
-
-  def ask_password
-    @password = nil
-    while  @password.nil? || @password.empty?
-      write("\r\nPassword:")
-      @password = wait_for_command
-    end
-    @password.chomp!
-    true
-  end
-  
-  def handle_command
-    order = wait_for_command
-    case order
-    when "/quit"
-      write("Byebye!\r\n")
-      @busy = false
-    else
-      @server.broadcast("#@login said #{order}\r\n")
-    end
-  end
-  
   # generic negotiation
   def setup_negotiate(command, option, yes_event, no_event)
     @telnet.telnet_send_negotiate(command, option)
-    tev = wait_for_input(0.5)
+    tev = wait_for_input(1.0)
     return false, nil unless tev
     return false, nil if tev.type == no_event
     return false, tev unless tev.type == yes_event && tev.data[0] == option
@@ -220,16 +201,18 @@ class Client
     return tev unless ok    
     @telnet.telnet_begin_compress2
     log_info("Client #{@id} started COMPRESS2 compression")
+    @support_compress2 = true
   end
   
   # Negotiate NAWS (window size) support
   def setup_naws  
     ok, tev = setup_negotiate(TELNET_DO, TELNET_TELOPT_NAWS, :will, :wont)
     return tev unless ok
-    tev2 = wait_for_input(0.5)
+    tev2 = wait_for_input(1.0)
     return tev2 unless tev2 && tev2.type == :naws
     @window_h, @window_w = *tev2.data
     log_info("Client #{@id} window size #{@window_w}x#{@window_h}") 
+    @support_naws = true
     return nil
   end
   
@@ -240,13 +223,116 @@ class Client
     return tev unless ok
     mssp = @server.mssp
     @telnet.telnet_send_mssp(mssp)
+    log_info("Client #{@id} accepts MSSP.") 
+    @support_mssp = true
     return nil
   end
   
+  # Check for MXP (html-like) support (but don't implement it yet)
+  def setup_mxp
+    ok, tev = setup_negotiate(TELNET_DO, TELNET_TELOPT_MXP, :will, :wont)
+    return tev unless ok
+    log_info("Client #{@id} supports MXP.") 
+    @support_mxp = true
+  end
+  
+  # Check for MSP (sound) support (but don't implement it yet)
+  def setup_msp
+    ok, tev = setup_negotiate(TELNET_DO, TELNET_TELOPT_MSP, :will, :wont)
+    return tev unless ok
+    log_info("Client #{@id} supports MSP.")
+    @support_msp = true
+  end
+  
+  # check for MSDP support (extendedboth-way MSSP) but don't support it yet
+  def setup_msdp
+    ok, tev = setup_negotiate(TELNET_WILL, TELNET_TELOPT_MSDP, :do, :dont)
+    return tev unless ok
+    mssp = @server.mssp
+    @telnet.telnet_send_mssp(mssp)
+    log_info("Client #{@id} accepts MSDP.") 
+    @support_msdp = true
+  end
   
+  # Negotiate MTTS/TTYPE (TERMINAL TYPE) support
+  def setup_ttype
+    @terminals = []
+    ok, tev = setup_negotiate(TELNET_DO, TELNET_TELOPT_TTYPE, :will, :wont)    
+    p "ttype 1 #{tev} #{ok}"
+    return tev unless ok
+    last = "none"
+    now  = ""
+    p "ttype 2"
+    until last == now
+      last = now
+      @telnet.telnet_ttype_send()
+      tev2 = nil
+      # Some clients (like KildClient, but not TinTin or telnet), 
+      # insist on spamming useless NUL characters
+      # here... So we have to retry a few times to get a ttype_is
+      # throwing away any undesirable junk in between.
+      3.times do
+        tev2 = wait_for_input(1.0)
+        break if tev2 && tev2.type == :ttype_is
+      end
+      p "ttype 3 #{tev2}"
+      return tev2 unless tev2 && tev2.type == :ttype_is
+      now = tev2.data.first
+      @terminal = now
+      @terminals << now unless @terminals.member?(now)
+    end 
+    log_info "Client #{@id} supported terminals #{@terminals}"
+    mtts_term = @terminals.find { |t| t =~ /MTTS / }
+    if mtts_term
+      @mtts = mtts_term.split(" ").last.to_i rescue nil
+      log_info "Client #{@id} supports MTTS #{@mtts}" if @mtts
+    end
+    @support_ttype = true
+    return nil
+  end
+  
+  # Switches to "password" mode.
+  def password_mode
+    # The server sends "IAC WILL ECHO", meaning "I, the server, will do any 
+    # echoing from now on." The client should acknowledge this with an IAC DO 
+    # ECHO, and then stop putting echoed text in the input buffer. 
+    # It should also do whatever is appropriate for password entry to the input 
+    # box thing - for example, it might * it out. Text entered in server-echoes 
+    # mode should also not be placed any command history.
+    # don't use the Q state machne for echos
+    @telnet.telnet_send_bytes(TELNET_IAC, TELNET_WILL, TELNET_TELOPT_ECHO)
+    tev = wait_for_input(0.1)
+    return tev if tev && tev.type != :do
+    return nil
+  end
+
+  # Switches to "normal, or non-password mode.
+  def normal_mode
+    # When the server wants the client to start local echoing again, it sends 
+    # "IAC WONT ECHO" - the client must respond to this with "IAC DONT ECHO".
+    # Again don't use Q state machine.   
+    @telnet.telnet_send_bytes(TELNET_IAC, TELNET_WONT, TELNET_TELOPT_ECHO)
+    tev = wait_for_input(0.1)
+    return tev if tev && tev.type != :dont
+    return nil
+  end
+  
+  def color_test
+    self.write("\e[1mBold\e[0m\r\n")
+    self.write("\e[3mItalic\e[0m\r\n")
+    self.write("\e[4mUnderline\e[0m\r\n")
+    30.upto(37) do | fg |
+      self.write("\e[#{fg}mForeground Color #{fg}\e[0m\r\n")
+      self.write("\e[1;#{fg}mBold Foreground Color #{fg}\e[0m\r\n")
+    end  
+    40.upto(47) do | bg |
+      self.write("\e[#{bg}mBackground Color #{bg}\e[0m\r\n")
+      self.write("\e[1;#{bg}mBold Background Color #{bg}\e[0m\r\n")
+    end    
+  end
   
   def setup_telnet
-    loop do 
+    loop do
       tev = wait_for_input(0.5)
       if tev
         p "setup_telnet", tev
@@ -258,6 +344,12 @@ class Client
     setup_mssp
     setup_compress2
     setup_naws
+    setup_ttype
+    setup_mxp
+    setup_msp
+    setup_msdp
+    # color_test
+    
     
     #p "mssp ev #{tev}"
     # @telnet.telnet_send_negotiate(TELNET_WILL, TELNET_TELOPT_MSSP)        
@@ -268,14 +360,93 @@ class Client
     
     
   end
+ 
+  LOGIN_RE = /\A[A-Za-z][A-Za-z0-9]*\Z/
+  
+  def ask_something(prompt, re, nomatch_prompt)
+    something = nil
     
+    while  something.nil? || something.empty? 
+      write("#{prompt}:")
+      something = wait_for_command
+      if something
+          something.chomp!
+        if re && something !~ re
+          write("\r\n#{nomatch_prompt}\r\n")
+          something = nil
+        end
+      end
+    end
+    something.chomp!
+    true
+  end
+  
+  
+  
+  def ask_login
+    return ask_something("Login", LOGIN_RE, "Login must consist of a letter followed by letters or numbers.")
+  end
+
+  EMAIL_RE = /@/
+
+  def ask_email
+    return ask_something("E-mail:", EMAIL_RE, "Email must have at least an @ in there somewhere.")
+  end
+
+
+  def ask_password(prompt = "Password")
+    password = nil
+    password_mode
+    while  password.nil? || password.empty?
+      write("\r\n#{prompt}:")
+      password = wait_for_command
+    end
+    password.chomp!
+    normal_mode
+    true
+  end
+  
+  def handle_command
+    order = wait_for_command
+    case order
+    when "/quit"
+      write("Byebye!\r\n")
+      @busy = false
+    else
+      @server.broadcast("#@login said #{order}\r\n")
+    end
+  end
+ 
   def serve()
     setup_telnet
-    data = nil
-    lok  = ask_login
-    return false unless lok    
-    pok  = ask_password
-    return false unless pok
+    login  = ask_login
+    return false unless login
+    @account = Account.fetch(login)
+    if @account
+      pass  = ask_password
+      return false unless pass
+      
+    else
+      while !@account 
+        printf("\nWelcome, %s! Creating new account...\n", login)
+        pass1  = ask_password
+        return false unless pass
+        pass2 = ask_password("Repeat Password:")
+        return false unless pass
+        if pass1 != pass2
+          printf("\nPasswords do not match.\n")
+          next
+        end
+        email = ask_email
+        return false unless email
+        
+        
+      
+      end
+      
+      
+    end
+    
     write("\r\nWelcome #{@login} #{@password}!\r\n")
     while @busy do
       handle_command

+ 5 - 3
lib/woe/server.rb

@@ -146,11 +146,11 @@ class Server
     return nil
   end
   
-   # Nodify clients that havea read timeout set
+   # Notify clients that have a read timeout set
   def handle_timeouts
     now = Time.now
     @clients.each  do |id, cl| 
-      if cl.timeout_at && cl.timeout_at > now
+      if cl.timeout?
         cl.command(:timeout, nil)
       end
     end
@@ -177,7 +177,9 @@ class Server
           else
             # Tell the client to get their read on.              
             client  = client_for_socket(rsock)
-            text    = client.command(:read, nil)
+            if client
+              text    = client.command(:read, nil)
+            end
           end
         end
       end