DEFINITIONS
This source file includes following functions.
1 =begin
2
3 == NAME
4
5 net/telnet.rb - simple telnet client library
6
7 Wakou Aoyama <wakou@fsinet.or.jp>
8
9
10 === MAKE NEW TELNET OBJECT
11
12 host = Net::Telnet::new({
13 "Binmode" => false, # default: false
14 "Host" => "localhost", # default: "localhost"
15 "Output_log" => "output_log", # default: nil (no output)
16 "Dump_log" => "dump_log", # default: nil (no output)
17 "Port" => 23, # default: 23
18 "Prompt" => /[$%#>] \z/n, # default: /[$%#>] \z/n
19 "Telnetmode" => true, # default: true
20 "Timeout" => 10, # default: 10
21 # if ignore timeout then set "Timeout" to false.
22 "Waittime" => 0, # default: 0
23 "Proxy" => proxy # default: nil
24 # proxy is Net::Telnet or IO object
25 })
26
27 Telnet object has socket class methods.
28
29 if set "Telnetmode" option to false. not telnet command interpretation.
30 "Waittime" is time to confirm "Prompt". There is a possibility that
31 the same character as "Prompt" is included in the data, and, when
32 the network or the host is very heavy, the value is enlarged.
33
34
35 === STATUS OUTPUT
36
37 host = Net::Telnet::new({"Host" => "localhost"}){|c| print c }
38
39 connection status output.
40
41 example:
42
43 Trying localhost...
44 Connected to localhost.
45
46
47 === WAIT FOR MATCH
48
49 line = host.waitfor(/match/)
50 line = host.waitfor({"Match" => /match/,
51 "String" => "string",
52 "Timeout" => secs})
53 # if ignore timeout then set "Timeout" to false.
54
55 if set "String" option, then Match == Regexp.new(quote("string"))
56
57
58 ==== REALTIME OUTPUT
59
60 host.waitfor(/match/){|c| print c }
61 host.waitfor({"Match" => /match/,
62 "String" => "string",
63 "Timeout" => secs}){|c| print c}
64
65 of cource, set sync=true or flush is necessary.
66
67
68 === SEND STRING AND WAIT PROMPT
69
70 line = host.cmd("string")
71 line = host.cmd({"String" => "string",
72 "Match" => /[$%#>] \z/n,
73 "Timeout" => 10})
74
75
76 ==== REALTIME OUTPUT
77
78 host.cmd("string"){|c| print c }
79 host.cmd({"String" => "string",
80 "Match" => /[$%#>] \z/n,
81 "Timeout" => 10}){|c| print c }
82
83 of cource, set sync=true or flush is necessary.
84
85
86 === SEND STRING
87
88 host.print("string")
89 host.puts("string")
90
91 Telnet#puts() adds "\n" to the last of "string".
92
93 WARNING: Telnet#print() NOT adds "\n" to the last of "string", in the future.
94
95 If "Telnetmode" option is true, then escape IAC code ("\xFF"). If
96 "Binmode" option is false, then convert "\n" to EOL(end of line) code.
97
98 If "WILL SGA" and "DO BIN", then EOL is CR. If "WILL SGA", then EOL is
99 CR + NULL. If the other cases, EOL is CR + LF.
100
101
102 === TOGGLE TELNET COMMAND INTERPRETATION
103
104 host.telnetmode # return the current status (true or false)
105 host.telnetmode = true # do telnet command interpretation (default)
106 host.telnetmode = false # don't telnet command interpretation
107
108
109 === TOGGLE NEWLINE TRANSLATION
110
111 host.binmode # return the current status (true or false)
112 host.binmode = true # no translate newline
113 host.binmode = false # translate newline (default)
114
115
116 === LOGIN
117
118 host.login("username", "password")
119 host.login({"Name" => "username",
120 "Password" => "password"})
121
122 if no password prompt:
123
124 host.login("username")
125 host.login({"Name" => "username"})
126
127
128 ==== REALTIME OUTPUT
129
130 host.login("username", "password"){|c| print c }
131 host.login({"Name" => "username",
132 "Password" => "password"}){|c| print c }
133
134 of cource, set sync=true or flush is necessary.
135
136
137
138 == EXAMPLE
139
140 === LOGIN AND SEND COMMAND
141
142 localhost = Net::Telnet::new({"Host" => "localhost",
143 "Timeout" => 10,
144 "Prompt" => /[$%#>] \z/n})
145 localhost.login("username", "password"){|c| print c }
146 localhost.cmd("command"){|c| print c }
147 localhost.close
148
149
150 === CHECKS A POP SERVER TO SEE IF YOU HAVE MAIL
151
152 pop = Net::Telnet::new({"Host" => "your_destination_host_here",
153 "Port" => 110,
154 "Telnetmode" => false,
155 "Prompt" => /^\+OK/n})
156 pop.cmd("user " + "your_username_here"){|c| print c}
157 pop.cmd("pass " + "your_password_here"){|c| print c}
158 pop.cmd("list"){|c| print c}
159
160
161 =end
162
163
164 require "socket"
165 require "delegate"
166 require "timeout"
167 require "English"
168
169 module Net
170 class Telnet < SimpleDelegator
171
172 IAC = 255.chr # "\377" # "\xff" # interpret as command:
173 DONT = 254.chr # "\376" # "\xfe" # you are not to use option
174 DO = 253.chr # "\375" # "\xfd" # please, you use option
175 WONT = 252.chr # "\374" # "\xfc" # I won't use option
176 WILL = 251.chr # "\373" # "\xfb" # I will use option
177 SB = 250.chr # "\372" # "\xfa" # interpret as subnegotiation
178 GA = 249.chr # "\371" # "\xf9" # you may reverse the line
179 EL = 248.chr # "\370" # "\xf8" # erase the current line
180 EC = 247.chr # "\367" # "\xf7" # erase the current character
181 AYT = 246.chr # "\366" # "\xf6" # are you there
182 AO = 245.chr # "\365" # "\xf5" # abort output--but let prog finish
183 IP = 244.chr # "\364" # "\xf4" # interrupt process--permanently
184 BREAK = 243.chr # "\363" # "\xf3" # break
185 DM = 242.chr # "\362" # "\xf2" # data mark--for connect. cleaning
186 NOP = 241.chr # "\361" # "\xf1" # nop
187 SE = 240.chr # "\360" # "\xf0" # end sub negotiation
188 EOR = 239.chr # "\357" # "\xef" # end of record (transparent mode)
189 ABORT = 238.chr # "\356" # "\xee" # Abort process
190 SUSP = 237.chr # "\355" # "\xed" # Suspend process
191 EOF = 236.chr # "\354" # "\xec" # End of file
192 SYNCH = 242.chr # "\362" # "\xf2" # for telfunc calls
193
194 OPT_BINARY = 0.chr # "\000" # "\x00" # Binary Transmission
195 OPT_ECHO = 1.chr # "\001" # "\x01" # Echo
196 OPT_RCP = 2.chr # "\002" # "\x02" # Reconnection
197 OPT_SGA = 3.chr # "\003" # "\x03" # Suppress Go Ahead
198 OPT_NAMS = 4.chr # "\004" # "\x04" # Approx Message Size Negotiation
199 OPT_STATUS = 5.chr # "\005" # "\x05" # Status
200 OPT_TM = 6.chr # "\006" # "\x06" # Timing Mark
201 OPT_RCTE = 7.chr # "\a" # "\x07" # Remote Controlled Trans and Echo
202 OPT_NAOL = 8.chr # "\010" # "\x08" # Output Line Width
203 OPT_NAOP = 9.chr # "\t" # "\x09" # Output Page Size
204 OPT_NAOCRD = 10.chr # "\n" # "\x0a" # Output Carriage-Return Disposition
205 OPT_NAOHTS = 11.chr # "\v" # "\x0b" # Output Horizontal Tab Stops
206 OPT_NAOHTD = 12.chr # "\f" # "\x0c" # Output Horizontal Tab Disposition
207 OPT_NAOFFD = 13.chr # "\r" # "\x0d" # Output Formfeed Disposition
208 OPT_NAOVTS = 14.chr # "\016" # "\x0e" # Output Vertical Tabstops
209 OPT_NAOVTD = 15.chr # "\017" # "\x0f" # Output Vertical Tab Disposition
210 OPT_NAOLFD = 16.chr # "\020" # "\x10" # Output Linefeed Disposition
211 OPT_XASCII = 17.chr # "\021" # "\x11" # Extended ASCII
212 OPT_LOGOUT = 18.chr # "\022" # "\x12" # Logout
213 OPT_BM = 19.chr # "\023" # "\x13" # Byte Macro
214 OPT_DET = 20.chr # "\024" # "\x14" # Data Entry Terminal
215 OPT_SUPDUP = 21.chr # "\025" # "\x15" # SUPDUP
216 OPT_SUPDUPOUTPUT = 22.chr # "\026" # "\x16" # SUPDUP Output
217 OPT_SNDLOC = 23.chr # "\027" # "\x17" # Send Location
218 OPT_TTYPE = 24.chr # "\030" # "\x18" # Terminal Type
219 OPT_EOR = 25.chr # "\031" # "\x19" # End of Record
220 OPT_TUID = 26.chr # "\032" # "\x1a" # TACACS User Identification
221 OPT_OUTMRK = 27.chr # "\e" # "\x1b" # Output Marking
222 OPT_TTYLOC = 28.chr # "\034" # "\x1c" # Terminal Location Number
223 OPT_3270REGIME = 29.chr # "\035" # "\x1d" # Telnet 3270 Regime
224 OPT_X3PAD = 30.chr # "\036" # "\x1e" # X.3 PAD
225 OPT_NAWS = 31.chr # "\037" # "\x1f" # Negotiate About Window Size
226 OPT_TSPEED = 32.chr # " " # "\x20" # Terminal Speed
227 OPT_LFLOW = 33.chr # "!" # "\x21" # Remote Flow Control
228 OPT_LINEMODE = 34.chr # "\"" # "\x22" # Linemode
229 OPT_XDISPLOC = 35.chr # "#" # "\x23" # X Display Location
230 OPT_OLD_ENVIRON = 36.chr # "$" # "\x24" # Environment Option
231 OPT_AUTHENTICATION = 37.chr # "%" # "\x25" # Authentication Option
232 OPT_ENCRYPT = 38.chr # "&" # "\x26" # Encryption Option
233 OPT_NEW_ENVIRON = 39.chr # "'" # "\x27" # New Environment Option
234 OPT_EXOPL = 255.chr # "\377" # "\xff" # Extended-Options-List
235
236 NULL = "\000"
237 CR = "\015"
238 LF = "\012"
239 EOL = CR + LF
240 REVISION = '$Id: telnet.rb,v 1.18 2002/07/11 08:22:16 matz Exp $'
241
242 def initialize(options)
243 @options = options
244 @options["Host"] = "localhost" unless @options.has_key?("Host")
245 @options["Port"] = 23 unless @options.has_key?("Port")
246 @options["Prompt"] = /[$%#>] \z/n unless @options.has_key?("Prompt")
247 @options["Timeout"] = 10 unless @options.has_key?("Timeout")
248 @options["Waittime"] = 0 unless @options.has_key?("Waittime")
249 unless @options.has_key?("Binmode")
250 @options["Binmode"] = false
251 else
252 unless (true == @options["Binmode"] or false == @options["Binmode"])
253 raise ArgumentError, "Binmode option required true or false"
254 end
255 end
256
257 unless @options.has_key?("Telnetmode")
258 @options["Telnetmode"] = true
259 else
260 unless (true == @options["Telnetmode"] or false == @options["Telnetmode"])
261 raise ArgumentError, "Telnetmode option required true or false"
262 end
263 end
264
265 @telnet_option = { "SGA" => false, "BINARY" => false }
266
267 if @options.has_key?("Output_log")
268 @log = File.open(@options["Output_log"], 'a+')
269 @log.sync = true
270 @log.binmode
271 end
272
273 if @options.has_key?("Dump_log")
274 @dumplog = File.open(@options["Dump_log"], 'a+')
275 @dumplog.sync = true
276 @dumplog.binmode
277 def @dumplog.log_dump(dir, x)
278 len = x.length
279 addr = 0
280 offset = 0
281 while 0 < len
282 if len < 16
283 line = x[offset, len]
284 else
285 line = x[offset, 16]
286 end
287 hexvals = line.unpack('H*')[0]
288 hexvals += ' ' * (32 - hexvals.length)
289 hexvals = format "%s %s %s %s " * 4, *hexvals.unpack('a2' * 16)
290 line = line.gsub(/[\000-\037\177-\377]/n, '.')
291 printf "%s 0x%5.5x: %s%s\n", dir, addr, hexvals, line
292 addr += 16
293 offset += 16
294 len -= 16
295 end
296 print "\n"
297 end
298 end
299
300 if @options.has_key?("Proxy")
301 if @options["Proxy"].kind_of?(Net::Telnet)
302 @sock = @options["Proxy"].sock
303 elsif @options["Proxy"].kind_of?(IO)
304 @sock = @options["Proxy"]
305 else
306 raise "Error; Proxy is Net::Telnet or IO object."
307 end
308 else
309 message = "Trying " + @options["Host"] + "...\n"
310 yield(message) if block_given?
311 @log.write(message) if @options.has_key?("Output_log")
312 @dumplog.log_dump('#', message) if @options.has_key?("Dump_log")
313
314 begin
315 if @options["Timeout"] == false
316 @sock = TCPSocket.open(@options["Host"], @options["Port"])
317 else
318 timeout(@options["Timeout"]) do
319 @sock = TCPSocket.open(@options["Host"], @options["Port"])
320 end
321 end
322 rescue TimeoutError
323 raise TimeoutError, "timed-out; opening of the host"
324 rescue
325 @log.write($ERROR_INFO.to_s + "\n") if @options.has_key?("Output_log")
326 @dumplog.log_dump('#', $ERROR_INFO.to_s + "\n") if @options.has_key?("Dump_log")
327 raise
328 end
329 @sock.sync = true
330 @sock.binmode
331
332 message = "Connected to " + @options["Host"] + ".\n"
333 yield(message) if block_given?
334 @log.write(message) if @options.has_key?("Output_log")
335 @dumplog.log_dump('#', message) if @options.has_key?("Dump_log")
336 end
337
338 super(@sock)
339 end # initialize
340
341 attr :sock
342
343 def telnetmode(mode = nil)
344 case mode
345 when nil
346 @options["Telnetmode"]
347 when true, false
348 @options["Telnetmode"] = mode
349 else
350 raise ArgumentError, "required true or false"
351 end
352 end
353
354 def telnetmode=(mode)
355 if (true == mode or false == mode)
356 @options["Telnetmode"] = mode
357 else
358 raise ArgumentError, "required true or false"
359 end
360 end
361
362 def binmode(mode = nil)
363 case mode
364 when nil
365 @options["Binmode"]
366 when true, false
367 @options["Binmode"] = mode
368 else
369 raise ArgumentError, "required true or false"
370 end
371 end
372
373 def binmode=(mode)
374 if (true == mode or false == mode)
375 @options["Binmode"] = mode
376 else
377 raise ArgumentError, "required true or false"
378 end
379 end
380
381 def preprocess(string)
382 # combine CR+NULL into CR
383 string = string.gsub(/#{CR}#{NULL}/no, CR) if @options["Telnetmode"]
384
385 # combine EOL into "\n"
386 string = string.gsub(/#{EOL}/no, "\n") unless @options["Binmode"]
387
388 string.gsub(/#{IAC}(
389 [#{IAC}#{AO}#{AYT}#{DM}#{IP}#{NOP}]|
390 [#{DO}#{DONT}#{WILL}#{WONT}]
391 [#{OPT_BINARY}-#{OPT_NEW_ENVIRON}#{OPT_EXOPL}]|
392 #{SB}[^#{IAC}]*#{IAC}#{SE}
393 )/xno) do
394 if IAC == $1 # handle escaped IAC characters
395 IAC
396 elsif AYT == $1 # respond to "IAC AYT" (are you there)
397 self.write("nobody here but us pigeons" + EOL)
398 ''
399 elsif DO[0] == $1[0] # respond to "IAC DO x"
400 if OPT_BINARY[0] == $1[1]
401 @telnet_option["BINARY"] = true
402 self.write(IAC + WILL + OPT_BINARY)
403 else
404 self.write(IAC + WONT + $1[1..1])
405 end
406 ''
407 elsif DONT[0] == $1[0] # respond to "IAC DON'T x" with "IAC WON'T x"
408 self.write(IAC + WONT + $1[1..1])
409 ''
410 elsif WILL[0] == $1[0] # respond to "IAC WILL x"
411 if OPT_BINARY[0] == $1[1]
412 self.write(IAC + DO + OPT_BINARY)
413 elsif OPT_ECHO[0] == $1[1]
414 self.write(IAC + DO + OPT_ECHO)
415 elsif OPT_SGA[0] == $1[1]
416 @telnet_option["SGA"] = true
417 self.write(IAC + DO + OPT_SGA)
418 else
419 self.write(IAC + DONT + $1[1..1])
420 end
421 ''
422 elsif WONT[0] == $1[0] # respond to "IAC WON'T x"
423 if OPT_ECHO[0] == $1[1]
424 self.write(IAC + DONT + OPT_ECHO)
425 elsif OPT_SGA[0] == $1[1]
426 @telnet_option["SGA"] = false
427 self.write(IAC + DONT + OPT_SGA)
428 else
429 self.write(IAC + DONT + $1[1..1])
430 end
431 ''
432 else
433 ''
434 end
435 end
436 end # preprocess
437
438 def waitfor(options)
439 time_out = @options["Timeout"]
440 waittime = @options["Waittime"]
441
442 if options.kind_of?(Hash)
443 prompt = if options.has_key?("Match")
444 options["Match"]
445 elsif options.has_key?("Prompt")
446 options["Prompt"]
447 elsif options.has_key?("String")
448 Regexp.new( Regexp.quote(options["String"]) )
449 end
450 time_out = options["Timeout"] if options.has_key?("Timeout")
451 waittime = options["Waittime"] if options.has_key?("Waittime")
452 else
453 prompt = options
454 end
455
456 if time_out == false
457 time_out = nil
458 end
459
460 line = ''
461 buf = ''
462 rest = ''
463 until(prompt === line and not IO::select([@sock], nil, nil, waittime))
464 unless IO::select([@sock], nil, nil, time_out)
465 raise TimeoutError, "timed-out; wait for the next data"
466 end
467 begin
468 c = @sock.sysread(1024 * 1024)
469 @dumplog.log_dump('<', c) if @options.has_key?("Dump_log")
470 if @options["Telnetmode"]
471 c = rest + c
472 if Integer(c.rindex(/#{IAC}#{SE}/no)) <
473 Integer(c.rindex(/#{IAC}#{SB}/no))
474 buf = preprocess(c[0 ... c.rindex(/#{IAC}#{SB}/no)])
475 rest = c[c.rindex(/#{IAC}#{SB}/no) .. -1]
476 elsif pt = c.rindex(/#{IAC}[^#{IAC}#{AO}#{AYT}#{DM}#{IP}#{NOP}]?\z/no)
477 buf = preprocess(c[0 ... pt])
478 rest = c[pt .. -1]
479 else
480 buf = preprocess(c)
481 rest = ''
482 end
483 end
484 @log.print(buf) if @options.has_key?("Output_log")
485 line += buf
486 yield buf if block_given?
487 rescue EOFError # End of file reached
488 if line == ''
489 line = nil
490 yield nil if block_given?
491 end
492 break
493 end
494 end
495 line
496 end
497
498 def write(string)
499 length = string.length
500 while 0 < length
501 IO::select(nil, [@sock])
502 @dumplog.log_dump('>', string[-length..-1]) if @options.has_key?("Dump_log")
503 length -= @sock.syswrite(string[-length..-1])
504 end
505 end
506
507 def _print(string)
508 string = string.gsub(/#{IAC}/no, IAC + IAC) if @options["Telnetmode"]
509
510 if @options["Binmode"]
511 self.write(string)
512 else
513 if @telnet_option["BINARY"] and @telnet_option["SGA"]
514 # IAC WILL SGA IAC DO BIN send EOL --> CR
515 self.write(string.gsub(/\n/n, CR))
516 elsif @telnet_option["SGA"]
517 # IAC WILL SGA send EOL --> CR+NULL
518 self.write(string.gsub(/\n/n, CR + NULL))
519 else
520 # NONE send EOL --> CR+LF
521 self.write(string.gsub(/\n/n, EOL))
522 end
523 end
524 end
525
526 def puts(string)
527 self._print(string + "\n")
528 end
529
530 def print(string)
531 if $VERBOSE
532 $stderr.puts 'WARNING: Telnet#print("string") NOT adds "\n" to the last of "string", in the future.'
533 $stderr.puts ' cf. Telnet#puts().'
534 end
535 self.puts(string)
536 end
537
538 def cmd(options)
539 match = @options["Prompt"]
540 time_out = @options["Timeout"]
541
542 if options.kind_of?(Hash)
543 string = options["String"]
544 match = options["Match"] if options.has_key?("Match")
545 time_out = options["Timeout"] if options.has_key?("Timeout")
546 else
547 string = options
548 end
549
550 self.puts(string)
551 if block_given?
552 waitfor({"Prompt" => match, "Timeout" => time_out}){|c| yield c }
553 else
554 waitfor({"Prompt" => match, "Timeout" => time_out})
555 end
556 end
557
558 def login(options, password = nil)
559 if options.kind_of?(Hash)
560 username = options["Name"]
561 password = options["Password"]
562 else
563 username = options
564 end
565
566 if block_given?
567 line = waitfor(/login[: ]*\z/n){|c| yield c }
568 if password
569 line += cmd({"String" => username,
570 "Match" => /Password[: ]*\z/n}){|c| yield c }
571 line += cmd(password){|c| yield c }
572 else
573 line += cmd(username){|c| yield c }
574 end
575 else
576 line = waitfor(/login[: ]*\z/n)
577 if password
578 line += cmd({"String" => username,
579 "Match" => /Password[: ]*\z/n})
580 line += cmd(password)
581 else
582 line += cmd(username)
583 end
584 end
585 line
586 end
587
588 end
589 end
590
591
592 =begin
593
594 == HISTORY
595
596 delete. see cvs log.
597
598
599 =end