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