em-simple_telnet 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/em-simple_telnet.rb +1027 -0
- metadata +48 -0
@@ -0,0 +1,1027 @@
|
|
1
|
+
require "fiber"
|
2
|
+
require 'timeout' # for Timeout::Error
|
3
|
+
require "socket" # for SocketError
|
4
|
+
require "eventmachine"
|
5
|
+
|
6
|
+
##
|
7
|
+
# This defines EventMachine::defers_finished? which is used by StopWhenEMDone
|
8
|
+
# to stop EventMachine safely when everything is done. The method returns
|
9
|
+
# +true+ if the @threadqueue and @resultqueue are undefined/nil/empty *and*
|
10
|
+
# none of the threads in the threadpool isn't working anymore.
|
11
|
+
#
|
12
|
+
# To do this, the method ::spawn_threadpool is redefined to start threads that
|
13
|
+
# provide a thread-local variable :working (like
|
14
|
+
# <tt>thread_obj[:working]</tt>). This variable tells whether the thread is
|
15
|
+
# still working on a deferred action or not.
|
16
|
+
#
|
17
|
+
module EventMachine # :nodoc:
|
18
|
+
def self.defers_finished?
|
19
|
+
(not defined? @threadqueue or (tq=@threadqueue).nil? or tq.empty? ) and
|
20
|
+
(not defined? @resultqueue or (rq=@resultqueue).nil? or rq.empty? ) and
|
21
|
+
(not defined? @threadpool or (tp=@threadpool).nil? or tp.none? {|t|t[:working]})
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.spawn_threadpool
|
25
|
+
until @threadpool.size == @threadpool_size.to_i
|
26
|
+
thread = Thread.new do
|
27
|
+
Thread.current.abort_on_exception = true
|
28
|
+
while true
|
29
|
+
Thread.current[:working] = false
|
30
|
+
op, cback = *@threadqueue.pop
|
31
|
+
Thread.current[:working] = true
|
32
|
+
result = op.call
|
33
|
+
@resultqueue << [result, cback]
|
34
|
+
EventMachine.signal_loopbreak
|
35
|
+
end
|
36
|
+
end
|
37
|
+
@threadpool << thread
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
##
|
43
|
+
# Provides the facility to connect to telnet servers using EventMachine. The
|
44
|
+
# asynchronity is hidden so you can use this library just like Net::Telnet in
|
45
|
+
# a seemingly synchronous manner. See README for an example.
|
46
|
+
#
|
47
|
+
# EventMachine.run do
|
48
|
+
#
|
49
|
+
# opts = {
|
50
|
+
# host: "localhost",
|
51
|
+
# username: "user",
|
52
|
+
# password: "secret",
|
53
|
+
# }
|
54
|
+
#
|
55
|
+
# EM::P::SimpleTelnet.new(opts) do |host|
|
56
|
+
# # already logged in
|
57
|
+
# puts host.cmd("ls -la")
|
58
|
+
# end
|
59
|
+
# end
|
60
|
+
#
|
61
|
+
# Because of being event-driven, it performs quite well and can handle a lot
|
62
|
+
# of connections concurrently.
|
63
|
+
#
|
64
|
+
class EventMachine::Protocols::SimpleTelnet < EventMachine::Connection
|
65
|
+
|
66
|
+
# :stopdoc:
|
67
|
+
IAC = 255.chr # "\377" # "\xff" # interpret as command
|
68
|
+
DONT = 254.chr # "\376" # "\xfe" # you are not to use option
|
69
|
+
DO = 253.chr # "\375" # "\xfd" # please, you use option
|
70
|
+
WONT = 252.chr # "\374" # "\xfc" # I won't use option
|
71
|
+
WILL = 251.chr # "\373" # "\xfb" # I will use option
|
72
|
+
SB = 250.chr # "\372" # "\xfa" # interpret as subnegotiation
|
73
|
+
GA = 249.chr # "\371" # "\xf9" # you may reverse the line
|
74
|
+
EL = 248.chr # "\370" # "\xf8" # erase the current line
|
75
|
+
EC = 247.chr # "\367" # "\xf7" # erase the current character
|
76
|
+
AYT = 246.chr # "\366" # "\xf6" # are you there
|
77
|
+
AO = 245.chr # "\365" # "\xf5" # abort output--but let prog finish
|
78
|
+
IP = 244.chr # "\364" # "\xf4" # interrupt process--permanently
|
79
|
+
BREAK = 243.chr # "\363" # "\xf3" # break
|
80
|
+
DM = 242.chr # "\362" # "\xf2" # data mark--for connect. cleaning
|
81
|
+
NOP = 241.chr # "\361" # "\xf1" # nop
|
82
|
+
SE = 240.chr # "\360" # "\xf0" # end sub negotiation
|
83
|
+
EOR = 239.chr # "\357" # "\xef" # end of record (transparent mode)
|
84
|
+
ABORT = 238.chr # "\356" # "\xee" # Abort process
|
85
|
+
SUSP = 237.chr # "\355" # "\xed" # Suspend process
|
86
|
+
EOF = 236.chr # "\354" # "\xec" # End of file
|
87
|
+
SYNCH = 242.chr # "\362" # "\xf2" # for telfunc calls
|
88
|
+
|
89
|
+
OPT_BINARY = 0.chr # "\000" # "\x00" # Binary Transmission
|
90
|
+
OPT_ECHO = 1.chr # "\001" # "\x01" # Echo
|
91
|
+
OPT_RCP = 2.chr # "\002" # "\x02" # Reconnection
|
92
|
+
OPT_SGA = 3.chr # "\003" # "\x03" # Suppress Go Ahead
|
93
|
+
OPT_NAMS = 4.chr # "\004" # "\x04" # Approx Message Size Negotiation
|
94
|
+
OPT_STATUS = 5.chr # "\005" # "\x05" # Status
|
95
|
+
OPT_TM = 6.chr # "\006" # "\x06" # Timing Mark
|
96
|
+
OPT_RCTE = 7.chr # "\a" # "\x07" # Remote Controlled Trans and Echo
|
97
|
+
OPT_NAOL = 8.chr # "\010" # "\x08" # Output Line Width
|
98
|
+
OPT_NAOP = 9.chr # "\t" # "\x09" # Output Page Size
|
99
|
+
OPT_NAOCRD = 10.chr # "\n" # "\x0a" # Output Carriage-Return Disposition
|
100
|
+
OPT_NAOHTS = 11.chr # "\v" # "\x0b" # Output Horizontal Tab Stops
|
101
|
+
OPT_NAOHTD = 12.chr # "\f" # "\x0c" # Output Horizontal Tab Disposition
|
102
|
+
OPT_NAOFFD = 13.chr # "\r" # "\x0d" # Output Formfeed Disposition
|
103
|
+
OPT_NAOVTS = 14.chr # "\016" # "\x0e" # Output Vertical Tabstops
|
104
|
+
OPT_NAOVTD = 15.chr # "\017" # "\x0f" # Output Vertical Tab Disposition
|
105
|
+
OPT_NAOLFD = 16.chr # "\020" # "\x10" # Output Linefeed Disposition
|
106
|
+
OPT_XASCII = 17.chr # "\021" # "\x11" # Extended ASCII
|
107
|
+
OPT_LOGOUT = 18.chr # "\022" # "\x12" # Logout
|
108
|
+
OPT_BM = 19.chr # "\023" # "\x13" # Byte Macro
|
109
|
+
OPT_DET = 20.chr # "\024" # "\x14" # Data Entry Terminal
|
110
|
+
OPT_SUPDUP = 21.chr # "\025" # "\x15" # SUPDUP
|
111
|
+
OPT_SUPDUPOUTPUT = 22.chr # "\026" # "\x16" # SUPDUP Output
|
112
|
+
OPT_SNDLOC = 23.chr # "\027" # "\x17" # Send Location
|
113
|
+
OPT_TTYPE = 24.chr # "\030" # "\x18" # Terminal Type
|
114
|
+
OPT_EOR = 25.chr # "\031" # "\x19" # End of Record
|
115
|
+
OPT_TUID = 26.chr # "\032" # "\x1a" # TACACS User Identification
|
116
|
+
OPT_OUTMRK = 27.chr # "\e" # "\x1b" # Output Marking
|
117
|
+
OPT_TTYLOC = 28.chr # "\034" # "\x1c" # Terminal Location Number
|
118
|
+
OPT_3270REGIME = 29.chr # "\035" # "\x1d" # Telnet 3270 Regime
|
119
|
+
OPT_X3PAD = 30.chr # "\036" # "\x1e" # X.3 PAD
|
120
|
+
OPT_NAWS = 31.chr # "\037" # "\x1f" # Negotiate About Window Size
|
121
|
+
OPT_TSPEED = 32.chr # " " # "\x20" # Terminal Speed
|
122
|
+
OPT_LFLOW = 33.chr # "!" # "\x21" # Remote Flow Control
|
123
|
+
OPT_LINEMODE = 34.chr # "\"" # "\x22" # Linemode
|
124
|
+
OPT_XDISPLOC = 35.chr # "#" # "\x23" # X Display Location
|
125
|
+
OPT_OLD_ENVIRON = 36.chr # "$" # "\x24" # Environment Option
|
126
|
+
OPT_AUTHENTICATION = 37.chr # "%" # "\x25" # Authentication Option
|
127
|
+
OPT_ENCRYPT = 38.chr # "&" # "\x26" # Encryption Option
|
128
|
+
OPT_NEW_ENVIRON = 39.chr # "'" # "\x27" # New Environment Option
|
129
|
+
OPT_EXOPL = 255.chr # "\377" # "\xff" # Extended-Options-List
|
130
|
+
|
131
|
+
NULL = "\000"
|
132
|
+
CR = "\015"
|
133
|
+
LF = "\012"
|
134
|
+
EOL = CR + LF
|
135
|
+
# :startdoc:
|
136
|
+
|
137
|
+
# raised when establishing the TCP connection fails
|
138
|
+
class ConnectionFailed < SocketError; end
|
139
|
+
|
140
|
+
# raised when the login procedure fails
|
141
|
+
class LoginFailed < Timeout::Error; end
|
142
|
+
|
143
|
+
##
|
144
|
+
# Extens Timeout::Error by the attributes _hostname_ and _command_ so one
|
145
|
+
# knows where the exception comes from and why.
|
146
|
+
#
|
147
|
+
class TimeoutError < Timeout::Error
|
148
|
+
# hostname this timeout comes from
|
149
|
+
attr_accessor :hostname
|
150
|
+
|
151
|
+
# command that caused this timeout
|
152
|
+
attr_accessor :command
|
153
|
+
end
|
154
|
+
|
155
|
+
# default options for new connections (used for merging)
|
156
|
+
DefaultOptions = {
|
157
|
+
host: "localhost",
|
158
|
+
port: 23,
|
159
|
+
prompt: %r{[$%#>] \z}n,
|
160
|
+
connect_timeout: 3,
|
161
|
+
timeout: 10,
|
162
|
+
wait_time: 0,
|
163
|
+
bin_mode: false,
|
164
|
+
telnet_mode: true,
|
165
|
+
output_log: nil,
|
166
|
+
command_log: nil,
|
167
|
+
login_prompt: %r{[Ll]ogin[: ]*\z}n,
|
168
|
+
password_prompt: %r{[Pp]ass(?:word|phrase)[: ]*\z}n,
|
169
|
+
username: nil,
|
170
|
+
password: nil,
|
171
|
+
|
172
|
+
# telnet protocol stuff
|
173
|
+
SGA: false,
|
174
|
+
BINARY: false,
|
175
|
+
}.freeze
|
176
|
+
|
177
|
+
# used to terminate the reactor when everything is done
|
178
|
+
stop_ticks = 0
|
179
|
+
StopWhenEMDone = lambda do
|
180
|
+
stop_ticks += 1
|
181
|
+
if stop_ticks >= 100
|
182
|
+
stop_ticks = 0
|
183
|
+
# stop when everything is done
|
184
|
+
if self.connection_count.zero? and EventMachine.defers_finished?
|
185
|
+
EventMachine.stop
|
186
|
+
else
|
187
|
+
EventMachine.next_tick(&StopWhenEMDone)
|
188
|
+
end
|
189
|
+
else
|
190
|
+
EventMachine.next_tick(&StopWhenEMDone)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
# number of active connections
|
195
|
+
@@_telnet_connection_count = 0
|
196
|
+
|
197
|
+
class << self
|
198
|
+
|
199
|
+
##
|
200
|
+
# Recognizes whether this call was issued by the user program or by
|
201
|
+
# EventMachine. If the call was not issued by EventMachine, merges the
|
202
|
+
# options provided with the DefaultOptions and creates a Fiber (not
|
203
|
+
# started yet). Inside the Fiber SimpleTelnet.connect would be called.
|
204
|
+
#
|
205
|
+
# If EventMachine's reactor is already running, just starts the Fiber.
|
206
|
+
#
|
207
|
+
# If it's not running yet, starts a new EventMachine reactor and starts the
|
208
|
+
# Fiber. The EventMachine block is stopped using the StopWhenEMDone proc
|
209
|
+
# (lambda).
|
210
|
+
#
|
211
|
+
# The (closed) connection is returned.
|
212
|
+
#
|
213
|
+
def new *args, &blk
|
214
|
+
# call super if first argument is a connection signature of
|
215
|
+
# EventMachine
|
216
|
+
return super(*args, &blk) if args.first.is_a? Integer
|
217
|
+
|
218
|
+
# This method was probably called with a Hash of connection options.
|
219
|
+
|
220
|
+
# create new fiber to connect and execute block
|
221
|
+
opts = args[0] || {}
|
222
|
+
connection = nil
|
223
|
+
fiber = Fiber.new do | callback |
|
224
|
+
connection = connect(opts, &blk)
|
225
|
+
callback.call if callback
|
226
|
+
end
|
227
|
+
|
228
|
+
if EventMachine.reactor_running?
|
229
|
+
# Transfer control to the "inner" Fiber and stop the current one.
|
230
|
+
# The block will be called after connect() returned to transfer control
|
231
|
+
# back to the "outer" Fiber.
|
232
|
+
outer_fiber = Fiber.current
|
233
|
+
fiber.transfer ->{ outer_fiber.transfer }
|
234
|
+
|
235
|
+
else
|
236
|
+
# start EventMachine and stop it when connection is done
|
237
|
+
EventMachine.run do
|
238
|
+
fiber.resume
|
239
|
+
EventMachine.next_tick(&StopWhenEMDone)
|
240
|
+
end
|
241
|
+
end
|
242
|
+
return connection
|
243
|
+
end
|
244
|
+
|
245
|
+
##
|
246
|
+
# Merges DefaultOptions with _opts_. Establishes the connection to the
|
247
|
+
# <tt>:host</tt> key using EventMachine.connect, logs in using #login and
|
248
|
+
# passes the connection to the block provided. Closes the connection using
|
249
|
+
# #close after the block terminates. The connection is then returned.
|
250
|
+
#
|
251
|
+
def connect opts
|
252
|
+
opts = DefaultOptions.merge opts
|
253
|
+
|
254
|
+
params = [
|
255
|
+
# for EventMachine.connect
|
256
|
+
opts[:host],
|
257
|
+
opts[:port],
|
258
|
+
self,
|
259
|
+
|
260
|
+
# pass the *merged* options to SimpleTelnet#initialize
|
261
|
+
opts
|
262
|
+
]
|
263
|
+
|
264
|
+
# start establishing the connection
|
265
|
+
connection = EventMachine.connect(*params)
|
266
|
+
|
267
|
+
# set callback to be executed when connection establishing
|
268
|
+
# fails/succeeds
|
269
|
+
f = Fiber.current
|
270
|
+
connection.connection_state_callback = lambda do |obj=nil|
|
271
|
+
@connection_state_callback = nil
|
272
|
+
f.resume obj
|
273
|
+
end
|
274
|
+
|
275
|
+
# block here and get result from establishing connection
|
276
|
+
state = Fiber.yield
|
277
|
+
|
278
|
+
# raise if exception (e.g. Telnet::ConnectionFailed)
|
279
|
+
raise state if state.is_a? Exception
|
280
|
+
|
281
|
+
# login
|
282
|
+
connection.instance_eval { login }
|
283
|
+
|
284
|
+
begin
|
285
|
+
yield connection
|
286
|
+
ensure
|
287
|
+
# Use #close so a subclass can execute some kind of logout command
|
288
|
+
# before the connection is closed.
|
289
|
+
connection.close
|
290
|
+
end
|
291
|
+
|
292
|
+
return connection
|
293
|
+
end
|
294
|
+
|
295
|
+
##
|
296
|
+
# Returns the number of active connections
|
297
|
+
# (<tt>@@_telnet_connection_count</tt>).
|
298
|
+
#
|
299
|
+
def connection_count
|
300
|
+
@@_telnet_connection_count
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
##
|
305
|
+
# Initializes the current instance. _opts_ is a Hash of options. The default
|
306
|
+
# values are in the constant DefaultOptions. The following keys are
|
307
|
+
# recognized:
|
308
|
+
#
|
309
|
+
# +:host+::
|
310
|
+
# the hostname or IP address of the host to connect to, as a String.
|
311
|
+
# Defaults to "localhost".
|
312
|
+
#
|
313
|
+
# +:port+::
|
314
|
+
# the port to connect to. Defaults to 23.
|
315
|
+
#
|
316
|
+
# +:bin_mode+::
|
317
|
+
# if +false+ (the default), newline substitution is performed. Outgoing LF
|
318
|
+
# is converted to CRLF, and incoming CRLF is converted to LF. If +true+,
|
319
|
+
# this substitution is not performed. This value can also be set with the
|
320
|
+
# #bin_mode= method. The outgoing conversion only applies to the #puts
|
321
|
+
# and #print methods, not the #write method. The precise nature of the
|
322
|
+
# newline conversion is also affected by the telnet options SGA and BIN.
|
323
|
+
#
|
324
|
+
# +:output_log+::
|
325
|
+
# the name of the file to write connection status messages and all
|
326
|
+
# received traffic to. In the case of a proper Telnet session, this will
|
327
|
+
# include the client input as echoed by the host; otherwise, it only
|
328
|
+
# includes server responses. Output is appended verbatim to this file.
|
329
|
+
# By default, no output log is kept.
|
330
|
+
#
|
331
|
+
# +:command_log+::
|
332
|
+
# the name of the file to write the commands executed in this Telnet
|
333
|
+
# session. Commands are appended to this file. By default, no command
|
334
|
+
# log is kept.
|
335
|
+
#
|
336
|
+
# +:prompt+::
|
337
|
+
# a regular expression matching the host's command-line prompt sequence.
|
338
|
+
# This is needed by the Telnet class to determine when the output from a
|
339
|
+
# command has finished and the host is ready to receive a new command. By
|
340
|
+
# default, this regular expression is <tt>%r{[$%#>] \z}n</tt>.
|
341
|
+
#
|
342
|
+
# +:login_prompt+::
|
343
|
+
# a regular expression (or String, see #waitfor) used to wait for the
|
344
|
+
# login prompt.
|
345
|
+
#
|
346
|
+
# +:password_prompt+::
|
347
|
+
# a regular expression (or String, see #waitfor) used to wait for the
|
348
|
+
# password prompt.
|
349
|
+
#
|
350
|
+
# +:username+::
|
351
|
+
# the String that is sent to the telnet server after seeing the login
|
352
|
+
# prompt. Just leave this value as +nil+ which is the default value if you
|
353
|
+
# don't have to log in.
|
354
|
+
#
|
355
|
+
# +:password+::
|
356
|
+
# the String that is sent to the telnet server after seeing the password
|
357
|
+
# prompt. Just leave this value as +nil+ which is the default value if you
|
358
|
+
# don't have to print a password after printing the username.
|
359
|
+
#
|
360
|
+
# +:telnet_mode+::
|
361
|
+
# a boolean value, +true+ by default. In telnet mode, traffic received
|
362
|
+
# from the host is parsed for special command sequences, and these
|
363
|
+
# sequences are escaped in outgoing traffic sent using #puts or #print
|
364
|
+
# (but not #write). If you are connecting to a non-telnet service (such
|
365
|
+
# as SMTP or POP), this should be set to "false" to prevent undesired data
|
366
|
+
# corruption. This value can also be set by the #telnetmode method.
|
367
|
+
#
|
368
|
+
# +:timeout+::
|
369
|
+
# the number of seconds (default: +10+) to wait before timing out while
|
370
|
+
# waiting for the prompt (in #waitfor). Exceeding this timeout causes a
|
371
|
+
# TimeoutError to be raised. You can disable the timeout by setting
|
372
|
+
# this value to +nil+.
|
373
|
+
#
|
374
|
+
# +:connect_timeout+::
|
375
|
+
# the number of seconds (default: +3+) to wait before timing out the
|
376
|
+
# initial attempt to connect. You can disable the timeout by setting this
|
377
|
+
# value to +nil+.
|
378
|
+
#
|
379
|
+
# +:wait_time+::
|
380
|
+
# the amount of time to wait after seeing what looks like a prompt (that
|
381
|
+
# is, received data that matches the Prompt option regular expression) to
|
382
|
+
# see if more data arrives. If more data does arrive in this time, it
|
383
|
+
# assumes that what it saw was not really a prompt. This is to try to
|
384
|
+
# avoid false matches, but it can also lead to missing real prompts (if,
|
385
|
+
# for instance, a background process writes to the terminal soon after the
|
386
|
+
# prompt is displayed). By default, set to 0, meaning not to wait for
|
387
|
+
# more data.
|
388
|
+
#
|
389
|
+
# The options are actually merged in connect().
|
390
|
+
#
|
391
|
+
def initialize opts
|
392
|
+
@telnet_options = opts
|
393
|
+
@last_command = nil
|
394
|
+
|
395
|
+
@logged_in = nil
|
396
|
+
@connection_state = :connecting
|
397
|
+
@connection_state_callback = nil
|
398
|
+
@input_buffer = ""
|
399
|
+
@input_rest = ""
|
400
|
+
@wait_time_timer = nil
|
401
|
+
@check_input_buffer_timer = nil
|
402
|
+
|
403
|
+
setup_logging
|
404
|
+
end
|
405
|
+
|
406
|
+
# Last command that was executed in this telnet session
|
407
|
+
attr_reader :last_command
|
408
|
+
|
409
|
+
# Logger used to log output
|
410
|
+
attr_reader :output_logger
|
411
|
+
|
412
|
+
# Logger used to log commands
|
413
|
+
attr_reader :command_logger
|
414
|
+
|
415
|
+
# used telnet options Hash
|
416
|
+
attr_reader :telnet_options
|
417
|
+
|
418
|
+
# the callback executed after connection established or failed
|
419
|
+
attr_accessor :connection_state_callback
|
420
|
+
|
421
|
+
# last prompt matched
|
422
|
+
attr_reader :last_prompt
|
423
|
+
|
424
|
+
##
|
425
|
+
# Return current telnet mode option of this connection.
|
426
|
+
#
|
427
|
+
def telnet_mode?
|
428
|
+
@telnet_options[:telnet_mode]
|
429
|
+
end
|
430
|
+
|
431
|
+
##
|
432
|
+
# Turn telnet command interpretation on or off for this connection. It
|
433
|
+
# should be on for true telnet sessions, off if used to connect to a
|
434
|
+
# non-telnet service such as SMTP.
|
435
|
+
#
|
436
|
+
def telnet_mode=(bool)
|
437
|
+
@telnet_options[:telnet_mode] = bool
|
438
|
+
end
|
439
|
+
|
440
|
+
##
|
441
|
+
# Return current bin mode option of this connection.
|
442
|
+
#
|
443
|
+
def bin_mode?
|
444
|
+
@telnet_options[:bin_mode]
|
445
|
+
end
|
446
|
+
|
447
|
+
##
|
448
|
+
# Turn newline conversion on or off for this connection.
|
449
|
+
#
|
450
|
+
def bin_mode=(bool)
|
451
|
+
@telnet_options[:bin_mode] = bool
|
452
|
+
end
|
453
|
+
|
454
|
+
##
|
455
|
+
# 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
|
460
|
+
set_comm_inactivity_timeout( seconds )
|
461
|
+
end
|
462
|
+
|
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.
|
468
|
+
#
|
469
|
+
# If no block is given, the current timeout is returned.
|
470
|
+
#
|
471
|
+
# Example:
|
472
|
+
#
|
473
|
+
# current_timeout = host.timeout
|
474
|
+
#
|
475
|
+
# host.timeout 200 do
|
476
|
+
# host.cmd "command 1"
|
477
|
+
# host.cmd "command 2"
|
478
|
+
# end
|
479
|
+
#
|
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
|
+
warn "Warning: Use EM::P::SimpleTelnet#timeout= to set the timeout."
|
492
|
+
end
|
493
|
+
@telnet_options[:timeout]
|
494
|
+
end
|
495
|
+
end
|
496
|
+
|
497
|
+
##
|
498
|
+
# When the login succeeded for this connection.
|
499
|
+
#
|
500
|
+
attr_reader :logged_in
|
501
|
+
|
502
|
+
##
|
503
|
+
# Returns +true+ if the login already succeeded for this connection.
|
504
|
+
# Returns +false+ otherwise.
|
505
|
+
#
|
506
|
+
def logged_in?
|
507
|
+
@logged_in ? true : false
|
508
|
+
end
|
509
|
+
|
510
|
+
##
|
511
|
+
# Returns +true+ if the connection is closed.
|
512
|
+
#
|
513
|
+
def closed?
|
514
|
+
@connection_state == :closed
|
515
|
+
end
|
516
|
+
|
517
|
+
##
|
518
|
+
# Called by EventMachine when data is received.
|
519
|
+
#
|
520
|
+
# The data is processed using #preprocess_telnet and appended to the
|
521
|
+
# <tt>@input_buffer</tt>. The appended data is also logged using
|
522
|
+
# #log_output. Then #check_input_buffer is called which checks the input
|
523
|
+
# buffer for the prompt.
|
524
|
+
#
|
525
|
+
def receive_data data
|
526
|
+
if @telnet_options[:telnet_mode]
|
527
|
+
c = @input_rest + data
|
528
|
+
se_pos = c.rindex(/#{IAC}#{SE}/no) || 0
|
529
|
+
sb_pos = c.rindex(/#{IAC}#{SB}/no) || 0
|
530
|
+
if se_pos < sb_pos
|
531
|
+
buf = preprocess_telnet(c[0 ... sb_pos])
|
532
|
+
@input_rest = c[sb_pos .. -1]
|
533
|
+
|
534
|
+
elsif pt_pos = c.rindex(
|
535
|
+
/#{IAC}[^#{IAC}#{AO}#{AYT}#{DM}#{IP}#{NOP}]?\z/no) ||
|
536
|
+
c.rindex(/\r\z/no)
|
537
|
+
|
538
|
+
buf = preprocess_telnet(c[0 ... pt_pos])
|
539
|
+
@input_rest = c[pt_pos .. -1]
|
540
|
+
|
541
|
+
else
|
542
|
+
buf = preprocess_telnet(c)
|
543
|
+
@input_rest.clear
|
544
|
+
end
|
545
|
+
else
|
546
|
+
# Not Telnetmode.
|
547
|
+
#
|
548
|
+
# We cannot use #preprocess_telnet on this data, because that
|
549
|
+
# method makes some Telnetmode-specific assumptions.
|
550
|
+
buf = @input_rest + data
|
551
|
+
@input_rest.clear
|
552
|
+
unless @telnet_options[:bin_mode]
|
553
|
+
if pt_pos = buf.rindex(/\r\z/no)
|
554
|
+
buf = buf[0 ... pt_pos]
|
555
|
+
@input_rest = buf[pt_pos .. -1]
|
556
|
+
end
|
557
|
+
buf.gsub!(/#{EOL}/no, "\n")
|
558
|
+
end
|
559
|
+
end
|
560
|
+
|
561
|
+
# in case only telnet sequences were received
|
562
|
+
return if buf.empty?
|
563
|
+
|
564
|
+
# append output from server to input buffer and log it
|
565
|
+
@input_buffer << buf
|
566
|
+
log_output buf, true
|
567
|
+
|
568
|
+
# cancel the timer for wait_time value because we received more data
|
569
|
+
if @wait_time_timer
|
570
|
+
@wait_time_timer.cancel
|
571
|
+
@wait_time_timer = nil
|
572
|
+
end
|
573
|
+
|
574
|
+
# we only need to do something if there's a connection state callback
|
575
|
+
return unless @connection_state_callback
|
576
|
+
|
577
|
+
# we ensure there's no timer running to check the input buffer
|
578
|
+
if @check_input_buffer_timer
|
579
|
+
@check_input_buffer_timer.cancel
|
580
|
+
@check_input_buffer_timer = nil
|
581
|
+
end
|
582
|
+
|
583
|
+
if @input_buffer.size >= 100_000
|
584
|
+
##
|
585
|
+
# if the input buffer is really big
|
586
|
+
#
|
587
|
+
|
588
|
+
# We postpone checking the input buffer by one second because the regular
|
589
|
+
# expression matches can get quite slow.
|
590
|
+
#
|
591
|
+
# So as long as data is received (continuously), the input buffer is not
|
592
|
+
# checked. It's only checked one second after the whole output has been
|
593
|
+
# received.
|
594
|
+
@check_input_buffer_timer = EventMachine::Timer.new(1) do
|
595
|
+
@check_input_buffer_timer = nil
|
596
|
+
check_input_buffer
|
597
|
+
end
|
598
|
+
else
|
599
|
+
##
|
600
|
+
# as long as the input buffer is small
|
601
|
+
#
|
602
|
+
|
603
|
+
# check the input buffer now
|
604
|
+
check_input_buffer
|
605
|
+
end
|
606
|
+
end
|
607
|
+
|
608
|
+
##
|
609
|
+
# Checks the input buffer (<tt>@input_buffer</tt>) for the prompt we're
|
610
|
+
# waiting for. Calls the proc in <tt>@connection_state_callback</tt> if the
|
611
|
+
# prompt has been found. Thus, call this method *only* if
|
612
|
+
# <tt>@connection_state_callback</tt> is set!
|
613
|
+
#
|
614
|
+
# If <tt>@telnet_options[:wait_time]</tt> is set, this amount of seconds is
|
615
|
+
# waited (call to <tt>@connection_state_callback</tt> is scheduled) after
|
616
|
+
# seeing what looks like the prompt before firing the
|
617
|
+
# <tt>@connection_state_callback</tt> is fired, so more data can come until
|
618
|
+
# the real prompt is reached. This is useful for commands which will cause
|
619
|
+
# multiple prompts to be sent.
|
620
|
+
#
|
621
|
+
def check_input_buffer
|
622
|
+
if md = @input_buffer.match(@telnet_options[:prompt])
|
623
|
+
blk = lambda do
|
624
|
+
@last_prompt = md.to_s # remember last prompt
|
625
|
+
output = md.pre_match + @last_prompt
|
626
|
+
@input_buffer = md.post_match
|
627
|
+
@connection_state_callback.call(output)
|
628
|
+
end
|
629
|
+
|
630
|
+
if s = @telnet_options[:wait_time]
|
631
|
+
# fire @connection_state_callback after s seconds
|
632
|
+
@wait_time_timer = EventMachine::Timer.new(s, &blk)
|
633
|
+
else
|
634
|
+
# fire @connection_state_callback now
|
635
|
+
blk.call
|
636
|
+
end
|
637
|
+
end
|
638
|
+
end
|
639
|
+
|
640
|
+
##
|
641
|
+
# Read data from the host until a certain sequence is matched.
|
642
|
+
#
|
643
|
+
# All data read will be returned in a single string. Note that the received
|
644
|
+
# data includes the matched sequence we were looking for.
|
645
|
+
#
|
646
|
+
# _prompt_ can be a Regexp or String. If it's not a Regexp, it's converted
|
647
|
+
# to a Regexp (all special characters escaped) assuming it's a String.
|
648
|
+
#
|
649
|
+
# _opts_ can be a hash of options. The following options are used and thus
|
650
|
+
# can be overridden:
|
651
|
+
#
|
652
|
+
# * +:timeout+
|
653
|
+
# * +:wait_time+ (actually used by #check_input_buffer)
|
654
|
+
#
|
655
|
+
def waitfor prompt=nil, opts={}
|
656
|
+
options_were = @telnet_options
|
657
|
+
timeout_was = self.timeout if opts.key?(:timeout)
|
658
|
+
opts[:prompt] = prompt if prompt
|
659
|
+
@telnet_options = @telnet_options.merge opts
|
660
|
+
|
661
|
+
# convert String prompt into a Regexp
|
662
|
+
unless @telnet_options[:prompt].is_a? Regexp
|
663
|
+
regex = Regexp.new(Regexp.quote(@telnet_options[:prompt]))
|
664
|
+
@telnet_options[:prompt] = regex
|
665
|
+
end
|
666
|
+
|
667
|
+
# set custom inactivity timeout, if wanted
|
668
|
+
self.timeout = @telnet_options[:timeout] if opts.key?(:timeout)
|
669
|
+
|
670
|
+
# so #unbind knows we were waiting for a prompt (in case that inactivity
|
671
|
+
# timeout fires)
|
672
|
+
@connection_state = :waiting_for_prompt
|
673
|
+
|
674
|
+
# for the block in @connection_state_callback
|
675
|
+
f = Fiber.current
|
676
|
+
|
677
|
+
# will be called by #receive_data to resume at "Fiber.yield" below
|
678
|
+
@connection_state_callback = lambda do |output|
|
679
|
+
@connection_state_callback = nil
|
680
|
+
f.resume(output)
|
681
|
+
end
|
682
|
+
|
683
|
+
result = Fiber.yield
|
684
|
+
|
685
|
+
raise result if result.is_a? Exception
|
686
|
+
return result
|
687
|
+
ensure
|
688
|
+
@telnet_options = options_were
|
689
|
+
self.timeout = timeout_was if opts.key?(:timeout)
|
690
|
+
@connection_state = :connected
|
691
|
+
end
|
692
|
+
|
693
|
+
alias :write :send_data
|
694
|
+
|
695
|
+
##
|
696
|
+
# Sends a string to the host.
|
697
|
+
#
|
698
|
+
# This does _not_ automatically append a newline to the string. Embedded
|
699
|
+
# newlines may be converted and telnet command sequences escaped depending
|
700
|
+
# upon the values of #telnet_mode, #bin_mode, and telnet options set by the
|
701
|
+
# host.
|
702
|
+
#
|
703
|
+
def print(string)
|
704
|
+
string = string.gsub(/#{IAC}/no, IAC + IAC) if telnet_mode?
|
705
|
+
|
706
|
+
unless bin_mode?
|
707
|
+
string = if @telnet_options[:BINARY] and @telnet_options[:SGA]
|
708
|
+
# IAC WILL SGA IAC DO BIN send EOL --> CR
|
709
|
+
string.gsub(/\n/n, CR)
|
710
|
+
|
711
|
+
elsif @telnet_options[:SGA]
|
712
|
+
# IAC WILL SGA send EOL --> CR+NULL
|
713
|
+
string.gsub(/\n/n, CR + NULL)
|
714
|
+
|
715
|
+
else
|
716
|
+
# NONE send EOL --> CR+LF
|
717
|
+
string.gsub(/\n/n, EOL)
|
718
|
+
end
|
719
|
+
end
|
720
|
+
|
721
|
+
send_data string
|
722
|
+
end
|
723
|
+
|
724
|
+
##
|
725
|
+
# Sends a string to the host.
|
726
|
+
#
|
727
|
+
# Same as #print, but appends a newline to the string unless there's
|
728
|
+
# already one.
|
729
|
+
#
|
730
|
+
def puts(string)
|
731
|
+
string += "\n" unless string.end_with? "\n"
|
732
|
+
print string
|
733
|
+
end
|
734
|
+
|
735
|
+
##
|
736
|
+
# Sends a command to the host.
|
737
|
+
#
|
738
|
+
# More exactly, the following things are done:
|
739
|
+
#
|
740
|
+
# * stores the command in @last_command
|
741
|
+
# * logs it using #log_command
|
742
|
+
# * sends a string to the host (#print or #puts)
|
743
|
+
# * reads in all received data (using #waitfor)
|
744
|
+
# * returns the received data as String
|
745
|
+
#
|
746
|
+
# _opts_ can be a Hash of options. It is passed to #waitfor as the second
|
747
|
+
# parameter. The element in _opts_ with the key <tt>:prompt</tt> is used as
|
748
|
+
# the first parameter in the call to #waitfor. Example usage:
|
749
|
+
#
|
750
|
+
# host.cmd "delete user john", prompt: /Are you sure?/
|
751
|
+
# host.cmd "yes"
|
752
|
+
#
|
753
|
+
# Note that the received data includes the prompt and in most cases the
|
754
|
+
# host's echo of our command.
|
755
|
+
#
|
756
|
+
# If _opts_ has the key <tt>:hide</tt> which evaluates to +true+, calls
|
757
|
+
# #log_command with <tt>"<hidden command>"</tt> instead of the command
|
758
|
+
# itself. This is useful for passwords, so they don't get logged to the
|
759
|
+
# command log.
|
760
|
+
#
|
761
|
+
# If _opts_ has the key <tt>:raw_command</tt> which evaluates to +true+,
|
762
|
+
# #print is used to send the command to the host instead of #puts.
|
763
|
+
#
|
764
|
+
def cmd command, opts={}
|
765
|
+
command = command.to_s
|
766
|
+
@last_command = command
|
767
|
+
|
768
|
+
# log the command
|
769
|
+
log_command(opts[:hide] ? "<hidden command>" : command)
|
770
|
+
|
771
|
+
# send the command
|
772
|
+
sendcmd = opts[:raw_command] ? :print : :puts
|
773
|
+
self.__send__(sendcmd, command)
|
774
|
+
|
775
|
+
# wait for the output
|
776
|
+
waitfor(opts[:prompt], opts)
|
777
|
+
end
|
778
|
+
|
779
|
+
##
|
780
|
+
# Login to the host with a given username and password.
|
781
|
+
#
|
782
|
+
# host.login username: "myuser", password: "mypass"
|
783
|
+
#
|
784
|
+
# This method looks for the login and password prompt (see implementation)
|
785
|
+
# from the host to determine when to send the username and password. If the
|
786
|
+
# login sequence does not follow this pattern (for instance, you are
|
787
|
+
# connecting to a service other than telnet), you will need to handle login
|
788
|
+
# yourself.
|
789
|
+
#
|
790
|
+
# If the key <tt>:password</tt> is omitted (and not set on connection
|
791
|
+
# level), the method will not look for a prompt.
|
792
|
+
#
|
793
|
+
# The method returns all data received during the login process from the
|
794
|
+
# host, including the echoed username but not the password (which the host
|
795
|
+
# should not echo anyway).
|
796
|
+
#
|
797
|
+
# Don't forget to set <tt>@logged_in</tt> after the login succeeds when you
|
798
|
+
# redefine this method!
|
799
|
+
#
|
800
|
+
def login opts={}
|
801
|
+
opts = @telnet_options.merge opts
|
802
|
+
|
803
|
+
# don't log in if username is not set
|
804
|
+
if opts[:username].nil?
|
805
|
+
@logged_in = Time.now
|
806
|
+
return
|
807
|
+
end
|
808
|
+
|
809
|
+
begin
|
810
|
+
output = waitfor opts[:login_prompt]
|
811
|
+
|
812
|
+
if opts[:password]
|
813
|
+
# login with username and password
|
814
|
+
output << cmd(opts[:username], prompt: opts[:password_prompt])
|
815
|
+
output << cmd(opts[:password], hide: true)
|
816
|
+
else
|
817
|
+
# login with username only
|
818
|
+
output << cmd(opts[:username])
|
819
|
+
end
|
820
|
+
rescue Timeout::Error
|
821
|
+
e = LoginFailed.new("Timed out while expecting some kind of prompt.")
|
822
|
+
e.set_backtrace $!.backtrace
|
823
|
+
raise e
|
824
|
+
end
|
825
|
+
|
826
|
+
@logged_in = Time.now
|
827
|
+
output
|
828
|
+
end
|
829
|
+
|
830
|
+
##
|
831
|
+
# Called by EventMachine when the connection is being established (not after
|
832
|
+
# the connection is established! see #connection_completed). This occurs
|
833
|
+
# directly after the call to #initialize.
|
834
|
+
#
|
835
|
+
# Sets the +pending_connect_timeout+ to
|
836
|
+
# <tt>@telnet_options[:connect_timeout]</tt> seconds. This is the duration
|
837
|
+
# after which a TCP connection in the connecting state will fail (abort and
|
838
|
+
# run #unbind). Increases <tt>@@_telnet_connection_count</tt> by one after
|
839
|
+
# that.
|
840
|
+
#
|
841
|
+
# Sets also the +comm_inactivity_timeout+ to
|
842
|
+
# <tt>@telnet_options[:timeout]</tt> seconds. This is the duration after
|
843
|
+
# which a TCP connection is automatically closed if no data was sent or
|
844
|
+
# received.
|
845
|
+
#
|
846
|
+
def post_init
|
847
|
+
self.pending_connect_timeout = @telnet_options[:connect_timeout]
|
848
|
+
self.comm_inactivity_timeout = @telnet_options[:timeout]
|
849
|
+
@@_telnet_connection_count += 1
|
850
|
+
end
|
851
|
+
|
852
|
+
##
|
853
|
+
# Called by EventMachine after this connection is closed.
|
854
|
+
#
|
855
|
+
# Decreases <tt>@@_telnet_connection_count</tt> by one and calls #close_logs.
|
856
|
+
#
|
857
|
+
# After that and if <tt>@connection_state_callback</tt> is set, it takes a
|
858
|
+
# look on <tt>@connection_state</tt>. If it was <tt>:connecting</tt>, calls
|
859
|
+
# <tt>@connection_state_callback</tt> with a new instance of
|
860
|
+
# ConnectionFailed. If it was <tt>:waiting_for_prompt</tt>, calls the
|
861
|
+
# callback with a new instance of TimeoutError.
|
862
|
+
#
|
863
|
+
# Finally, the <tt>@connection_state</tt> is set to +closed+.
|
864
|
+
#
|
865
|
+
def unbind
|
866
|
+
@@_telnet_connection_count -= 1
|
867
|
+
close_logs
|
868
|
+
|
869
|
+
if @connection_state_callback
|
870
|
+
# if we were connecting or waiting for a prompt, return an exception to
|
871
|
+
# #waitfor
|
872
|
+
case @connection_state
|
873
|
+
when :connecting
|
874
|
+
@connection_state_callback.call(ConnectionFailed.new)
|
875
|
+
when :waiting_for_prompt
|
876
|
+
error = TimeoutError.new
|
877
|
+
|
878
|
+
# set hostname and command
|
879
|
+
if hostname = @telnet_options[:host]
|
880
|
+
error.hostname = hostname
|
881
|
+
end
|
882
|
+
error.command = @last_command if @last_command
|
883
|
+
|
884
|
+
@connection_state_callback.call(error)
|
885
|
+
end
|
886
|
+
end
|
887
|
+
|
888
|
+
@connection_state = :closed
|
889
|
+
end
|
890
|
+
|
891
|
+
##
|
892
|
+
# Called by EventMachine after the connection is successfully established.
|
893
|
+
#
|
894
|
+
def connection_completed
|
895
|
+
@connection_state = :connected
|
896
|
+
@connection_state_callback.call if @connection_state_callback
|
897
|
+
end
|
898
|
+
|
899
|
+
##
|
900
|
+
# Tells EventMachine to close the connection after sending what's in the
|
901
|
+
# output buffer. Redefine this method to execute some logout command like
|
902
|
+
# +exit+ or +logout+ before the connection is closed. Don't forget: The
|
903
|
+
# command will probably not return a prompt, so use #puts, which doesn't
|
904
|
+
# wait for a prompt.
|
905
|
+
#
|
906
|
+
def close
|
907
|
+
close_connection_after_writing
|
908
|
+
end
|
909
|
+
|
910
|
+
##
|
911
|
+
# Close output and command logs if they're set. IOError is rescued because
|
912
|
+
# they could already be closed. #closed? can't be used, because the method
|
913
|
+
# is not implemented by Logger, for example.
|
914
|
+
#
|
915
|
+
def close_logs
|
916
|
+
begin @output_logger.close
|
917
|
+
rescue IOError
|
918
|
+
end if @telnet_options[:output_log]
|
919
|
+
begin @command_logger.close
|
920
|
+
rescue IOError
|
921
|
+
end if @telnet_options[:command_log]
|
922
|
+
end
|
923
|
+
|
924
|
+
private
|
925
|
+
|
926
|
+
##
|
927
|
+
# Sets up output and command logging.
|
928
|
+
#
|
929
|
+
def setup_logging
|
930
|
+
require 'logger'
|
931
|
+
if @telnet_options[:output_log]
|
932
|
+
@output_logger = Logger.new @telnet_options[:output_log]
|
933
|
+
log_output "\n# Starting telnet output log at #{Time.now}"
|
934
|
+
end
|
935
|
+
|
936
|
+
if @telnet_options[:command_log]
|
937
|
+
@command_logger = Logger.new @telnet_options[:command_log]
|
938
|
+
end
|
939
|
+
end
|
940
|
+
|
941
|
+
##
|
942
|
+
# Logs _output_ to output log. If _exact_ is +true+, it will use #print
|
943
|
+
# instead of #puts.
|
944
|
+
#
|
945
|
+
def log_output output, exact=false
|
946
|
+
return unless @telnet_options[:output_log]
|
947
|
+
if exact
|
948
|
+
@output_logger.print output
|
949
|
+
else
|
950
|
+
@output_logger.puts output
|
951
|
+
end
|
952
|
+
end
|
953
|
+
|
954
|
+
##
|
955
|
+
# Logs _command_ to command log.
|
956
|
+
#
|
957
|
+
def log_command command
|
958
|
+
return unless @telnet_options[:command_log]
|
959
|
+
@command_logger.info command
|
960
|
+
end
|
961
|
+
|
962
|
+
##
|
963
|
+
# Preprocess received data from the host.
|
964
|
+
#
|
965
|
+
# Performs newline conversion and detects telnet command sequences.
|
966
|
+
# Called automatically by #receive_data.
|
967
|
+
#
|
968
|
+
def preprocess_telnet string
|
969
|
+
# combine CR+NULL into CR
|
970
|
+
string = string.gsub(/#{CR}#{NULL}/no, CR) if telnet_mode?
|
971
|
+
|
972
|
+
# combine EOL into "\n"
|
973
|
+
string = string.gsub(/#{EOL}/no, "\n") unless bin_mode?
|
974
|
+
|
975
|
+
# remove NULL
|
976
|
+
string = string.gsub(/#{NULL}/no, '') unless bin_mode?
|
977
|
+
|
978
|
+
string.gsub(/#{IAC}(
|
979
|
+
[#{IAC}#{AO}#{AYT}#{DM}#{IP}#{NOP}]|
|
980
|
+
[#{DO}#{DONT}#{WILL}#{WONT}]
|
981
|
+
[#{OPT_BINARY}-#{OPT_NEW_ENVIRON}#{OPT_EXOPL}]|
|
982
|
+
#{SB}[^#{IAC}]*#{IAC}#{SE}
|
983
|
+
)/xno) do
|
984
|
+
if IAC == $1 # handle escaped IAC characters
|
985
|
+
IAC
|
986
|
+
elsif AYT == $1 # respond to "IAC AYT" (are you there)
|
987
|
+
send_data("nobody here but us pigeons" + EOL)
|
988
|
+
''
|
989
|
+
elsif DO[0] == $1[0] # respond to "IAC DO x"
|
990
|
+
if OPT_BINARY[0] == $1[1]
|
991
|
+
@telnet_options[:BINARY] = true
|
992
|
+
send_data(IAC + WILL + OPT_BINARY)
|
993
|
+
else
|
994
|
+
send_data(IAC + WONT + $1[1..1])
|
995
|
+
end
|
996
|
+
''
|
997
|
+
elsif DONT[0] == $1[0] # respond to "IAC DON'T x" with "IAC WON'T x"
|
998
|
+
send_data(IAC + WONT + $1[1..1])
|
999
|
+
''
|
1000
|
+
elsif WILL[0] == $1[0] # respond to "IAC WILL x"
|
1001
|
+
if OPT_BINARY[0] == $1[1]
|
1002
|
+
send_data(IAC + DO + OPT_BINARY)
|
1003
|
+
elsif OPT_ECHO[0] == $1[1]
|
1004
|
+
send_data(IAC + DO + OPT_ECHO)
|
1005
|
+
elsif OPT_SGA[0] == $1[1]
|
1006
|
+
@telnet_options[:SGA] = true
|
1007
|
+
send_data(IAC + DO + OPT_SGA)
|
1008
|
+
else
|
1009
|
+
send_data(IAC + DONT + $1[1..1])
|
1010
|
+
end
|
1011
|
+
''
|
1012
|
+
elsif WONT[0] == $1[0] # respond to "IAC WON'T x"
|
1013
|
+
if OPT_ECHO[0] == $1[1]
|
1014
|
+
send_data(IAC + DONT + OPT_ECHO)
|
1015
|
+
elsif OPT_SGA[0] == $1[1]
|
1016
|
+
@telnet_options[:SGA] = false
|
1017
|
+
send_data(IAC + DONT + OPT_SGA)
|
1018
|
+
else
|
1019
|
+
send_data(IAC + DONT + $1[1..1])
|
1020
|
+
end
|
1021
|
+
''
|
1022
|
+
else
|
1023
|
+
''
|
1024
|
+
end
|
1025
|
+
end
|
1026
|
+
end
|
1027
|
+
end
|
metadata
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: em-simple_telnet
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Patrik Wenger
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-11-21 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: This library provides a very simple way to connect to telnet servers
|
15
|
+
using EventMachine in a seemingly synchronous manner.
|
16
|
+
email:
|
17
|
+
- paddor@gmail.com
|
18
|
+
executables: []
|
19
|
+
extensions: []
|
20
|
+
extra_rdoc_files: []
|
21
|
+
files:
|
22
|
+
- lib/em-simple_telnet.rb
|
23
|
+
homepage: http://github.com/paddor/em-simple_telnet
|
24
|
+
licenses: []
|
25
|
+
post_install_message:
|
26
|
+
rdoc_options: []
|
27
|
+
require_paths:
|
28
|
+
- lib
|
29
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
30
|
+
none: false
|
31
|
+
requirements:
|
32
|
+
- - ! '>='
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '0'
|
35
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
36
|
+
none: false
|
37
|
+
requirements:
|
38
|
+
- - ! '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
requirements: []
|
42
|
+
rubyforge_project:
|
43
|
+
rubygems_version: 1.8.11
|
44
|
+
signing_key:
|
45
|
+
specification_version: 3
|
46
|
+
summary: Simple telnet client on EventMachine
|
47
|
+
test_files: []
|
48
|
+
has_rdoc:
|