123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492 |
- require 'tempfile'
- require 'fiber'
- require 'timeout'
- require_relative '../monolog'
- require_relative '../telnet'
- module Woe
- class Client
- include Monolog
- include Telnet::Codes
-
- attr_reader :io
- attr_reader :id
- # to allow for read timeouts
- attr_reader :timeout_at
-
- def initialize(server, id, io)
- @server = server
- @id = id
- @io = io
- @fiber = Fiber.new { serve }
- @telnet = Telnet.new(self)
- @telnet.set_support(TELNET_TELOPT_NAWS)
- @telnet.set_support(TELNET_TELOPT_MSSP)
- @telnet.set_support(TELNET_TELOPT_TTYPE)
- @telnet.set_support(TELNET_TELOPT_ECHO)
- @telnet.set_support(TELNET_TELOPT_COMPRESS2)
- @busy = true
- # telnet event queue
- @telnet_events = []
- @timeout_at = nil
- end
-
- def alive?
- @fiber.alive? && @busy
- end
-
- def command(cmd, args)
- @fiber.resume(cmd, args)
- end
-
- def write(data)
- @io.write(data)
- end
-
-
- def on_start
- p "Starting client fiber"
- return nil
- end
-
- def on_write(data)
- p "On write:"
- self.write("Client #{socket}:")
- self.write(args)
- end
-
- # Telnet event class
- class TelnetEvent
- attr_accessor :type
- attr_accessor :data
- def initialize(type, data)
- @type = type
- @data = data
- end
-
- def to_s
- "<TelnetEvent #{@type} #{@data}>"
- end
- end
- def telnet_event(type, *data)
- # store in the event queue
- @telnet_events << TelnetEvent.new(type, data)
- p "Received tenet event #{@telnet_events}."
- end
-
- def telnet_send_data(buf)
- # @telnet_events << TelnetEvent.new(:command, buf)
- p "Sending telnet data."
- self.write(buf)
- end
-
- def process_telnet_events
-
- end
-
- def on_read
- data = @io.readpartial(4096)
- p "After read: #{data}"
- @io.flush
- @telnet.telnet_receive(data)
- # now, the data and any telnet events are in @telnet_events
- return data
- end
- # Waits for input from the client.
- # any
- # This is always wrapped as a TelnetEvent.
- # Pure commands have the field type == :command
- # consisting of a type and a data key in a hash
- # Pass in nloops to time out the loop a loop
- # has no definite timing.
- def wait_for_input(timeout = nil)
- loop do
- # Timout based on number of loops.
- if timeout
- @timeout_at = Time.now + timeout
- else
- @timeout_at = nil
- end
-
- unless @telnet_events.empty?
- return @telnet_events.shift
- end
- cmd, arg = Fiber.yield
- data = nil
- case cmd
- when :start
- on_start
- when :timeout
- @timeout_at = nil
- return nil
- when :read
- data = on_read
- # all data ends up in he telnet_events queue
- unless @telnet_events.empty?
- return @telnet_events.shift
- end
- when :write
- on_write(arg)
- else
- p "Unknown command #{cmd}"
- end
- end
- end
-
-
- def autohandle_event(tev)
- case tev.type
- when :naws
- @window_h, @window_w = *tev.data
- log_info("Client #{@id} window size #{@window_w}x#{@window_h}")
- else
- log_info('Telnet event #{tev} ignored')
- end
- end
-
- def wait_for_command(timeout = nil)
- loop do
- tevent = wait_for_input(timeout)
- return nil if tevent.nil?
- if tevent.type == :data
- return tevent.data.join('').strip
- else
- autohandle_event(tevent)
- end
- end
- 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
-
- def setup_naws
- # Negotiate NAWS (window size) support
- @telnet.telnet_send_negotiate(TELNET_DO, TELNET_TELOPT_NAWS)
- tev = wait_for_input(0.5)
- return nil unless tev
- ask, cmd, opt = *tev.data
- return tev unless tev.type == :will
- tev2 = wait_for_input(0.5)
- return tev2 unless tev2 && tev2.type == :naws
- @window_h, @window_w = *tev2.data
- log_info("Client #{@id} window size #{@window_w}x#{@window_h}")
- return nil
- end
-
- def setup_telnet
- loop do
- tev = wait_for_input(0.5)
- if tev
- p "setup_telnet", tev
- else
- p "no telnet setup received..."
- break
- end
- end
- setup_naws
-
- #p "mssp ev #{tev}"
- # @telnet.telnet_send_negotiate(TELNET_WILL, TELNET_TELOPT_MSSP)
- # tev = wait_for_input(0.5)
- # p "mssp ev #{tev}"
-
- # @telnet.telnet_ttype_send
-
-
- end
-
- def serve()
- setup_telnet
- data = nil
- lok = ask_login
- return false unless lok
- pok = ask_password
- return false unless pok
- write("\r\nWelcome #{@login} #{@password}!\r\n")
- while @busy do
- handle_command
- end
- end
-
- =begin
- attr_accessor :id
- attr_accessor :server
-
- def initialize(server, id, socket)
- @id = id
- @server = server
- @connected = true
- @socket = socket
- @telnet = Telnet.new(self)
- @busy = true
- end
-
-
- # Get some details about the telnet connection
- def setup_telnet
- @telnet.telnet_send_negotiate(TELNET_DO, TELNET_TELOPT_TTYPE)
- @telnet.telnet_ttype_send
- type, *args = wait_for_event
- p type, args
- end
-
- def post_init()
- send_data("Welcome!\n")
- log_info("Client #{@id} connected.")
- self.send_data("Login:")
- end
-
- # Send data to the socket
- def send_data(data)
- @socket.write(data)
- end
-
- # Run the client's main loop
- def run
- post_init
- while @connected
- data = @socket.readpartial(4096)
- unless data.nil? || data.empty?
- receive_data(data)
- end
- p data
- end
- end
-
-
- def save
- self.send_data("Saving...")
-
- do_save = proc do
- begin
- f = Tempfile.new('random')
- sleep 3
- f.write("I'm saving data.")
- ensure
- f.close
- end
- end
-
- on_save = proc do
- self.send_data("Saved.")
- end
-
- Celluloid.defer(do_save, on_save)
- end
-
- def wait_for_event
- return Fiber.yield
- end
-
- # Basically, this method yields the fiber, and will return
- # with the input that will cme later when the fiber is resumed, normally
- # when more input becomes available from the client.
- # Any telnet commands are dispatched to the related telnet handlers.
- def wait_for_input
- loop do
- type, *args = Fiber.yield
- if type == :data
- return args.first
- else
- telnet_dispatch(type, *args)
- end
- end
- end
-
- def try
- self.send_data("\nOK, let's try. What do you say?:")
- try = wait_for_input
- self.send_data("\nOK, nice try #{try}.\n")
- end
-
- # Fake synchronous handing of input
- def handle_input()
- setup_telnet
- @login = wait_for_input
-
- self.send_data([TELNET_IAC, TELNET_WILL, TELNET_TELOPT_ECHO].pack('c*'))
- self.send_data("\nPassword for #{@login}:")
- @password = wait_for_input
- self.send_data([TELNET_IAC, TELNET_WONT, TELNET_TELOPT_ECHO].pack('c*'))
- self.send_data("\nOK #{@password}, switching to command mode.\n")
-
- while @connected
- line = wait_for_input
- # If the user says 'quit', disconnect them
- if line =~ /^\/quit/
- @connected = false
- close_connection_after_writing
- # Shut down the server if we hear 'shutdown'
- elsif line =~ /^\/reload/
- @server.reload
- elsif line =~ /^\/shutdown/
- @connected = false
- @server.stop
- elsif line =~ /^\/save/
- self.save
- elsif line =~ /^\/try/
- self.try
- else
- @server.broadcast("Client #{id} says #{line}")
- end
- end
- end
-
- def receive_data(data)
- # Ignore any input if already requested disconnection
- return unless @connected
- @telnet.telnet_receive(data)
- end
-
-
-
- def unbind
- log_info("Client #{@id} has left from #{@ip}:#{@port}")
- @server.disconnect(@id)
- end
- # Called when the telnet module wants to send data.
- def telnet_send_data(buffer)
- p "Sending telnet data #{buffer}"
- self.send_data(buffer)
- end
-
- # Dispatches a telnet event to a function named telnet_(event_name)
- def telnet_dispatch(type, *args)
- meth = "telnet_#{type}".to_sym
- self.send(meth, *args)
- end
-
-
- # Telnet event handler, called on incoming events.
- def telnet_event(type, *args)
- log_info("Telnet event received by client #{id}: #{type}, #{args}")
- if @fiber
- # restart the fiber if available
- @fiber.resume(type, *args)
- else
- # set up a fiber to handle the events
- # Like that, the handle_input can be programmed in a fake-syncronous way
- @fiber = Fiber.new do
- handle_input
- end
- # Must resume twice becaus of the way telnet_event_fiber works
- @fiber.resume()
- @fiber.resume(type, *args)
- end
- end
-
-
- # Real handler, called inside a fiber
- def telnet_event_fiber()
- raise "not implemented"
- end
-
- def telnet_environment(fb, vars)
- p fb,vars
- end
-
- def telnet_environment(fb, vars)
- p fb,vars
- end
-
-
- def telnet_mssp(vars)
- @mssp_vars = vars
- end
-
- def telnet_ttype_is(term)
- @term = term
- self.send_data("\nYou have a #{@term} type terminal.\n")
- p "term #{@term}"
- end
-
- def telnet_ttype_send(term)
- p "term #{term} sent"
- end
-
-
- def telnet_compress(compress)
- p "compress #{compress} set"
- end
-
-
- def telnet_subnegotiate(sb_telopt, buffer)
- p "received subnegotiate #{sb_telopt} #{buffer}"
- end
-
-
-
- def do_main
- end
-
-
- def telnet_data(data)
- =begin
- # send data over telnet protocol. Should arrive below in telnet_data
- if @fiber
- @fiber.resume(data)
- else
- # set up a fiber to handle the input
- # Like that, the handle_input can be programmed in a fake-syncronous way
- @fiber = Fiber.new do
- handle_input()
- end
- # Must resume twice becaus of the way handle_input works
- @fiber.resume()
- @fiber.resume(data)
- end
- end
-
- def telnet_iac(byte)
- p "received iac #{byte}"
- end
- =end
- end # class Client
- end # module Woe
|