em-simple_telnet 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|