ruby--ruby/lib/telnet.rb

697 lines
18 KiB
Ruby

=begin
$Date: 1999/10/04 22:51:26 $
== SIMPLE TELNET CLIANT LIBRARY
telnet.rb
Version 1.00
Wakou Aoyama <wakou@fsinet.or.jp>
=== 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 <sinara@blade.nagaokaut.ac.jp>
- 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 <tadf@kt.rim.or.jp>
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 <tetsu@jpn.hp.com>
- 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