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 +4 -4
- data/.gitignore +9 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/LICENSE +5 -0
- data/README.md +149 -0
- data/Rakefile +10 -0
- data/TODO +9 -0
- data/em-simple_telnet.gemspec +26 -0
- data/lib/em-simple_telnet.rb +368 -350
- data/lib/em-simple_telnet/version.rb +3 -0
- metadata +61 -12
- data/README.rdoc +0 -47
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5739cbe125e5b0ad1ce3313796fe31a7905e76a5
|
4
|
+
data.tar.gz: ce6cf7d71db0ffbb7d5e97d8bf542b174161fd1a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 82d630f9d1c0899ad219284670b4c903cba6835cb5a215447ae2092c4d13f54e55b108713dcbaa5242b7739fe9448755995c4b8fec108d9e885a73f19516b362
|
7
|
+
data.tar.gz: fa7ce0a94c122e71326e96db0ceddfaa0a7d08747f94d0b19939b96865d2b880ebf6ccc183e0f41b356366eb421b7c45152866246637dcf0a8881b6c580666ee
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
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.
|
data/README.md
ADDED
@@ -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.
|
data/Rakefile
ADDED
data/TODO
ADDED
@@ -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
|
data/lib/em-simple_telnet.rb
CHANGED
@@ -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
|
-
#
|
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
|
-
|
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
|
-
|
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
|
-
#
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
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(&
|
176
|
+
EventMachine.next_tick(&STOP_WHEN_DONE)
|
158
177
|
end
|
159
178
|
end
|
160
179
|
|
161
|
-
#
|
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
|
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.
|
189
|
-
# (
|
208
|
+
# Fiber. It'll stop automatically when everything has completed
|
209
|
+
# (connections and deferred tasks).
|
190
210
|
#
|
191
|
-
#
|
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(&
|
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
|
-
|
242
|
-
|
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
|
296
|
-
# recognized:
|
319
|
+
# values are in the constant {DEFAULT_OPTIONS}.
|
297
320
|
#
|
298
|
-
#
|
299
|
-
# the hostname or IP address of the host to connect to
|
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
|
-
#
|
303
|
-
# the port to connect to
|
324
|
+
# @option opts [Integer] :port (23)
|
325
|
+
# the TCP port to connect to
|
304
326
|
#
|
305
|
-
#
|
306
|
-
# if +false
|
307
|
-
#
|
308
|
-
#
|
309
|
-
# #bin_mode=
|
310
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
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
|
-
#
|
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
|
-
#
|
336
|
-
#
|
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
|
-
#
|
364
|
+
# @option opts [String, nil] :username (nil)
|
340
365
|
# the String that is sent to the telnet server after seeing the login
|
341
|
-
# prompt.
|
342
|
-
# don't have to log in.
|
366
|
+
# prompt. +nil+ means there's no need to log in.
|
343
367
|
#
|
344
|
-
#
|
368
|
+
# @option opts [String, nil] :password (nil)
|
345
369
|
# the String that is sent to the telnet server after seeing the password
|
346
|
-
# prompt.
|
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
|
-
#
|
350
|
-
#
|
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
|
355
|
-
# corruption. This value can also be set by the #
|
356
|
-
#
|
357
|
-
#
|
358
|
-
# the number of seconds
|
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
|
-
#
|
364
|
-
# the number of seconds
|
365
|
-
#
|
366
|
-
#
|
367
|
-
#
|
368
|
-
#
|
369
|
-
# the
|
370
|
-
# is, received data that matches the
|
371
|
-
#
|
372
|
-
#
|
373
|
-
# avoid false matches, but it can also lead to missing real prompts
|
374
|
-
# for instance, a background process writes to the terminal soon
|
375
|
-
# prompt is displayed).
|
376
|
-
# more data.
|
377
|
-
#
|
378
|
-
# The options are actually merged in connect
|
379
|
-
#
|
380
|
-
def initialize
|
381
|
-
@
|
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 :
|
430
|
+
# @return [Hash] used telnet options Hash
|
431
|
+
attr_reader :options
|
409
432
|
|
410
|
-
#
|
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
|
-
#
|
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
|
-
|
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
|
-
@
|
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
|
-
@
|
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
|
-
@
|
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
|
-
@
|
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
|
-
|
459
|
-
|
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
|
-
#
|
465
|
-
#
|
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
|
-
#
|
472
|
-
#
|
473
|
-
# current_timeout = host.timeout
|
500
|
+
# @example
|
501
|
+
# current_timeout = host.timeout
|
474
502
|
#
|
475
|
-
#
|
476
|
-
#
|
477
|
-
#
|
478
|
-
#
|
503
|
+
# host.timeout(200) do
|
504
|
+
# host.cmd "command 1"
|
505
|
+
# host.cmd "command 2"
|
506
|
+
# end
|
479
507
|
#
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
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 #
|
521
|
-
#
|
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
|
-
|
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 @
|
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 =
|
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 =
|
556
|
+
buf = preprocess(c[0 ... pt_pos])
|
538
557
|
@input_rest = c[pt_pos .. -1]
|
539
558
|
|
540
559
|
else
|
541
|
-
buf =
|
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 #
|
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 @
|
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
|
-
|
579
|
+
@output_logger << buf if @output_logger
|
561
580
|
process_payload(buf)
|
562
581
|
end
|
563
582
|
|
564
|
-
|
565
|
-
#
|
566
|
-
#
|
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 "#{
|
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
|
647
|
+
# waiting for. Calls <tt>@fiber_resumer</tt> with the output if the
|
629
648
|
# prompt has been found.
|
630
649
|
#
|
631
|
-
# If <tt>@
|
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
|
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(@
|
658
|
+
return unless md = @input_buffer.match(@options[:prompt])
|
639
659
|
|
640
|
-
if s = @
|
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
|
-
#
|
650
|
-
#
|
651
|
-
#
|
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
|
-
#
|
663
|
-
#
|
664
|
-
#
|
665
|
-
#
|
666
|
-
#
|
667
|
-
#
|
668
|
-
#
|
669
|
-
#
|
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 = @
|
695
|
+
options_were = @options
|
680
696
|
timeout_was = self.timeout if opts.key?(:timeout)
|
681
697
|
opts[:prompt] = prompt if prompt
|
682
|
-
@
|
698
|
+
@options = @options.merge opts
|
683
699
|
|
684
700
|
# convert String prompt into a Regexp
|
685
|
-
unless @
|
686
|
-
regex = Regexp.new(Regexp.quote(@
|
687
|
-
@
|
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 = @
|
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
|
-
@
|
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
|
711
|
-
|
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
|
-
#
|
729
|
-
#
|
730
|
-
def
|
731
|
-
@
|
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
|
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
|
744
|
+
# should return or raise something.
|
737
745
|
#
|
738
|
-
#
|
739
|
-
#
|
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
|
-
#
|
755
|
-
#
|
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
|
-
@
|
771
|
+
@last_data_sent_at = Time.now
|
760
772
|
log_recently_received_data
|
761
|
-
logger.debug "#{
|
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 @
|
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 @
|
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
|
811
|
-
# * logs it
|
812
|
-
# * sends a string to the host (#print or #puts)
|
813
|
-
# * reads in all received data (using #waitfor)
|
814
|
-
#
|
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
|
-
#
|
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
|
-
#
|
824
|
-
# host's echo of
|
825
|
-
#
|
826
|
-
#
|
827
|
-
#
|
828
|
-
#
|
829
|
-
# command
|
830
|
-
#
|
831
|
-
#
|
832
|
-
# #print
|
833
|
-
#
|
834
|
-
|
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
|
-
|
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
|
-
#
|
859
|
-
#
|
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 = @
|
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
|
-
|
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
|
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
|
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 = @
|
916
|
-
self.comm_inactivity_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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
926
|
-
#
|
927
|
-
|
928
|
-
#
|
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
|
-
#
|
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 "#{
|
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 = @
|
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 "#{
|
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.
|
988
|
-
|
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
|
-
|
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
|
-
|
995
|
-
|
1015
|
+
# already closed
|
1016
|
+
end if @options[:output_log]
|
1017
|
+
|
1018
|
+
begin
|
1019
|
+
@command_logger.close
|
996
1020
|
rescue IOError
|
997
|
-
|
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 "#{
|
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
|
1021
|
-
#
|
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
|
-
#
|
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 = @
|
1037
|
-
@output_logger =
|
1038
|
-
|
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 = @
|
1042
|
-
@command_logger =
|
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
|
-
|
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
|
-
@
|
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
|
-
@
|
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
|
-
@
|
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
|
-
|
1143
|
+
|
1144
|
+
# backwards compatibility
|
1145
|
+
EventMachine::Protocols::SimpleTelnet = SimpleTelnet::Connection
|