1# = net/telnet.rb - Simple Telnet Client Library 2# 3# Author:: Wakou Aoyama <wakou@ruby-lang.org> 4# Documentation:: William Webber and Wakou Aoyama 5# 6# This file holds the class Net::Telnet, which provides client-side 7# telnet functionality. 8# 9# For documentation, see Net::Telnet. 10# 11 12require "net/protocol" 13require "English" 14 15module Net 16 17 # 18 # == Net::Telnet 19 # 20 # Provides telnet client functionality. 21 # 22 # This class also has, through delegation, all the methods of a 23 # socket object (by default, a +TCPSocket+, but can be set by the 24 # +Proxy+ option to <tt>new()</tt>). This provides methods such as 25 # <tt>close()</tt> to end the session and <tt>sysread()</tt> to read 26 # data directly from the host, instead of via the <tt>waitfor()</tt> 27 # mechanism. Note that if you do use <tt>sysread()</tt> directly 28 # when in telnet mode, you should probably pass the output through 29 # <tt>preprocess()</tt> to extract telnet command sequences. 30 # 31 # == Overview 32 # 33 # The telnet protocol allows a client to login remotely to a user 34 # account on a server and execute commands via a shell. The equivalent 35 # is done by creating a Net::Telnet class with the +Host+ option 36 # set to your host, calling #login() with your user and password, 37 # issuing one or more #cmd() calls, and then calling #close() 38 # to end the session. The #waitfor(), #print(), #puts(), and 39 # #write() methods, which #cmd() is implemented on top of, are 40 # only needed if you are doing something more complicated. 41 # 42 # A Net::Telnet object can also be used to connect to non-telnet 43 # services, such as SMTP or HTTP. In this case, you normally 44 # want to provide the +Port+ option to specify the port to 45 # connect to, and set the +Telnetmode+ option to false to prevent 46 # the client from attempting to interpret telnet command sequences. 47 # Generally, #login() will not work with other protocols, and you 48 # have to handle authentication yourself. 49 # 50 # For some protocols, it will be possible to specify the +Prompt+ 51 # option once when you create the Telnet object and use #cmd() calls; 52 # for others, you will have to specify the response sequence to 53 # look for as the Match option to every #cmd() call, or call 54 # #puts() and #waitfor() directly; for yet others, you will have 55 # to use #sysread() instead of #waitfor() and parse server 56 # responses yourself. 57 # 58 # It is worth noting that when you create a new Net::Telnet object, 59 # you can supply a proxy IO channel via the Proxy option. This 60 # can be used to attach the Telnet object to other Telnet objects, 61 # to already open sockets, or to any read-write IO object. This 62 # can be useful, for instance, for setting up a test fixture for 63 # unit testing. 64 # 65 # == Examples 66 # 67 # === Log in and send a command, echoing all output to stdout 68 # 69 # localhost = Net::Telnet::new("Host" => "localhost", 70 # "Timeout" => 10, 71 # "Prompt" => /[$%#>] \z/n) 72 # localhost.login("username", "password") { |c| print c } 73 # localhost.cmd("command") { |c| print c } 74 # localhost.close 75 # 76 # 77 # === Check a POP server to see if you have mail 78 # 79 # pop = Net::Telnet::new("Host" => "your_destination_host_here", 80 # "Port" => 110, 81 # "Telnetmode" => false, 82 # "Prompt" => /^\+OK/n) 83 # pop.cmd("user " + "your_username_here") { |c| print c } 84 # pop.cmd("pass " + "your_password_here") { |c| print c } 85 # pop.cmd("list") { |c| print c } 86 # 87 # == References 88 # 89 # There are a large number of RFCs relevant to the Telnet protocol. 90 # RFCs 854-861 define the base protocol. For a complete listing 91 # of relevant RFCs, see 92 # http://www.omnifarious.org/~hopper/technical/telnet-rfc.html 93 # 94 class Telnet 95 96 # :stopdoc: 97 IAC = 255.chr # "\377" # "\xff" # interpret as command 98 DONT = 254.chr # "\376" # "\xfe" # you are not to use option 99 DO = 253.chr # "\375" # "\xfd" # please, you use option 100 WONT = 252.chr # "\374" # "\xfc" # I won't use option 101 WILL = 251.chr # "\373" # "\xfb" # I will use option 102 SB = 250.chr # "\372" # "\xfa" # interpret as subnegotiation 103 GA = 249.chr # "\371" # "\xf9" # you may reverse the line 104 EL = 248.chr # "\370" # "\xf8" # erase the current line 105 EC = 247.chr # "\367" # "\xf7" # erase the current character 106 AYT = 246.chr # "\366" # "\xf6" # are you there 107 AO = 245.chr # "\365" # "\xf5" # abort output--but let prog finish 108 IP = 244.chr # "\364" # "\xf4" # interrupt process--permanently 109 BREAK = 243.chr # "\363" # "\xf3" # break 110 DM = 242.chr # "\362" # "\xf2" # data mark--for connect. cleaning 111 NOP = 241.chr # "\361" # "\xf1" # nop 112 SE = 240.chr # "\360" # "\xf0" # end sub negotiation 113 EOR = 239.chr # "\357" # "\xef" # end of record (transparent mode) 114 ABORT = 238.chr # "\356" # "\xee" # Abort process 115 SUSP = 237.chr # "\355" # "\xed" # Suspend process 116 EOF = 236.chr # "\354" # "\xec" # End of file 117 SYNCH = 242.chr # "\362" # "\xf2" # for telfunc calls 118 119 OPT_BINARY = 0.chr # "\000" # "\x00" # Binary Transmission 120 OPT_ECHO = 1.chr # "\001" # "\x01" # Echo 121 OPT_RCP = 2.chr # "\002" # "\x02" # Reconnection 122 OPT_SGA = 3.chr # "\003" # "\x03" # Suppress Go Ahead 123 OPT_NAMS = 4.chr # "\004" # "\x04" # Approx Message Size Negotiation 124 OPT_STATUS = 5.chr # "\005" # "\x05" # Status 125 OPT_TM = 6.chr # "\006" # "\x06" # Timing Mark 126 OPT_RCTE = 7.chr # "\a" # "\x07" # Remote Controlled Trans and Echo 127 OPT_NAOL = 8.chr # "\010" # "\x08" # Output Line Width 128 OPT_NAOP = 9.chr # "\t" # "\x09" # Output Page Size 129 OPT_NAOCRD = 10.chr # "\n" # "\x0a" # Output Carriage-Return Disposition 130 OPT_NAOHTS = 11.chr # "\v" # "\x0b" # Output Horizontal Tab Stops 131 OPT_NAOHTD = 12.chr # "\f" # "\x0c" # Output Horizontal Tab Disposition 132 OPT_NAOFFD = 13.chr # "\r" # "\x0d" # Output Formfeed Disposition 133 OPT_NAOVTS = 14.chr # "\016" # "\x0e" # Output Vertical Tabstops 134 OPT_NAOVTD = 15.chr # "\017" # "\x0f" # Output Vertical Tab Disposition 135 OPT_NAOLFD = 16.chr # "\020" # "\x10" # Output Linefeed Disposition 136 OPT_XASCII = 17.chr # "\021" # "\x11" # Extended ASCII 137 OPT_LOGOUT = 18.chr # "\022" # "\x12" # Logout 138 OPT_BM = 19.chr # "\023" # "\x13" # Byte Macro 139 OPT_DET = 20.chr # "\024" # "\x14" # Data Entry Terminal 140 OPT_SUPDUP = 21.chr # "\025" # "\x15" # SUPDUP 141 OPT_SUPDUPOUTPUT = 22.chr # "\026" # "\x16" # SUPDUP Output 142 OPT_SNDLOC = 23.chr # "\027" # "\x17" # Send Location 143 OPT_TTYPE = 24.chr # "\030" # "\x18" # Terminal Type 144 OPT_EOR = 25.chr # "\031" # "\x19" # End of Record 145 OPT_TUID = 26.chr # "\032" # "\x1a" # TACACS User Identification 146 OPT_OUTMRK = 27.chr # "\e" # "\x1b" # Output Marking 147 OPT_TTYLOC = 28.chr # "\034" # "\x1c" # Terminal Location Number 148 OPT_3270REGIME = 29.chr # "\035" # "\x1d" # Telnet 3270 Regime 149 OPT_X3PAD = 30.chr # "\036" # "\x1e" # X.3 PAD 150 OPT_NAWS = 31.chr # "\037" # "\x1f" # Negotiate About Window Size 151 OPT_TSPEED = 32.chr # " " # "\x20" # Terminal Speed 152 OPT_LFLOW = 33.chr # "!" # "\x21" # Remote Flow Control 153 OPT_LINEMODE = 34.chr # "\"" # "\x22" # Linemode 154 OPT_XDISPLOC = 35.chr # "#" # "\x23" # X Display Location 155 OPT_OLD_ENVIRON = 36.chr # "$" # "\x24" # Environment Option 156 OPT_AUTHENTICATION = 37.chr # "%" # "\x25" # Authentication Option 157 OPT_ENCRYPT = 38.chr # "&" # "\x26" # Encryption Option 158 OPT_NEW_ENVIRON = 39.chr # "'" # "\x27" # New Environment Option 159 OPT_EXOPL = 255.chr # "\377" # "\xff" # Extended-Options-List 160 161 NULL = "\000" 162 CR = "\015" 163 LF = "\012" 164 EOL = CR + LF 165 REVISION = '$Id: telnet.rb 35304 2012-04-11 21:20:51Z drbrain $' 166 # :startdoc: 167 168 # 169 # Creates a new Net::Telnet object. 170 # 171 # Attempts to connect to the host (unless the Proxy option is 172 # provided: see below). If a block is provided, it is yielded 173 # status messages on the attempt to connect to the server, of 174 # the form: 175 # 176 # Trying localhost... 177 # Connected to localhost. 178 # 179 # +options+ is a hash of options. The following example lists 180 # all options and their default values. 181 # 182 # host = Net::Telnet::new( 183 # "Host" => "localhost", # default: "localhost" 184 # "Port" => 23, # default: 23 185 # "Binmode" => false, # default: false 186 # "Output_log" => "output_log", # default: nil (no output) 187 # "Dump_log" => "dump_log", # default: nil (no output) 188 # "Prompt" => /[$%#>] \z/n, # default: /[$%#>] \z/n 189 # "Telnetmode" => true, # default: true 190 # "Timeout" => 10, # default: 10 191 # # if ignore timeout then set "Timeout" to false. 192 # "Waittime" => 0, # default: 0 193 # "Proxy" => proxy # default: nil 194 # # proxy is Net::Telnet or IO object 195 # ) 196 # 197 # The options have the following meanings: 198 # 199 # Host:: the hostname or IP address of the host to connect to, as a String. 200 # Defaults to "localhost". 201 # 202 # Port:: the port to connect to. Defaults to 23. 203 # 204 # Binmode:: if false (the default), newline substitution is performed. 205 # Outgoing LF is 206 # converted to CRLF, and incoming CRLF is converted to LF. If 207 # true, this substitution is not performed. This value can 208 # also be set with the #binmode() method. The 209 # outgoing conversion only applies to the #puts() and #print() 210 # methods, not the #write() method. The precise nature of 211 # the newline conversion is also affected by the telnet options 212 # SGA and BIN. 213 # 214 # Output_log:: the name of the file to write connection status messages 215 # and all received traffic to. In the case of a proper 216 # Telnet session, this will include the client input as 217 # echoed by the host; otherwise, it only includes server 218 # responses. Output is appended verbatim to this file. 219 # By default, no output log is kept. 220 # 221 # Dump_log:: as for Output_log, except that output is written in hexdump 222 # format (16 bytes per line as hex pairs, followed by their 223 # printable equivalent), with connection status messages 224 # preceded by '#', sent traffic preceded by '>', and 225 # received traffic preceded by '<'. By default, not dump log 226 # is kept. 227 # 228 # Prompt:: a regular expression matching the host's command-line prompt 229 # sequence. This is needed by the Telnet class to determine 230 # when the output from a command has finished and the host is 231 # ready to receive a new command. By default, this regular 232 # expression is /[$%#>] \z/n. 233 # 234 # Telnetmode:: a boolean value, true by default. In telnet mode, 235 # traffic received from the host is parsed for special 236 # command sequences, and these sequences are escaped 237 # in outgoing traffic sent using #puts() or #print() 238 # (but not #write()). If you are using the Net::Telnet 239 # object to connect to a non-telnet service (such as 240 # SMTP or POP), this should be set to "false" to prevent 241 # undesired data corruption. This value can also be set 242 # by the #telnetmode() method. 243 # 244 # Timeout:: the number of seconds to wait before timing out both the 245 # initial attempt to connect to host (in this constructor), 246 # which raises a Net::OpenTimeout, and all attempts to read data 247 # from the host, which raises a Net::ReadTimeout (in #waitfor(), 248 # #cmd(), and #login()). The default value is 10 seconds. 249 # You can disable the timeout by setting this value to false. 250 # In this case, the connect attempt will eventually timeout 251 # on the underlying connect(2) socket call with an 252 # Errno::ETIMEDOUT error (but generally only after a few 253 # minutes), but other attempts to read data from the host 254 # will hang indefinitely if no data is forthcoming. 255 # 256 # Waittime:: the amount of time to wait after seeing what looks like a 257 # prompt (that is, received data that matches the Prompt 258 # option regular expression) to see if more data arrives. 259 # If more data does arrive in this time, Net::Telnet assumes 260 # that what it saw was not really a prompt. This is to try to 261 # avoid false matches, but it can also lead to missing real 262 # prompts (if, for instance, a background process writes to 263 # the terminal soon after the prompt is displayed). By 264 # default, set to 0, meaning not to wait for more data. 265 # 266 # Proxy:: a proxy object to used instead of opening a direct connection 267 # to the host. Must be either another Net::Telnet object or 268 # an IO object. If it is another Net::Telnet object, this 269 # instance will use that one's socket for communication. If an 270 # IO object, it is used directly for communication. Any other 271 # kind of object will cause an error to be raised. 272 # 273 def initialize(options) # :yield: mesg 274 @options = options 275 @options["Host"] = "localhost" unless @options.has_key?("Host") 276 @options["Port"] = 23 unless @options.has_key?("Port") 277 @options["Prompt"] = /[$%#>] \z/n unless @options.has_key?("Prompt") 278 @options["Timeout"] = 10 unless @options.has_key?("Timeout") 279 @options["Waittime"] = 0 unless @options.has_key?("Waittime") 280 unless @options.has_key?("Binmode") 281 @options["Binmode"] = false 282 else 283 unless (true == @options["Binmode"] or false == @options["Binmode"]) 284 raise ArgumentError, "Binmode option must be true or false" 285 end 286 end 287 288 unless @options.has_key?("Telnetmode") 289 @options["Telnetmode"] = true 290 else 291 unless (true == @options["Telnetmode"] or false == @options["Telnetmode"]) 292 raise ArgumentError, "Telnetmode option must be true or false" 293 end 294 end 295 296 @telnet_option = { "SGA" => false, "BINARY" => false } 297 298 if @options.has_key?("Output_log") 299 @log = File.open(@options["Output_log"], 'a+') 300 @log.sync = true 301 @log.binmode 302 end 303 304 if @options.has_key?("Dump_log") 305 @dumplog = File.open(@options["Dump_log"], 'a+') 306 @dumplog.sync = true 307 @dumplog.binmode 308 def @dumplog.log_dump(dir, x) # :nodoc: 309 len = x.length 310 addr = 0 311 offset = 0 312 while 0 < len 313 if len < 16 314 line = x[offset, len] 315 else 316 line = x[offset, 16] 317 end 318 hexvals = line.unpack('H*')[0] 319 hexvals += ' ' * (32 - hexvals.length) 320 hexvals = format("%s %s %s %s " * 4, *hexvals.unpack('a2' * 16)) 321 line = line.gsub(/[\000-\037\177-\377]/n, '.') 322 printf "%s 0x%5.5x: %s%s\n", dir, addr, hexvals, line 323 addr += 16 324 offset += 16 325 len -= 16 326 end 327 print "\n" 328 end 329 end 330 331 if @options.has_key?("Proxy") 332 if @options["Proxy"].kind_of?(Net::Telnet) 333 @sock = @options["Proxy"].sock 334 elsif @options["Proxy"].kind_of?(IO) 335 @sock = @options["Proxy"] 336 else 337 raise "Error: Proxy must be an instance of Net::Telnet or IO." 338 end 339 else 340 message = "Trying " + @options["Host"] + "...\n" 341 yield(message) if block_given? 342 @log.write(message) if @options.has_key?("Output_log") 343 @dumplog.log_dump('#', message) if @options.has_key?("Dump_log") 344 345 begin 346 if @options["Timeout"] == false 347 @sock = TCPSocket.open(@options["Host"], @options["Port"]) 348 else 349 Timeout.timeout(@options["Timeout"], Net::OpenTimeout) do 350 @sock = TCPSocket.open(@options["Host"], @options["Port"]) 351 end 352 end 353 rescue Net::OpenTimeout 354 raise Net::OpenTimeout, "timed out while opening a connection to the host" 355 rescue 356 @log.write($ERROR_INFO.to_s + "\n") if @options.has_key?("Output_log") 357 @dumplog.log_dump('#', $ERROR_INFO.to_s + "\n") if @options.has_key?("Dump_log") 358 raise 359 end 360 @sock.sync = true 361 @sock.binmode 362 363 message = "Connected to " + @options["Host"] + ".\n" 364 yield(message) if block_given? 365 @log.write(message) if @options.has_key?("Output_log") 366 @dumplog.log_dump('#', message) if @options.has_key?("Dump_log") 367 end 368 369 end # initialize 370 371 # The socket the Telnet object is using. Note that this object becomes 372 # a delegate of the Telnet object, so normally you invoke its methods 373 # directly on the Telnet object. 374 attr :sock 375 376 # Set telnet command interpretation on (+mode+ == true) or off 377 # (+mode+ == false), or return the current value (+mode+ not 378 # provided). It should be on for true telnet sessions, off if 379 # using Net::Telnet to connect to a non-telnet service such 380 # as SMTP. 381 def telnetmode(mode = nil) 382 case mode 383 when nil 384 @options["Telnetmode"] 385 when true, false 386 @options["Telnetmode"] = mode 387 else 388 raise ArgumentError, "argument must be true or false, or missing" 389 end 390 end 391 392 # Turn telnet command interpretation on (true) or off (false). It 393 # should be on for true telnet sessions, off if using Net::Telnet 394 # to connect to a non-telnet service such as SMTP. 395 def telnetmode=(mode) 396 if (true == mode or false == mode) 397 @options["Telnetmode"] = mode 398 else 399 raise ArgumentError, "argument must be true or false" 400 end 401 end 402 403 # Turn newline conversion on (+mode+ == false) or off (+mode+ == true), 404 # or return the current value (+mode+ is not specified). 405 def binmode(mode = nil) 406 case mode 407 when nil 408 @options["Binmode"] 409 when true, false 410 @options["Binmode"] = mode 411 else 412 raise ArgumentError, "argument must be true or false" 413 end 414 end 415 416 # Turn newline conversion on (false) or off (true). 417 def binmode=(mode) 418 if (true == mode or false == mode) 419 @options["Binmode"] = mode 420 else 421 raise ArgumentError, "argument must be true or false" 422 end 423 end 424 425 # Preprocess received data from the host. 426 # 427 # Performs newline conversion and detects telnet command sequences. 428 # Called automatically by #waitfor(). You should only use this 429 # method yourself if you have read input directly using sysread() 430 # or similar, and even then only if in telnet mode. 431 def preprocess(string) 432 # combine CR+NULL into CR 433 string = string.gsub(/#{CR}#{NULL}/no, CR) if @options["Telnetmode"] 434 435 # combine EOL into "\n" 436 string = string.gsub(/#{EOL}/no, "\n") unless @options["Binmode"] 437 438 # remove NULL 439 string = string.gsub(/#{NULL}/no, '') unless @options["Binmode"] 440 441 string.gsub(/#{IAC}( 442 [#{IAC}#{AO}#{AYT}#{DM}#{IP}#{NOP}]| 443 [#{DO}#{DONT}#{WILL}#{WONT}] 444 [#{OPT_BINARY}-#{OPT_NEW_ENVIRON}#{OPT_EXOPL}]| 445 #{SB}[^#{IAC}]*#{IAC}#{SE} 446 )/xno) do 447 if IAC == $1 # handle escaped IAC characters 448 IAC 449 elsif AYT == $1 # respond to "IAC AYT" (are you there) 450 self.write("nobody here but us pigeons" + EOL) 451 '' 452 elsif DO[0] == $1[0] # respond to "IAC DO x" 453 if OPT_BINARY[0] == $1[1] 454 @telnet_option["BINARY"] = true 455 self.write(IAC + WILL + OPT_BINARY) 456 else 457 self.write(IAC + WONT + $1[1..1]) 458 end 459 '' 460 elsif DONT[0] == $1[0] # respond to "IAC DON'T x" with "IAC WON'T x" 461 self.write(IAC + WONT + $1[1..1]) 462 '' 463 elsif WILL[0] == $1[0] # respond to "IAC WILL x" 464 if OPT_BINARY[0] == $1[1] 465 self.write(IAC + DO + OPT_BINARY) 466 elsif OPT_ECHO[0] == $1[1] 467 self.write(IAC + DO + OPT_ECHO) 468 elsif OPT_SGA[0] == $1[1] 469 @telnet_option["SGA"] = true 470 self.write(IAC + DO + OPT_SGA) 471 else 472 self.write(IAC + DONT + $1[1..1]) 473 end 474 '' 475 elsif WONT[0] == $1[0] # respond to "IAC WON'T x" 476 if OPT_ECHO[0] == $1[1] 477 self.write(IAC + DONT + OPT_ECHO) 478 elsif OPT_SGA[0] == $1[1] 479 @telnet_option["SGA"] = false 480 self.write(IAC + DONT + OPT_SGA) 481 else 482 self.write(IAC + DONT + $1[1..1]) 483 end 484 '' 485 else 486 '' 487 end 488 end 489 end # preprocess 490 491 # Read data from the host until a certain sequence is matched. 492 # 493 # If a block is given, the received data will be yielded as it 494 # is read in (not necessarily all in one go), or nil if EOF 495 # occurs before any data is received. Whether a block is given 496 # or not, all data read will be returned in a single string, or again 497 # nil if EOF occurs before any data is received. Note that 498 # received data includes the matched sequence we were looking for. 499 # 500 # +options+ can be either a regular expression or a hash of options. 501 # If a regular expression, this specifies the data to wait for. 502 # If a hash, this can specify the following options: 503 # 504 # Match:: a regular expression, specifying the data to wait for. 505 # Prompt:: as for Match; used only if Match is not specified. 506 # String:: as for Match, except a string that will be converted 507 # into a regular expression. Used only if Match and 508 # Prompt are not specified. 509 # Timeout:: the number of seconds to wait for data from the host 510 # before raising a Timeout::Error. If set to false, 511 # no timeout will occur. If not specified, the 512 # Timeout option value specified when this instance 513 # was created will be used, or, failing that, the 514 # default value of 10 seconds. 515 # Waittime:: the number of seconds to wait after matching against 516 # the input data to see if more data arrives. If more 517 # data arrives within this time, we will judge ourselves 518 # not to have matched successfully, and will continue 519 # trying to match. If not specified, the Waittime option 520 # value specified when this instance was created will be 521 # used, or, failing that, the default value of 0 seconds, 522 # which means not to wait for more input. 523 # FailEOF:: if true, when the remote end closes the connection then an 524 # EOFError will be raised. Otherwise, defaults to the old 525 # behaviour that the function will return whatever data 526 # has been received already, or nil if nothing was received. 527 # 528 def waitfor(options) # :yield: recvdata 529 time_out = @options["Timeout"] 530 waittime = @options["Waittime"] 531 fail_eof = @options["FailEOF"] 532 533 if options.kind_of?(Hash) 534 prompt = if options.has_key?("Match") 535 options["Match"] 536 elsif options.has_key?("Prompt") 537 options["Prompt"] 538 elsif options.has_key?("String") 539 Regexp.new( Regexp.quote(options["String"]) ) 540 end 541 time_out = options["Timeout"] if options.has_key?("Timeout") 542 waittime = options["Waittime"] if options.has_key?("Waittime") 543 fail_eof = options["FailEOF"] if options.has_key?("FailEOF") 544 else 545 prompt = options 546 end 547 548 if time_out == false 549 time_out = nil 550 end 551 552 line = '' 553 buf = '' 554 rest = '' 555 until(prompt === line and not IO::select([@sock], nil, nil, waittime)) 556 unless IO::select([@sock], nil, nil, time_out) 557 raise Net::ReadTimeout, "timed out while waiting for more data" 558 end 559 begin 560 c = @sock.readpartial(1024 * 1024) 561 @dumplog.log_dump('<', c) if @options.has_key?("Dump_log") 562 if @options["Telnetmode"] 563 c = rest + c 564 if Integer(c.rindex(/#{IAC}#{SE}/no) || 0) < 565 Integer(c.rindex(/#{IAC}#{SB}/no) || 0) 566 buf = preprocess(c[0 ... c.rindex(/#{IAC}#{SB}/no)]) 567 rest = c[c.rindex(/#{IAC}#{SB}/no) .. -1] 568 elsif pt = c.rindex(/#{IAC}[^#{IAC}#{AO}#{AYT}#{DM}#{IP}#{NOP}]?\z/no) || 569 c.rindex(/\r\z/no) 570 buf = preprocess(c[0 ... pt]) 571 rest = c[pt .. -1] 572 else 573 buf = preprocess(c) 574 rest = '' 575 end 576 else 577 # Not Telnetmode. 578 # 579 # We cannot use preprocess() on this data, because that 580 # method makes some Telnetmode-specific assumptions. 581 buf = rest + c 582 rest = '' 583 unless @options["Binmode"] 584 if pt = buf.rindex(/\r\z/no) 585 buf = buf[0 ... pt] 586 rest = buf[pt .. -1] 587 end 588 buf.gsub!(/#{EOL}/no, "\n") 589 end 590 end 591 @log.print(buf) if @options.has_key?("Output_log") 592 line += buf 593 yield buf if block_given? 594 rescue EOFError # End of file reached 595 raise if fail_eof 596 if line == '' 597 line = nil 598 yield nil if block_given? 599 end 600 break 601 end 602 end 603 line 604 end 605 606 # Write +string+ to the host. 607 # 608 # Does not perform any conversions on +string+. Will log +string+ to the 609 # dumplog, if the Dump_log option is set. 610 def write(string) 611 length = string.length 612 while 0 < length 613 IO::select(nil, [@sock]) 614 @dumplog.log_dump('>', string[-length..-1]) if @options.has_key?("Dump_log") 615 length -= @sock.syswrite(string[-length..-1]) 616 end 617 end 618 619 # Sends a string to the host. 620 # 621 # This does _not_ automatically append a newline to the string. Embedded 622 # newlines may be converted and telnet command sequences escaped 623 # depending upon the values of telnetmode, binmode, and telnet options 624 # set by the host. 625 def print(string) 626 string = string.gsub(/#{IAC}/no, IAC + IAC) if @options["Telnetmode"] 627 628 if @options["Binmode"] 629 self.write(string) 630 else 631 if @telnet_option["BINARY"] and @telnet_option["SGA"] 632 # IAC WILL SGA IAC DO BIN send EOL --> CR 633 self.write(string.gsub(/\n/n, CR)) 634 elsif @telnet_option["SGA"] 635 # IAC WILL SGA send EOL --> CR+NULL 636 self.write(string.gsub(/\n/n, CR + NULL)) 637 else 638 # NONE send EOL --> CR+LF 639 self.write(string.gsub(/\n/n, EOL)) 640 end 641 end 642 end 643 644 # Sends a string to the host. 645 # 646 # Same as #print(), but appends a newline to the string. 647 def puts(string) 648 self.print(string + "\n") 649 end 650 651 # Send a command to the host. 652 # 653 # More exactly, sends a string to the host, and reads in all received 654 # data until is sees the prompt or other matched sequence. 655 # 656 # If a block is given, the received data will be yielded to it as 657 # it is read in. Whether a block is given or not, the received data 658 # will be return as a string. Note that the received data includes 659 # the prompt and in most cases the host's echo of our command. 660 # 661 # +options+ is either a String, specified the string or command to 662 # send to the host; or it is a hash of options. If a hash, the 663 # following options can be specified: 664 # 665 # String:: the command or other string to send to the host. 666 # Match:: a regular expression, the sequence to look for in 667 # the received data before returning. If not specified, 668 # the Prompt option value specified when this instance 669 # was created will be used, or, failing that, the default 670 # prompt of /[$%#>] \z/n. 671 # Timeout:: the seconds to wait for data from the host before raising 672 # a Timeout error. If not specified, the Timeout option 673 # value specified when this instance was created will be 674 # used, or, failing that, the default value of 10 seconds. 675 # 676 # The command or other string will have the newline sequence appended 677 # to it. 678 def cmd(options) # :yield: recvdata 679 match = @options["Prompt"] 680 time_out = @options["Timeout"] 681 fail_eof = @options["FailEOF"] 682 683 if options.kind_of?(Hash) 684 string = options["String"] 685 match = options["Match"] if options.has_key?("Match") 686 time_out = options["Timeout"] if options.has_key?("Timeout") 687 fail_eof = options["FailEOF"] if options.has_key?("FailEOF") 688 else 689 string = options 690 end 691 692 self.puts(string) 693 if block_given? 694 waitfor({"Prompt" => match, "Timeout" => time_out, "FailEOF" => fail_eof}){|c| yield c } 695 else 696 waitfor({"Prompt" => match, "Timeout" => time_out, "FailEOF" => fail_eof}) 697 end 698 end 699 700 # Login to the host with a given username and password. 701 # 702 # The username and password can either be provided as two string 703 # arguments in that order, or as a hash with keys "Name" and 704 # "Password". 705 # 706 # This method looks for the strings "login" and "Password" from the 707 # host to determine when to send the username and password. If the 708 # login sequence does not follow this pattern (for instance, you 709 # are connecting to a service other than telnet), you will need 710 # to handle login yourself. 711 # 712 # The password can be omitted, either by only 713 # provided one String argument, which will be used as the username, 714 # or by providing a has that has no "Password" key. In this case, 715 # the method will not look for the "Password:" prompt; if it is 716 # sent, it will have to be dealt with by later calls. 717 # 718 # The method returns all data received during the login process from 719 # the host, including the echoed username but not the password (which 720 # the host should not echo). If a block is passed in, this received 721 # data is also yielded to the block as it is received. 722 def login(options, password = nil) # :yield: recvdata 723 login_prompt = /[Ll]ogin[: ]*\z/n 724 password_prompt = /[Pp]ass(?:word|phrase)[: ]*\z/n 725 if options.kind_of?(Hash) 726 username = options["Name"] 727 password = options["Password"] 728 login_prompt = options["LoginPrompt"] if options["LoginPrompt"] 729 password_prompt = options["PasswordPrompt"] if options["PasswordPrompt"] 730 else 731 username = options 732 end 733 734 if block_given? 735 line = waitfor(login_prompt){|c| yield c } 736 if password 737 line += cmd({"String" => username, 738 "Match" => password_prompt}){|c| yield c } 739 line += cmd(password){|c| yield c } 740 else 741 line += cmd(username){|c| yield c } 742 end 743 else 744 line = waitfor(login_prompt) 745 if password 746 line += cmd({"String" => username, 747 "Match" => password_prompt}) 748 line += cmd(password) 749 else 750 line += cmd(username) 751 end 752 end 753 line 754 end 755 756 # Closes the connection 757 def close 758 @sock.close 759 end 760 761 end # class Telnet 762end # module Net 763 764