Exscript.protocols.protocol module

An abstract base class for all protocols.

class Exscript.protocols.protocol.Protocol(driver=None, stdout=None, stderr=None, debug=0, connect_timeout=30, timeout=30, logfile=None, termtype=u'dumb', verify_fingerprint=True, account_factory=None, banner_timeout=20, encoding=u'latin-1')[source]

Bases: future.types.newobject.newobject

This is the base class for all protocols; it defines the common portions of the API.

The goal of all protocol classes is to provide an interface that is unified across protocols, such that the adapters may be used interchangeably without changing any other code.

In order to achieve this, the main challenge are the differences arising from the authentication methods that are used. The reason is that many devices may support the following variety authentication/authorization methods:

  1. Protocol level authentication, such as SSH’s built-in authentication.

    • p1: password only
    • p2: username
    • p3: username + password
    • p4: username + key
    • p5: username + key + password
  2. App level authentication, such that the authentication may happen long after a connection is already accepted. This type of authentication is normally used in combination with Telnet, but some SSH hosts also do this (users have reported devices from Enterasys). These devices may also combine protocol-level authentication with app-level authentication. The following types of app-level authentication exist:

    • a1: password only
    • a2: username
    • a3: username + password
  3. App level authorization: In order to implement the AAA protocol, some devices ask for two separate app-level logins, whereas the first serves to authenticate the user, and the second serves to authorize him. App-level authorization may support the same methods as app-level authentication:

    • A1: password only
    • A2: username
    • A3: username + password

We are assuming that the following methods are used:

  • Telnet:
    • p1 - p5: never
    • a1 - a3: optional
    • A1 - A3: optional
  • SSH:
    • p1 - p5: optional
    • a1 - a3: optional
    • A1 - A3: optional

To achieve authentication method compatibility across different protocols, we must hide all this complexity behind one single API call, and figure out which ones are supported.

As a use-case, our goal is that the following code will always work, regardless of which combination of authentication methods a device supports:

key = PrivateKey.from_file('~/.ssh/id_rsa', 'my_key_password')

# The user account to use for protocol level authentication.
# The key defaults to None, in which case key authentication is
# not attempted.
account = Account(name     = 'myuser',
                  password = 'mypassword',
                  key      = key)

# The account to use for app-level authentication.
# password2 defaults to password.
app_account = Account(name      = 'myuser',
                      password  = 'my_app_password',
                      password2 = 'my_app_password2')

# app_account defaults to account.
conn.login(account, app_account = None, flush = True)

Another important consideration is that once the login is complete, the device must be in a clearly defined state, i.e. we need to have processed the data that was retrieved from the connected host.

More precisely, the buffer that contains the incoming data must be in a state such that the following call to expect_prompt() will either always work, or always fail.

We hide the following methods behind the login() call:

# Protocol level authentication.
conn.protocol_authenticate(...)
# App-level authentication.
conn.app_authenticate(...)
# App-level authorization.
conn.app_authorize(...)

The code produces the following result:

Telnet:
    conn.protocol_authenticate -> NOP
    conn.app_authenticate
        -> waits for username or password prompt, authenticates,
           returns after a CLI prompt was seen.
    conn.app_authorize
        -> calls driver.enable(), waits for username or password
           prompt, authorizes, returns after a CLI prompt was seen.

SSH:
    conn.protocol_authenticate -> authenticates using user/key/password
    conn.app_authenticate -> like Telnet
    conn.app_authorize -> like Telnet

We can see the following:

  • protocol_authenticate() must not wait for a prompt, because else app_authenticate() has no way of knowing whether an app-level login is even necessary.

  • app_authenticate() must check the buffer first, to see if authentication has already succeeded. In the case that app_authenticate() is not necessary (i.e. the buffer contains a CLI prompt), it just returns.

    app_authenticate() must NOT eat the prompt from the buffer, because else the result may be inconsistent with devices that do not do any authentication; i.e., when app_authenticate() is not called.

  • Since the prompt must still be contained in the buffer, conn.driver.app_authorize() needs to eat it before it sends the command for starting the authorization procedure.

    This has a drawback - if a user attempts to call app_authorize() at a time where there is no prompt in the buffer, it would fail. So we need to eat the prompt only in cases where we know that auto_app_authorize() will attempt to execute a command. Hence the driver requires the Driver.supports_auto_authorize() method.

    However, app_authorize() must not eat the CLI prompt that follows.

  • Once all logins are processed, it makes sense to eat the prompt depending on the wait parameter. Wait should default to True, because it’s better that the connection stalls waiting forever, than to risk that an error is not immediately discovered due to timing issues (this is a race condition that I’m not going to detail here).

