Class: SimpleTelnet::Connection

Inherits:
EventMachine::Connection
  • Object
show all
Defined in:
lib/em-simple_telnet.rb

Overview

Provides the facility to connect to telnet servers using EventMachine. The asynchronity is hidden so you can use this library just like Net::Telnet in a seemingly synchronous manner. See README for an example.

Examples:

Standalone

opts = {
  host: "localhost",
  username: "user",
  password: "secret",
}

EM::P::SimpleTelnet.new(opts) do |host|
  # At this point, we're already logged in.

  host.cmd("touch /my/file")

  # get some output
  puts host.cmd("ls -la")

  host.timeout(30) do
    # custom timeout for this block
    host.cmd "slow command"
  end
end

Inside an existing EventMachine loop

EventMachine.run do

  opts = {
    host: "localhost",
    username: "user",
    password: "secret",
    output_log: "output.log", # log output to file
    command_log: "command.log", # log commands to file
  }

  EM::P::SimpleTelnet.new(opts) do |host|
    # already logged in
    puts host.cmd("ls -la")
  end
end

Defined Under Namespace

Classes: ConnectionFailed, LoginFailed, TimeoutError

Constant Summary collapse

IAC =

:stopdoc:

255.chr
DONT =

“377” # “xff” # interpret as command

254.chr
DO =

“376” # “xfe” # you are not to use option

253.chr
WONT =

“375” # “xfd” # please, you use option

252.chr
WILL =

“374” # “xfc” # I won’t use option

251.chr
SB =

“373” # “xfb” # I will use option

250.chr
GA =

“372” # “xfa” # interpret as subnegotiation

249.chr
EL =

“371” # “xf9” # you may reverse the line

248.chr
EC =

“370” # “xf8” # erase the current line

247.chr
AYT =

“367” # “xf7” # erase the current character

246.chr
AO =

“366” # “xf6” # are you there

245.chr
IP =

“365” # “xf5” # abort output–but let prog finish

244.chr
BREAK =

“364” # “xf4” # interrupt process–permanently

243.chr
DM =

“363” # “xf3” # break

242.chr
NOP =

“362” # “xf2” # data mark–for connect. cleaning

241.chr
SE =

“361” # “xf1” # nop

240.chr
EOR =

“360” # “xf0” # end sub negotiation

239.chr
ABORT =

“357” # “xef” # end of record (transparent mode)

238.chr
SUSP =

“356” # “xee” # Abort process

237.chr
EOF =

“355” # “xed” # Suspend process

236.chr
SYNCH =

“354” # “xec” # End of file

242.chr
OPT_BINARY =

“362” # “xf2” # for telfunc calls

0.chr
OPT_ECHO =

“000” # “x00” # Binary Transmission

1.chr
OPT_RCP =

“001” # “x01” # Echo

2.chr
OPT_SGA =

“002” # “x02” # Reconnection

3.chr
OPT_NAMS =

“003” # “x03” # Suppress Go Ahead

4.chr
OPT_STATUS =

“004” # “x04” # Approx Message Size Negotiation

5.chr
OPT_TM =

“005” # “x05” # Status

6.chr
OPT_RCTE =

“006” # “x06” # Timing Mark

7.chr
OPT_NAOL =

“a” # “x07” # Remote Controlled Trans and Echo

8.chr
OPT_NAOP =

“010” # “x08” # Output Line Width

9.chr
OPT_NAOCRD =

“t” # “x09” # Output Page Size

10.chr
OPT_NAOHTS =

“n” # “x0a” # Output Carriage-Return Disposition

11.chr
OPT_NAOHTD =

“v” # “x0b” # Output Horizontal Tab Stops

12.chr
OPT_NAOFFD =

“f” # “x0c” # Output Horizontal Tab Disposition

13.chr
OPT_NAOVTS =

“r” # “x0d” # Output Formfeed Disposition

14.chr
OPT_NAOVTD =

“016” # “x0e” # Output Vertical Tabstops

15.chr
OPT_NAOLFD =

“017” # “x0f” # Output Vertical Tab Disposition

16.chr
OPT_XASCII =

“020” # “x10” # Output Linefeed Disposition

17.chr
OPT_LOGOUT =

“021” # “x11” # Extended ASCII

18.chr
OPT_BM =

“022” # “x12” # Logout

19.chr
OPT_DET =

“023” # “x13” # Byte Macro

20.chr
OPT_SUPDUP =

“024” # “x14” # Data Entry Terminal

21.chr
OPT_SUPDUPOUTPUT =

“025” # “x15” # SUPDUP

22.chr
OPT_SNDLOC =

“026” # “x16” # SUPDUP Output

23.chr
OPT_TTYPE =

