class SimpleTelnet::Connection

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.

@example 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

@example 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

Constants

DEFAULT_OPTIONS

@return [Hash] default options for new connections (used for merging) @see initialize @see .connect

DefaultOptions

@deprecated

RootFiber

@return [Fiber] the root fiber

STOP_WHEN_DONE

@return [Proc] used to stop EventMachine when everything has completed

StopWhenEMDone

@deprecated

Attributes

command_logger[R]

@return [Logger] connection specific logger used to log commands

fiber_resumer[RW]

@return [Proc] the callback executed again and again to resume this

connection's Fiber
last_command[R]

@return [String] Last command that was executed in this telnet session

last_data_sent_at[R]

@return [Time] when the last data was sent

last_prompt[R]

@return [String] last prompt matched

logged_in[R]

@return [Time] when the login succeeded for this connection

logger[RW]

@return [Logger, debug, debug?, info, …] logger for connection

activity (messages from SimpleTelnet)
options[R]

@return [Hash] used telnet options Hash

output_logger[R]

@return [Logger] connection specific logger used to log output

Public Class Methods

connect(opts) { |connection| ... } click to toggle source

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.

@option opts [String] :host the hostname to connect to @option opts [Integer] :port the TCP port to connect to @yieldparam connection [SimpleTelnet::Connection] the logged in

connection

@return [SimpleTelnet::Connection]

# 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_count() click to toggle source

@return [Integer] number of active connections

# File lib/em-simple_telnet.rb, line 313
def connection_count
  @@_telnet_connection_count
end
logger() click to toggle source

@return [Logger, debug, debug?, info, warn, …] the logger instance

for SimpleTelnet
# File lib/em-simple_telnet.rb, line 198
def logger() @logger end
new(*args, &blk) click to toggle source

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).

@return [SimpleTelnet::Connection] the (closed) connection

Calls superclass method
# 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
new(opts) click to toggle source

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

@option opts [String] :host (“localhost”)

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

@option opts [Integer] :port (23)

the TCP port to connect to