__init__(driver=None, stdout=None, stderr=None, debug=0, connect_timeout=30, timeout=30, logfile=None, termtype=u'dumb', verify_fingerprint=True, account_factory=None, banner_timeout=20, encoding=u'latin-1')[source]

Constructor. The following events are provided:

  • data_received_event: A packet was received from the connected host.
  • otp_requested_event: The connected host requested a one-time-password to be entered.
Parameters:
  • driver – Driver()|str
  • stdout – Where to write the device response. Defaults to an in-memory buffer.
  • stderr – Where to write debug info. Defaults to stderr.
  • debug – An integer between 0 (no debugging) and 5 (very verbose debugging) that specifies the amount of debug info sent to the terminal. The default value is 0.
  • connect_timeout – Timeout for the initial TCP connection attempt
  • timeout – See set_timeout(). The default value is 30.
  • logfile – A file into which a log of the conversation with the device is dumped.
  • termtype – The terminal type to request from the remote host, e.g. ‘vt100’.
  • verify_fingerprint – Whether to verify the host’s fingerprint.
  • account_factory – A function that produces a new Account.
  • banner_timeout (bool) – The time to wait for the banner.
  • encoding (str) – The encoding of data received from the remote host.
add_monitor(pattern, callback, limit=80)[source]

Calls the given function whenever the given pattern matches the incoming data.

Hint

If you want to catch all incoming data regardless of a pattern, use the Protocol.data_received_event event instead.

Arguments passed to the callback are the protocol instance, the index of the match, and the match object of the regular expression.

Parameters:
  • pattern (str|re.RegexObject|list(str|re.RegexObject)) – One or more regular expressions.
  • callback (callable) – The function that is called.
  • limit (int) – The maximum size of the tail of the buffer that is searched, in number of bytes.
app_authenticate(account=None, flush=True, bailout=False)[source]

Attempt to perform application-level authentication. Application level authentication is needed on devices where the username and password are requested from the user after the connection was already accepted by the remote device.

The difference between app-level authentication and protocol-level authentication is that in the latter case, the prompting is handled by the client, whereas app-level authentication is handled by the remote device.

App-level authentication comes in a large variety of forms, and while this method tries hard to support them all, there is no guarantee that it will always work.

We attempt to smartly recognize the user and password prompts; for a list of supported operating systems please check the Exscript.protocols.drivers module.

Returns upon finding the first command line prompt. Depending on whether the flush argument is True, it also removes the prompt from the incoming buffer.

Parameters:
  • account (Account) – An account object, like login().
  • flush (bool) – Whether to flush the last prompt from the buffer.
  • bailout (bool) – Whether to wait for a prompt after sending the password.
app_authorize(account=None, flush=True, bailout=False)[source]

Like app_authenticate(), but uses the authorization password of the account.

For the difference between authentication and authorization please google for AAA.

Parameters:
  • account (Account) – An account object, like login().
  • flush (bool) – Whether to flush the last prompt from the buffer.
  • bailout (bool) – Whether to wait for a prompt after sending the password.
authenticate(account=None, app_account=None, flush=True)[source]

Like login(), but skips the authorization procedure.

Hint

If you are unsure whether to use authenticate() or login(), stick with login.

Parameters:
  • account (Account) – The account for protocol level authentication.
  • app_account (Account) – The account for app level authentication.
  • flush (bool) – Whether to flush the last prompt from the buffer.
auto_app_authorize(account=None, flush=True, bailout=False)[source]

Like authorize(), but instead of just waiting for a user or password prompt, it automatically initiates the authorization procedure by sending a driver-specific command.

In the case of devices that understand AAA, that means sending a command to the device. For example, on routers running Cisco IOS, this command executes the ‘enable’ command before expecting the password.

In the case of a device that is not recognized to support AAA, this method does nothing.

Parameters:
  • account (Account) – An account object, like login().
  • flush (bool) – Whether to flush the last prompt from the buffer.
  • bailout (bool) – Whether to wait for a prompt after sending the password.
autoinit()[source]

Make the remote host more script-friendly by automatically executing one or more commands on it. The commands executed depend on the currently used driver. For example, the driver for Cisco IOS would execute the following commands:

term len 0
term width 0
cancel_expect()[source]

Cancel the current call to expect() as soon as control returns to the protocol adapter. This method may be used in callbacks to the events emitted by this class, e.g. Protocol.data_received_event.