“027” # “x17” # Send Location

24.chr
OPT_EOR =

“030” # “x18” # Terminal Type

25.chr
OPT_TUID =

“031” # “x19” # End of Record

26.chr
OPT_OUTMRK =

“032” # “x1a” # TACACS User Identification

27.chr
OPT_TTYLOC =

“e” # “x1b” # Output Marking

28.chr
OPT_3270REGIME =

“034” # “x1c” # Terminal Location Number

29.chr
OPT_X3PAD =

“035” # “x1d” # Telnet 3270 Regime

30.chr
OPT_NAWS =

“036” # “x1e” # X.3 PAD

31.chr
OPT_TSPEED =

“037” # “x1f” # Negotiate About Window Size

32.chr
OPT_LFLOW =

“ ” # “x20” # Terminal Speed

33.chr
OPT_LINEMODE =

“!” # “x21” # Remote Flow Control

34.chr
OPT_XDISPLOC =

“"” # “x22” # Linemode

35.chr
OPT_OLD_ENVIRON =

“#” # “x23” # X Display Location

36.chr
OPT_AUTHENTICATION =

“$” # “x24” # Environment Option

37.chr
OPT_ENCRYPT =

“%” # “x25” # Authentication Option

38.chr
OPT_NEW_ENVIRON =

“&” # “x26” # Encryption Option

39.chr
OPT_EXOPL =

“‘” # “x27” # New Environment Option

255.chr
NULL =

“377” # “xff” # Extended-Options-List

"\000"
CR =
"\015"
LF =
"\012"
EOL =
CR + LF
DEFAULT_OPTIONS =

Returns default options for new connections (used for merging).

Returns:

  • (Hash)

    default options for new connections (used for merging)

See Also:

