# # telnet.rb # ver0.12 1998/06/01 # Wakou Aoyama # # == make new Telnet object # host = Telnet.new({"Binmode" => TRUE, default: TRUE # "Host" => "localhost", default: "localhost" # "Output_log" => "output_log", default: not output # "Port" => 23, default: 23 # "Prompt" => /[$%#>] $/, default: /[$%#>] $/ # "Telnetmode" => TRUE, default: TRUE # "Timeout" => 10, default: 10 # "Waittime" => 0}) default: 0 # # if set "Telnetmode" option 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. # # == wait for match # line = host.waitfor(/match/) # line = host.waitfor({"Match" => /match/, # "String" => "string", # "Timeout" => secs}) # if set "String" option. Match = Regexp.new(quote(string)) # # realtime output. of cource, set sync=TRUE or flush is necessary. # host.waitfor(/match/){|c| print c } # host.waitfor({"Match" => /match/, # "String" => "string", # "Timeout" => secs}){|c| print c} # # == send string and wait prompt # line = host.cmd("string") # line = host.cmd({"String" => "string", # "Prompt" => /[$%#>] $//, # "Timeout" => 10}) # # realtime output. of cource, set sync=TRUE or flush is necessary. # host.cmd("string"){|c| print c } # host.cmd({"String" => "string", # "Prompt" => /[$%#>] $//, # "Timeout" => 10}){|c| print c } # # == login # host.login("username", "password") # host.login({"Name" => "username", # "Password" => "password", # "Prompt" => /[$%#>] $/, # "Timeout" => 10}) # # realtime output. of cource, set sync=TRUE or flush is necessary. # host.login("username", "password"){|c| print c } # host.login({"Name" => "username", # "Password" => "password", # "Prompt" => /[$%#>] $/, # "Timeout" => 10}){|c| print c } # # and Telnet object has socket class methods # # == sample # localhost = Telnet.new({"Host" => "localhost", # "Timeout" => 10, # "Prompt" => /[$%#>] $/}) # localhost.login("username", "password"){|c| print c } # localhost.cmd("command"){|c| print c } # localhost.close require "socket" require "delegate" require "thread" class TimeOut < Exception end class Telnet < SimpleDelegator def timeout(sec) is_timeout = FALSE begin x = Thread.current y = Thread.start { sleep sec if x.alive? #print "timeout!\n" x.raise TimeOut, "timeout" end } begin yield rescue TimeOut is_timeout = TRUE end ensure Thread.kill y if y && y.alive? end is_timeout end # For those who are curious, here are some of the special characters # interpretted by the telnet protocol: # Name Octal Dec. Description CR = "\015" LF = "\012" EOL = CR + LF # /* end of line */ IAC = "\377" # 255 /* interpret as command: */ DONT = "\376" # 254 /* you are not to use option */ DO = "\375" # 253 /* please, you use option */ WONT = "\374" # 252 /* I won't use option */ WILL = "\373" # 251 /* I will use option */ # SB = "\372" # 250 /* interpret as subnegotiation */ # GA = "\371" # 249 /* you may reverse the line */ # EL = "\370" # 248 /* erase the current line */ # EC = "\367" # 247 /* erase the current character */ AYT = "\366" # 246 /* are you there */ # AO = "\365" # 245 /* abort output--but let prog finish */ # IP = "\364" # 244 /* interrupt process--permanently */ # BREAK = "\363" # 243 /* break */ # DM = "\362" # 242 /* data mark--for connect. cleaning */ # NOP = "\361" # 241 /* nop */ # SE = "\360" # 240 /* end sub negotiation */ # EOR = "\357" # 239 /* end of record (transparent mode) */ def initialize(options) @options = options @options["Binmode"] = TRUE if not @options.include?("Binmode") @options["Host"] = "localhost" if not @options.include?("Host") @options["Port"] = 23 if not @options.include?("Port") @options["Prompt"] = /[$%#>] $/ if not @options.include?("Prompt") @options["Telnetmode"] = TRUE if not @options.include?("Telnetmode") @options["Timeout"] = 10 if not @options.include?("Timeout") @options["Waittime"] = 0 if not @options.include?("Waittime") if @options.include?("Output_log") @log = File.open(@options["Output_log"], 'a+') @log.sync = TRUE @log.binmode if @options["Binmode"] end message = "Trying " + @options["Host"] + "...\n" STDOUT << message @log << message if @options.include?("Output_log") is_timeout = timeout(@options["Timeout"]){ begin @sock = TCPsocket.open(@options["Host"], @options["Port"]) rescue @log << $! << "\n" if @options.include?("Output_log") raise end } raise TimeOut, "timed-out; opening of the host" if is_timeout @sock.sync = TRUE @sock.binmode if @options["Binmode"] message = "Connected to " + @options["Host"] + ".\n" STDOUT << message @log << message if @options.include?("Output_log") super(@sock) end def preprocess(str) str.gsub!(/#{EOL}/no, "\n") # combine EOL into "\n" # respond to "IAC DO x" or "IAC DON'T x" with "IAC WON'T x" str.gsub!(/([^#{IAC}])?#{IAC}[#{DO}#{DONT}](.|\n)/no){ @sock << IAC << WONT << $2 $1 } # ignore "IAC WILL x" or "IAC WON'T x" str.gsub!(/([^#{IAC}])?#{IAC}[#{WILL}#{WONT}](.|\n)/no, '\1') # respond to "IAC AYT" (are you there) str.gsub!(/([^#{IAC}])?#{IAC}#{AYT}/no){ @sock << "nobody here but us pigeons" << CR $1 } str.gsub(/#{IAC}#{IAC}/no, IAC) # handle escaped IAC characters end def waitfor(options) timeout = @options["Timeout"] waittime = @options["Waittime"] if options.kind_of?(Hash) prompt = options["Prompt"] if options.include?("Prompt") timeout = options["Timeout"] if options.include?("Timeout") waittime = options["Waittime"] if options.include?("Waittime") prompt = Regexp.new( Regexp.quote(options["String"]) ) if options.include?("String") else prompt = options end line = '' until(not select([@sock], nil, nil, waittime) and prompt === line) raise TimeOut, "timed-out; wait for the next data" if not select([@sock], nil, nil, timeout) buf = '' begin buf = if @options["Telnetmode"] preprocess( @sock.sysread(1024 * 1024) ) else @sock.sysread(1024 * 1024) end rescue EOFError # End of file reached break ensure @log.print(buf) if @options.include?("Output_log") yield buf if iterator? line += buf end end line end def cmd(options) match = @options["Prompt"] timeout = @options["Timeout"] if options.kind_of?(Hash) string = options["String"] match = options["Match"] if options.include?("Match") timeout = options["Timeout"] if options.include?("Timeout") else string = options end select(nil, [@sock]) @sock << string.gsub(/\n/, CR) << CR if iterator? waitfor({"Prompt" => match, "Timeout" => timeout}){|c| yield c } else waitfor({"Prompt" => match, "Timeout" => timeout}) 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[: ]*$/){|c| yield c } line += cmd({"String" => username, "Match" => /Password[: ]*$/}){|c| yield c } line += cmd(password){|c| yield c } else line = waitfor(/login[: ]*$/) line += cmd({"String" => username, "Match" => /Password[: ]*$/}) line += cmd(password) end line end end