em-simple_telnet 0.1.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6eecc874ba8de0fa9887693d979872ae36afd56e
4
- data.tar.gz: 279a6a84e2073af9da310ca56bc01d45a71ef17a
3
+ metadata.gz: 5739cbe125e5b0ad1ce3313796fe31a7905e76a5
4
+ data.tar.gz: ce6cf7d71db0ffbb7d5e97d8bf542b174161fd1a
5
5
  SHA512:
6
- metadata.gz: ee41aa50efbb6104ade671e9c56c48a3fd94232a97ff96e8e03e2f114575b6ff20d1e89d1c5c6515721b69a6adcc7009e28373c260cd6fcedbd3f3ca77211574
7
- data.tar.gz: 1ec323a2a4a9cdf82e6997751f77924f07049465b359a1511800c83c9329c505d91dc9fb5c91989496bcf018462cea01544b6c7f2465ae8b7ae72244a0ea13f1
6
+ metadata.gz: 82d630f9d1c0899ad219284670b4c903cba6835cb5a215447ae2092c4d13f54e55b108713dcbaa5242b7739fe9448755995c4b8fec108d9e885a73f19516b362
7
+ data.tar.gz: fa7ce0a94c122e71326e96db0ceddfaa0a7d08747f94d0b19939b96865d2b880ebf6ccc183e0f41b356366eb421b7c45152866246637dcf0a8881b6c580666ee
@@ -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.3.0
4
+ before_install: gem install bundler -v 1.11.2
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in em-simple_telnet.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,5 @@
1
+ Copyright (c) 2016, Patrik Wenger
2
+
3
+ Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
4
+
5
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
@@ -0,0 +1,149 @@
1
+ [![Build Status on Travis CI](https://travis-ci.org/paddor/em-simple_telnet.svg?branch=master)](https://travis-ci.org/paddor/em-simple_telnet?branch=master)
2
+ [![Inline docs](http://inch-ci.org/github/paddor/em-simple_telnet.svg?branch=master&style=shields)](http://inch-ci.org/github/paddor/em-simple_telnet)
3
+
4
+ # SimpleTelnet
5
+
6
+ Provides telnet client functionality.
7
+
8
+ This class was once derived from the
9
+ [Net::Telnet](http://ruby-doc.org/stdlib-2.0.0/libdoc/net/telnet/rdoc/Net/Telnet.html)
10
+ class in Ruby's standard library. It uses EventMachine, adds some
11
+ functionality around logging and other useful features that came up useful back
12
+ when I was using this code productively.
13
+
14
+ It was developed with simplicity in mind. It tries to hide the complexity of
15
+ asynchronous programming using Fibers.
16
+
17
+
18
+ Here's the [API documentation](http://www.rubydoc.info/gems/em-simple_telnet)
19
+ of the current release.
20
+
21
+ ## News
22
+
23
+ With the coming up release I plan to modernize this repository a bit. That
24
+ means porting it to a standard `bundle gem`-like structure, and improve
25
+ documentation and make it more YARD compatible, which is used on
26
+ [rubydoc.info](http://www.rubydoc.info/github/paddor/em-simple_telnet/master).
27
+
28
+ I plan to add specs as soon as I've released a related gem called
29
+ _em-telnet_server_ (see below).
30
+
31
+ ## Overview
32
+
33
+ This section has been copied and slightly modified from Net::Telnet's documentation.
34
+
35
+ The telnet protocol allows a client to login remotely to a user account on a
36
+ server and execute commands via a shell. The equivalent is done by creating a
37
+ SimpleTelnet::Connection instance with the `:host` option set to your host
38
+ along with a block which defines the task to be done on the host. The
39
+ established connection (login already performed by `#login`) is passed to the
40
+ block. In the block, you'd normally make one or more calls to `#cmd`. After the
41
+ block returns, the connection is automatically closed.
42
+
43
+ This class can also be used to connect to non-telnet services, such as SMTP
44
+ or HTTP. In this case, you normally want to provide the `:port`
45
+ option to specify the port to connect to, and set the `:telnet_mode`
46
+ option to `false` to prevent the client from attempting to interpret telnet
47
+ command sequences. Generally, `#login` will not work with other protocols,
48
+ and you have to handle authentication yourself.
49
+
50
+ ## Differences to Net::Telnet
51
+
52
+ * based on EventMachine and `Fiber`s
53
+ * uses lowercase Symbols (like `:host`) for options (instead of `"Host")
54
+ * provides per connection logging for:
55
+ * general activity (see `#logger`)
56
+ * output log (see the option `:output_log` and `#output_log`)
57
+ * commands sent (see the option `:command_log` and `#command_log`)
58
+ * debug logging (if `logger.debug?`)
59
+ * prints recently received data in a more human-friendly 0.5s interval, as
60
+ opposed to single characters (because EventMachine is fast)
61
+ * no hexdump log
62
+ * can handle extremely big outputs by deferring checking for the prompt in the
63
+ output in case it's getting huge
64
+ * the `:connect_timeout` which specifies the timeout for establishing new
65
+ connections
66
+ * the `:wait_time` option which is useful for commands that result in multiple
67
+ prompts
68
+ - it specifies the time to wait for more data to arrive after what looks like
69
+ a prompt
70
+ * `#last_command` sent
71
+ * `#last_prompt` last prompt matched
72
+ * `#logged_in` time when login succeeded
73
+ * `#last_data_sent_at` time when last data was sent
74
+ * `SimpleTelnet::TimeoutError` exceptions know the causing command and on which
75
+ host it happened (`#hostname`, `#command`)
76
+
77
+ ## Examples
78
+
79
+ If you're starting from scratch and simply want to access a single host via
80
+ telnet, do something like this:
81
+
82
+ ```ruby
83
+ opts = {
84
+ host: "localhost",
85
+ username: "user",
86
+ password: "secret",
87
+ }
88
+
89
+ EM::P::SimpleTelnet.new(opts) do |host|
90
+ # At this point, we're already logged in.
91
+
92
+ host.cmd("touch /my/file")
93
+
94
+ # get some output
95
+ puts host.cmd("ls -la")
96
+
97
+ host.timeout(30) do
98
+ # custom timeout for this block
99
+ host.cmd "slow command"
100
+ end
101
+ end
102
+ ```
103
+
104
+ If you already have an EventMachine reactor running, you can use SimpleTelnet
105
+ inside it, like here:
106
+
107
+ ```ruby
108
+ EventMachine.run do
109
+
110
+ opts = {
111
+ host: "localhost",
112
+ username: "user",
113
+ password: "secret",
114
+ output_log: "output.log", # log output to file
115
+ command_log: "command.log", # log commands to file
116
+ }
117
+
118
+ EM::P::SimpleTelnet.new(opts) do |host|
119
+ # already logged in
120
+ puts host.cmd("ls -la")
121
+ end
122
+ end
123
+ ```
124
+
125
+ By the way, `SimpleTelnet::Connection` and
126
+ `EventMachine::Protocols::SimpleTelnet` are the same.
127
+
128
+ # Related Projects
129
+
130
+ I'm planning to release
131
+ [_em-massive_telnet_](https://github.com/paddor/em-massive_telnet) for
132
+ massively parallel telnet connections (from client to server) and
133
+ [_em-telnet_server_](https://github.com/paddor/em-telnet_server) which can be
134
+ used to build your own telnet server based on EventMachine.
135
+
136
+ I've written the code for these two gems years ago, but I'll need to carefully
137
+ extract it so any sensitive information is stripped off.
138
+
139
+ ## References
140
+
141
+ There is a large number of RFCs relevant to the Telnet protocol.
142
+ RFCs 854-861 define the base protocol. For a complete listing
143
+ of relevant RFCs, see
144
+ http://www.omnifarious.org/~hopper/technical/telnet-rfc.html
145
+
146
+ ## License
147
+
148
+ The gem is available as open source under the terms of the [ISC License](http://opensource.org/licenses/ISC).
149
+ See the [LICENSE](https://github.com/paddor/em-simple_telnet/blob/master/LICENSE) file.
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task :default => :test
data/TODO ADDED
@@ -0,0 +1,9 @@
1
+ * new gem: em-massive_telnet
2
+ * uses SimpleTelnet::Connection
3
+ * MassiveTelnet
4
+ - Node
5
+ - #connection_proxy
6
+ - Migration (or better name)
7
+ * new gem: em-telnet_server
8
+ * uses Simulators::Node
9
+ * factor login related stuff out to WithLogin
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'em-simple_telnet/version'
5
+
6
+
7
+ Gem::Specification.new do |s|
8
+ s.name = 'em-simple_telnet'
9
+ s.version = SimpleTelnet::VERSION
10
+ s.authors = ["Patrik Wenger"]
11
+ s.email = ["paddor@gmail.com"]
12
+
13
+ s.summary = "Simple telnet client on EventMachine"
14
+ s.description = "This library provides a very simple way to connect to " +
15
+ "telnet servers using EventMachine in a seemingly synchronous manner."
16
+ s.homepage = "http://github.com/paddor/em-simple_telnet"
17
+ s.license = "ISC"
18
+
19
+ s.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
20
+ s.require_paths = ["lib"]
21
+
22
+ s.add_development_dependency "bundler", "~> 1.11"
23
+ s.add_development_dependency "rake", "~> 10.0"
24
+ s.add_development_dependency "minitest", "~> 5.0"
25
+ s.add_dependency('eventmachine', '>= 1.0.0')
26
+ end
@@ -1,34 +1,54 @@
1
+ require 'em-simple_telnet/version'
1
2
  require "fiber"
2
3
  require 'timeout' # for Timeout::Error
3
4
  require "socket" # for SocketError
4
5
  require "eventmachine"
5
6
  require "logger"
6
7
 
7
- module EventMachine
8
-
9
8
  ##
10
9
  # Provides the facility to connect to telnet servers using EventMachine. The
11
10
  # asynchronity is hidden so you can use this library just like Net::Telnet in
12
11
  # a seemingly synchronous manner. See README for an example.
13
12
  #
14
- # EventMachine.run do
13
+ # @example Standalone
14
+ # opts = {
15
+ # host: "localhost",
16
+ # username: "user",
17
+ # password: "secret",
18
+ # }
19
+ #
20
+ # EM::P::SimpleTelnet.new(opts) do |host|
21
+ # # At this point, we're already logged in.
22
+ #
23
+ # host.cmd("touch /my/file")
24
+ #
25
+ # # get some output
26
+ # puts host.cmd("ls -la")
27
+ #
28
+ # host.timeout(30) do
29
+ # # custom timeout for this block
30
+ # host.cmd "slow command"
31
+ # end
32
+ # end
15
33
  #
34
+ # @example Inside an existing EventMachine loop
35
+ # EventMachine.run do
36
+ #
16
37
  # opts = {
17
38
  # host: "localhost",
18
39
  # username: "user",
19
40
  # password: "secret",
41
+ # output_log: "output.log", # log output to file
42
+ # command_log: "command.log", # log commands to file
20
43
  # }
21
- #
44
+ #
22
45
  # EM::P::SimpleTelnet.new(opts) do |host|
23
46
  # # already logged in
24
47
  # puts host.cmd("ls -la")
25
48
  # end
26
49
  # end
27
50
  #
28
- # Because of being event-driven, it performs quite well and can handle a lot
29
- # of connections concurrently.
30
- #
31
- class Protocols::SimpleTelnet < Connection
51
+ class SimpleTelnet::Connection < EventMachine::Connection
32
52
 
33
53
  # :stopdoc:
34
54
  IAC = 255.chr # "\377" # "\xff" # interpret as command
@@ -119,8 +139,10 @@ class Protocols::SimpleTelnet < Connection
119
139
  attr_accessor :command
120
140
  end
121
141
 
122
- # default options for new connections (used for merging)
123
- DefaultOptions = {
142
+ # @return [Hash] default options for new connections (used for merging)
143
+ # @see #initialize
144
+ # @see .connect
145
+ DEFAULT_OPTIONS = {
124
146
  host: "localhost",
125
147
  port: 23,
126
148
  prompt: %r{[$%#>] \z}n,
@@ -131,6 +153,7 @@ class Protocols::SimpleTelnet < Connection
131
153
  telnet_mode: true,
132
154
  output_log: nil,
133
155
  command_log: nil,
156
+ logger_class: Logger,
134
157
  login_prompt: %r{[Ll]ogin[: ]*\z}n,
135
158
  password_prompt: %r{[Pp]ass(?:word|phrase)[: ]*\z}n,
136
159
  username: nil,
@@ -141,27 +164,26 @@ class Protocols::SimpleTelnet < Connection
141
164
  BINARY: false,
142
165
  }.freeze
143
166
 
144
- # used to terminate the reactor when everything is done
145
- stop_ticks = 0
146
- StopWhenEMDone = lambda do
147
- stop_ticks += 1
148
- if stop_ticks >= 100
149
- stop_ticks = 0
150
- # stop when everything is done
151
- if self.connection_count.zero? and EventMachine.defers_finished?
152
- EventMachine.stop
153
- else
154
- EventMachine.next_tick(&StopWhenEMDone)
155
- end
167
+ # @deprecated
168
+ DefaultOptions = DEFAULT_OPTIONS
169
+
170
+ # @return [Proc] used to stop EventMachine when everything has completed
171
+ STOP_WHEN_DONE = lambda do
172
+ # stop when everything is done
173
+ if self.connection_count.zero? and EventMachine.defers_finished?
174
+ EventMachine.stop
156
175
  else
157
- EventMachine.next_tick(&StopWhenEMDone)
176
+ EventMachine.next_tick(&STOP_WHEN_DONE)
158
177
  end
159
178
  end
160
179
 
161
- # number of active connections
180
+ # @deprecated
181
+ StopWhenEMDone = STOP_WHEN_DONE
182
+
183
+ # @return [Integer] number of active connections
162
184
  @@_telnet_connection_count = 0
163
185
 
164
- # the root fiber
186
+ # @return [Fiber] the root fiber
165
187
  RootFiber = Fiber.current
166
188
 
167
189
  # SimpleTelnet.logger
@@ -170,25 +192,23 @@ class Protocols::SimpleTelnet < Connection
170
192
  @logger.level = Logger::INFO
171
193
  @logger.level = Logger::DEBUG if $DEBUG
172
194
 
173
- # @!attribute [r]
174
- # the logger instance for SimpleTelnet
175
- def self.logger() @logger end
176
-
177
195
  class << self
196
+ # @return [Logger, #debug, #debug?, #info, #warn, ...] the logger instance
197
+ # for SimpleTelnet
198
+ def logger() @logger end
178
199
 
179
- ##
180
200
  # Recognizes whether this call was issued by the user program or by
181
201
  # EventMachine. If the call was not issued by EventMachine, merges the
182
- # options provided with the DefaultOptions and creates a Fiber (not
202
+ # options provided with the {DEFAULT_OPTIONS} and creates a Fiber (not
183
203
  # started yet). Inside the Fiber SimpleTelnet.connect would be called.
184
204
  #
185
205
  # If EventMachine's reactor is already running, just starts the Fiber.
186
206
  #
187
207
  # If it's not running yet, starts a new EventMachine reactor and starts the
188
- # Fiber. The EventMachine block is stopped using the StopWhenEMDone proc
189
- # (lambda).
208
+ # Fiber. It'll stop automatically when everything has completed
209
+ # (connections and deferred tasks).
190
210
  #
191
- # The (closed) connection is returned.
211
+ # @return [SimpleTelnet::Connection] the (closed) connection
192
212
  #
193
213
  def new *args, &blk
194
214
  # call super if first argument is a connection signature of
@@ -225,21 +245,29 @@ class Protocols::SimpleTelnet < Connection
225
245
  # start EventMachine and stop it when connection is done
226
246
  EventMachine.run do
227
247
  fiber.resume
228
- EventMachine.next_tick(&StopWhenEMDone)
248
+ EventMachine.next_tick(&STOP_WHEN_DONE)
229
249
  end
230
250
  end
231
251
  return connection
232
252
  end
233
253
 
234
- ##
235
- # Merges DefaultOptions with _opts_. Establishes the connection to the
236
- # <tt>:host</tt> key using EventMachine.connect, logs in using #login and
237
- # passes the connection to the block provided. Closes the connection using
238
- # #close after the block terminates, unless it's already #closed?. The
239
- # connection is then returned.
254
+ # Establishes connection to the host.
240
255
  #
241
- def connect opts
242
- opts = DefaultOptions.merge opts
256
+ # Merges {DEFAULT_OPTIONS} with _opts_. Tells EventMachine to establish
257
+ # the connection to the desired host and port using
258
+ # {SimpleTelnet::Connection}, and logs into the host using {#login}.
259
+ #
260
+ # Passes the connection to the block provided. It also ensures that the
261
+ # connection is closed using {#close} after the block returns, unless it's
262
+ # already {#closed?}. The connection is then returned.
263
+ #
264
+ # @option opts [String] :host the hostname to connect to
265
+ # @option opts [Integer] :port the TCP port to connect to
266
+ # @yieldparam connection [SimpleTelnet::Connection] the logged in
267
+ # connection
268
+ # @return [SimpleTelnet::Connection]
269
+ def connect(opts)
270
+ opts = DEFAULT_OPTIONS.merge opts
243
271
 
244
272
  params = [
245
273
  # for EventMachine.connect
@@ -281,104 +309,99 @@ class Protocols::SimpleTelnet < Connection
281
309
  return connection
282
310
  end
283
311
 
284
- ##
285
- # Returns the number of active connections
286
- # (<tt>@@_telnet_connection_count</tt>).
287
- #
312
+ # @return [Integer] number of active connections
288
313
  def connection_count
289
314
  @@_telnet_connection_count
290
315
  end
291
316
  end
292
317
 
293
- ##
294
318
  # Initializes the current instance. _opts_ is a Hash of options. The default
295
- # values are in the constant DefaultOptions. The following keys are
296
- # recognized:
319
+ # values are in the constant {DEFAULT_OPTIONS}.
297
320
  #
298
- # +:host+::
299
- # the hostname or IP address of the host to connect to, as a String.
300
- # Defaults to "localhost".
321
+ # @option opts [String] :host ("localhost")
322
+ # the hostname or IP address of the host to connect to.
301
323
  #
302
- # +:port+::
303
- # the port to connect to. Defaults to 23.
324
+ # @option opts [Integer] :port (23)
325
+ # the TCP port to connect to
304
326
  #
305
- # +:bin_mode+::
306
- # if +false+ (the default), newline substitution is performed. Outgoing LF
307
- # is converted to CRLF, and incoming CRLF is converted to LF. If +true+,
308
- # this substitution is not performed. This value can also be set with the
309
- # #bin_mode= method. The outgoing conversion only applies to the #puts
310
- # and #print methods, not the #write method. The precise nature of the
327
+ # @option opts [Boolean] :bin_mode (false)
328
+ # if +false+, newline substitution is performed. Outgoing LF is converted
329
+ # to CRLF, and incoming CRLF is converted to LF. If +true+, this
330
+ # substitution is not performed. This value can also be set using
331
+ # {#bin_mode=}. The outgoing conversion only applies to the {#puts} and
332
+ # {#print} methods, not the {#write} method. The precise nature of the
311
333
  # newline conversion is also affected by the telnet options SGA and BIN.
312
334
  #
313
- # +:output_log+::
335
+ # @option opts [String, nil] :output_log (nil)
314
336
  # the name of the file to write connection status messages and all
315
337
  # received traffic to. In the case of a proper Telnet session, this will
316
338
  # include the client input as echoed by the host; otherwise, it only
317
339
  # includes server responses. Output is appended verbatim to this file.
318
340
  # By default, no output log is kept.
319
341
  #
320
- # +:command_log+::
342
+ # @option opts [String, nil] :command_log (nil)
321
343
  # the name of the file to write the commands executed in this Telnet
322
344
  # session. Commands are appended to this file. By default, no command
323
345
  # log is kept.
324
346
  #
325
- # +:prompt+::
347
+ # @option opts [Class] :logger_class (Logger)
348
+ # the class used to create two logger instances which are used for output
349
+ # and command logging.
350
+ #
351
+ # @option opts [Regexp, String] :prompt (%r{[$%#>] \z}n)
326
352
  # a regular expression matching the host's command-line prompt sequence.
327
- # This is needed by the Telnet class to determine when the output from a
328
- # command has finished and the host is ready to receive a new command. By
329
- # default, this regular expression is <tt>%r{[$%#>] \z}n</tt>.
353
+ # This is needed to determine the end of a command's output.
330
354
  #
331
- # +:login_prompt+::
332
- # a regular expression (or String, see #waitfor) used to wait for the
355
+ # @option opts [Regexp, String] :login_prompt (%r{[Ll]ogin[: ]*\z}n)
356
+ # a regular expression (or String, see {#waitfor}) used to wait for the
333
357
  # login prompt.
334
358
  #
335
- # +:password_prompt+::
336
- # a regular expression (or String, see #waitfor) used to wait for the
359
+ # @option opts [Regexp, String] :password_prompt
360
+ # (%r{[Pp]ass(?:word|phrase)[: ]*\z}n)
361
+ # a regular expression (or String, see {#waitfor}) used to wait for the
337
362
  # password prompt.
338
363
  #
339
- # +:username+::
364
+ # @option opts [String, nil] :username (nil)
340
365
  # the String that is sent to the telnet server after seeing the login
341
- # prompt. Just leave this value as +nil+ which is the default value if you
342
- # don't have to log in.
366
+ # prompt. +nil+ means there's no need to log in.
343
367
  #
344
- # +:password+::
368
+ # @option opts [String, nil] :password (nil)
345
369
  # the String that is sent to the telnet server after seeing the password
346
- # prompt. Just leave this value as +nil+ which is the default value if you
347
- # don't have to print a password after printing the username.
370
+ # prompt. +nil+ means there's no need for a password.
348
371
  #
349
- # +:telnet_mode+::
350
- # a boolean value, +true+ by default. In telnet mode, traffic received
372
+ # @option opts [Boolean] :telnet_mode (true)
373
+ # In telnet mode, traffic received
351
374
  # from the host is parsed for special command sequences, and these
352
- # sequences are escaped in outgoing traffic sent using #puts or #print
353
- # (but not #write). If you are connecting to a non-telnet service (such
354
- # as SMTP or POP), this should be set to "false" to prevent undesired data
355
- # corruption. This value can also be set by the #telnetmode method.
356
- #
357
- # +:timeout+::
358
- # the number of seconds (default: +10+) to wait before timing out while
359
- # waiting for the prompt (in #waitfor). Exceeding this timeout causes a
375
+ # sequences are escaped in outgoing traffic sent using {#puts} or {#print}
376
+ # (but not {#write}). If you are connecting to a non-telnet service (such
377
+ # as SMTP or POP), this should be set to +false+ to prevent undesired data
378
+ # corruption. This value can also be set by the {#telnet_mode=} method.
379
+ #
380
+ # @option opts [Integer] :timeout (10)
381
+ # the number of seconds to wait before timing out while
382
+ # waiting for the prompt (in {#waitfor}). Exceeding this timeout causes a
360
383
  # TimeoutError to be raised. You can disable the timeout by setting
361
384
  # this value to +nil+.
362
385
  #
363
- # +:connect_timeout+::
364
- # the number of seconds (default: +3+) to wait before timing out the
365
- # initial attempt to connect. You can disable the timeout by setting this
366
- # value to +nil+.
367
- #
368
- # +:wait_time+::
369
- # the amount of time to wait after seeing what looks like a prompt (that
370
- # is, received data that matches the Prompt option regular expression) to
371
- # see if more data arrives. If more data does arrive in this time, it
372
- # assumes that what it saw was not really a prompt. This is to try to
373
- # avoid false matches, but it can also lead to missing real prompts (if,
374
- # for instance, a background process writes to the terminal soon after the
375
- # prompt is displayed). By default, set to 0, meaning not to wait for
376
- # more data.
377
- #
378
- # The options are actually merged in connect().
379
- #
380
- def initialize opts
381
- @telnet_options = opts
386
+ # @option opts [Integer] :connect_timeout (3)
387
+ # the number of seconds to wait before timing out the initial attempt to
388
+ # connect. You can disable the timeout during login by setting this value
389
+ # to +nil+.
390
+ #
391
+ # @option opts [Integer, Float] :wait_time (0)
392
+ # the number of seconds to wait after seeing what looks like a prompt
393
+ # (that is, received data that matches the :prompt option value) to let
394
+ # more data arrive. If more data does arrive during that time, it is
395
+ # assumed that the previous prompt was in fact not the final prompt. This
396
+ # can avoid false matches, but it can also lead to missing real prompts
397
+ # (if, for instance, a background process writes to the terminal soon
398
+ # after the prompt is displayed). The default of zero means not to wait
399
+ # for more data after seeing what looks like a prompt.
400
+ #
401
+ # The options are actually merged in {.connect}.
402
+ #
403
+ def initialize(opts)
404
+ @options = opts
382
405
  @last_command = nil
383
406
 
384
407
  @logged_in = nil
@@ -395,22 +418,31 @@ class Protocols::SimpleTelnet < Connection
395
418
  setup_logging
396
419
  end
397
420
 
398
- # Last command that was executed in this telnet session
421
+ # @return [String] Last command that was executed in this telnet session
399
422
  attr_reader :last_command
400
423
 
401
- # connection specific logger used to log output
424
+ # @return [Logger] connection specific logger used to log output
402
425
  attr_reader :output_logger
403
426
 
404
- # connection specific logger used to log commands
427
+ # @return [Logger] connection specific logger used to log commands
405
428
  attr_reader :command_logger
406
429
 
407
- # used telnet options Hash
408
- attr_reader :telnet_options
430
+ # @return [Hash] used telnet options Hash
431
+ attr_reader :options
409
432
 
410
- # the callback executed again and again to resume this connection's Fiber
433
+ # @deprecated
434
+ # Same as {#options}.
435
+ # @return [Hash]
436
+ def telnet_options
437
+ @options
438
+ end
439
+
440
+ # @return [Proc] the callback executed again and again to resume this
441
+ # connection's Fiber
411
442
  attr_accessor :fiber_resumer
412
443
 
413
- # logger for connection activity (messages from SimpleTelnet)
444
+ # @return [Logger, #debug, #debug?, #info, ...] logger for connection
445
+ # activity (messages from SimpleTelnet)
414
446
  attr_accessor :logger
415
447
 
416
448
  # @deprecated use {#fiber_resumer} instead
@@ -418,137 +450,124 @@ class Protocols::SimpleTelnet < Connection
418
450
  fiber_resumer
419
451
  end
420
452
 
421
- # last prompt matched
453
+ # @return [String] last prompt matched
422
454
  attr_reader :last_prompt
423
455
 
424
- ##
425
- # Return current telnet mode option of this connection.
426
- #
456
+ # @return [Time] when the last data was sent
457
+ attr_reader :last_data_sent_at
458
+
459
+ # @return [Boolean] whether telnet mode is enabled or not
427
460
  def telnet_mode?
428
- @telnet_options[:telnet_mode]
461
+ @options[:telnet_mode]
429
462
  end
430
463
 
431
- ##
432
464
  # Turn telnet command interpretation on or off for this connection. It
433
465
  # should be on for true telnet sessions, off if used to connect to a
434
466
  # non-telnet service such as SMTP.
435
467
  #
468
+ # @param bool [Boolean] whether to use telnet mode for this connection
436
469
  def telnet_mode=(bool)
437
- @telnet_options[:telnet_mode] = bool
470
+ @options[:telnet_mode] = bool
438
471
  end
439
472
 
440
- ##
441
- # Return current bin mode option of this connection.
442
- #
473
+ # @return [Boolean] current bin mode option of this connection
443
474
  def bin_mode?
444
- @telnet_options[:bin_mode]
475
+ @options[:bin_mode]
445
476
  end
446
477
 
447
- ##
448
478
  # Turn newline conversion on or off for this connection.
449
- #
479
+ # @param bool [Boolean] whether to use bin mode (no newline conversion) for
480
+ # this connection
450
481
  def bin_mode=(bool)
451
- @telnet_options[:bin_mode] = bool
482
+ @options[:bin_mode] = bool
452
483
  end
453
484
 
454
- ##
455
485
  # Set the activity timeout to _seconds_ for this connection. To disable it,
456
- # set it to +0+ or +nil+.
457
- #
458
- def timeout= seconds
459
- @telnet_options[:timeout] = seconds
486
+ # set it to +0+ or +nil+. If no data is received (or sent) for that amount
487
+ # of time, the connection will be closed.
488
+ # @param seconds [Integer] the new timeout in seconds
489
+ def timeout=(seconds)
490
+ @options[:timeout] = seconds
460
491
  set_comm_inactivity_timeout( seconds )
461
492
  end
462
493
 
463
- ##
464
- # If a block is given, sets the timeout to _seconds_ (see #timeout=),
465
- # executes the block and restores the previous timeout. The block value is
466
- # returned. This is useful if you want to execute one or more commands with
467
- # a special timeout.
494
+ # If a block is given, sets the timeout to _seconds_ and executes the block
495
+ # and restores the previous timeout. This is useful when you want to
496
+ # temporarily change the timeout for some commands.
468
497
  #
469
498
  # If no block is given, the current timeout is returned.
470
499
  #
471
- # Example:
472
- #
473
- # current_timeout = host.timeout
500
+ # @example
501
+ # current_timeout = host.timeout
474
502
  #
475
- # host.timeout 200 do
476
- # host.cmd "command 1"
477
- # host.cmd "command 2"
478
- # end
503
+ # host.timeout(200) do
504
+ # host.cmd "command 1"
505
+ # host.cmd "command 2"
506
+ # end
479
507
  #
480
- def timeout seconds=nil
481
- if block_given?
482
- before = @telnet_options[:timeout]
483
- self.timeout = seconds
484
- begin
485
- yield
486
- ensure
487
- self.timeout = before
488
- end
489
- else
490
- if seconds
491
- logger.warn "Use #timeout= to set the timeout."
492
- end
493
- @telnet_options[:timeout]
494
- end
508
+ # @return [Integer] the current timeout, if no block given
509
+ # @return [Object] the block's value, if it was given
510
+ # @see #timeout=
511
+ def timeout(seconds = nil)
512
+ return @options[:timeout] unless block_given?
513
+
514
+ before = @options[:timeout]
515
+ self.timeout = seconds
516
+ yield
517
+ ensure
518
+ self.timeout = before
495
519
  end
496
520
 
497
- ##
498
- # When the login succeeded for this connection.
499
- #
521
+ # @return [Time] when the login succeeded for this connection
500
522
  attr_reader :logged_in
501
523
 
502
- ##
503
- # Returns +true+ if the login already succeeded for this connection.
504
- # Returns +false+ otherwise.
505
- #
524
+ # @return [Boolean] whether the login already succeeded for this connection
506
525
  def logged_in?
507
526
  @logged_in ? true : false
508
527
  end
509
528
 
510
- ##
511
- # Returns +true+ if the connection is closed.
512
- #
529
+ # @return [Boolean] whether the connection is closed.
513
530
  def closed?
514
531
  @connection_state == :closed
515
532
  end
516
533
 
517
- ##
518
534
  # Called by EventMachine when data is received.
519
535
  #
520
- # The data is processed using #preprocess_telnet. The data then logged using
521
- # #log_output. Then calls #process_payload.
536
+ # The data is processed using {#preprocess}, which processes telnet
537
+ # sequences and strips them away. The resulting "payload" is
538
+ # logged and handed over to {#process_payload}.
522
539
  #
523
- def receive_data data
540
+ # @param data [String] newly received raw data, including telnet sequences
541
+ # @return [void]
542
+ def receive_data(data)
524
543
  @recently_received_data << data if log_recently_received_data?
525
- if @telnet_options[:telnet_mode]
544
+ if @options[:telnet_mode]
526
545
  c = @input_rest + data
527
546
  se_pos = c.rindex(/#{IAC}#{SE}/no) || 0
528
547
  sb_pos = c.rindex(/#{IAC}#{SB}/no) || 0
529
548
  if se_pos < sb_pos
530
- buf = preprocess_telnet(c[0 ... sb_pos])
549
+ buf = preprocess(c[0 ... sb_pos])
531
550
  @input_rest = c[sb_pos .. -1]
532
551
 
533
552
  elsif pt_pos = c.rindex(
534
553
  /#{IAC}[^#{IAC}#{AO}#{AYT}#{DM}#{IP}#{NOP}]?\z/no) ||
535
554
  c.rindex(/\r\z/no)
536
555
 
537
- buf = preprocess_telnet(c[0 ... pt_pos])
556
+ buf = preprocess(c[0 ... pt_pos])
538
557
  @input_rest = c[pt_pos .. -1]
539
558
 
540
559
  else
541
- buf = preprocess_telnet(c)
560
+ buf = preprocess(c)
542
561
  @input_rest.clear
543
562
  end
544
563
  else
545
564
  # Not Telnetmode.
546
565
  #
547
- # We cannot use #preprocess_telnet on this data, because that
566
+ # We cannot use #preprocess on this data, because that
548
567
  # method makes some Telnetmode-specific assumptions.
549
568
  buf = @input_rest + data
550
569
  @input_rest.clear
551
- unless @telnet_options[:bin_mode]
570
+ unless @options[:bin_mode]
552
571
  buf.chop! if buf =~ /\r\z/no
553
572
  buf.gsub!(/#{EOL}/no, "\n")
554
573
  end
@@ -557,18 +576,19 @@ class Protocols::SimpleTelnet < Connection
557
576
  # in case only telnet sequences were received
558
577
  return if buf.empty?
559
578
 
560
- log_output(buf)
579
+ @output_logger << buf if @output_logger
561
580
  process_payload(buf)
562
581
  end
563
582
 
564
- ##
565
- # Appends _buf_ to the <tt>@input_buffer</tt>.
566
- # Then cancels the @wait_time_timer and @check_input_buffer_timer if they're
567
- # set.
583
+ # Appends _buf_ to the <tt>@input_buffer</tt>.
584
+ # Then cancels the <tt>@wait_time_timer</tt> and
585
+ # <tt>@check_input_buffer_timer</tt> if they're set.
568
586
  #
569
587
  # Does some performance optimizations in case the input buffer is becoming
570
- # huge and finally calls #check_input_buffer.
588
+ # huge and finally calls {#check_input_buffer}.
571
589
  #
590
+ # @param buf [String] received data with telnet sequences stripped away
591
+ # @return [void]
572
592
  def process_payload(buf)
573
593
  # append output from server to input buffer and log it
574
594
  @input_buffer << buf
@@ -614,7 +634,7 @@ class Protocols::SimpleTelnet < Connection
614
634
  when :listening
615
635
  @fiber_resumer.(buf)
616
636
  when :connected, :sleeping
617
- logger.debug "#{node}: Discarding data that was received " +
637
+ logger.debug "#{host}: Discarding data that was received " +
618
638
  "while not waiting " +
619
639
  "for data (state = #{@connection_state.inspect}): #{buf.inspect}"
620
640
  else
@@ -623,21 +643,21 @@ class Protocols::SimpleTelnet < Connection
623
643
  end
624
644
  end
625
645
 
626
- ##
627
646
  # Checks the input buffer (<tt>@input_buffer</tt>) for the prompt we're
628
- # waiting for. Calls @fiber_resumer with the output if the
647
+ # waiting for. Calls <tt>@fiber_resumer</tt> with the output if the
629
648
  # prompt has been found.
630
649
  #
631
- # If <tt>@telnet_options[:wait_time]</tt> is set, it will wait this amount
650
+ # If <tt>@options[:wait_time]</tt> is set, it will wait this amount
632
651
  # of seconds after seeing what looks like the prompt before calling
633
652
  # @fiber_resumer. This way, more data can be received
634
- # until the real prompt is received. This is useful for commands that send
635
- # multiple prompts.
653
+ # until the real prompt is received. This is useful for commands that result
654
+ # in multiple prompts.
636
655
  #
656
+ # @return [void]
637
657
  def check_input_buffer
638
- return unless md = @input_buffer.match(@telnet_options[:prompt])
658
+ return unless md = @input_buffer.match(@options[:prompt])
639
659
 
640
- if s = @telnet_options[:wait_time] and s > 0
660
+ if s = @options[:wait_time] and s > 0
641
661
  # resume Fiber after s seconds
642
662
  @wait_time_timer = EventMachine::Timer.new(s) { process_match_data(md) }
643
663
  else
@@ -646,9 +666,10 @@ class Protocols::SimpleTelnet < Connection
646
666
  end
647
667
  end
648
668
 
649
- # Takes out the @last_prompt from _md_ (MatchData) and remembers it.
650
- # Resumes the fiber (using @fiber_resumer) with the output (which
651
- # includes the prompt and everything before).
669
+ # Remembers md as the <tt>@last_prompt</tt> and resumes the fiber, passing
670
+ # it the whole output received up to and including the match data.
671
+ # @param md [MatchData]
672
+ # @return [void]
652
673
  def process_match_data(md)
653
674
  @last_prompt = md.to_s # remember the prompt matched
654
675
  output = md.pre_match + @last_prompt
@@ -656,39 +677,34 @@ class Protocols::SimpleTelnet < Connection
656
677
  @fiber_resumer.(output)
657
678
  end
658
679
 
659
- ##
660
680
  # Read data from the host until a certain sequence is matched.
661
681
  #
662
- # All data read will be returned in a single string. Note that the received
663
- # data includes the matched sequence we were looking for.
664
- #
665
- # _prompt_ can be a Regexp or String. If it's not a Regexp, it's converted
666
- # to a Regexp (all special characters escaped) assuming it's a String.
667
- #
668
- # _opts_ can be a hash of options. The following options are used and thus
669
- # can be overridden:
670
- #
671
- # * +:timeout+
672
- # * +:wait_time+ (actually used by #check_input_buffer)
673
- #
674
- def waitfor prompt=nil, opts={}
682
+ # @param prompt [Regexp, String] If it's not a Regexp, it's converted to
683
+ # a Regexp (all special characters escaped) assuming it's a String.
684
+ # @option opts [Integer] :timeout the timeout while waiting for new data to
685
+ # arrive
686
+ # @option opts [Integer] :wait_time time to wait after receiving what looks
687
+ # like a prompt
688
+ # @return [String] output including prompt
689
+ # @raise [Errno::ENOTCONN] if connection is closed
690
+ def waitfor(prompt = nil, opts = {})
675
691
  if closed?
676
692
  raise Errno::ENOTCONN,
677
693
  "Can't wait for anything when connection is already closed!"
678
694
  end
679
- options_were = @telnet_options
695
+ options_were = @options
680
696
  timeout_was = self.timeout if opts.key?(:timeout)
681
697
  opts[:prompt] = prompt if prompt
682
- @telnet_options = @telnet_options.merge opts
698
+ @options = @options.merge opts
683
699
 
684
700
  # convert String prompt into a Regexp
685
- unless @telnet_options[:prompt].is_a? Regexp
686
- regex = Regexp.new(Regexp.quote(@telnet_options[:prompt]))
687
- @telnet_options[:prompt] = regex
701
+ unless @options[:prompt].is_a? Regexp
702
+ regex = Regexp.new(Regexp.quote(@options[:prompt]))
703
+ @options[:prompt] = regex
688
704
  end
689
705
 
690
706
  # set custom inactivity timeout, if wanted
691
- self.timeout = @telnet_options[:timeout] if opts.key?(:timeout)
707
+ self.timeout = @options[:timeout] if opts.key?(:timeout)
692
708
 
693
709
  # so #unbind knows we were waiting for a prompt (in case that inactivity
694
710
  # timeout fires)
@@ -696,47 +712,40 @@ class Protocols::SimpleTelnet < Connection
696
712
 
697
713
  pause_and_wait_for_result
698
714
  ensure
699
- @telnet_options = options_were
715
+ @options = options_were
700
716
  self.timeout = timeout_was if opts.key?(:timeout)
701
717
 
702
- # #unbind could have been called in the meantime
718
+ # NOTE: #unbind could have been called in the meantime
703
719
  self.connection_state = :connected if !closed?
704
720
  end
705
721
 
706
722
  # Pauses the current Fiber. When resumed, returns the value passed. If the
707
723
  # value passed is an Exeption, it's raised.
724
+ # @return [String] value passed to Fiber#resume (output received)
725
+ # @raise [Exception] exception that has been passed to Fiber#resume
708
726
  def pause_and_wait_for_result
709
727
  result = nil
710
- while result == nil
711
- # measure how long Fiber is paused
712
- if logger.debug?
713
- before_pause = Time.now
714
- result = Fiber.yield
715
- pause_duration = Time.now - before_pause
716
-
717
- logger.debug "#{node}: Fiber was paused for " +
718
- "#{pause_duration * 1000}ms and is resumed with: #{result.inspect}"
719
- else
720
- result = Fiber.yield
721
- end
728
+ while result.nil?
729
+ result = Fiber.yield
722
730
  end
723
731
 
724
732
  raise result if result.is_a? Exception
725
733
  return result
726
734
  end
727
735
 
728
- # Identifier for this connection. Like an IP address or hostname. In this
729
- # case, it's <tt>@telnet_options[:host]</tt>.
730
- def node
731
- @telnet_options[:host]
736
+ # Hostname/address for this connection.
737
+ # @return [String]
738
+ def host
739
+ @options[:host]
732
740
  end
733
741
 
734
- # Listen for anything that's received from the node. Each received chunk
742
+ # Listen for anything that's received from the host. Each received chunk
735
743
  # will be yielded to the block passed. To make it stop listening, the block
736
- # should +return+ or +raise+ something.
744
+ # should return or raise something.
737
745
  #
738
- # The default timeout during listening is 90 seconds. Use the option
739
- # +:timeout+ to change this.
746
+ # @option opts [Integer] :timeout (90) temporary {#timeout} to use
747
+ # @yieldparam output [String] the newly output received
748
+ # @return [void]
740
749
  def listen(opts = {}, &blk)
741
750
  self.connection_state = :listening
742
751
  timeout(opts.fetch(:timeout, 90)) do
@@ -746,19 +755,22 @@ class Protocols::SimpleTelnet < Connection
746
755
  self.connection_state = :connected if !closed?
747
756
  end
748
757
 
749
- # Passes argument to #send_data.
758
+ # Passes argument to {#send_data}.
759
+ # @param s [String] raw data to send
750
760
  def write(s)
751
761
  send_data(s)
752
762
  end
753
763
 
754
- # Raises Errno::ENOTCONN in case the connection is closed (#unbind has been
755
- # called before).
764
+ # Tells EventMachine to send raw data to the telnet server. This also
765
+ # updates {#last_data_sent_at} and logs recently received data, if wanted.
766
+ # @param s [String] what to send
767
+ # @raise [Errno::ENOTCONN] if the connection is closed
756
768
  def send_data(s)
757
769
  raise Errno::ENOTCONN,
758
770
  "Can't send data: Connection is already closed." if closed?
759
- @last_sent_data = Time.now
771
+ @last_data_sent_at = Time.now
760
772
  log_recently_received_data
761
- logger.debug "#{node}: Sending #{s.inspect}"
773
+ logger.debug "#{host}: Sending #{s.inspect}"
762
774
  super
763
775
  end
764
776
 
@@ -767,18 +779,20 @@ class Protocols::SimpleTelnet < Connection
767
779
  #
768
780
  # This does _not_ automatically append a newline to the string. Embedded
769
781
  # newlines may be converted and telnet command sequences escaped depending
770
- # upon the values of #telnet_mode, #bin_mode, and telnet options set by the
782
+ # upon the values of {#telnet_mode}, {#bin_mode}, and telnet options set by the
771
783
  # host.
772
784
  #
785
+ # @param string [String] what to send
786
+ # @return [void]
773
787
  def print(string)
774
788
  string = string.gsub(/#{IAC}/no, IAC + IAC) if telnet_mode?
775
789
 
776
790
  unless bin_mode?
777
- string = if @telnet_options[:BINARY] and @telnet_options[:SGA]
791
+ string = if @options[:BINARY] and @options[:SGA]
778
792
  # IAC WILL SGA IAC DO BIN send EOL --> CR
779
793
  string.gsub(/\n/n, CR)
780
794
 
781
- elsif @telnet_options[:SGA]
795
+ elsif @options[:SGA]
782
796
  # IAC WILL SGA send EOL --> CR+NULL
783
797
  string.gsub(/\n/n, CR + NULL)
784
798
 
@@ -792,50 +806,52 @@ class Protocols::SimpleTelnet < Connection
792
806
  end
793
807
 
794
808
  ##
795
- # Sends a string to the host.
796
- #
797
- # Same as #print, but appends a newline to the string unless there's
798
- # already one.
809
+ # Sends a string to the host, along with an appended newline if there isn't
810
+ # one already.
799
811
  #
812
+ # @param string [String] what to send
813
+ # @return [void]
800
814
  def puts(string)
801
815
  string += "\n" unless string.end_with? "\n"
802
816
  print string
803
817
  end
804
818
 
805
- ##
806
- # Sends a command to the host.
819
+ # Sends a command to the host and returns its output.
807
820
  #
808
821
  # More exactly, the following things are done:
809
822
  #
810
- # * stores the command in @last_command
811
- # * logs it using #log_command
812
- # * sends a string to the host (#print or #puts)
813
- # * reads in all received data (using #waitfor)
814
- # * returns the received data as String
823
+ # * stores the command (see {#last_command})
824
+ # * logs it (see {#command_logger})
825
+ # * sends a string to the host ({#print} or {#puts})
826
+ # * reads in all received data (using {#waitfor})
827
+ #
828
+ # @example Normal usage
829
+ # output = host.cmd "ls -la"
815
830
  #
816
- # _opts_ can be a Hash of options. It is passed to #waitfor as the second
817
- # parameter. The element in _opts_ with the key <tt>:prompt</tt> is used as
818
- # the first parameter in the call to #waitfor. Example usage:
819
- #
831
+ # @example Custom Prompt
820
832
  # host.cmd "delete user john", prompt: /Are you sure?/
821
833
  # host.cmd "yes"
822
834
  #
823
- # Note that the received data includes the prompt and in most cases the
824
- # host's echo of our command.
825
- #
826
- # If _opts_ has the key <tt>:hide</tt> which evaluates to +true+, calls
827
- # #log_command with <tt>"<hidden command>"</tt> instead of the command
828
- # itself. This is useful for passwords, so they don't get logged to the
829
- # command log.
830
- #
831
- # If _opts_ has the key <tt>:raw_command</tt> which evaluates to +true+,
832
- # #print is used to send the command to the host instead of #puts.
833
- #
834
- def cmd command, opts={}
835
+ # @note The returned output includes the prompt and in most cases the
836
+ # host's echo of the command sent.
837
+ #
838
+ # @param command [String] the command to execute
839
+ # @option opts [Boolean] :hide (false) whether to hide the command from the
840
+ # command log ({#command_logger}). If so, it is logged as <tt>"<hidden
841
+ # command>"</tt> instead of the command itself. This is useful for
842
+ # passwords, so they don't get logged to the command log.
843
+ # @option opts [Boolean] :raw_command (false) whether to send a raw command
844
+ # using {#print}, as opposed to using {#puts}
845
+ # @option opts [Regexp, String] :prompt (nil) prompt to look for after this
846
+ # command's output (instead of the one set in <tt>options[:prompt]</tt>)
847
+ # @return [String] the command's output, including prompt
848
+ def cmd(command, opts = {})
835
849
  @last_command = command = command.to_s
836
850
 
837
851
  # log the command
838
- log_command(opts[:hide] ? "<hidden command>" : command)
852
+ if @command_logger
853
+ @command_logger.info(opts[:hide] ? "<hidden command>" : command)
854
+ end
839
855
 
840
856
  # send the command
841
857
  opts[:raw_command] ? print(command) : puts(command)
@@ -844,9 +860,9 @@ class Protocols::SimpleTelnet < Connection
844
860
  waitfor(opts[:prompt], opts)
845
861
  end
846
862
 
847
- ##
848
863
  # Login to the host with a given username and password.
849
864
  #
865
+ # @example
850
866
  # host.login username: "myuser", password: "mypass"
851
867
  #
852
868
  # This method looks for the login and password prompt (see implementation)
@@ -855,18 +871,16 @@ class Protocols::SimpleTelnet < Connection
855
871
  # connecting to a service other than telnet), you will need to handle login
856
872
  # yourself.
857
873
  #
858
- # If the key <tt>:password</tt> is omitted (and not set on connection
859
- # level), the method will not look for a prompt.
860
- #
861
- # The method returns all data received during the login process from the
862
- # host, including the echoed username but not the password (which the host
863
- # should not echo anyway).
864
- #
865
- # Don't forget to set <tt>@logged_in</tt> after the login succeeds when you
866
- # redefine this method!
874
+ # @note Don't forget to set <tt>@logged_in</tt> after the login succeeds if
875
+ # you override this method!
867
876
  #
877
+ # @option opts [String, nil] :username (options[:username]) the username to
878
+ # use to log in, if login is needed
879
+ # @option opts [String, nil] :password (options[:password] the password to
880
+ # use to log in, if a password is needed
881
+ # @return [String] all data received during the login process
868
882
  def login opts={}
869
- opts = @telnet_options.merge opts
883
+ opts = @options.merge opts
870
884
 
871
885
  # don't log in if username is not set
872
886
  if opts[:username].nil?
@@ -886,54 +900,54 @@ class Protocols::SimpleTelnet < Connection
886
900
  output << cmd(opts[:username])
887
901
  end
888
902
  rescue Timeout::Error
889
- e = LoginFailed.new("Timed out while expecting some kind of prompt.")
890
- e.set_backtrace $!.backtrace
891
- raise e
903
+ raise LoginFailed, "Timed out while expecting some kind of prompt."
892
904
  end
893
905
 
894
906
  @logged_in = Time.now
895
907
  output
896
908
  end
897
909
 
898
- ##
899
910
  # Called by EventMachine when the connection is being established (not after
900
- # the connection is established! see #connection_completed). This occurs
901
- # directly after the call to #initialize.
911
+ # the connection is established! see {#connection_completed}). This occurs
912
+ # directly after the call to {#initialize}.
902
913
  #
903
914
  # Sets the +pending_connect_timeout+ to
904
- # <tt>@telnet_options[:connect_timeout]</tt> seconds. This is the duration
915
+ # <tt>options[:connect_timeout]</tt> seconds. This is the duration
905
916
  # after which a TCP connection in the connecting state will fail (abort and
906
- # run #unbind). Increases <tt>@@_telnet_connection_count</tt> by one after
917
+ # run {#unbind}). Increases <tt>@@_telnet_connection_count</tt> by one after
907
918
  # that.
908
919
  #
909
920
  # Sets also the +comm_inactivity_timeout+ to
910
- # <tt>@telnet_options[:timeout]</tt> seconds. This is the duration after
921
+ # <tt>options[:timeout]</tt> seconds. This is the duration after
911
922
  # which a TCP connection is automatically closed if no data was sent or
912
923
  # received.
913
924
  #
925
+ # @return [void]
926
+ #
914
927
  def post_init
915
- self.pending_connect_timeout = @telnet_options[:connect_timeout]
916
- self.comm_inactivity_timeout = @telnet_options[:timeout]
928
+ self.pending_connect_timeout = @options[:connect_timeout]
929
+ self.comm_inactivity_timeout = @options[:timeout]
917
930
  @@_telnet_connection_count += 1
918
931
  end
919
932
 
920
- ##
921
- # Called by EventMachine after this connection is closed.
933
+ # Called by EventMachine after this connection has been closed.
934
+ #
935
+ # Decreases <tt>@@_telnet_connection_count</tt> by one and calls {#close_logs}.
922
936
  #
923
- # Decreases <tt>@@_telnet_connection_count</tt> by one and calls #close_logs.
937
+ # If we were in the connection state <tt>:waiting_for_prompt</tt>, this will
938
+ # cause a TimeoutError to be raised.
924
939
  #
925
- # After that is set, it takes a
926
- # look at <tt>@connection_state</tt>. If it was <tt>:connecting</tt>, calls
927
- # @fiber_resumer with a new instance of ConnectionFailed.
928
- # If it was <tt>:waiting_for_prompt</tt>, calls the same method with a new
929
- # instance of TimeoutError.
940
+ # If we were in the process of connecting, this will cause
941
+ # {ConnectionFailed} to be raised.
942
+
943
+ # Finally, the connection state is set to +:closed+.
930
944
  #
931
- # Finally, the <tt>@connection_state</tt> is set to +closed+.
945
+ # @return [void]
932
946
  #
933
947
  def unbind(reason)
934
948
  prev_conn_state = @connection_state
935
949
  self.connection_state = :closed
936
- logger.debug "#{node}: Unbind reason: " + reason.inspect
950
+ logger.debug "#{host}: Unbind reason: " + reason.inspect
937
951
  @@_telnet_connection_count -= 1
938
952
  close_logs
939
953
 
@@ -945,7 +959,7 @@ class Protocols::SimpleTelnet < Connection
945
959
  error = TimeoutError.new
946
960
 
947
961
  # set hostname and command
948
- if hostname = @telnet_options[:host]
962
+ if hostname = @options[:host]
949
963
  error.hostname = hostname
950
964
  end
951
965
  error.command = @last_command if @last_command
@@ -956,14 +970,17 @@ class Protocols::SimpleTelnet < Connection
956
970
  when :connecting
957
971
  @fiber_resumer.(ConnectionFailed.new)
958
972
  else
959
- logger.error "#{node}: bad connection state #{prev_conn_state.inspect} " +
973
+ logger.error "#{host}: bad connection state #{prev_conn_state.inspect} " +
960
974
  "while unbinding"
961
975
  end
962
976
  end
963
977
 
964
- ##
965
978
  # Called by EventMachine after the connection is successfully established.
966
979
  #
980
+ # If the debug level in {#logger} is active, this will cause received data
981
+ # to be logged periodically.
982
+ #
983
+ # @return [void]
967
984
  def connection_completed
968
985
  self.connection_state = :connected
969
986
  @fiber_resumer.(:connection_completed)
@@ -977,24 +994,32 @@ class Protocols::SimpleTelnet < Connection
977
994
  ##
978
995
  # Redefine this method to execute some logout command like +exit+ or
979
996
  # +logout+ before the connection is closed. Don't forget: The command will
980
- # probably not return a prompt, so use #puts, which doesn't wait for a
997
+ # probably not return a prompt, so use {#puts}, which doesn't wait for a
981
998
  # prompt.
982
999
  #
983
1000
  def close
984
1001
  end
985
1002
 
986
1003
  ##
987
- # Close output and command logs if they're set. IOError is rescued because
988
- # they could already be closed. #closed? can't be used, because the method
989
- # is not implemented by Logger, for example.
1004
+ # Close output and command logs if they're set.
1005
+
990
1006
  #
991
1007
  def close_logs
992
- begin @output_logger.close
1008
+ # NOTE: IOError is rescued because they could already be closed.
1009
+ # {#closed?} can't be used, because the method is not implemented by
1010
+ # Logger, for example.
1011
+
1012
+ begin
1013
+ @output_logger.close
993
1014
  rescue IOError
994
- end if @telnet_options[:output_log]
995
- begin @command_logger.close
1015
+ # already closed
1016
+ end if @options[:output_log]
1017
+
1018
+ begin
1019
+ @command_logger.close
996
1020
  rescue IOError
997
- end if @telnet_options[:command_log]
1021
+ # already closed
1022
+ end if @options[:command_log]
998
1023
  end
999
1024
 
1000
1025
  private
@@ -1006,9 +1031,11 @@ class Protocols::SimpleTelnet < Connection
1006
1031
  # The goal is to log data in a more readable way, by periodically log what
1007
1032
  # has recently been received, as opposed to each single character in case of
1008
1033
  # a slowly answering telnet server.
1034
+ #
1035
+ # @return [void]
1009
1036
  def log_recently_received_data
1010
1037
  return if @recently_received_data.empty? || !log_recently_received_data?
1011
- logger.debug "#{node}: Received: #{@recently_received_data.inspect}"
1038
+ logger.debug "#{host}: Received: #{@recently_received_data.inspect}"
1012
1039
  @recently_received_data.clear
1013
1040
  end
1014
1041
 
@@ -1017,8 +1044,10 @@ class Protocols::SimpleTelnet < Connection
1017
1044
  logger.debug?
1018
1045
  end
1019
1046
 
1020
- # Sets the @connection_state to _new_state_. Raises if current (old) state is
1021
- # :closed, because that can't be changed.
1047
+ # Sets a new connection state.
1048
+ # @param new_state [Symbol]
1049
+ # @raise [Errno::ENOTCONN] if current (old) state is +:closed+, because that
1050
+ # can't be changed.
1022
1051
  def connection_state=(new_state)
1023
1052
  if closed?
1024
1053
  raise Errno::ENOTCONN,
@@ -1027,44 +1056,31 @@ class Protocols::SimpleTelnet < Connection
1027
1056
  @connection_state = new_state
1028
1057
  end
1029
1058
 
1030
- ##
1031
- # Sets up output and command logging.
1059
+ # Sets up output and command logging. This depends on
1060
+ # <tt>options[:output_log]</tt> and <tt>options[:command_log]</tt>.
1032
1061
  #
1062
+ # @return [void]
1033
1063
  def setup_logging
1034
1064
  @output_logger = @command_logger = nil
1035
1065
 
1036
- if file = @telnet_options[:output_log]
1037
- @output_logger = Logger.new(file)
1038
- log_output "# Starting telnet output log at #{Time.now}\n"
1066
+ if file = @options[:output_log]
1067
+ @output_logger = @options[:logger_class].new(file)
1068
+ @output_logger.info "# Starting telnet output log at #{Time.now}\n"
1039
1069
  end
1040
1070
 
1041
- if file = @telnet_options[:command_log]
1042
- @command_logger = Logger.new(file)
1071
+ if file = @options[:command_log]
1072
+ @command_logger = @options[:logger_class].new(file)
1043
1073
  end
1044
1074
  end
1045
1075
 
1046
- ##
1047
- # Logs _output_ to output log. Appends a newline if +output+ doesn't end in
1048
- # one. To supress this behavior, set +exact+ to +true+.
1049
- #
1050
- def log_output output
1051
- @output_logger and @output_logger << output
1052
- end
1053
-
1054
- ##
1055
- # Logs _command_ to command log.
1056
- #
1057
- def log_command command
1058
- @command_logger and @command_logger.info command
1059
- end
1060
-
1061
- ##
1062
1076
  # Preprocess received data from the host.
1063
1077
  #
1064
1078
  # Performs newline conversion and detects telnet command sequences.
1065
- # Called automatically by #receive_data.
1079
+ # Called automatically by {#receive_data}.
1066
1080
  #
1067
- def preprocess_telnet string
1081
+ # @param string [String] the raw string including telnet sequences
1082
+ # @return [String] resulting data, hereby called "payload"
1083
+ def preprocess string
1068
1084
  # combine CR+NULL into CR
1069
1085
  string = string.gsub(/#{CR}#{NULL}/no, CR) if telnet_mode?
1070
1086
 
@@ -1087,7 +1103,7 @@ class Protocols::SimpleTelnet < Connection
1087
1103
  ''
1088
1104
  elsif DO[0] == $1[0] # respond to "IAC DO x"
1089
1105
  if OPT_BINARY[0] == $1[1]
1090
- @telnet_options[:BINARY] = true
1106
+ @options[:BINARY] = true
1091
1107
  send_data(IAC + WILL + OPT_BINARY)
1092
1108
  else
1093
1109
  send_data(IAC + WONT + $1[1..1])
@@ -1102,7 +1118,7 @@ class Protocols::SimpleTelnet < Connection
1102
1118
  elsif OPT_ECHO[0] == $1[1]
1103
1119
  send_data(IAC + DO + OPT_ECHO)
1104
1120
  elsif OPT_SGA[0] == $1[1]
1105
- @telnet_options[:SGA] = true
1121
+ @options[:SGA] = true
1106
1122
  send_data(IAC + DO + OPT_SGA)
1107
1123
  else
1108
1124
  send_data(IAC + DONT + $1[1..1])
@@ -1112,7 +1128,7 @@ class Protocols::SimpleTelnet < Connection
1112
1128
  if OPT_ECHO[0] == $1[1]
1113
1129
  send_data(IAC + DONT + OPT_ECHO)
1114
1130
  elsif OPT_SGA[0] == $1[1]
1115
- @telnet_options[:SGA] = false
1131
+ @options[:SGA] = false
1116
1132
  send_data(IAC + DONT + OPT_SGA)
1117
1133
  else
1118
1134
  send_data(IAC + DONT + $1[1..1])
@@ -1121,7 +1137,9 @@ class Protocols::SimpleTelnet < Connection
1121
1137
  else
1122
1138
  ''
1123
1139
  end
1124
- end
1140
+ end # string.gsub
1125
1141
  end
1126
1142
  end
1127
- end
1143
+
1144
+ # backwards compatibility
1145
+ EventMachine::Protocols::SimpleTelnet = SimpleTelnet::Connection