close(force=False)[source]

Closes the connection with the remote host.

connect(hostname=None, port=None)[source]

Opens the connection to the remote host or IP address.

Parameters:
  • hostname (string) – The remote host or IP address.
  • port (int) – The remote TCP port number.
execute(command, consume=True)[source]

Sends the given data to the remote host (with a newline appended) and waits for a prompt in the response. The prompt attempts to use a sane default that works with many devices running Unix, IOS, IOS-XR, or Junos and others. If that fails, a custom prompt may also be defined using the set_prompt() method. This method also modifies the value of the response (self.response) attribute, for details please see the documentation of the expect() method.

Parameters:
  • command (string) – The data that is sent to the remote host.
  • consume (boolean (Default: True)) – Whether to consume the prompt from the buffer or not.
Return type:

int, re.MatchObject

Returns:

The index of the prompt regular expression that matched, and the match object.

expect(prompt)[source]

Like waitfor(), but also removes the matched string from the buffer containing the incoming data. In other words, the following may not alway complete:

conn.expect('myprompt>')
conn.expect('myprompt>') # timeout

Returns the index of the regular expression that matched.

Hint

May raise the same exceptions as waitfor.

Parameters:prompt (str|re.RegexObject|list(str|re.RegexObject)) – One or more regular expressions.
Return type:int, re.MatchObject
Returns:The index of the regular expression that matched, and the match object.
expect_prompt(consume=True)[source]

Monitors the data received from the remote host and waits for a prompt in the response. The prompt attempts to use a sane default that works with many devices running Unix, IOS, IOS-XR, or Junos and others. If that fails, a custom prompt may also be defined using the set_prompt() method. This method also stores the received data in the response attribute (self.response).

Parameters:consume (boolean (Default: True)) – Whether to consume the prompt from the buffer or not.
Return type:int, re.MatchObject
Returns:The index of the prompt regular expression that matched, and the match object.
get_banner()[source]

Returns the banner that was received upon login. Only supported on SSH2; returns None on all other protocols. Also returns None if the client is not yet connected.

Return type:str|None
Returns:The banner as a string
get_connect_timeout()[source]

Returns the current connect_timeout in seconds.

Return type:int
Returns:The connect_timeout in seconds.
get_driver()[source]

Returns the currently used driver.

Return type:Driver
Returns:A regular expression.
get_error_prompt()[source]

Returns the regular expression that is used to monitor the response of the connected host for errors.

Return type:regex
Returns:A regular expression.
get_host()[source]

Returns the name or address of the currently connected host.

Return type:string
Returns:A name or an address.
get_login_error_prompt()[source]

Returns the regular expression that is used to monitor the response of the connected host for login errors; this is only used during the login procedure, i.e. app_authenticate() or app_authorize().

Return type:regex
Returns:A regular expression.
get_password_prompt()[source]

Returns the regular expression that is used to monitor the response of the connected host for a username prompt.

Return type:regex
Returns:A regular expression.
get_prompt()[source]

Returns the regular expressions that is matched against the host response when calling the expect_prompt() method.

Return type:list(re.RegexObject)
Returns:A list of regular expression objects.
get_remote_version()[source]

Returns the remote version idstring that was received upon login. Only supported on SSH2; returns None on all other protocols. Also returns None if the client is not yet connected.

Return type:str|None
Returns:The idstring.
get_timeout()[source]

Returns the current timeout in seconds.

Return type:int
Returns:The timeout in seconds.
get_username_prompt()[source]

Returns the regular expression that is used to monitor the response of the connected host for a username prompt.

Return type:regex
Returns:A regular expression.
guess_os()[source]

Returns an identifier that specifies the operating system that is running on the remote host. This OS is obtained by watching the response of the remote host, such as any messages retrieved during the login procedure.

The OS is also a wild guess that often depends on volatile information, so there is no guarantee that this will always work.

Return type:string
Returns:A string to help identify the remote operating system.
interact(key_handlers=None, handle_window_size=True)[source]

Opens a simple interactive shell. Returns when the remote host sends EOF. The optional key handlers are functions that are called whenever the user presses a specific key. For example, to catch CTRL+y:

conn.interact({'': mycallback})

Warning

handle_window_size is not supported on Windows platforms.

Parameters:
  • key_handlers (dict(str: callable)) – A dictionary mapping chars to a functions.
  • handle_window_size (bool) – Whether the connected host is notified when the terminal size changes.
