=begin $Date: 1999/10/04 22:51:26 $ == SIMPLE TELNET CLIANT LIBRARY telnet.rb Version 1.00 Wakou Aoyama === MAKE NEW TELNET OBJECT host = Telnet.new({"Binmode" => false, # default: false "Host" => "localhost", # default: "localhost" "Output_log" => "output_log", # default: not output "Dump_log" => "dump_log", # default: not output "Port" => 23, # default: 23 "Prompt" => /[$%#>] \z/n, # default: /[$%#>] \z/n "Telnetmode" => true, # default: true "Timeout" => 10, # default: 10 # if ignore timeout then set "Timeout" to false. "Waittime" => 0, # default: 0 "Proxy" => proxy}) # default: nil # proxy is Telnet or TCPsocket object Telnet object has socket class methods. if set "Telnetmode" option to false. not telnet command interpretation. "Waittime" is time to confirm "Prompt". There is a possibility that the same character as "Prompt" is included in the data, and, when the network or the host is very heavy, the value is enlarged. === STATUS OUTPUT host = Telnet.new({"Host" => "localhost"}){|c| print c } connection status output. example Trying localhost... Connected to localhost. === WAIT FOR MATCH line = host.waitfor(/match/) line = host.waitfor({"Match" => /match/, "String" => "string", "Timeout" => secs}) # if ignore timeout then set "Timeout" to false. if set "String" option, then Match == Regexp.new(quote("string")) ==== REALTIME OUTPUT host.waitfor(/match/){|c| print c } host.waitfor({"Match" => /match/, "String" => "string", "Timeout" => secs}){|c| print c} of cource, set sync=true or flush is necessary. === SEND STRING AND WAIT PROMPT line = host.cmd("string") line = host.cmd({"String" => "string", "Prompt" => /[$%#>] \z/n, "Timeout" => 10}) ==== REALTIME OUTPUT host.cmd("string"){|c| print c } host.cmd({"String" => "string", "Prompt" => /[$%#>] \z/n, "Timeout" => 10}){|c| print c } of cource, set sync=true or flush is necessary. === SEND STRING host.print("string") # == host.write("string\n") === TURN TELNET COMMAND INTERPRETATION host.telnetmode # turn on/off host.telnetmode(true) # on host.telnetmode(false) # off === TOGGLE NEWLINE TRANSLATION host.binmode # turn true/false host.binmode(true) # no translate newline host.binmode(false) # translate newline === LOGIN host.login("username", "password") host.login({"Name" => "username", "Password" => "password", "Prompt" => /[$%#>] \z/n, "Timeout" => 10}) if no password prompt. host.login("username") host.login({"Name" => "username", "Prompt" => /[$%#>] \z/n, "Timeout" => 10}) ==== REALTIME OUTPUT host.login("username", "password"){|c| print c } host.login({"Name" => "username", "Password" => "password", "Prompt" => /[$%#>] \z/n, "Timeout" => 10}){|c| print c } of cource, set sync=true or flush is necessary. == EXAMPLE === LOGIN AND SEND COMMAND localhost = Telnet.new({"Host" => "localhost", "Timeout" => 10, "Prompt" => /[$%#>] \z/n}) localhost.login("username", "password"){|c| print c } localhost.cmd("command"){|c| print c } localhost.close === CHECKS A POP SERVER TO SEE IF YOU HAVE MAIL pop = Telnet.new({"Host" => "your_destination_host_here", "Port" => 110, "Telnetmode" => false, "Prompt" => /^\+OK/n}) pop.cmd("user " + "your_username_here"){|c| print c} pop.cmd("pass " + "your_password_here"){|c| print c} pop.cmd("list"){|c| print c} == HISTORY === Version 1.00 1999/10/04 22:51:26 - bug fix: waitfor(preprocess) method thanks to Shin-ichiro Hara - add simple support for AO, DM, IP, NOP, SB, SE - COUTION! TimeOut --> TimeoutError === Version 0.50 1999/09/21 21:24:07 - add write method === Version 0.40 1999/09/17 17:41:41 - bug fix: preprocess method === Version 0.30 1999/09/14 23:09:05 - change prompt check order. not IO::select([@sock], nil, nil, waittime) and prompt === line --> prompt === line and not IO::select([@sock], nil, nil, waittime) === Version 0.24 1999/09/13 22:28:33 - Telnet#login if ommit password, then not require password prompt. === Version 0.232 1999/08/10 05:20:21 - STATUS OUTPUT sample code typo. thanks to Tadayoshi Funaba host = Telnet.new({"Hosh" => "localhost"){|c| print c } --> host = Telnet.new({"Host" => "localhost"){|c| print c } === Version 0.231 1999/07/16 13:39:42 - TRUE --> true, FALSE --> false === Version 0.23 1999/07/15 22:32:09 - waitfor: if end of file reached, then return nil. === Version 0.22 1999/06/29 09:08:51 - new, waitfor, cmd: {"Timeout" => false} # ignore timeout === Version 0.21 1999/06/28 18:18:55 - waitfor: not rescue (EOFError) === Version 0.20 1999/06/04 06:24:58 - waitfor: support for divided telnet command === Version 0.181 1999/05/22 - bug fix: print method === Version 0.18 1999/05/14 - respond to "IAC WON'T SGA" with "IAC DON'T SGA" - DON'T SGA : end of line --> CR + LF - bug fix: preprocess method === Version 0.17 1999/04/30 - bug fix: $! + "\n" --> $!.to_s + "\n" === Version 0.163 1999/04/11 - STDOUT.write(message) --> yield(message) if iterator? === Version 0.162 1999/03/17 - add "Proxy" option - required timeout.rb === Version 0.161 1999/02/03 - select --> IO::select === Version 0.16 1998/10/09 - preprocess method change for the better - add binmode method. - change default Binmode. TRUE --> FALSE === Version 0.15 1998/10/04 - add telnetmode method. === Version 0.141 1998/09/22 - change default prompt. /[$%#>] $/ --> /[$%#>] \Z/ === Version 0.14 1998/09/01 - IAC WILL SGA send EOL --> CR+NULL - IAC WILL SGA IAC DO BIN send EOL --> CR - NONE send EOL --> LF - add Dump_log option. === Version 0.13 1998/08/25 - add print method. === Version 0.122 1998/08/05 - support for HP-UX 10.20 thanks to WATANABE Tetsuya - socket.<< --> socket.write === Version 0.121 1998/07/15 - string.+= --> string.concat === Version 0.12 1998/06/01 - add timeout, waittime. === Version 0.11 1998/04/21 - add realtime output. === Version 0.10 1998/04/13 - first release. =end require "socket" require "delegate" require "thread" require "timeout" class Telnet < SimpleDelegator IAC = 255.chr # "\377" # interpret as command: DONT = 254.chr # "\376" # you are not to use option DO = 253.chr # "\375" # please, you use option WONT = 252.chr # "\374" # I won't use option WILL = 251.chr # "\373" # I will use option SB = 250.chr # "\372" # interpret as subnegotiation GA = 249.chr # "\371" # you may reverse the line EL = 248.chr # "\370" # erase the current line EC = 247.chr # "\367" # erase the current character AYT = 246.chr # "\366" # are you there AO = 245.chr # "\365" # abort output--but let prog finish IP = 244.chr # "\364" # interrupt process--permanently BREAK = 243.chr # "\363" # break DM = 242.chr # "\362" # data mark--for connect. cleaning NOP = 241.chr # "\361" # nop SE = 240.chr # "\360" # end sub negotiation EOR = 239.chr # "\357" # end of record (transparent mode) ABORT = 238.chr # "\356" # Abort process SUSP = 237.chr # "\355" # Suspend process EOF = 236.chr # "\354" # End of file SYNCH = 242.chr # "\362" # for telfunc calls OPT_BINARY = 0.chr # "\000" # Binary Transmission OPT_ECHO = 1.chr # "\001" # Echo OPT_RCP = 2.chr # "\002" # Reconnection OPT_SGA = 3.chr # "\003" # Suppress Go Ahead OPT_NAMS = 4.chr # "\004" # Approx Message Size Negotiation OPT_STATUS = 5.chr # "\005" # Status OPT_TM = 6.chr # "\006" # Timing Mark OPT_RCTE = 7.chr # "\a" # Remote Controlled Trans and Echo OPT_NAOL = 8.chr # "\010" # Output Line Width OPT_NAOP = 9.chr # "\t" # Output Page Size OPT_NAOCRD = 10.chr # "\n" # Output Carriage-Return Disposition OPT_NAOHTS = 11.chr # "\v" # Output Horizontal Tab Stops OPT_NAOHTD = 12.chr # "\f" # Output Horizontal Tab Disposition OPT_NAOFFD = 13.chr # "\r" # Output Formfeed Disposition OPT_NAOVTS = 14.chr # "\016" # Output Vertical Tabstops OPT_NAOVTD = 15.chr # "\017" # Output Vertical Tab Disposition OPT_NAOLFD = 16.chr # "\020" # Output Linefeed Disposition OPT_XASCII = 17.chr # "\021" # Extended ASCII OPT_LOGOUT = 18.chr # "\022" # Logout OPT_BM = 19.chr # "\023" # Byte Macro OPT_DET = 20.chr # "\024" # Data Entry Terminal OPT_SUPDUP = 21.chr # "\025" # SUPDUP OPT_SUPDUPOUTPUT = 22.chr # "\026" # SUPDUP Output OPT_SNDLOC = 23.chr # "\027" # Send Location OPT_TTYPE = 24.chr # "\030" # Terminal Type OPT_EOR = 25.chr # "\031" # End of Record OPT_TUID = 26.chr # "\032" # TACACS User Identification OPT_OUTMRK = 27.chr # "\e" # Output Marking OPT_TTYLOC = 28.chr # "\034" # Terminal Location Number OPT_3270REGIME = 29.chr # "\035" # Telnet 3270 Regime OPT_X3PAD = 30.chr # "\036" # X.3 PAD OPT_NAWS = 31.chr # "\037" # Negotiate About Window Size OPT_TSPEED = 32.chr # " " # Terminal Speed OPT_LFLOW = 33.chr # "!" # Remote Flow Control OPT_LINEMODE = 34.chr # "\"" # Linemode OPT_XDISPLOC = 35.chr # "#" # X Display Location OPT_OLD_ENVIRON = 36.chr # "$" # Environment Option OPT_AUTHENTICATION = 37.chr # "%" # Authentication Option OPT_ENCRYPT = 38.chr # "&" # Encryption Option OPT_NEW_ENVIRON = 39.chr # "'" # New Environment Option OPT_EXOPL = 255.chr # "\377" # Extended-Options-List NULL = "\000" CR = "\015" LF = "\012" EOL = CR + LF v = $-v $-v = false VERSION = "1.00" RELEASE_DATE = "$Date: 1999/10/04 22:51:26 $" $-v = v def initialize(options) @options = options @options["Binmode"] = false unless @options.key?("Binmode") @options["Host"] = "localhost" unless @options.key?("Host") @options["Port"] = 23 unless @options.key?("Port") @options["Prompt"] = /[$%#>] \z/n unless @options.key?("Prompt") @options["Telnetmode"] = true unless @options.key?("Telnetmode") @options["Timeout"] = 10 unless @options.key?("Timeout") @options["Waittime"] = 0 unless @options.key?("Waittime") @telnet_option = { "SGA" => false, "BINARY" => false } if @options.key?("Output_log") @log = File.open(@options["Output_log"], 'a+') @log.sync = true @log.binmode end if @options.key?("Dump_log") @dumplog = File.open(@options["Dump_log"], 'a+') @dumplog.sync = true @dumplog.binmode end if @options.key?("Proxy") if @options["Proxy"].kind_of?(Telnet) @sock = @options["Proxy"].sock elsif @options["Proxy"].kind_of?(TCPsocket) @sock = @options["Proxy"] else raise "Error; Proxy is Telnet or TCPSocket object." end else message = "Trying " + @options["Host"] + "...\n" yield(message) if iterator? @log.write(message) if @options.key?("Output_log") @dumplog.write(message) if @options.key?("Dump_log") begin if @options["Timeout"] == false @sock = TCPsocket.open(@options["Host"], @options["Port"]) else timeout(@options["Timeout"]){ @sock = TCPsocket.open(@options["Host"], @options["Port"]) } end rescue TimeoutError raise TimeoutError, "timed-out; opening of the host" rescue @log.write($!.to_s + "\n") if @options.key?("Output_log") @dumplog.write($!.to_s + "\n") if @options.key?("Dump_log") raise end @sock.sync = true @sock.binmode message = "Connected to " + @options["Host"] + ".\n" yield(message) if iterator? @log.write(message) if @options.key?("Output_log") @dumplog.write(message) if @options.key?("Dump_log") end super(@sock) end # initialize attr :sock def telnetmode(mode = 'turn') if 'turn' == mode @options["Telnetmode"] = @options["Telnetmode"] ? false : true else @options["Telnetmode"] = mode ? true : false end end def binmode(mode = 'turn') if 'turn' == mode @options["Binmode"] = @options["Binmode"] ? false : true else @options["Binmode"] = mode ? true : false end end def preprocess(string) str = string.dup # combine CR+NULL into CR str.gsub!(/#{CR}#{NULL}/no, CR) if @options["Telnetmode"] # combine EOL into "\n" str.gsub!(/#{EOL}/no, "\n") unless @options["Binmode"] str.gsub!(/#{IAC}( [#{IAC}#{AO}#{AYT}#{DM}#{IP}#{NOP}]| [#{DO}#{DONT}#{WILL}#{WONT}] [#{OPT_BINARY}-#{OPT_NEW_ENVIRON}#{OPT_EXOPL}]| #{SB}[^#{IAC}]*#{IAC}#{SE} )/xno){ if IAC == $1 # handle escaped IAC characters IAC elsif AYT == $1 # respond to "IAC AYT" (are you there) self.write("nobody here but us pigeons" + EOL) '' elsif DO[0] == $1[0] # respond to "IAC DO x" if OPT_BINARY[0] == $1[1] @telnet_option["BINARY"] = true self.write(IAC + WILL + OPT_BINARY) else self.write(IAC + WONT + $1[1..1]) end '' elsif DONT[0] == $1[0] # respond to "IAC DON'T x" with "IAC WON'T x" self.write(IAC + WONT + $1[1..1]) '' elsif WILL[0] == $1[0] # respond to "IAC WILL x" if OPT_ECHO[0] == $1[1] self.write(IAC + DO + OPT_ECHO) elsif OPT_SGA[0] == $1[1] @telnet_option["SGA"] = true self.write(IAC + DO + OPT_SGA) end '' elsif WONT[0] == $1[0] # respond to "IAC WON'T x" if OPT_ECHO[0] == $1[1] self.write(IAC + DONT + OPT_ECHO) elsif OPT_SGA[0] == $1[1] @telnet_option["SGA"] = false self.write(IAC + DONT + OPT_SGA) end '' else '' end } str end # preprocess def waitfor(options) time_out = @options["Timeout"] waittime = @options["Waittime"] if options.kind_of?(Hash) prompt = if options.key?("Match") options["Match"] elsif options.key?("Prompt") options["Prompt"] elsif options.key?("String") Regexp.new( Regexp.quote(options["String"]) ) end time_out = options["Timeout"] if options.key?("Timeout") waittime = options["Waittime"] if options.key?("Waittime") else prompt = options end if time_out == false time_out = nil end line = '' buf = '' rest = '' until(prompt === line and not IO::select([@sock], nil, nil, waittime)) unless IO::select([@sock], nil, nil, time_out) raise TimeoutError, "timed-out; wait for the next data" end begin c = @sock.sysread(1024 * 1024) @dumplog.print(c) if @options.key?("Dump_log") if @options["Telnetmode"] if Integer(c.rindex(/#{IAC}#{SE}/no)) < Integer(c.rindex(/#{IAC}#{SB}/no)) buf = preprocess(rest + c[0 ... c.rindex(/#{IAC}#{SB}/no)]) rest = c[c.rindex(/#{IAC}#{SB}/no) .. -1] elsif pt = c.rindex(/#{IAC}[^#{IAC}#{AO}#{AYT}#{DM}#{IP}#{NOP}]?\z/no) buf = preprocess(rest + c[0 ... pt]) rest = c[pt .. -1] else buf = preprocess(c) rest = '' end end @log.print(buf) if @options.key?("Output_log") line.concat(buf) yield buf if iterator? rescue EOFError # End of file reached if line == '' line = nil yield nil if iterator? end break end end line end def write(string) length = string.length while 0 < length IO::select(nil, [@sock]) length -= @sock.syswrite(string) end end def print(string) str = string.dup + "\n" str.gsub!(/#{IAC}/no, IAC + IAC) if @options["Telnetmode"] unless @options["Binmode"] if @telnet_option["BINARY"] and @telnet_option["SGA"] # IAC WILL SGA IAC DO BIN send EOL --> CR str.gsub!(/\n/n, CR) elsif @telnet_option["SGA"] # IAC WILL SGA send EOL --> CR+NULL str.gsub!(/\n/n, CR + NULL) else # NONE send EOL --> CR+LF str.gsub!(/\n/n, EOL) end end self.write(str) end def cmd(options) match = @options["Prompt"] time_out = @options["Timeout"] if options.kind_of?(Hash) string = options["String"] match = options["Match"] if options.key?("Match") time_out = options["Timeout"] if options.key?("Timeout") else string = options end self.print(string) if iterator? waitfor({"Prompt" => match, "Timeout" => time_out}){|c| yield c } else waitfor({"Prompt" => match, "Timeout" => time_out}) end end def login(options, password = nil) if options.kind_of?(Hash) username = options["Name"] password = options["Password"] else username = options end if iterator? line = waitfor(/login[: ]*\z/n){|c| yield c } if password line.concat( cmd({"String" => username, "Match" => /Password[: ]*\z/n}){|c| yield c } ) line.concat( cmd(password){|c| yield c } ) else line.concat( cmd(username){|c| yield c } ) end else line = waitfor(/login[: ]*\z/n) if password line.concat( cmd({"String" => username, "Match" => /Password[: ]*\z/n}) ) line.concat( cmd(password) ) else line.concat( cmd(username) ) end end line end end