diff options
Diffstat (limited to 'lib/telnet.rb')
-rw-r--r-- | lib/telnet.rb | 636 |
1 files changed, 636 insertions, 0 deletions
diff --git a/lib/telnet.rb b/lib/telnet.rb new file mode 100644 index 0000000000..96e2cac100 --- /dev/null +++ b/lib/telnet.rb @@ -0,0 +1,636 @@ +=begin +$Date: 1999/08/10 05:20:21 $ + +== SIMPLE TELNET CLIANT LIBRARY + +telnet.rb + +Version 0.232 + +Wakou Aoyama <[email protected]> + + +=== 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") + + +=== 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}) + + +==== 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 0.232 + +1999/08/10 05:20:21 + +- STATUS OUTPUT sample code typo. thanks to Tadayoshi Funaba <[email protected]> + 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 <[email protected]> +- 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" +TimeOut = TimeoutError + +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 = "0.232" + RELEASE_DATE = "$Date: 1999/08/10 05:20:21 $" +$-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 TimeOut, "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"] + + # respond to "IAC DO x" + str.gsub!(/([^#{IAC}]?)#{IAC}#{DO}([#{OPT_BINARY}-#{OPT_NEW_ENVIRON}#{OPT_EXOPL}])/no){ + if OPT_BINARY == $2 + @telnet_option["BINARY"] = true + @sock.write(IAC + WILL + OPT_BINARY) + else + @sock.write(IAC + WONT + $2) + end + $1 + } + + # respond to "IAC DON'T x" with "IAC WON'T x" + str.gsub!(/([^#{IAC}]?)#{IAC}#{DONT}([#{OPT_BINARY}-#{OPT_NEW_ENVIRON}#{OPT_EXOPL}])/no){ + @sock.write(IAC + WONT + $2) + $1 + } + + # respond to "IAC WILL x" + str.gsub!(/([^#{IAC}]?)#{IAC}#{WILL}([#{OPT_BINARY}-#{OPT_NEW_ENVIRON}#{OPT_EXOPL}])/no){ + if OPT_ECHO == $2 + @sock.write(IAC + DO + OPT_ECHO) + elsif OPT_SGA == $2 + @telnet_option["SGA"] = true + @sock.write(IAC + DO + OPT_SGA) + end + $1 + } + + # respond to "IAC WON'T x" + str.gsub!(/([^#{IAC}]?)#{IAC}#{WONT}([#{OPT_BINARY}-#{OPT_NEW_ENVIRON}#{OPT_EXOPL}])/no){ + if OPT_ECHO == $2 + @sock.write(IAC + DONT + OPT_ECHO) + elsif OPT_SGA == $2 + @telnet_option["SGA"] = false + @sock.write(IAC + DONT + OPT_SGA) + end + $1 + } + + # respond to "IAC AYT" (are you there) + str.gsub!(/([^#{IAC}]?)#{IAC}#{AYT}/no){ + @sock.write("nobody here but us pigeons" + EOL) + $1 + } + + str.gsub!(/#{IAC}#{IAC}/no, IAC) # handle escaped IAC characters + + 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 = '' + until(not IO::select([@sock], nil, nil, waittime) and prompt === line) + unless IO::select([@sock], nil, nil, time_out) + raise TimeOut, "timed-out; wait for the next data" + end + begin + c = @sock.sysread(1024 * 1024) + @dumplog.print(c) if @options.key?("Dump_log") + buf.concat c + if @options["Telnetmode"] + buf = preprocess(buf) + if /#{IAC}.?\z/no === buf + next + end + end + @log.print(buf) if @options.key?("Output_log") + yield buf if iterator? + line.concat(buf) + buf = '' + rescue EOFError # End of file reached + if line == '' + line = nil + yield nil if iterator? + end + break + end + end + line + 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 + + @sock.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 + + IO::select(nil, [@sock]) + 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 = '') + 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 } + line.concat( cmd({"String" => username, + "Match" => /Password[: ]*\z/n}){|c| yield c } ) + line.concat( cmd(password){|c| yield c } ) + else + line = waitfor(/login[: ]*\z/n) + line.concat( cmd({"String" => username, + "Match" => /Password[: ]*\z/n}) ) + line.concat( cmd(password) ) + end + line + end + +end |