@option opts [Boolean] :bin_mode (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.

@option opts [String, nil] :output_log (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.

@option opts [String, nil] :command_log (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.

@option opts [Class] :logger_class (Logger)

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

@option opts [Regexp, String] :prompt (%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.

@option opts [Regexp, String] :login_prompt (%r{[Ll]ogin[: ]*z}n)

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

@option opts [Regexp, String] :password_prompt

(%r{[Pp]ass(?:word|phrase)[: ]*\z}n)
a regular expression (or String, see {#waitfor}) used to wait for the
password prompt.

@option opts [String, nil] :username (nil)

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

@option opts [String, nil] :password (nil)

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

@option opts [Boolean] :telnet_mode (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.

@option opts [Integer] :timeout (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+.

@option opts [Integer] :connect_timeout (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+.

@option opts [Integer, Float] :wait_time (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.

The options are actually merged in {.connect}.

# 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

Public Instance Methods

bin_mode=(bool) click to toggle source

Turn newline conversion on or off for this connection. @param bool [Boolean] whether to use bin mode (no newline conversion) for

this connection
# File lib/em-simple_telnet.rb, line 481
def bin_mode=(bool)
  @options[:bin_mode] = bool
end
bin_mode?() click to toggle source

@return [Boolean] current bin mode option of this connection

# File lib/em-simple_telnet.rb, line 474
def bin_mode?
  @options[:bin_mode]
end
check_input_buffer() click to toggle source

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 @fiber_resumer. This way, more data can be received until the real prompt is received. This is useful for commands that result in multiple prompts.

@return [void]

# 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
close() click to toggle source

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.

# File lib/em-simple_telnet.rb, line 1000
def close
end
close_logs() click to toggle source
# 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?() click to toggle source

@return [Boolean] whether the connection is closed.

# File lib/em-simple_telnet.rb, line 530
def closed?
  @connection_state == :closed
end
cmd(command, opts = {}) click to toggle source

Sends a command to the host and returns its output.

More exactly, the following things are done:

  • stores the command (see {#last_command})

  • logs it (see {#command_logger})

  • sends a string to the host ({#print} or {#puts})

  • reads in all received data (using {#waitfor})

@example Normal usage

output = host.cmd "ls -la"

@example Custom Prompt

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

@note The returned output includes the prompt and in most cases the host’s echo of the command sent.

@param command [String] the command to execute @option opts [Boolean] :hide (false) whether to hide the command from the

command log ({#command_logger}). If so, it is logged as <tt>"<hidden
command>"</tt> instead of the command itself. This is useful for
passwords, so they don't get logged to the command log.

@option opts [Boolean] :raw_command (false) whether to send a raw command

using {#print}, as opposed to using {#puts}

@option opts [Regexp, String] :prompt (nil) prompt to look for after this

command's output (instead of the one set in <tt>options[:prompt]</tt>)

@return [String] the command’s output, including prompt

# 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_completed() click to toggle source

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.

@return [void]

# 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_callback() click to toggle source

@deprecated use {#fiber_resumer} instead

# File lib/em-simple_telnet.rb, line 449
def connection_state_callback
  fiber_resumer
end
host() click to toggle source

Hostname/address for this connection. @return [String]

# File lib/em-simple_telnet.rb, line 738
def host
  @options[:host]
end
listen(opts = {}) { |pause_and_wait_for_result while true| ... } click to toggle source

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.

@option opts [Integer] :timeout (90) temporary {#timeout} to use @yieldparam output [String] the newly output received @return [void]

# 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?() click to toggle source

@return [Boolean] whether the login already succeeded for this connection

# File lib/em-simple_telnet.rb, line 525
def logged_in?
  @logged_in ? true : false
end
login(opts={}) click to toggle source

Login to the host with a given username and password.

@example

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

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.

@note Don’t forget to set @logged_in after the login succeeds if

you override this method!

@option opts [String, nil] :username (options) the username to

use to log in, if login is needed

@option opts [String, nil] :password (options the password to

use to log in, if a password is needed

@return [String] all data received during the login process

# File lib/em-simple_telnet.rb, line 882
def login 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_result() click to toggle source

Pauses the current Fiber. When resumed, returns the value passed. If the value passed is an Exeption, it’s raised. @return [String] value passed to Fiber#resume (output received) @raise [Exception] exception that has been passed to Fiber#resume

# 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_init() click to toggle source

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.

@return [void]

# 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
print(string) click to toggle source

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.

@param string [String] what to send @return [void]

process_match_data(md) click to toggle source

Remembers md as the @last_prompt and resumes the fiber, passing it the whole output received up to and including the match data. @param md [MatchData] @return [void]

# 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) click to toggle source

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}.

@param buf [String] received data with telnet sequences stripped away @return [void]

# 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) click to toggle source

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

@param string [String] what to send @return [void]

# File lib/em-simple_telnet.rb, line 814
def puts(string)
  string += "\n" unless string.end_with? "\n"
  print string
end
receive_data(data) click to toggle source

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}.

@param data [String] newly received raw data, including telnet sequences @return [void]

# 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) click to toggle source

Tells EventMachine to send raw data to the telnet server. This also updates {#last_data_sent_at} and logs recently received data, if wanted. @param s [String] what to send @raise [Errno::ENOTCONN] if the connection is closed

Calls superclass method
# 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) click to toggle source

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.

@param bool [Boolean] whether to use telnet mode for this connection

# File lib/em-simple_telnet.rb, line 469
def telnet_mode=(bool)
  @options[:telnet_mode] = bool
end
telnet_mode?() click to toggle source

@return [Boolean] whether telnet mode is enabled or not

# File lib/em-simple_telnet.rb, line 460
def telnet_mode?
  @options[:telnet_mode]
end
telnet_options() click to toggle source

@deprecated Same as {#options}. @return [Hash]

# File lib/em-simple_telnet.rb, line 436
def telnet_options
  @options
end
timeout(seconds = nil) { || ... } click to toggle source

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.

@example

current_timeout = host.timeout

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

@return [Integer] the current timeout, if no block given @return [Object] the block’s value, if it was given @see timeout=

# 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) click to toggle source

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. @param seconds [Integer] the new timeout in seconds

# File lib/em-simple_telnet.rb, line 489
def timeout=(seconds)
  @options[:timeout] = seconds
  set_comm_inactivity_timeout( seconds )
end
unbind(reason) click to toggle source

Finally, the connection state is set to :closed.

@return [void]

# 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 = {}) click to toggle source

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

@param prompt [Regexp, String] If it’s not a Regexp, it’s converted to

a Regexp (all special characters escaped) assuming it's a String.

@option opts [Integer] :timeout the timeout while waiting for new data to

arrive

@option opts [Integer] :wait_time time to wait after receiving what looks

like a prompt

@return [String] output including prompt @raise [Errno::ENOTCONN] if connection is closed

# 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) click to toggle source

Passes argument to {#send_data}. @param s [String] raw data to send

# File lib/em-simple_telnet.rb, line 760
def write(s)
  send_data(s)
end

Private Instance Methods

connection_state=(new_state) click to toggle source

Sets a new connection state. @param new_state [Symbol] @raise [Errno::ENOTCONN] if current (old) state is :closed, because that

can't be changed.
# File lib/em-simple_telnet.rb, line 1051
def connection_state=(new_state)
  if closed?
    raise Errno::ENOTCONN,
      "Can't change connection state: Connection is already closed."
  end
  @connection_state = new_state
end
log_recently_received_data() click to toggle source

Prints recently received data (@recently_received_data) if {#log_recently_received_data?} says so and there is recently received data in the buffer. Clears the buffer afterwards.

The goal is to log data in a more readable way, by periodically log what has recently been received, as opposed to each single character in case of a slowly answering telnet server.

@return [void]

# File lib/em-simple_telnet.rb, line 1036
def log_recently_received_data
  return if @recently_received_data.empty? || !log_recently_received_data?
  logger.debug "#{host}: Received: #{@recently_received_data.inspect}"
  @recently_received_data.clear
end
log_recently_received_data?() click to toggle source

@return [Boolean] if recently received data should be logged or not

# File lib/em-simple_telnet.rb, line 1043
def log_recently_received_data?
  logger.debug?
end
preprocess(string) click to toggle source

Preprocess received data from the host.

Performs newline conversion and detects telnet command sequences. Called automatically by {#receive_data}.

@param string [String] the raw string including telnet sequences @return [String] resulting data, hereby called “payload”

# File lib/em-simple_telnet.rb, line 1083
def preprocess string
  # combine CR+NULL into CR
  string = string.gsub(/#{CR}#{NULL}/no, CR) if telnet_mode?

  # combine EOL into "\n"
  string = string.gsub(/#{EOL}/no, LF) unless bin_mode?

  # remove NULL
  string = string.gsub(/#{NULL}/no, '') unless bin_mode?

  string.gsub(/#{IAC}(
               [#{IAC}#{AO}#{AYT}#{DM}#{IP}#{NOP}]|
               [#{DO}#{DONT}#{WILL}#{WONT}]
                 [#{OPT_BINARY}-#{OPT_NEW_ENVIRON}#{OPT_EXOPL}]|
               #{SB}[^#{IAC}]*#{IAC}#{SE}
             )/xno) do
    if    IAC == $1  # handle escaped IAC characters
      IAC
    elsif AYT == $1  # respond to "IAC AYT" (are you there)
      send_data("nobody here but us pigeons" + EOL)
      ''
    elsif DO[0] == $1[0]  # respond to "IAC DO x"
      if OPT_BINARY[0] == $1[1]
        @options[:BINARY] = true
        send_data(IAC + WILL + OPT_BINARY)
      else
        send_data(IAC + WONT + $1[1..1])
      end
      ''
    elsif DONT[0] == $1[0]  # respond to "IAC DON'T x" with "IAC WON'T x"
      send_data(IAC + WONT + $1[1..1])
      ''
    elsif WILL[0] == $1[0]  # respond to "IAC WILL x"
      if    OPT_BINARY[0] == $1[1]
        send_data(IAC + DO + OPT_BINARY)
      elsif OPT_ECHO[0] == $1[1]
        send_data(IAC + DO + OPT_ECHO)
      elsif OPT_SGA[0]  == $1[1]
        @options[:SGA] = true
        send_data(IAC + DO + OPT_SGA)
      else
        send_data(IAC + DONT + $1[1..1])
      end
      ''
    elsif WONT[0] == $1[0]  # respond to "IAC WON'T x"
      if    OPT_ECHO[0] == $1[1]
        send_data(IAC + DONT + OPT_ECHO)
      elsif OPT_SGA[0]  == $1[1]
        @options[:SGA] = false
        send_data(IAC + DONT + OPT_SGA)
      else
        send_data(IAC + DONT + $1[1..1])
      end
      ''
    else
      ''
    end
  end # string.gsub
end
setup_logging() click to toggle source

Sets up output and command logging. This depends on options[:output_log] and options[:command_log].

@return [void]

# File lib/em-simple_telnet.rb, line 1063
def setup_logging
  @output_logger = @command_logger = nil

  if file = @options[:output_log]
    @output_logger = @options[:logger_class].new(file)
    @output_logger.info "# Starting telnet output log at #{Time.now}\n"
  end

  if file = @options[:command_log]
    @command_logger = @options[:logger_class].new(file)
  end
end