{
  host: "localhost",
  port: 23,
  prompt: %r{[$%#>] \z}n,
  connect_timeout: 3,
  timeout: 10,
  wait_time: 0,
  bin_mode: false,
  telnet_mode: true,
  output_log: nil,
  command_log: nil,
  logger_class: Logger,
  login_prompt: %r{[Ll]ogin[: ]*\z}n,
  password_prompt: %r{[Pp]ass(?:word|phrase)[: ]*\z}n,
  username: nil,
  password: nil,

  # telnet protocol stuff
  SGA: false,
  BINARY: false,
}.freeze
DefaultOptions =
Deprecated.
DEFAULT_OPTIONS
STOP_WHEN_DONE =

Returns used to stop EventMachine when everything has completed.

Returns:

  • (Proc)

    used to stop EventMachine when everything has completed

lambda do
  # stop when everything is done
  if self.connection_count.zero? and EventMachine.defers_finished?
    EventMachine.stop
  else
    EventMachine.next_tick(&STOP_WHEN_DONE)
  end
end
StopWhenEMDone =
Deprecated.
STOP_WHEN_DONE
RootFiber =

Returns the root fiber.

Returns:

  • (Fiber)

    the root fiber

Fiber.current
@@_telnet_connection_count =

Returns number of active connections.

Returns:

  • (Integer)

    number of active connections

0

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(opts) ⇒ Connection

Initializes the current instance. opts is a Hash of options. The default values are in the constant DEFAULT_OPTIONS.

The options are actually merged in connect.

Parameters:

  • opts (Hash)

    a customizable set of options

Options Hash (opts):

  • :host (String) — default: "localhost"

    the hostname or IP address of the host to connect to.

  • :port (Integer) — default: 23

    the TCP port to connect to

  • :bin_mode (Boolean) — default: false

    if false, newline substitution is performed. Outgoing LF is converted to CRLF, and incoming CRLF is converted to LF. If true, this substitution is not performed. This value can also be set using #bin_mode=. The outgoing conversion only applies to the #puts and #print methods, not the #write method. The precise nature of the newline conversion is also affected by the telnet options SGA and BIN.

  • :output_log (String, nil) — default: nil

    the name of the file to write connection status messages and all received traffic to. In the case of a proper Telnet session, this will include the client input as echoed by the host; otherwise, it only includes server responses. Output is appended verbatim to this file. By default, no output log is kept.

  • :command_log (String, nil) — default: nil

    the name of the file to write the commands executed in this Telnet session. Commands are appended to this file. By default, no command log is kept.

  • :logger_class (Class) — default: Logger

    the class used to create two logger instances which are used for output and command logging.

  • :prompt (Regexp, String) — default: %r{[$%#>] \z}n

    a regular expression matching the host’s command-line prompt sequence. This is needed to determine the end of a command’s output.

  • :login_prompt (Regexp, String) — default: %r{[Ll]ogin[: ]*\z}n

    a regular expression (or String, see #waitfor) used to wait for the login prompt.

  • :password_prompt (Regexp, String) — default: %r{[Pp]ass(?:word|phrase)[: ]*\z}n

    a regular expression (or String, see #waitfor) used to wait for the password prompt.

  • :username (String, nil) — default: nil

    the String that is sent to the telnet server after seeing the login prompt. nil means there’s no need to log in.

  • :password (String, nil) — default: nil

    the String that is sent to the telnet server after seeing the password prompt. nil means there’s no need for a password.

  • :telnet_mode (Boolean) — default: true

    In telnet mode, traffic received from the host is parsed for special command sequences, and these sequences are escaped in outgoing traffic sent using #puts or #print (but not #write). If you are connecting to a non-telnet service (such as SMTP or POP), this should be set to false to prevent undesired data corruption. This value can also be set by the #telnet_mode= method.

  • :timeout (Integer) — default: 10

    the number of seconds to wait before timing out while waiting for the prompt (in #waitfor). Exceeding this timeout causes a TimeoutError to be raised. You can disable the timeout by setting this value to nil.

  • :connect_timeout (Integer) — default: 3

    the number of seconds to wait before timing out the initial attempt to connect. You can disable the timeout during login by setting this value to nil.

  • :wait_time (Integer, Float) — default: 0

    the number of seconds to wait after seeing what looks like a prompt (that is, received data that matches the :prompt option value) to let more data arrive. If more data does arrive during that time, it is assumed that the previous prompt was in fact not the final prompt. This can avoid false matches, but it can also lead to missing real prompts (if, for instance, a background process writes to the terminal soon after the prompt is displayed). The default of zero means not to wait for more data after seeing what looks like a prompt.



403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
# File 'lib/em-simple_telnet.rb', line 403

def initialize(opts)
  @options = opts
  @last_command = nil

  @logged_in = nil
  @connection_state = :connecting
  f = Fiber.current
  @fiber_resumer = ->(result = nil){ f.resume(result) }
  @input_buffer = ""
  @input_rest = ""
  @wait_time_timer = nil
  @check_input_buffer_timer = nil
  @recently_received_data = ""
  @logger = opts[:logger] || EventMachine::Protocols::SimpleTelnet.logger

  setup_logging
end

Instance Attribute Details

#command_loggerLogger (readonly)

Returns connection specific logger used to log commands.

Returns:

  • (Logger)

    connection specific logger used to log commands



428
429
430
# File 'lib/em-simple_telnet.rb', line 428

def command_logger
  @command_logger
end

#fiber_resumerProc

Returns the callback executed again and again to resume this connection’s Fiber.

Returns:

  • (Proc)

    the callback executed again and again to resume this connection’s Fiber



442
443
444
# File 'lib/em-simple_telnet.rb', line 442

def fiber_resumer
  @fiber_resumer
end

#last_commandString (readonly)

Returns Last command that was executed in this telnet session.

Returns:

  • (String)

    Last command that was executed in this telnet session



422
423
424
# File 'lib/em-simple_telnet.rb', line 422

def last_command
  @last_command
end

#last_data_sent_atTime (readonly)

Returns when the last data was sent.

Returns:

  • (Time)

    when the last data was sent



457
458
459
# File 'lib/em-simple_telnet.rb', line 457

def last_data_sent_at
  @last_data_sent_at
end

#last_promptString (readonly)

Returns last prompt matched.

Returns:

  • (String)

    last prompt matched



454
455
456
# File 'lib/em-simple_telnet.rb', line 454

def last_prompt
  @last_prompt
end

#logged_inTime (readonly)

Returns when the login succeeded for this connection.

Returns:

  • (Time)

    when the login succeeded for this connection



522
523
524
# File 'lib/em-simple_telnet.rb', line 522

def logged_in
  @logged_in
end

#loggerLogger, ...

Returns logger for connection activity (messages from SimpleTelnet).

Returns:

  • (Logger, #debug, #debug?, #info, ...)

    logger for connection activity (messages from SimpleTelnet)



446
447
448
# File 'lib/em-simple_telnet.rb', line 446

def logger
  @logger
end

#optionsHash (readonly)

Returns used telnet options Hash.

Returns:

  • (Hash)

    used telnet options Hash



431
432
433
# File 'lib/em-simple_telnet.rb', line 431

def options
  @options
end

#output_loggerLogger (readonly)

Returns connection specific logger used to log output.

Returns:

  • (Logger)

    connection specific logger used to log output



425
426
427
# File 'lib/em-simple_telnet.rb', line 425

def output_logger
  @output_logger
end

Class Method Details

.connect(opts) {|connection| ... } ⇒ SimpleTelnet::Connection

Establishes connection to the host.

Merges DEFAULT_OPTIONS with opts. Tells EventMachine to establish the connection to the desired host and port using SimpleTelnet::Connection, and logs into the host using #login.

Passes the connection to the block provided. It also ensures that the connection is closed using #close after the block returns, unless it’s already #closed?. The connection is then returned.

Parameters:

  • opts (Hash)

    a customizable set of options

Options Hash (opts):

  • :host (String)

    the hostname to connect to

  • :port (Integer)

    the TCP port to connect to

Yield Parameters:

Returns:



269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
# File 'lib/em-simple_telnet.rb', line 269

def connect(opts)
  opts = DEFAULT_OPTIONS.merge opts

  params = [
    # for EventMachine.connect
    opts[:host],
    opts[:port],
    self,

    # pass the *merged* options to SimpleTelnet#initialize
    opts
  ]

  begin
    # start establishing the connection
    connection = EventMachine.connect(*params)

    # will be resumed by #connection_completed or #unbind
    connection.pause_and_wait_for_result

    # login
    connection.__send__(:login)

    begin
      yield connection
    ensure
      # Use #close so a subclass can execute some kind of logout command
      # before the connection is closed.
      connection.close unless connection.closed?
    end
  ensure
    # close the connection in any case
    if connection
      connection.close_connection_after_writing

      # give some time to send the remaining data, which should be nothing
      EventMachine.add_timer(2) { connection.close_connection }
    end
  end

  return connection
end

.connection_countInteger

Returns number of active connections.

Returns:

  • (Integer)

    number of active connections



313
314
315
# File 'lib/em-simple_telnet.rb', line 313

def connection_count
  @@_telnet_connection_count
end

.loggerLogger, ...

Returns the logger instance for SimpleTelnet.

Returns:

  • (Logger, #debug, #debug?, #info, #warn, ...)

    the logger instance for SimpleTelnet



198
# File 'lib/em-simple_telnet.rb', line 198

def logger() @logger end

.new(*args, &blk) ⇒ SimpleTelnet::Connection

Recognizes whether this call was issued by the user program or by EventMachine. If the call was not issued by EventMachine, merges the options provided with the DEFAULT_OPTIONS and creates a Fiber (not started yet). Inside the Fiber SimpleTelnet.connect would be called.

If EventMachine’s reactor is already running, just starts the Fiber.

If it’s not running yet, starts a new EventMachine reactor and starts the Fiber. It’ll stop automatically when everything has completed (connections and deferred tasks).

Returns:



213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
# File 'lib/em-simple_telnet.rb', line 213

def new *args, &blk
  # call super if first argument is a connection signature of
  # EventMachine
  return super(*args, &blk) if args.first.is_a? Integer

  # This method was probably called with a Hash of connection options.

  # create new fiber to connect and execute block
  opts = args[0] || {}
  connection = nil
  fiber = Fiber.new do | callback |
    connection = connect(opts, &blk)
    callback.call if callback
  end

  if EventMachine.reactor_running? and Fiber.current == RootFiber
    logger.debug "EventMachine reactor had been started " +
      "independently. Won't stop it automatically."
    fiber.resume

  elsif EventMachine.reactor_running?
    # NOTE: Seems like the EventMachine reactor is already running, but we're
    # not in the root Fiber. That means we're probably in the process of
    # establishing a nested connection (from inside a Fiber created by SimpleTelnet).

    # Transfer control to the "inner" Fiber and stop the current one.
    # The block will be called after connect() returned to transfer control
    # back to the "outer" Fiber.
    outer_fiber = Fiber.current
    fiber.transfer ->{ outer_fiber.transfer }

  else
    # start EventMachine and stop it when connection is done
    EventMachine.run do
      fiber.resume
      EventMachine.next_tick(&STOP_WHEN_DONE)
    end
  end
  return connection
end

Instance Method Details

#bin_mode=(bool) ⇒ Object

Turn newline conversion on or off for this connection.

Parameters:

  • bool (Boolean)

    whether to use bin mode (no newline conversion) for this connection



481
482
483
# File 'lib/em-simple_telnet.rb', line 481

def bin_mode=(bool)
  @options[:bin_mode] = bool
end

#bin_mode?Boolean

Returns current bin mode option of this connection.

Returns:

  • (Boolean)

    current bin mode option of this connection



474
475
476
# File 'lib/em-simple_telnet.rb', line 474

def bin_mode?
  @options[:bin_mode]
end

#check_input_buffervoid

This method returns an undefined value.

Checks the input buffer (@input_buffer) for the prompt we’re waiting for. Calls @fiber_resumer with the output if the prompt has been found.

If @options[:wait_time] is set, it will wait this amount of seconds after seeing what looks like the prompt before calling until the real prompt is received. This is useful for commands that result in multiple prompts.



657
658
659
660
661
662
663
664
665
666
667
# File 'lib/em-simple_telnet.rb', line 657

def check_input_buffer
  return unless md = @input_buffer.match(@options[:prompt])

  if s = @options[:wait_time] and s > 0
    # resume Fiber after s seconds
    @wait_time_timer = EventMachine::Timer.new(s) { process_match_data(md) }
  else
    # resume Fiber now
    process_match_data(md)
  end
end

#closeObject

Redefine this method to execute some logout command like exit or logout before the connection is closed. Don’t forget: The command will probably not return a prompt, so use #puts, which doesn’t wait for a prompt.



1000
1001
# File 'lib/em-simple_telnet.rb', line 1000

def close
end

#close_logsObject



1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
# File 'lib/em-simple_telnet.rb', line 1007

def close_logs
  # NOTE: IOError is rescued because they could already be closed.
  # {#closed?} can't be used, because the method is not implemented by
  # Logger, for example.

  begin
    @output_logger.close
  rescue IOError
    # already closed
  end if @options[:output_log]

  begin
    @command_logger.close
  rescue IOError
    # already closed
  end if @options[:command_log]
end

#closed?Boolean

Returns whether the connection is closed.

Returns:

  • (Boolean)

    whether the connection is closed.



530
531
532
# File 'lib/em-simple_telnet.rb', line 530

def closed?
  @connection_state == :closed
end

#cmd(command, opts = {}) ⇒ String

Note:

The returned output includes the prompt and in most cases the

Sends a command to the host and returns its output.

More exactly, the following things are done:

host’s echo of the command sent.

Examples:

Normal usage

output = host.cmd "ls -la"

Custom Prompt

host.cmd "delete user john", prompt: /Are you sure?/
host.cmd "yes"

Parameters:

  • command (String)

    the command to execute

  • opts (Hash) (defaults to: {})

    a customizable set of options

Options Hash (opts):

  • :hide (Boolean) — default: false

    whether to hide the command from the command log (#command_logger). If so, it is logged as "<hidden command>" instead of the command itself. This is useful for passwords, so they don’t get logged to the command log.

  • :raw_command (Boolean) — default: false

    whether to send a raw command using #print, as opposed to using #puts

  • :prompt (Regexp, String) — default: nil

    prompt to look for after this command’s output (instead of the one set in options[:prompt])

Returns:

  • (String)

    the command’s output, including prompt



848
849
850
851
852
853
854
855
856
857
858
859
860
861
# File 'lib/em-simple_telnet.rb', line 848

def cmd(command, opts = {})
  @last_command = command = command.to_s

  # log the command
  if @command_logger
    @command_logger.info(opts[:hide] ? "<hidden command>" : command)
  end

  # send the command
  opts[:raw_command] ? print(command) : puts(command)

  # wait for the output
  waitfor(opts[:prompt], opts)
end

#connection_completedvoid

This method returns an undefined value.

Called by EventMachine after the connection is successfully established.

If the debug level in #logger is active, this will cause received data to be logged periodically.



984
985
986
987
988
989
990
991
992
# File 'lib/em-simple_telnet.rb', line 984

def connection_completed
  self.connection_state = :connected
  @fiber_resumer.(:connection_completed)

  # log received data in a more readable way
  if logger.debug?
    EventMachine.add_periodic_timer(0.5) { log_recently_received_data }
  end
end

#connection_state_callbackObject

Deprecated.

use #fiber_resumer instead



449
450
451
# File 'lib/em-simple_telnet.rb', line 449

def connection_state_callback
  fiber_resumer
end

#hostString

Hostname/address for this connection.

Returns:

  • (String)


738
739
740
# File 'lib/em-simple_telnet.rb', line 738

def host
  @options[:host]
end

#listen(opts = {}) {|output| ... } ⇒ void

This method returns an undefined value.

Listen for anything that’s received from the host. Each received chunk will be yielded to the block passed. To make it stop listening, the block should return or raise something.

Parameters:

  • opts (Hash) (defaults to: {})

    a customizable set of options

Options Hash (opts):

  • :timeout (Integer) — default: 90

    temporary #timeout to use

Yield Parameters:

  • output (String)

    the newly output received



749
750
751
752
753
754
755
756
# File 'lib/em-simple_telnet.rb', line 749

def listen(opts = {}, &blk)
  self.connection_state = :listening
  timeout(opts.fetch(:timeout, 90)) do
    yield pause_and_wait_for_result while true
  end
ensure
  self.connection_state = :connected if !closed?
end

#logged_in?Boolean

Returns whether the login already succeeded for this connection.

Returns:

  • (Boolean)

    whether the login already succeeded for this connection



525
526
527
# File 'lib/em-simple_telnet.rb', line 525

def logged_in?
  @logged_in ? true : false
end

#login(opts = {}) ⇒ String

Note:

Don’t forget to set @logged_in after the login succeeds if you override this method!

Login to the host with a given username and password.

This method looks for the login and password prompt (see implementation) from the host to determine when to send the username and password. If the login sequence does not follow this pattern (for instance, you are connecting to a service other than telnet), you will need to handle login yourself.

Examples:

host. username: "myuser", password: "mypass"

Parameters:

  • opts (Hash) (defaults to: {})

    a customizable set of options

Options Hash (opts):

  • :username (String, nil) — default: options[:username]

    the username to use to log in, if login is needed

  • :password (String, nil) — default: options[:password] the password to use to log in, if a password is needed

    options the password to use to log in, if a password is needed

Returns:

  • (String)

    all data received during the login process



882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
# File 'lib/em-simple_telnet.rb', line 882

def  opts={}
  opts = @options.merge opts

  # don't log in if username is not set
  if opts[:username].nil?
    @logged_in = Time.now
    return
  end

  begin
    output = waitfor opts[:login_prompt]

    if opts[:password]
      # login with username and password
      output << cmd(opts[:username], prompt: opts[:password_prompt])
      output << cmd(opts[:password], hide: true)
    else
      # login with username only
      output << cmd(opts[:username])
    end
  rescue Timeout::Error
    raise LoginFailed, "Timed out while expecting some kind of prompt."
  end

  @logged_in = Time.now
  output
end

#pause_and_wait_for_resultString

Pauses the current Fiber. When resumed, returns the value passed. If the value passed is an Exeption, it’s raised.

Returns:

  • (String)

    value passed to Fiber#resume (output received)

Raises:

  • (Exception)

    exception that has been passed to Fiber#resume



726
727
728
729
730
731
732
733
734
# File 'lib/em-simple_telnet.rb', line 726

def pause_and_wait_for_result
  result = nil
  while result.nil?
    result = Fiber.yield
  end

  raise result if result.is_a? Exception
  return result
end

#post_initvoid

This method returns an undefined value.

Called by EventMachine when the connection is being established (not after the connection is established! see #connection_completed). This occurs directly after the call to #initialize.

Sets the pending_connect_timeout to options[:connect_timeout] seconds. This is the duration after which a TCP connection in the connecting state will fail (abort and run #unbind). Increases @@_telnet_connection_count by one after that.

Sets also the comm_inactivity_timeout to options[:timeout] seconds. This is the duration after which a TCP connection is automatically closed if no data was sent or received.



927
928
929
930
931
# File 'lib/em-simple_telnet.rb', line 927

def post_init
  self.pending_connect_timeout = @options[:connect_timeout]
  self.comm_inactivity_timeout = @options[:timeout]
  @@_telnet_connection_count += 1
end

This method returns an undefined value.

Sends a string to the host.

This does not automatically append a newline to the string. Embedded newlines may be converted and telnet command sequences escaped depending upon the values of #telnet_mode, #bin_mode, and telnet options set by the host.

Parameters:

  • string (String)

    what to send



787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
# File 'lib/em-simple_telnet.rb', line 787

def print(string)
  string = string.gsub(/#{IAC}/no, IAC + IAC) if telnet_mode?

  unless bin_mode?
    string = if @options[:BINARY] and @options[:SGA]
      # IAC WILL SGA IAC DO BIN send EOL --> CR
      string.gsub(/\n/n, CR)

    elsif @options[:SGA]
      # IAC WILL SGA send EOL --> CR+NULL
      string.gsub(/\n/n, CR + NULL)

    else
      # NONE send EOL --> CR+LF
      string.gsub(/\n/n, EOL)
    end
  end

  send_data string
end

#process_match_data(md) ⇒ void

This method returns an undefined value.

Remembers md as the @last_prompt and resumes the fiber, passing it the whole output received up to and including the match data.

Parameters:

  • md (MatchData)


673
674
675
676
677
678
# File 'lib/em-simple_telnet.rb', line 673

def process_match_data(md)
  @last_prompt = md.to_s # remember the prompt matched
  output = md.pre_match + @last_prompt
  @input_buffer = md.post_match
  @fiber_resumer.(output)
end

#process_payload(buf) ⇒ void

This method returns an undefined value.

Appends buf to the @input_buffer. Then cancels the @wait_time_timer and @check_input_buffer_timer if they’re set.

Does some performance optimizations in case the input buffer is becoming huge and finally calls #check_input_buffer.

Parameters:

  • buf (String)

    received data with telnet sequences stripped away



592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
# File 'lib/em-simple_telnet.rb', line 592

def process_payload(buf)
  # append output from server to input buffer and log it
  @input_buffer << buf

  case @connection_state
  when :waiting_for_prompt

    # cancel the timer for wait_time value because we received more data
    if @wait_time_timer
      @wait_time_timer.cancel
      @wait_time_timer = nil
    end

    # we ensure there's no timer running for checking the input buffer
    if @check_input_buffer_timer
      @check_input_buffer_timer.cancel
      @check_input_buffer_timer = nil
    end

    if @input_buffer.size >= 100_000
      ##
      # if the input buffer is really big
      #

      # We postpone checking the input buffer by one second because the regular
      # expression matches can get quite slow.
      #
      # So as long as data is being received (continuously), the input buffer
      # is not checked. It's only checked one second after the whole output
      # has been received.
      @check_input_buffer_timer = EventMachine::Timer.new(1) do
        @check_input_buffer_timer = nil
        check_input_buffer
      end
    else
      ##
      # as long as the input buffer is small
      #

      # check the input buffer now
      check_input_buffer
    end
  when :listening
    @fiber_resumer.(buf)
  when :connected, :sleeping
    logger.debug "#{host}: Discarding data that was received " +
      "while not waiting " +
      "for data (state = #{@connection_state.inspect}): #{buf.inspect}"
  else
    raise "Don't know what to do with received data while being in " +
      "connection state #{@connection_state.inspect}"
  end
end

#puts(string) ⇒ void

This method returns an undefined value.

Sends a string to the host, along with an appended newline if there isn’t one already.

Parameters:

  • string (String)

    what to send



814
815
816
817
# File 'lib/em-simple_telnet.rb', line 814

def puts(string)
  string += "\n" unless string.end_with? "\n"
  print string
end

#receive_data(data) ⇒ void

This method returns an undefined value.

Called by EventMachine when data is received.

The data is processed using #preprocess, which processes telnet sequences and strips them away. The resulting “payload” is logged and handed over to #process_payload.

Parameters:

  • data (String)

    newly received raw data, including telnet sequences



542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
# File 'lib/em-simple_telnet.rb', line 542

def receive_data(data)
  @recently_received_data << data if log_recently_received_data?
  if @options[:telnet_mode]
    c = @input_rest + data
    se_pos = c.rindex(/#{IAC}#{SE}/no) || 0
    sb_pos = c.rindex(/#{IAC}#{SB}/no) || 0
    if se_pos < sb_pos
      buf = preprocess(c[0 ... sb_pos])
      @input_rest = c[sb_pos .. -1]

    elsif pt_pos = c.rindex(
      /#{IAC}[^#{IAC}#{AO}#{AYT}#{DM}#{IP}#{NOP}]?\z/no) ||
      c.rindex(/\r\z/no)

      buf = preprocess(c[0 ... pt_pos])
      @input_rest = c[pt_pos .. -1]

    else
      buf = preprocess(c)
      @input_rest.clear
    end
  else
    # Not Telnetmode.
    #
    # We cannot use #preprocess on this data, because that
    # method makes some Telnetmode-specific assumptions.
    buf = @input_rest + data
    @input_rest.clear
    unless @options[:bin_mode]
      buf.chop! if buf =~ /\r\z/no
      buf.gsub!(/#{EOL}/no, "\n")
    end
  end

  # in case only telnet sequences were received
  return if buf.empty?

  @output_logger << buf if @output_logger
  process_payload(buf)
end

#send_data(s) ⇒ Object

Tells EventMachine to send raw data to the telnet server. This also updates #last_data_sent_at and logs recently received data, if wanted.

Parameters:

  • s (String)

    what to send

Raises:

  • (Errno::ENOTCONN)

    if the connection is closed



768
769
770
771
772
773
774
775
# File 'lib/em-simple_telnet.rb', line 768

def send_data(s)
  raise Errno::ENOTCONN,
    "Can't send data: Connection is already closed." if closed?
  @last_data_sent_at = Time.now
  log_recently_received_data
  logger.debug "#{host}: Sending #{s.inspect}"
  super
end

#telnet_mode=(bool) ⇒ Object

Turn telnet command interpretation on or off for this connection. It should be on for true telnet sessions, off if used to connect to a non-telnet service such as SMTP.

Parameters:

  • bool (Boolean)

    whether to use telnet mode for this connection



469
470
471
# File 'lib/em-simple_telnet.rb', line 469

def telnet_mode=(bool)
  @options[:telnet_mode] = bool
end

#telnet_mode?Boolean

Returns whether telnet mode is enabled or not.

Returns:

  • (Boolean)

    whether telnet mode is enabled or not



460
461
462
# File 'lib/em-simple_telnet.rb', line 460

def telnet_mode?
  @options[:telnet_mode]
end

#telnet_optionsHash

Deprecated.

Same as #options.

Returns:

  • (Hash)


436
437
438
# File 'lib/em-simple_telnet.rb', line 436

def telnet_options
  @options
end

#timeout(seconds = nil) ⇒ Integer, Object

If a block is given, sets the timeout to seconds and executes the block and restores the previous timeout. This is useful when you want to temporarily change the timeout for some commands.

If no block is given, the current timeout is returned.

Examples:

current_timeout = host.timeout

 host.timeout(200) do
   host.cmd "command 1"
   host.cmd "command 2"
 end

Returns:

  • (Integer)

    the current timeout, if no block given

  • (Object)

    the block’s value, if it was given

See Also:



511
512
513
514
515
516
517
518
519
# File 'lib/em-simple_telnet.rb', line 511

def timeout(seconds = nil)
  return @options[:timeout] unless block_given?

  before = @options[:timeout]
  self.timeout = seconds
  yield
ensure
  self.timeout = before
end

#timeout=(seconds) ⇒ Object

Set the activity timeout to seconds for this connection. To disable it, set it to 0 or nil. If no data is received (or sent) for that amount of time, the connection will be closed.

Parameters:

  • seconds (Integer)

    the new timeout in seconds



489
490
491
492
# File 'lib/em-simple_telnet.rb', line 489

def timeout=(seconds)
  @options[:timeout] = seconds
  set_comm_inactivity_timeout( seconds )
end

#unbind(reason) ⇒ void

This method returns an undefined value.

Finally, the connection state is set to :closed.



947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
# File 'lib/em-simple_telnet.rb', line 947

def unbind(reason)
  prev_conn_state = @connection_state
  self.connection_state = :closed
  logger.debug "#{host}: Unbind reason: " + reason.inspect
  @@_telnet_connection_count -= 1
  close_logs

  # if we were connecting or waiting for a prompt, return an exception to
  # #waitfor
  case prev_conn_state
  when :waiting_for_prompt, :listening
    # NOTE: reason should be Errno::ETIMEDOUT in these cases.
    error = TimeoutError.new

    # set hostname and command
    if hostname = @options[:host]
      error.hostname = hostname
    end
    error.command = @last_command if @last_command

    @fiber_resumer.(error)
  when :sleeping, :connected

  when :connecting
    @fiber_resumer.(ConnectionFailed.new)
  else
    logger.error "#{host}: bad connection state #{prev_conn_state.inspect} " +
      "while unbinding"
  end
end

#waitfor(prompt = nil, opts = {}) ⇒ String

Read data from the host until a certain sequence is matched.

Parameters:

  • prompt (Regexp, String) (defaults to: nil)

    If it’s not a Regexp, it’s converted to a Regexp (all special characters escaped) assuming it’s a String.

  • opts (Hash) (defaults to: {})

    a customizable set of options

Options Hash (opts):

  • :timeout (Integer)

    the timeout while waiting for new data to arrive

  • :wait_time (Integer)

    time to wait after receiving what looks like a prompt

Returns:

  • (String)

    output including prompt

Raises:

  • (Errno::ENOTCONN)

    if connection is closed



690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
# File 'lib/em-simple_telnet.rb', line 690

def waitfor(prompt = nil, opts = {})
  if closed?
    raise Errno::ENOTCONN,
      "Can't wait for anything when connection is already closed!"
  end
  options_were = @options
  timeout_was = self.timeout if opts.key?(:timeout)
  opts[:prompt] = prompt if prompt
  @options = @options.merge opts

  # convert String prompt into a Regexp
  unless @options[:prompt].is_a? Regexp
    regex = Regexp.new(Regexp.quote(@options[:prompt]))
    @options[:prompt] = regex
  end

  # set custom inactivity timeout, if wanted
  self.timeout = @options[:timeout] if opts.key?(:timeout)

  # so #unbind knows we were waiting for a prompt (in case that inactivity
  # timeout fires)
  self.connection_state = :waiting_for_prompt

  pause_and_wait_for_result
ensure
  @options = options_were
  self.timeout = timeout_was if opts.key?(:timeout)

  # NOTE: #unbind could have been called in the meantime
  self.connection_state = :connected if !closed?
end

#write(s) ⇒ Object

Passes argument to #send_data.

Parameters:

  • s (String)

    raw data to send



760
761
762
# File 'lib/em-simple_telnet.rb', line 760

def write(s)
  send_data(s)
end