net-telnet 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 54b611c0db7661fe5030825a8921243d9e27386c
4
+ data.tar.gz: 145cfebbc77be5c5ba90c36166b2be3f042f3a1f
5
+ SHA512:
6
+ metadata.gz: 5d58b41544b11e78876a67f9bbc4abc2f2b6319d47998866b68fc6a152ebeafabe4fe8689d1a36d3d9175fbe7150f476dd01180e82b5777b0d20b79db6e5d96e
7
+ data.tar.gz: 8510c2c995ecd8c9ed35169330dd8988f5de7f13df9bf0e69b8b7903ada78f63f7bed3364038b8e277653b6195ca47f3d3b441b973be4155f09bbe193618acd0
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.2
4
+ - ruby-head
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in net-telnet.gemspec
4
+ gemspec
@@ -0,0 +1,60 @@
1
+ # Net::Telnet
2
+
3
+ Provides telnet client functionality.
4
+
5
+ This class also has, through delegation, all the methods of a socket object (by default, a **TCPSocket**, but can be set by the **Proxy** option to ```new()```). This provides methods such as ```close()``` to end the session and ```sysread()``` to read data directly from the host, instead of via the ```waitfor()``` mechanism. Note that if you do use ```sysread()``` directly when in telnet mode, you should probably pass the output through ```preprocess()``` to extract telnet command sequences.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'net-telnet'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install net-telnet
22
+
23
+ ## Usage
24
+
25
+ ### Log in and send a command, echoing all output to stdout
26
+
27
+ ```ruby
28
+ localhost = Net::Telnet::new("Host" => "localhost",
29
+ "Timeout" => 10,
30
+ "Prompt" => /[$%#>] \z/n)
31
+ localhost.login("username", "password") { |c| print c }
32
+ localhost.cmd("command") { |c| print c }
33
+ localhost.close
34
+ ```
35
+
36
+ ### Check a POP server to see if you have mail
37
+
38
+ ```ruby
39
+ pop = Net::Telnet::new("Host" => "your_destination_host_here",
40
+ "Port" => 110,
41
+ "Telnetmode" => false,
42
+ "Prompt" => /^\+OK/n)
43
+ pop.cmd("user " + "your_username_here") { |c| print c }
44
+ pop.cmd("pass " + "your_password_here") { |c| print c }
45
+ pop.cmd("list") { |c| print c }
46
+ ```
47
+
48
+ ## Development
49
+
50
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt that will allow you to experiment.
51
+
52
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release` to create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
53
+
54
+ ## Contributing
55
+
56
+ 1. Fork it ( https://github.com/ruby/net-telnet/fork )
57
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
58
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
59
+ 4. Push to the branch (`git push origin my-new-feature`)
60
+ 5. Create a new Pull Request
@@ -0,0 +1,9 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << "test"
6
+ t.test_files = FileList['test/test_*.rb']
7
+ end
8
+
9
+ task(default: :test)
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "net/telnet"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1 @@
1
+ require 'net/telnet'
@@ -0,0 +1,763 @@
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
+
12
+ require "net/protocol"
13
+ require "English"
14
+
15
+ module 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$'
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_reader :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
762
+ end # module Net
763
+
@@ -0,0 +1,5 @@
1
+ module Net
2
+ class Telnet
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'net/telnet/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "net-telnet"
8
+ spec.version = Net::Telnet::VERSION
9
+ spec.authors = ["SHIBATA Hiroshi"]
10
+ spec.email = ["hsbt@ruby-lang.org"]
11
+
12
+ spec.summary = %q{Provides telnet client functionality.}
13
+ spec.description = %q{Provides telnet client functionality.}
14
+ spec.homepage = "https://github.com/ruby/net-telnet"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.bindir = "exe"
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.9"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+ end
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: net-telnet
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - SHIBATA Hiroshi
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2015-04-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.9'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.9'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ description: Provides telnet client functionality.
42
+ email:
43
+ - hsbt@ruby-lang.org
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - ".gitignore"
49
+ - ".travis.yml"
50
+ - Gemfile
51
+ - README.md
52
+ - Rakefile
53
+ - bin/console
54
+ - bin/setup
55
+ - lib/net-telnet.rb
56
+ - lib/net/telnet.rb
57
+ - lib/net/telnet/version.rb
58
+ - net-telnet.gemspec
59
+ homepage: https://github.com/ruby/net-telnet
60
+ licenses: []
61
+ metadata: {}
62
+ post_install_message:
63
+ rdoc_options: []
64
+ require_paths:
65
+ - lib
66
+ required_ruby_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ required_rubygems_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ requirements: []
77
+ rubyforge_project:
78
+ rubygems_version: 2.4.6
79
+ signing_key:
80
+ specification_version: 4
81
+ summary: Provides telnet client functionality.
82
+ test_files: []