telnet.rb 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739
  1. # file:: telnetfilter.rb
  2. # author:: Jon A. Lambert
  3. # version:: 2.8.0
  4. # date:: 01/19/2006
  5. #
  6. # This source code copyright (C) 2005, 2006 by Jon A. Lambert
  7. # All rights reserved.
  8. #
  9. # Released under the terms of the TeensyMUD Public License
  10. # See LICENSE file for additional information.
  11. #
  12. $:.unshift "lib" if !$:.include? "lib"
  13. $:.unshift "vendor" if !$:.include? "vendor"
  14. require 'strscan'
  15. require 'ostruct'
  16. require 'network/protocol/filter'
  17. require 'network/protocol/telnetcodes'
  18. require 'network/protocol/asciicodes'
  19. # The TelnetFilter class implements the Telnet protocol.
  20. #
  21. # This implements most of basic Telnet as per RFCs 854/855/1129/1143 and
  22. # options in RFCs 857/858/1073/1091
  23. #
  24. class Telnet
  25. include Telnet::Codes
  26. # Initialize state of filter
  27. def initialize(server)
  28. @server = server
  29. @mode = :normal # Parse mode :normal, :cmd, :cr
  30. @state = {}
  31. @sc = nil
  32. @sneg_opts = [ TTYPE, ZMP ]
  33. @ttype = []
  34. @init_tries = 0 # Number of tries at negotitating sub options
  35. @synch = false
  36. log.debug "telnet filter initialized - #{@init_tries}"
  37. end
  38. # Wait for input from the server
  39. def wait_for_input
  40. return Fiber.yield
  41. end
  42. # Called when client data should be filtered before being passed to the server
  43. def client_to_server(data)
  44. end
  45. # Called when server data should be filtered before being passed to the client
  46. def server_to_client(data)
  47. end
  48. # Negotiate starting wanted options
  49. #
  50. # [+args+] Optional initial options
  51. def init(args)
  52. if @server.service_type == :client # let server offer and ask for client
  53. # several sorts of options here - server offer, ask client or both
  54. @wopts.each do |key,val|
  55. case key
  56. when ECHO, SGA, BINARY, ZMP, EOREC
  57. ask_him(key,val)
  58. else
  59. offer_us(key,val)
  60. end
  61. end
  62. else
  63. # several sorts of options here - server offer, ask client or both
  64. @wopts.each do |key,val|
  65. case key
  66. when ECHO, SGA, BINARY, ZMP, EOREC
  67. offer_us(key,val)
  68. else
  69. ask_him(key,val)
  70. end
  71. end
  72. end
  73. true
  74. end
  75. # The filter_in method filters input data
  76. # [+str+] The string to be processed
  77. # [+return+] The filtered data
  78. def filter_in(str)
  79. # init_subneg
  80. return "" if str.nil? || str.empty?
  81. buf = ""
  82. @sc ? @sc.concat(str) : @sc = StringScanner.new(str)
  83. while b = @sc.get_byte
  84. # OOB sync data
  85. if @pstack.urgent_on || b.getbyte(0) == DM
  86. log.debug("(#{@pstack.conn.object_id}) Sync mode on")
  87. @pstack.urgent_on = false
  88. @synch = true
  89. break
  90. end
  91. case mode?
  92. when :normal
  93. case b.getbyte(0)
  94. when CR
  95. next if @synch
  96. set_mode(:cr) if !@pstack.binary_on
  97. when LF # LF or LF/CR may be issued by broken mud servers and clients
  98. next if @synch
  99. set_mode(:lf) if !@pstack.binary_on
  100. buf << LF.chr
  101. echo(CR.chr + LF.chr)
  102. when IAC
  103. set_mode(:cmd)
  104. when NUL # ignore NULs in stream when in normal mode
  105. next if @synch
  106. if @pstack.binary_on
  107. buf << b
  108. echo(b)
  109. else
  110. log.debug("(#{@pstack.conn.object_id}) unexpected NUL found in stream")
  111. end
  112. when BS, DEL
  113. next if @synch
  114. # Leaves BS, DEL in input stream for higher filter to deal with.
  115. buf << b
  116. echo(BS.chr)
  117. else
  118. next if @synch
  119. ### NOTE - we will allow 8-bit NVT against RFC 1123 recommendation "should not"
  120. ###
  121. # Only let 7-bit values through in normal mode
  122. #if (b[0] & 0x80 == 0) && !@pstack.binary_on
  123. buf << b
  124. echo(b)
  125. #else
  126. # log.debug("(#{@pstack.conn.object_id}) unexpected 8-bit byte found in stream '#{b[0]}'")
  127. #end
  128. end
  129. when :cr
  130. # handle CRLF and CRNUL by insertion of LF into buffer
  131. case b.getbyte(0)
  132. when LF
  133. buf << LF.chr
  134. echo(CR.chr + LF.chr)
  135. when NUL
  136. if @server.service_type == :client # Don't xlate CRNUL when client
  137. buf << CR.chr
  138. echo(CR.chr)
  139. else
  140. buf << LF.chr
  141. echo(CR.chr + LF.chr)
  142. end
  143. else # eat lone CR
  144. buf << b
  145. echo(b)
  146. end
  147. set_mode(:normal)
  148. when :lf
  149. # liberally handle LF, LFCR for clients that aren't telnet correct
  150. case b.getbyte(0)
  151. when CR # Handle LFCR by swallowing CR
  152. else # Handle other stuff that follows - single LF
  153. buf << b
  154. echo(b)
  155. end
  156. set_mode(:normal)
  157. when :cmd
  158. case b.getbyte(0)
  159. when IAC
  160. # IAC escapes IAC
  161. buf << IAC.chr
  162. set_mode(:normal)
  163. when AYT
  164. log.debug("(#{@pstack.conn.object_id}) AYT sent - Msg returned")
  165. @pstack.conn.sock.send("WOE is here.\n",0)
  166. set_mode(:normal)
  167. when AO
  168. log.debug("(#{@pstack.conn.object_id}) AO sent - Synch returned")
  169. @pstack.conn.sockio.write_flush
  170. @pstack.conn.sock.send(IAC.chr + DM.chr, 0)
  171. @pstack.conn.sockio.write_urgent(DM.chr)
  172. set_mode(:normal)
  173. when IP
  174. @pstack.conn.sockio.read_flush
  175. @pstack.conn.sockio.write_flush
  176. log.debug("(#{@pstack.conn.object_id}) IP sent")
  177. set_mode(:normal)
  178. when GA, NOP, BRK # not implemented or ignored
  179. log.debug("(#{@pstack.conn.object_id}) GA, NOP or BRK sent")
  180. set_mode(:normal)
  181. when DM
  182. log.debug("(#{@pstack.conn.object_id}) Synch mode off")
  183. @synch = false
  184. set_mode(:normal)
  185. when EC
  186. next if @synch
  187. log.debug("(#{@pstack.conn.object_id}) EC sent")
  188. if buf.size > 1
  189. buf.slice!(-1)
  190. elsif @pstack.conn.inbuffer.size > 0
  191. @pstack.conn.inbuffer.slice(-1)
  192. end
  193. set_mode(:normal)
  194. when EL
  195. next if @synch
  196. log.debug("(#{@pstack.conn.object_id}) EL sent")
  197. p = buf.rindex("\n")
  198. if p
  199. buf.slice!(p+1..-1)
  200. else
  201. buf = ""
  202. p = @pstack.conn.inbuffer.rindex("\n")
  203. if p
  204. @pstack.conn.inbuffer.slice!(p+1..-1)
  205. end
  206. end
  207. set_mode(:normal)
  208. when DO, DONT, WILL, WONT
  209. if @sc.eos?
  210. @sc.unscan
  211. break
  212. end
  213. opt = @sc.get_byte
  214. case b.getbyte(0)
  215. when WILL
  216. replies_him(opt.getbyte(0),true)
  217. when WONT
  218. replies_him(opt.getbyte(0),false)
  219. when DO
  220. requests_us(opt.getbyte(0),true)
  221. when DONT
  222. requests_us(opt.getbyte(0),false)
  223. end
  224. # Update interesting things in ProtocolStack after negotiation
  225. case opt.getbyte(0)
  226. when ECHO
  227. @pstack.echo_on = enabled?(ECHO, :us)
  228. when BINARY
  229. @pstack.binary_on = enabled?(BINARY, :us)
  230. when ZMP
  231. @pstack.zmp_on = enabled?(ZMP, :us)
  232. end
  233. set_mode(:normal)
  234. when SB
  235. @sc.unscan
  236. break if @sc.check_until(/#{IAC.chr}#{SE.chr}/).nil?
  237. @sc.get_byte
  238. opt = @sc.get_byte
  239. data = @sc.scan_until(/#{IAC.chr}#{SE.chr}/).chop.chop
  240. parse_subneg(opt.getbyte(0),data)
  241. set_mode(:normal)
  242. else
  243. log.debug("(#{@pstack.conn.object_id}) Unknown Telnet command - #{b.getbyte(0)}")
  244. set_mode(:normal)
  245. end
  246. end
  247. end # while b
  248. @sc = nil if @sc.eos?
  249. buf
  250. end
  251. # The filter_out method filters output data
  252. # [+str+] The string to be processed
  253. # [+return+] The filtered data
  254. def filter_out(str)
  255. return '' if str.nil? || str.empty?
  256. if !@pstack.binary_on
  257. str.gsub!(/\n/, "\r\n")
  258. end
  259. str
  260. end
  261. ###### Custom public methods
  262. # Test to see if option is enabled
  263. # [+opt+] The Telnet option code
  264. # [+who+] The side to check :us or :him
  265. def enabled?(opt, who)
  266. option(opt)
  267. e = @state[opt].send(who)
  268. e == :yes ? true : false
  269. end
  270. # Test to see which state we prefer this option to be in
  271. # [+opt+] The Telnet option code
  272. def desired?(opt)
  273. st = @wopts[opt]
  274. st = false if st.nil?
  275. st
  276. end
  277. # Handle server-side echo
  278. # [+ch+] character string to echo
  279. def echo(ch)
  280. return if @server.service_type == :client # Never echo for server when client
  281. # Remove this if it makes sense for peer to peer
  282. if @pstack.echo_on
  283. if @pstack.hide_on && ch.getbyte(0) != CR
  284. @pstack.conn.sock.send('*',0)
  285. else
  286. @pstack.conn.sock.send(ch,0)
  287. end
  288. end
  289. end
  290. # Negotiate starting wanted options that imply subnegotation
  291. # So far only terminal type
  292. def init_subneg
  293. return if @init_tries > 20
  294. @init_tries += 1
  295. @wopts.each_key do |opt|
  296. next if !@sneg_opts.include?(opt)
  297. log.debug("(#{@pstack.conn.object_id}) Subnegotiation attempt for option #{opt}.")
  298. case opt
  299. when TTYPE
  300. who = :him
  301. else
  302. who = :us
  303. end
  304. if desired?(opt) == enabled?(opt, who)
  305. case opt
  306. when TTYPE
  307. @pstack.conn.sendmsg(IAC.chr + SB.chr + TTYPE.chr + 1.chr + IAC.chr + SE.chr)
  308. when ZMP
  309. log.info("(#{@pstack.conn.object_id}) ZMP successfully negotiated." )
  310. @pstack.conn.sendmsg("#{IAC.chr}#{SB.chr}#{ZMP.chr}" +
  311. "zmp.check#{NUL.chr}color.#{NUL.chr}" +
  312. "#{IAC.chr}#{SE.chr}")
  313. @pstack.conn.sendmsg("#{IAC.chr}#{SB.chr}#{ZMP.chr}" +
  314. "zmp.ident#{NUL.chr}WOE#{NUL.chr}#{Version}#{NUL.chr}A mud based on the TeensyMUD server#{NUL.chr}" +
  315. "#{IAC.chr}#{SE.chr}")
  316. @pstack.conn.sendmsg("#{IAC.chr}#{SB.chr}#{ZMP.chr}" +
  317. "zmp.ping#{NUL.chr}" +
  318. "#{IAC.chr}#{SE.chr}")
  319. @pstack.conn.sendmsg("#{IAC.chr}#{SB.chr}#{ZMP.chr}" +
  320. "zmp.input#{NUL.chr}\n I see you support...\n ZMP protocol\n#{NUL.chr}" +
  321. "#{IAC.chr}#{SE.chr}")
  322. end
  323. @sneg_opts.delete(opt)
  324. end
  325. end
  326. if @init_tries > 20
  327. log.debug("(#{@pstack.conn.object_id}) Telnet init_subneg option - Timed out after #{@init_tries} tries.")
  328. @sneg_opts = []
  329. @pstack.conn.set_initdone
  330. if !@pstack.terminal or @pstack.terminal.empty?
  331. @pstack.terminal = "dumb"
  332. end
  333. end
  334. end
  335. def send_naws
  336. return if !enabled?(NAWS, :us)
  337. ts = @pstack.query(:termsize)
  338. data = [ts[0]].pack('n') + [ts[1]].pack('n')
  339. data.gsub!(/#{IAC}/, IAC.chr + IAC.chr) # 255 needs to be doubled
  340. @pstack.conn.sendmsg(IAC.chr + SB.chr + NAWS.chr + data + IAC.chr + SE.chr)
  341. end
  342. private
  343. ###### Private methods
  344. def getopts(wopts)
  345. # supported options
  346. wopts.each do |op|
  347. case op
  348. when :ttype
  349. @wopts[TTYPE] = true
  350. when :echo
  351. @wopts[ECHO] = true
  352. when :sga
  353. @wopts[SGA] = true
  354. when :naws
  355. @wopts[NAWS] = true
  356. when :eorec
  357. @wopts[EOREC] = true
  358. when :binary
  359. @wopts[BINARY] = true
  360. when :zmp
  361. @wopts[ZMP] = true
  362. end
  363. end
  364. end
  365. # parse the subnegotiation data and save it
  366. # [+opt+] The Telnet option found
  367. # [+data+] The data found between SB OPTION and IAC SE
  368. def parse_subneg(opt,data)
  369. data.gsub!(/#{IAC}#{IAC}/, IAC.chr) # 255 needs to be undoubled from all data
  370. case opt
  371. when NAWS
  372. @pstack.twidth = data[0..1].unpack('n')[0]
  373. @pstack.theight = data[2..3].unpack('n')[0]
  374. @pstack.conn.publish(:termsize)
  375. log.debug("(#{@pstack.conn.object_id}) Terminal width #{@pstack.twidth} / height #{@pstack.theight}")
  376. when TTYPE
  377. if data.getbyte(0) == 0
  378. log.debug("(#{@pstack.conn.object_id}) Terminal type - #{data[1..-1]}")
  379. if !@ttype.include?(data[1..-1])
  380. # short-circuit choice because of Zmud
  381. if data[1..-1].downcase == 'zmud'
  382. @ttype << data[1..-1]
  383. @pstack.terminal = 'zmud'
  384. log.debug("(#{@pstack.conn.object_id}) Terminal choice - #{@pstack.terminal} in list #{@ttype.inspect}")
  385. end
  386. # short-circuit choice because of Windows telnet client
  387. if data[1..-1].downcase == 'vt100'
  388. @ttype << data[1..-1]
  389. @pstack.terminal = 'vt100'
  390. log.debug("(#{@pstack.conn.object_id}) Terminal choice - #{@pstack.terminal} in list #{@ttype.inspect}")
  391. end
  392. return if @pstack.terminal
  393. @ttype << data[1..-1]
  394. @pstack.conn.sendmsg(IAC.chr + SB.chr + TTYPE.chr + 1.chr + IAC.chr + SE.chr)
  395. else
  396. return if @pstack.terminal
  397. choose_terminal
  398. end
  399. elsif data.getbyte(0) == 1 # send - should only be called by :client
  400. return if !@pstack.terminal
  401. @pstack.conn.sendmsg(IAC.chr + SB.chr + TTYPE.chr + 0.chr + @pstack.terminal + IAC.chr + SE.chr)
  402. end
  403. when ZMP
  404. args = data.split("\0")
  405. cmd = args.shift
  406. handle_zmp(cmd,args)
  407. end
  408. end
  409. # Pick a preferred terminal
  410. # Order is vt100, vt999, ansi, xterm, or a recognized custom client
  411. # Should not pick vtnt as we dont handle it
  412. def choose_terminal
  413. if @ttype.empty?
  414. @pstack.terminal = "dumb"
  415. end
  416. # Pick most capable from list of terminals
  417. @pstack.terminal = @ttype.find {|t| t =~ /mushclient/i } if !@pstack.terminal
  418. @pstack.terminal = @ttype.find {|t| t =~ /simplemu/i } if !@pstack.terminal
  419. @pstack.terminal = @ttype.find {|t| t =~ /(zmud).*/i } if !@pstack.terminal
  420. @pstack.terminal = @ttype.find {|t| t =~ /linux/i } if !@pstack.terminal
  421. @pstack.terminal = @ttype.find {|t| t =~ /cygwin/i } if !@pstack.terminal
  422. @pstack.terminal = @ttype.find {|t| t =~ /(cons25).*/i } if !@pstack.terminal
  423. @pstack.terminal = @ttype.find {|t| t =~ /(xterm).*/i } if !@pstack.terminal
  424. @pstack.terminal = @ttype.find {|t| t =~ /(vt)[-]?100/i } if !@pstack.terminal
  425. @pstack.terminal = @ttype.find {|t| t =~ /(vt)[-]?\d+/i } if !@pstack.terminal
  426. @pstack.terminal = @ttype.find {|t| t =~ /(ansi).*/i } if !@pstack.terminal
  427. if @pstack.terminal && @ttype.last != @pstack.terminal # short circuit retraversal of options
  428. @ttype.each do |t|
  429. @pstack.conn.sendmsg(IAC.chr + SB.chr + TTYPE.chr + 1.chr + IAC.chr + SE.chr)
  430. break if t == @pstack.terminal
  431. end
  432. elsif @ttype.last != @pstack.terminal
  433. @pstack.terminal = 'dumb'
  434. end
  435. @pstack.terminal.downcase!
  436. # translate certain terminals to something meaningful
  437. case @pstack.terminal
  438. when /cygwin/i, /cons25/i, /linux/i, /dec-vt/i
  439. @pstack.terminal = 'vt100'
  440. when /ansis/i then
  441. @pstack.terminal = 'ansi'
  442. end
  443. log.debug("(#{@pstack.conn.object_id}) Terminal set to - #{@pstack.terminal} from list #{@ttype.inspect}")
  444. end
  445. # Get current parse mode
  446. # [+return+] The current parse mode
  447. def mode?
  448. return @mode
  449. end
  450. # set current parse mode
  451. # [+m+] Mode to set it to
  452. def set_mode(m)
  453. @mode = m
  454. end
  455. # Creates an option entry in our state table and sets its initial state
  456. def option(opt)
  457. return if @state.key?(opt)
  458. o = OpenStruct.new
  459. o.us = :no
  460. o.him = :no
  461. o.usq = :empty
  462. o.himq = :empty
  463. @state[opt] = o
  464. end
  465. # Ask the client to enable or disable an option.
  466. #
  467. # [+opt+] The option code
  468. # [+enable+] true for enable, false for disable
  469. def ask_him(opt, enable)
  470. log.debug("(#{@pstack.conn.object_id}) Requested Telnet option #{opt.to_s} set to #{enable.to_s}")
  471. initiate(opt, enable, :him)
  472. end
  473. # Offer the server to enable or disable an option
  474. #
  475. # [+opt+] The option code
  476. # [+enable+] true for enable, false for disable
  477. def offer_us(opt, enable)
  478. log.debug("(#{@pstack.conn.object_id}) Offered Telnet option #{opt.to_s} set to #{enable.to_s}")
  479. initiate(opt, enable, :us)
  480. end
  481. # Initiate a request to client. Called by ask_him or offer_us.
  482. #
  483. # [+opt+] The option code
  484. # [+enable+] true for enable, false for disable
  485. # [+who+] :him if asking client, :us if server offering
  486. def initiate(opt, enable, who)
  487. option(opt)
  488. case who
  489. when :him
  490. willdo = DO.chr
  491. wontdont = DONT.chr
  492. whoq = :himq
  493. when :us
  494. willdo = WILL.chr
  495. wontdont = WONT.chr
  496. whoq = :usq
  497. else
  498. # Error
  499. end
  500. case @state[opt].send(who)
  501. when :no
  502. if enable
  503. @state[opt].send("#{who}=", :wantyes)
  504. @pstack.conn.sendmsg(IAC.chr + willdo + opt.chr)
  505. else
  506. # Error already disabled
  507. log.error("(#{@pstack.conn.object_id}) Telnet negotiation: option #{opt.to_s} already disabled")
  508. end
  509. when :yes
  510. if enable
  511. # Error already enabled
  512. log.error("(#{@pstack.conn.object_id}) Telnet negotiation: option #{opt.to_s} already enabled")
  513. else
  514. @state[opt].send("#{who}=", :wantno)
  515. @pstack.conn.sendmsg(IAC.chr + wontdont + opt.chr)
  516. end
  517. when :wantno
  518. if enable
  519. case @state[opt].send(whoq)
  520. when :empty
  521. @state[opt].send("#{whoq}=", :opposite)
  522. when :opposite
  523. # Error already queued enable request
  524. log.error("(#{@pstack.conn.object_id}) Telnet negotiation: option #{opt.to_s} already queued enable request")
  525. end
  526. else
  527. case @state[opt].send(whoq)
  528. when :empty
  529. # Error already negotiating for disable
  530. log.error("(#{@pstack.conn.object_id}) Telnet negotiation: option #{opt.to_s} already negotiating for disable")
  531. when :opposite
  532. @state[opt].send("#{whoq}=", :empty)
  533. end
  534. end
  535. when :wantyes
  536. if enable
  537. case @state[opt].send(whoq)
  538. when :empty
  539. #Error already negotiating for enable
  540. log.error("(#{@pstack.conn.object_id}) Telnet negotiation: option #{opt.to_s} already negotiating for enable")
  541. when :opposite
  542. @state[opt].send("#{whoq}=", :empty)
  543. end
  544. else
  545. case @state[opt].send(whoq)
  546. when :empty
  547. @state[opt].send("#{whoq}=", :opposite)
  548. when :opposite
  549. #Error already queued for disable request
  550. log.error("(#{@pstack.conn.object_id}) Telnet negotiation: option #{opt.to_s} already queued for disable request")
  551. end
  552. end
  553. end
  554. end
  555. # Client replies WILL or WONT
  556. #
  557. # [+opt+] The option code
  558. # [+enable+] true for WILL answer, false for WONT answer
  559. def replies_him(opt, enable)
  560. log.debug("(#{@pstack.conn.object_id}) Client replies to Telnet option #{opt.to_s} set to #{enable.to_s}")
  561. response(opt, enable, :him)
  562. end
  563. # Client requests DO or DONT
  564. #
  565. # [+opt+] The option code
  566. # [+enable+] true for DO request, false for DONT request
  567. def requests_us(opt, enable)
  568. log.debug("(#{@pstack.conn.object_id}) Client requests Telnet option #{opt.to_s} set to #{enable.to_s}")
  569. response(opt, enable, :us)
  570. end
  571. # Handle client response. Called by requests_us or replies_him
  572. #
  573. # [+opt+] The option code
  574. # [+enable+] true for WILL answer, false for WONT answer
  575. # [+who+] :him if client replies, :us if client requests
  576. def response(opt, enable, who)
  577. option(opt)
  578. case who
  579. when :him
  580. willdo = DO.chr
  581. wontdont = DONT.chr
  582. whoq = :himq
  583. when :us
  584. willdo = WILL.chr
  585. wontdont = WONT.chr
  586. whoq = :usq
  587. else
  588. # Error
  589. end
  590. case @state[opt].send(who)
  591. when :no
  592. if enable
  593. if desired?(opt)
  594. # If we agree
  595. @state[opt].send("#{who}=", :yes)
  596. @pstack.conn.sendmsg(IAC.chr + willdo + opt.chr)
  597. log.debug("(#{@pstack.conn.object_id}) Telnet negotiation: agreed to enable option #{opt}")
  598. else
  599. # If we disagree
  600. @pstack.conn.sendmsg(IAC.chr + wontdont + opt.chr)
  601. log.debug("(#{@pstack.conn.object_id}) Telnet negotiation: disagreed to enable option #{opt}")
  602. end
  603. else
  604. # Ignore
  605. end
  606. when :yes
  607. if enable
  608. # Ignore
  609. else
  610. @state[opt].send("#{who}=", :no)
  611. @pstack.conn.sendmsg(IAC.chr + wontdont + opt.chr)
  612. end
  613. when :wantno
  614. if enable
  615. case @state[opt].send(whoq)
  616. when :empty
  617. #Error DONT/WONT answered by WILL/DO
  618. @state[opt].send("#{who}=", :no)
  619. when :opposite
  620. #Error DONT/WONT answered by WILL/DO
  621. @state[opt].send("#{who}=", :yes)
  622. @state[opt].send("#{whoq}=", :empty)
  623. end
  624. log.error("(#{@pstack.conn.object_id}) Telnet negotiation: option #{opt.to_s} DONT/WONT answered by WILL/DO")
  625. else
  626. case @state[opt].send(whoq)
  627. when :empty
  628. @state[opt].send("#{who}=", :no)
  629. log.debug("(#{@pstack.conn.object_id}) Telnet negotiation: agreed to disable option #{opt}")
  630. when :opposite
  631. @state[opt].send("#{who}=", :wantyes)
  632. @state[opt].send("#{whoq}=", :empty)
  633. @pstack.conn.sendmsg(IAC.chr + willdo + opt.chr)
  634. end
  635. end
  636. when :wantyes
  637. if enable
  638. case @state[opt].send(whoq)
  639. when :empty
  640. @state[opt].send("#{who}=", :yes)
  641. log.debug("(#{@pstack.conn.object_id}) Telnet negotiation: agreed to enable option #{opt}")
  642. when :opposite
  643. @state[opt].send("#{who}=", :wantno)
  644. @state[opt].send("#{whoq}=", :empty)
  645. @pstack.conn.sendmsg(IAC.chr + wontdont + opt.chr)
  646. end
  647. else
  648. case @state[opt].send(whoq)
  649. when :empty
  650. @state[opt].send("#{who}=", :no)
  651. log.debug("(#{@pstack.conn.object_id}) Telnet negotiation: agreed to disable option #{opt}")
  652. when :opposite
  653. @state[opt].send("#{who}=", :no)
  654. @state[opt].send("#{whoq}=", :empty)
  655. end
  656. end
  657. end
  658. end
  659. def handle_zmp(cmd,args)
  660. log.debug("(#{@pstack.conn.object_id}) ZMP command recieved - '#{cmd}' args: #{args.inspect}" )
  661. case cmd
  662. when "zmp.ping"
  663. @pstack.conn.sendmsg("#{IAC.chr}#{SB.chr}#{ZMP.chr}" +
  664. "zmp.time#{NUL.chr}#{Time.now.utc.strftime("%Y-%m-%d %H:%M:%S")}#{NUL.chr}" +
  665. "#{IAC.chr}#{SE.chr}")
  666. when "zmp.time"
  667. when "zmp.ident"
  668. # That's nice
  669. when "zmp.check"
  670. case args[0]
  671. when /zmp.*/
  672. # We support all 'zmp.' package and commands so..
  673. @pstack.conn.sendmsg("#{IAC.chr}#{SB.chr}#{ZMP.chr}" +
  674. "zmp.support#{NUL.chr}#{args[0]}{NUL.chr}" +
  675. "#{IAC.chr}#{SE.chr}")
  676. else
  677. @pstack.conn.sendmsg("#{IAC.chr}#{SB.chr}#{ZMP.chr}" +
  678. "zmp.no-support#{NUL.chr}#{args[0]}#{NUL.chr}" +
  679. "#{IAC.chr}#{SE.chr}")
  680. end
  681. when "zmp.support"
  682. when "zmp.no-support"
  683. when "zmp.input"
  684. # Now we just simply pass this whole load to the Character.parse
  685. # WARN: This means there is a possibility of out-of-order processing
  686. # of @inbuffer, though extremely unlikely.
  687. @pstack.conn.publish(args[0])
  688. end
  689. end
  690. end