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