is_app_authenticated()[source]

Returns True if the application-level authentication procedure was completed, False otherwise.

Return type:bool
Returns:Whether the authentication was completed.
is_app_authorized()[source]

Returns True if the application-level authorization procedure was completed, False otherwise.

Return type:bool
Returns:Whether the authorization was completed.
is_dummy()[source]

Returns True if the adapter implements a virtual device, i.e. it isn’t an actual network connection.

Return type:Boolean
Returns:True for dummy adapters, False for network adapters.
is_protocol_authenticated()[source]

Returns True if the protocol-level authentication procedure was completed, False otherwise.

Return type:bool
Returns:Whether the authentication was completed.
login(account=None, app_account=None, flush=True)[source]

Log into the connected host using the best method available. If an account is not given, default to the account that was used during the last call to login(). If a previous call was not made, use the account that was passed to the constructor. If that also fails, raise a TypeError.

The app_account is passed to app_authenticate() and app_authorize(). If app_account is not given, default to the value of the account argument.

Parameters:
  • account (Account) – The account for protocol level authentication.
  • app_account (Account) – The account for app level authentication.
  • flush (bool) – Whether to flush the last prompt from the buffer.
protocol_authenticate(account=None)[source]

Low-level API to perform protocol-level authentication on protocols that support it.

Hint

In most cases, you want to use the login() method instead, as it automatically chooses the best login method for each protocol.

Parameters:account (Account) – An account object, like login().
send(data)[source]

Sends the given data to the remote host. Returns without waiting for a response.

Parameters:data (string) – The data that is sent to the remote host.
Return type:Boolean
Returns:True on success, False otherwise.
set_connect_timeout(timeout)[source]

Defines the maximum time that the adapter waits for initial connection.

Parameters:timeout (int) – The maximum time in seconds.
set_driver(driver=None)[source]

Defines the driver that is used to recognize prompts and implement behavior depending on the remote system. The driver argument may be an instance of a protocols.drivers.Driver subclass, a known driver name (string), or None. If the driver argument is None, the adapter automatically chooses a driver using the guess_os() function.

Parameters:driver (Driver()|str) – The pattern that, when matched, causes an error.
set_error_prompt(error=None)[source]

Defines a pattern that is used to monitor the response of the connected host. If the pattern matches (any time the expect() or expect_prompt() methods are used), an error is raised.

Parameters:error (RegEx) – The pattern that, when matched, causes an error.
set_login_error_prompt(error=None)[source]

Defines a pattern that is used to monitor the response of the connected host during the authentication procedure. If the pattern matches an error is raised.

Parameters:error (RegEx) – The pattern that, when matched, causes an error.
set_password_prompt(regex=None)[source]

Defines a pattern that is used to monitor the response of the connected host for a password prompt.

Parameters:regex (RegEx) – The pattern that, when matched, causes an error.
set_prompt(prompt=None)[source]

Defines a pattern that is waited for when calling the expect_prompt() method. If the set_prompt() method is not called, or if it is called with the prompt argument set to None, a default prompt is used that should work with many devices running Unix, IOS, IOS-XR, or Junos and others.

Parameters:prompt (RegEx) – The pattern that matches the prompt of the remote host.
set_timeout(timeout)[source]

Defines the maximum time that the adapter waits before a call to expect() or expect_prompt() fails.

Parameters:timeout (int) – The maximum time in seconds.
set_username_prompt(regex=None)[source]

Defines a pattern that is used to monitor the response of the connected host for a username prompt.

Parameters:regex (RegEx) – The pattern that, when matched, causes an error.
waitfor(prompt)[source]

Monitors the data received from the remote host and waits until the response matches the given prompt. Once a match has been found, the buffer containing incoming data is NOT changed. In other words, consecutive calls to this function will always work, e.g.:

conn.waitfor('myprompt>')
conn.waitfor('myprompt>')
conn.waitfor('myprompt>')

will always work. Hence in most cases, you probably want to use expect() instead.

This method also stores the received data in the response attribute (self.response).

Returns the index of the regular expression that matched.

Parameters:prompt (str|re.RegexObject|list(str|re.RegexObject)) – One or more regular expressions.
Return type:int, re.MatchObject
Returns:The index of the regular expression that matched, and the match object.

@raise TimeoutException: raised if the timeout was reached.

@raise ExpectCancelledException: raised when cancel_expect() was called in a callback.

@raise ProtocolException: on other internal errors.

@raise Exception: May raise other exceptions that are caused within the underlying protocol implementations.