diff --git a/changes/859.fixed b/changes/859.fixed new file mode 100644 index 00000000..32e0077b --- /dev/null +++ b/changes/859.fixed @@ -0,0 +1 @@ +Fixed config parser failing to parse more than three consecutive banners, which caused "Unable to parse banner end." errors on configs with many banners (e.g. Cisco IOS). diff --git a/netutils/config/parser.py b/netutils/config/parser.py index b38248f1..09c9f6e0 100644 --- a/netutils/config/parser.py +++ b/netutils/config/parser.py @@ -290,6 +290,7 @@ def _update_config_lines(self, config_line: str) -> None: entry = ConfigLine(config_line, self._current_parents) self.config_lines.append(entry) + # pylint: disable=too-many-branches def build_config_relationship(self) -> t.List[ConfigLine]: r"""Parse text tree of config lines and their parents. @@ -337,10 +338,11 @@ def build_config_relationship(self) -> t.List[ConfigLine]: if line is None: break elif self.is_banner_start(line): - line = self._build_banner(line) # type: ignore - # line can potentially be another banner start therefore we do a secondary check. - if self.is_banner_start(line): + # Handles multiple consecutive banners (e.g. Cisco IOS) + while line is not None and self.is_banner_start(line): line = self._build_banner(line) # type: ignore + if line is None: + break self._update_config_lines(line) return self.config_lines @@ -1371,6 +1373,7 @@ def set_delimiter(self, config_line: str) -> None: return None raise ValueError("Unable to find banner delimiter.") + # pylint: disable=too-many-branches def build_config_relationship(self) -> t.List[ConfigLine]: r"""Parse text tree of config lines and their parents. @@ -1413,7 +1416,11 @@ def build_config_relationship(self) -> t.List[ConfigLine]: if line is None: break elif self.is_banner_start(line): - line = self._build_banner(line) # type: ignore + # Handles multiple consecutive banners (e.g. Cisco IOS) + while line is not None and self.is_banner_start(line): + line = self._build_banner(line) # type: ignore + if line is None: + break self._update_config_lines(line) return self.config_lines diff --git a/tests/unit/mock/config/compliance/feature_compliance/ios_multiple_banners_actual.txt b/tests/unit/mock/config/compliance/feature_compliance/ios_multiple_banners_actual.txt new file mode 100644 index 00000000..6fa8f3a0 --- /dev/null +++ b/tests/unit/mock/config/compliance/feature_compliance/ios_multiple_banners_actual.txt @@ -0,0 +1,18 @@ +hostname multi-banner +! +banner exec ^C +* exec banner * +^C +banner incoming ^C +* incoming banner * +^C +banner login ^C +* login banner actual * +^C +banner motd ^C +* motd banner * +^C +banner prompt-timeout ^C +* prompt-timeout banner * +^C +! diff --git a/tests/unit/mock/config/compliance/feature_compliance/ios_multiple_banners_feature.py b/tests/unit/mock/config/compliance/feature_compliance/ios_multiple_banners_feature.py new file mode 100644 index 00000000..221beb45 --- /dev/null +++ b/tests/unit/mock/config/compliance/feature_compliance/ios_multiple_banners_feature.py @@ -0,0 +1,2 @@ +feature = {"name": "login banner", "ordered": False, "section": ["banner login"]} +network_os = "cisco_ios" diff --git a/tests/unit/mock/config/compliance/feature_compliance/ios_multiple_banners_intended.txt b/tests/unit/mock/config/compliance/feature_compliance/ios_multiple_banners_intended.txt new file mode 100644 index 00000000..944ec832 --- /dev/null +++ b/tests/unit/mock/config/compliance/feature_compliance/ios_multiple_banners_intended.txt @@ -0,0 +1,18 @@ +hostname multi-banner +! +banner exec ^C +* exec banner * +^C +banner incoming ^C +* incoming banner * +^C +banner login ^C +* login banner intended * +^C +banner motd ^C +* motd banner * +^C +banner prompt-timeout ^C +* prompt-timeout banner * +^C +! diff --git a/tests/unit/mock/config/compliance/feature_compliance/ios_multiple_banners_received.py b/tests/unit/mock/config/compliance/feature_compliance/ios_multiple_banners_received.py new file mode 100644 index 00000000..663442d6 --- /dev/null +++ b/tests/unit/mock/config/compliance/feature_compliance/ios_multiple_banners_received.py @@ -0,0 +1,10 @@ +data = { + "compliant": False, + "missing": "banner login ^C\n* login banner intended *\n^C", + "extra": "banner login ^C\n* login banner actual *\n^C", + "cannot_parse": True, + "unordered_compliant": False, + "ordered_compliant": False, + "actual": "banner login ^C\n* login banner actual *\n^C", + "intended": "banner login ^C\n* login banner intended *\n^C", +} diff --git a/tests/unit/mock/config/parser/base/cisco_ios/ios_dual_banner_received.py b/tests/unit/mock/config/parser/base/cisco_ios/ios_dual_banner_received.py index dca51426..07eb8661 100755 --- a/tests/unit/mock/config/parser/base/cisco_ios/ios_dual_banner_received.py +++ b/tests/unit/mock/config/parser/base/cisco_ios/ios_dual_banner_received.py @@ -8,5 +8,4 @@ ConfigLine( config_line="======\nintended config motd banner\n======\n || ($hostname) ||\n^C", parents=("banner motd ^C",) ), - ConfigLine(config_line=None, parents=()), ] diff --git a/tests/unit/mock/config/parser/base/cisco_ios/ios_multiple_banners_received.py b/tests/unit/mock/config/parser/base/cisco_ios/ios_multiple_banners_received.py new file mode 100644 index 00000000..ba63314c --- /dev/null +++ b/tests/unit/mock/config/parser/base/cisco_ios/ios_multiple_banners_received.py @@ -0,0 +1,18 @@ +from netutils.config.parser import ConfigLine + +data = [ + ConfigLine(config_line="hostname multi-banner", parents=()), + ConfigLine(config_line="banner exec ^C", parents=()), + ConfigLine(config_line="* exec banner line 1 *\n* exec banner line 2 *\n^C", parents=("banner exec ^C",)), + ConfigLine(config_line="banner incoming ^C", parents=()), + ConfigLine( + config_line="* incoming banner line 1 *\n* incoming banner line 2 *\n^C", parents=("banner incoming ^C",) + ), + ConfigLine(config_line="banner login ^C", parents=()), + ConfigLine(config_line="* login banner line 1 *\n* login banner line 2 *\n^C", parents=("banner login ^C",)), + ConfigLine(config_line="banner motd ^C", parents=()), + ConfigLine(config_line="* motd banner line 1 *\n* motd banner line 2 *\n^C", parents=("banner motd ^C",)), + ConfigLine(config_line="banner prompt-timeout ^C", parents=()), + ConfigLine(config_line="* prompt-timeout banner line 1 *\n^C", parents=("banner prompt-timeout ^C",)), + ConfigLine(config_line="ip route 0.0.0.0 0.0.0.0 192.168.1.1", parents=()), +] diff --git a/tests/unit/mock/config/parser/base/cisco_ios/ios_multiple_banners_sent.txt b/tests/unit/mock/config/parser/base/cisco_ios/ios_multiple_banners_sent.txt new file mode 100644 index 00000000..8c862ea4 --- /dev/null +++ b/tests/unit/mock/config/parser/base/cisco_ios/ios_multiple_banners_sent.txt @@ -0,0 +1,22 @@ +hostname multi-banner +! +banner exec ^C +* exec banner line 1 * +* exec banner line 2 * +^C +banner incoming ^C +* incoming banner line 1 * +* incoming banner line 2 * +^C +banner login ^C +* login banner line 1 * +* login banner line 2 * +^C +banner motd ^C +* motd banner line 1 * +* motd banner line 2 * +^C +banner prompt-timeout ^C +* prompt-timeout banner line 1 * +^C +ip route 0.0.0.0 0.0.0.0 192.168.1.1 diff --git a/tests/unit/mock/config/parser/base/cisco_iosxr/iosxr_multiple_banners_received.py b/tests/unit/mock/config/parser/base/cisco_iosxr/iosxr_multiple_banners_received.py new file mode 100644 index 00000000..0d6b2974 --- /dev/null +++ b/tests/unit/mock/config/parser/base/cisco_iosxr/iosxr_multiple_banners_received.py @@ -0,0 +1,14 @@ +from netutils.config.parser import ConfigLine + +data = [ + ConfigLine(config_line="hostname multi-banner-xr", parents=()), + ConfigLine(config_line="banner exec ~", parents=()), + ConfigLine(config_line="* exec banner line 1 *\n* exec banner line 2 *\n~", parents=("banner exec ~",)), + ConfigLine(config_line="banner login ~", parents=()), + ConfigLine(config_line="* login banner line 1 *\n* login banner line 2 *\n~", parents=("banner login ~",)), + ConfigLine(config_line="banner motd ~", parents=()), + ConfigLine(config_line="* motd banner line 1 *\n* motd banner line 2 *\n~", parents=("banner motd ~",)), + ConfigLine(config_line="banner prompt-timeout ~", parents=()), + ConfigLine(config_line="* prompt-timeout banner line 1 *\n~", parents=("banner prompt-timeout ~",)), + ConfigLine(config_line="logging trap informational", parents=()), +] diff --git a/tests/unit/mock/config/parser/base/cisco_iosxr/iosxr_multiple_banners_sent.txt b/tests/unit/mock/config/parser/base/cisco_iosxr/iosxr_multiple_banners_sent.txt new file mode 100644 index 00000000..e9544350 --- /dev/null +++ b/tests/unit/mock/config/parser/base/cisco_iosxr/iosxr_multiple_banners_sent.txt @@ -0,0 +1,18 @@ +hostname multi-banner-xr +! +banner exec ~ +* exec banner line 1 * +* exec banner line 2 * +~ +banner login ~ +* login banner line 1 * +* login banner line 2 * +~ +banner motd ~ +* motd banner line 1 * +* motd banner line 2 * +~ +banner prompt-timeout ~ +* prompt-timeout banner line 1 * +~ +logging trap informational