././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1719528567.8639758
netmiko-4.5.0/LICENSE 0000644 0000000 0000000 00000002066 14637366170 011142 0 ustar 00 The MIT License (MIT)
Copyright (c) 2016 Kirk Byers
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1733781006.1549468
netmiko-4.5.0/README.md 0000644 0000000 0000000 00000014542 14725663016 011413 0 ustar 00 [](https://img.shields.io/pypi/pyversions/netmiko)
[](https://pypi.python.org/pypi/netmiko)
[](https://static.pepy.tech/badge/netmiko)
[](https://GitHub.com/ktbyers/netmiko/graphs/contributors/)
[](https://github.com/ambv/black)
Netmiko
=======
Multi-vendor library to simplify CLI connections to network devices
## Contributing to Netmiko
[CONTRIBUTING.md](https://github.com/ktbyers/netmiko/blob/develop/CONTRIBUTING.md)
## Why Netmiko?
Network automation to screen-scraping devices is primarily concerned with gathering output from show commands and with making configuration changes.
Netmiko aims to accomplish both of these operations and to do it across a very broad set of platforms. It seeks to do this while abstracting away low-level state control (i.e. eliminate low-level regex pattern matching to the extent practical).
## Getting Started
- [Getting Started](#getting-started-1)
## Examples
*You really should look here.*
- [Netmiko Examples](https://github.com/ktbyers/netmiko/blob/develop/EXAMPLES.md)
## Supported Platforms
[PLATFORMS](PLATFORMS.md)
## Installation
To install netmiko, simply use pip:
```
$ pip install netmiko
```
## API-Documentation
[API-Documentation](https://ktbyers.github.io/netmiko/docs/netmiko/index.html)
## Common Issues/FAQ
Answers to some [common questions](COMMON_ISSUES.md)
### Tutorials
- [Netmiko Overview](https://pynet.twb-tech.com/blog/automation/netmiko.html)
- [Secure Copy](https://pynet.twb-tech.com/blog/automation/netmiko-scp.html)
- [Netmiko through SSH Proxy](https://pynet.twb-tech.com/blog/automation/netmiko-proxy.html)
- [Netmiko and TextFSM](https://pynet.twb-tech.com/blog/automation/netmiko-textfsm.html)
- [Netmiko and what constitutes done](https://pynet.twb-tech.com/blog/automation/netmiko-what-is-done.html)
### Getting Started:
#### Create a dictionary representing the device.
Supported device_types can be found in [ssh_dispatcher.py](https://github.com/ktbyers/netmiko/blob/master/netmiko/ssh_dispatcher.py), see CLASS_MAPPER keys.
```py
from netmiko import ConnectHandler
cisco_881 = {
'device_type': 'cisco_ios',
'host': '10.10.10.10',
'username': 'test',
'password': 'password',
'port' : 8022, # optional, defaults to 22
'secret': 'secret', # optional, defaults to ''
}
```
#### Establish an SSH connection to the device by passing in the device dictionary.
```py
net_connect = ConnectHandler(**cisco_881)
```
#### Execute show commands.
```py
output = net_connect.send_command('show ip int brief')
print(output)
```
```
Interface IP-Address OK? Method Status Protocol
FastEthernet0 unassigned YES unset down down
FastEthernet1 unassigned YES unset down down
FastEthernet2 unassigned YES unset down down
FastEthernet3 unassigned YES unset down down
FastEthernet4 10.10.10.10 YES manual up up
Vlan1 unassigned YES unset down down
```
#### Execute configuration change commands (will automatically enter into config mode)
```py
config_commands = [ 'logging buffered 20000',
'logging buffered 20010',
'no logging console' ]
output = net_connect.send_config_set(config_commands)
print(output)
```
```
pynet-rtr1#config term
Enter configuration commands, one per line. End with CNTL/Z.
pynet-rtr1(config)#logging buffered 20000
pynet-rtr1(config)#logging buffered 20010
pynet-rtr1(config)#no logging console
pynet-rtr1(config)#end
pynet-rtr1#
```
## API-Documentation
API Documentation
Below are some of the particularly handy Classes/functions for easy reference:
- [Base Connection Object](https://ktbyers.github.io/netmiko/docs/netmiko/base_connection.html)
- [SSH Autodetect](https://ktbyers.github.io/netmiko/docs/netmiko/index.html#netmiko.SSHDetect)
- [SSH Dispatcher](https://ktbyers.github.io/netmiko/docs/netmiko/index.html#netmiko.ssh_dispatcher)
- [Redispatch](https://ktbyers.github.io/netmiko/docs/netmiko/index.html#netmiko.redispatch)
## Contributing
Contributors are welcome.
You can contribute to Netmiko in a variety of ways: answering questions on Slack (see below in Questions/Discussions), responding to issues, adding to the common issues, reporting/fixing bugs, or even adding your own device type.
Before contributing a new vendor/platform device type, remember that any code added needs to be supported in some fashion. To add a vendor/platform you can follow the outline [here](VENDOR.md). Once you've worked on your first pass of your driver and have it functional, you'll need to include test data in order for it to be merged into develop, you can see the general flow of how to do that [here](TESTING.md).
For all code contributions, please ensure that you have ran `black` against the code or your code will fail the Travis CI build.
## Questions/Discussion
If you find an issue with Netmiko, then you can open an issue on this projects issue page here: [https://github.com/ktbyers/netmiko/issues](https://github.com/ktbyers/netmiko/issues). Please make sure you've read through the common issues and examples prior to opening an issue. Please only open issues for bugs, feature requests, or other topics related to development of Netmiko. If you simply have a question, join us on Slack...
If you have questions or would like to discuss Netmiko, a #netmiko channel exists in [this Slack](https://pynet.slack.com) workspace. To join, use [this invitation](https://join.slack.com/t/pynet/shared_invite/zt-km2k3upf-AkWHY4YEx3sI1R5irMmc7Q). Once you have entered the workspace, then you can join the #netmiko channel.
---
Kirk Byers
Python for Network Engineers
https://pynet.twb-tech.com
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1733781006.2149477
netmiko-4.5.0/netmiko/__init__.py 0000644 0000000 0000000 00000005236 14725663016 013713 0 ustar 00 import sys
__version__ = "4.5.0"
PY_MAJ_VER = 3
PY_MIN_VER = 8
MIN_PYTHON_VER = "3.8"
# Make sure user is using a valid Python version (for Netmiko)
def check_python_version(): # type: ignore
python_snake = "\U0001F40D"
# Use old-school .format() method in case someone tries to use Netmiko with very old Python
msg = """
Netmiko Version {net_ver} requires Python Version {py_ver} or higher.
""".format(
net_ver=__version__, py_ver=MIN_PYTHON_VER
)
if sys.version_info.major != PY_MAJ_VER:
raise ValueError(msg)
elif sys.version_info.minor < PY_MIN_VER:
# Why not :-)
msg = msg.rstrip() + " {snake}\n\n".format(snake=python_snake)
raise ValueError(msg)
check_python_version() # type: ignore
import logging # noqa
# Logging configuration
log = logging.getLogger(__name__)
log.addHandler(logging.NullHandler())
from netmiko.ssh_dispatcher import ConnectHandler # noqa
from netmiko.ssh_dispatcher import TelnetFallback # noqa
from netmiko.ssh_dispatcher import ConnLogOnly # noqa
from netmiko.ssh_dispatcher import ConnUnify # noqa
from netmiko.ssh_dispatcher import ssh_dispatcher # noqa
from netmiko.ssh_dispatcher import redispatch # noqa
from netmiko.ssh_dispatcher import platforms # noqa
from netmiko.ssh_dispatcher import FileTransfer # noqa
from netmiko.scp_handler import SCPConn # noqa
from netmiko.cisco.cisco_ios import InLineTransfer # noqa
from netmiko.exceptions import ( # noqa
NetmikoTimeoutException,
NetMikoTimeoutException,
)
from netmiko.exceptions import ( # noqa
NetmikoAuthenticationException,
NetMikoAuthenticationException,
)
from netmiko.exceptions import ConfigInvalidException # noqa
from netmiko.exceptions import ReadException, ReadTimeout # noqa
from netmiko.exceptions import NetmikoBaseException, ConnectionException # noqa
from netmiko.ssh_autodetect import SSHDetect # noqa
from netmiko.base_connection import BaseConnection # noqa
from netmiko.scp_functions import file_transfer, progress_bar # noqa
# Alternate naming
Netmiko = ConnectHandler
__all__ = (
"ConnectHandler",
"AgnosticHandler",
"ConnLogOnly",
"ConnUnify",
"ssh_dispatcher",
"platforms",
"SCPConn",
"FileTransfer",
"NetmikoBaseException",
"ConnectionException",
"NetmikoTimeoutException",
"NetMikoTimeoutException",
"ConfigInvalidException",
"ReadException",
"ReadTimeout",
"NetmikoAuthenticationException",
"NetMikoAuthenticationException",
"InLineTransfer",
"redispatch",
"SSHDetect",
"BaseConnection",
"Netmiko",
"file_transfer",
"progress_bar",
)
# Cisco cntl-shift-six sequence
CNTL_SHIFT_6 = chr(30)
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1719528567.9239757
netmiko-4.5.0/netmiko/_telnetlib/LICENSE 0000644 0000000 0000000 00000033325 14637366170 014733 0 ustar 00 #
# This license applies to the included telnetlib.py code
# (and not any other part of Netmiko).
#
A. HISTORY OF THE SOFTWARE
==========================
Python was created in the early 1990s by Guido van Rossum at Stichting
Mathematisch Centrum (CWI, see https://www.cwi.nl) in the Netherlands
as a successor of a language called ABC. Guido remains Python's
principal author, although it includes many contributions from others.
In 1995, Guido continued his work on Python at the Corporation for
National Research Initiatives (CNRI, see https://www.cnri.reston.va.us)
in Reston, Virginia where he released several versions of the
software.
In May 2000, Guido and the Python core development team moved to
BeOpen.com to form the BeOpen PythonLabs team. In October of the same
year, the PythonLabs team moved to Digital Creations, which became
Zope Corporation. In 2001, the Python Software Foundation (PSF, see
https://www.python.org/psf/) was formed, a non-profit organization
created specifically to own Python-related Intellectual Property.
Zope Corporation was a sponsoring member of the PSF.
All Python releases are Open Source (see https://opensource.org for
the Open Source Definition). Historically, most, but not all, Python
releases have also been GPL-compatible; the table below summarizes
the various releases.
Release Derived Year Owner GPL-
from compatible? (1)
0.9.0 thru 1.2 1991-1995 CWI yes
1.3 thru 1.5.2 1.2 1995-1999 CNRI yes
1.6 1.5.2 2000 CNRI no
2.0 1.6 2000 BeOpen.com no
1.6.1 1.6 2001 CNRI yes (2)
2.1 2.0+1.6.1 2001 PSF no
2.0.1 2.0+1.6.1 2001 PSF yes
2.1.1 2.1+2.0.1 2001 PSF yes
2.1.2 2.1.1 2002 PSF yes
2.1.3 2.1.2 2002 PSF yes
2.2 and above 2.1.1 2001-now PSF yes
Footnotes:
(1) GPL-compatible doesn't mean that we're distributing Python under
the GPL. All Python licenses, unlike the GPL, let you distribute
a modified version without making your changes open source. The
GPL-compatible licenses make it possible to combine Python with
other software that is released under the GPL; the others don't.
(2) According to Richard Stallman, 1.6.1 is not GPL-compatible,
because its license has a choice of law clause. According to
CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1
is "not incompatible" with the GPL.
Thanks to the many outside volunteers who have worked under Guido's
direction to make these releases possible.
B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON
===============================================================
Python software and documentation are licensed under the
Python Software Foundation License Version 2.
Starting with Python 3.8.6, examples, recipes, and other code in
the documentation are dual licensed under the PSF License Version 2
and the Zero-Clause BSD license.
Some software incorporated into Python is under different licenses.
The licenses are listed with code falling under that license.
PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
--------------------------------------------
1. This LICENSE AGREEMENT is between the Python Software Foundation
("PSF"), and the Individual or Organization ("Licensee") accessing and
otherwise using this software ("Python") in source or binary form and
its associated documentation.
2. Subject to the terms and conditions of this License Agreement, PSF hereby
grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
analyze, test, perform and/or display publicly, prepare derivative works,
distribute, and otherwise use Python alone or in any derivative version,
provided, however, that PSF's License Agreement and PSF's notice of copyright,
i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023 Python Software Foundation;
All Rights Reserved" are retained in Python alone or in any derivative version
prepared by Licensee.
3. In the event Licensee prepares a derivative work that is based on
or incorporates Python or any part thereof, and wants to make
the derivative work available to others as provided herein, then
Licensee hereby agrees to include in any such work a brief summary of
the changes made to Python.
4. PSF is making Python available to Licensee on an "AS IS"
basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
INFRINGE ANY THIRD PARTY RIGHTS.
5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
6. This License Agreement will automatically terminate upon a material
breach of its terms and conditions.
7. Nothing in this License Agreement shall be deemed to create any
relationship of agency, partnership, or joint venture between PSF and
Licensee. This License Agreement does not grant permission to use PSF
trademarks or trade name in a trademark sense to endorse or promote
products or services of Licensee, or any third party.
8. By copying, installing or otherwise using Python, Licensee
agrees to be bound by the terms and conditions of this License
Agreement.
BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0
-------------------------------------------
BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1
1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an
office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the
Individual or Organization ("Licensee") accessing and otherwise using
this software in source or binary form and its associated
documentation ("the Software").
2. Subject to the terms and conditions of this BeOpen Python License
Agreement, BeOpen hereby grants Licensee a non-exclusive,
royalty-free, world-wide license to reproduce, analyze, test, perform
and/or display publicly, prepare derivative works, distribute, and
otherwise use the Software alone or in any derivative version,
provided, however, that the BeOpen Python License is retained in the
Software, alone or in any derivative version prepared by Licensee.
3. BeOpen is making the Software available to Licensee on an "AS IS"
basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT
INFRINGE ANY THIRD PARTY RIGHTS.
4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE
SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS
AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY
DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
5. This License Agreement will automatically terminate upon a material
breach of its terms and conditions.
6. This License Agreement shall be governed by and interpreted in all
respects by the law of the State of California, excluding conflict of
law provisions. Nothing in this License Agreement shall be deemed to
create any relationship of agency, partnership, or joint venture
between BeOpen and Licensee. This License Agreement does not grant
permission to use BeOpen trademarks or trade names in a trademark
sense to endorse or promote products or services of Licensee, or any
third party. As an exception, the "BeOpen Python" logos available at
http://www.pythonlabs.com/logos.html may be used according to the
permissions granted on that web page.
7. By copying, installing or otherwise using the software, Licensee
agrees to be bound by the terms and conditions of this License
Agreement.
CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1
---------------------------------------
1. This LICENSE AGREEMENT is between the Corporation for National
Research Initiatives, having an office at 1895 Preston White Drive,
Reston, VA 20191 ("CNRI"), and the Individual or Organization
("Licensee") accessing and otherwise using Python 1.6.1 software in
source or binary form and its associated documentation.
2. Subject to the terms and conditions of this License Agreement, CNRI
hereby grants Licensee a nonexclusive, royalty-free, world-wide
license to reproduce, analyze, test, perform and/or display publicly,
prepare derivative works, distribute, and otherwise use Python 1.6.1
alone or in any derivative version, provided, however, that CNRI's
License Agreement and CNRI's notice of copyright, i.e., "Copyright (c)
1995-2001 Corporation for National Research Initiatives; All Rights
Reserved" are retained in Python 1.6.1 alone or in any derivative
version prepared by Licensee. Alternately, in lieu of CNRI's License
Agreement, Licensee may substitute the following text (omitting the
quotes): "Python 1.6.1 is made available subject to the terms and
conditions in CNRI's License Agreement. This Agreement together with
Python 1.6.1 may be located on the internet using the following
unique, persistent identifier (known as a handle): 1895.22/1013. This
Agreement may also be obtained from a proxy server on the internet
using the following URL: http://hdl.handle.net/1895.22/1013".
3. In the event Licensee prepares a derivative work that is based on
or incorporates Python 1.6.1 or any part thereof, and wants to make
the derivative work available to others as provided herein, then
Licensee hereby agrees to include in any such work a brief summary of
the changes made to Python 1.6.1.
4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS"
basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT
INFRINGE ANY THIRD PARTY RIGHTS.
5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1,
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
6. This License Agreement will automatically terminate upon a material
breach of its terms and conditions.
7. This License Agreement shall be governed by the federal
intellectual property law of the United States, including without
limitation the federal copyright law, and, to the extent such
U.S. federal law does not apply, by the law of the Commonwealth of
Virginia, excluding Virginia's conflict of law provisions.
Notwithstanding the foregoing, with regard to derivative works based
on Python 1.6.1 that incorporate non-separable material that was
previously distributed under the GNU General Public License (GPL), the
law of the Commonwealth of Virginia shall govern this License
Agreement only as to issues arising under or with respect to
Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this
License Agreement shall be deemed to create any relationship of
agency, partnership, or joint venture between CNRI and Licensee. This
License Agreement does not grant permission to use CNRI trademarks or
trade name in a trademark sense to endorse or promote products or
services of Licensee, or any third party.
8. By clicking on the "ACCEPT" button where indicated, or by copying,
installing or otherwise using Python 1.6.1, Licensee agrees to be
bound by the terms and conditions of this License Agreement.
ACCEPT
CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2
--------------------------------------------------
Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam,
The Netherlands. All rights reserved.
Permission to use, copy, modify, and distribute this software and its
documentation for any purpose and without fee is hereby granted,
provided that the above copyright notice appear in all copies and that
both that copyright notice and this permission notice appear in
supporting documentation, and that the name of Stichting Mathematisch
Centrum or CWI not be used in advertising or publicity pertaining to
distribution of the software without specific, written prior
permission.
STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO
THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE
FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
ZERO-CLAUSE BSD LICENSE FOR CODE IN THE PYTHON DOCUMENTATION
----------------------------------------------------------------------
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1719528567.9239757
netmiko-4.5.0/netmiko/_telnetlib/README.md 0000644 0000000 0000000 00000000644 14637366170 015203 0 ustar 00
From https://github.com/python/cpython/blob/v3.12.4/Lib/telnetlib.py
Tag: v3.12.4
Licensed as per the included LICENSE file in this directory.
All contributions were made by the original contributor as documented
in the referenced github.com repository or as documented otherwise
by telnetlib's history.
Kirk Byers has made no code contributions to this library (well I
removed one deprecation warning for PY3.13)
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1719528567.9239757
netmiko-4.5.0/netmiko/_telnetlib/telnetlib.py 0000644 0000000 0000000 00000055450 14637366170 016265 0 ustar 00 r"""TELNET client class.
Based on RFC 854: TELNET Protocol Specification, by J. Postel and
J. Reynolds
Example:
>>> from telnetlib import Telnet
>>> tn = Telnet('www.python.org', 79) # connect to finger port
>>> tn.write(b'guido\r\n')
>>> print(tn.read_all())
Login Name TTY Idle When Where
guido Guido van Rossum pts/2 snag.cnri.reston..
>>>
Note that read_all() won't read until eof -- it just reads some data
-- but it guarantees to read at least one byte unless EOF is hit.
It is possible to pass a Telnet object to a selector in order to wait until
more data is available. Note that in this case, read_eager() may return b''
even if there was data on the socket, because the protocol negotiation may have
eaten the data. This is why EOFError is needed in some cases to distinguish
between "no data" and "connection closed" (since the socket also appears ready
for reading when it is closed).
To do:
- option negotiation
- timeout should be intrinsic to the connection object instead of an
option on one of the read calls only
"""
# Imported modules
import sys
import socket
import selectors
from time import monotonic as _time
import warnings
# warnings._deprecated(__name__, remove=(3, 13))
__all__ = ["Telnet"]
# Tunable parameters
DEBUGLEVEL = 0
# Telnet protocol defaults
TELNET_PORT = 23
# Telnet protocol characters (don't change)
IAC = bytes([255]) # "Interpret As Command"
DONT = bytes([254])
DO = bytes([253])
WONT = bytes([252])
WILL = bytes([251])
theNULL = bytes([0])
SE = bytes([240]) # Subnegotiation End
NOP = bytes([241]) # No Operation
DM = bytes([242]) # Data Mark
BRK = bytes([243]) # Break
IP = bytes([244]) # Interrupt process
AO = bytes([245]) # Abort output
AYT = bytes([246]) # Are You There
EC = bytes([247]) # Erase Character
EL = bytes([248]) # Erase Line
GA = bytes([249]) # Go Ahead
SB = bytes([250]) # Subnegotiation Begin
# Telnet protocol options code (don't change)
# These ones all come from arpa/telnet.h
BINARY = bytes([0]) # 8-bit data path
ECHO = bytes([1]) # echo
RCP = bytes([2]) # prepare to reconnect
SGA = bytes([3]) # suppress go ahead
NAMS = bytes([4]) # approximate message size
STATUS = bytes([5]) # give status
TM = bytes([6]) # timing mark
RCTE = bytes([7]) # remote controlled transmission and echo
NAOL = bytes([8]) # negotiate about output line width
NAOP = bytes([9]) # negotiate about output page size
NAOCRD = bytes([10]) # negotiate about CR disposition
NAOHTS = bytes([11]) # negotiate about horizontal tabstops
NAOHTD = bytes([12]) # negotiate about horizontal tab disposition
NAOFFD = bytes([13]) # negotiate about formfeed disposition
NAOVTS = bytes([14]) # negotiate about vertical tab stops
NAOVTD = bytes([15]) # negotiate about vertical tab disposition
NAOLFD = bytes([16]) # negotiate about output LF disposition
XASCII = bytes([17]) # extended ascii character set
LOGOUT = bytes([18]) # force logout
BM = bytes([19]) # byte macro
DET = bytes([20]) # data entry terminal
SUPDUP = bytes([21]) # supdup protocol
SUPDUPOUTPUT = bytes([22]) # supdup output
SNDLOC = bytes([23]) # send location
TTYPE = bytes([24]) # terminal type
EOR = bytes([25]) # end or record
TUID = bytes([26]) # TACACS user identification
OUTMRK = bytes([27]) # output marking
TTYLOC = bytes([28]) # terminal location number
VT3270REGIME = bytes([29]) # 3270 regime
X3PAD = bytes([30]) # X.3 PAD
NAWS = bytes([31]) # window size
TSPEED = bytes([32]) # terminal speed
LFLOW = bytes([33]) # remote flow control
LINEMODE = bytes([34]) # Linemode option
XDISPLOC = bytes([35]) # X Display Location
OLD_ENVIRON = bytes([36]) # Old - Environment variables
AUTHENTICATION = bytes([37]) # Authenticate
ENCRYPT = bytes([38]) # Encryption option
NEW_ENVIRON = bytes([39]) # New - Environment variables
# the following ones come from
# http://www.iana.org/assignments/telnet-options
# Unfortunately, that document does not assign identifiers
# to all of them, so we are making them up
TN3270E = bytes([40]) # TN3270E
XAUTH = bytes([41]) # XAUTH
CHARSET = bytes([42]) # CHARSET
RSP = bytes([43]) # Telnet Remote Serial Port
COM_PORT_OPTION = bytes([44]) # Com Port Control Option
SUPPRESS_LOCAL_ECHO = bytes([45]) # Telnet Suppress Local Echo
TLS = bytes([46]) # Telnet Start TLS
KERMIT = bytes([47]) # KERMIT
SEND_URL = bytes([48]) # SEND-URL
FORWARD_X = bytes([49]) # FORWARD_X
PRAGMA_LOGON = bytes([138]) # TELOPT PRAGMA LOGON
SSPI_LOGON = bytes([139]) # TELOPT SSPI LOGON
PRAGMA_HEARTBEAT = bytes([140]) # TELOPT PRAGMA HEARTBEAT
EXOPL = bytes([255]) # Extended-Options-List
NOOPT = bytes([0])
# poll/select have the advantage of not requiring any extra file descriptor,
# contrarily to epoll/kqueue (also, they require a single syscall).
if hasattr(selectors, 'PollSelector'):
_TelnetSelector = selectors.PollSelector
else:
_TelnetSelector = selectors.SelectSelector
class Telnet:
"""Telnet interface class.
An instance of this class represents a connection to a telnet
server. The instance is initially not connected; the open()
method must be used to establish a connection. Alternatively, the
host name and optional port number can be passed to the
constructor, too.
Don't try to reopen an already connected instance.
This class has many read_*() methods. Note that some of them
raise EOFError when the end of the connection is read, because
they can return an empty string for other reasons. See the
individual doc strings.
read_until(expected, [timeout])
Read until the expected string has been seen, or a timeout is
hit (default is no timeout); may block.
read_all()
Read all data until EOF; may block.
read_some()
Read at least one byte or EOF; may block.
read_very_eager()
Read all data available already queued or on the socket,
without blocking.
read_eager()
Read either data already queued or some data available on the
socket, without blocking.
read_lazy()
Read all data in the raw queue (processing it first), without
doing any socket I/O.
read_very_lazy()
Reads all data in the cooked queue, without doing any socket
I/O.
read_sb_data()
Reads available data between SB ... SE sequence. Don't block.
set_option_negotiation_callback(callback)
Each time a telnet option is read on the input flow, this callback
(if set) is called with the following parameters :
callback(telnet socket, command, option)
option will be chr(0) when there is no option.
No other action is done afterwards by telnetlib.
"""
sock = None # for __del__()
def __init__(self, host=None, port=0,
timeout=socket._GLOBAL_DEFAULT_TIMEOUT):
"""Constructor.
When called without arguments, create an unconnected instance.
With a hostname argument, it connects the instance; port number
and timeout are optional.
"""
self.debuglevel = DEBUGLEVEL
self.host = host
self.port = port
self.timeout = timeout
self.sock = None
self.rawq = b''
self.irawq = 0
self.cookedq = b''
self.eof = 0
self.iacseq = b'' # Buffer for IAC sequence.
self.sb = 0 # flag for SB and SE sequence.
self.sbdataq = b''
self.option_callback = None
if host is not None:
self.open(host, port, timeout)
def open(self, host, port=0, timeout=socket._GLOBAL_DEFAULT_TIMEOUT):
"""Connect to a host.
The optional second argument is the port number, which
defaults to the standard telnet port (23).
Don't try to reopen an already connected instance.
"""
self.eof = 0
if not port:
port = TELNET_PORT
self.host = host
self.port = port
self.timeout = timeout
sys.audit("telnetlib.Telnet.open", self, host, port)
self.sock = socket.create_connection((host, port), timeout)
def __del__(self):
"""Destructor -- close the connection."""
self.close()
def msg(self, msg, *args):
"""Print a debug message, when the debug level is > 0.
If extra arguments are present, they are substituted in the
message using the standard string formatting operator.
"""
if self.debuglevel > 0:
print('Telnet(%s,%s):' % (self.host, self.port), end=' ')
if args:
print(msg % args)
else:
print(msg)
def set_debuglevel(self, debuglevel):
"""Set the debug level.
The higher it is, the more debug output you get (on sys.stdout).
"""
self.debuglevel = debuglevel
def close(self):
"""Close the connection."""
sock = self.sock
self.sock = None
self.eof = True
self.iacseq = b''
self.sb = 0
if sock:
sock.close()
def get_socket(self):
"""Return the socket object used internally."""
return self.sock
def fileno(self):
"""Return the fileno() of the socket object used internally."""
return self.sock.fileno()
def write(self, buffer):
"""Write a string to the socket, doubling any IAC characters.
Can block if the connection is blocked. May raise
OSError if the connection is closed.
"""
if IAC in buffer:
buffer = buffer.replace(IAC, IAC+IAC)
sys.audit("telnetlib.Telnet.write", self, buffer)
self.msg("send %r", buffer)
self.sock.sendall(buffer)
def read_until(self, match, timeout=None):
"""Read until a given string is encountered or until timeout.
When no match is found, return whatever is available instead,
possibly the empty string. Raise EOFError if the connection
is closed and no cooked data is available.
"""
n = len(match)
self.process_rawq()
i = self.cookedq.find(match)
if i >= 0:
i = i+n
buf = self.cookedq[:i]
self.cookedq = self.cookedq[i:]
return buf
if timeout is not None:
deadline = _time() + timeout
with _TelnetSelector() as selector:
selector.register(self, selectors.EVENT_READ)
while not self.eof:
if selector.select(timeout):
i = max(0, len(self.cookedq)-n)
self.fill_rawq()
self.process_rawq()
i = self.cookedq.find(match, i)
if i >= 0:
i = i+n
buf = self.cookedq[:i]
self.cookedq = self.cookedq[i:]
return buf
if timeout is not None:
timeout = deadline - _time()
if timeout < 0:
break
return self.read_very_lazy()
def read_all(self):
"""Read all data until EOF; block until connection closed."""
self.process_rawq()
while not self.eof:
self.fill_rawq()
self.process_rawq()
buf = self.cookedq
self.cookedq = b''
return buf
def read_some(self):
"""Read at least one byte of cooked data unless EOF is hit.
Return b'' if EOF is hit. Block if no data is immediately
available.
"""
self.process_rawq()
while not self.cookedq and not self.eof:
self.fill_rawq()
self.process_rawq()
buf = self.cookedq
self.cookedq = b''
return buf
def read_very_eager(self):
"""Read everything that's possible without blocking in I/O (eager).
Raise EOFError if connection closed and no cooked data
available. Return b'' if no cooked data available otherwise.
Don't block unless in the midst of an IAC sequence.
"""
self.process_rawq()
while not self.eof and self.sock_avail():
self.fill_rawq()
self.process_rawq()
return self.read_very_lazy()
def read_eager(self):
"""Read readily available data.
Raise EOFError if connection closed and no cooked data
available. Return b'' if no cooked data available otherwise.
Don't block unless in the midst of an IAC sequence.
"""
self.process_rawq()
while not self.cookedq and not self.eof and self.sock_avail():
self.fill_rawq()
self.process_rawq()
return self.read_very_lazy()
def read_lazy(self):
"""Process and return data that's already in the queues (lazy).
Raise EOFError if connection closed and no data available.
Return b'' if no cooked data available otherwise. Don't block
unless in the midst of an IAC sequence.
"""
self.process_rawq()
return self.read_very_lazy()
def read_very_lazy(self):
"""Return any data available in the cooked queue (very lazy).
Raise EOFError if connection closed and no data available.
Return b'' if no cooked data available otherwise. Don't block.
"""
buf = self.cookedq
self.cookedq = b''
if not buf and self.eof and not self.rawq:
raise EOFError('telnet connection closed')
return buf
def read_sb_data(self):
"""Return any data available in the SB ... SE queue.
Return b'' if no SB ... SE available. Should only be called
after seeing a SB or SE command. When a new SB command is
found, old unread SB data will be discarded. Don't block.
"""
buf = self.sbdataq
self.sbdataq = b''
return buf
def set_option_negotiation_callback(self, callback):
"""Provide a callback function called after each receipt of a telnet option."""
self.option_callback = callback
def process_rawq(self):
"""Transfer from raw queue to cooked queue.
Set self.eof when connection is closed. Don't block unless in
the midst of an IAC sequence.
"""
buf = [b'', b'']
try:
while self.rawq:
c = self.rawq_getchar()
if not self.iacseq:
if c == theNULL:
continue
if c == b"\021":
continue
if c != IAC:
buf[self.sb] = buf[self.sb] + c
continue
else:
self.iacseq += c
elif len(self.iacseq) == 1:
# 'IAC: IAC CMD [OPTION only for WILL/WONT/DO/DONT]'
if c in (DO, DONT, WILL, WONT):
self.iacseq += c
continue
self.iacseq = b''
if c == IAC:
buf[self.sb] = buf[self.sb] + c
else:
if c == SB: # SB ... SE start.
self.sb = 1
self.sbdataq = b''
elif c == SE:
self.sb = 0
self.sbdataq = self.sbdataq + buf[1]
buf[1] = b''
if self.option_callback:
# Callback is supposed to look into
# the sbdataq
self.option_callback(self.sock, c, NOOPT)
else:
# We can't offer automatic processing of
# suboptions. Alas, we should not get any
# unless we did a WILL/DO before.
self.msg('IAC %d not recognized' % ord(c))
elif len(self.iacseq) == 2:
cmd = self.iacseq[1:2]
self.iacseq = b''
opt = c
if cmd in (DO, DONT):
self.msg('IAC %s %d',
cmd == DO and 'DO' or 'DONT', ord(opt))
if self.option_callback:
self.option_callback(self.sock, cmd, opt)
else:
self.sock.sendall(IAC + WONT + opt)
elif cmd in (WILL, WONT):
self.msg('IAC %s %d',
cmd == WILL and 'WILL' or 'WONT', ord(opt))
if self.option_callback:
self.option_callback(self.sock, cmd, opt)
else:
self.sock.sendall(IAC + DONT + opt)
except EOFError: # raised by self.rawq_getchar()
self.iacseq = b'' # Reset on EOF
self.sb = 0
self.cookedq = self.cookedq + buf[0]
self.sbdataq = self.sbdataq + buf[1]
def rawq_getchar(self):
"""Get next char from raw queue.
Block if no data is immediately available. Raise EOFError
when connection is closed.
"""
if not self.rawq:
self.fill_rawq()
if self.eof:
raise EOFError
c = self.rawq[self.irawq:self.irawq+1]
self.irawq = self.irawq + 1
if self.irawq >= len(self.rawq):
self.rawq = b''
self.irawq = 0
return c
def fill_rawq(self):
"""Fill raw queue from exactly one recv() system call.
Block if no data is immediately available. Set self.eof when
connection is closed.
"""
if self.irawq >= len(self.rawq):
self.rawq = b''
self.irawq = 0
# The buffer size should be fairly small so as to avoid quadratic
# behavior in process_rawq() above
buf = self.sock.recv(50)
self.msg("recv %r", buf)
self.eof = (not buf)
self.rawq = self.rawq + buf
def sock_avail(self):
"""Test whether data is available on the socket."""
with _TelnetSelector() as selector:
selector.register(self, selectors.EVENT_READ)
return bool(selector.select(0))
def interact(self):
"""Interaction function, emulates a very dumb telnet client."""
if sys.platform == "win32":
self.mt_interact()
return
with _TelnetSelector() as selector:
selector.register(self, selectors.EVENT_READ)
selector.register(sys.stdin, selectors.EVENT_READ)
while True:
for key, events in selector.select():
if key.fileobj is self:
try:
text = self.read_eager()
except EOFError:
print('*** Connection closed by remote host ***')
return
if text:
sys.stdout.write(text.decode('ascii'))
sys.stdout.flush()
elif key.fileobj is sys.stdin:
line = sys.stdin.readline().encode('ascii')
if not line:
return
self.write(line)
def mt_interact(self):
"""Multithreaded version of interact()."""
import _thread
_thread.start_new_thread(self.listener, ())
while 1:
line = sys.stdin.readline()
if not line:
break
self.write(line.encode('ascii'))
def listener(self):
"""Helper for mt_interact() -- this executes in the other thread."""
while 1:
try:
data = self.read_eager()
except EOFError:
print('*** Connection closed by remote host ***')
return
if data:
sys.stdout.write(data.decode('ascii'))
else:
sys.stdout.flush()
def expect(self, list, timeout=None):
"""Read until one from a list of a regular expressions matches.
The first argument is a list of regular expressions, either
compiled (re.Pattern instances) or uncompiled (strings).
The optional second argument is a timeout, in seconds; default
is no timeout.
Return a tuple of three items: the index in the list of the
first regular expression that matches; the re.Match object
returned; and the text read up till and including the match.
If EOF is read and no text was read, raise EOFError.
Otherwise, when nothing matches, return (-1, None, text) where
text is the text received so far (may be the empty string if a
timeout happened).
If a regular expression ends with a greedy match (e.g. '.*')
or if more than one expression can match the same input, the
results are undeterministic, and may depend on the I/O timing.
"""
re = None
list = list[:]
indices = range(len(list))
for i in indices:
if not hasattr(list[i], "search"):
if not re: import re
list[i] = re.compile(list[i])
if timeout is not None:
deadline = _time() + timeout
with _TelnetSelector() as selector:
selector.register(self, selectors.EVENT_READ)
while not self.eof:
self.process_rawq()
for i in indices:
m = list[i].search(self.cookedq)
if m:
e = m.end()
text = self.cookedq[:e]
self.cookedq = self.cookedq[e:]
return (i, m, text)
if timeout is not None:
ready = selector.select(timeout)
timeout = deadline - _time()
if not ready:
if timeout < 0:
break
else:
continue
self.fill_rawq()
text = self.read_very_lazy()
if not text and self.eof:
raise EOFError
return (-1, None, text)
def __enter__(self):
return self
def __exit__(self, type, value, traceback):
self.close()
def test():
"""Test program for telnetlib.
Usage: python telnetlib.py [-d] ... [host [port]]
Default host is localhost; default port is 23.
"""
debuglevel = 0
while sys.argv[1:] and sys.argv[1] == '-d':
debuglevel = debuglevel+1
del sys.argv[1]
host = 'localhost'
if sys.argv[1:]:
host = sys.argv[1]
port = 0
if sys.argv[2:]:
portstr = sys.argv[2]
try:
port = int(portstr)
except ValueError:
port = socket.getservbyname(portstr, 'tcp')
with Telnet() as tn:
tn.set_debuglevel(debuglevel)
tn.open(host, port, timeout=0.5)
tn.interact()
if __name__ == '__main__':
test()
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1719528567.9239757
netmiko-4.5.0/netmiko/a10/__init__.py 0000644 0000000 0000000 00000000075 14637366170 014273 0 ustar 00 from netmiko.a10.a10_ssh import A10SSH
__all__ = ["A10SSH"]
././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1725389754.726303
netmiko-4.5.0/netmiko/a10/a10_ssh.py 0000644 0000000 0000000 00000001271 14665655673 014003 0 ustar 00 """A10 support."""
from netmiko.cisco_base_connection import CiscoSSHConnection
class A10SSH(CiscoSSHConnection):
"""A10 support."""
def session_preparation(self) -> None:
"""A10 requires to be enable mode to disable paging."""
self._test_channel_read(pattern=r"[>#]")
self.set_base_prompt()
self.enable()
# terminal width ill not do anything without A10 specific command
# self.set_terminal_width()
self.disable_paging(command="terminal length 0")
def save_config(
self, cmd: str = "", confirm: bool = False, confirm_response: str = ""
) -> str:
"""Not Implemented"""
raise NotImplementedError
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1719528567.9239757
netmiko-4.5.0/netmiko/accedian/__init__.py 0000644 0000000 0000000 00000000121 14637366170 015431 0 ustar 00 from netmiko.accedian.accedian_ssh import AccedianSSH
__all__ = ["AccedianSSH"]
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1719528567.9239757
netmiko-4.5.0/netmiko/accedian/accedian_ssh.py 0000644 0000000 0000000 00000002112 14637366170 016300 0 ustar 00 from typing import Optional
from netmiko.no_enable import NoEnable
from netmiko.no_config import NoConfig
from netmiko.cisco_base_connection import CiscoSSHConnection
class AccedianSSH(NoEnable, NoConfig, CiscoSSHConnection):
def session_preparation(self) -> None:
self._test_channel_read(pattern=r"[:#]")
self.set_base_prompt()
def set_base_prompt(
self,
pri_prompt_terminator: str = ":",
alt_prompt_terminator: str = "#",
delay_factor: float = 2.0,
pattern: Optional[str] = None,
) -> str:
"""Sets self.base_prompt: used as delimiter for stripping of trailing prompt in output."""
super().set_base_prompt(
pri_prompt_terminator=pri_prompt_terminator,
alt_prompt_terminator=alt_prompt_terminator,
delay_factor=delay_factor,
pattern=pattern,
)
return self.base_prompt
def save_config(
self, cmd: str = "", confirm: bool = False, confirm_response: str = ""
) -> str:
"""Not Implemented"""
raise NotImplementedError
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1719528567.9239757
netmiko-4.5.0/netmiko/adtran/__init__.py 0000644 0000000 0000000 00000000153 14637366170 015160 0 ustar 00 from netmiko.adtran.adtran import AdtranOSSSH, AdtranOSTelnet
__all__ = ["AdtranOSSSH", "AdtranOSTelnet"]
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1719528567.9239757
netmiko-4.5.0/netmiko/adtran/adtran.py 0000644 0000000 0000000 00000010433 14637366170 014674 0 ustar 00 from typing import Any, Optional
import re
from netmiko.cisco_base_connection import CiscoBaseConnection
from netmiko.exceptions import NetmikoTimeoutException
class AdtranOSBase(CiscoBaseConnection):
prompt_pattern = r"[>#]"
def session_preparation(self) -> None:
"""Prepare the session after the connection has been established."""
self.ansi_escape_codes = True
self._test_channel_read(pattern=self.prompt_pattern)
self.set_base_prompt()
self.disable_paging(command="terminal length 0")
cmd = "terminal width 132"
self.set_terminal_width(command=cmd, pattern=cmd)
def check_enable_mode(self, check_string: str = "#") -> bool:
return super().check_enable_mode(check_string=check_string)
def enable(
self,
cmd: str = "enable",
pattern: str = "ssword",
enable_pattern: Optional[str] = None,
check_state: bool = True,
re_flags: int = re.IGNORECASE,
) -> str:
output = ""
msg = (
"Failed to enter enable mode. Please ensure you pass "
"the 'secret' argument to ConnectHandler."
)
# Check if in enable mode already.
if check_state and self.check_enable_mode():
return output
# Send "enable" mode command
self.write_channel(self.normalize_cmd(cmd))
try:
# Read the command echo
if self.global_cmd_verify is not False:
output += self.read_until_pattern(pattern=re.escape(cmd.strip()))
# Search for trailing prompt or password pattern
output += self.read_until_prompt_or_pattern(
pattern=pattern, re_flags=re_flags
)
# Send the "secret" in response to password pattern
if re.search(pattern, output):
self.write_channel(self.normalize_cmd(self.secret))
# Handle the fallback to local authentication case
fallback_pattern = r"Falling back"
new_output = self.read_until_prompt_or_pattern(
pattern=fallback_pattern, re_flags=re_flags
)
output += new_output
if "Falling back" in new_output:
self.write_channel(self.normalize_cmd(self.secret))
output += self.read_until_prompt()
# Search for terminating pattern if defined
if enable_pattern and not re.search(enable_pattern, output):
output += self.read_until_pattern(pattern=enable_pattern)
else:
if not self.check_enable_mode():
raise ValueError(msg)
except NetmikoTimeoutException:
raise ValueError(msg)
return output
def exit_enable_mode(self, exit_command: str = "disable") -> str:
return super().exit_enable_mode(exit_command=exit_command)
def check_config_mode(
self, check_string: str = ")#", pattern: str = "", force_regex: bool = False
) -> bool:
return super().check_config_mode(check_string=check_string, pattern=pattern)
def config_mode(
self, config_command: str = "config term", pattern: str = "", re_flags: int = 0
) -> str:
"""Enter configuration mode."""
return super().config_mode(
config_command=config_command, pattern=pattern, re_flags=re_flags
)
def exit_config_mode(self, exit_config: str = "end", pattern: str = "#") -> str:
return super().exit_config_mode(exit_config=exit_config, pattern=pattern)
def set_base_prompt(
self,
pri_prompt_terminator: str = ">",
alt_prompt_terminator: str = "#",
delay_factor: float = 1.0,
pattern: Optional[str] = None,
) -> str:
return super().set_base_prompt(
pri_prompt_terminator=pri_prompt_terminator,
alt_prompt_terminator=alt_prompt_terminator,
delay_factor=delay_factor,
pattern=pattern,
)
class AdtranOSSSH(AdtranOSBase):
pass
class AdtranOSTelnet(AdtranOSBase):
def __init__(self, *args: Any, **kwargs: Any) -> None:
default_enter = kwargs.get("default_enter")
kwargs["default_enter"] = "\r\n" if default_enter is None else default_enter
super().__init__(*args, **kwargs)
././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1725389754.726303
netmiko-4.5.0/netmiko/adva/__init__.py 0000644 0000000 0000000 00000000336 14665655673 014637 0 ustar 00 """Adva Device Drivers"""
from netmiko.adva.adva_aos_fsp_150_f2 import AdvaAosFsp150F2SSH
from netmiko.adva.adva_aos_fsp_150_f3 import AdvaAosFsp150F3SSH
__all__ = [
"AdvaAosFsp150F2SSH",
"AdvaAosFsp150F3SSH",
]
././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1725389754.726303
netmiko-4.5.0/netmiko/adva/adva_aos_fsp_150_f2.py 0000644 0000000 0000000 00000004024 14665655673 016477 0 ustar 00 """Adva support."""
import re
from typing import Any, Optional
from netmiko.no_enable import NoEnable
from netmiko.no_config import NoConfig
from netmiko.cisco_base_connection import CiscoSSHConnection
class AdvaAosFsp150F2SSH(NoEnable, NoConfig, CiscoSSHConnection):
"""
Adva AOS FSP 15P F2 SSH Base Class
F2 AOS applies for the following FSP150 device types: FSP150CC-825
These devices don't have an Enable Mode or a Config Mode.
Configuration Should be applied via the configuration context:
home
configure snmp
add v3user guytest noauth-nopriv
home
configure system
home
Use of home to return to CLI root context, home cannot be used from root
LAB-R2-825-1:--> home
Unrecognized command
"""
def __init__(self, **kwargs: Any) -> None:
"""
\n for default enter causes some issues with the Adva so setting to \r.
"""
if kwargs.get("default_enter") is None:
kwargs["default_enter"] = "\r"
return super().__init__(**kwargs)
def session_preparation(self) -> None:
"""
Prepare the session after the connection has been established.
Handles devices with security prompt enabled
"""
data = self.read_until_pattern(
pattern=r"Do you wish to continue \[Y\|N\]-->|-->"
)
if "continue" in data:
self.write_channel(f"y{self.RETURN}")
else:
self.write_channel(f"help?{self.RETURN}")
data = self.read_until_pattern(pattern=r"-->")
self.set_base_prompt()
def set_base_prompt(
self,
pri_prompt_terminator: str = r"(^.+?)-->$",
alt_prompt_terminator: str = "",
delay_factor: float = 1.0,
pattern: Optional[str] = None,
) -> str:
prompt = self.find_prompt()
match = re.search(pri_prompt_terminator, prompt)
if not match:
raise ValueError(f"Router prompt not found: {repr(prompt)}")
self.base_prompt = match[1]
return self.base_prompt
././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1725389754.726303
netmiko-4.5.0/netmiko/adva/adva_aos_fsp_150_f3.py 0000644 0000000 0000000 00000010445 14665655673 016504 0 ustar 00 """Adva F3 Device Support"""
import re
from typing import (
Optional,
Sequence,
TextIO,
Iterator,
Union,
Any,
)
from netmiko.no_enable import NoEnable
from netmiko.no_config import NoConfig
from netmiko.cisco_base_connection import CiscoSSHConnection
class AdvaAosFsp150F3SSH(NoEnable, NoConfig, CiscoSSHConnection):
"""
Adva AOS FSP 15P F3 SSH Base Class
F3 AOS applies for the following FSP150 device types:
FSP150CC-XG21x
FSP150CC-GE11x
FSP150CC-GE20x
These devices don't have a Enable Mode or Config Mode
Configuration should be applied via the configuration context:
home
configure communication
add ip-route nexthop xxxxxxx
#
#CLI:PORT N2A SHAPER-1-1-1-3-0 Create
#
home
network-element ne-1
Use of home to return to CLI root context
"""
def __init__(self, **kwargs: Any) -> None:
"""
\n for default enter causes some issues with the Adva so setting to \r.
"""
if kwargs.get("default_enter") is None:
kwargs["default_enter"] = "\r"
return super().__init__(**kwargs)
def session_preparation(self) -> None:
"""
Prepare the session after the connection has been established.
Handles devices with security prompt enabled
"""
data = self.read_until_pattern(
pattern=r"Do you wish to continue \[Y\|N\]-->|-->"
)
if "continue" in data:
self.write_channel(f"y{self.RETURN}")
else:
self.write_channel(f"home{self.RETURN}")
data = self.read_until_pattern(pattern=r"-->")
self.set_base_prompt()
self.disable_paging(cmd_verify=False)
def disable_paging(
self,
command: str = "",
delay_factor: Optional[float] = None,
cmd_verify: bool = True,
pattern: Optional[str] = None,
) -> str:
"""Method to disable paging on the Adva, multi-line configuration command required."""
if command:
raise ValueError(
f"Unexpected value for command in disable_paging() method: {command}"
)
commands = [
"configure user-security",
f"config-user {self.username} cli-paging disabled",
"home",
]
return self.send_config_set(
commands, delay_factor=delay_factor, cmd_verify=cmd_verify
)
def set_base_prompt(
self,
pri_prompt_terminator: str = r"(^.+?)-->$",
alt_prompt_terminator: str = "",
delay_factor: float = 1.0,
pattern: Optional[str] = None,
) -> str:
prompt = self.find_prompt()
match = re.search(pri_prompt_terminator, prompt)
if not match:
raise ValueError(f"Router prompt not found: {repr(prompt)}")
self.base_prompt = match[1]
return self.base_prompt
def send_config_set(
self,
config_commands: Union[str, Sequence[str], Iterator[str], TextIO, None] = None,
*,
exit_config_mode: bool = True,
read_timeout: Optional[float] = 2.0,
delay_factor: Optional[float] = None,
max_loops: Optional[int] = None,
strip_prompt: bool = False,
strip_command: bool = False,
config_mode_command: Optional[str] = None,
cmd_verify: bool = True,
enter_config_mode: bool = True,
error_pattern: str = "",
terminator: str = r"#",
bypass_commands: Optional[str] = None,
) -> str:
if bypass_commands is None:
categories = (
r"(?:superuser|crypto|maintenance|provisioning|retrieve|test-user)"
)
bypass_commands = rf"(?:add\s+\S+\s+\S+\s+\S+\s+{categories}|secret.*)"
return super().send_config_set(
config_commands=config_commands,
exit_config_mode=exit_config_mode,
read_timeout=read_timeout,
delay_factor=delay_factor,
max_loops=max_loops,
strip_prompt=strip_prompt,
strip_command=strip_command,
config_mode_command=config_mode_command,
cmd_verify=cmd_verify,
enter_config_mode=enter_config_mode,
error_pattern=error_pattern,
terminator=terminator,
bypass_commands=bypass_commands,
)
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1733781006.2149477
netmiko-4.5.0/netmiko/alaxala/__init__.py 0000644 0000000 0000000 00000000131 14725663016 015303 0 ustar 00 from netmiko.alaxala.alaxala_ax36s import AlaxalaAx36sSSH
__all__ = ["AlaxalaAx36sSSH"]
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1733781006.2149477
netmiko-4.5.0/netmiko/alaxala/alaxala_ax36s.py 0000644 0000000 0000000 00000005266 14725663016 016211 0 ustar 00 import time
from typing import Optional
from netmiko.cisco_base_connection import CiscoSSHConnection
class AlaxalaAx36sBase(CiscoSSHConnection):
def session_preparation(self) -> None:
"""Prepare the session after the connection has been established."""
self._test_channel_read(pattern=r"[>#]")
self.set_base_prompt()
time.sleep(0.3 * self.global_delay_factor)
self.disable_paging(command="set terminal pager disable")
def set_base_prompt(
self,
pri_prompt_terminator: str = "#",
alt_prompt_terminator: str = ">",
delay_factor: float = 1.0,
pattern: Optional[str] = None,
) -> str:
base_prompt = super().set_base_prompt(
pri_prompt_terminator=pri_prompt_terminator,
alt_prompt_terminator=alt_prompt_terminator,
delay_factor=delay_factor,
pattern=pattern,
)
self.base_prompt = base_prompt[1:]
return self.base_prompt
def exit_config_mode(self, exit_command: str = "end", pattern: str = "") -> str:
"""
If there are unsaved configuration changes, the prompt is
"Unsaved changes found! Do you exit "configure" without save ? (y/n):" is output.
enter "y" to exit configure mode.
"""
output = ""
if self.check_config_mode():
self.write_channel(self.normalize_cmd(exit_command))
time.sleep(1)
output = self.read_channel()
if "(y/n)" in output:
self.write_channel("y\n")
if self.base_prompt not in output:
output += self.read_until_prompt(read_entire_line=True)
if self.check_config_mode():
raise ValueError("Failed to exit config mode.")
return output
def save_config(
self,
cmd: str = "write",
confirm: bool = False,
confirm_response: str = "",
) -> str:
"""
"save_config" must be executed in config mode.
if the configuration change is not saved,
a "!" will appear at the beginning of the prompt.
"""
output = ""
if not self.check_config_mode():
self.config_mode()
output = self._send_command_timing_str(
command_string=cmd, strip_prompt=False, strip_command=False
)
output += self._send_command_timing_str(
self.RETURN, strip_prompt=False, strip_command=False
)
self.exit_config_mode()
if self.base_prompt not in output:
output += self.read_until_prompt(read_entire_line=True)
return output
class AlaxalaAx36sSSH(AlaxalaAx36sBase):
"""AlaxalA AX36S SSH driver."""
pass
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1719528567.9239757
netmiko-4.5.0/netmiko/alcatel/__init__.py 0000644 0000000 0000000 00000000127 14637366170 015315 0 ustar 00 from netmiko.alcatel.alcatel_aos_ssh import AlcatelAosSSH
__all__ = ["AlcatelAosSSH"]
././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1725389754.726303
netmiko-4.5.0/netmiko/alcatel/alcatel_aos_ssh.py 0000644 0000000 0000000 00000001505 14665655673 016715 0 ustar 00 """Alcatel-Lucent Enterprise AOS support (AOS6 and AOS8)."""
from netmiko.no_enable import NoEnable
from netmiko.no_config import NoConfig
from netmiko.cisco_base_connection import CiscoSSHConnection
class AlcatelAosSSH(NoEnable, NoConfig, CiscoSSHConnection):
"""Alcatel-Lucent Enterprise AOS support (AOS6 and AOS8)."""
def session_preparation(self) -> None:
# Prompt can be anything, but best practice is to end with > or #
self._test_channel_read(pattern=r"[>#]")
self.set_base_prompt()
def save_config(
self,
cmd: str = "write memory flash-synchro",
confirm: bool = False,
confirm_response: str = "",
) -> str:
"""Save Config"""
return super().save_config(
cmd=cmd, confirm=confirm, confirm_response=confirm_response
)
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1719528567.9239757
netmiko-4.5.0/netmiko/allied_telesis/__init__.py 0000644 0000000 0000000 00000000166 14637366170 016675 0 ustar 00 from netmiko.allied_telesis.allied_telesis_awplus import AlliedTelesisAwplusSSH
__all__ = ["AlliedTelesisAwplusSSH"]
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1719528567.9239757
netmiko-4.5.0/netmiko/allied_telesis/allied_telesis_awplus.py 0000644 0000000 0000000 00000003107 14637366170 021511 0 ustar 00 from netmiko.cisco_base_connection import CiscoBaseConnection
import time
class AlliedTelesisAwplusBase(CiscoBaseConnection):
"""Implement methods for interacting with Allied Telesis devices."""
def session_preparation(self) -> None:
"""
Prepare the session after the connection has been established.
Disable paging (the '--more--' prompts).
Set the base prompt for interaction ('>').
"""
""" AWPlus Configuration """
self.disable_paging()
self.set_base_prompt()
time.sleep(0.3 * self.global_delay_factor)
def _enter_shell(self) -> str:
"""Enter the Bourne Shell."""
return self._send_command_str("start shell sh", expect_string=r"[\$#]")
def _return_cli(self) -> str:
"""Return to the Awplus CLI."""
return self._send_command_str("exit", expect_string=r"[#>]")
def exit_config_mode(self, exit_config: str = "exit", pattern: str = "") -> str:
"""Exit configuration mode."""
output = ""
if self.check_config_mode():
output += self._send_command_timing_str(
exit_config, strip_prompt=False, strip_command=False
)
if "Exit with uncommitted changes?" in output:
output += self._send_command_timing_str(
"yes", strip_prompt=False, strip_command=False
)
if self.check_config_mode():
raise ValueError("Failed to exit configuration mode")
return output
class AlliedTelesisAwplusSSH(AlliedTelesisAwplusBase):
pass
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1719528567.9239757
netmiko-4.5.0/netmiko/apresia/__init__.py 0000644 0000000 0000000 00000000176 14637366170 015340 0 ustar 00 from netmiko.apresia.apresia_aeos import ApresiaAeosSSH, ApresiaAeosTelnet
__all__ = ["ApresiaAeosSSH", "ApresiaAeosTelnet"]
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1719528567.9239757
netmiko-4.5.0/netmiko/apresia/apresia_aeos.py 0000644 0000000 0000000 00000002601 14637366170 016227 0 ustar 00 from typing import Any, Optional
from netmiko.cisco_base_connection import CiscoSSHConnection
class ApresiaAeosBase(CiscoSSHConnection):
def session_preparation(self) -> None:
"""Prepare the session after the connection has been established."""
self._test_channel_read(pattern=r"[>#]")
self.set_base_prompt()
self.disable_paging()
def disable_paging(
self,
command: str = "terminal length 0",
delay_factor: Optional[float] = None,
cmd_verify: bool = True,
pattern: Optional[str] = None,
) -> str:
self.enable()
check_command = f"show running-config | include {command}"
show_run = self._send_command_str(check_command)
output = ""
if self.allow_auto_change and command not in show_run:
output += super().disable_paging(
command=command,
delay_factor=delay_factor,
cmd_verify=cmd_verify,
pattern=pattern,
)
self.exit_enable_mode()
return output
class ApresiaAeosSSH(ApresiaAeosBase):
pass
class ApresiaAeosTelnet(ApresiaAeosBase):
def __init__(self, *args: Any, **kwargs: Any) -> None:
default_enter = kwargs.get("default_enter")
kwargs["default_enter"] = "\r\n" if default_enter is None else default_enter
super().__init__(*args, **kwargs)
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1719528567.9239757
netmiko-4.5.0/netmiko/arista/__init__.py 0000644 0000000 0000000 00000000215 14637366170 015171 0 ustar 00 from netmiko.arista.arista import AristaSSH, AristaTelnet, AristaFileTransfer
__all__ = ["AristaSSH", "AristaTelnet", "AristaFileTransfer"]
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1733781006.2149477
netmiko-4.5.0/netmiko/arista/arista.py 0000644 0000000 0000000 00000012766 14725663016 014730 0 ustar 00 from typing import Any, Optional, Union, Sequence
from typing import TYPE_CHECKING
import re
from netmiko.cisco_base_connection import CiscoSSHConnection
from netmiko.cisco_base_connection import CiscoFileTransfer
if TYPE_CHECKING:
from netmiko.base_connection import BaseConnection
class AristaBase(CiscoSSHConnection):
prompt_pattern = r"[$>#]"
def session_preparation(self) -> None:
"""Prepare the session after the connection has been established."""
self.ansi_escape_codes = True
self._test_channel_read(pattern=self.prompt_pattern)
cmd = "terminal width 511"
self.set_terminal_width(command=cmd, pattern=r"Width set to")
self.disable_paging(cmd_verify=False, pattern=r"Pagination disabled")
self.set_base_prompt()
def find_prompt(
self, delay_factor: float = 1.0, pattern: Optional[str] = None
) -> str:
"""
Arista's sometimes duplicate the command echo if they fall behind.
arista9-napalm#
show version | json
arista9-napalm#show version | json
Using the terminating pattern tries to ensure that it is less likely they
fall behind.
"""
if not pattern:
pattern = self.prompt_pattern
return super().find_prompt(delay_factor=delay_factor, pattern=pattern)
def enable(
self,
cmd: str = "enable",
pattern: str = "ssword",
enable_pattern: Optional[str] = r"\#",
check_state: bool = True,
re_flags: int = re.IGNORECASE,
) -> str:
return super().enable(
cmd=cmd,
pattern=pattern,
enable_pattern=enable_pattern,
check_state=check_state,
re_flags=re_flags,
)
def check_config_mode(
self,
check_string: str = ")#",
pattern: str = r"[>\#]",
force_regex: bool = False,
) -> bool:
"""
Checks if the device is in configuration mode or not.
Arista, unfortunately, does this:
loc1-core01(s1)#
Can also be (s2)
"""
self.write_channel(self.RETURN)
output = self.read_until_pattern(pattern=pattern)
output = output.replace("(s1)", "")
output = output.replace("(s2)", "")
return check_string in output
def config_mode(
self,
config_command: str = "configure terminal",
pattern: str = "",
re_flags: int = 0,
) -> str:
"""Force arista to read pattern all the way to prompt on the next line."""
if not re_flags:
re_flags = re.DOTALL
check_string = re.escape(")#")
if not pattern:
pattern = re.escape(self.base_prompt[:16])
pattern = f"{pattern}.*{check_string}"
return super().config_mode(
config_command=config_command, pattern=pattern, re_flags=re_flags
)
def _enter_shell(self) -> str:
"""Enter the Bourne Shell."""
output = self._send_command_str("bash", expect_string=r"[\$#]")
return output
def _return_cli(self) -> str:
"""Return to the CLI."""
output = self._send_command_str("exit", expect_string=r"[#>]")
return output
class AristaSSH(AristaBase):
pass
class AristaTelnet(AristaBase):
def __init__(self, *args: Any, **kwargs: Any) -> None:
default_enter = kwargs.get("default_enter")
kwargs["default_enter"] = "\r\n" if default_enter is None else default_enter
super().__init__(*args, **kwargs)
class AristaFileTransfer(CiscoFileTransfer):
"""Arista SCP File Transfer driver."""
def __init__(
self,
ssh_conn: "BaseConnection",
source_file: str,
dest_file: str,
file_system: Optional[str] = "/mnt/flash",
direction: str = "put",
**kwargs: Any,
) -> None:
return super().__init__(
ssh_conn=ssh_conn,
source_file=source_file,
dest_file=dest_file,
file_system=file_system,
direction=direction,
**kwargs,
)
def remote_space_available(self, search_pattern: str = "") -> int:
"""Return space available on remote device."""
return self._remote_space_available_unix(search_pattern=search_pattern)
def check_file_exists(self, remote_cmd: str = "") -> bool:
"""Check if the dest_file already exists on the file system (return boolean)."""
return self._check_file_exists_unix(remote_cmd=remote_cmd)
def remote_file_size(
self, remote_cmd: str = "", remote_file: Optional[str] = None
) -> int:
"""Get the file size of the remote file."""
return self._remote_file_size_unix(
remote_cmd=remote_cmd, remote_file=remote_file
)
def remote_md5(
self, base_cmd: str = "verify /md5", remote_file: Optional[str] = None
) -> str:
if remote_file is None:
if self.direction == "put":
remote_file = self.dest_file
elif self.direction == "get":
remote_file = self.source_file
remote_md5_cmd = f"{base_cmd} file:{self.file_system}/{remote_file}"
dest_md5 = self.ssh_ctl_chan._send_command_str(remote_md5_cmd, read_timeout=600)
dest_md5 = self.process_md5(dest_md5)
return dest_md5
def enable_scp(self, cmd: Union[str, Sequence[str], None] = None) -> None:
raise NotImplementedError
def disable_scp(self, cmd: Union[str, Sequence[str], None] = None) -> None:
raise NotImplementedError
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1719528567.9239757
netmiko-4.5.0/netmiko/arris/__init__.py 0000644 0000000 0000000 00000000151 14637366170 015025 0 ustar 00 from netmiko.arris.arris_cer import ArrisCERBase, ArrisCERSSH
__all__ = ["ArrisCERBase", "ArrisCERSSH"]
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1719528567.9239757
netmiko-4.5.0/netmiko/arris/arris_cer.py 0000644 0000000 0000000 00000001664 14637366170 015251 0 ustar 00 from netmiko.cisco_base_connection import CiscoSSHConnection
class ArrisCERBase(CiscoSSHConnection):
"""
Arris CER Support.
Implements methods for interacting with Arris CER platforms.
"""
def config_mode(
self,
config_command: str = "configure",
pattern: str = "",
re_flags: int = 0,
) -> str:
"""Enters configuration mode."""
return super().config_mode(
config_command=config_command,
pattern=pattern,
re_flags=re_flags,
)
def save_config(
self,
cmd: str = "write memory",
confirm: bool = False,
confirm_response: str = "",
) -> str:
"""Saves the running configuration to NVRAM."""
return super().save_config(
cmd=cmd, confirm=confirm, confirm_response=confirm_response
)
class ArrisCERSSH(ArrisCERBase):
"""Arris CER SSH Driver."""
pass
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1719528567.9239757
netmiko-4.5.0/netmiko/aruba/__init__.py 0000644 0000000 0000000 00000000207 14637366170 015001 0 ustar 00 from netmiko.aruba.aruba_aoscx import ArubaCxSSH
from netmiko.aruba.aruba_os import ArubaOsSSH
__all__ = ["ArubaOsSSH", "ArubaCxSSH"]
././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1725389754.726303
netmiko-4.5.0/netmiko/aruba/aruba_aoscx.py 0000644 0000000 0000000 00000002307 14665655673 015546 0 ustar 00 """
Aruba AOS CX support.
For use with Aruba AOS CX devices.
"""
from typing import Any
from netmiko.cisco_base_connection import CiscoSSHConnection
class ArubaCxSSH(CiscoSSHConnection):
"""Aruba AOS CX support"""
def __init__(self, **kwargs: Any) -> None:
if kwargs.get("default_enter") is None:
kwargs["default_enter"] = "\r"
return super().__init__(**kwargs)
def session_preparation(self) -> None:
self.ansi_escape_codes = True
self._test_channel_read(pattern=r"[>#]")
self.set_base_prompt()
self.disable_paging(command="no page")
def check_config_mode(
self,
check_string: str = "(config)#",
pattern: str = r"[>#]",
force_regex: bool = False,
) -> bool:
return super().check_config_mode(check_string=check_string, pattern=pattern)
def config_mode(
self,
config_command: str = "configure term",
pattern: str = "",
re_flags: int = 0,
) -> str:
"""Aruba auto completes on space so 'configure' needs fully spelled-out."""
return super().config_mode(
config_command=config_command, pattern=pattern, re_flags=re_flags
)
././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1725389754.726303
netmiko-4.5.0/netmiko/aruba/aruba_os.py 0000644 0000000 0000000 00000003215 14665655673 015051 0 ustar 00 """
Aruba OS support.
For use with Aruba OS Controllers.
"""
from typing import Any
from netmiko.cisco_base_connection import CiscoSSHConnection
class ArubaOsSSH(CiscoSSHConnection):
"""Aruba OS support"""
def __init__(self, **kwargs: Any) -> None:
if kwargs.get("default_enter") is None:
kwargs["default_enter"] = "\r"
# Aruba has an auto-complete on space behavior that is problematic
if kwargs.get("global_cmd_verify") is None:
kwargs["global_cmd_verify"] = False
return super().__init__(**kwargs)
def session_preparation(self) -> None:
"""Aruba OS requires enable mode to disable paging."""
# Aruba switches output ansi codes
self.ansi_escape_codes = True
self._test_channel_read(pattern=r"[>#]")
self.set_base_prompt()
self.enable()
self.disable_paging(command="no paging")
def check_config_mode(
self,
check_string: str = "(config) #",
pattern: str = r"[>#]",
force_regex: bool = False,
) -> bool:
"""
Checks if the device is in configuration mode or not.
Aruba uses "() (config) #" as config prompt
"""
return super().check_config_mode(check_string=check_string, pattern=pattern)
def config_mode(
self,
config_command: str = "configure term",
pattern: str = "",
re_flags: int = 0,
) -> str:
"""Aruba auto completes on space so 'configure' needs fully spelled-out."""
return super().config_mode(
config_command=config_command, pattern=pattern, re_flags=re_flags
)
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1719528567.9239757
netmiko-4.5.0/netmiko/audiocode/__init__.py 0000644 0000000 0000000 00000000531 14637366170 015643 0 ustar 00 from netmiko.audiocode.audiocode_ssh import (
Audiocode72SSH,
Audiocode66SSH,
AudiocodeShellSSH,
Audiocode72Telnet,
Audiocode66Telnet,
AudiocodeShellTelnet,
)
__all__ = [
"Audiocode72SSH",
"Audiocode66SSH",
"AudiocodeShellSSH",
"Audiocode72Telnet",
"Audiocode66Telnet",
"AudiocodeShellTelnet",
]
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1719528567.9239757
netmiko-4.5.0/netmiko/audiocode/audiocode_ssh.py 0000644 0000000 0000000 00000037271 14637366170 016730 0 ustar 00 from typing import Any, Optional, Sequence, Iterator, TextIO, Union, List
import time
import re
from netmiko.base_connection import BaseConnection
from netmiko.no_enable import NoEnable
class AudiocodeBase(BaseConnection):
"""Common Methods for AudioCode Drivers."""
prompt_pattern = r"[>#]"
def __init__(self, *args: Any, **kwargs: Any) -> None:
default_enter = kwargs.get("default_enter")
kwargs["default_enter"] = "\r" if default_enter is None else default_enter
super().__init__(*args, **kwargs)
def session_preparation(self) -> None:
"""Prepare the session after the connection has been established."""
self._test_channel_read(pattern=self.prompt_pattern)
self.set_base_prompt()
self.disable_paging()
# Clear the read buffer
self.clear_buffer()
def set_base_prompt(
self,
pri_prompt_terminator: str = "#",
alt_prompt_terminator: str = ">",
delay_factor: float = 1.0,
pattern: Optional[str] = None,
) -> str:
if pattern is None:
pattern = rf"\*?{self.prompt_pattern}"
if pattern:
prompt = self.find_prompt(delay_factor=delay_factor, pattern=pattern)
else:
prompt = self.find_prompt(delay_factor=delay_factor)
if not prompt[-1] in (pri_prompt_terminator, alt_prompt_terminator):
raise ValueError(f"Router prompt not found: {repr(prompt)}")
# If all we have is the 'terminator' just use that :-(
if len(prompt) == 1:
self.base_prompt = prompt
else:
# Audiocode will return a prompt with * in it in certain
# situations: 'MYDEVICE*#', strip this off.
if "*#" in prompt or "*>" in prompt:
self.base_prompt = prompt[:-2]
else:
# Strip off trailing terminator
self.base_prompt = prompt[:-1]
return self.base_prompt
def find_prompt(
self,
delay_factor: float = 1.0,
pattern: Optional[str] = None,
) -> str:
if pattern is None:
pattern = rf"\*?{self.prompt_pattern}"
return super().find_prompt(
delay_factor=delay_factor,
pattern=pattern,
)
def _enable_paging(
self,
delay_factor: Optional[float] = 0.5,
) -> str:
return ""
def check_config_mode(
self,
check_string: str = r"(?:\)#|\)\*#)",
pattern: str = r"..#",
force_regex: bool = True,
) -> bool:
return super().check_config_mode(
check_string=check_string, pattern=pattern, force_regex=force_regex
)
def check_enable_mode(self, check_string: str = "#") -> bool:
return super().check_enable_mode(check_string=check_string)
def cleanup(self, command: str = "exit") -> None:
"""Gracefully exit the SSH session."""
try:
self._enable_paging()
if self.check_config_mode():
self.exit_config_mode()
except Exception:
pass
# Always try to send final 'exit' (command)
self._session_log_fin = True
self.write_channel(command + self.RETURN)
def enable(
self,
cmd: str = "enable",
pattern: str = "ssword",
enable_pattern: Optional[str] = "#",
check_state: bool = True,
re_flags: int = re.IGNORECASE,
) -> str:
return super().enable(
cmd=cmd,
pattern=pattern,
enable_pattern=enable_pattern,
check_state=check_state,
re_flags=re_flags,
)
def exit_config_mode(self, exit_config: str = "exit", pattern: str = r"#") -> str:
output = ""
max_exit_depth = 10
if self.check_config_mode():
# Keep "exitting" until out of config mode
for _ in range(max_exit_depth):
self.write_channel(self.normalize_cmd(exit_config))
# Make sure you read until you detect the command echo (avoid getting out of sync)
if self.global_cmd_verify is not False:
output += self.read_until_pattern(
pattern=re.escape(exit_config.strip())
)
if pattern:
output += self.read_until_pattern(pattern=pattern)
else:
output += self.read_until_prompt(read_entire_line=True)
if not self.check_config_mode():
# No longer in config mode
break
else: # no-break
raise ValueError("Failed to exit configuration mode")
return output
def exit_enable_mode(self, exit_command: str = "disable") -> str:
"""Exit enable mode."""
return super().exit_enable_mode(exit_command=exit_command)
def send_config_set(
self,
config_commands: Union[str, Sequence[str], Iterator[str], TextIO, None] = None,
*,
exit_config_mode: bool = True,
read_timeout: Optional[float] = None,
delay_factor: Optional[float] = 1.0,
max_loops: Optional[int] = 150,
strip_prompt: bool = False,
strip_command: bool = False,
config_mode_command: Optional[str] = None,
cmd_verify: bool = True,
enter_config_mode: bool = True,
error_pattern: str = "",
terminator: str = r"\*?#",
bypass_commands: Optional[str] = None,
) -> str:
if enter_config_mode and config_mode_command is None:
msg = """
send_config_set() for the Audiocode drivers require that you specify the
config_mode_command. For example, config_mode_command="configure system"
(or "configure voip" or "configure network" etc.)
"""
raise ValueError(msg)
return super().send_config_set(
config_commands=config_commands,
exit_config_mode=exit_config_mode,
read_timeout=read_timeout,
delay_factor=delay_factor,
max_loops=max_loops,
strip_prompt=strip_prompt,
strip_command=strip_command,
config_mode_command=config_mode_command,
cmd_verify=cmd_verify,
enter_config_mode=enter_config_mode,
error_pattern=error_pattern,
terminator=terminator,
bypass_commands=bypass_commands,
)
def save_config(
self, cmd: str = "write", confirm: bool = False, confirm_response: str = ""
) -> str:
"""Saves the running configuration."""
self.enable()
if confirm:
output = self._send_command_timing_str(command_string=cmd)
if confirm_response:
output += self._send_command_timing_str(confirm_response)
else:
# Send enter by default
output += self._send_command_timing_str(self.RETURN)
else:
# Some devices are slow so match on trailing-prompt if you can
output = self._send_command_str(command_string=cmd)
return output
def _reload_device(
self,
cmd_save: str = "reload now",
cmd_no_save: str = "reload without-saving",
reload_save: bool = True,
) -> str:
"""Reloads the device."""
if reload_save:
cmd = cmd_save
else:
cmd = cmd_no_save
self._enable_paging()
self.enable()
return self._send_command_timing_str(command_string=cmd)
class Audiocode72Base(AudiocodeBase):
"""Common Methods for AudioCodes running 7.2 CLI."""
def disable_paging(
self,
command: str = "",
delay_factor: Optional[float] = 0.5,
cmd_verify: bool = True,
pattern: Optional[str] = None,
) -> str:
if command:
return super().disable_paging(
command=command,
delay_factor=delay_factor,
cmd_verify=cmd_verify,
pattern=pattern,
)
else:
command_list: List[str] = [
"cli-settings",
"window-height 0",
]
self.enable()
assert isinstance(delay_factor, float)
delay_factor = self.select_delay_factor(delay_factor)
time.sleep(delay_factor * 0.1)
self.clear_buffer()
return self.send_config_set(
config_commands=command_list,
config_mode_command="config system",
)
def _enable_paging(
self,
delay_factor: Optional[float] = 0.5,
) -> str:
"""This is designed to re-enable window paging."""
command_list: List[str] = [
"cli-settings",
"window-height automatic",
]
self.enable()
assert isinstance(delay_factor, float)
delay_factor = self.select_delay_factor(delay_factor)
time.sleep(delay_factor * 0.1)
self.clear_buffer()
return self.send_config_set(
config_commands=command_list,
config_mode_command="config system",
)
class Audiocode72Telnet(Audiocode72Base):
pass
class Audiocode72SSH(Audiocode72Base):
pass
class AudiocodeBase66(AudiocodeBase):
"""Audiocode this applies to 6.6 Audiocode Firmware versions."""
def disable_paging(
self,
command: str = "",
delay_factor: Optional[float] = 0.5,
cmd_verify: bool = True,
pattern: Optional[str] = None,
) -> str:
if command:
return super().disable_paging(
command=command,
delay_factor=delay_factor,
cmd_verify=cmd_verify,
pattern=pattern,
)
else:
command_list: List[str] = [
"cli-terminal",
"set window-height 0",
]
self.enable()
assert isinstance(delay_factor, float)
delay_factor = self.select_delay_factor(delay_factor)
time.sleep(delay_factor * 0.1)
self.clear_buffer()
return self.send_config_set(
config_commands=command_list,
config_mode_command="config system",
)
def _enable_paging(
self,
delay_factor: Optional[float] = 0.5,
) -> str:
command_list: List[str] = [
"cli-terminal",
"set window-height 100",
]
self.enable()
assert isinstance(delay_factor, float)
delay_factor = self.select_delay_factor(delay_factor)
time.sleep(delay_factor * 0.1)
self.clear_buffer()
return self.send_config_set(
config_commands=command_list,
config_mode_command="config system",
)
class Audiocode66SSH(AudiocodeBase66):
pass
class Audiocode66Telnet(AudiocodeBase66):
pass
class AudiocodeShellBase(NoEnable, AudiocodeBase):
"""Audiocode this applies to 6.6 Audiocode Firmware versions that only use the Shell."""
def session_preparation(self) -> None:
"""Prepare the session after the connection has been established."""
self.write_channel(self.RETURN)
self.write_channel(self.RETURN)
self._test_channel_read(pattern=r"/>")
self.set_base_prompt()
# Clear the read buffer
time.sleep(0.3 * self.global_delay_factor)
self.clear_buffer()
def set_base_prompt(
self,
pri_prompt_terminator: str = r"/>",
alt_prompt_terminator: str = "",
delay_factor: float = 1.0,
pattern: Optional[str] = r"/>",
) -> str:
prompt = self.find_prompt(delay_factor=delay_factor, pattern=pattern)
pattern = pri_prompt_terminator
if not re.search(pattern, prompt):
raise ValueError(f"Router prompt not found: {repr(prompt)}")
else:
# Strip off trailing terminator
self.base_prompt = prompt
return self.base_prompt
def find_prompt(
self,
delay_factor: float = 1.0,
pattern: Optional[str] = r"/>",
) -> str:
return super().find_prompt(
delay_factor=delay_factor,
pattern=pattern,
)
def send_config_set(
self,
config_commands: Union[str, Sequence[str], Iterator[str], TextIO, None] = None,
*,
exit_config_mode: bool = True,
read_timeout: Optional[float] = None,
delay_factor: Optional[float] = 1.0,
max_loops: Optional[int] = 150,
strip_prompt: bool = False,
strip_command: bool = False,
config_mode_command: Optional[str] = None,
cmd_verify: bool = True,
enter_config_mode: bool = True,
error_pattern: str = "",
terminator: str = r"/.*>",
bypass_commands: Optional[str] = None,
) -> str:
return super().send_config_set(
config_commands=config_commands,
exit_config_mode=exit_config_mode,
read_timeout=read_timeout,
delay_factor=delay_factor,
max_loops=max_loops,
strip_prompt=strip_prompt,
strip_command=strip_command,
config_mode_command=config_mode_command,
cmd_verify=cmd_verify,
enter_config_mode=enter_config_mode,
error_pattern=error_pattern,
terminator=terminator,
bypass_commands=bypass_commands,
)
def config_mode(
self, config_command: str = "", pattern: str = r"/.*>", re_flags: int = 0
) -> str:
return super().config_mode(
config_command=config_command, pattern=pattern, re_flags=re_flags
)
def check_config_mode(
self,
check_string: str = r"/CONFiguration>",
pattern: str = r"/.*>",
force_regex: bool = True,
) -> bool:
return super().check_config_mode(
check_string=check_string, pattern=pattern, force_regex=force_regex
)
def exit_config_mode(self, exit_config: str = "..", pattern: str = r"/>") -> str:
return super().exit_config_mode(exit_config=exit_config, pattern=pattern)
def disable_paging(
self,
command: str = "",
delay_factor: Optional[float] = 0.5,
cmd_verify: bool = True,
pattern: Optional[str] = None,
) -> str:
"""Not supported"""
return ""
def save_config(
self,
cmd: str = "SaveConfiguration",
confirm: bool = False,
confirm_response: str = "",
) -> str:
return super().save_config(
cmd=cmd, confirm=confirm, confirm_response=confirm_response
)
def _reload_device(
self,
cmd_save: str = "SaveAndReset",
cmd_no_save: str = "ReSetDevice",
reload_save: bool = True,
) -> str:
"""Reloads the device."""
return super()._reload_device(
cmd_save=cmd_save, cmd_no_save=cmd_no_save, reload_save=reload_save
)
def _enable_paging(
self,
delay_factor: Optional[float] = 0.5,
) -> str:
"""Not supported"""
return ""
def strip_command(self, command_string: str, output: str) -> str:
# Support for Audiocode_Shell.
pattern = r"^SIP.*[\s\S]?PING.*>?.*[\s\S]?.*>?$"
output = re.sub(pattern, "", output, flags=re.M)
cmd = command_string.strip()
pattern = re.escape(cmd)
output = re.sub(pattern, "", output, flags=re.M)
return super().strip_command(command_string=command_string, output=output)
def strip_prompt(self, a_string: str) -> str:
pattern = r"^/>?"
a_string = re.sub(pattern, "", a_string, flags=re.M)
return super().strip_prompt(
a_string=a_string,
)
class AudiocodeShellSSH(AudiocodeShellBase):
pass
class AudiocodeShellTelnet(AudiocodeShellBase):
pass
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1733781006.2149477
netmiko-4.5.0/netmiko/base_connection.py 0000644 0000000 0000000 00000276632 14725663016 015317 0 ustar 00 """
Base connection class for netmiko
Handles SSH connection and methods that are generically applicable to different
platforms (Cisco and non-Cisco).
Also defines methods that should generally be supported by child classes
"""
from typing import (
Optional,
Callable,
Any,
List,
Dict,
TypeVar,
cast,
Type,
Sequence,
Iterator,
TextIO,
Union,
Tuple,
Deque,
)
from typing import TYPE_CHECKING
from types import TracebackType
import io
import re
import socket
import time
from collections import deque
from os import path
from pathlib import Path
from threading import Lock
import functools
import logging
import itertools
import paramiko
import serial
import warnings
from netmiko import log
from netmiko.netmiko_globals import BACKSPACE_CHAR
from netmiko.exceptions import (
NetmikoTimeoutException,
NetmikoAuthenticationException,
ConfigInvalidException,
ReadException,
ReadTimeout,
)
from netmiko._telnetlib import telnetlib
from netmiko.channel import Channel, SSHChannel, TelnetChannel, SerialChannel
from netmiko.session_log import SessionLog
from netmiko.utilities import (
write_bytes,
check_serial_port,
structured_data_converter,
run_ttp_template,
select_cmd_verify,
calc_old_timeout,
)
from netmiko.utilities import m_exec_time # noqa
from netmiko import telnet_proxy
if TYPE_CHECKING:
from os import PathLike
# For decorators
F = TypeVar("F", bound=Callable[..., Any])
DELAY_FACTOR_DEPR_SIMPLE_MSG = """\n
Netmiko 4.x and later has deprecated the use of delay_factor and/or
max_loops in this context. You should remove any use of delay_factor=x
from this method call.\n"""
# Logging filter for #2597
class SecretsFilter(logging.Filter):
def __init__(self, no_log: Optional[Dict[Any, str]] = None) -> None:
self.no_log = no_log
def filter(self, record: logging.LogRecord) -> bool:
"""Removes secrets (no_log) from messages"""
if self.no_log:
for hidden_data in self.no_log.values():
record.msg = record.msg.replace(hidden_data, "********")
return True
def lock_channel(func: F) -> F:
@functools.wraps(func)
def wrapper_decorator(self: "BaseConnection", *args: Any, **kwargs: Any) -> Any:
self._lock_netmiko_session()
try:
return_val = func(self, *args, **kwargs)
finally:
# Always unlock the channel, even on exception.
self._unlock_netmiko_session()
return return_val
return cast(F, wrapper_decorator)
def flush_session_log(func: F) -> F:
@functools.wraps(func)
def wrapper_decorator(self: "BaseConnection", *args: Any, **kwargs: Any) -> Any:
try:
return_val = func(self, *args, **kwargs)
finally:
# Always flush the session_log
if self.session_log:
self.session_log.flush()
return return_val
return cast(F, wrapper_decorator)
def log_writes(func: F) -> F:
"""Handle both session_log and log of writes."""
@functools.wraps(func)
def wrapper_decorator(self: "BaseConnection", out_data: str) -> None:
func(self, out_data)
try:
log.debug(
"write_channel: {}".format(
str(write_bytes(out_data, encoding=self.encoding))
)
)
if self.session_log:
if self.session_log.fin or self.session_log.record_writes:
self.session_log.write(out_data)
except UnicodeDecodeError:
# Don't log non-ASCII characters; this is null characters and telnet IAC (PY2)
pass
return None
return cast(F, wrapper_decorator)
class BaseConnection:
"""
Defines vendor independent methods.
Otherwise method left as a stub method.
"""
def __init__(
self,
ip: str = "",
host: str = "",
username: str = "",
password: Optional[str] = None,
secret: str = "",
port: Optional[int] = None,
device_type: str = "",
verbose: bool = False,
global_delay_factor: float = 1.0,
global_cmd_verify: Optional[bool] = None,
use_keys: bool = False,
key_file: Optional[str] = None,
pkey: Optional[paramiko.PKey] = None,
passphrase: Optional[str] = None,
disabled_algorithms: Optional[Dict[str, Any]] = None,
disable_sha2_fix: bool = False,
allow_agent: bool = False,
ssh_strict: bool = False,
system_host_keys: bool = False,
alt_host_keys: bool = False,
alt_key_file: str = "",
ssh_config_file: Optional[str] = None,
#
# Connect timeouts
# ssh-connect --> TCP conn (conn_timeout) --> SSH-banner (banner_timeout)
# --> Auth response (auth_timeout)
conn_timeout: int = 10,
# Timeout to wait for authentication response
auth_timeout: Optional[int] = None,
banner_timeout: int = 15, # Timeout to wait for the banner to be presented
# Other timeouts
blocking_timeout: int = 20, # Read blocking timeout
timeout: int = 100, # TCP connect timeout | overloaded to read-loop timeout
session_timeout: int = 60, # Used for locking/sharing the connection
read_timeout_override: Optional[float] = None,
keepalive: int = 0,
default_enter: Optional[str] = None,
response_return: Optional[str] = None,
serial_settings: Optional[Dict[str, Any]] = None,
fast_cli: bool = True,
_legacy_mode: bool = False,
session_log: Optional[SessionLog] = None,
session_log_record_writes: bool = False,
session_log_file_mode: str = "write",
allow_auto_change: bool = False,
encoding: str = "utf-8",
sock: Optional[socket.socket] = None,
sock_telnet: Optional[Dict[str, Any]] = None,
auto_connect: bool = True,
delay_factor_compat: bool = False,
disable_lf_normalization: bool = False,
) -> None:
"""
Initialize attributes for establishing connection to target device.
:param ip: IP address of target device. Not required if `host` is
provided.
:param host: Hostname of target device. Not required if `ip` is
provided.
:param username: Username to authenticate against target device if
required.
:param password: Password to authenticate against target device if
required.
:param secret: The enable password if target device requires one.
:param port: The destination port used to connect to the target
device.
:param device_type: Class selection based on device type.
:param verbose: Enable additional messages to standard output.
:param global_delay_factor: Multiplication factor affecting Netmiko delays (default: 1).
:param use_keys: Connect to target device using SSH keys.
:param key_file: Filename path of the SSH key file to use.
:param pkey: SSH key object to use.
:param passphrase: Passphrase to use for encrypted key; password will be used for key
decryption if not specified.
:param disabled_algorithms: Dictionary of SSH algorithms to disable. Refer to the Paramiko
documentation for a description of the expected format.
:param disable_sha2_fix: Boolean that fixes Paramiko issue with missing server-sig-algs
https://github.com/paramiko/paramiko/issues/1961 (default: False)
:param allow_agent: Enable use of SSH key-agent.
:param ssh_strict: Automatically reject unknown SSH host keys (default: False, which
means unknown SSH host keys will be accepted).
:param system_host_keys: Load host keys from the users known_hosts file.
:param alt_host_keys: If `True` host keys will be loaded from the file specified in
alt_key_file.
:param alt_key_file: SSH host key file to use (if alt_host_keys=True).
:param ssh_config_file: File name of OpenSSH configuration file.
:param conn_timeout: TCP connection timeout.
:param session_timeout: Set a timeout for parallel requests.
:param auth_timeout: Set a timeout (in seconds) to wait for an authentication response.
:param banner_timeout: Set a timeout to wait for the SSH banner (pass to Paramiko).
:param read_timeout_override: Set a timeout that will override the default read_timeout
of both send_command and send_command_timing. This is useful for 3rd party
libraries where directly accessing method arguments might be impractical.
:param keepalive: Send SSH keepalive packets at a specific interval, in seconds.
Currently defaults to 0, for backwards compatibility (it will not attempt
to keep the connection alive).
:param default_enter: Character(s) to send to correspond to enter key (default: \n).
:param response_return: Character(s) to use in normalized return data to represent
enter key (default: \n)
:param serial_settings: Dictionary of settings for use with serial port (pySerial).
:param fast_cli: Provide a way to optimize for performance. Converts select_delay_factor
to select smallest of global and specific. Sets default global_delay_factor to .1
(default: True)
:param session_log: File path, SessionLog object, or BufferedIOBase subclass object
to write the session log to.
:param session_log_record_writes: The session log generally only records channel reads due
to eliminate command duplication due to command echo. You can enable this if you
want to record both channel reads and channel writes in the log (default: False).
:param session_log_file_mode: "write" or "append" for session_log file mode
(default: "write")
:param allow_auto_change: Allow automatic configuration changes for terminal settings.
(default: False)
:param encoding: Encoding to be used when writing bytes to the output channel.
(default: "utf-8")
:param sock: An open socket or socket-like object (such as a `.Channel`) to use for
communication to the target host (default: None).
:param sock_telnet: A dictionary of telnet socket parameters (SOCKS proxy). See
telnet_proxy.py code for details.
:param global_cmd_verify: Control whether command echo verification is enabled or disabled
(default: None). Global attribute takes precedence over function `cmd_verify`
argument. Value of `None` indicates to use function `cmd_verify` argument.
:param auto_connect: Control whether Netmiko automatically establishes the connection as
part of the object creation (default: True).
:param delay_factor_compat: Set send_command and send_command_timing back to using Netmiko
3.x behavior for delay_factor/global_delay_factor/max_loops. This argument will be
eliminated in Netmiko 5.x (default: False).
:param disable_lf_normalization: Disable Netmiko's linefeed normalization behavior
(default: False)
"""
self.remote_conn: Union[
None, telnetlib.Telnet, paramiko.Channel, serial.Serial
] = None
# Does the platform support a configuration mode
self._config_mode = True
self._read_buffer = ""
self.delay_factor_compat = delay_factor_compat
self.TELNET_RETURN = "\r\n"
if default_enter is None:
if "telnet" not in device_type:
self.RETURN = "\n"
else:
self.RETURN = self.TELNET_RETURN
else:
self.RETURN = default_enter
# Line Separator in response lines
self.RESPONSE_RETURN = "\n" if response_return is None else response_return
self.disable_lf_normalization = True if disable_lf_normalization else False
if ip:
self.host = ip.strip()
elif host:
self.host = host.strip()
if not ip and not host and "serial" not in device_type:
raise ValueError("Either ip or host must be set")
if port is None:
if "telnet" in device_type:
port = 23
else:
port = 22
self.port = int(port)
self.username = username
self.password = password
self.secret = secret
self.device_type = device_type
self.ansi_escape_codes = False
self.verbose = verbose
self.auth_timeout = auth_timeout
self.banner_timeout = banner_timeout
self.blocking_timeout = blocking_timeout
self.conn_timeout = conn_timeout
self.session_timeout = session_timeout
self.timeout = timeout
self.read_timeout_override = read_timeout_override
self.keepalive = keepalive
self.allow_auto_change = allow_auto_change
self.encoding = encoding
self.sock = sock
self.sock_telnet = sock_telnet
self.fast_cli = fast_cli
self._legacy_mode = _legacy_mode
self.global_delay_factor = global_delay_factor
self.global_cmd_verify = global_cmd_verify
if self.fast_cli and self.global_delay_factor == 1:
self.global_delay_factor = 0.1
self.session_log = None
self._session_log_close = False
# prevent logging secret data
no_log = {}
if self.password:
no_log["password"] = self.password
if self.secret:
no_log["secret"] = self.secret
# Always sanitize username and password
self._secrets_filter = SecretsFilter(no_log=no_log)
log.addFilter(self._secrets_filter)
# Netmiko will close the session_log if we open the file
if session_log is not None:
if isinstance(session_log, str):
# If session_log is a string, open a file corresponding to string name.
self.session_log = SessionLog(
file_name=session_log,
file_mode=session_log_file_mode,
no_log=no_log,
record_writes=session_log_record_writes,
)
self.session_log.open()
elif isinstance(session_log, io.BufferedIOBase):
# In-memory buffer or an already open file handle
self.session_log = SessionLog(
buffered_io=session_log,
no_log=no_log,
record_writes=session_log_record_writes,
)
elif isinstance(session_log, SessionLog):
# SessionLog object
self.session_log = session_log
self.session_log.open()
else:
raise ValueError(
"session_log must be a path to a file, a file handle, "
"SessionLog object, or a BufferedIOBase subclass."
)
# Default values
self.serial_settings = {
"port": "COM1",
"baudrate": 9600,
"bytesize": serial.EIGHTBITS,
"parity": serial.PARITY_NONE,
"stopbits": serial.STOPBITS_ONE,
}
if serial_settings is None:
serial_settings = {}
self.serial_settings.update(serial_settings)
if "serial" in device_type:
self.host = "serial"
comm_port = self.serial_settings.pop("port")
# Get the proper comm port reference if a name was enterred
comm_port = check_serial_port(comm_port)
self.serial_settings.update({"port": comm_port})
# set in set_base_prompt method
self.base_prompt = ""
self._session_locker = Lock()
# determine if telnet or SSH
if "_telnet" in device_type:
self.protocol = "telnet"
self.password = password or ""
elif "_serial" in device_type:
self.protocol = "serial"
self.password = password or ""
else:
self.protocol = "ssh"
self.key_policy: paramiko.client.MissingHostKeyPolicy
if not ssh_strict:
self.key_policy = paramiko.AutoAddPolicy()
else:
self.key_policy = paramiko.RejectPolicy()
# Options for SSH host_keys
self.use_keys = use_keys
self.key_file = (
path.abspath(path.expanduser(key_file)) if key_file else None
)
if self.use_keys is True:
self._key_check()
self.pkey = pkey
self.passphrase = passphrase
self.allow_agent = allow_agent
self.system_host_keys = system_host_keys
self.alt_host_keys = alt_host_keys
self.alt_key_file = alt_key_file
self.disabled_algorithms = disabled_algorithms
if disable_sha2_fix:
sha2_pubkeys = ["rsa-sha2-256", "rsa-sha2-512"]
if self.disabled_algorithms is None:
self.disabled_algorithms = {"pubkeys": sha2_pubkeys}
else:
# Merge sha2_pubkeys into pubkeys and prevent duplicates
current_pubkeys = self.disabled_algorithms.get("pubkeys", [])
self.disabled_algorithms["pubkeys"] = list(
set(current_pubkeys + sha2_pubkeys)
)
# For SSH proxy support
self.ssh_config_file = ssh_config_file
# Establish the remote connection
if auto_connect:
self._open()
def _open(self) -> None:
"""Decouple connection creation from __init__ for mocking."""
self._modify_connection_params()
self.establish_connection()
self._try_session_preparation()
def __enter__(self) -> "BaseConnection":
"""Establish a session using a Context Manager."""
return self
def __exit__(
self,
exc_type: Optional[Type[BaseException]],
exc_value: Optional[BaseException],
traceback: Optional[TracebackType],
) -> None:
"""Gracefully close connection on Context Manager exit."""
self.disconnect()
def _modify_connection_params(self) -> None:
"""Modify connection parameters prior to SSH connection."""
pass
def _timeout_exceeded(self, start: float, msg: str = "Timeout exceeded!") -> bool:
"""Raise NetmikoTimeoutException if waiting too much in the serving queue.
:param start: Initial start time to see if session lock timeout has been exceeded
:type start: float (from time.time() call i.e. epoch time)
:param msg: Exception message if timeout was exceeded
:type msg: str
"""
if not start:
# Must provide a comparison time
return False
if time.time() - start > self.session_timeout:
# session_timeout exceeded
raise NetmikoTimeoutException(msg)
return False
def _lock_netmiko_session(self, start: Optional[float] = None) -> bool:
"""Try to acquire the Netmiko session lock. If not available, wait in the queue until
the channel is available again.
:param start: Initial start time to measure the session timeout
:type start: float (from time.time() call i.e. epoch time)
"""
if not start:
start = time.time()
# Wait here until the SSH channel lock is acquired or until session_timeout exceeded
while not self._session_locker.acquire(False) and not self._timeout_exceeded(
start, "The netmiko channel is not available!"
):
time.sleep(0.1)
return True
def _unlock_netmiko_session(self) -> None:
"""
Release the channel at the end of the task.
"""
if self._session_locker.locked():
self._session_locker.release()
def _autodetect_fs(self, cmd: str = "", pattern: str = "") -> str:
raise NotImplementedError
def _enter_shell(self) -> str:
raise NotImplementedError
def _return_cli(self) -> str:
raise NotImplementedError
def _key_check(self) -> bool:
"""Verify key_file exists."""
msg = f"""
use_keys has been set to True, but specified key_file does not exist:
use_keys: {self.use_keys}
key_file: {self.key_file}
"""
if self.key_file is None:
raise ValueError(msg)
my_key_file = Path(self.key_file)
if not my_key_file.is_file():
raise ValueError(msg)
return True
@lock_channel
@log_writes
def write_channel(self, out_data: str) -> None:
"""Generic method that will write data out the channel.
:param out_data: data to be written to the channel
:type out_data: str
"""
self.channel.write_channel(out_data)
def is_alive(self) -> bool:
"""Returns a boolean flag with the state of the connection."""
null = chr(0)
if self.remote_conn is None:
log.error("Connection is not initialised, is_alive returns False")
return False
if self.protocol == "telnet":
try:
# Try sending IAC + NOP (IAC is telnet way of sending command)
# IAC = Interpret as Command; it comes before the NOP.
log.debug("Sending IAC + NOP")
# Need to send multiple times to test connection
assert isinstance(self.remote_conn, telnetlib.Telnet)
telnet_socket = self.remote_conn.get_socket() # type: ignore
telnet_socket.sendall(telnetlib.IAC + telnetlib.NOP)
telnet_socket.sendall(telnetlib.IAC + telnetlib.NOP)
telnet_socket.sendall(telnetlib.IAC + telnetlib.NOP)
return True
except AttributeError:
return False
else:
# SSH
try:
# Try sending ASCII null byte to maintain the connection alive
log.debug("Sending the NULL byte")
self.write_channel(null)
assert isinstance(self.remote_conn, paramiko.Channel)
assert self.remote_conn.transport is not None
result = self.remote_conn.transport.is_active()
assert isinstance(result, bool)
return result
except (socket.error, EOFError):
log.error("Unable to send", exc_info=True)
# If unable to send, we can tell for sure that the connection is unusable
return False
return False
@lock_channel
def read_channel(self) -> str:
"""Generic handler that will read all the data from given channel."""
new_data = self.channel.read_channel()
if self.disable_lf_normalization is False:
start = time.time()
# Data blocks shouldn't end in '\r' (can cause problems with normalize_linefeeds)
# Only do the extra read if '\n' exists in the output
# this avoids devices that only use \r.
while ("\n" in new_data) and (time.time() - start < 1.0):
if new_data[-1] == "\r":
time.sleep(0.01)
new_data += self.channel.read_channel()
else:
break
new_data = self.normalize_linefeeds(new_data)
if self.ansi_escape_codes:
new_data = self.strip_ansi_escape_codes(new_data)
log.debug(f"read_channel: {new_data}")
if self.session_log:
self.session_log.write(new_data)
# If data had been previously saved to the buffer, the prepend it to output
# do post read_channel so session_log/log doesn't record buffered data twice
if self._read_buffer:
output = self._read_buffer + new_data
self._read_buffer = ""
else:
output = new_data
return output
def read_until_pattern(
self,
pattern: str = "",
read_timeout: float = 10.0,
re_flags: int = 0,
max_loops: Optional[int] = None,
) -> str:
"""Read channel until pattern is detected.
Will return string up to and including pattern.
Returns ReadTimeout if pattern not detected in read_timeout seconds.
:param pattern: Regular expression pattern used to identify that reading is done.
:param read_timeout: maximum time to wait looking for pattern. Will raise ReadTimeout.
A read_timeout value of 0 will cause the loop to never timeout (i.e. it will keep
reading indefinitely until pattern is detected.
:param re_flags: regex flags used in conjunction with pattern (defaults to no flags).
:param max_loops: Deprecated in Netmiko 4.x. Will be eliminated in Netmiko 5.
"""
if max_loops is not None:
msg = """\n
Netmiko 4.x has deprecated the use of max_loops with read_until_pattern.
You should convert all uses of max_loops over to read_timeout=x
where x is the total number of seconds to wait before timing out.\n"""
warnings.warn(msg, DeprecationWarning)
if self.read_timeout_override:
read_timeout = self.read_timeout_override
output = ""
loop_delay = 0.01
start_time = time.time()
# if read_timeout == 0 or 0.0 keep reading indefinitely
while (time.time() - start_time < read_timeout) or (not read_timeout):
output += self.read_channel()
if re.search(pattern, output, flags=re_flags):
if "(" in pattern and "(?:" not in pattern:
msg = f"""
Parenthesis found in pattern.
pattern: {pattern}\n
This can be problemtic when used in read_until_pattern().
You should ensure that you use either non-capture groups i.e. '(?:' or that the
parenthesis completely wrap the pattern '(pattern)'"""
log.debug(msg)
results = re.split(pattern, output, maxsplit=1, flags=re_flags)
# The string matched by pattern must be retained in the output string.
# re.split will do this if capturing parenthesis are used.
if len(results) == 2:
# no capturing parenthesis, convert and try again.
pattern = f"({pattern})"
results = re.split(pattern, output, maxsplit=1, flags=re_flags)
if len(results) != 3:
# well, we tried
msg = f"""Unable to successfully split output based on pattern:
pattern={pattern}
output={repr(output)}
results={results}
"""
raise ReadException(msg)
# Process such that everything before and including pattern is return.
# Everything else is retained in the _read_buffer
output, match_str, buffer = results
output = output + match_str
if buffer:
self._read_buffer += buffer
log.debug(f"Pattern found: {pattern} {output}")
return output
time.sleep(loop_delay)
msg = f"""\n\nPattern not detected: {repr(pattern)} in output.
Things you might try to fix this:
1. Adjust the regex pattern to better identify the terminating string. Note, in
many situations the pattern is automatically based on the network device's prompt.
2. Increase the read_timeout to a larger value.
You can also look at the Netmiko session_log or debug log for more information.\n\n"""
raise ReadTimeout(msg)
def read_channel_timing(
self,
last_read: float = 2.0,
read_timeout: float = 120.0,
delay_factor: Optional[float] = None,
max_loops: Optional[int] = None,
) -> str:
"""Read data on the channel based on timing delays.
General pattern is keep reading until no new data is read.
Once no new data is read wait `last_read` amount of time (one last read).
As long as no new data, then return data.
Setting `read_timeout` to zero will cause read_channel_timing to never expire based
on an absolute timeout. It will only complete based on timeout based on there being
no new data.
:param last_read: Amount of time to wait before performing one last read (under the
idea that we should be done reading at this point and there should be no new
data).
:param read_timeout: Absolute timer for how long Netmiko should keep reading data on
the channel (waiting for there to be no new data). Will raise ReadTimeout if this
timeout expires. A read_timeout value of 0 will cause the read-loop to never timeout
(i.e. Netmiko will keep reading indefinitely until there is no new data and last_read
passes).
:param delay_factor: Deprecated in Netmiko 4.x. Will be eliminated in Netmiko 5.
:param max_loops: Deprecated in Netmiko 4.x. Will be eliminated in Netmiko 5.
"""
if delay_factor is not None or max_loops is not None:
warnings.warn(DELAY_FACTOR_DEPR_SIMPLE_MSG, DeprecationWarning)
if self.read_timeout_override:
read_timeout = self.read_timeout_override
# Time to delay in each read loop
loop_delay = 0.1
channel_data = ""
start_time = time.time()
# Set read_timeout to 0 to never timeout
while (time.time() - start_time < read_timeout) or (not read_timeout):
time.sleep(loop_delay)
new_data = self.read_channel()
# gather new output
if new_data:
channel_data += new_data
# if we have some output, but nothing new, then do the last read
elif channel_data != "":
# Make sure really done (i.e. no new data)
time.sleep(last_read)
new_data = self.read_channel()
if not new_data:
break
else:
channel_data += new_data
else:
msg = f"""\n
read_channel_timing's absolute timer expired.
The network device was continually outputting data for longer than {read_timeout}
seconds.
If this is expected i.e. the command you are executing is continually emitting
data for a long period of time, then you can set 'read_timeout=x' seconds. If
you want Netmiko to keep reading indefinitely (i.e. to only stop when there is
no new data), then you can set 'read_timeout=0'.
You can look at the Netmiko session_log or debug log for more information.
"""
raise ReadTimeout(msg)
return channel_data
def read_until_prompt(
self,
read_timeout: float = 10.0,
read_entire_line: bool = False,
re_flags: int = 0,
max_loops: Optional[int] = None,
) -> str:
"""Read channel up to and including self.base_prompt."""
pattern = re.escape(self.base_prompt)
if read_entire_line:
pattern = f"{pattern}.*"
return self.read_until_pattern(
pattern=pattern,
re_flags=re_flags,
max_loops=max_loops,
read_timeout=read_timeout,
)
def read_until_prompt_or_pattern(
self,
pattern: str = "",
read_timeout: float = 10.0,
read_entire_line: bool = False,
re_flags: int = 0,
max_loops: Optional[int] = None,
) -> str:
"""Read until either self.base_prompt or pattern is detected."""
prompt_pattern = re.escape(self.base_prompt)
if read_entire_line:
prompt_pattern = f"{prompt_pattern}.*"
if pattern:
combined_pattern = r"(?:{}|{})".format(prompt_pattern, pattern)
else:
combined_pattern = prompt_pattern
return self.read_until_pattern(
pattern=combined_pattern,
re_flags=re_flags,
max_loops=max_loops,
read_timeout=read_timeout,
)
def serial_login(
self,
pri_prompt_terminator: str = r"#\s*$",
alt_prompt_terminator: str = r">\s*$",
username_pattern: str = r"(?:[Uu]ser:|sername|ogin)",
pwd_pattern: str = r"assword",
delay_factor: float = 1.0,
max_loops: int = 20,
) -> str:
return self.telnet_login(
pri_prompt_terminator,
alt_prompt_terminator,
username_pattern,
pwd_pattern,
delay_factor,
max_loops,
)
def telnet_login(
self,
pri_prompt_terminator: str = r"#\s*$",
alt_prompt_terminator: str = r">\s*$",
username_pattern: str = r"(?:user:|username|login|user name)",
pwd_pattern: str = r"assword",
delay_factor: float = 1.0,
max_loops: int = 20,
) -> str:
"""Telnet login. Can be username/password or just password.
:param pri_prompt_terminator: Primary trailing delimiter for identifying a device prompt
:param alt_prompt_terminator: Alternate trailing delimiter for identifying a device prompt
:param username_pattern: Pattern used to identify the username prompt
:param pwd_pattern: Pattern used to identify the pwd prompt
:param delay_factor: See __init__: global_delay_factor
:param max_loops: Controls the wait time in conjunction with the delay_factor
"""
delay_factor = self.select_delay_factor(delay_factor)
# Revert telnet_login back to old speeds/delays
if delay_factor < 1:
if not self._legacy_mode and self.fast_cli:
delay_factor = 1
time.sleep(1 * delay_factor)
output = ""
return_msg = ""
i = 1
while i <= max_loops:
try:
output = self.read_channel()
return_msg += output
# Search for username pattern / send username
if re.search(username_pattern, output, flags=re.I):
# Sometimes username/password must be terminated with "\r" and not "\r\n"
self.write_channel(self.username + "\r")
time.sleep(1 * delay_factor)
output = self.read_channel()
return_msg += output
# Search for password pattern / send password
if re.search(pwd_pattern, output, flags=re.I):
# Sometimes username/password must be terminated with "\r" and not "\r\n"
assert isinstance(self.password, str)
self.write_channel(self.password + "\r")
time.sleep(0.5 * delay_factor)
output = self.read_channel()
return_msg += output
if re.search(
pri_prompt_terminator, output, flags=re.M
) or re.search(alt_prompt_terminator, output, flags=re.M):
return return_msg
# Check if proper data received
if re.search(pri_prompt_terminator, output, flags=re.M) or re.search(
alt_prompt_terminator, output, flags=re.M
):
return return_msg
self.write_channel(self.TELNET_RETURN)
time.sleep(0.5 * delay_factor)
i += 1
except EOFError:
assert self.remote_conn is not None
self.remote_conn.close()
msg = f"Login failed: {self.host}"
raise NetmikoAuthenticationException(msg)
# Last try to see if we already logged in
self.write_channel(self.TELNET_RETURN)
time.sleep(0.5 * delay_factor)
output = self.read_channel()
return_msg += output
if re.search(pri_prompt_terminator, output, flags=re.M) or re.search(
alt_prompt_terminator, output, flags=re.M
):
return return_msg
msg = f"Login failed: {self.host}"
assert self.remote_conn is not None
self.remote_conn.close()
raise NetmikoAuthenticationException(msg)
def _try_session_preparation(self, force_data: bool = True) -> None:
"""
In case of an exception happening during `session_preparation()` Netmiko should
gracefully clean-up after itself. This might be challenging for library users
to do since they do not have a reference to the object. This is possibly related
to threads used in Paramiko.
"""
try:
# Netmiko needs there to be data for session_preparation to work.
if force_data:
self.write_channel(self.RETURN)
time.sleep(0.1)
self.session_preparation()
except Exception:
self.disconnect()
raise
def session_preparation(self) -> None:
"""
Prepare the session after the connection has been established
This method handles some differences that occur between various devices
early on in the session.
In general, it should include:
self._test_channel_read(pattern=r"some_pattern")
self.set_base_prompt()
self.set_terminal_width()
self.disable_paging()
"""
self._test_channel_read()
self.set_base_prompt()
self.set_terminal_width()
self.disable_paging()
def _use_ssh_config(self, dict_arg: Dict[str, Any]) -> Dict[str, Any]:
"""Update SSH connection parameters based on contents of SSH config file.
:param dict_arg: Dictionary of SSH connection parameters
"""
connect_dict = dict_arg.copy()
# Use SSHConfig to generate source content.
assert self.ssh_config_file is not None
full_path = path.abspath(path.expanduser(self.ssh_config_file))
source: Union[paramiko.config.SSHConfigDict, Dict[str, Any]]
if path.exists(full_path):
ssh_config_instance = paramiko.SSHConfig()
with io.open(full_path, "rt", encoding="utf-8") as f:
ssh_config_instance.parse(f)
source = ssh_config_instance.lookup(self.host)
else:
source = {}
# Keys get normalized to lower-case
proxy: Optional[paramiko.proxy.ProxyCommand]
if "proxycommand" in source:
proxy = paramiko.ProxyCommand(source["proxycommand"])
elif "proxyjump" in source:
hops = list(reversed(source["proxyjump"].split(",")))
if len(hops) > 1:
raise ValueError(
"ProxyJump with more than one proxy server is not supported."
)
port = source.get("port", self.port)
host = source.get("hostname", self.host)
# -F {full_path} forces the continued use of the same SSH config file
cmd = "ssh -F {} -W {}:{} {}".format(full_path, host, port, hops[0])
proxy = paramiko.ProxyCommand(cmd)
else:
proxy = None
# Only update 'hostname', 'sock', 'port', and 'username'
# For 'port' and 'username' only update if using object defaults
if connect_dict["port"] == 22:
connect_dict["port"] = int(source.get("port", self.port))
if connect_dict["username"] == "":
connect_dict["username"] = source.get("user", self.username)
if proxy:
connect_dict["sock"] = proxy
connect_dict["hostname"] = source.get("hostname", self.host)
return connect_dict
def _connect_params_dict(self) -> Dict[str, Any]:
"""Generate dictionary of Paramiko connection parameters."""
conn_dict = {
"hostname": self.host,
"port": self.port,
"username": self.username,
"password": self.password,
"look_for_keys": self.use_keys,
"allow_agent": self.allow_agent,
"key_filename": self.key_file,
"pkey": self.pkey,
"passphrase": self.passphrase,
"disabled_algorithms": self.disabled_algorithms,
"timeout": self.conn_timeout,
"auth_timeout": self.auth_timeout,
"banner_timeout": self.banner_timeout,
"sock": self.sock,
}
# Check if using SSH 'config' file mainly for SSH proxy support
if self.ssh_config_file:
conn_dict = self._use_ssh_config(conn_dict)
return conn_dict
def _sanitize_output(
self,
output: str,
strip_command: bool = False,
command_string: Optional[str] = None,
strip_prompt: bool = False,
) -> str:
"""Strip out command echo and trailing router prompt."""
if strip_command and command_string:
output = self.strip_command(command_string, output)
if strip_prompt:
output = self.strip_prompt(output)
return output
def establish_connection(self, width: int = 511, height: int = 1000) -> None:
"""Establish SSH connection to the network device
Timeout will generate a NetmikoTimeoutException
Authentication failure will generate a NetmikoAuthenticationException
:param width: Specified width of the VT100 terminal window (default: 511)
:type width: int
:param height: Specified height of the VT100 terminal window (default: 1000)
:type height: int
"""
self.channel: Channel
if self.protocol == "telnet":
if self.sock_telnet:
self.remote_conn = telnet_proxy.Telnet(
self.host,
port=self.port,
timeout=self.timeout,
proxy_dict=self.sock_telnet,
)
else:
self.remote_conn = telnetlib.Telnet( # type: ignore
self.host, port=self.port, timeout=self.timeout
)
# Migrating communication to channel class
self.channel = TelnetChannel(conn=self.remote_conn, encoding=self.encoding)
self.telnet_login()
elif self.protocol == "serial":
self.remote_conn = serial.Serial(**self.serial_settings)
self.channel = SerialChannel(conn=self.remote_conn, encoding=self.encoding)
self.serial_login()
elif self.protocol == "ssh":
ssh_connect_params = self._connect_params_dict()
self.remote_conn_pre: Optional[paramiko.SSHClient]
self.remote_conn_pre = self._build_ssh_client()
# initiate SSH connection
try:
self.remote_conn_pre.connect(**ssh_connect_params)
except socket.error as conn_error:
self.paramiko_cleanup()
msg = f"""TCP connection to device failed.
Common causes of this problem are:
1. Incorrect hostname or IP address.
2. Wrong TCP port.
3. Intermediate firewall blocking access.
Device settings: {self.device_type} {self.host}:{self.port}
"""
# Handle DNS failures separately
if "Name or service not known" in str(conn_error):
msg = (
f"DNS failure--the hostname you provided was not resolvable "
f"in DNS: {self.host}:{self.port}"
)
msg = msg.lstrip()
raise NetmikoTimeoutException(msg)
except paramiko.ssh_exception.AuthenticationException as auth_err:
self.paramiko_cleanup()
msg = f"""Authentication to device failed.
Common causes of this problem are:
1. Invalid username and password
2. Incorrect SSH-key file
3. Connecting to the wrong device
Device settings: {self.device_type} {self.host}:{self.port}
"""
msg += self.RETURN + str(auth_err)
raise NetmikoAuthenticationException(msg)
except paramiko.ssh_exception.SSHException as e:
self.paramiko_cleanup()
if "No existing session" in str(e):
msg = (
"Paramiko: 'No existing session' error: "
"try increasing 'conn_timeout' to 15 seconds or larger."
)
raise NetmikoTimeoutException(msg)
else:
msg = f"""
A paramiko SSHException occurred during connection creation:
{str(e)}
"""
raise NetmikoTimeoutException(msg)
if self.verbose:
print(f"SSH connection established to {self.host}:{self.port}")
# Use invoke_shell to establish an 'interactive session'
self.remote_conn = self.remote_conn_pre.invoke_shell(
term="vt100", width=width, height=height
)
self.remote_conn.settimeout(self.blocking_timeout)
if self.keepalive:
assert isinstance(self.remote_conn.transport, paramiko.Transport)
self.remote_conn.transport.set_keepalive(self.keepalive)
# Migrating communication to channel class
self.channel = SSHChannel(conn=self.remote_conn, encoding=self.encoding)
self.special_login_handler()
if self.verbose:
print("Interactive SSH session established")
return None
def _test_channel_read(self, count: int = 40, pattern: str = "") -> str:
"""Try to read the channel (generally post login) verify you receive data back.
:param count: the number of times to check the channel for data
:param pattern: Regular expression pattern used to determine end of channel read
"""
def _increment_delay(
main_delay: float, increment: float = 1.1, maximum: int = 8
) -> float:
"""Increment sleep time to a maximum value."""
main_delay = main_delay * increment
if main_delay >= maximum:
main_delay = maximum
return main_delay
i = 0
delay_factor = self.select_delay_factor(delay_factor=0)
if pattern:
return self.read_until_pattern(pattern=pattern, read_timeout=20)
main_delay = delay_factor * 0.1
time.sleep(main_delay * 10)
new_data = ""
while i <= count:
new_data += self.read_channel_timing(read_timeout=20)
if new_data:
return new_data
self.write_channel(self.RETURN)
main_delay = _increment_delay(main_delay)
time.sleep(main_delay)
i += 1
raise NetmikoTimeoutException("Timed out waiting for data")
def _build_ssh_client(self) -> paramiko.SSHClient:
"""Prepare for Paramiko SSH connection."""
# Create instance of SSHClient object
remote_conn_pre = paramiko.SSHClient()
# Load host_keys for better SSH security
if self.system_host_keys:
remote_conn_pre.load_system_host_keys()
if self.alt_host_keys and path.isfile(self.alt_key_file):
remote_conn_pre.load_host_keys(self.alt_key_file)
# Default is to automatically add untrusted hosts (make sure appropriate for your env)
remote_conn_pre.set_missing_host_key_policy(self.key_policy)
return remote_conn_pre
def select_delay_factor(self, delay_factor: float) -> float:
"""
Choose the greater of delay_factor or self.global_delay_factor (default).
In fast_cli choose the lesser of delay_factor of self.global_delay_factor.
:param delay_factor: See __init__: global_delay_factor
:type delay_factor: int
"""
if self.fast_cli:
if delay_factor and delay_factor <= self.global_delay_factor:
return delay_factor
else:
return self.global_delay_factor
else:
if delay_factor >= self.global_delay_factor:
return delay_factor
else:
return self.global_delay_factor
def special_login_handler(self, delay_factor: float = 1.0) -> None:
"""Handler for devices like WLC, Extreme ERS that throw up characters prior to login."""
pass
def disable_paging(
self,
command: str = "terminal length 0",
delay_factor: Optional[float] = None,
cmd_verify: bool = True,
pattern: Optional[str] = None,
) -> str:
"""Disable paging default to a Cisco CLI method.
:param command: Device command to disable pagination of output
:param delay_factor: Deprecated in Netmiko 4.x. Will be eliminated in Netmiko 5.
:param cmd_verify: Verify command echo before proceeding (default: True).
:param pattern: Pattern to terminate reading of channel
"""
if delay_factor is not None:
warnings.warn(DELAY_FACTOR_DEPR_SIMPLE_MSG, DeprecationWarning)
command = self.normalize_cmd(command)
log.debug("In disable_paging")
log.debug(f"Command: {command}")
self.write_channel(command)
# Make sure you read until you detect the command echo (avoid getting out of sync)
if cmd_verify and self.global_cmd_verify is not False:
output = self.read_until_pattern(
pattern=re.escape(command.strip()), read_timeout=20
)
elif pattern:
output = self.read_until_pattern(pattern=pattern, read_timeout=20)
else:
output = self.read_until_prompt()
log.debug(f"{output}")
log.debug("Exiting disable_paging")
return output
def set_terminal_width(
self,
command: str = "",
delay_factor: Optional[float] = None,
cmd_verify: bool = False,
pattern: Optional[str] = None,
) -> str:
"""CLI terminals try to automatically adjust the line based on the width of the terminal.
This causes the output to get distorted when accessed programmatically.
Set terminal width to 511 which works on a broad set of devices.
:param command: Command string to send to the device
:param delay_factor: Deprecated in Netmiko 4.x. Will be eliminated in Netmiko 5.
"""
if delay_factor is not None:
warnings.warn(DELAY_FACTOR_DEPR_SIMPLE_MSG, DeprecationWarning)
if not command:
return ""
command = self.normalize_cmd(command)
self.write_channel(command)
# Avoid cmd_verify here as terminal width must be set before doing cmd_verify
if cmd_verify and self.global_cmd_verify is not False:
output = self.read_until_pattern(pattern=re.escape(command.strip()))
elif pattern:
output = self.read_until_pattern(pattern=pattern)
else:
output = self.read_until_prompt()
return output
def set_base_prompt(
self,
pri_prompt_terminator: str = "#",
alt_prompt_terminator: str = ">",
delay_factor: float = 1.0,
pattern: Optional[str] = None,
) -> str:
"""Sets self.base_prompt
Used as delimiter for stripping of trailing prompt in output.
Should be set to something that is general and applies in multiple contexts. For Cisco
devices this will be set to router hostname (i.e. prompt without > or #).
This will be set on entering user exec or privileged exec on Cisco, but not when
entering/exiting config mode.
:param pri_prompt_terminator: Primary trailing delimiter for identifying a device prompt
:param alt_prompt_terminator: Alternate trailing delimiter for identifying a device prompt
:param delay_factor: See __init__: global_delay_factor
:param pattern: Regular expression pattern to search for in find_prompt() call
"""
if pattern is None:
if pri_prompt_terminator and alt_prompt_terminator:
pri_term = re.escape(pri_prompt_terminator)
alt_term = re.escape(alt_prompt_terminator)
pattern = rf"({pri_term}|{alt_term})"
elif pri_prompt_terminator:
pattern = re.escape(pri_prompt_terminator)
elif alt_prompt_terminator:
pattern = re.escape(alt_prompt_terminator)
if pattern:
prompt = self.find_prompt(delay_factor=delay_factor, pattern=pattern)
else:
prompt = self.find_prompt(delay_factor=delay_factor)
if not prompt[-1] in (pri_prompt_terminator, alt_prompt_terminator):
raise ValueError(f"Router prompt not found: {repr(prompt)}")
# If all we have is the 'terminator' just use that :-(
if len(prompt) == 1:
self.base_prompt = prompt
else:
# Strip off trailing terminator
self.base_prompt = prompt[:-1]
return self.base_prompt
def find_prompt(
self, delay_factor: float = 1.0, pattern: Optional[str] = None
) -> str:
"""Finds the current network device prompt, last line only.
:param delay_factor: See __init__: global_delay_factor
:type delay_factor: int
:param pattern: Regular expression pattern to determine whether prompt is valid
"""
delay_factor = self.select_delay_factor(delay_factor)
sleep_time = delay_factor * 0.25
self.clear_buffer()
self.write_channel(self.RETURN)
if pattern:
prompt = self.read_until_pattern(pattern=pattern)
else:
# Initial read
time.sleep(sleep_time)
prompt = self.read_channel().strip()
count = 0
while count <= 12 and not prompt:
if not prompt:
self.write_channel(self.RETURN)
time.sleep(sleep_time)
prompt = self.read_channel().strip()
if sleep_time <= 3:
# Double the sleep_time when it is small
sleep_time *= 2
else:
sleep_time += 1
count += 1
# If multiple lines in the output take the last line
prompt = prompt.split(self.RESPONSE_RETURN)[-1]
prompt = prompt.strip()
self.clear_buffer()
if not prompt:
raise ValueError(f"Unable to find prompt: {prompt}")
log.debug(f"[find_prompt()]: prompt is {prompt}")
return prompt
def clear_buffer(
self,
backoff: bool = True,
backoff_max: float = 3.0,
delay_factor: Optional[float] = None,
) -> str:
"""Read any data available in the channel."""
if delay_factor is None:
delay_factor = self.global_delay_factor
sleep_time = 0.1 * delay_factor
output = ""
for _ in range(10):
time.sleep(sleep_time)
data = self.read_channel()
data = self.strip_ansi_escape_codes(data)
output += data
if not data:
break
# Double sleep time each time we detect data
log.debug("Clear buffer detects data in the channel")
if backoff:
sleep_time *= 2
sleep_time = backoff_max if sleep_time >= backoff_max else sleep_time
return output
def command_echo_read(self, cmd: str, read_timeout: float) -> str:
# Make sure you read until you detect the command echo (avoid getting out of sync)
new_data = self.read_until_pattern(
pattern=re.escape(cmd), read_timeout=read_timeout
)
# There can be echoed prompts that haven't been cleared before the cmd echo
# this can later mess up the trailing prompt pattern detection. Clear this out.
lines = new_data.split(cmd)
if len(lines) == 2:
# lines[-1] should realistically just be the null string
new_data = f"{cmd}{lines[-1]}"
else:
# cmd exists in the output multiple times? Just retain the original output
pass
return new_data
@flush_session_log
@select_cmd_verify
def send_command_timing(
self,
command_string: str,
last_read: float = 2.0,
read_timeout: float = 120.0,
delay_factor: Optional[float] = None,
max_loops: Optional[int] = None,
strip_prompt: bool = True,
strip_command: bool = True,
normalize: bool = True,
use_textfsm: bool = False,
textfsm_template: Optional[str] = None,
use_ttp: bool = False,
ttp_template: Optional[str] = None,
use_genie: bool = False,
cmd_verify: bool = False,
raise_parsing_error: bool = False,
) -> Union[str, List[Any], Dict[str, Any]]:
"""Execute command_string on the SSH channel using a delay-based mechanism. Generally
used for show commands.
:param command_string: The command to be executed on the remote device.
:param last_read: Time waited after end of data
:param read_timeout: Absolute timer for how long Netmiko should keep reading data on
the channel (waiting for there to be no new data). Will raise ReadTimeout if this
timeout expires. A read_timeout value of 0 will cause the read-loop to never timeout
(i.e. Netmiko will keep reading indefinitely until there is no new data and last_read
passes).
:param delay_factor: Deprecated in Netmiko 4.x. Will be eliminated in Netmiko 5.
:param max_loops: Deprecated in Netmiko 4.x. Will be eliminated in Netmiko 5.
:param strip_prompt: Remove the trailing router prompt from the output (default: True).
:param strip_command: Remove the echo of the command from the output (default: True).
:param normalize: Ensure the proper enter is sent at end of command (default: True).
:param use_textfsm: Process command output through TextFSM template (default: False).
:param textfsm_template: Name of template to parse output with; can be fully qualified
path, relative path, or name of file in current directory. (default: None).
:param use_ttp: Process command output through TTP template (default: False).
:param ttp_template: Name of template to parse output with; can be fully qualified
path, relative path, or name of file in current directory. (default: None).
:param use_genie: Process command output through PyATS/Genie parser (default: False).
:param cmd_verify: Verify command echo before proceeding (default: False).
:param raise_parsing_error: Raise exception when parsing output to structured data fails.
"""
if delay_factor is not None or max_loops is not None:
warnings.warn(DELAY_FACTOR_DEPR_SIMPLE_MSG, DeprecationWarning)
output = ""
new_data = ""
if normalize:
command_string = self.normalize_cmd(command_string)
self.write_channel(command_string)
cmd = command_string.strip()
if cmd and cmd_verify:
new_data = self.command_echo_read(cmd=cmd, read_timeout=10)
output += new_data
output += self.read_channel_timing(
last_read=last_read, read_timeout=read_timeout
)
output = self._sanitize_output(
output,
strip_command=strip_command,
command_string=command_string,
strip_prompt=strip_prompt,
)
return_data = structured_data_converter(
command=command_string,
raw_data=output,
platform=self.device_type,
use_textfsm=use_textfsm,
use_ttp=use_ttp,
use_genie=use_genie,
textfsm_template=textfsm_template,
ttp_template=ttp_template,
raise_parsing_error=raise_parsing_error,
)
return return_data
def _send_command_timing_str(self, *args: Any, **kwargs: Any) -> str:
"""Wrapper for `send_command_timing` method that always returns a
string"""
output = self.send_command_timing(*args, **kwargs)
assert isinstance(output, str)
return output
def strip_prompt(self, a_string: str) -> str:
"""Strip the trailing router prompt from the output.
:param a_string: Returned string from device
:type a_string: str
"""
response_list = a_string.split(self.RESPONSE_RETURN)
last_line = response_list[-1]
if self.base_prompt in last_line:
return self.RESPONSE_RETURN.join(response_list[:-1])
else:
return a_string
def _first_line_handler(self, data: str, search_pattern: str) -> Tuple[str, bool]:
"""
In certain situations the first line will get repainted which causes a false
match on the terminating pattern.
Filter this out.
returns a tuple of (data, first_line_processed)
Where data is the original data potentially with the first line modified
and the first_line_processed is a flag indicating that we have handled the
first line.
"""
try:
# First line is the echo line containing the command. In certain situations
# it gets repainted and needs filtered
lines = data.split(self.RETURN)
first_line = lines[0]
if BACKSPACE_CHAR in first_line:
pattern = search_pattern + r".*$"
first_line = re.sub(pattern, repl="", string=first_line)
lines[0] = first_line
data = self.RETURN.join(lines)
return (data, True)
except IndexError:
return (data, False)
def _prompt_handler(self, auto_find_prompt: bool) -> str:
if auto_find_prompt:
try:
prompt = self.find_prompt()
except ValueError:
prompt = self.base_prompt
else:
prompt = self.base_prompt
return re.escape(prompt.strip())
@flush_session_log
@select_cmd_verify
def send_command(
self,
command_string: str,
expect_string: Optional[str] = None,
read_timeout: float = 10.0,
delay_factor: Optional[float] = None,
max_loops: Optional[int] = None,
auto_find_prompt: bool = True,
strip_prompt: bool = True,
strip_command: bool = True,
normalize: bool = True,
use_textfsm: bool = False,
textfsm_template: Optional[str] = None,
use_ttp: bool = False,
ttp_template: Optional[str] = None,
use_genie: bool = False,
cmd_verify: bool = True,
raise_parsing_error: bool = False,
) -> Union[str, List[Any], Dict[str, Any]]:
"""Execute command_string on the SSH channel using a pattern-based mechanism. Generally
used for show commands. By default this method will keep waiting to receive data until the
network device prompt is detected. The current network device prompt will be determined
automatically.
:param command_string: The command to be executed on the remote device.
:param expect_string: Regular expression pattern to use for determining end of output.
If left blank will default to being based on router prompt.
:param read_timeout: Maximum time to wait looking for pattern. Will raise ReadTimeout
if timeout is exceeded.
:param delay_factor: Deprecated in Netmiko 4.x. Will be eliminated in Netmiko 5.
:param max_loops: Deprecated in Netmiko 4.x. Will be eliminated in Netmiko 5.
:param auto_find_prompt: Use find_prompt() to override base prompt
:param strip_prompt: Remove the trailing router prompt from the output (default: True).
:param strip_command: Remove the echo of the command from the output (default: True).
:param normalize: Ensure the proper enter is sent at end of command (default: True).
:param use_textfsm: Process command output through TextFSM template (default: False).
:param textfsm_template: Name of template to parse output with; can be fully qualified
path, relative path, or name of file in current directory. (default: None).
:param use_ttp: Process command output through TTP template (default: False).
:param ttp_template: Name of template to parse output with; can be fully qualified
path, relative path, or name of file in current directory. (default: None).
:param use_genie: Process command output through PyATS/Genie parser (default: False).
:param cmd_verify: Verify command echo before proceeding (default: True).
:param raise_parsing_error: Raise exception when parsing output to structured data fails.
"""
# Time to delay in each read loop
loop_delay = 0.025
if self.read_timeout_override:
read_timeout = self.read_timeout_override
if self.delay_factor_compat:
# For compatibility calculate the old equivalent read_timeout
# i.e. what it would have been in Netmiko 3.x
if delay_factor is None:
tmp_delay_factor = self.global_delay_factor
else:
tmp_delay_factor = self.select_delay_factor(delay_factor)
compat_timeout = calc_old_timeout(
max_loops=max_loops,
delay_factor=tmp_delay_factor,
loop_delay=0.2,
old_timeout=self.timeout,
)
msg = f"""\n
You have chosen to use Netmiko's delay_factor compatibility mode for
send_command. This will revert Netmiko to behave similarly to how it
did in Netmiko 3.x (i.e. to use delay_factor/global_delay_factor and
max_loops).
Using these parameters Netmiko has calculated an effective read_timeout
of {compat_timeout} and will set the read_timeout to this value.
Please convert your code to that new format i.e.:
net_connect.send_command(cmd, read_timeout={compat_timeout})
And then disable delay_factor_compat.
delay_factor_compat will be removed in Netmiko 5.x.\n"""
warnings.warn(msg, DeprecationWarning)
# Override the read_timeout with Netmiko 3.x way :-(
read_timeout = compat_timeout
else:
# No need for two deprecation messages so only display this if not using
# delay_factor_compat
if delay_factor is not None or max_loops is not None:
msg = """\n
Netmiko 4.x has deprecated the use of delay_factor/max_loops with
send_command. You should convert all uses of delay_factor and max_loops
over to read_timeout=x where x is the total number of seconds to wait
before timing out.\n"""
warnings.warn(msg, DeprecationWarning)
if expect_string is not None:
search_pattern = expect_string
else:
search_pattern = self._prompt_handler(auto_find_prompt)
if normalize:
command_string = self.normalize_cmd(command_string)
# Start the clock
start_time = time.time()
self.write_channel(command_string)
new_data = ""
cmd = command_string.strip()
if cmd and cmd_verify:
new_data = self.command_echo_read(cmd=cmd, read_timeout=10)
MAX_CHARS = 2_000_000
DEQUE_SIZE = 20
output = ""
# Check only the past N-reads. This is for the case where the output is
# very large (i.e. searching a very large string for a pattern a whole bunch of times)
past_n_reads: Deque[str] = deque(maxlen=DEQUE_SIZE)
first_line_processed = False
# Keep reading data until search_pattern is found or until read_timeout
while time.time() - start_time < read_timeout:
if new_data:
output += new_data
past_n_reads.append(new_data)
# Case where we haven't processed the first_line yet (there is a potential issue
# in the first line (in cases where the line is repainted).
if not first_line_processed:
output, first_line_processed = self._first_line_handler(
output, search_pattern
)
# Check if we have already found our pattern
if re.search(search_pattern, output):
break
else:
if len(output) <= MAX_CHARS:
if re.search(search_pattern, output):
break
else:
# Switch to deque mode if output is greater than MAX_CHARS
# Check if pattern is in the past n reads
if re.search(search_pattern, "".join(past_n_reads)):
break
time.sleep(loop_delay)
new_data = self.read_channel()
else: # nobreak
msg = f"""
Pattern not detected: {repr(search_pattern)} in output.
Things you might try to fix this:
1. Explicitly set your pattern using the expect_string argument.
2. Increase the read_timeout to a larger value.
You can also look at the Netmiko session_log or debug log for more information.
"""
raise ReadTimeout(msg)
output = self._sanitize_output(
output,
strip_command=strip_command,
command_string=command_string,
strip_prompt=strip_prompt,
)
return_val = structured_data_converter(
command=command_string,
raw_data=output,
platform=self.device_type,
use_textfsm=use_textfsm,
use_ttp=use_ttp,
use_genie=use_genie,
textfsm_template=textfsm_template,
ttp_template=ttp_template,
raise_parsing_error=raise_parsing_error,
)
return return_val
def _send_command_str(self, *args: Any, **kwargs: Any) -> str:
"""Wrapper for `send_command` method that always returns a string"""
output = self.send_command(*args, **kwargs)
assert isinstance(output, str)
return output
def send_command_expect(
self, *args: Any, **kwargs: Any
) -> Union[str, List[Any], Dict[str, Any]]:
"""Support previous name of send_command method."""
return self.send_command(*args, **kwargs)
def _multiline_kwargs(self, **kwargs: Any) -> Dict[str, Any]:
strip_prompt = kwargs.get("strip_prompt", False)
kwargs["strip_prompt"] = strip_prompt
strip_command = kwargs.get("strip_command", False)
kwargs["strip_command"] = strip_command
return kwargs
def send_multiline(
self,
commands: Sequence[Union[str, List[str]]],
multiline: bool = True,
**kwargs: Any,
) -> str:
"""
commands should either be:
commands = [[cmd1, expect1], [cmd2, expect2], ...]]
Or
commands = [cmd1, cmd2, cmd3, ...]
Any expect_string that is a null-string will use pattern based on
device's prompt (unless expect_string argument is passed in via
kwargs.
"""
output = ""
if multiline:
kwargs = self._multiline_kwargs(**kwargs)
default_expect_string = kwargs.pop("expect_string", None)
if not default_expect_string:
auto_find_prompt = kwargs.get("auto_find_prompt", True)
default_expect_string = self._prompt_handler(auto_find_prompt)
if commands and isinstance(commands[0], str):
# If list of commands just send directly using default_expect_string (probably prompt)
for cmd in commands:
cmd = str(cmd)
output += self._send_command_str(
cmd, expect_string=default_expect_string, **kwargs
)
else:
# If list of lists, then first element is cmd and second element is expect_string
for cmd_item in commands:
assert not isinstance(cmd_item, str)
cmd, expect_string = cmd_item
if not expect_string:
expect_string = default_expect_string
output += self._send_command_str(
cmd, expect_string=expect_string, **kwargs
)
return output
def send_multiline_timing(
self, commands: Sequence[str], multiline: bool = True, **kwargs: Any
) -> str:
if multiline:
kwargs = self._multiline_kwargs(**kwargs)
output = ""
for cmd in commands:
cmd = str(cmd)
output += self._send_command_timing_str(cmd, **kwargs)
return output
@staticmethod
def strip_backspaces(output: str) -> str:
"""Strip any backspace characters out of the output.
:param output: Output obtained from a remote network device.
:type output: str
"""
backspace_char = "\x08"
return output.replace(backspace_char, "")
def strip_command(self, command_string: str, output: str) -> str:
"""
Strip command_string from output string
Cisco IOS adds backspaces into output for long commands (i.e. for commands that line wrap)
:param command_string: The command string sent to the device
:type command_string: str
:param output: The returned output as a result of the command string sent to the device
:type output: str
"""
backspace_char = "\x08"
# Check for line wrap (remove backspaces)
if backspace_char in output:
output = output.replace(backspace_char, "")
# Juniper has a weird case where the echoed command will be " \n"
# i.e. there is an extra space there.
cmd = command_string.strip()
if output.startswith(cmd):
output_lines = output.split(self.RESPONSE_RETURN)
new_output = output_lines[1:]
return self.RESPONSE_RETURN.join(new_output)
else:
# command_string isn't there; do nothing
return output
def normalize_linefeeds(self, a_string: str) -> str:
"""Convert `\r\r\n`,`\r\n`, `\n\r` to `\n.`
:param a_string: A string that may have non-normalized line feeds
i.e. output returned from device, or a device prompt
:type a_string: str
"""
newline = re.compile("(\r\r\r\n|\r\r\n|\r\n|\n\r)")
a_string = newline.sub(self.RESPONSE_RETURN, a_string)
if self.RESPONSE_RETURN == "\n":
# Convert any remaining \r to \n
return re.sub("\r", self.RESPONSE_RETURN, a_string)
else:
return a_string
def normalize_cmd(self, command: str) -> str:
"""Normalize CLI commands to have a single trailing newline.
:param command: Command that may require line feed to be normalized
:type command: str
"""
command = command.rstrip()
command += self.RETURN
return command
def check_enable_mode(self, check_string: str = "") -> bool:
"""Check if in enable mode. Return boolean.
:param check_string: Identification of privilege mode from device
:type check_string: str
"""
self.write_channel(self.RETURN)
output = self.read_until_prompt(read_entire_line=True)
return check_string in output
def enable(
self,
cmd: str = "",
pattern: str = "ssword",
enable_pattern: Optional[str] = None,
check_state: bool = True,
re_flags: int = re.IGNORECASE,
) -> str:
"""Enter enable mode.
:param cmd: Device command to enter enable mode
:param pattern: pattern to search for indicating device is waiting for password
:param enable_pattern: pattern indicating you have entered enable mode
:param check_state: Determine whether we are already in enable_mode using
check_enable_mode() before trying to elevate privileges (default: True)
:param re_flags: Regular expression flags used in conjunction with pattern
"""
output = ""
msg = (
"Failed to enter enable mode. Please ensure you pass "
"the 'secret' argument to ConnectHandler."
)
# Check if in enable mode already.
if check_state and self.check_enable_mode():
return output
# Send "enable" mode command
self.write_channel(self.normalize_cmd(cmd))
try:
# Read the command echo
if self.global_cmd_verify is not False:
output += self.read_until_pattern(pattern=re.escape(cmd.strip()))
# Search for trailing prompt or password pattern
output += self.read_until_prompt_or_pattern(
pattern=pattern, re_flags=re_flags
)
# Send the "secret" in response to password pattern
if re.search(pattern, output):
self.write_channel(self.normalize_cmd(self.secret))
output += self.read_until_prompt()
# Search for terminating pattern if defined
if enable_pattern and not re.search(enable_pattern, output):
output += self.read_until_pattern(pattern=enable_pattern)
else:
if not self.check_enable_mode():
raise ValueError(msg)
except NetmikoTimeoutException:
raise ValueError(msg)
return output
def exit_enable_mode(self, exit_command: str = "") -> str:
"""Exit enable mode.
:param exit_command: Command that exits the session from privileged mode
:type exit_command: str
"""
output = ""
if self.check_enable_mode():
self.write_channel(self.normalize_cmd(exit_command))
output += self.read_until_prompt()
if self.check_enable_mode():
raise ValueError("Failed to exit enable mode.")
return output
def check_config_mode(
self, check_string: str = "", pattern: str = "", force_regex: bool = False
) -> bool:
"""Checks if the device is in configuration mode or not.
:param check_string: Identification of configuration mode from the device
:type check_string: str
:param pattern: Pattern to terminate reading of channel
:type pattern: str
:param force_regex: Use regular expression pattern to find check_string in output
:type force_regex: bool
"""
self.write_channel(self.RETURN)
# You can encounter an issue here (on router name changes) prefer delay-based solution
if not pattern:
output = self.read_channel_timing(read_timeout=10.0)
else:
output = self.read_until_pattern(pattern=pattern)
if force_regex:
return bool(re.search(check_string, output))
else:
return check_string in output
def config_mode(
self, config_command: str = "", pattern: str = "", re_flags: int = 0
) -> str:
"""Enter into config_mode.
:param config_command: Configuration command to send to the device
:type config_command: str
:param pattern: Pattern to terminate reading of channel
:type pattern: str
:param re_flags: Regular expression flags
:type re_flags: RegexFlag
"""
output = ""
if not self.check_config_mode():
self.write_channel(self.normalize_cmd(config_command))
# Make sure you read until you detect the command echo (avoid getting out of sync)
if self.global_cmd_verify is not False:
output += self.read_until_pattern(
pattern=re.escape(config_command.strip())
)
if pattern:
output += self.read_until_pattern(pattern=pattern, re_flags=re_flags)
else:
output += self.read_until_prompt(read_entire_line=True)
if not self.check_config_mode():
raise ValueError("Failed to enter configuration mode.")
return output
def exit_config_mode(self, exit_config: str = "", pattern: str = "") -> str:
"""Exit from configuration mode.
:param exit_config: Command to exit configuration mode
:type exit_config: str
:param pattern: Pattern to terminate reading of channel
:type pattern: str
"""
output = ""
if self.check_config_mode():
self.write_channel(self.normalize_cmd(exit_config))
# Make sure you read until you detect the command echo (avoid getting out of sync)
if self.global_cmd_verify is not False:
output += self.read_until_pattern(
pattern=re.escape(exit_config.strip())
)
if pattern:
output += self.read_until_pattern(pattern=pattern)
else:
output += self.read_until_prompt(read_entire_line=True)
if self.check_config_mode():
raise ValueError("Failed to exit configuration mode")
log.debug(f"exit_config_mode: {output}")
return output
def send_config_from_file(
self, config_file: Union[str, bytes, "PathLike[Any]"], **kwargs: Any
) -> str:
"""
Send configuration commands down the SSH channel from a file.
The file is processed line-by-line and each command is sent down the
SSH channel.
**kwargs are passed to send_config_set method.
:param config_file: Path to configuration file to be sent to the device
:param kwargs: params to be sent to send_config_set method
"""
with io.open(config_file, "rt", encoding="utf-8") as cfg_file:
commands = cfg_file.readlines()
return self.send_config_set(commands, **kwargs)
@flush_session_log
def send_config_set(
self,
config_commands: Union[str, Sequence[str], Iterator[str], TextIO, None] = None,
*,
exit_config_mode: bool = True,
read_timeout: Optional[float] = None,
delay_factor: Optional[float] = None,
max_loops: Optional[int] = None,
strip_prompt: bool = False,
strip_command: bool = False,
config_mode_command: Optional[str] = None,
cmd_verify: bool = True,
enter_config_mode: bool = True,
error_pattern: str = "",
terminator: str = r"#",
bypass_commands: Optional[str] = None,
) -> str:
"""
Send configuration commands down the SSH channel.
config_commands is an iterable containing all of the configuration commands.
The commands will be executed one after the other.
Automatically exits/enters configuration mode.
:param config_commands: Multiple configuration commands to be sent to the device
:param exit_config_mode: Determines whether or not to exit config mode after complete
:param delay_factor: Deprecated in Netmiko 4.x. Will be eliminated in Netmiko 5.
:param max_loops: Deprecated in Netmiko 4.x. Will be eliminated in Netmiko 5.
:param strip_prompt: Determines whether or not to strip the prompt
:param strip_command: Determines whether or not to strip the command
:param read_timeout: Absolute timer to send to read_channel_timing. Also adjusts
read_timeout in read_until_pattern calls.
:param config_mode_command: The command to enter into config mode
:param cmd_verify: Whether or not to verify command echo for each command in config_set
:param enter_config_mode: Do you enter config mode before sending config commands
:param error_pattern: Regular expression pattern to detect config errors in the
output.
:param terminator: Regular expression pattern to use as an alternate terminator in certain
situations.
:param bypass_commands: Regular expression pattern indicating configuration commands
where cmd_verify is automatically disabled.
"""
if self.global_cmd_verify is not None:
cmd_verify = self.global_cmd_verify
if delay_factor is not None or max_loops is not None:
warnings.warn(DELAY_FACTOR_DEPR_SIMPLE_MSG, DeprecationWarning)
# Calculate an equivalent read_timeout (if using old settings)
# Eliminate in Netmiko 5.x
if read_timeout is None:
max_loops = 150 if max_loops is None else max_loops
delay_factor = 1.0 if delay_factor is None else delay_factor
# If delay_factor has been set, then look at global_delay_factor
delay_factor = self.select_delay_factor(delay_factor)
read_timeout = calc_old_timeout(
max_loops=max_loops, delay_factor=delay_factor, loop_delay=0.1
)
if delay_factor is None:
delay_factor = self.select_delay_factor(0)
else:
delay_factor = self.select_delay_factor(delay_factor)
if read_timeout is None:
read_timeout = 15
else:
read_timeout = read_timeout
if config_commands is None:
return ""
elif isinstance(config_commands, str):
config_commands = (config_commands,)
if not hasattr(config_commands, "__iter__"):
raise ValueError("Invalid argument passed into send_config_set")
if bypass_commands is None:
# Commands where cmd_verify is automatically disabled reg-ex logical-or
bypass_commands = r"^banner .*$"
# Set bypass_commands="" to force no-bypass (usually for testing)
bypass_detected = False
if bypass_commands:
# Make a copy of the iterator
config_commands, config_commands_tmp = itertools.tee(config_commands, 2)
bypass_detected = any(
[True for cmd in config_commands_tmp if re.search(bypass_commands, cmd)]
)
if bypass_detected:
cmd_verify = False
# Send config commands
output = ""
if enter_config_mode:
if config_mode_command:
output += self.config_mode(config_mode_command)
else:
output += self.config_mode()
# Perform output gathering line-by-line (legacy way)
if self.fast_cli and self._legacy_mode and not error_pattern:
for cmd in config_commands:
self.write_channel(self.normalize_cmd(cmd))
# Gather output
output += self.read_channel_timing(read_timeout=read_timeout)
elif not cmd_verify:
for cmd in config_commands:
self.write_channel(self.normalize_cmd(cmd))
time.sleep(delay_factor * 0.05)
# Gather the output incrementally due to error_pattern requirements
if error_pattern:
output += self.read_channel_timing(read_timeout=read_timeout)
if re.search(error_pattern, output, flags=re.M):
msg = f"Invalid input detected at command: {cmd}"
raise ConfigInvalidException(msg)
# Standard output gathering (no error_pattern)
if not error_pattern:
output += self.read_channel_timing(read_timeout=read_timeout)
else:
for cmd in config_commands:
self.write_channel(self.normalize_cmd(cmd))
# Make sure command is echoed
output += self.read_until_pattern(
pattern=re.escape(cmd.strip()), read_timeout=read_timeout
)
# Read until next prompt or terminator (#); the .*$ forces read of entire line
pattern = f"(?:{re.escape(self.base_prompt)}.*$|{terminator}.*$)"
output += self.read_until_pattern(
pattern=pattern, read_timeout=read_timeout, re_flags=re.M
)
if error_pattern:
if re.search(error_pattern, output, flags=re.M):
msg = f"Invalid input detected at command: {cmd}"
raise ConfigInvalidException(msg)
if exit_config_mode:
output += self.exit_config_mode()
output = self._sanitize_output(output)
log.debug(f"{output}")
return output
def strip_ansi_escape_codes(self, string_buffer: str) -> str:
"""
Remove any ANSI (VT100) ESC codes from the output
http://en.wikipedia.org/wiki/ANSI_escape_code
Note: this does not capture ALL possible ANSI Escape Codes only the ones
that have been encountered
Current codes that are filtered:
ESC = '\x1b' or chr(27)
ESC = is the escape character [^ in hex ('\x1b')
ESC[24;27H Position cursor
ESC[?25h Show the cursor
ESC[E Next line (HP does ESC-E)
ESC[K Erase line from cursor to the end of line
ESC[2K Erase entire line
ESC[1;24r Enable scrolling from start to row end
ESC[?6l Reset mode screen with options 640 x 200 monochrome (graphics)
ESC[?7l Disable line wrapping
ESC[2J Code erase display
ESC[00;32m Color Green (30 to 37 are different colors)
ESC[6n Get cursor position
ESC[1D Move cursor position leftward by x characters (1 in this case)
ESC[9999B Move cursor down N-lines (very large value is attempt to move to the
very bottom of the screen)
ESC[c Query Device (used by MikroTik in 'Safe-Mode')
ESC[2004h Enable bracketed paste mode
ESC[2004l Disable bracketed paste mode
:param string_buffer: The string to be processed to remove ANSI escape codes
:type string_buffer: str
""" # noqa
code_position_cursor = chr(27) + r"\[\d+;\d+H"
code_show_cursor = chr(27) + r"\[\?25h"
code_next_line = chr(27) + r"E"
code_erase_line_end = chr(27) + r"\[K"
code_erase_line = chr(27) + r"\[2K"
code_erase_start_line = chr(27) + r"\[K"
code_enable_scroll = chr(27) + r"\[\d+;\d+r"
code_insert_line = chr(27) + r"\[(\d+)L"
code_carriage_return = chr(27) + r"\[1M"
code_disable_line_wrapping = chr(27) + r"\[\?7l"
code_reset_mode_screen_options = chr(27) + r"\[\?\d+l"
code_reset_graphics_mode = chr(27) + r"\[00m"
code_erase_display = chr(27) + r"\[2J"
code_erase_display_0 = chr(27) + r"\[J"
code_graphics_mode = chr(27) + r"\[\dm"
code_graphics_mode1 = chr(27) + r"\[\d\d;\d\dm"
code_graphics_mode2 = chr(27) + r"\[\d\d;\d\d;\d\dm"
code_graphics_mode3 = chr(27) + r"\[(3|4)\dm"
code_graphics_mode4 = chr(27) + r"\[(9|10)[0-7]m"
code_get_cursor_position = chr(27) + r"\[6n"
code_cursor_position = chr(27) + r"\[m"
code_attrs_off = chr(27) + r"\[0m"
code_reverse = chr(27) + r"\[7m"
code_cursor_left = chr(27) + r"\[\d+D"
code_cursor_forward = chr(27) + r"\[\d*C"
code_cursor_up = chr(27) + r"\[\d*A"
code_cursor_down = chr(27) + r"\[\d*B"
code_wrap_around = chr(27) + r"\[\?7h"
code_enable_bracketed_paste_mode = chr(27) + r"\[\?2004h"
code_disable_bracketed_paste_mode = chr(27) + r"\[\?2004l"
code_underline = chr(27) + r"\[4m"
code_query_device = chr(27) + r"\[c"
code_set = [
code_position_cursor,
code_show_cursor,
code_erase_line,
code_enable_scroll,
code_erase_start_line,
code_carriage_return,
code_disable_line_wrapping,
code_erase_line_end,
code_reset_mode_screen_options,
code_reset_graphics_mode,
code_erase_display,
code_graphics_mode,
code_graphics_mode1,
code_graphics_mode2,
code_graphics_mode3,
code_graphics_mode4,
code_get_cursor_position,
code_cursor_position,
code_erase_display,
code_erase_display_0,
code_attrs_off,
code_reverse,
code_cursor_left,
code_cursor_up,
code_cursor_down,
code_cursor_forward,
code_wrap_around,
code_enable_bracketed_paste_mode,
code_disable_bracketed_paste_mode,
code_underline,
code_query_device,
]
output = string_buffer
for ansi_esc_code in code_set:
output = re.sub(ansi_esc_code, "", output)
# CODE_NEXT_LINE must substitute with return
output = re.sub(code_next_line, self.RETURN, output)
# Aruba and ProCurve switches can use code_insert_line for
insert_line_match = re.search(code_insert_line, output)
if insert_line_match:
# Substitute each insert_line with a new
count = int(insert_line_match.group(1))
output = re.sub(code_insert_line, count * self.RETURN, output)
return output
def cleanup(self, command: str = "") -> None:
"""Logout of the session on the network device plus any additional cleanup."""
pass
def paramiko_cleanup(self) -> None:
"""Cleanup Paramiko to try to gracefully handle SSH session ending."""
if self.remote_conn_pre is not None:
self.remote_conn_pre.close()
del self.remote_conn_pre
def disconnect(self) -> None:
"""Try to gracefully close the session."""
try:
self.cleanup()
except Exception:
# Keep going on cleanup process even if exceptions
pass
try:
if self.protocol == "ssh":
self.paramiko_cleanup()
elif self.protocol == "telnet":
assert isinstance(self.remote_conn, telnetlib.Telnet)
self.remote_conn.close() # type: ignore
elif self.protocol == "serial":
assert isinstance(self.remote_conn, serial.Serial)
self.remote_conn.close()
except Exception:
# There have been race conditions observed on disconnect.
pass
finally:
self.remote_conn_pre = None
self.remote_conn = None
if self.session_log:
self.session_log.close()
log.removeFilter(self._secrets_filter)
def commit(self) -> str:
"""Commit method for platforms that support this."""
raise AttributeError("Network device does not support 'commit()' method")
def save_config(
self, cmd: str = "", confirm: bool = False, confirm_response: str = ""
) -> str:
"""Not Implemented"""
raise NotImplementedError
def run_ttp(
self,
template: Union[str, bytes, "PathLike[Any]"],
res_kwargs: Optional[Dict[str, Any]] = None,
**kwargs: Any,
) -> Any:
"""
Run TTP template parsing by using input parameters to collect
devices output.
:param template: template content, OS path to template or reference
to template within TTP templates collection in
ttp://path/to/template.txt format
:param res_kwargs: ``**res_kwargs`` arguments to pass to TTP result method
:param kwargs: any other ``**kwargs`` to use for TTP object instantiation
TTP template must have inputs defined together with below parameters.
:param method: name of Netmiko connection object method to call, default ``send_command``
:param kwargs: Netmiko connection object method arguments
:param commands: list of commands to collect
Inputs' load could be of one of the supported formats and controlled by input's ``load``
attribute, supported values - python, yaml or json. For each input output collected
from device and parsed accordingly.
"""
if res_kwargs is None:
res_kwargs = {}
return run_ttp_template(
connection=self, template=template, res_kwargs=res_kwargs, **kwargs
)
class TelnetConnection(BaseConnection):
pass
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1719528567.9239757
netmiko-4.5.0/netmiko/broadcom/__init__.py 0000644 0000000 0000000 00000000143 14637366170 015474 0 ustar 00 from netmiko.broadcom.broadcom_icos_ssh import BroadcomIcosSSH
__all__ = ["BroadcomIcosSSH"]
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1719528567.9239757
netmiko-4.5.0/netmiko/broadcom/broadcom_icos_ssh.py 0000644 0000000 0000000 00000003326 14637366170 017423 0 ustar 00 from netmiko.cisco_base_connection import CiscoSSHConnection
class BroadcomIcosSSH(CiscoSSHConnection):
"""
Implements support for Broadcom Icos devices.
Syntax its almost identical to Cisco IOS in most cases
"""
def session_preparation(self) -> None:
self._test_channel_read(pattern=r"[>#]")
self.set_base_prompt()
self.enable()
self.set_base_prompt()
self.set_terminal_width()
self.disable_paging()
def check_config_mode(
self, check_string: str = ")#", pattern: str = "", force_regex: bool = False
) -> bool:
"""Checks if the device is in configuration mode or not."""
return super().check_config_mode(check_string=check_string, pattern=pattern)
def config_mode(
self, config_command: str = "configure", pattern: str = "", re_flags: int = 0
) -> str:
"""Enter configuration mode."""
return super().config_mode(
config_command=config_command, pattern=pattern, re_flags=re_flags
)
def exit_config_mode(self, exit_config: str = "exit", pattern: str = "") -> str:
"""Exit configuration mode."""
return super().exit_config_mode(exit_config=exit_config)
def exit_enable_mode(self, exit_command: str = "exit") -> str:
"""Exit enable mode."""
return super().exit_enable_mode(exit_command=exit_command)
def save_config(
self,
cmd: str = "write memory",
confirm: bool = False,
confirm_response: str = "",
) -> str:
"""Saves configuration."""
return super().save_config(
cmd=cmd, confirm=confirm, confirm_response=confirm_response
)
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1719528567.9239757
netmiko-4.5.0/netmiko/brocade/__init__.py 0000644 0000000 0000000 00000000127 14637366170 015307 0 ustar 00 from netmiko.brocade.brocade_fos_ssh import BrocadeFOSSSH
__all__ = ["BrocadeFOSSSH"]
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1719528567.9239757
netmiko-4.5.0/netmiko/brocade/brocade_fos_ssh.py 0000644 0000000 0000000 00000001063 14637366170 016673 0 ustar 00 from typing import Any
from netmiko.no_enable import NoEnable
from netmiko.no_config import NoConfig
from netmiko.cisco_base_connection import CiscoSSHConnection
class BrocadeFOSSSH(NoEnable, NoConfig, CiscoSSHConnection):
"""Brocade Fabric OS support"""
def __init__(self, **kwargs: Any) -> None:
if kwargs.get("default_enter") is None:
kwargs["default_enter"] = "\r"
return super().__init__(**kwargs)
def session_preparation(self) -> None:
self._test_channel_read(pattern=r">")
self.set_base_prompt()
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1719528567.9239757
netmiko-4.5.0/netmiko/calix/__init__.py 0000644 0000000 0000000 00000000150 14637366170 015004 0 ustar 00 from netmiko.calix.calix_b6 import CalixB6SSH, CalixB6Telnet
__all__ = ["CalixB6SSH", "CalixB6Telnet"]
././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1725389754.726303
netmiko-4.5.0/netmiko/calix/calix_b6.py 0000644 0000000 0000000 00000007466 14665655673 014767 0 ustar 00 """Calix B6 SSH Driver for Netmiko"""
from typing import Any
import time
from os import path
from paramiko import SSHClient
from netmiko.cisco_base_connection import CiscoSSHConnection
from netmiko.ssh_auth import SSHClient_noauth
from netmiko.exceptions import NetmikoTimeoutException
class CalixB6Base(CiscoSSHConnection):
"""Common methods for Calix B6, both SSH and Telnet."""
def __init__(self, *args: Any, **kwargs: Any) -> None:
default_enter = kwargs.get("default_enter")
kwargs["default_enter"] = "\r\n" if default_enter is None else default_enter
super().__init__(*args, **kwargs)
def session_preparation(self) -> Any:
"""Prepare the session after the connection has been established."""
self.ansi_escape_codes = True
self._test_channel_read(pattern=r"[>#]")
self.set_base_prompt()
self.set_terminal_width(command="terminal width 511", pattern="terminal")
self.disable_paging()
def special_login_handler(self, delay_factor: float = 1.0) -> None:
"""
Calix B6 presents with the following on login:
login as:
Password: ****
"""
new_data = ""
time.sleep(0.1)
start = time.time()
login_timeout = 20
while time.time() - start < login_timeout:
output = self.read_channel() if not new_data else new_data
new_data = ""
if output:
if "login as:" in output:
assert isinstance(self.username, str)
self.write_channel(self.username + self.RETURN)
elif "Password:" in output:
assert isinstance(self.password, str)
self.write_channel(self.password + self.RETURN)
break
time.sleep(0.1)
else:
# No new data...sleep longer
time.sleep(0.5)
new_data = self.read_channel()
# If still no data, send an
if not new_data:
self.write_channel(self.RETURN)
else: # no-break
msg = """
Login process failed to Calix B6 device. Unable to login in {login_timeout} seconds.
"""
raise NetmikoTimeoutException(msg)
def check_config_mode(
self, check_string: str = ")#", pattern: str = "", force_regex: bool = False
) -> bool:
"""Checks if the device is in configuration mode"""
return super().check_config_mode(check_string=check_string)
def save_config(
self,
cmd: str = "copy run start",
confirm: bool = False,
confirm_response: str = "",
) -> str:
return super().save_config(
cmd=cmd, confirm=confirm, confirm_response=confirm_response
)
class CalixB6SSH(CalixB6Base):
"""Calix B6 SSH Driver.
To make it work, we have to override the SSHClient _auth method and manually handle
the username/password.
"""
def _build_ssh_client(self) -> SSHClient:
"""Prepare for Paramiko SSH connection."""
# Create instance of SSHClient object
# If not using SSH keys, we use noauth
if not self.use_keys:
remote_conn_pre: SSHClient = SSHClient_noauth()
else:
remote_conn_pre = SSHClient()
# Load host_keys for better SSH security
if self.system_host_keys:
remote_conn_pre.load_system_host_keys()
if self.alt_host_keys and path.isfile(self.alt_key_file):
remote_conn_pre.load_host_keys(self.alt_key_file)
# Default is to automatically add untrusted hosts (make sure appropriate for your env)
remote_conn_pre.set_missing_host_key_policy(self.key_policy)
return remote_conn_pre
class CalixB6Telnet(CalixB6Base):
"""Calix B6 Telnet Driver."""
pass
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1719528567.9239757
netmiko-4.5.0/netmiko/casa/__init__.py 0000644 0000000 0000000 00000000150 14637366170 014613 0 ustar 00 from netmiko.casa.casa_cmts import CasaCMTSBase, CasaCMTSSSH
__all__ = ["CasaCMTSBase", "CasaCMTSSSH"]
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1719528567.9239757
netmiko-4.5.0/netmiko/casa/casa_cmts.py 0000644 0000000 0000000 00000003653 14637366170 015024 0 ustar 00 from typing import Optional
from netmiko.cisco_base_connection import CiscoSSHConnection
from netmiko.no_enable import NoEnable
class CasaCMTSBase(NoEnable, CiscoSSHConnection):
"""
Casa CMTS support.
Implements methods for interacting with Casa CMTS platforms.
"""
def disable_paging(
self,
command: str = "page-off",
delay_factor: Optional[float] = None,
cmd_verify: bool = True,
pattern: Optional[str] = None,
) -> str:
"""Disables paging."""
return super().disable_paging(
command=command,
delay_factor=delay_factor,
cmd_verify=cmd_verify,
pattern=pattern,
)
def config_mode(
self,
config_command: str = "config",
pattern: str = "",
re_flags: int = 0,
) -> str:
"""Enters configuration mode."""
return super().config_mode(
config_command=config_command,
pattern=pattern,
re_flags=re_flags,
)
def exit_config_mode(
self, exit_config: str = chr(26), pattern: str = r"#.*"
) -> str:
"""
Exits configuration mode.
Must use CTRL-Z (ASCII 26) to reliably exit from any
tier in the configuration hierarchy.
Since CTRL-Z is a non-printable character, we must temporarily disable
global_cmd_verify to prevent an exception trying to read the
echoed input.
"""
if self.global_cmd_verify is not False and exit_config == chr(26):
global_cmd_verify_tmp = self.global_cmd_verify
self.global_cmd_verify = False
output = super().exit_config_mode(exit_config, pattern)
self.global_cmd_verify = global_cmd_verify_tmp
else:
output = super().exit_config_mode(exit_config, pattern)
return output
class CasaCMTSSSH(CasaCMTSBase):
"""Casa CMTS SSH Driver."""
pass
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1719528567.9239757
netmiko-4.5.0/netmiko/cdot/__init__.py 0000644 0000000 0000000 00000000116 14637366170 014637 0 ustar 00 from netmiko.cdot.cdot_cros_ssh import CdotCrosSSH
__all__ = ["CdotCrosSSH"]
././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1719528567.933976
netmiko-4.5.0/netmiko/cdot/cdot_cros_ssh.py 0000644 0000000 0000000 00000007753 14637366170 015752 0 ustar 00 #!/usr/bin/env python
# CDOT = Centre for Development of Telematics, India
# CROS = CDOT Router OS
# Script: cros_ssh.py
# Author: Maloy Ghosh
# Updated by Kirk Byers
#
# Purpose: Provide basic SSH connection to CROS based router products
from typing import Optional, Union, Sequence, Iterator, TextIO, Any
import time
import warnings
from netmiko.no_enable import NoEnable
from netmiko.cisco_base_connection import CiscoBaseConnection
from netmiko.base_connection import DELAY_FACTOR_DEPR_SIMPLE_MSG
class CdotCrosSSH(NoEnable, CiscoBaseConnection):
"""Implement methods for interacting with CROS network devices."""
def session_preparation(self) -> None:
"""Prepare the session after the connection has been established."""
self._test_channel_read(pattern=r"[#\$]")
self.set_base_prompt()
self._disable_complete_on_space()
self.set_terminal_width(command="screen-width 511", pattern=r"screen.width 511")
self.disable_paging(command="screen-length 0")
return
def send_config_set(
self,
config_commands: Union[str, Sequence[str], Iterator[str], TextIO, None] = None,
exit_config_mode: bool = False,
**kwargs: Any,
) -> str:
"""CROS requires you not exit from configuration mode."""
return super().send_config_set(
config_commands=config_commands, exit_config_mode=exit_config_mode, **kwargs
)
def check_config_mode(
self,
check_string: str = ")#",
pattern: str = r"[#\$]",
force_regex: bool = False,
) -> bool:
"""Checks if device is in configuration mode"""
return super().check_config_mode(check_string=check_string, pattern=pattern)
def config_mode(
self, config_command: str = "config", pattern: str = "", re_flags: int = 0
) -> str:
"""Enter configuration mode."""
return super().config_mode(
config_command=config_command, pattern=pattern, re_flags=re_flags
)
def commit(
self,
comment: str = "",
read_timeout: float = 120.0,
delay_factor: Optional[float] = None,
and_quit: bool = True,
) -> str:
"""
Commit the candidate configuration.
Commit the entered configuration. Raise an error and return the failure
if the commit fails.
default:
command_string = commit
comment:
command_string = commit comment
delay_factor: Deprecated in Netmiko 4.x. Will be eliminated in Netmiko 5.
"""
if delay_factor is not None:
warnings.warn(DELAY_FACTOR_DEPR_SIMPLE_MSG, DeprecationWarning)
command_string = "commit"
commit_marker = ["Commit complete", "No modifications to commit"]
if comment:
if '"' in comment:
raise ValueError("Invalid comment contains double quote")
command_string += f' comment "{comment}"'
output = self.config_mode()
output += self._send_command_str(
command_string,
strip_prompt=False,
strip_command=True,
read_timeout=read_timeout,
)
if not (any(x in output for x in commit_marker)):
raise ValueError(f"Commit failed with the following errors:\n\n{output}")
if and_quit:
self.exit_config_mode()
return output
def _disable_complete_on_space(self) -> str:
"""
CROS tries to auto complete commands when you type a "space" character.
This is a bad idea for automation as what your program is sending no longer matches
the command echo from the device. So we disable this behavior.
"""
delay_factor = self.select_delay_factor(delay_factor=0)
time.sleep(delay_factor * 0.1)
command = "complete-on-space false"
self.write_channel(self.normalize_cmd(command))
time.sleep(delay_factor * 0.1)
output = self.read_channel()
return output
././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1719528567.933976
netmiko-4.5.0/netmiko/centec/__init__.py 0000644 0000000 0000000 00000000156 14637366170 015153 0 ustar 00 from netmiko.centec.centec_os import CentecOSSSH, CentecOSTelnet
__all__ = ["CentecOSSSH", "CentecOSTelnet"]
././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1725389754.726303
netmiko-4.5.0/netmiko/centec/centec_os.py 0000644 0000000 0000000 00000001324 14665655673 015366 0 ustar 00 """Centec OS Support"""
from netmiko.cisco_base_connection import CiscoBaseConnection
class CentecOSBase(CiscoBaseConnection):
def session_preparation(self) -> None:
"""Prepare the session after the connection has been established."""
self._test_channel_read(pattern=r"[>#]")
self.set_base_prompt()
self.disable_paging()
def save_config(
self, cmd: str = "write", confirm: bool = False, confirm_response: str = ""
) -> str:
"""Save config: write"""
return super().save_config(
cmd=cmd, confirm=confirm, confirm_response=confirm_response
)
class CentecOSSSH(CentecOSBase):
pass
class CentecOSTelnet(CentecOSBase):
pass
././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1719528567.933976
netmiko-4.5.0/netmiko/channel.py 0000644 0000000 0000000 00000013053 14637366170 013563 0 ustar 00 from typing import Any, Optional
from abc import ABC, abstractmethod
import paramiko
import serial
from netmiko._telnetlib import telnetlib
from netmiko.utilities import write_bytes
from netmiko.netmiko_globals import MAX_BUFFER
from netmiko.exceptions import ReadException, WriteException
class Channel(ABC):
@abstractmethod
def __init__(self, *args: Any, **kwargs: Any) -> None:
"""Create the object."""
pass
# @abstractmethod
# def __repr__(self) -> str:
# """String representation of the object."""
# pass
#
# @abstractmethod
# def open(self, width: int = 511, height: int = 1000) -> None:
# """Create the underlying connection."""
# pass
#
# @abstractmethod
# def close(self) -> None:
# """Close the underlying connection."""
# pass
#
# @abstractmethod
# def login(self) -> None:
# """Handle the channel login process for any channel that requires it."""
# pass
@abstractmethod
def read_buffer(self) -> str:
"""Single read of available data."""
pass
@abstractmethod
def read_channel(self) -> str:
"""Read all of the available data from the channel."""
pass
@abstractmethod
def write_channel(self, out_data: str) -> None:
"""Write data down the channel."""
pass
# @abstractmethod
# def is_alive(self) -> bool:
# """Is the channel alive."""
# pass
class SSHChannel(Channel):
def __init__(self, conn: Optional[paramiko.Channel], encoding: str) -> None:
"""
Placeholder __init__ method so that reading and writing can be moved to the
channel class.
"""
self.remote_conn = conn
# FIX: move encoding to GlobalState object?
self.encoding = encoding
def write_channel(self, out_data: str) -> None:
if self.remote_conn is None:
raise WriteException(
"Attempt to write data, but there is no active channel."
)
self.remote_conn.sendall(write_bytes(out_data, encoding=self.encoding))
def read_buffer(self) -> str:
"""Single read of available data."""
if self.remote_conn is None:
raise ReadException("Attempt to read, but there is no active channel.")
output = ""
if self.remote_conn.recv_ready():
outbuf = self.remote_conn.recv(MAX_BUFFER)
if len(outbuf) == 0:
raise ReadException("Channel stream closed by remote device.")
output += outbuf.decode(self.encoding, "ignore")
return output
def read_channel(self) -> str:
"""Read all of the available data from the channel."""
if self.remote_conn is None:
raise ReadException("Attempt to read, but there is no active channel.")
output = ""
while True:
new_output = self.read_buffer()
output += new_output
if new_output == "":
break
return output
class TelnetChannel(Channel):
def __init__(self, conn: Optional[telnetlib.Telnet], encoding: str) -> None:
"""
Placeholder __init__ method so that reading and writing can be moved to the
channel class.
"""
self.remote_conn = conn
# FIX: move encoding to GlobalState object?
self.encoding = encoding
def write_channel(self, out_data: str) -> None:
if self.remote_conn is None:
raise WriteException(
"Attempt to write data, but there is no active channel."
)
self.remote_conn.write(write_bytes(out_data, encoding=self.encoding)) # type: ignore
def read_buffer(self) -> str:
"""Single read of available data."""
raise NotImplementedError
def read_channel(self) -> str:
"""Read all of the available data from the channel."""
if self.remote_conn is None:
raise ReadException("Attempt to read, but there is no active channel.")
return self.remote_conn.read_very_eager().decode(self.encoding, "ignore") # type: ignore
class SerialChannel(Channel):
def __init__(self, conn: Optional[serial.Serial], encoding: str) -> None:
"""
Placeholder __init__ method so that reading and writing can be moved to the
channel class.
"""
self.remote_conn = conn
# FIX: move encoding to GlobalState object?
self.encoding = encoding
def write_channel(self, out_data: str) -> None:
if self.remote_conn is None:
raise WriteException(
"Attempt to write data, but there is no active channel."
)
self.remote_conn.write(write_bytes(out_data, encoding=self.encoding))
self.remote_conn.flush()
def read_buffer(self) -> str:
"""Single read of available data."""
if self.remote_conn is None:
raise ReadException("Attempt to read, but there is no active channel.")
if self.remote_conn.in_waiting > 0:
output = self.remote_conn.read(self.remote_conn.in_waiting).decode(
self.encoding, "ignore"
)
assert isinstance(output, str)
return output
else:
return ""
def read_channel(self) -> str:
"""Read all of the available data from the channel."""
if self.remote_conn is None:
raise ReadException("Attempt to read, but there is no active channel.")
output = ""
while self.remote_conn.in_waiting > 0:
output += self.read_buffer()
return output
././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1719528567.933976
netmiko-4.5.0/netmiko/checkpoint/__init__.py 0000644 0000000 0000000 00000000146 14637366170 016040 0 ustar 00 from netmiko.checkpoint.checkpoint_gaia_ssh import CheckPointGaiaSSH
__all__ = ["CheckPointGaiaSSH"]
././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1719528567.933976
netmiko-4.5.0/netmiko/checkpoint/checkpoint_gaia_ssh.py 0000644 0000000 0000000 00000001312 14637366170 020262 0 ustar 00 from netmiko.no_config import NoConfig
from netmiko.base_connection import BaseConnection
class CheckPointGaiaSSH(NoConfig, BaseConnection):
"""
Implements methods for communicating with Check Point Gaia
firewalls.
"""
def session_preparation(self) -> None:
"""
Prepare the session after the connection has been established.
Set the base prompt for interaction ('>').
"""
self._test_channel_read(pattern=r"[>#]")
self.set_base_prompt()
self.disable_paging(command="set clienv rows 0")
def save_config(
self, cmd: str = "", confirm: bool = False, confirm_response: str = ""
) -> str:
raise NotImplementedError
././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1719528567.933976
netmiko-4.5.0/netmiko/ciena/__init__.py 0000644 0000000 0000000 00000000263 14637366170 014770 0 ustar 00 from netmiko.ciena.ciena_saos import (
CienaSaosSSH,
CienaSaosTelnet,
CienaSaosFileTransfer,
)
__all__ = ["CienaSaosSSH", "CienaSaosTelnet", "CienaSaosFileTransfer"]
././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1725389754.726303
netmiko-4.5.0/netmiko/ciena/ciena_saos.py 0000644 0000000 0000000 00000017374 14665655673 015362 0 ustar 00 """Ciena SAOS support."""
from typing import Optional, Any
import re
import os
from netmiko.no_enable import NoEnable
from netmiko.no_config import NoConfig
from netmiko.base_connection import BaseConnection
from netmiko.scp_handler import BaseFileTransfer
class CienaSaosBase(NoEnable, NoConfig, BaseConnection):
"""
Ciena SAOS support.
Implements methods for interacting Ciena Saos devices.
"""
prompt_pattern = r"[>#$]"
def set_base_prompt(
self,
pri_prompt_terminator: str = "",
alt_prompt_terminator: str = "",
delay_factor: float = 1.0,
pattern: Optional[str] = None,
) -> str:
"""Ciena can use '>', '$', '#' for prompt terminator depending on the device."""
prompt = self.find_prompt(delay_factor=delay_factor)
pattern = rf"^.+{self.prompt_pattern}$"
if not re.search(pattern, prompt):
raise ValueError(f"Router prompt not found: {repr(prompt)}")
# Strip off trailing terminator
self.base_prompt = prompt[:-1]
return self.base_prompt
def session_preparation(self) -> None:
self._test_channel_read(pattern=self.prompt_pattern)
self.set_base_prompt()
self.disable_paging(command="system shell session set more off")
def _enter_shell(self) -> str:
"""Enter the Bourne Shell."""
output = self._send_command_str("diag shell", expect_string=self.prompt_pattern)
if "SHELL PARSER FAILURE" in output:
msg = "SCP support on Ciena SAOS requires 'diag shell' permissions"
raise ValueError(msg)
return output
def _return_cli(self) -> str:
"""Return to the Ciena SAOS CLI."""
return self._send_command_str("exit", expect_string=r"[>]")
def save_config(
self,
cmd: str = "configuration save",
confirm: bool = False,
confirm_response: str = "",
) -> str:
"""Saves Config."""
return super().save_config(
cmd=cmd, confirm=confirm, confirm_response=confirm_response
)
class CienaSaosSSH(CienaSaosBase):
pass
class CienaSaosTelnet(CienaSaosBase):
def __init__(self, *args: Any, **kwargs: Any) -> None:
default_enter = kwargs.get("default_enter")
kwargs["default_enter"] = "\r\n" if default_enter is None else default_enter
super().__init__(*args, **kwargs)
class CienaSaosFileTransfer(BaseFileTransfer):
"""Ciena SAOS SCP File Transfer driver."""
def __init__(
self,
ssh_conn: BaseConnection,
source_file: str,
dest_file: str,
file_system: Optional[str] = None,
direction: str = "put",
**kwargs: Any,
) -> None:
if file_system is None:
file_system = f"/tmp/users/{ssh_conn.username}"
return super().__init__(
ssh_conn=ssh_conn,
source_file=source_file,
dest_file=dest_file,
file_system=file_system,
direction=direction,
**kwargs,
)
def remote_space_available(self, search_pattern: str = "") -> int:
"""
Return space available on Ciena SAOS
Output should only have the file-system that matches {self.file_system}
Filesystem 1K-blocks Used Available Use% Mounted on
tmpfs 1048576 648 1047928 0% /tmp
"""
remote_cmd = f"file vols -P {self.file_system}"
remote_output = self.ssh_ctl_chan._send_command_str(remote_cmd)
remote_output = remote_output.strip()
err_msg = (
f"Parsing error, unexpected output from {remote_cmd}:\n{remote_output}"
)
# First line is the header; file_system_line is the output we care about
header_line, filesystem_line = remote_output.splitlines()
filesystem, _, _, space_avail, *_ = header_line.split()
if "Filesystem" != filesystem or "Avail" not in space_avail:
# Filesystem 1K-blocks Used Available Use% Mounted on
raise ValueError(err_msg)
# Normalize output - in certain outputs ciena will line wrap (this fixes that)
# Strip the extra newline
# /dev/mapper/EN--VOL-config
# 4096 1476 2620 36% /etc/hosts
filesystem_line = re.sub(r"(^\S+$)\n", r"\1", filesystem_line, flags=re.M)
# Checks to make sure what was returned is what we expect
_, k_blocks, used, space_avail, _, _ = filesystem_line.split()
for integer_check in (k_blocks, used, space_avail):
try:
int(integer_check)
except ValueError:
raise ValueError(err_msg)
return int(space_avail) * 1024
def check_file_exists(self, remote_cmd: str = "") -> bool:
"""Check if the dest_file already exists on the file system (return boolean)."""
if self.direction == "put":
if not remote_cmd:
remote_cmd = f"file ls {self.file_system}/{self.dest_file}"
remote_out = self.ssh_ctl_chan._send_command_str(remote_cmd)
search_string = re.escape(f"{self.file_system}/{self.dest_file}")
if "ERROR" in remote_out:
return False
elif re.search(search_string, remote_out):
return True
else:
raise ValueError("Unexpected output from check_file_exists")
elif self.direction == "get":
return os.path.exists(self.dest_file)
else:
raise ValueError("Unexpected value for self.direction")
def remote_file_size(
self, remote_cmd: str = "", remote_file: Optional[str] = None
) -> int:
"""Get the file size of the remote file."""
if remote_file is None:
if self.direction == "put":
remote_file = self.dest_file
elif self.direction == "get":
remote_file = self.source_file
remote_file = f"{self.file_system}/{remote_file}"
if not remote_cmd:
remote_cmd = f"file ls -l {remote_file}"
remote_out = self.ssh_ctl_chan._send_command_str(remote_cmd)
if "No such file or directory" in remote_out:
raise IOError("Unable to find file on remote system")
escape_file_name = re.escape(remote_file)
pattern = r"^.* ({}).*$".format(escape_file_name)
match = re.search(pattern, remote_out, flags=re.M)
if match:
# Format: -rw-r--r-- 1 pyclass wheel 12 Nov 5 19:07 /var/tmp/test3.txt
line = match.group(0)
file_size = line.split()[4]
return int(file_size)
raise ValueError(
"Search pattern not found for remote file size during SCP transfer."
)
def remote_md5(self, base_cmd: str = "", remote_file: Optional[str] = None) -> str:
"""Calculate remote MD5 and returns the hash.
This command can be CPU intensive on the remote device.
"""
if base_cmd == "":
base_cmd = "md5sum"
if remote_file is None:
if self.direction == "put":
remote_file = self.dest_file
elif self.direction == "get":
remote_file = self.source_file
remote_md5_cmd = f"{base_cmd} {self.file_system}/{remote_file}"
self.ssh_ctl_chan._enter_shell()
dest_md5 = self.ssh_ctl_chan._send_command_str(
remote_md5_cmd, expect_string=r"[$#>]"
)
self.ssh_ctl_chan._return_cli()
dest_md5 = self.process_md5(dest_md5, pattern=r"([0-9a-f]+)\s+")
return dest_md5
def enable_scp(self, cmd: str = "system server scp enable") -> None:
return super().enable_scp(cmd=cmd)
def disable_scp(self, cmd: str = "system server scp disable") -> None:
return super().disable_scp(cmd=cmd)
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1733781006.2149477
netmiko-4.5.0/netmiko/cisco/__init__.py 0000644 0000000 0000000 00000002622 14725663016 015007 0 ustar 00 from netmiko.cisco.cisco_ios import (
CiscoIosBase,
CiscoIosSSH,
CiscoIosTelnet,
CiscoIosSerial,
)
from netmiko.cisco.cisco_ios import CiscoIosFileTransfer
from netmiko.cisco.cisco_ios import InLineTransfer
from netmiko.cisco.cisco_asa_ssh import CiscoAsaSSH, CiscoAsaFileTransfer
from netmiko.cisco.cisco_ftd_ssh import CiscoFtdSSH
from netmiko.cisco.cisco_nxos_ssh import CiscoNxosSSH, CiscoNxosFileTransfer
from netmiko.cisco.cisco_xr import CiscoXrSSH, CiscoXrTelnet, CiscoXrFileTransfer
from netmiko.cisco.cisco_wlc_ssh import CiscoWlcSSH
from netmiko.cisco.cisco_s200 import CiscoS200SSH
from netmiko.cisco.cisco_s200 import CiscoS200Telnet
from netmiko.cisco.cisco_s300 import CiscoS300SSH
from netmiko.cisco.cisco_s300 import CiscoS300Telnet
from netmiko.cisco.cisco_tp_tcce import CiscoTpTcCeSSH
from netmiko.cisco.cisco_viptela import CiscoViptelaSSH
from netmiko.cisco.cisco_apic import CiscoApicSSH
__all__ = [
"CiscoIosSSH",
"CiscoIosTelnet",
"CiscoAsaSSH",
"CiscoFtdSSH",
"CiscoNxosSSH",
"CiscoXrSSH",
"CiscoXrTelnet",
"CiscoWlcSSH",
"CiscoS200SSH",
"CiscoS200Telnet",
"CiscoS300SSH",
"CiscoS300Telnet",
"CiscoTpTcCeSSH",
"CiscoViptelaSSH",
"CiscoIosBase",
"CiscoIosFileTransfer",
"InLineTransfer",
"CiscoAsaFileTransfer",
"CiscoNxosFileTransfer",
"CiscoIosSerial",
"CiscoXrFileTransfer",
"CiscoApicSSH",
]
././@PaxHeader 0000000 0000000 0000000 00000000034 00000000000 010212 x ustar 00 28 mtime=1733781006.2149477
netmiko-4.5.0/netmiko/cisco/cisco_apic.py 0000644 0000000 0000000 00000000377 14725663016 015351 0 ustar 00 """Subclass specific to Cisco APIC."""
from netmiko.linux.linux_ssh import LinuxSSH
class CiscoApicSSH(LinuxSSH):
"""
Subclass specific to Cisco APIC.
This class inherit from LinuxSSH because Cisco APIC is based on Linux
"""
pass
././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1725389754.726303
netmiko-4.5.0/netmiko/cisco/cisco_asa_ssh.py 0000644 0000000 0000000 00000013662 14665655673 016074 0 ustar 00 """Subclass specific to Cisco ASA."""
from typing import Any, Union, List, Dict, Optional
import re
import time
from netmiko.cisco_base_connection import CiscoSSHConnection, CiscoFileTransfer
from netmiko.exceptions import NetmikoAuthenticationException
class CiscoAsaSSH(CiscoSSHConnection):
"""Subclass specific to Cisco ASA."""
def __init__(self, *args: Any, **kwargs: Any) -> None:
kwargs.setdefault("allow_auto_change", True)
return super().__init__(*args, **kwargs)
def session_preparation(self) -> None:
"""Prepare the session after the connection has been established."""
# Make sure the ASA is ready
command = "show curpriv\n"
self.write_channel(command)
self.read_until_pattern(pattern=re.escape(command.strip()))
# The 'enable' call requires the base_prompt to be set.
self.set_base_prompt()
if self.secret:
self.enable()
else:
self.asa_login()
self.disable_paging(command="terminal pager 0")
if self.allow_auto_change:
try:
self.send_config_set("terminal width 511")
except ValueError:
# Don't fail for the terminal width
pass
else:
# Disable cmd_verify if the terminal width can't be set
self.global_cmd_verify = False
self.set_base_prompt()
def check_config_mode(
self,
check_string: str = ")#",
pattern: str = r"[>\#]",
force_regex: bool = False,
) -> bool:
return super().check_config_mode(check_string=check_string, pattern=pattern)
def enable(
self,
cmd: str = "enable",
pattern: str = "ssword",
enable_pattern: Optional[str] = r"\#",
check_state: bool = True,
re_flags: int = re.IGNORECASE,
) -> str:
return super().enable(
cmd=cmd,
pattern=pattern,
enable_pattern=enable_pattern,
check_state=check_state,
re_flags=re_flags,
)
def send_command_timing(
self, *args: Any, **kwargs: Any
) -> Union[str, List[Any], Dict[str, Any]]:
"""
If the ASA is in multi-context mode, then the base_prompt needs to be
updated after each context change.
"""
output = super().send_command_timing(*args, **kwargs)
if len(args) >= 1:
command_string = args[0]
else:
command_string = kwargs["command_string"]
if "changeto" in command_string:
self.set_base_prompt()
return output
def send_command(
self, *args: Any, **kwargs: Any
) -> Union[str, List[Any], Dict[str, Any]]:
"""
If the ASA is in multi-context mode, then the base_prompt needs to be
updated after each context change.
"""
if len(args) >= 1:
command_string = args[0]
else:
command_string = kwargs["command_string"]
# If changeto in command, look for '#' to determine command is done
if "changeto" in command_string:
if len(args) <= 1:
expect_string = kwargs.get("expect_string", "#")
kwargs["expect_string"] = expect_string
output = super().send_command(*args, **kwargs)
if "changeto" in command_string:
self.set_base_prompt()
return output
def set_base_prompt(self, *args: Any, **kwargs: Any) -> str:
"""
Cisco ASA in multi-context mode needs to have the base prompt updated
(if you switch contexts i.e. 'changeto')
This switch of ASA contexts can occur in configuration mode. If this
happens the trailing '(config*' needs stripped off.
"""
cur_base_prompt = super().set_base_prompt(*args, **kwargs)
match = re.search(r"(.*)\(conf.*", cur_base_prompt)
if match:
# strip off (conf.* from base_prompt
self.base_prompt = match.group(1)
return self.base_prompt
else:
return cur_base_prompt
def asa_login(self) -> None:
"""
Handle ASA reaching privilege level 15 using login
twb-dc-fw1> login
Username: admin
Raises NetmikoAuthenticationException, if we do not reach privilege
level 15 after 10 loops.
"""
delay_factor = self.select_delay_factor(0)
i = 1
max_attempts = 10
self.write_channel("login" + self.RETURN)
output = self.read_until_pattern(pattern=r"login")
while i <= max_attempts:
time.sleep(0.5 * delay_factor)
output = self.read_channel()
if "sername" in output:
assert isinstance(self.username, str)
self.write_channel(self.username + self.RETURN)
elif "ssword" in output:
assert isinstance(self.password, str)
self.write_channel(self.password + self.RETURN)
elif "#" in output:
return
else:
self.write_channel("login" + self.RETURN)
i += 1
msg = "Unable to enter enable mode!"
raise NetmikoAuthenticationException(msg)
def save_config(
self, cmd: str = "write mem", confirm: bool = False, confirm_response: str = ""
) -> str:
"""Saves Config"""
return super().save_config(
cmd=cmd, confirm=confirm, confirm_response=confirm_response
)
def normalize_linefeeds(self, a_string: str) -> str:
"""Cisco ASA needed that extra \r\n\r"""
newline = re.compile("(\r\n\r|\r\r\r\n|\r\r\n|\r\n|\n\r)")
a_string = newline.sub(self.RESPONSE_RETURN, a_string)
if self.RESPONSE_RETURN == "\n":
# Delete any remaining \r
return re.sub("\r", "", a_string)
else:
return a_string
class CiscoAsaFileTransfer(CiscoFileTransfer):
"""Cisco ASA SCP File Transfer driver."""
pass
././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1725389754.726303
netmiko-4.5.0/netmiko/cisco/cisco_ftd_ssh.py 0000644 0000000 0000000 00000001522 14665655673 016075 0 ustar 00 """Subclass specific to Cisco FTD."""
from typing import Any
from netmiko.no_enable import NoEnable
from netmiko.no_config import NoConfig
from netmiko.cisco_base_connection import CiscoSSHConnection
class CiscoFtdSSH(NoEnable, NoConfig, CiscoSSHConnection):
"""Subclass specific to Cisco FTD."""
def session_preparation(self) -> None:
"""Prepare the session after the connection has been established."""
self._test_channel_read(pattern=r"[>#]")
self.set_base_prompt()
def send_config_set(self, *args: Any, **kwargs: Any) -> str:
"""Canot change config on FTD via ssh"""
raise NotImplementedError
def check_config_mode(
self, check_string: str = "", pattern: str = "", force_regex: bool = False
) -> bool:
"""Canot change config on FTD via ssh"""
return False
././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1719528567.933976
netmiko-4.5.0/netmiko/cisco/cisco_ios.py 0000644 0000000 0000000 00000022625 14637366170 015232 0 ustar 00 from typing import Any, Optional, Callable, Type
from types import TracebackType
import time
import re
import os
import hashlib
import io
from netmiko.cisco_base_connection import CiscoBaseConnection, CiscoFileTransfer
from netmiko.base_connection import BaseConnection
class CiscoIosBase(CiscoBaseConnection):
"""Common Methods for IOS (both SSH and telnet)."""
def session_preparation(self) -> None:
"""Prepare the session after the connection has been established."""
cmd = "terminal width 511"
self.set_terminal_width(command=cmd, pattern=cmd)
self.disable_paging()
self.set_base_prompt()
def set_base_prompt(
self,
pri_prompt_terminator: str = "#",
alt_prompt_terminator: str = ">",
delay_factor: float = 1.0,
pattern: Optional[str] = None,
) -> str:
"""
Cisco IOS/IOS-XE abbreviates the prompt at 20-chars in config mode.
Consequently, abbreviate the base_prompt
"""
base_prompt = super().set_base_prompt(
pri_prompt_terminator=pri_prompt_terminator,
alt_prompt_terminator=alt_prompt_terminator,
delay_factor=delay_factor,
pattern=pattern,
)
self.base_prompt = base_prompt[:16]
return self.base_prompt
def check_config_mode(
self,
check_string: str = ")#",
pattern: str = r"[>#]",
force_regex: bool = False,
) -> bool:
"""
Checks if the device is in configuration mode or not.
Cisco IOS devices abbreviate the prompt at 20 chars in config mode
"""
return super().check_config_mode(check_string=check_string, pattern=pattern)
def save_config(
self, cmd: str = "write mem", confirm: bool = False, confirm_response: str = ""
) -> str:
"""Saves Config Using Copy Run Start"""
return super().save_config(
cmd=cmd, confirm=confirm, confirm_response=confirm_response
)
class CiscoIosSSH(CiscoIosBase):
"""Cisco IOS SSH driver."""
pass
class CiscoIosTelnet(CiscoIosBase):
"""Cisco IOS Telnet driver."""
pass
class CiscoIosSerial(CiscoIosBase):
"""Cisco IOS Serial driver."""
pass
class CiscoIosFileTransfer(CiscoFileTransfer):
"""Cisco IOS SCP File Transfer driver."""
pass
class InLineTransfer(CiscoIosFileTransfer):
"""Use TCL on Cisco IOS to directly transfer file."""
def __init__(
self,
ssh_conn: BaseConnection,
source_file: str = "",
dest_file: str = "",
file_system: Optional[str] = None,
direction: str = "put",
source_config: Optional[str] = None,
socket_timeout: float = 10.0,
progress: Optional[Callable[..., Any]] = None,
progress4: Optional[Callable[..., Any]] = None,
hash_supported: bool = True,
) -> None:
if not dest_file:
raise ValueError(
"Destination file must be specified for InlineTransfer operations."
)
if hash_supported is False:
raise ValueError("hash_supported=False is not supported for InLineTransfer")
if source_file and source_config:
msg = "Invalid call to InLineTransfer both source_file and source_config specified."
raise ValueError(msg)
if direction != "put":
raise ValueError("Only put operation supported by InLineTransfer.")
if progress is not None or progress4 is not None:
raise NotImplementedError(
"Progress bar is not supported on inline transfers."
)
else:
self.progress = progress
self.progress4 = progress4
self.ssh_ctl_chan = ssh_conn
self.source_file = source_file
if source_file:
self.source_config = None
self.source_md5 = self.file_md5(source_file)
self.file_size = os.stat(source_file).st_size
elif source_config:
self.source_config = source_config
self.source_md5 = self.config_md5(source_config)
self.file_size = len(source_config.encode("UTF-8"))
self.dest_file = dest_file
self.direction = direction
if not file_system:
self.file_system = self.ssh_ctl_chan._autodetect_fs()
else:
self.file_system = file_system
self.socket_timeout = socket_timeout
@staticmethod
def _read_file(file_name: str) -> str:
with io.open(file_name, "rt", encoding="utf-8") as f:
return f.read()
@staticmethod
def _tcl_newline_rationalize(tcl_string: str) -> str:
r"""
When using put inside a TCL {} section the newline is considered a new TCL
statement and causes a missing curly-brace message. Convert "\n" to "\r". TCL
will convert the "\r" to a "\n" i.e. you will see a "\n" inside the file on the
Cisco IOS device.
"""
NEWLINE = r"\n"
CARRIAGE_RETURN = r"\r"
tmp_string = re.sub(NEWLINE, CARRIAGE_RETURN, tcl_string)
if re.search(r"[{}]", tmp_string):
msg = "Curly brace detected in string; TCL requires this be escaped."
raise ValueError(msg)
return tmp_string
def __enter__(self) -> "InLineTransfer":
self._enter_tcl_mode()
return self
def __exit__(
self,
exc_type: Optional[Type[BaseException]],
exc_value: Optional[BaseException],
traceback: Optional[TracebackType],
) -> None:
self._exit_tcl_mode()
def _enter_tcl_mode(self) -> str:
TCL_ENTER = "tclsh"
cmd_failed = ['Translating "tclsh"', "% Unknown command", "% Bad IP address"]
output = self.ssh_ctl_chan._send_command_str(
TCL_ENTER,
expect_string=r"\(tcl\)#",
strip_prompt=False,
strip_command=False,
)
for pattern in cmd_failed:
if pattern in output:
raise ValueError(f"Failed to enter tclsh mode on router: {output}")
return output
def _exit_tcl_mode(self) -> str:
TCL_EXIT = "tclquit"
self.ssh_ctl_chan.write_channel("\r")
time.sleep(1)
output = self.ssh_ctl_chan.read_channel()
if "(tcl)" in output:
self.ssh_ctl_chan.write_channel(TCL_EXIT + "\r")
time.sleep(1)
output += self.ssh_ctl_chan.read_channel()
return output
def establish_scp_conn(self) -> None:
raise NotImplementedError
def close_scp_chan(self) -> None:
raise NotImplementedError
def local_space_available(self) -> bool:
raise NotImplementedError
def file_md5(self, file_name: str, add_newline: bool = False) -> str:
"""Compute MD5 hash of file."""
if add_newline is True:
raise ValueError(
"add_newline argument is not supported for inline transfers."
)
file_contents = self._read_file(file_name)
file_contents = file_contents + "\n" # Cisco IOS automatically adds this
file_contents_bytes = file_contents.encode("UTF-8")
return hashlib.md5(file_contents_bytes).hexdigest()
def config_md5(self, source_config: str) -> str:
"""Compute MD5 hash of text."""
file_contents = source_config + "\n" # Cisco IOS automatically adds this
file_contents_bytes = file_contents.encode("UTF-8")
return hashlib.md5(file_contents_bytes).hexdigest()
def put_file(self) -> None:
curlybrace = r"{"
TCL_FILECMD_ENTER = 'puts [open "{}{}" w+] {}'.format(
self.file_system, self.dest_file, curlybrace
)
TCL_FILECMD_EXIT = "}"
if self.source_file:
file_contents = self._read_file(self.source_file)
elif self.source_config:
file_contents = self.source_config
file_contents = self._tcl_newline_rationalize(file_contents)
# Try to remove any existing data
self.ssh_ctl_chan.clear_buffer()
self.ssh_ctl_chan.write_channel(TCL_FILECMD_ENTER)
time.sleep(0.25)
self.ssh_ctl_chan.write_channel(file_contents)
self.ssh_ctl_chan.write_channel(TCL_FILECMD_EXIT + "\r")
# This operation can be slow (depends on the size of the file)
read_timeout = 100
sleep_time = 4
if self.file_size >= 2500:
read_timeout = 300
sleep_time = 12
elif self.file_size >= 7500:
read_timeout = 600
sleep_time = 25
# Initial delay
time.sleep(sleep_time)
# File paste and TCL_FILECMD_exit should be indicated by "router(tcl)#"
output = self.ssh_ctl_chan.read_until_pattern(
pattern=r"\(tcl\).*$", re_flags=re.M, read_timeout=read_timeout
)
# The file doesn't write until tclquit
TCL_EXIT = "tclquit"
self.ssh_ctl_chan.write_channel(TCL_EXIT + "\r")
time.sleep(1)
# Read all data remaining from the TCLSH session
pattern = rf"tclquit.*{self.ssh_ctl_chan.base_prompt}.*$"
re_flags = re.DOTALL | re.M
output += self.ssh_ctl_chan.read_until_pattern(
pattern=pattern, re_flags=re_flags, read_timeout=read_timeout
)
return None
def get_file(self) -> None:
raise NotImplementedError
def enable_scp(self, cmd: str = "") -> None:
raise NotImplementedError
def disable_scp(self, cmd: str = "") -> None:
raise NotImplementedError
././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1719528567.933976
netmiko-4.5.0/netmiko/cisco/cisco_nxos_ssh.py 0000644 0000000 0000000 00000015332 14637366170 016301 0 ustar 00 from typing import Any, Optional, Callable
import re
import os
from netmiko.base_connection import BaseConnection
from netmiko.cisco_base_connection import CiscoSSHConnection
from netmiko.cisco_base_connection import CiscoFileTransfer
class CiscoNxosSSH(CiscoSSHConnection):
def session_preparation(self) -> None:
"""Prepare the session after the connection has been established."""
self.ansi_escape_codes = True
# NX-OS has an issue where it echoes the command even though it hasn't returned the prompt
self._test_channel_read(pattern=r"[>#]")
self.set_terminal_width(
command="terminal width 511", pattern=r"terminal width 511"
)
self.disable_paging()
self.set_base_prompt()
def normalize_linefeeds(self, a_string: str) -> str:
"""Convert '\r\n' or '\r\r\n' to '\n, and remove extra '\r's in the text."""
newline = re.compile(r"(\r\r\n\r|\r\r\n|\r\n)")
# NX-OS fix for incorrect MD5 on 9K (due to strange patterns on NX-OS)
return newline.sub(self.RESPONSE_RETURN, a_string).replace("\r", "\n")
def check_config_mode(
self,
check_string: str = ")#",
pattern: str = r"[>#]",
force_regex: bool = False,
) -> bool:
"""Checks if the device is in configuration mode or not."""
return super().check_config_mode(check_string=check_string, pattern=pattern)
def save_config(
self,
cmd: str = "copy running-config startup-config",
confirm: bool = False,
confirm_response: str = "",
) -> str:
self.enable()
output = ""
if confirm:
output += self._send_command_timing_str(
command_string=cmd, strip_prompt=False, strip_command=False
)
if confirm_response:
output += self._send_command_timing_str(
confirm_response, strip_prompt=False, strip_command=False
)
else:
# Send enter by default
output += self._send_command_timing_str(
self.RETURN, strip_prompt=False, strip_command=False
)
else:
# NX-OS is very slow on save_config ensure it waits long enough.
output += self._send_command_str(
command_string=cmd,
strip_prompt=False,
strip_command=False,
read_timeout=100,
)
return output
class CiscoNxosFileTransfer(CiscoFileTransfer):
"""Cisco NXOS SCP File Transfer driver."""
def __init__(
self,
ssh_conn: BaseConnection,
source_file: str,
dest_file: str,
file_system: str = "bootflash:",
direction: str = "put",
socket_timeout: float = 10.0,
progress: Optional[Callable[..., Any]] = None,
progress4: Optional[Callable[..., Any]] = None,
hash_supported: bool = True,
) -> None:
self.ssh_ctl_chan = ssh_conn
self.source_file = source_file
self.dest_file = dest_file
self.direction = direction
if hash_supported is False:
raise ValueError("hash_supported=False is not supported for NX-OS")
if file_system:
self.file_system = file_system
else:
raise ValueError("Destination file system must be specified for NX-OS")
if direction == "put":
self.source_md5 = self.file_md5(source_file)
self.file_size = os.stat(source_file).st_size
elif direction == "get":
self.source_md5 = self.remote_md5(remote_file=source_file)
self.file_size = self.remote_file_size(remote_file=source_file)
else:
raise ValueError("Invalid direction specified")
self.socket_timeout = socket_timeout
self.progress = progress
self.progress4 = progress4
def check_file_exists(self, remote_cmd: str = "") -> bool:
"""Check if the dest_file already exists on the file system (return boolean)."""
if self.direction == "put":
if not remote_cmd:
remote_cmd = f"dir {self.file_system}{self.dest_file}"
remote_out = self.ssh_ctl_chan._send_command_str(remote_cmd)
search_string = r"{}.*Usage for".format(self.dest_file)
if "No such file or directory" in remote_out:
return False
elif re.search(search_string, remote_out, flags=re.DOTALL):
return True
else:
raise ValueError("Unexpected output from check_file_exists")
elif self.direction == "get":
return os.path.exists(self.dest_file)
else:
raise ValueError("Invalid value for file transfer direction.")
def remote_file_size(
self, remote_cmd: str = "", remote_file: Optional[str] = None
) -> int:
"""Get the file size of the remote file."""
if remote_file is None:
if self.direction == "put":
remote_file = self.dest_file
elif self.direction == "get":
remote_file = self.source_file
else:
raise ValueError("Invalid value for file transfer direction.")
if not remote_cmd:
remote_cmd = f"dir {self.file_system}/{remote_file}"
remote_out = self.ssh_ctl_chan._send_command_str(remote_cmd)
if re.search("no such file or directory", remote_out, flags=re.I):
raise IOError("Unable to find file on remote system")
# Match line containing file name
escape_file_name = re.escape(remote_file)
pattern = r".*({}).*".format(escape_file_name)
match = re.search(pattern, remote_out)
if match:
file_size = match.group(0)
file_size = file_size.split()[0]
return int(file_size)
raise IOError("Unable to find file on remote system")
@staticmethod
def process_md5(md5_output: str, pattern: str = r"= (.*)") -> str:
"""Not needed on NX-OS."""
raise NotImplementedError
def remote_md5(
self, base_cmd: str = "show file", remote_file: Optional[str] = None
) -> str:
if remote_file is None:
if self.direction == "put":
remote_file = self.dest_file
elif self.direction == "get":
remote_file = self.source_file
remote_md5_cmd = f"{base_cmd} {self.file_system}{remote_file} md5sum"
output = self.ssh_ctl_chan._send_command_str(remote_md5_cmd, read_timeout=300)
output = output.strip()
return output
def enable_scp(self, cmd: str = "") -> None:
raise NotImplementedError
def disable_scp(self, cmd: str = "") -> None:
raise NotImplementedError
././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1719528567.933976
netmiko-4.5.0/netmiko/cisco/cisco_s200.py 0000644 0000000 0000000 00000006162 14637366170 015122 0 ustar 00 import re
from os import path
from paramiko import SSHClient
from netmiko.ssh_auth import SSHClient_noauth
from netmiko.cisco_base_connection import CiscoSSHConnection
from netmiko.exceptions import NetmikoAuthenticationException
class CiscoS200Base(CiscoSSHConnection):
"""
Support for Cisco SG200 series of devices.
This connection class writes for low cost switches SG200 series, in which there is no command:
ip ssh password-auth
Consequently, Netmiko must handle the SSH authentication itself.
"""
prompt_pattern = r"(?m:[>#]\s*$)" # force re.Multiline
def _build_ssh_client(self) -> SSHClient:
"""Allow passwordless authentication for Cisco SG200 devices being provisioned."""
# Create instance of SSHClient object. Use noauth
remote_conn_pre = SSHClient_noauth()
# Load host_keys for better SSH security
if self.system_host_keys:
remote_conn_pre.load_system_host_keys()
if self.alt_host_keys and path.isfile(self.alt_key_file):
remote_conn_pre.load_host_keys(self.alt_key_file)
# Default is to automatically add untrusted hosts (make sure appropriate for your env)
remote_conn_pre.set_missing_host_key_policy(self.key_policy)
return remote_conn_pre
def special_login_handler(self, delay_factor: float = 1.0) -> None:
"""Cisco SG2xx presents with the following on login
login as: user
Welcome to Layer 2 Managed Switch
Username: user
Password:****
"""
output = ""
uname = "Username:"
login = "login as"
password = "ssword"
pattern = rf"(?:{uname}|{login}|{password}|{self.prompt_pattern})"
while True:
new_data = self.read_until_pattern(pattern=pattern, read_timeout=25.0)
output += new_data
# Fully logged-in, switch prompt detected.
if re.search(self.prompt_pattern, new_data):
return
if uname in new_data or login in new_data:
assert isinstance(self.username, str)
self.write_channel(self.username + self.RETURN)
elif password in new_data:
assert isinstance(self.password, str)
self.write_channel(self.password + self.RETURN)
else:
msg = f"""
Failed to login to Cisco SG2xx.
Pattern not detected: {pattern}
output:
{output}
"""
raise NetmikoAuthenticationException(msg)
def session_preparation(self) -> None:
"""Prepare the session after the connection has been established."""
self.ansi_escape_codes = True
self._test_channel_read(pattern=r"[>#]")
self.set_base_prompt()
self.disable_paging(command="terminal length 0")
def save_config(
self,
cmd: str = "write memory",
confirm: bool = True,
confirm_response: str = "Y",
) -> str:
return super().save_config(
cmd=cmd, confirm=confirm, confirm_response=confirm_response
)
class CiscoS200SSH(CiscoS200Base):
pass
class CiscoS200Telnet(CiscoS200Base):
pass
././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1728961118.245834
netmiko-4.5.0/netmiko/cisco/cisco_s300.py 0000644 0000000 0000000 00000002246 14703355136 015114 0 ustar 00 from netmiko.cisco_base_connection import CiscoSSHConnection
class CiscoS300Base(CiscoSSHConnection):
"""
Support for Cisco SG300 series of devices.
Note, must configure the following to disable SG300 from prompting for username twice:
configure terminal
ip ssh password-auth
"""
def session_preparation(self) -> None:
"""Prepare the session after the connection has been established."""
self.ansi_escape_codes = True
self._test_channel_read(pattern=r"[>#]")
self.set_base_prompt()
self.set_terminal_width(command="terminal width 511", pattern="terminal")
self.disable_paging(command="terminal datadump")
def save_config(
self,
cmd: str = "write memory",
confirm: bool = True,
confirm_response: str = "Y",
) -> str:
return super().save_config(
cmd=cmd, confirm=confirm, confirm_response=confirm_response
)
class CiscoS300SSH(CiscoS300Base):
pass
class CiscoS300Telnet(CiscoS300Base):
"""
Support for Cisco SG300 series of devices, with telnet.
Note: can be used with Sx200 series, with telnet enabled.
"""
pass
././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1725389754.726303
netmiko-4.5.0/netmiko/cisco/cisco_tp_tcce.py 0000644 0000000 0000000 00000006217 14665655673 016072 0 ustar 00 """
CiscoTpTcCeSSH Class
Class to manage Cisco Telepresence Endpoint on TC/CE software release. Also working for Cisco
Expressway/VCS
Written by Ahmad Barrin
Updated by Kirk Byers
"""
from typing import Any, Union, List, Dict
import time
import re
from netmiko.cisco_base_connection import CiscoSSHConnection
class CiscoTpTcCeSSH(CiscoSSHConnection):
def __init__(self, *args: Any, **kwargs: Any) -> None:
default_enter = kwargs.get("default_enter")
kwargs["default_enter"] = "\r\n" if default_enter is None else default_enter
super().__init__(*args, **kwargs)
def disable_paging(self, *args: Any, **kwargs: Any) -> str:
"""Paging is disabled by default."""
return ""
def session_preparation(self) -> None:
"""
Prepare the session after the connection has been established
This method handles some of vagaries that occur between various devices
early on in the session.
In general, it should include:
self.set_base_prompt()
self.disable_paging()
self.set_terminal_width()
"""
# Could not work out what the CLI looked like. It would be good to switch to
# a pattern on the _test_channel_read() call.
self._test_channel_read()
self.set_base_prompt()
self.set_terminal_width()
self.disable_paging()
# Clear the read buffer
time.sleep(0.3 * self.global_delay_factor)
self.clear_buffer()
def set_base_prompt(self, *args: Any, **kwargs: Any) -> str:
"""Use 'OK' as base_prompt."""
self.base_prompt = "OK"
return self.base_prompt
def find_prompt(self, *args: Any, **kwargs: Any) -> str:
"""Use 'OK' as standard prompt."""
return "OK"
def strip_prompt(self, a_string: str) -> str:
"""Strip the trailing router prompt from the output."""
expect_string = r"^(OK|ERROR|Command not recognized\.)$"
response_list = a_string.split(self.RESPONSE_RETURN)
last_line = response_list[-1]
if re.search(expect_string, last_line):
return self.RESPONSE_RETURN.join(response_list[:-1])
else:
return a_string
def send_command(
self, *args: Any, **kwargs: Any
) -> Union[str, List[Any], Dict[str, Any]]:
"""
Send command to network device retrieve output until router_prompt or expect_string
By default this method will keep waiting to receive data until the network device prompt is
detected. The current network device prompt will be determined automatically.
"""
if len(args) >= 2:
expect_string = args[1]
else:
expect_string = kwargs.get("expect_string")
if expect_string is None:
expect_string = r"(OK|ERROR|Command not recognized\.)"
expect_string = self.RETURN + expect_string + self.RETURN
kwargs.setdefault("expect_string", expect_string)
output = super().send_command(*args, **kwargs)
return output
def save_config(self, *args: Any, **kwargs: Any) -> str:
"""Not Implemented"""
raise NotImplementedError
././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1725389754.726303
netmiko-4.5.0/netmiko/cisco/cisco_viptela.py 0000644 0000000 0000000 00000006110 14665655673 016105 0 ustar 00 """Subclass specific to Cisco Viptela."""
from typing import Union, Sequence, Iterator, TextIO, Any
import re
from netmiko.cisco_base_connection import CiscoSSHConnection
class CiscoViptelaSSH(CiscoSSHConnection):
"""Subclass specific to Cisco Viptela."""
def session_preparation(self) -> None:
"""Prepare the session after the connection has been established."""
self._test_channel_read(pattern=r"[>#]")
self.set_base_prompt()
self.disable_paging(command="paginate false")
def check_config_mode(
self, check_string: str = ")#", pattern: str = "#", force_regex: bool = False
) -> bool:
"""Checks if the device is in configuration mode or not."""
return super().check_config_mode(check_string=check_string, pattern=pattern)
def commit(self, confirm: bool = False, confirm_response: str = "") -> str:
cmd = "commit"
return super().save_config(
cmd=cmd, confirm=confirm, confirm_response=confirm_response
)
def config_mode(
self,
config_command: str = "conf terminal",
pattern: str = "",
re_flags: int = 0,
) -> str:
return super().config_mode(
config_command=config_command, pattern=pattern, re_flags=re_flags
)
def send_config_set(
self,
config_commands: Union[str, Sequence[str], Iterator[str], TextIO, None] = None,
exit_config_mode: bool = False,
**kwargs: Any,
) -> str:
return super().send_config_set(
config_commands=config_commands, exit_config_mode=exit_config_mode, **kwargs
)
def exit_config_mode(self, exit_config: str = "end", pattern: str = r"#") -> str:
"""
Exit from configuration mode.
Viptela might have the following in the output (if no 'commit()' occurred.
Uncommitted changes found, commit them? [yes/no/CANCEL]
"""
output = ""
if self.check_config_mode():
self.write_channel(self.normalize_cmd(exit_config))
# Make sure you read until you detect the command echo (avoid getting out of sync)
if self.global_cmd_verify is not False:
output += self.read_until_pattern(
pattern=re.escape(exit_config.strip())
)
if not re.search(pattern, output, flags=re.M):
uncommit_pattern = r"Uncommitted changes found"
new_pattern = f"({pattern}|{uncommit_pattern})"
output += self.read_until_pattern(pattern=new_pattern)
# Do not save 'uncommited changes'
if uncommit_pattern in output:
self.write_channel(self.normalize_cmd("no"))
output += self.read_until_pattern(pattern=pattern)
if self.check_config_mode():
raise ValueError("Failed to exit configuration mode")
return output
def save_config(
self, cmd: str = "commit", confirm: bool = False, confirm_response: str = ""
) -> str:
"""Saves Config"""
raise NotImplementedError
././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1725389754.726303
netmiko-4.5.0/netmiko/cisco/cisco_wlc_ssh.py 0000644 0000000 0000000 00000021131 14665655673 016103 0 ustar 00 """Netmiko Cisco WLC support."""
from typing import Any, Union, Sequence, Iterator, TextIO
import time
import re
import socket
from netmiko.exceptions import NetmikoAuthenticationException
from netmiko.base_connection import BaseConnection
class CiscoWlcSSH(BaseConnection):
"""Netmiko Cisco WLC support."""
prompt_pattern = r"(?m:[>#]\s*$)" # force re.Multiline
def special_login_handler(self, delay_factor: float = 1.0) -> None:
"""WLC presents with the following on login (in certain OS versions)
login as: user
(Cisco Controller)
User: user
Password:****
"""
output = ""
uname = "User:"
login = "login as"
password = "ssword"
pattern = rf"(?:{uname}|{login}|{password}|{self.prompt_pattern})"
while True:
new_data = self.read_until_pattern(pattern=pattern, read_timeout=25.0)
output += new_data
if re.search(self.prompt_pattern, new_data):
return
if uname in new_data or login in new_data:
assert isinstance(self.username, str)
self.write_channel(self.username + self.RETURN)
elif password in new_data:
assert isinstance(self.password, str)
self.write_channel(self.password + self.RETURN)
else:
msg = f"""
Failed to login to Cisco WLC Device.
Pattern not detected: {pattern}
output:
{output}
"""
raise NetmikoAuthenticationException(msg)
def session_preparation(self) -> None:
"""
Prepare the session after the connection has been established
Cisco WLC uses "config paging disable" to disable paging
"""
# _test_channel_read() will happen in the special_login_handler()
try:
self.set_base_prompt()
except ValueError:
msg = f"Authentication failed: {self.host}"
raise NetmikoAuthenticationException(msg)
self.disable_paging(command="config paging disable")
def send_command_w_enter(self, *args: Any, **kwargs: Any) -> str:
"""
For 'show run-config' Cisco WLC adds a 'Press Enter to continue...' message
Even though pagination is disabled.
show run-config also has excessive delays in the output which requires special
handling.
Arguments are the same as send_command_timing() method.
"""
if len(args) > 1:
raise ValueError("Must pass in delay_factor as keyword argument")
# If no delay_factor use 1 for default value
delay_factor = kwargs.get("delay_factor", 1)
kwargs["delay_factor"] = self.select_delay_factor(delay_factor)
output = self._send_command_timing_str(*args, **kwargs)
second_args = list(args)
if len(args) == 1:
second_args[0] = self.RETURN
else:
kwargs["command_string"] = self.RETURN
if not kwargs.get("max_loops"):
kwargs["max_loops"] = 150
if "Press any key" in output or "Press Enter to" in output:
# Send an 'enter'
output += self._send_command_timing_str(*second_args, **kwargs)
# WLC has excessive delay after this appears on screen
if "802.11b Advanced Configuration" in output:
# Defaults to 30 seconds
time.sleep(kwargs["delay_factor"] * 30)
not_done = True
i = 1
while not_done and i <= 150:
time.sleep(kwargs["delay_factor"] * 3)
i += 1
new_data = ""
new_data = self.read_channel()
if new_data:
output += new_data
else:
not_done = False
strip_prompt = kwargs.get("strip_prompt", True)
if strip_prompt:
# Had to strip trailing prompt twice.
output = self.strip_prompt(output)
output = self.strip_prompt(output)
return output
def _send_command_w_yes(self, *args: Any, **kwargs: Any) -> str:
"""
For 'show interface summary' Cisco WLC adds a
'Would you like to display the next 15 entries?' message.
Even though pagination is disabled
Arguments are the same as send_command_timing() method.
"""
if len(args) > 1:
raise ValueError("Must pass in delay_factor as keyword argument")
# If no delay_factor use 1 for default value
delay_factor = kwargs.get("delay_factor", 1)
kwargs["delay_factor"] = self.select_delay_factor(delay_factor)
output = ""
new_output = self._send_command_timing_str(*args, **kwargs)
second_args = list(args)
if len(args) == 1:
second_args[0] = "y"
else:
kwargs["command_string"] = "y"
strip_prompt = kwargs.get("strip_prompt", True)
while True:
output += new_output
if "display the next" in new_output.lower():
new_output = self._send_command_timing_str(*second_args, **kwargs)
else:
break
# Remove from output 'Would you like to display the next 15 entries? (y/n)'
pattern = r"^.*display the next.*\n$"
output = re.sub(pattern, "", output, flags=re.M)
if strip_prompt:
# Had to strip trailing prompt twice.
output = self.strip_prompt(output)
output = self.strip_prompt(output)
return output
def cleanup(self, command: str = "logout") -> None:
"""Reset WLC back to normal paging and gracefully close session."""
self.send_command_timing("config paging enable")
# Exit configuration mode
try:
# The pattern="" forces use of send_command_timing
if self.check_config_mode(pattern=""):
self.exit_config_mode()
except Exception:
pass
# End SSH/telnet session
self.write_channel(command + self.RETURN)
count = 0
output = ""
while count <= 5:
time.sleep(0.5)
# The connection might be dead at this point.
try:
output += self.read_channel()
except socket.error:
break
# Don't automatically save the config (user's responsibility)
if "Would you like to save them now" in output:
self._session_log_fin = True
self.write_channel("n" + self.RETURN)
time.sleep(0.5)
try:
self.write_channel(self.RETURN)
except socket.error:
break
count += 1
def check_config_mode(
self, check_string: str = "config", pattern: str = "", force_regex: bool = False
) -> bool:
"""Checks if the device is in configuration mode or not."""
if not pattern:
pattern = re.escape(self.base_prompt)
return super().check_config_mode(check_string, pattern)
def config_mode(
self, config_command: str = "config", pattern: str = "", re_flags: int = 0
) -> str:
"""Enter into config_mode."""
return super().config_mode(
config_command=config_command, pattern=pattern, re_flags=re_flags
)
def exit_config_mode(self, exit_config: str = "exit", pattern: str = "") -> str:
"""Exit config_mode."""
return super().exit_config_mode(exit_config, pattern)
def send_config_set(
self,
config_commands: Union[str, Sequence[str], Iterator[str], TextIO, None] = None,
exit_config_mode: bool = False,
enter_config_mode: bool = False,
**kwargs: Any,
) -> str:
return super().send_config_set(
config_commands=config_commands,
exit_config_mode=exit_config_mode,
enter_config_mode=enter_config_mode,
**kwargs,
)
def save_config(
self,
cmd: str = "save config",
confirm: bool = True,
confirm_response: str = "y",
) -> str:
"""Saves Config."""
self.enable()
if confirm:
output = self._send_command_timing_str(command_string=cmd)
if confirm_response:
output += self._send_command_timing_str(confirm_response)
else:
# Send enter by default
output += self._send_command_timing_str(self.RETURN)
else:
# Some devices are slow so match on trailing-prompt if you can
output = self._send_command_str(command_string=cmd)
return output
././@PaxHeader 0000000 0000000 0000000 00000000033 00000000000 010211 x ustar 00 27 mtime=1719528567.933976
netmiko-4.5.0/netmiko/cisco/cisco_xr.py 0000644 0000000 0000000 00000023302 14637366170 015062 0 ustar 00 from typing import Optional, Any, Union, Sequence, Iterator, TextIO
import re
import warnings
from netmiko.base_connection import DELAY_FACTOR_DEPR_SIMPLE_MSG
from netmiko.cisco_base_connection import CiscoBaseConnection, CiscoFileTransfer
class CiscoXrBase(CiscoBaseConnection):
def establish_connection(self, width: int = 511, height: int = 511) -> None:
"""Establish SSH connection to the network device"""
super().establish_connection(width=width, height=height)
def session_preparation(self) -> None:
"""Prepare the session after the connection has been established."""
# IOS-XR has an issue where it echoes the command even though it hasn't returned the prompt
self._test_channel_read(pattern=r"[>#]")
cmd = "terminal width 511"
self.set_terminal_width(command=cmd, pattern=cmd)
self.disable_paging()
self._test_channel_read(pattern=r"[>#]")
self.set_base_prompt()
def set_base_prompt(
self,
pri_prompt_terminator: str = "#",
alt_prompt_terminator: str = ">",
delay_factor: float = 1.0,
pattern: Optional[str] = None,
) -> str:
"""
Cisco IOS-XR abbreviates the prompt at 31-chars in config mode.
Consequently, abbreviate the base_prompt
"""
base_prompt = super().set_base_prompt(
pri_prompt_terminator=pri_prompt_terminator,
alt_prompt_terminator=alt_prompt_terminator,
delay_factor=delay_factor,
pattern=pattern,
)
self.base_prompt = base_prompt[:31]
return self.base_prompt
def send_config_set(
self,
config_commands: Union[str, Sequence[str], Iterator[str], TextIO, None] = None,
exit_config_mode: bool = False,
**kwargs: Any,
) -> str:
"""IOS-XR requires you not exit from configuration mode."""
return super().send_config_set(
config_commands=config_commands, exit_config_mode=exit_config_mode, **kwargs
)
def commit(
self,
confirm: bool = False,
confirm_delay: Optional[int] = None,
comment: str = "",
label: str = "",
read_timeout: float = 120.0,
delay_factor: Optional[float] = None,
) -> str:
"""
Commit the candidate configuration.
default (no options):
command_string = commit
confirm and confirm_delay:
command_string = commit confirmed
label (which is a label name):
command_string = commit label