client.cr 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726
  1. require 'tempfile'
  2. require 'fiber'
  3. require 'timeout'
  4. require_relative '../monolog'
  5. require_relative '../telnet'
  6. require_relative 'account'
  7. module Woe
  8. class Client
  9. include Monolog
  10. include Telnet::Codes
  11. attr_reader :io
  12. attr_reader :id
  13. # to allow for read timeouts
  14. attr_reader :timeout_at
  15. def initialize(server, id, io)
  16. @server = server
  17. @id = id
  18. @io = io
  19. @fiber = Fiber.new { serve }
  20. @telnet = Telnet.new(self)
  21. @telnet.set_support(TELNET_TELOPT_NAWS)
  22. @telnet.set_support(TELNET_TELOPT_MSSP)
  23. @telnet.set_support(TELNET_TELOPT_TTYPE)
  24. @telnet.set_support(TELNET_TELOPT_ECHO)
  25. @telnet.set_support(TELNET_TELOPT_COMPRESS2)
  26. @busy = true
  27. # telnet event queue
  28. @telnet_events = []
  29. @timeout_at = nil
  30. end
  31. # Closes up the client
  32. def close
  33. @telnet.close
  34. @io.close
  35. end
  36. # Is the client in read timeout state
  37. def timeout?
  38. return false unless @timeout_at
  39. return Time.now >= @timeout_at
  40. end
  41. def alive?
  42. @fiber.alive? && @busy
  43. end
  44. def command(cmd, args)
  45. @fiber.resume(cmd, args)
  46. end
  47. def write_raw(data)
  48. @io.write(data)
  49. @io.flush
  50. end
  51. def write(data)
  52. @telnet.send_escaped(data)
  53. end
  54. def printf(fmt, *args)
  55. @telnet.telnet_printf(fmt, *args)
  56. end
  57. def on_start
  58. p "Starting client fiber"
  59. return nil
  60. end
  61. def on_write(data)
  62. p "On write:"
  63. self.write("Client #{socket}:")
  64. self.write(args)
  65. end
  66. # Telnet event class
  67. class TelnetEvent
  68. attr_accessor :type
  69. attr_accessor :data
  70. def initialize(type, data)
  71. @type = type
  72. @data = data
  73. end
  74. def to_s
  75. "<TelnetEvent #{@type} #{@data}>"
  76. end
  77. end
  78. def telnet_event(type, *data)
  79. # store in the event queue
  80. @telnet_events << TelnetEvent.new(type, data)
  81. log_debug("Received tenet event #{@telnet_events}.")
  82. end
  83. def telnet_send_data(buf)
  84. self.write_raw(buf)
  85. end
  86. def process_telnet_events
  87. end
  88. def on_read
  89. data = @io.readpartial(4096)
  90. @io.flush
  91. @telnet.telnet_receive(data)
  92. # now, the data and any telnet events are in @telnet_events
  93. return data
  94. end
  95. # Waits for input from the client.
  96. # any
  97. # This is always wrapped as a TelnetEvent.
  98. # Pure commands have the field type == :command
  99. # consisting of a type and a data key in a hash
  100. # Pass in nloops to time out the loop a loop
  101. # has no definite timing.
  102. def wait_for_input(timeout = nil)
  103. loop do
  104. # Timout based on number of loops.
  105. if timeout
  106. @timeout_at = Time.now + timeout
  107. else
  108. @timeout_at = nil
  109. end
  110. unless @telnet_events.empty?
  111. @timeout_at = nil
  112. return @telnet_events.shift
  113. end
  114. cmd, arg = Fiber.yield
  115. data = nil
  116. case cmd
  117. when :start
  118. on_start
  119. when :timeout
  120. @timeout_at = nil
  121. return nil
  122. when :read
  123. data = on_read
  124. # all data ends up in he telnet_events queue
  125. unless @telnet_events.empty?
  126. @timeout_at = nil
  127. return @telnet_events.shift
  128. end
  129. when :write
  130. on_write(arg)
  131. else
  132. log_warning("Unknown command #{cmd}")
  133. end
  134. end
  135. end
  136. def autohandle_event(tev)
  137. case tev.type
  138. when :naws
  139. @window_h, @window_w = *tev.data
  140. log_info("Client #{@id} window size #{@window_w}x#{@window_h}")
  141. else
  142. log_info("Telnet event #{tev} ignored")
  143. end
  144. end
  145. def wait_for_command(timeout = nil)
  146. loop do
  147. tevent = wait_for_input(timeout)
  148. return nil if tevent.nil?
  149. if tevent.type == :data
  150. return tevent.data.join('').strip
  151. else
  152. autohandle_event(tevent)
  153. end
  154. end
  155. end
  156. # generic negotiation
  157. def setup_negotiate(command, option, yes_event, no_event)
  158. @telnet.telnet_send_negotiate(command, option)
  159. tev = wait_for_input(1.0)
  160. return false, nil unless tev
  161. return false, nil if tev.type == no_event
  162. return false, tev unless tev.type == yes_event && tev.data[0] == option
  163. return true, nil
  164. end
  165. # Negotiate COMPRESS2 support
  166. def setup_compress2
  167. ok, tev = setup_negotiate(TELNET_WILL, TELNET_TELOPT_COMPRESS2, :do, :dont)
  168. return tev unless ok
  169. @telnet.telnet_begin_compress2
  170. log_info("Client #{@id} started COMPRESS2 compression")
  171. @support_compress2 = true
  172. end
  173. # Negotiate NAWS (window size) support
  174. def setup_naws
  175. ok, tev = setup_negotiate(TELNET_DO, TELNET_TELOPT_NAWS, :will, :wont)
  176. return tev unless ok
  177. tev2 = wait_for_input(1.0)
  178. return tev2 unless tev2 && tev2.type == :naws
  179. @window_h, @window_w = *tev2.data
  180. log_info("Client #{@id} window size #{@window_w}x#{@window_h}")
  181. @support_naws = true
  182. return nil
  183. end
  184. # Negotiate MSSP (mud server status protocol) support
  185. def setup_mssp
  186. ok, tev = setup_negotiate(TELNET_WILL, TELNET_TELOPT_MSSP, :do, :dont)
  187. return tev unless ok
  188. mssp = @server.mssp
  189. @telnet.telnet_send_mssp(mssp)
  190. log_info("Client #{@id} accepts MSSP.")
  191. @support_mssp = true
  192. return nil
  193. end
  194. # Check for MXP (html-like) support (but don't implement it yet)
  195. def setup_mxp
  196. ok, tev = setup_negotiate(TELNET_DO, TELNET_TELOPT_MXP, :will, :wont)
  197. return tev unless ok
  198. log_info("Client #{@id} supports MXP.")
  199. @support_mxp = true
  200. end
  201. # Check for MSP (sound) support (but don't implement it yet)
  202. def setup_msp
  203. ok, tev = setup_negotiate(TELNET_DO, TELNET_TELOPT_MSP, :will, :wont)
  204. return tev unless ok
  205. log_info("Client #{@id} supports MSP.")
  206. @support_msp = true
  207. end
  208. # check for MSDP support (extendedboth-way MSSP) but don't support it yet
  209. def setup_msdp
  210. ok, tev = setup_negotiate(TELNET_WILL, TELNET_TELOPT_MSDP, :do, :dont)
  211. return tev unless ok
  212. mssp = @server.mssp
  213. @telnet.telnet_send_mssp(mssp)
  214. log_info("Client #{@id} accepts MSDP.")
  215. @support_msdp = true
  216. end
  217. # Negotiate MTTS/TTYPE (TERMINAL TYPE) support
  218. def setup_ttype
  219. @terminals = []
  220. ok, tev = setup_negotiate(TELNET_DO, TELNET_TELOPT_TTYPE, :will, :wont)
  221. p "ttype 1 #{tev} #{ok}"
  222. return tev unless ok
  223. last = "none"
  224. now = ""
  225. p "ttype 2"
  226. until last == now
  227. last = now
  228. @telnet.telnet_ttype_send()
  229. tev2 = nil
  230. # Some clients (like KildClient, but not TinTin or telnet),
  231. # insist on spamming useless NUL characters
  232. # here... So we have to retry a few times to get a ttype_is
  233. # throwing away any undesirable junk in between.
  234. 3.times do
  235. tev2 = wait_for_input(1.0)
  236. break if tev2 && tev2.type == :ttype_is
  237. end
  238. p "ttype 3 #{tev2}"
  239. return tev2 unless tev2 && tev2.type == :ttype_is
  240. now = tev2.data.first
  241. @terminal = now
  242. @terminals << now unless @terminals.member?(now)
  243. end
  244. log_info "Client #{@id} supported terminals #{@terminals}"
  245. mtts_term = @terminals.find { |t| t =~ /MTTS / }
  246. if mtts_term
  247. @mtts = mtts_term.split(" ").last.to_i rescue nil
  248. log_info "Client #{@id} supports MTTS #{@mtts}" if @mtts
  249. end
  250. @support_ttype = true
  251. return nil
  252. end
  253. # Switches to "password" mode.
  254. def password_mode
  255. # The server sends "IAC WILL ECHO", meaning "I, the server, will do any
  256. # echoing from now on." The client should acknowledge this with an IAC DO
  257. # ECHO, and then stop putting echoed text in the input buffer.
  258. # It should also do whatever is appropriate for password entry to the input
  259. # box thing - for example, it might * it out. Text entered in server-echoes
  260. # mode should also not be placed any command history.
  261. # don't use the Q state machne for echos
  262. @telnet.telnet_send_bytes(TELNET_IAC, TELNET_WILL, TELNET_TELOPT_ECHO)
  263. tev = wait_for_input(0.1)
  264. return tev if tev && tev.type != :do
  265. return nil
  266. end
  267. # Switches to "normal, or non-password mode.
  268. def normal_mode
  269. # When the server wants the client to start local echoing again, it sends
  270. # "IAC WONT ECHO" - the client must respond to this with "IAC DONT ECHO".
  271. # Again don't use Q state machine.
  272. @telnet.telnet_send_bytes(TELNET_IAC, TELNET_WONT, TELNET_TELOPT_ECHO)
  273. tev = wait_for_input(0.1)
  274. return tev if tev && tev.type != :dont
  275. return nil
  276. end
  277. def color_test
  278. self.write("\e[1mBold\e[0m\r\n")
  279. self.write("\e[3mItalic\e[0m\r\n")
  280. self.write("\e[4mUnderline\e[0m\r\n")
  281. 30.upto(37) do | fg |
  282. self.write("\e[#{fg}mForeground Color #{fg}\e[0m\r\n")
  283. self.write("\e[1;#{fg}mBold Foreground Color #{fg}\e[0m\r\n")
  284. end
  285. 40.upto(47) do | bg |
  286. self.write("\e[#{bg}mBackground Color #{bg}\e[0m\r\n")
  287. self.write("\e[1;#{bg}mBold Background Color #{bg}\e[0m\r\n")
  288. end
  289. end
  290. def setup_telnet
  291. loop do
  292. tev = wait_for_input(0.5)
  293. if tev
  294. p "setup_telnet", tev
  295. else
  296. p "no telnet setup received..."
  297. break
  298. end
  299. end
  300. setup_mssp
  301. setup_compress2
  302. setup_naws
  303. setup_ttype
  304. setup_mxp
  305. setup_msp
  306. setup_msdp
  307. # color_test
  308. #p "mssp ev #{tev}"
  309. # @telnet.telnet_send_negotiate(TELNET_WILL, TELNET_TELOPT_MSSP)
  310. # tev = wait_for_input(0.5)
  311. # p "mssp ev #{tev}"
  312. # @telnet.telnet_ttype_send
  313. end
  314. LOGIN_RE = /\A[A-Za-z][A-Za-z0-9]*\Z/
  315. def ask_something(prompt, re, nomatch_prompt, noecho=false)
  316. something = nil
  317. if noecho
  318. password_mode
  319. end
  320. while something.nil? || something.empty?
  321. write("#{prompt}:")
  322. something = wait_for_command
  323. if something
  324. something.chomp!
  325. if re && something !~ re
  326. write("\r\n#{nomatch_prompt}\r\n")
  327. something = nil
  328. end
  329. end
  330. end
  331. if noecho
  332. normal_mode
  333. end
  334. something.chomp!
  335. return something
  336. end
  337. def ask_login
  338. return ask_something("Login", LOGIN_RE, "Login must consist of a letter followed by letters or numbers.")
  339. end
  340. EMAIL_RE = /@/
  341. def ask_email
  342. return ask_something("E-mail", EMAIL_RE, "Email must have at least an @ in there somewhere.")
  343. end
  344. def ask_password(prompt = "Password")
  345. return ask_something(prompt, nil, "", true)
  346. end
  347. def handle_command
  348. order = wait_for_command
  349. case order
  350. when "/quit"
  351. write("Byebye!\r\n")
  352. @busy = false
  353. else
  354. @server.broadcast("#{@account.id} said #{order}\r\n")
  355. end
  356. end
  357. def existing_account_dialog
  358. pass = ask_password
  359. return false unless pass
  360. unless @account.challenge?(pass)
  361. printf("Password not correct!\n")
  362. return false
  363. end
  364. return true
  365. end
  366. def new_account_dialog(login)
  367. while !@account
  368. printf("\nWelcome, %s! Creating new account...\n", login)
  369. pass1 = ask_password
  370. return false unless pass1
  371. pass2 = ask_password("Repeat Password")
  372. return false unless pass2
  373. if pass1 != pass2
  374. printf("\nPasswords do not match! Please try again!\n")
  375. next
  376. end
  377. email = ask_email
  378. return false unless email
  379. @account = Woe::Account.new(:id => login, :email => email )
  380. @account.password = pass1
  381. @account.woe_points = 7
  382. unless @account.save_one
  383. printf("\nFailed to save your account! Please contact a WOE administrator!\n")
  384. return false
  385. end
  386. printf("\nSaved your account.\n")
  387. return true
  388. end
  389. end
  390. def account_dialog
  391. login = ask_login
  392. return false unless login
  393. @account = Account.fetch(login)
  394. if @account
  395. return existing_account_dialog
  396. else
  397. return new_account_dialog(login)
  398. end
  399. end
  400. def serve()
  401. setup_telnet
  402. aok = account_dialog
  403. unless aok
  404. printf "\nLogin failed. Breaking connection.\n"
  405. return false
  406. end
  407. write("\r\nWelcome #{@account.id}!\r\n")
  408. while @busy do
  409. handle_command
  410. end
  411. end
  412. =begin
  413. attr_accessor :id
  414. attr_accessor :server
  415. def initialize(server, id, socket)
  416. @id = id
  417. @server = server
  418. @connected = true
  419. @socket = socket
  420. @telnet = Telnet.new(self)
  421. @busy = true
  422. end
  423. # Get some details about the telnet connection
  424. def setup_telnet
  425. @telnet.telnet_send_negotiate(TELNET_DO, TELNET_TELOPT_TTYPE)
  426. @telnet.telnet_ttype_send
  427. type, *args = wait_for_event
  428. p type, args
  429. end
  430. def post_init()
  431. send_data("Welcome!\n")
  432. log_info("Client #{@id} connected.")
  433. self.send_data("Login:")
  434. end
  435. # Send data to the socket
  436. def send_data(data)
  437. @socket.write(data)
  438. end
  439. # Run the client's main loop
  440. def run
  441. post_init
  442. while @connected
  443. data = @socket.readpartial(4096)
  444. unless data.nil? || data.empty?
  445. receive_data(data)
  446. end
  447. p data
  448. end
  449. end
  450. def save
  451. self.send_data("Saving...")
  452. do_save = proc do
  453. begin
  454. f = Tempfile.new('random')
  455. sleep 3
  456. f.write("I'm saving data.")
  457. ensure
  458. f.close
  459. end
  460. end
  461. on_save = proc do
  462. self.send_data("Saved.")
  463. end
  464. Celluloid.defer(do_save, on_save)
  465. end
  466. def wait_for_event
  467. return Fiber.yield
  468. end
  469. # Basically, this method yields the fiber, and will return
  470. # with the input that will cme later when the fiber is resumed, normally
  471. # when more input becomes available from the client.
  472. # Any telnet commands are dispatched to the related telnet handlers.
  473. def wait_for_input
  474. loop do
  475. type, *args = Fiber.yield
  476. if type == :data
  477. return args.first
  478. else
  479. telnet_dispatch(type, *args)
  480. end
  481. end
  482. end
  483. def try
  484. self.send_data("\nOK, let's try. What do you say?:")
  485. try = wait_for_input
  486. self.send_data("\nOK, nice try #{try}.\n")
  487. end
  488. # Fake synchronous handing of input
  489. def handle_input()
  490. setup_telnet
  491. @login = wait_for_input
  492. self.send_data([TELNET_IAC, TELNET_WILL, TELNET_TELOPT_ECHO].pack('c*'))
  493. self.send_data("\nPassword for #{@login}:")
  494. @password = wait_for_input
  495. self.send_data([TELNET_IAC, TELNET_WONT, TELNET_TELOPT_ECHO].pack('c*'))
  496. self.send_data("\nOK #{@password}, switching to command mode.\n")
  497. while @connected
  498. line = wait_for_input
  499. # If the user says 'quit', disconnect them
  500. if line =~ /^\/quit/
  501. @connected = false
  502. close_connection_after_writing
  503. # Shut down the server if we hear 'shutdown'
  504. elsif line =~ /^\/reload/
  505. @server.reload
  506. elsif line =~ /^\/shutdown/
  507. @connected = false
  508. @server.stop
  509. elsif line =~ /^\/save/
  510. self.save
  511. elsif line =~ /^\/try/
  512. self.try
  513. else
  514. @server.broadcast("Client #{id} says #{line}")
  515. end
  516. end
  517. end
  518. def receive_data(data)
  519. # Ignore any input if already requested disconnection
  520. return unless @connected
  521. @telnet.telnet_receive(data)
  522. end
  523. def unbind
  524. log_info("Client #{@id} has left from #{@ip}:#{@port}")
  525. @server.disconnect(@id)
  526. end
  527. # Called when the telnet module wants to send data.
  528. def telnet_send_data(buffer)
  529. p "Sending telnet data #{buffer}"
  530. self.send_data(buffer)
  531. end
  532. # Dispatches a telnet event to a function named telnet_(event_name)
  533. def telnet_dispatch(type, *args)
  534. meth = "telnet_#{type}".to_sym
  535. self.send(meth, *args)
  536. end
  537. # Telnet event handler, called on incoming events.
  538. def telnet_event(type, *args)
  539. log_info("Telnet event received by client #{id}: #{type}, #{args}")
  540. if @fiber
  541. # restart the fiber if available
  542. @fiber.resume(type, *args)
  543. else
  544. # set up a fiber to handle the events
  545. # Like that, the handle_input can be programmed in a fake-syncronous way
  546. @fiber = Fiber.new do
  547. handle_input
  548. end
  549. # Must resume twice becaus of the way telnet_event_fiber works
  550. @fiber.resume()
  551. @fiber.resume(type, *args)
  552. end
  553. end
  554. # Real handler, called inside a fiber
  555. def telnet_event_fiber()
  556. raise "not implemented"
  557. end
  558. def telnet_environment(fb, vars)
  559. p fb,vars
  560. end
  561. def telnet_environment(fb, vars)
  562. p fb,vars
  563. end
  564. def telnet_mssp(vars)
  565. @mssp_vars = vars
  566. end
  567. def telnet_ttype_is(term)
  568. @term = term
  569. self.send_data("\nYou have a #{@term} type terminal.\n")
  570. p "term #{@term}"
  571. end
  572. def telnet_ttype_send(term)
  573. p "term #{term} sent"
  574. end
  575. def telnet_compress(compress)
  576. p "compress #{compress} set"
  577. end
  578. def telnet_subnegotiate(sb_telopt, buffer)
  579. p "received subnegotiate #{sb_telopt} #{buffer}"
  580. end
  581. def do_main
  582. end
  583. def telnet_data(data)
  584. =begin
  585. # send data over telnet protocol. Should arrive below in telnet_data
  586. if @fiber
  587. @fiber.resume(data)
  588. else
  589. # set up a fiber to handle the input
  590. # Like that, the handle_input can be programmed in a fake-syncronous way
  591. @fiber = Fiber.new do
  592. handle_input()
  593. end
  594. # Must resume twice becaus of the way handle_input works
  595. @fiber.resume()
  596. @fiber.resume(data)
  597. end
  598. end
  599. def telnet_iac(byte)
  600. p "received iac #{byte}"
  601. end
  602. =end
  603. end # class Client
  604. end # module Woe