1# 2# Copyright (C) 2010-2017 Samuel Abels 3# The MIT License (MIT) 4# 5# Permission is hereby granted, free of charge, to any person obtaining 6# a copy of this software and associated documentation files 7# (the "Software"), to deal in the Software without restriction, 8# including without limitation the rights to use, copy, modify, merge, 9# publish, distribute, sublicense, and/or sell copies of the Software, 10# and to permit persons to whom the Software is furnished to do so, 11# subject to the following conditions: 12# 13# The above copyright notice and this permission notice shall be 14# included in all copies or substantial portions of the Software. 15# 16# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23""" 24An abstract base class for all protocols. 25""" 26from __future__ import absolute_import, unicode_literals 27from future import standard_library 28standard_library.install_aliases() 29from builtins import object 30import re 31import sys 32import select 33import socket 34import signal 35import errno 36import os 37from io import StringIO 38from functools import partial 39from ..util.impl import Context, _Context 40from ..util.buffer import MonitoredBuffer 41from ..util.crypt import otp 42from ..util.event import Event 43from ..util.cast import to_regexs 44from ..util.tty import get_terminal_size 45from .drivers import driver_map, Driver 46from .osguesser import OsGuesser 47from .exception import InvalidCommandException, LoginFailure, \ 48 TimeoutException, DriverReplacedException, ExpectCancelledException 49 50try: 51 import termios 52 import tty 53 _have_termios = True 54except ImportError: 55 _have_termios = False 56 57_skey_re = re.compile(r'(?:s\/key|otp-md4) (\d+) (\S+)(?=\s|[\r\n])') 58 59 60class Protocol(object): 61 62 """ 63 This is the base class for all protocols; it defines the common portions 64 of the API. 65 66 The goal of all protocol classes is to provide an interface that 67 is unified across protocols, such that the adapters may be used 68 interchangeably without changing any other code. 69 70 In order to achieve this, the main challenge are the differences 71 arising from the authentication methods that are used. 72 The reason is that many devices may support the following variety 73 authentication/authorization methods: 74 75 1. Protocol level authentication, such as SSH's built-in 76 authentication. 77 78 - p1: password only 79 - p2: username 80 - p3: username + password 81 - p4: username + key 82 - p5: username + key + password 83 84 2. App level authentication, such that the authentication may 85 happen long after a connection is already accepted. 86 This type of authentication is normally used in combination with 87 Telnet, but some SSH hosts also do this (users have reported 88 devices from Enterasys). These devices may also combine 89 protocol-level authentication with app-level authentication. 90 The following types of app-level authentication exist: 91 92 - a1: password only 93 - a2: username 94 - a3: username + password 95 96 3. App level authorization: In order to implement the AAA protocol, 97 some devices ask for two separate app-level logins, whereas the 98 first serves to authenticate the user, and the second serves to 99 authorize him. 100 App-level authorization may support the same methods as app-level 101 authentication: 102 103 - A1: password only 104 - A2: username 105 - A3: username + password 106 107 We are assuming that the following methods are used: 108 109 - Telnet: 110 111 - p1 - p5: never 112 - a1 - a3: optional 113 - A1 - A3: optional 114 115 - SSH: 116 117 - p1 - p5: optional 118 - a1 - a3: optional 119 - A1 - A3: optional 120 121 To achieve authentication method compatibility across different 122 protocols, we must hide all this complexity behind one single API 123 call, and figure out which ones are supported. 124 125 As a use-case, our goal is that the following code will always work, 126 regardless of which combination of authentication methods a device 127 supports:: 128 129 key = PrivateKey.from_file('~/.ssh/id_rsa', 'my_key_password') 130 131 # The user account to use for protocol level authentication. 132 # The key defaults to None, in which case key authentication is 133 # not attempted. 134 account = Account(name = 'myuser', 135 password = 'mypassword', 136 key = key) 137 138 # The account to use for app-level authentication. 139 # password2 defaults to password. 140 app_account = Account(name = 'myuser', 141 password = 'my_app_password', 142 password2 = 'my_app_password2') 143 144 # app_account defaults to account. 145 conn.login(account, app_account = None, flush = True) 146 147 Another important consideration is that once the login is complete, the 148 device must be in a clearly defined state, i.e. we need to 149 have processed the data that was retrieved from the connected host. 150 151 More precisely, the buffer that contains the incoming data must be in 152 a state such that the following call to expect_prompt() will either 153 always work, or always fail. 154 155 We hide the following methods behind the login() call:: 156 157 # Protocol level authentication. 158 conn.protocol_authenticate(...) 159 # App-level authentication. 160 conn.app_authenticate(...) 161 # App-level authorization. 162 conn.app_authorize(...) 163 164 The code produces the following result:: 165 166 Telnet: 167 conn.protocol_authenticate -> NOP 168 conn.app_authenticate 169 -> waits for username or password prompt, authenticates, 170 returns after a CLI prompt was seen. 171 conn.app_authorize 172 -> calls driver.enable(), waits for username or password 173 prompt, authorizes, returns after a CLI prompt was seen. 174 175 SSH: 176 conn.protocol_authenticate -> authenticates using user/key/password 177 conn.app_authenticate -> like Telnet 178 conn.app_authorize -> like Telnet 179 180 We can see the following: 181 182 - protocol_authenticate() must not wait for a prompt, because else 183 app_authenticate() has no way of knowing whether an app-level 184 login is even necessary. 185 186 - app_authenticate() must check the buffer first, to see if 187 authentication has already succeeded. In the case that 188 app_authenticate() is not necessary (i.e. the buffer contains a 189 CLI prompt), it just returns. 190 191 app_authenticate() must NOT eat the prompt from the buffer, because 192 else the result may be inconsistent with devices that do not do 193 any authentication; i.e., when app_authenticate() is not called. 194 195 - Since the prompt must still be contained in the buffer, 196 conn.driver.app_authorize() needs to eat it before it sends the 197 command for starting the authorization procedure. 198 199 This has a drawback - if a user attempts to call app_authorize() 200 at a time where there is no prompt in the buffer, it would fail. 201 So we need to eat the prompt only in cases where we know that 202 auto_app_authorize() will attempt to execute a command. Hence 203 the driver requires the Driver.supports_auto_authorize() method. 204 205 However, app_authorize() must not eat the CLI prompt that follows. 206 207 - Once all logins are processed, it makes sense to eat the prompt 208 depending on the wait parameter. Wait should default to True, 209 because it's better that the connection stalls waiting forever, 210 than to risk that an error is not immediately discovered due to 211 timing issues (this is a race condition that I'm not going to 212 detail here). 213 """ 214 215 def __init__(self, 216 driver=None, 217 stdout=None, 218 stderr=None, 219 debug=0, 220 connect_timeout=30, 221 timeout=30, 222 logfile=None, 223 termtype='dumb', 224 verify_fingerprint=True, 225 account_factory=None, 226 banner_timeout=20, 227 encoding='latin-1'): 228 """ 229 Constructor. 230 The following events are provided: 231 232 - data_received_event: A packet was received from the connected host. 233 - otp_requested_event: The connected host requested a 234 one-time-password to be entered. 235 236 :keyword driver: Driver()|str 237 :keyword stdout: Where to write the device response. Defaults to 238 an in-memory buffer. 239 :keyword stderr: Where to write debug info. Defaults to stderr. 240 :keyword debug: An integer between 0 (no debugging) and 5 (very 241 verbose debugging) that specifies the amount of debug info 242 sent to the terminal. The default value is 0. 243 :keyword connect_timeout: Timeout for the initial TCP connection attempt 244 :keyword timeout: See set_timeout(). The default value is 30. 245 :keyword logfile: A file into which a log of the conversation with the 246 device is dumped. 247 :keyword termtype: The terminal type to request from the remote host, 248 e.g. 'vt100'. 249 :keyword verify_fingerprint: Whether to verify the host's fingerprint. 250 :keyword account_factory: A function that produces a new :class:`Account`. 251 :type banner_timeout: bool 252 :keyword banner_timeout: The time to wait for the banner. 253 :type encoding: str 254 :keyword encoding: The encoding of data received from the remote host. 255 """ 256 self.data_received_event = Event() 257 self.otp_requested_event = Event() 258 self.os_guesser = OsGuesser() 259 self.auto_driver = driver_map[self.guess_os()] 260 self.proto_authenticated = False 261 self.app_authenticated = False 262 self.app_authorized = False 263 self.manual_user_re = None 264 self.manual_password_re = None 265 self.manual_prompt_re = None 266 self.manual_error_re = None 267 self.manual_login_error_re = None 268 self.driver_replaced = False 269 self.host = None 270 self.port = None 271 self.last_account = None 272 self.termtype = termtype 273 self.verify_fingerprint = verify_fingerprint 274 self.manual_driver = None 275 self.debug = debug 276 self.connect_timeout = connect_timeout 277 self.timeout = timeout 278 self.logfile = logfile 279 self.response = None 280 self.buffer = MonitoredBuffer() 281 self.account_factory = account_factory 282 self.banner_timeout = banner_timeout 283 self.encoding = encoding 284 self.send_data = None 285 if stdout is None: 286 self.stdout = StringIO() 287 else: 288 self.stdout = stdout 289 if stderr is None: 290 self.stderr = sys.stderr 291 else: 292 self.stderr = stderr 293 if logfile is None: 294 self.log = None 295 else: 296 self.log = open(logfile, 'a') 297 298 # set manual_driver 299 if driver is not None: 300 if isinstance(driver, str): 301 if driver in driver_map: 302 self.manual_driver = driver_map[driver] 303 else: 304 self._dbg(1, 'Invalid driver string given. Ignoring...') 305 elif isinstance(driver, Driver): 306 self.manual_driver = driver 307 else: 308 self._dbg(1, 'Invalid driver given. Ignoring...') 309 310 def __copy__(self): 311 """ 312 Overwritten to return the very same object instead of copying the 313 stream, because copying a network connection is impossible. 314 315 :rtype: Protocol 316 :return: self 317 """ 318 return self 319 320 def __deepcopy__(self, memo): 321 """ 322 Overwritten to return the very same object instead of copying the 323 stream, because copying a network connection is impossible. 324 325 :type memo: object 326 :param memo: Please refer to Python's standard library documentation. 327 :rtype: Protocol 328 :return: self 329 """ 330 return self 331 332 def _driver_replaced_notify(self, old, new): 333 self.driver_replaced = True 334 self.cancel_expect() 335 msg = 'Protocol: driver replaced: %s -> %s' % (old.name, new.name) 336 self._dbg(1, msg) 337 338 def _receive_cb(self, data, remove_cr=True): 339 # Clean the data up. 340 if remove_cr: 341 text = data.replace('\r', '') 342 else: 343 text = data 344 345 # Write to a logfile. 346 self.stdout.write(text) 347 self.stdout.flush() 348 if self.log is not None: 349 self.log.write(text) 350 351 # Check whether a better driver is found based on the incoming data. 352 old_driver = self.get_driver() 353 self.os_guesser.data_received(data, self.is_app_authenticated()) 354 self.auto_driver = driver_map[self.guess_os()] 355 new_driver = self.get_driver() 356 if old_driver != new_driver: 357 self._driver_replaced_notify(old_driver, new_driver) 358 359 # Send signals to subscribers. 360 self.data_received_event(data) 361 362 def is_dummy(self): 363 """ 364 Returns True if the adapter implements a virtual device, i.e. 365 it isn't an actual network connection. 366 367 :rtype: Boolean 368 :return: True for dummy adapters, False for network adapters. 369 """ 370 return False 371 372 def _dbg(self, level, msg): 373 if self.debug < level: 374 return 375 self.stderr.write(self.get_driver().name + ': ' + msg + '\n') 376 377 def set_driver(self, driver=None): 378 """ 379 Defines the driver that is used to recognize prompts and implement 380 behavior depending on the remote system. 381 The driver argument may be an instance of a protocols.drivers.Driver 382 subclass, a known driver name (string), or None. 383 If the driver argument is None, the adapter automatically chooses 384 a driver using the guess_os() function. 385 386 :type driver: Driver()|str 387 :param driver: The pattern that, when matched, causes an error. 388 """ 389 if driver is None: 390 self.manual_driver = None 391 elif isinstance(driver, str): 392 if driver not in driver_map: 393 raise TypeError('no such driver:' + repr(driver)) 394 self.manual_driver = driver_map[driver] 395 elif isinstance(driver, Driver): 396 self.manual_driver = driver 397 else: 398 raise TypeError('unsupported argument type:' + type(driver)) 399 400 def get_driver(self): 401 """ 402 Returns the currently used driver. 403 404 :rtype: Driver 405 :return: A regular expression. 406 """ 407 if self.manual_driver: 408 return self.manual_driver 409 return self.auto_driver 410 411 def get_banner(self): 412 """ 413 Returns the banner that was received upon login. 414 Only supported on SSH2; returns None on all other protocols. 415 Also returns None if the client is not yet connected. 416 417 :rtype: str|None 418 :return: The banner as a string 419 """ 420 return None 421 422 def get_remote_version(self): 423 """ 424 Returns the remote version idstring that was received upon login. 425 Only supported on SSH2; returns None on all other protocols. 426 Also returns None if the client is not yet connected. 427 428 :rtype: str|None 429 :return: The idstring. 430 """ 431 return None 432 433 def autoinit(self): 434 """ 435 Make the remote host more script-friendly by automatically executing 436 one or more commands on it. 437 The commands executed depend on the currently used driver. 438 For example, the driver for Cisco IOS would execute the 439 following commands:: 440 441 term len 0 442 term width 0 443 """ 444 self.get_driver().init_terminal(self) 445 446 def set_username_prompt(self, regex=None): 447 """ 448 Defines a pattern that is used to monitor the response of the 449 connected host for a username prompt. 450 451 :type regex: RegEx 452 :param regex: The pattern that, when matched, causes an error. 453 """ 454 if regex is None: 455 self.manual_user_re = regex 456 else: 457 self.manual_user_re = to_regexs(regex) 458 459 def get_username_prompt(self): 460 """ 461 Returns the regular expression that is used to monitor the response 462 of the connected host for a username prompt. 463 464 :rtype: regex 465 :return: A regular expression. 466 """ 467 if self.manual_user_re: 468 return self.manual_user_re 469 return self.get_driver().user_re 470 471 def set_password_prompt(self, regex=None): 472 """ 473 Defines a pattern that is used to monitor the response of the 474 connected host for a password prompt. 475 476 :type regex: RegEx 477 :param regex: The pattern that, when matched, causes an error. 478 """ 479 if regex is None: 480 self.manual_password_re = regex 481 else: 482 self.manual_password_re = to_regexs(regex) 483 484 def get_password_prompt(self): 485 """ 486 Returns the regular expression that is used to monitor the response 487 of the connected host for a username prompt. 488 489 :rtype: regex 490 :return: A regular expression. 491 """ 492 if self.manual_password_re: 493 return self.manual_password_re 494 return self.get_driver().password_re 495 496 def set_prompt(self, prompt=None): 497 """ 498 Defines a pattern that is waited for when calling the expect_prompt() 499 method. 500 If the set_prompt() method is not called, or if it is called with the 501 prompt argument set to None, a default prompt is used that should 502 work with many devices running Unix, IOS, IOS-XR, or Junos and others. 503 504 :type prompt: RegEx 505 :param prompt: The pattern that matches the prompt of the remote host. 506 """ 507 if prompt is None: 508 self.manual_prompt_re = prompt 509 else: 510 self.manual_prompt_re = to_regexs(prompt) 511 512 def get_prompt(self): 513 """ 514 Returns the regular expressions that is matched against the host 515 response when calling the expect_prompt() method. 516 517 :rtype: list(re.RegexObject) 518 :return: A list of regular expression objects. 519 """ 520 if self.manual_prompt_re: 521 return self.manual_prompt_re 522 return self.get_driver().prompt_re 523 524 def set_error_prompt(self, error=None): 525 """ 526 Defines a pattern that is used to monitor the response of the 527 connected host. If the pattern matches (any time the expect() or 528 expect_prompt() methods are used), an error is raised. 529 530 :type error: RegEx 531 :param error: The pattern that, when matched, causes an error. 532 """ 533 if error is None: 534 self.manual_error_re = error 535 else: 536 self.manual_error_re = to_regexs(error) 537 538 def get_error_prompt(self): 539 """ 540 Returns the regular expression that is used to monitor the response 541 of the connected host for errors. 542 543 :rtype: regex 544 :return: A regular expression. 545 """ 546 if self.manual_error_re: 547 return self.manual_error_re 548 return self.get_driver().error_re 549 550 def set_login_error_prompt(self, error=None): 551 """ 552 Defines a pattern that is used to monitor the response of the 553 connected host during the authentication procedure. 554 If the pattern matches an error is raised. 555 556 :type error: RegEx 557 :param error: The pattern that, when matched, causes an error. 558 """ 559 if error is None: 560 self.manual_login_error_re = error 561 else: 562 self.manual_login_error_re = to_regexs(error) 563 564 def get_login_error_prompt(self): 565 """ 566 Returns the regular expression that is used to monitor the response 567 of the connected host for login errors; this is only used during 568 the login procedure, i.e. app_authenticate() or app_authorize(). 569 570 :rtype: regex 571 :return: A regular expression. 572 """ 573 if self.manual_login_error_re: 574 return self.manual_login_error_re 575 return self.get_driver().login_error_re 576 577 def set_connect_timeout(self, timeout): 578 """ 579 Defines the maximum time that the adapter waits for initial connection. 580 581 :type timeout: int 582 :param timeout: The maximum time in seconds. 583 """ 584 self.connect_timeout = int(timeout) 585 586 def get_connect_timeout(self): 587 """ 588 Returns the current connect_timeout in seconds. 589 590 :rtype: int 591 :return: The connect_timeout in seconds. 592 """ 593 return self.connect_timeout 594 595 def set_timeout(self, timeout): 596 """ 597 Defines the maximum time that the adapter waits before a call to 598 :class:`expect()` or :class:`expect_prompt()` fails. 599 600 :type timeout: int 601 :param timeout: The maximum time in seconds. 602 """ 603 self.timeout = int(timeout) 604 605 def get_timeout(self): 606 """ 607 Returns the current timeout in seconds. 608 609 :rtype: int 610 :return: The timeout in seconds. 611 """ 612 return self.timeout 613 614 def _connect_hook(self, host, port): 615 """ 616 Should be overwritten. 617 """ 618 raise NotImplementedError() 619 620 def connect(self, hostname=None, port=None): 621 """ 622 Opens the connection to the remote host or IP address. 623 624 :type hostname: string 625 :param hostname: The remote host or IP address. 626 :type port: int 627 :param port: The remote TCP port number. 628 """ 629 if hostname is not None: 630 self.host = hostname 631 conn = self._connect_hook(self.host, port) 632 self.os_guesser.protocol_info(self.get_remote_version()) 633 self.auto_driver = driver_map[self.guess_os()] 634 if self.get_banner(): 635 self.os_guesser.data_received(self.get_banner(), False) 636 return conn 637 638 def _get_account(self, account): 639 if isinstance(account, Context) or isinstance(account, _Context): 640 return account.context() 641 if account is None: 642 account = self.last_account 643 if self.account_factory: 644 account = self.account_factory(account) 645 else: 646 if account is None: 647 raise TypeError('An account is required') 648 account.__enter__() 649 self.last_account = account 650 return account.context() 651 652 def login(self, account=None, app_account=None, flush=True): 653 """ 654 Log into the connected host using the best method available. 655 If an account is not given, default to the account that was 656 used during the last call to login(). If a previous call was not 657 made, use the account that was passed to the constructor. If that 658 also fails, raise a TypeError. 659 660 The app_account is passed to :class:`app_authenticate()` and 661 :class:`app_authorize()`. 662 If app_account is not given, default to the value of the account 663 argument. 664 665 :type account: Account 666 :param account: The account for protocol level authentication. 667 :type app_account: Account 668 :param app_account: The account for app level authentication. 669 :type flush: bool 670 :param flush: Whether to flush the last prompt from the buffer. 671 """ 672 with self._get_account(account) as account: 673 if app_account is None: 674 app_account = account 675 self.authenticate(account, flush=False) 676 if self.get_driver().supports_auto_authorize(): 677 self.expect_prompt() 678 self.auto_app_authorize(app_account, flush=flush) 679 680 def authenticate(self, account=None, app_account=None, flush=True): 681 """ 682 Like login(), but skips the authorization procedure. 683 684 .. HINT:: 685 If you are unsure whether to use :class:`authenticate()` or 686 :class:`login()`, stick with :class:`login`. 687 688 :type account: Account 689 :param account: The account for protocol level authentication. 690 :type app_account: Account 691 :param app_account: The account for app level authentication. 692 :type flush: bool 693 :param flush: Whether to flush the last prompt from the buffer. 694 """ 695 with self._get_account(account) as account: 696 if app_account is None: 697 app_account = account 698 699 if not self.proto_authenticated: 700 self.protocol_authenticate(account) 701 self.app_authenticate(app_account, flush=flush) 702 703 def _protocol_authenticate(self, user, password): 704 pass 705 706 def _protocol_authenticate_by_key(self, user, key): 707 pass 708 709 def protocol_authenticate(self, account=None): 710 """ 711 Low-level API to perform protocol-level authentication on protocols 712 that support it. 713 714 .. HINT:: 715 In most cases, you want to use the login() method instead, as 716 it automatically chooses the best login method for each protocol. 717 718 :type account: Account 719 :param account: An account object, like login(). 720 """ 721 with self._get_account(account) as account: 722 user = account.get_name() 723 password = account.get_password() 724 key = account.get_key() 725 if key is None: 726 self._dbg(1, "Attempting to authenticate %s." % user) 727 self._protocol_authenticate(user, password) 728 else: 729 self._dbg(1, "Authenticate %s with key." % user) 730 self._protocol_authenticate_by_key(user, key) 731 self.proto_authenticated = True 732 733 def is_protocol_authenticated(self): 734 """ 735 Returns True if the protocol-level authentication procedure was 736 completed, False otherwise. 737 738 :rtype: bool 739 :return: Whether the authentication was completed. 740 """ 741 return self.proto_authenticated 742 743 def _app_authenticate(self, 744 account, 745 password, 746 flush=True, 747 bailout=False): 748 user = account.get_name() 749 750 while True: 751 # Wait for any prompt. Once a match is found, we need to be able 752 # to find out which type of prompt was matched, so we build a 753 # structure to allow for mapping the match index back to the 754 # prompt type. 755 prompts = (('login-error', self.get_login_error_prompt()), 756 ('username', self.get_username_prompt()), 757 ('skey', [_skey_re]), 758 ('password', self.get_password_prompt()), 759 ('cli', self.get_prompt())) 760 prompt_map = [] 761 prompt_list = [] 762 for section, sectionprompts in prompts: 763 for prompt in sectionprompts: 764 prompt_map.append((section, prompt)) 765 prompt_list.append(prompt) 766 767 # Wait for the prompt. 768 try: 769 index, match = self._waitfor(prompt_list) 770 except TimeoutException: 771 if self.response is None: 772 self.response = '' 773 msg = "Buffer: %s" % repr(self.response) 774 raise TimeoutException(msg) 775 except DriverReplacedException: 776 # Driver replaced, retry. 777 self._dbg(1, 'Protocol.app_authenticate(): driver replaced') 778 continue 779 except ExpectCancelledException: 780 self._dbg(1, 'Protocol.app_authenticate(): expect cancelled') 781 raise 782 except EOFError: 783 self._dbg(1, 'Protocol.app_authenticate(): EOF') 784 raise 785 786 # Login error detected. 787 section, prompt = prompt_map[index] 788 if section == 'login-error': 789 raise LoginFailure("Login failed") 790 791 # User name prompt. 792 elif section == 'username': 793 self._dbg(1, "Username prompt %s received." % index) 794 self.expect(prompt) # consume the prompt from the buffer 795 self.send(user + '\r') 796 continue 797 798 # s/key prompt. 799 elif section == 'skey': 800 self._dbg(1, "S/Key prompt received.") 801 self.expect(prompt) # consume the prompt from the buffer 802 seq = int(match.group(1)) 803 seed = match.group(2) 804 self.otp_requested_event(account, seq, seed) 805 self._dbg(2, "Seq: %s, Seed: %s" % (seq, seed)) 806 phrase = otp(password, seed, seq) 807 808 # A password prompt is now required. 809 self.expect(self.get_password_prompt()) 810 self.send(phrase + '\r') 811 self._dbg(1, "Password sent.") 812 if bailout: 813 break 814 continue 815 816 # Cleartext password prompt. 817 elif section == 'password': 818 self._dbg(1, "Cleartext password prompt received.") 819 self.expect(prompt) # consume the prompt from the buffer 820 self.send(password + '\r') 821 if bailout: 822 break 823 continue 824 825 # Shell prompt. 826 elif section == 'cli': 827 self._dbg(1, 'Shell prompt received.') 828 if flush: 829 self.expect_prompt() 830 break 831 832 else: 833 assert False # No such section 834 835 def app_authenticate(self, account=None, flush=True, bailout=False): 836 """ 837 Attempt to perform application-level authentication. Application 838 level authentication is needed on devices where the username and 839 password are requested from the user after the connection was 840 already accepted by the remote device. 841 842 The difference between app-level authentication and protocol-level 843 authentication is that in the latter case, the prompting is handled 844 by the client, whereas app-level authentication is handled by the 845 remote device. 846 847 App-level authentication comes in a large variety of forms, and 848 while this method tries hard to support them all, there is no 849 guarantee that it will always work. 850 851 We attempt to smartly recognize the user and password prompts; 852 for a list of supported operating systems please check the 853 Exscript.protocols.drivers module. 854 855 Returns upon finding the first command line prompt. Depending 856 on whether the flush argument is True, it also removes the 857 prompt from the incoming buffer. 858 859 :type account: Account 860 :param account: An account object, like login(). 861 :type flush: bool 862 :param flush: Whether to flush the last prompt from the buffer. 863 :type bailout: bool 864 :param bailout: Whether to wait for a prompt after sending the password. 865 """ 866 with self._get_account(account) as account: 867 user = account.get_name() 868 password = account.get_password() 869 self._dbg(1, "Attempting to app-authenticate %s." % user) 870 self._app_authenticate(account, password, flush, bailout) 871 self.app_authenticated = True 872 873 def is_app_authenticated(self): 874 """ 875 Returns True if the application-level authentication procedure was 876 completed, False otherwise. 877 878 :rtype: bool 879 :return: Whether the authentication was completed. 880 """ 881 return self.app_authenticated 882 883 def app_authorize(self, account=None, flush=True, bailout=False): 884 """ 885 Like app_authenticate(), but uses the authorization password 886 of the account. 887 888 For the difference between authentication and authorization 889 please google for AAA. 890 891 :type account: Account 892 :param account: An account object, like login(). 893 :type flush: bool 894 :param flush: Whether to flush the last prompt from the buffer. 895 :type bailout: bool 896 :param bailout: Whether to wait for a prompt after sending the password. 897 """ 898 with self._get_account(account) as account: 899 user = account.get_name() 900 password = account.get_authorization_password() 901 if password is None: 902 password = account.get_password() 903 self._dbg(1, "Attempting to app-authorize %s." % user) 904 self._app_authenticate(account, password, flush, bailout) 905 self.app_authorized = True 906 907 def auto_app_authorize(self, account=None, flush=True, bailout=False): 908 """ 909 Like authorize(), but instead of just waiting for a user or 910 password prompt, it automatically initiates the authorization 911 procedure by sending a driver-specific command. 912 913 In the case of devices that understand AAA, that means sending 914 a command to the device. For example, on routers running Cisco 915 IOS, this command executes the 'enable' command before expecting 916 the password. 917 918 In the case of a device that is not recognized to support AAA, this 919 method does nothing. 920 921 :type account: Account 922 :param account: An account object, like login(). 923 :type flush: bool 924 :param flush: Whether to flush the last prompt from the buffer. 925 :type bailout: bool 926 :param bailout: Whether to wait for a prompt after sending the password. 927 """ 928 with self._get_account(account) as account: 929 self._dbg(1, 'Calling driver.auto_authorize().') 930 self.get_driver().auto_authorize(self, account, flush, bailout) 931 932 def is_app_authorized(self): 933 """ 934 Returns True if the application-level authorization procedure was 935 completed, False otherwise. 936 937 :rtype: bool 938 :return: Whether the authorization was completed. 939 """ 940 return self.app_authorized 941 942 def send(self, data): 943 """ 944 Sends the given data to the remote host. 945 Returns without waiting for a response. 946 947 :type data: string 948 :param data: The data that is sent to the remote host. 949 :rtype: Boolean 950 :return: True on success, False otherwise. 951 """ 952 raise NotImplementedError() 953 954 def execute(self, command, consume=True): 955 """ 956 Sends the given data to the remote host (with a newline appended) 957 and waits for a prompt in the response. The prompt attempts to use 958 a sane default that works with many devices running Unix, IOS, 959 IOS-XR, or Junos and others. If that fails, a custom prompt may 960 also be defined using the set_prompt() method. 961 This method also modifies the value of the response (self.response) 962 attribute, for details please see the documentation of the 963 expect() method. 964 965 :type command: string 966 :param command: The data that is sent to the remote host. 967 :type consume: boolean (Default: True) 968 :param consume: Whether to consume the prompt from the buffer or not. 969 :rtype: int, re.MatchObject 970 :return: The index of the prompt regular expression that matched, 971 and the match object. 972 """ 973 self.send(command + '\r') 974 return self.expect_prompt(consume) 975 976 def _domatch(self, prompt, flush): 977 """ 978 Should be overwritten. 979 """ 980 raise NotImplementedError() 981 982 def _waitfor(self, prompt): 983 re_list = to_regexs(prompt) 984 patterns = [p.pattern for p in re_list] 985 self._dbg(2, 'waiting for: ' + repr(patterns)) 986 result = self._domatch(re_list, False) 987 return result 988 989 def waitfor(self, prompt): 990 """ 991 Monitors the data received from the remote host and waits until 992 the response matches the given prompt. 993 Once a match has been found, the buffer containing incoming data 994 is NOT changed. In other words, consecutive calls to this function 995 will always work, e.g.:: 996 997 conn.waitfor('myprompt>') 998 conn.waitfor('myprompt>') 999 conn.waitfor('myprompt>') 1000 1001 will always work. Hence in most cases, you probably want to use 1002 expect() instead. 1003 1004 This method also stores the received data in the response 1005 attribute (self.response). 1006 1007 Returns the index of the regular expression that matched. 1008 1009 :type prompt: str|re.RegexObject|list(str|re.RegexObject) 1010 :param prompt: One or more regular expressions. 1011 :rtype: int, re.MatchObject 1012 :return: The index of the regular expression that matched, 1013 and the match object. 1014 1015 @raise TimeoutException: raised if the timeout was reached. 1016 1017 @raise ExpectCancelledException: raised when cancel_expect() was 1018 called in a callback. 1019 1020 @raise ProtocolException: on other internal errors. 1021 1022 @raise Exception: May raise other exceptions that are caused 1023 within the underlying protocol implementations. 1024 """ 1025 while True: 1026 try: 1027 result = self._waitfor(prompt) 1028 except DriverReplacedException: 1029 continue # retry 1030 return result 1031 1032 def _expect(self, prompt): 1033 result = self._domatch(to_regexs(prompt), True) 1034 return result 1035 1036 def expect(self, prompt): 1037 """ 1038 Like waitfor(), but also removes the matched string from the buffer 1039 containing the incoming data. In other words, the following may not 1040 alway complete:: 1041 1042 conn.expect('myprompt>') 1043 conn.expect('myprompt>') # timeout 1044 1045 Returns the index of the regular expression that matched. 1046 1047 .. HINT:: 1048 May raise the same exceptions as :class:`waitfor`. 1049 1050 :type prompt: str|re.RegexObject|list(str|re.RegexObject) 1051 :param prompt: One or more regular expressions. 1052 :rtype: int, re.MatchObject 1053 :return: The index of the regular expression that matched, 1054 and the match object. 1055 """ 1056 while True: 1057 try: 1058 result = self._expect(prompt) 1059 except DriverReplacedException: 1060 continue # retry 1061 return result 1062 1063 def expect_prompt(self, consume=True): 1064 """ 1065 Monitors the data received from the remote host and waits for a 1066 prompt in the response. The prompt attempts to use 1067 a sane default that works with many devices running Unix, IOS, 1068 IOS-XR, or Junos and others. If that fails, a custom prompt may 1069 also be defined using the set_prompt() method. 1070 This method also stores the received data in the response 1071 attribute (self.response). 1072 1073 :type consume: boolean (Default: True) 1074 :param consume: Whether to consume the prompt from the buffer or not. 1075 :rtype: int, re.MatchObject 1076 :return: The index of the prompt regular expression that matched, 1077 and the match object. 1078 """ 1079 if consume: 1080 result = self.expect(self.get_prompt()) 1081 else: 1082 self._dbg(1, "DO NOT CONSUME PROMPT!") 1083 result = self.waitfor(self.get_prompt()) 1084 1085 # We skip the first line because it contains the echo of the command 1086 # sent. 1087 self._dbg(5, "Checking %s for errors" % repr(self.response)) 1088 for line in self.response.split('\n')[1:]: 1089 for prompt in self.get_error_prompt(): 1090 if not prompt.search(line): 1091 continue 1092 args = repr(prompt.pattern), repr(line) 1093 self._dbg(5, "error prompt (%s) matches %s" % args) 1094 raise InvalidCommandException('Device said:\n' + self.response) 1095 1096 return result 1097 1098 def add_monitor(self, pattern, callback, limit=80): 1099 """ 1100 Calls the given function whenever the given pattern matches the 1101 incoming data. 1102 1103 .. HINT:: 1104 If you want to catch all incoming data regardless of a 1105 pattern, use the Protocol.data_received_event event instead. 1106 1107 Arguments passed to the callback are the protocol instance, the 1108 index of the match, and the match object of the regular expression. 1109 1110 :type pattern: str|re.RegexObject|list(str|re.RegexObject) 1111 :param pattern: One or more regular expressions. 1112 :type callback: callable 1113 :param callback: The function that is called. 1114 :type limit: int 1115 :param limit: The maximum size of the tail of the buffer 1116 that is searched, in number of bytes. 1117 """ 1118 self.buffer.add_monitor(pattern, partial(callback, self), limit) 1119 1120 def cancel_expect(self): 1121 """ 1122 Cancel the current call to :class:`expect()` as soon as control returns 1123 to the protocol adapter. This method may be used in callbacks to 1124 the events emitted by this class, e.g. Protocol.data_received_event. 1125 """ 1126 raise NotImplementedError() 1127 1128 def _call_key_handlers(self, key_handlers, data): 1129 if key_handlers is not None: 1130 for key, func in list(key_handlers.items()): 1131 if data == key: 1132 func(self) 1133 return True 1134 return False 1135 1136 def _set_terminal_size(self, rows, cols): 1137 raise NotImplementedError() 1138 1139 def _open_posix_shell(self, 1140 channel, 1141 key_handlers, 1142 handle_window_size): 1143 # We need to make sure to use an unbuffered stdin, else multi-byte 1144 # chars (such as arrow keys) won't work properly. 1145 with os.fdopen(sys.stdin.fileno(), 'r', 0) as stdin: 1146 oldtty = termios.tcgetattr(stdin) 1147 1148 # Update the terminal size whenever the size changes. 1149 if handle_window_size: 1150 def handle_sigwinch(signum, frame): 1151 rows, cols = get_terminal_size() 1152 self._set_terminal_size(rows, cols) 1153 signal.signal(signal.SIGWINCH, handle_sigwinch) 1154 handle_sigwinch(None, None) 1155 1156 # Read from stdin and write to the network, endlessly. 1157 try: 1158 tty.setraw(sys.stdin.fileno()) 1159 tty.setcbreak(sys.stdin.fileno()) 1160 channel.settimeout(0.0) 1161 1162 while True: 1163 try: 1164 r, w, e = select.select([channel, stdin], [], []) 1165 except select.error as e: 1166 code, message = e 1167 if code == errno.EINTR: 1168 # This may happen when SIGWINCH is called 1169 # during the select; we just retry then. 1170 continue 1171 raise 1172 1173 if channel in r: 1174 try: 1175 data = channel.recv(1024).decode(self.encoding) 1176 except socket.timeout: 1177 pass 1178 if not data: 1179 self._dbg(1, 'EOF from remote') 1180 break 1181 self._receive_cb(data, False) 1182 self.buffer.append(data) 1183 if stdin in r: 1184 data = stdin.read(1).decode(self.encoding) 1185 self.buffer.clear() 1186 if len(data) == 0: 1187 break 1188 1189 # Temporarily revert stdin behavior while callbacks are 1190 # active. 1191 curtty = termios.tcgetattr(stdin) 1192 termios.tcsetattr(stdin, termios.TCSADRAIN, oldtty) 1193 is_handled = self._call_key_handlers(key_handlers, data) 1194 termios.tcsetattr(stdin, termios.TCSADRAIN, curtty) 1195 1196 if not is_handled: 1197 if not self.send_data is None: 1198 self.send_data.write(data) 1199 channel.send(data) 1200 finally: 1201 termios.tcsetattr(stdin, termios.TCSADRAIN, oldtty) 1202 1203 def _open_windows_shell(self, channel, key_handlers, handle_window_size): 1204 import threading 1205 1206 def writeall(sock): 1207 while True: 1208 data = sock.recv(256) 1209 if not data: 1210 self._dbg(1, 'EOF from remote') 1211 break 1212 self._receive_cb(data) 1213 1214 writer = threading.Thread(target=writeall, args=(channel,)) 1215 writer.start() 1216 1217 try: 1218 while True: 1219 data = sys.stdin.read(1) 1220 if not data: 1221 break 1222 if not self._call_key_handlers(key_handlers, data): 1223 if not self.send_data is None: 1224 self.send_data.write(data) 1225 channel.send(data) 1226 except EOFError: 1227 self._dbg(1, 'User hit ^Z or F6') 1228 1229 def _open_shell(self, channel, key_handlers, handle_window_size): 1230 if _have_termios: 1231 return self._open_posix_shell(channel, key_handlers, handle_window_size) 1232 else: 1233 return self._open_windows_shell(channel, key_handlers, handle_window_size) 1234 1235 def interact(self, key_handlers=None, handle_window_size=True): 1236 """ 1237 Opens a simple interactive shell. Returns when the remote host 1238 sends EOF. 1239 The optional key handlers are functions that are called whenever 1240 the user presses a specific key. For example, to catch CTRL+y:: 1241 1242 conn.interact({'\031': mycallback}) 1243 1244 .. WARNING:: 1245 handle_window_size is not supported on Windows platforms. 1246 1247 :type key_handlers: dict(str: callable) 1248 :param key_handlers: A dictionary mapping chars to a functions. 1249 :type handle_window_size: bool 1250 :param handle_window_size: Whether the connected host is notified 1251 when the terminal size changes. 1252 """ 1253 raise NotImplementedError() 1254 1255 def close(self, force=False): 1256 """ 1257 Closes the connection with the remote host. 1258 """ 1259 if self.log: 1260 try: 1261 self.log.close() 1262 except: 1263 pass 1264 1265 def get_host(self): 1266 """ 1267 Returns the name or address of the currently connected host. 1268 1269 :rtype: string 1270 :return: A name or an address. 1271 """ 1272 return self.host 1273 1274 def guess_os(self): 1275 """ 1276 Returns an identifier that specifies the operating system that is 1277 running on the remote host. This OS is obtained by watching the 1278 response of the remote host, such as any messages retrieved during 1279 the login procedure. 1280 1281 The OS is also a wild guess that often depends on volatile 1282 information, so there is no guarantee that this will always work. 1283 1284 :rtype: string 1285 :return: A string to help identify the remote operating system. 1286 """ 1287 return self.os_guesser.get('os') 1288