trigger 2.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. trigger/__init__.py +7 -0
  2. trigger/acl/__init__.py +32 -0
  3. trigger/acl/autoacl.py +70 -0
  4. trigger/acl/db.py +324 -0
  5. trigger/acl/dicts.py +357 -0
  6. trigger/acl/grammar.py +112 -0
  7. trigger/acl/ios.py +222 -0
  8. trigger/acl/junos.py +422 -0
  9. trigger/acl/models.py +118 -0
  10. trigger/acl/parser.py +168 -0
  11. trigger/acl/queue.py +296 -0
  12. trigger/acl/support.py +1431 -0
  13. trigger/acl/tools.py +746 -0
  14. trigger/bin/__init__.py +0 -0
  15. trigger/bin/acl.py +233 -0
  16. trigger/bin/acl_script.py +574 -0
  17. trigger/bin/aclconv.py +82 -0
  18. trigger/bin/check_access.py +93 -0
  19. trigger/bin/check_syntax.py +66 -0
  20. trigger/bin/fe.py +197 -0
  21. trigger/bin/find_access.py +191 -0
  22. trigger/bin/gnng.py +434 -0
  23. trigger/bin/gong.py +86 -0
  24. trigger/bin/load_acl.py +841 -0
  25. trigger/bin/load_config.py +18 -0
  26. trigger/bin/netdev.py +317 -0
  27. trigger/bin/optimizer.py +638 -0
  28. trigger/bin/run_cmds.py +18 -0
  29. trigger/changemgmt/__init__.py +352 -0
  30. trigger/changemgmt/bounce.py +57 -0
  31. trigger/cmds.py +1217 -0
  32. trigger/conf/__init__.py +94 -0
  33. trigger/conf/global_settings.py +674 -0
  34. trigger/contrib/__init__.py +7 -0
  35. trigger/exceptions.py +307 -0
  36. trigger/gorc.py +172 -0
  37. trigger/netdevices/__init__.py +1288 -0
  38. trigger/netdevices/loader.py +174 -0
  39. trigger/netscreen.py +1030 -0
  40. trigger/packages/__init__.py +6 -0
  41. trigger/packages/peewee.py +8084 -0
  42. trigger/rancid.py +463 -0
  43. trigger/tacacsrc.py +584 -0
  44. trigger/twister.py +2203 -0
  45. trigger/twister2.py +745 -0
  46. trigger/utils/__init__.py +88 -0
  47. trigger/utils/cli.py +349 -0
  48. trigger/utils/importlib.py +77 -0
  49. trigger/utils/network.py +157 -0
  50. trigger/utils/rcs.py +178 -0
  51. trigger/utils/templates.py +81 -0
  52. trigger/utils/url.py +78 -0
  53. trigger/utils/xmltodict.py +298 -0
  54. trigger-2.0.0.dist-info/METADATA +146 -0
  55. trigger-2.0.0.dist-info/RECORD +61 -0
  56. trigger-2.0.0.dist-info/WHEEL +5 -0
  57. trigger-2.0.0.dist-info/entry_points.txt +15 -0
  58. trigger-2.0.0.dist-info/licenses/AUTHORS.md +20 -0
  59. trigger-2.0.0.dist-info/licenses/LICENSE.md +28 -0
  60. trigger-2.0.0.dist-info/top_level.txt +2 -0
  61. twisted/plugins/trigger_xmlrpc.py +124 -0
trigger/acl/support.py ADDED
@@ -0,0 +1,1431 @@
1
+ """
2
+ This code is originally from parser.py. It consists of a lot of classes which
3
+ support the various modules for parsing. This file is not meant to by used by itself.
4
+
5
+ # Functions
6
+ 'check_name',
7
+ 'check_range',
8
+ 'do_dscp_lookup',
9
+ 'do_icmp_type_lookup',
10
+ 'do_icmp_code_lookup',
11
+ 'do_ip_option_lookup',
12
+ 'do_lookup',
13
+ 'do_port_lookup',
14
+ 'do_protocol_lookup',
15
+ 'make_inverse_mask',
16
+ 'strip_comments',
17
+ # Classes
18
+ 'ACL',
19
+ 'Comment',
20
+ 'Matches',
21
+ 'Modifiers',
22
+ 'MyDict',
23
+ 'Protocol',
24
+ 'RangeList',
25
+ 'Term',
26
+ 'TermList',
27
+ 'TIP',
28
+ """
29
+
30
+ # Copied metadata from parser.py
31
+ __author__ = "Jathan McCollum, Mike Biancaniello, Michael Harding, Michael Shields"
32
+ __editor__ = "Joseph Malone"
33
+ __maintainer__ = "Jathan McCollum"
34
+ __email__ = "jathanism@aol.com"
35
+ __copyright__ = "Copyright 2006-2013, AOL Inc.; 2013 Saleforce.com"
36
+
37
+ import IPy
38
+
39
+ from trigger import exceptions
40
+
41
+ from .dicts import *
42
+
43
+ # Python 2/3 compatibility
44
+ try:
45
+ unicode
46
+ except NameError:
47
+ # Python 3
48
+ unicode = str
49
+
50
+ # Temporary resting place for comments, so the rest of the parser can
51
+ # ignore them. Yes, this makes the library not thread-safe.
52
+ Comments = []
53
+
54
+
55
+ def check_name(name, exc, max_len=255, extra_chars=" -_."):
56
+ """
57
+ Test whether something is a valid identifier (for any vendor).
58
+ This means letters, numbers, and other characters specified in the
59
+ @extra_chars argument. If the string is invalid, throw the specified
60
+ exception.
61
+
62
+ :param name: The name to test.
63
+ :param exc: Exception type to raise if the name is invalid.
64
+ :param max_len: Integer of the maximum length of the name.
65
+ :param extra_chars: Extra non-alphanumeric characters to allow in the name.
66
+ """
67
+ if name is None:
68
+ return
69
+ if name == "":
70
+ raise exc("Name cannot be null string")
71
+ if len(name) > max_len:
72
+ raise exc('Name "%s" cannot be longer than %d characters' % (name, max_len))
73
+ for char in name:
74
+ if not (
75
+ (extra_chars is not None and char in extra_chars)
76
+ or (char >= "a" and char <= "z")
77
+ or (char >= "A" and char <= "Z")
78
+ or (char >= "0" and char <= "9")
79
+ ):
80
+ raise exc(f'Invalid character "{char}" in name "{name}"')
81
+
82
+
83
+ def check_range(values, min, max):
84
+ for value in values:
85
+ try:
86
+ for subvalue in value:
87
+ check_range([subvalue], min, max)
88
+ except TypeError:
89
+ if not min <= value <= max:
90
+ raise exceptions.BadMatchArgRange(
91
+ "match arg %s must be between %d and %d" % (str(value), min, max)
92
+ )
93
+
94
+
95
+ # Having this take the dictionary itself instead of a function is very slow.
96
+ def do_lookup(lookup_func, arg):
97
+ if isinstance(arg, tuple):
98
+ return tuple([do_lookup(lookup_func, elt) for elt in arg])
99
+
100
+ try:
101
+ return int(arg)
102
+ except TypeError:
103
+ return arg
104
+ except ValueError:
105
+ pass
106
+ # Ok, look it up by name.
107
+ try:
108
+ return lookup_func(arg)
109
+ except KeyError:
110
+ raise exceptions.UnknownMatchArg(f'match argument "{arg}" not known')
111
+
112
+
113
+ def do_protocol_lookup(arg):
114
+ if isinstance(arg, tuple):
115
+ return (Protocol(arg[0]), Protocol(arg[1]))
116
+ else:
117
+ return Protocol(arg)
118
+
119
+
120
+ def do_port_lookup(arg):
121
+ return do_lookup(lambda x: ports[x], arg)
122
+
123
+
124
+ def do_icmp_type_lookup(arg):
125
+ return do_lookup(lambda x: icmp_types[x], arg)
126
+
127
+
128
+ def do_icmp_code_lookup(arg):
129
+ return do_lookup(lambda x: icmp_codes[x], arg)
130
+
131
+
132
+ def do_ip_option_lookup(arg):
133
+ return do_lookup(lambda x: ip_option_names[x], arg)
134
+
135
+
136
+ def do_dscp_lookup(arg):
137
+ return do_lookup(lambda x: dscp_names[x], arg)
138
+
139
+
140
+ def make_inverse_mask(prefixlen):
141
+ """
142
+ Return an IP object of the inverse mask of the CIDR prefix.
143
+
144
+ :param prefixlen:
145
+ CIDR prefix
146
+ """
147
+ inverse_bits = 2 ** (32 - prefixlen) - 1
148
+ return TIP(inverse_bits)
149
+
150
+
151
+ def strip_comments(tags):
152
+ if tags is None:
153
+ return
154
+ noncomments = []
155
+ for tag in tags:
156
+ if isinstance(tag, Comment):
157
+ Comments.append(tag)
158
+ else:
159
+ noncomments.append(tag)
160
+ return noncomments
161
+
162
+
163
+ class MyDict(dict):
164
+ """
165
+ A dictionary subclass to collect common behavior changes used in container
166
+ classes for the ACL components: Modifiers, Matches.
167
+ """
168
+
169
+ def __init__(self, d=None, **kwargs):
170
+ if d:
171
+ if not hasattr(d, "keys"):
172
+ d = dict(d)
173
+ self.update(d)
174
+ if kwargs:
175
+ self.update(kwargs)
176
+
177
+ def __repr__(self):
178
+ return f"<{self.__class__.__name__}: {str(self)}>"
179
+
180
+ def __str__(self):
181
+ return ", ".join([f"{k} {v}" for k, v in self.items()])
182
+
183
+ def update(self, d):
184
+ """Force this to go through __setitem__."""
185
+ for k, v in d.items():
186
+ self[k] = v
187
+
188
+
189
+ class Modifiers(MyDict):
190
+ """
191
+ Container class for modifiers. These are only supported by JunOS format
192
+ and are ignored by all others.
193
+ """
194
+
195
+ def __setitem__(self, key, value):
196
+ # Handle argument-less modifiers first.
197
+ if key in ("log", "sample", "syslog", "port-mirror"):
198
+ if value not in (None, True):
199
+ raise exceptions.ActionError(f'"{key}" action takes no argument')
200
+ super().__setitem__(key, None)
201
+ return
202
+ # Everything below requires an argument.
203
+ if value is None:
204
+ raise exceptions.ActionError(f'"{key}" action requires an argument')
205
+ if key == "count":
206
+ # JunOS 7.3 docs say this cannot contain underscores and that
207
+ # it must be 24 characters or less, but this appears to be false.
208
+ # Doc bug filed 2006-02-09, doc-sw/68420.
209
+ check_name(value, exceptions.BadCounterName, max_len=255)
210
+ elif key == "forwarding-class":
211
+ check_name(value, exceptions.BadForwardingClassName)
212
+ elif key == "ipsec-sa":
213
+ check_name(value, exceptions.BadIPSecSAName)
214
+ elif key == "loss-priority":
215
+ if value not in ("low", "high"):
216
+ raise exceptions.ActionError('"loss-priority" must be "low" or "high"')
217
+ elif key == "policer":
218
+ check_name(value, exceptions.BadPolicerName)
219
+ else:
220
+ raise exceptions.ActionError("invalid action: " + str(key))
221
+ super().__setitem__(key, value)
222
+
223
+ def output_junos(self):
224
+ """
225
+ Output the modifiers to the only supported format!
226
+ """
227
+ # Python 3: dict.keys() returns a view, convert to list and sort
228
+ keys = sorted(self.keys())
229
+ return [k + (self[k] and " " + str(self[k]) or "") + ";" for k in keys]
230
+
231
+
232
+ class RangeList:
233
+ """
234
+ A type which stores ordered sets, with efficient handling of
235
+ ranges. It can also store non-incrementable terms as an sorted set
236
+ without collapsing into ranges.
237
+
238
+ This is currently used to just store match conditions (e.g. protocols,
239
+ ports), but could be fleshed out into a general-purpose class. One
240
+ thing to think about is how/whether to handle a list of tuples as distinct
241
+ from a list of ranges. Should we just store them as xrange objects?
242
+ Should the object appear as discrete elements by default, for example
243
+ in len(), with the collapsed view as a method, or should we keep it
244
+ as it is now? All the current uses of this class are in this file
245
+ and have unit tests, so when we decided what the semantics of the
246
+ generalized module ought to be, we can make it so without worry.
247
+ """
248
+
249
+ # Another way to implement this would be as a radix tree.
250
+ def __init__(self, data=None):
251
+ if data is None:
252
+ data = []
253
+
254
+ self.data = data
255
+ self._do_collapse()
256
+
257
+ def _cleanup(self, L):
258
+ """
259
+ Prepare a potential list of lists, tuples, digits for collapse. Does
260
+ the following::
261
+
262
+ 1. Sort & Convert all inner lists to tuples
263
+ 2. Convert all tuples w/ only 1 item into single item
264
+ 3. Gather all single digits
265
+ 4. Convert to set to remove duplicates
266
+ 5. Return as a sorted list
267
+
268
+ """
269
+ ret = []
270
+
271
+ # Get all list/tuples and return tuples
272
+ tuples = [tuple(sorted(i)) for i in L if isinstance(i, (list, tuple))]
273
+ singles = [i[0] for i in tuples if len(i) == 1] # Grab len of 1
274
+ tuples = [i for i in tuples if len(i) == 2] # Filter out len of 1
275
+ digits = [i for i in L if isinstance(i, int)] # Get digits
276
+
277
+ ret.extend(singles)
278
+ ret.extend(tuples)
279
+ ret.extend(digits)
280
+
281
+ if not ret:
282
+ ret = L
283
+
284
+ return sorted(set(ret))
285
+
286
+ def _collapse(self, l):
287
+ """
288
+ Reduce a sorted list of elements to ranges represented as tuples;
289
+ e.g. [1, 2, 3, 4, 10] -> [(1, 4), 10]
290
+ """
291
+ l = self._cleanup(l) # Remove duplicates
292
+
293
+ # Don't bother reducing a single item
294
+ if len(l) <= 1:
295
+ return l
296
+
297
+ # Make sure the elements are incrementable, or we can't reduce at all.
298
+ try:
299
+ l[0] + 1
300
+ except (TypeError, AttributeError):
301
+ return l
302
+ """
303
+ try:
304
+ l[0][0] + 1
305
+ except (TypeError, AttributeError):
306
+ return l
307
+ """
308
+
309
+ # This last step uses a loop instead of pure functionalism because
310
+ # it will be common to step through it tens of thousands of times,
311
+ # for example in the case of (1024, 65535).
312
+ # [x, x+1, ..., x+n] -> [(x, x+n)]
313
+ n = 0
314
+ try:
315
+ while l[n] + 1 == l[n + 1]:
316
+ n += 1
317
+ except IndexError: # entire list collapses to one range
318
+ return [(l[0], l[-1])]
319
+ if n == 0:
320
+ return [l[0]] + self._collapse(l[1:])
321
+ else:
322
+ return [(l[0], l[n])] + self._collapse(l[n + 1 :])
323
+
324
+ def _do_collapse(self):
325
+ self.data = self._collapse(self._expand(self.data))
326
+
327
+ def _expand(self, l):
328
+ """Expand a list of elements and tuples back to discrete elements.
329
+ Opposite of _collapse()."""
330
+ if not l:
331
+ return l
332
+ try:
333
+ # Python 3: range() returns a range object, not a list
334
+ return list(range(l[0][0], l[0][1] + 1)) + self._expand(l[1:])
335
+ except AttributeError: # not incrementable
336
+ return l
337
+ except (TypeError, IndexError):
338
+ return [l[0]] + self._expand(l[1:])
339
+
340
+ def expanded(self):
341
+ """Return a list with all ranges converted to discrete elements."""
342
+ return self._expand(self.data)
343
+
344
+ def __add__(self, y):
345
+ for elt in y:
346
+ self.append(elt)
347
+
348
+ def append(self, obj):
349
+ # We could make this faster.
350
+ self.data.append(obj)
351
+ self._do_collapse()
352
+
353
+ def __eq__(self, other):
354
+ """Compare RangeList to another RangeList or a list."""
355
+ if isinstance(other, RangeList):
356
+ return self.data == other.data
357
+ elif isinstance(other, list):
358
+ return self.data == other
359
+ return NotImplemented
360
+
361
+ def __ne__(self, other):
362
+ result = self.__eq__(other)
363
+ if result is NotImplemented:
364
+ return NotImplemented
365
+ return not result
366
+
367
+ def __lt__(self, other):
368
+ if isinstance(other, RangeList):
369
+ return self.data < other.data
370
+ elif isinstance(other, list):
371
+ return self.data < other
372
+ return NotImplemented
373
+
374
+ def __le__(self, other):
375
+ if isinstance(other, RangeList):
376
+ return self.data <= other.data
377
+ elif isinstance(other, list):
378
+ return self.data <= other
379
+ return NotImplemented
380
+
381
+ def __gt__(self, other):
382
+ if isinstance(other, RangeList):
383
+ return self.data > other.data
384
+ elif isinstance(other, list):
385
+ return self.data > other
386
+ return NotImplemented
387
+
388
+ def __ge__(self, other):
389
+ if isinstance(other, RangeList):
390
+ return self.data >= other.data
391
+ elif isinstance(other, list):
392
+ return self.data >= other
393
+ return NotImplemented
394
+
395
+ def __contains__(self, obj):
396
+ """
397
+ Performs voodoo to compare the following:
398
+ * Compare single ports to tuples (i.e. 1700 in (1700, 1800))
399
+ * Compare tuples to tuples (i.e. (1700,1800) in (0,65535))
400
+ * Comparing tuple to integer ALWAYS returns False!!
401
+ """
402
+ for elt in self.data:
403
+ if isinstance(elt, tuple):
404
+ if isinstance(obj, tuple):
405
+ ## if obj is a tuple, see if it is within the range of elt
406
+ ## using range here (add +1 to include elt[1])
407
+ ## otherwise you end up 1 digit short of max
408
+ rng = range(elt[0], elt[1] + 1)
409
+ if obj[0] in rng and obj[1] in rng:
410
+ return True
411
+ else:
412
+ if elt[0] <= obj <= elt[1]:
413
+ return True
414
+
415
+ elif hasattr(elt, "__contains__"):
416
+ if obj in elt:
417
+ return True
418
+ else:
419
+ if elt == obj:
420
+ return True
421
+ return False
422
+
423
+ def __repr__(self):
424
+ return f"<{self.__class__.__name__}: {str(self.data)}>"
425
+
426
+ def __str__(self):
427
+ return str(self.data)
428
+
429
+ # Straight passthrough of these:
430
+ def __hash__(self):
431
+ return self.data.__hash__(self.data)
432
+
433
+ def __len__(self):
434
+ return len(self.data)
435
+
436
+ def __getitem__(self, key):
437
+ return self.data[key]
438
+
439
+ def __setitem__(self, key, value):
440
+ self.data[key] = value
441
+
442
+ def __delitem__(self, key):
443
+ del self.data[key]
444
+
445
+ def __iter__(self):
446
+ return self.data.__iter__()
447
+
448
+
449
+ class TIP(IPy.IP):
450
+ """
451
+ Class based on IPy.IP, but with extensions for Trigger.
452
+
453
+ Currently, only the only extension is the ability to negate a network
454
+ block. Only used internally within the parser, as it's not complete
455
+ (doesn't interact well with IPy.IP objects). Does not handle IPv6 yet.
456
+ """
457
+
458
+ def __init__(self, data, **kwargs):
459
+ # Insert logic to handle 'except' preserve negated flag if it exists
460
+ # already
461
+ negated = getattr(data, "negated", False)
462
+
463
+ # Handle 'inactive:' address objects by setting inactive flag
464
+ inactive = getattr(data, "inactive", False)
465
+
466
+ # Is data a string?
467
+ if isinstance(data, (str, unicode)):
468
+ d = data.split()
469
+ # This means we got something like "1.2.3.4 except" or "inactive:
470
+ # 1.2.3.4'
471
+ if len(d) == 2:
472
+ # Check if last word is 'except', set negated=True
473
+ if d[-1] == "except":
474
+ negated = True
475
+ data = d[0]
476
+ # Check if first word is 'inactive:', set inactive=True
477
+ elif d[0] == "inactive:":
478
+ inactive = True
479
+ data = d[1]
480
+ elif len(d) == 3:
481
+ if d[-1] == "except":
482
+ negated = True
483
+ if d[0] == "inactive:":
484
+ inactive = True
485
+ if inactive and negated:
486
+ data = d[1]
487
+
488
+ self.negated = negated # Set 'negated' variable
489
+ self.inactive = inactive # Set 'inactive' variable
490
+ IPy.IP.__init__(self, data, **kwargs)
491
+
492
+ # Make it print prefixes for /32, /128 if we're negated or inactive (and
493
+ # therefore assuming we're being used in a Juniper ACL.)
494
+ if self.negated or self.inactive:
495
+ self.NoPrefixForSingleIp = False
496
+
497
+ def _compare_to(self, other):
498
+ """Helper method for comparison. Returns -1, 0, or 1."""
499
+ # Regular IPy sorts by prefix length before network base, but Juniper
500
+ # (our baseline) does not. We also need comparisons to be different for
501
+ # negation. Following Juniper's sorting, use IP compare, and then break
502
+ # ties where negated < not negated.
503
+ # Python 3: Implement cmp() logic inline
504
+ if self.ip < other.ip:
505
+ diff = -1
506
+ elif self.ip > other.ip:
507
+ diff = 1
508
+ else:
509
+ diff = 0
510
+
511
+ if diff == 0:
512
+ # If the same IP, compare by prefixlen
513
+ if self.prefixlen() < other.prefixlen():
514
+ diff = -1
515
+ elif self.prefixlen() > other.prefixlen():
516
+ diff = 1
517
+ else:
518
+ diff = 0
519
+
520
+ # If both negated, they're the same
521
+ if self.negated == other.negated:
522
+ return diff
523
+ # Sort to make negated < not negated
524
+ if self.negated:
525
+ diff = -1
526
+ else:
527
+ diff = 1
528
+ # Return the base comparison
529
+ return diff
530
+
531
+ def __lt__(self, other):
532
+ return self._compare_to(other) < 0
533
+
534
+ def __le__(self, other):
535
+ return self._compare_to(other) <= 0
536
+
537
+ def __gt__(self, other):
538
+ return self._compare_to(other) > 0
539
+
540
+ def __ge__(self, other):
541
+ return self._compare_to(other) >= 0
542
+
543
+ def __eq__(self, other):
544
+ return self._compare_to(other) == 0
545
+
546
+ def __ne__(self, other):
547
+ return self._compare_to(other) != 0
548
+
549
+ def __hash__(self):
550
+ # Make TIP hashable for use in sets and as dict keys
551
+ # Base hash on IP address, prefix length, and negation status
552
+ return hash((str(self.ip), self.prefixlen(), self.negated, self.inactive))
553
+
554
+ def __repr__(self):
555
+ # Just stick an 'except' at the end if except is set since we don't
556
+ # code to accept this in the constructor really just provided, for now,
557
+ # as a debugging aid.
558
+ rs = IPy.IP.__repr__(self)
559
+ if self.negated:
560
+ # Insert ' except' into the repr. (Yes, it's a hack!)
561
+ rs = rs.split("'")
562
+ rs[1] += " except"
563
+ rs = "'".join(rs) # Restore original repr
564
+ if self.inactive:
565
+ # Insert 'inactive: ' into the repr. (Yes, it's also a hack!)
566
+ rs = rs.split("'")
567
+ rs[1] = "inactive: " + rs[1]
568
+ rs = "'".join(rs) # Restore original repr
569
+ return rs
570
+
571
+ def __str__(self):
572
+ # IPy is not a new-style class, so the following doesn't work:
573
+ # return super(TIP, self).__str__()
574
+ rs = IPy.IP.__str__(self)
575
+ if self.negated:
576
+ rs += " except"
577
+ if self.inactive:
578
+ rs = "inactive: " + rs
579
+ return rs
580
+
581
+ def __contains__(self, item):
582
+ """
583
+ Containment logic, including except.
584
+ """
585
+ item = TIP(item)
586
+ # Calculate XOR
587
+ xor = self.negated ^ item.negated
588
+ # If one item is negated, it's never contained.
589
+ if xor:
590
+ return False
591
+ matched = IPy.IP.__contains__(self, item)
592
+ return matched ^ self.negated
593
+
594
+
595
+ class Comment:
596
+ """
597
+ Container for inline comments.
598
+ """
599
+
600
+ def __init__(self, data):
601
+ self.data = data
602
+
603
+ def __repr__(self):
604
+ return f"<{self.__class__.__name__}: {repr(self.data)}>"
605
+
606
+ def __str__(self):
607
+ return self.data
608
+
609
+ def __len__(self):
610
+ """Defining this method allows null comments to be false."""
611
+ return len(self.data)
612
+
613
+ def __iter__(self):
614
+ return self.data.__iter__()
615
+
616
+ def __contains__(self, item):
617
+ return item in self.data
618
+
619
+ def output_junos(self):
620
+ """Output the Comment to JunOS format."""
621
+ return f"/*{self.data}*/"
622
+
623
+ def output_ios(self):
624
+ """Output the Comment to IOS traditional format."""
625
+ if not self.data:
626
+ return "!"
627
+
628
+ data = self.data
629
+ if data.startswith("!"):
630
+ prefix = "!"
631
+ data = prefix + data
632
+ else:
633
+ prefix = "! "
634
+ lines = data.splitlines()
635
+
636
+ return "\n".join(prefix + line for line in lines)
637
+
638
+ def output_ios_named(self):
639
+ """Output the Comment to IOS named format."""
640
+ return self.output_ios()
641
+
642
+ def output_iosxr(self):
643
+ """Output the Comment to IOS XR format."""
644
+ return self.output_ios()
645
+
646
+
647
+ class ACL:
648
+ """
649
+ An abstract access-list object intended to be created by the :func:`parse`
650
+ function.
651
+ """
652
+
653
+ def __init__(
654
+ self, name=None, terms=None, format=None, family=None, interface_specific=False
655
+ ):
656
+ check_name(name, exceptions.ACLNameError, max_len=24)
657
+ self.name = name
658
+ self.family = family
659
+ self.interface_specific = interface_specific
660
+ self.format = format
661
+ self.policers = []
662
+ if terms:
663
+ self.terms = terms
664
+ else:
665
+ self.terms = TermList()
666
+ global Comments
667
+ self.comments = Comments
668
+ Comments = []
669
+
670
+ def __repr__(self):
671
+ return f"<ACL: {self.name}>"
672
+
673
+ def __str__(self):
674
+ return "\n".join(self.output(format=self.format, family=self.family))
675
+
676
+ def output(self, format=None, *largs, **kwargs):
677
+ """
678
+ Output the ACL data in the specified format.
679
+ """
680
+ if format is None:
681
+ format = self.format
682
+ return getattr(self, "output_" + format)(*largs, **kwargs)
683
+
684
+ def output_junos(self, replace=False, family=None):
685
+ """
686
+ Output the ACL in JunOS format.
687
+
688
+ :param replace: If set the ACL is wrapped in a
689
+ ``firewall { replace: ... }`` section.
690
+ :param family: If set, the value is used to wrap the ACL in a
691
+ ``family inet { ...}`` section.
692
+ """
693
+ if self.name is None:
694
+ raise exceptions.MissingACLName("JunOS format requires a name")
695
+
696
+ # Make sure we properly set 'family' so it's automatically used for
697
+ # printing.
698
+ if family is not None:
699
+ assert family in ("inet", "inet6")
700
+ else:
701
+ family = self.family
702
+
703
+ # Prep the filter body
704
+ out = [f"filter {self.name} {{"]
705
+ out += [" " + c.output_junos() for c in self.comments if c]
706
+
707
+ # Add the policers
708
+ if self.policers:
709
+ for policer in self.policers:
710
+ out += [" " + x for x in policer.output()]
711
+
712
+ # Add interface-specific
713
+ if self.interface_specific:
714
+ out += [" " + "interface-specific;"]
715
+
716
+ # Add the terms
717
+ for t in self.terms:
718
+ out += [" " + x for x in t.output_junos()]
719
+ out += ["}"]
720
+
721
+ # Wrap in 'firewall {}' thingy.
722
+ if replace:
723
+ """
724
+ #out = ['firewall {', 'replace:'] + [' '+x for x in out] + ['}']
725
+ if family is None: # This happens more often
726
+ out = ['firewall {', 'replace:'] + [' '+x for x in out] + ['}']
727
+ else:
728
+ out = ['firewall {', family_head, 'replace:'] + [' '+x for x in out] + [family_tail, '}']
729
+ """
730
+
731
+ head = ["firewall {"]
732
+ body = ["replace:"] + [" " + x for x in out]
733
+ tail = ["}"]
734
+ if family is not None:
735
+ body = [f"family {family} {{"] + body + tail
736
+ body = [" " + x for x in body]
737
+ out = head + body + tail
738
+
739
+ return out
740
+
741
+ def output_ios(self, replace=False):
742
+ """
743
+ Output the ACL in IOS traditional format.
744
+
745
+ :param replace: If set the ACL is preceded by a ``no access-list`` line.
746
+ """
747
+ if self.name is None:
748
+ raise exceptions.MissingACLName("IOS format requires a name")
749
+ try:
750
+ x = int(self.name)
751
+ if not (100 <= x <= 199 or 2000 <= x <= 2699):
752
+ raise exceptions.BadACLName("IOS ACLs are 100-199 or 2000-2699")
753
+ except (TypeError, ValueError):
754
+ raise exceptions.BadACLName("IOS format requires a number as name")
755
+ out = [c.output_ios() for c in self.comments]
756
+ if self.policers:
757
+ raise exceptions.VendorSupportLacking("policers not supported in IOS")
758
+ if replace:
759
+ out.append("no access-list " + self.name)
760
+ prefix = f"access-list {self.name} "
761
+ for t in self.terms:
762
+ out += [x for x in t.output_ios(prefix)]
763
+ return out
764
+
765
+ def output_ios_brocade(self, replace=False, receive_acl=False):
766
+ """
767
+ Output the ACL in Brocade-flavored IOS format.
768
+
769
+ The difference between this and "traditional" IOS are:
770
+
771
+ - Stripping of comments
772
+ - Appending of ``ip rebind-acl`` or ``ip rebind-receive-acl`` line
773
+
774
+ :param replace: If set the ACL is preceded by a ``no access-list`` line.
775
+ :param receive_acl: If set the ACL is suffixed with a ``ip
776
+ rebind-receive-acl' instead of ``ip rebind-acl``.
777
+ """
778
+ self.strip_comments()
779
+
780
+ # Check if the is_receive_acl attr was set by the parser. This way we
781
+ # don't always have to pass the argument.
782
+ if hasattr(self, "is_receive_acl") and not receive_acl:
783
+ receive_acl = self.is_receive_acl
784
+
785
+ out = self.output_ios(replace=replace)
786
+ if receive_acl:
787
+ out.append(f"ip rebind-receive-acl {self.name}")
788
+ else:
789
+ out.append(f"ip rebind-acl {self.name}")
790
+
791
+ return out
792
+
793
+ def output_ios_named(self, replace=False):
794
+ """
795
+ Output the ACL in IOS named format.
796
+
797
+ :param replace: If set the ACL is preceded by a ``no access-list`` line.
798
+ """
799
+ if self.name is None:
800
+ raise exceptions.MissingACLName("IOS format requires a name")
801
+ out = [c.output_ios_named() for c in self.comments]
802
+ if self.policers:
803
+ raise exceptions.VendorSupportLacking("policers not supported in IOS")
804
+ if replace:
805
+ out.append("no ip access-list extended " + self.name)
806
+ out.append(f"ip access-list extended {self.name}")
807
+ for t in self.terms:
808
+ out += [x for x in t.output_ios_named(" ")]
809
+ return out
810
+
811
+ def output_iosxr(self, replace=False):
812
+ """
813
+ Output the ACL in IOS XR format.
814
+
815
+ :param replace: If set the ACL is preceded by a ``no ipv4 access-list`` line.
816
+ """
817
+ if self.name is None:
818
+ raise exceptions.MissingACLName("IOS XR format requires a name")
819
+ out = [c.output_iosxr() for c in self.comments]
820
+ if self.policers:
821
+ raise exceptions.VendorSupportLacking("policers not supported in IOS")
822
+ if replace:
823
+ out.append("no ipv4 access-list " + self.name)
824
+ out.append("ipv4 access-list " + self.name)
825
+ counter = 0 # 10 PRINT "CISCO SUCKS" 20 GOTO 10
826
+ for t in self.terms:
827
+ if t.name is None:
828
+ for line in t.output_ios():
829
+ counter = counter + 10
830
+ out += [" %d %s" % (counter, line)]
831
+ else:
832
+ try:
833
+ counter = int(t.name)
834
+ if not 1 <= counter <= 2147483646:
835
+ raise exceptions.BadTermName("Term %d out of range" % counter)
836
+ line = t.output_iosxr()
837
+ if len(line) > 1:
838
+ raise exceptions.VendorSupportLacking("one name per line")
839
+ out += [" " + line[0]]
840
+ except ValueError:
841
+ raise exceptions.BadTermName("IOS XR requires numbered terms")
842
+ return out
843
+
844
+ def name_terms(self):
845
+ """Assign names to all unnamed terms."""
846
+ n = 1
847
+ for t in self.terms:
848
+ if t.name is None:
849
+ t.name = "T%d" % n
850
+ n += 1
851
+
852
+ def strip_comments(self):
853
+ """Strips all comments from ACL header and all terms."""
854
+ self.comments = []
855
+ for term in self.terms:
856
+ term.comments = []
857
+
858
+
859
+ class Term:
860
+ """An individual term from which an ACL is made"""
861
+
862
+ def __init__(
863
+ self,
864
+ name=None,
865
+ action="accept",
866
+ match=None,
867
+ modifiers=None,
868
+ inactive=False,
869
+ isglobal=False,
870
+ extra=None,
871
+ ):
872
+ self.name = name
873
+ self.action = action
874
+ self.inactive = inactive
875
+ self.isglobal = isglobal
876
+ self.extra = extra
877
+ self.makediscard = False # set to True if 'make discard' is used
878
+ if match is None:
879
+ self.match = Matches()
880
+ else:
881
+ self.match = match
882
+
883
+ if modifiers is None:
884
+ self.modifiers = Modifiers()
885
+ else:
886
+ self.modifiers = modifiers
887
+
888
+ global Comments
889
+ self.comments = Comments
890
+ Comments = []
891
+
892
+ def __repr__(self):
893
+ return f"<Term: {self.name}>"
894
+
895
+ def getname(self):
896
+ return self.__name
897
+
898
+ def setname(self, name):
899
+ check_name(name, exceptions.BadTermName)
900
+ self.__name = name
901
+
902
+ def delname(self):
903
+ self.name = None
904
+
905
+ name = property(getname, setname, delname)
906
+
907
+ def getaction(self):
908
+ return self.__action
909
+
910
+ def setaction(self, action):
911
+ if action is None:
912
+ action = "accept"
913
+ if action == "next term":
914
+ action = ("next", "term")
915
+ if isinstance(action, str):
916
+ action = (action,)
917
+ if len(action) > 2:
918
+ raise exceptions.ActionError(
919
+ f'too many arguments to action "{str(action)}"'
920
+ )
921
+ action = tuple(action)
922
+ if action in (("accept",), ("discard",), ("reject",), ("next", "term")):
923
+ self.__action = action
924
+ elif action == ("permit",):
925
+ self.__action = ("accept",)
926
+ elif action == ("deny",):
927
+ self.__action = ("reject",)
928
+ elif action[0] == "reject":
929
+ if action[1] not in icmp_reject_codes:
930
+ raise exceptions.BadRejectCode("invalid rejection code " + action[1])
931
+ if action[1] == icmp_reject_codes[0]:
932
+ action = ("reject",)
933
+ self.__action = action
934
+ elif action[0] == "routing-instance":
935
+ check_name(action[1], exceptions.BadRoutingInstanceName)
936
+ self.__action = action
937
+ else:
938
+ raise exceptions.UnknownActionName(f'unknown action "{str(action)}"')
939
+
940
+ def delaction(self):
941
+ self.action = "accept"
942
+
943
+ action = property(getaction, setaction, delaction)
944
+
945
+ def set_action_or_modifier(self, action):
946
+ """
947
+ Add or replace a modifier, or set the primary action. This method exists
948
+ for the convenience of parsers.
949
+ """
950
+ try:
951
+ self.action = action
952
+ except exceptions.UnknownActionName:
953
+ if not isinstance(action, tuple):
954
+ self.modifiers[action] = None
955
+ else:
956
+ if len(action) == 1:
957
+ self.modifiers[action[0]] = None
958
+ else:
959
+ self.modifiers[action[0]] = action[1]
960
+
961
+ def output(self, format, *largs, **kwargs):
962
+ """
963
+ Output the term to the specified format
964
+
965
+ :param format: The desired output format.
966
+ """
967
+ return getattr(self, "output_" + format)(*largs, **kwargs)
968
+
969
+ def output_junos(self, *args, **kwargs):
970
+ """Convert the term to JunOS format."""
971
+ if self.name is None:
972
+ raise exceptions.MissingTermName("JunOS requires terms to be named")
973
+ out = ["{}term {} {{".format(self.inactive and "inactive: " or "", self.name)]
974
+ out += [" " + c.output_junos() for c in self.comments if c]
975
+ if self.extra:
976
+ blah = str(self.extra)
977
+ out += "/*", blah, "*/"
978
+ if self.match:
979
+ out.append(" from {")
980
+ out += [" " * 8 + x for x in self.match.output_junos()]
981
+ out.append(" }")
982
+ out.append(" then {")
983
+ acttext = " {};".format(" ".join(self.action))
984
+ # add a comment if 'make discard' is in use
985
+ if self.makediscard:
986
+ acttext += " /* REALLY AN ACCEPT, MODIFIED BY 'make discard' ABOVE */"
987
+ out.append(acttext)
988
+ out += [" " * 8 + x for x in self.modifiers.output_junos()]
989
+ out.append(" }")
990
+ out.append("}")
991
+ return out
992
+
993
+ def _ioslike(self, prefix=""):
994
+ if self.inactive:
995
+ raise exceptions.VendorSupportLacking("inactive terms not supported by IOS")
996
+ action = ""
997
+ if self.action == ("accept",):
998
+ action = "permit "
999
+ # elif self.action == ('reject',):
1000
+ elif self.action in (("reject",), ("discard",)):
1001
+ action = "deny "
1002
+ else:
1003
+ raise VendorSupportLacking(
1004
+ '"{}" action not supported by IOS'.format(" ".join(self.action))
1005
+ )
1006
+ suffix = ""
1007
+ for k, v in self.modifiers.items():
1008
+ if k == "syslog":
1009
+ suffix += " log"
1010
+ elif k == "count":
1011
+ pass # counters are implicit in IOS
1012
+ else:
1013
+ raise exceptions.VendorSupportLacking(
1014
+ f'"{k}" modifier not supported by IOS'
1015
+ )
1016
+ return [prefix + action + x + suffix for x in self.match.output_ios()]
1017
+
1018
+ def output_ios(self, prefix=None, acl_name=None):
1019
+ """
1020
+ Output term to IOS traditional format.
1021
+
1022
+ :param prefix: Prefix to use, default: 'access-list'
1023
+ :param acl_name: Name of access-list to display
1024
+ """
1025
+ comments = [c.output_ios() for c in self.comments]
1026
+ # If prefix isn't set, but name is, force the template
1027
+ if prefix is None and acl_name is not None:
1028
+ prefix = f"access-list {acl_name} "
1029
+
1030
+ # Or if prefix is set, but acl_name isn't, make sure prefix ends with ' '
1031
+ elif prefix is not None and acl_name is None:
1032
+ if not prefix.endswith(" "):
1033
+ prefix += " "
1034
+
1035
+ # Or if both are set, use them
1036
+ elif prefix is not None and acl_name is not None:
1037
+ prefix = f"{prefix.strip()} {acl_name.strip()} "
1038
+
1039
+ # Otherwise no prefix
1040
+ else:
1041
+ prefix = ""
1042
+
1043
+ return comments + self._ioslike(prefix)
1044
+
1045
+ def output_ios_named(self, prefix="", *args, **kwargs):
1046
+ """Output term to IOS named format."""
1047
+ comments = [c.output_ios_named() for c in self.comments]
1048
+ return comments + self._ioslike(prefix)
1049
+
1050
+ def output_iosxr(self, prefix="", *args, **kwargs):
1051
+ """Output term to IOS XR format."""
1052
+ comments = [c.output_iosxr() for c in self.comments]
1053
+ return comments + self._ioslike(prefix)
1054
+
1055
+
1056
+ class TermList(list):
1057
+ """Container class for Term objects within an ACL object."""
1058
+
1059
+ pass
1060
+
1061
+
1062
+ class Protocol:
1063
+ """
1064
+ A protocol object used for access membership tests in :class:`Term` objects.
1065
+ Acts like an integer, but stringify into a name if possible.
1066
+ """
1067
+
1068
+ num2name = {
1069
+ 1: "icmp",
1070
+ 2: "igmp",
1071
+ 4: "ipip",
1072
+ 6: "tcp",
1073
+ 8: "egp",
1074
+ 17: "udp",
1075
+ 41: "ipv6",
1076
+ # 46: 'rsvp',
1077
+ 47: "gre",
1078
+ 50: "esp",
1079
+ 51: "ah",
1080
+ 89: "ospf",
1081
+ 94: "nos",
1082
+ 103: "pim",
1083
+ # 112: 'vrrp' # Breaks Cisco compatibility
1084
+ }
1085
+
1086
+ name2num = {v: k for k, v in num2name.items()}
1087
+ name2num["ahp"] = 51 # undocumented Cisco special name
1088
+
1089
+ def __init__(self, arg):
1090
+ if isinstance(arg, Protocol):
1091
+ self.value = arg.value
1092
+ elif arg in Protocol.name2num:
1093
+ self.value = Protocol.name2num[arg]
1094
+ else:
1095
+ self.value = int(arg)
1096
+
1097
+ def __str__(self):
1098
+ if self.value in Protocol.num2name:
1099
+ return Protocol.num2name[self.value]
1100
+ else:
1101
+ return str(self.value)
1102
+
1103
+ def __repr__(self):
1104
+ return f"<{self.__class__.__name__}: {str(self)}>"
1105
+
1106
+ def _get_compare_value(self, other):
1107
+ """Helper to get comparison value from other."""
1108
+ try:
1109
+ return Protocol(other).value
1110
+ except (ValueError, TypeError):
1111
+ return NotImplemented
1112
+
1113
+ def __eq__(self, other):
1114
+ """Protocol(6) == 'tcp' == 6 == Protocol('6')."""
1115
+ other_value = self._get_compare_value(other)
1116
+ if other_value is NotImplemented:
1117
+ return NotImplemented
1118
+ return self.value == other_value
1119
+
1120
+ def __ne__(self, other):
1121
+ result = self.__eq__(other)
1122
+ if result is NotImplemented:
1123
+ return NotImplemented
1124
+ return not result
1125
+
1126
+ def __lt__(self, other):
1127
+ other_value = self._get_compare_value(other)
1128
+ if other_value is NotImplemented:
1129
+ return NotImplemented
1130
+ return self.value < other_value
1131
+
1132
+ def __le__(self, other):
1133
+ other_value = self._get_compare_value(other)
1134
+ if other_value is NotImplemented:
1135
+ return NotImplemented
1136
+ return self.value <= other_value
1137
+
1138
+ def __gt__(self, other):
1139
+ other_value = self._get_compare_value(other)
1140
+ if other_value is NotImplemented:
1141
+ return NotImplemented
1142
+ return self.value > other_value
1143
+
1144
+ def __ge__(self, other):
1145
+ other_value = self._get_compare_value(other)
1146
+ if other_value is NotImplemented:
1147
+ return NotImplemented
1148
+ return self.value >= other_value
1149
+
1150
+ def __hash__(self):
1151
+ return hash(self.value)
1152
+
1153
+ def __getattr__(self, name):
1154
+ """Allow arithmetic operations to work."""
1155
+ return getattr(self.value, name)
1156
+
1157
+
1158
+ class Matches(MyDict):
1159
+ """
1160
+ Container class for Term.match object used for membership tests on
1161
+ access checks.
1162
+ """
1163
+
1164
+ def __setitem__(self, key, arg):
1165
+ if key in (
1166
+ "ah-spi",
1167
+ "destination-mac-address",
1168
+ "ether-type",
1169
+ "esp-spi",
1170
+ "forwarding-class",
1171
+ "interface-group",
1172
+ "source-mac-address",
1173
+ "vlan-ether-type",
1174
+ "fragment-flags",
1175
+ "source-class",
1176
+ "destination-class",
1177
+ ):
1178
+ raise NotImplementedError(f"match on {key} not implemented")
1179
+
1180
+ if arg is None:
1181
+ raise exceptions.MatchError("match must have an argument")
1182
+
1183
+ negated = False
1184
+ if key.endswith("-except"):
1185
+ negated = True
1186
+ key = key[:-7]
1187
+
1188
+ if key in ("port", "source-port", "destination-port"):
1189
+ # Python 3: map() returns an iterator, convert to list
1190
+ arg = list(map(do_port_lookup, arg))
1191
+ check_range(arg, 0, 65535)
1192
+ elif key == "protocol":
1193
+ arg = list(map(do_protocol_lookup, arg))
1194
+ check_range(arg, 0, 255)
1195
+ elif key == "fragment-offset":
1196
+ arg = list(map(do_port_lookup, arg))
1197
+ check_range(arg, 0, 8191)
1198
+ elif key == "icmp-type":
1199
+ arg = list(map(do_icmp_type_lookup, arg))
1200
+ check_range(arg, 0, 255)
1201
+ elif key == "icmp-code":
1202
+ arg = list(map(do_icmp_code_lookup, arg))
1203
+ check_range(arg, 0, 255)
1204
+ elif key == "icmp-type-code":
1205
+ # Not intended for external use; this is for parser convenience.
1206
+ self["icmp-type"] = [arg[0]]
1207
+ try:
1208
+ self["icmp-code"] = [arg[1]]
1209
+ except IndexError:
1210
+ try:
1211
+ del self["icmp-code"]
1212
+ except KeyError:
1213
+ pass
1214
+ return
1215
+ elif key == "packet-length":
1216
+ arg = list(map(int, arg))
1217
+ check_range(arg, 0, 65535)
1218
+ elif key in ("address", "source-address", "destination-address"):
1219
+ arg = list(map(TIP, arg))
1220
+ elif key in ("prefix-list", "source-prefix-list", "destination-prefix-list"):
1221
+ for pl in arg:
1222
+ check_name(pl, exceptions.MatchError)
1223
+ elif key in tcp_flag_specials:
1224
+ # This cannot be the final form of how to represent tcp-flags.
1225
+ # Instead, we need to implement a real parser for it.
1226
+ # See: http://www.juniper.net/techpubs/software/junos/junos73/swconfig73-policy/html/firewall-config14.html
1227
+ arg = [tcp_flag_specials[key]]
1228
+ key = "tcp-flags"
1229
+ elif key == "tcp-flags":
1230
+ pass
1231
+ elif key == "ip-options":
1232
+ arg = list(map(do_ip_option_lookup, arg))
1233
+ check_range(arg, 0, 255)
1234
+ elif key in ("first-fragment", "is-fragment"):
1235
+ arg = []
1236
+ elif key == "dscp":
1237
+ pass
1238
+ elif key == "precedence":
1239
+ pass
1240
+ else:
1241
+ raise exceptions.UnknownMatchType(f'unknown match type "{key}"')
1242
+
1243
+ arg = RangeList(arg)
1244
+
1245
+ replacing = [key, key + "-except"]
1246
+ for type in ("port", "address", "prefix-list"):
1247
+ if key == type:
1248
+ for sd in ("source", "destination"):
1249
+ replacing += [sd + "-" + type, sd + "-" + type + "-except"]
1250
+ for k in replacing:
1251
+ try:
1252
+ del self[k]
1253
+ except KeyError:
1254
+ pass
1255
+ if negated:
1256
+ super().__setitem__(key + "-except", arg)
1257
+ else:
1258
+ super().__setitem__(key, arg)
1259
+
1260
+ def junos_str(self, pair):
1261
+ """
1262
+ Convert a 2-tuple into a hyphenated string, e.g. a range of ports. If
1263
+ not a tuple, tries to treat it as IPs or failing that, casts it to a
1264
+ string.
1265
+
1266
+ :param pair:
1267
+ The 2-tuple to convert.
1268
+ """
1269
+ try:
1270
+ return "%s-%s" % pair # Tuples back to ranges.
1271
+ except TypeError:
1272
+ try:
1273
+ # Make it print prefixes for /32, /128
1274
+ pair.NoPrefixForSingleIp = False
1275
+ except AttributeError:
1276
+ pass
1277
+ return str(pair)
1278
+
1279
+ def ios_port_str(self, ports):
1280
+ """
1281
+ Convert a list of tuples back to ranges, then to strings.
1282
+
1283
+ :param ports:
1284
+ A list of port tuples, e.g. [(0,65535), (1,2)].
1285
+ """
1286
+ a = []
1287
+ for port in ports:
1288
+ try:
1289
+ if port[0] == 0:
1290
+ # Omit ports if 0-65535
1291
+ if port[1] == 65535:
1292
+ continue
1293
+ a.append("lt %s" % (port[1] + 1))
1294
+ elif port[1] == 65535:
1295
+ a.append("gt %s" % (port[0] - 1))
1296
+ else:
1297
+ a.append("range {} {}".format(*port))
1298
+ except TypeError:
1299
+ a.append(f"eq {str(port)}")
1300
+ return a
1301
+
1302
+ def ios_address_str(self, addrs):
1303
+ """
1304
+ Convert a list of addresses to IOS-style stupid strings.
1305
+
1306
+ :param addrs:
1307
+ List of IP address objects.
1308
+ """
1309
+ a = []
1310
+ for addr in addrs:
1311
+ # xxx flag negated addresses?
1312
+ if addr.negated:
1313
+ raise exceptions.VendorSupportLacking(
1314
+ "negated addresses are not supported in IOS"
1315
+ )
1316
+ if addr.prefixlen() == 0:
1317
+ a.append("any")
1318
+ elif addr.prefixlen() == 32:
1319
+ a.append(f"host {addr.net()}")
1320
+ else:
1321
+ inverse_mask = make_inverse_mask(addr.prefixlen())
1322
+ a.append(f"{addr.net()} {inverse_mask}")
1323
+ return a
1324
+
1325
+ def output_junos(self):
1326
+ """Return a list that can form the ``from { ... }`` clause of the term."""
1327
+ a = []
1328
+ # Python 3: dict.keys() returns a view, convert to list for sorting
1329
+ keys = sorted(self.keys(), key=lambda x: junos_match_order.get(x, 999))
1330
+ for s in keys:
1331
+ # Python 3: map() returns an iterator, convert to list
1332
+ matches = list(map(self.junos_str, self[s]))
1333
+ has_negated_addrs = any(m for m in matches if m.endswith(" except"))
1334
+ if s in address_matches:
1335
+ # Check to see if any of the added is any, and if so break out,
1336
+ # but only if none of the addresses is "negated".
1337
+ if "0.0.0.0/0" in matches and not has_negated_addrs:
1338
+ continue
1339
+ a.append(s + " {")
1340
+ a += [" " + x + ";" for x in matches]
1341
+ a.append("}")
1342
+ continue
1343
+ if s == "tcp-flags" and len(self[s]) == 1:
1344
+ try:
1345
+ a.append(tcp_flag_rev[self[s][0]] + ";")
1346
+ continue
1347
+ except KeyError:
1348
+ pass
1349
+ if len(matches) == 1:
1350
+ s += " " + matches[0]
1351
+ elif len(matches) > 1:
1352
+ s += " [ " + " ".join(matches) + " ]"
1353
+ a.append(s + ";")
1354
+ return a
1355
+
1356
+ def output_ios(self):
1357
+ """Return a string of IOS ACL bodies."""
1358
+ # This is a mess! Thanks, Cisco.
1359
+ protos = []
1360
+ sources = []
1361
+ dests = []
1362
+ sourceports = []
1363
+ destports = []
1364
+ trailers = []
1365
+ for key, arg in self.items():
1366
+ if key == "source-port":
1367
+ sourceports += self.ios_port_str(arg)
1368
+ elif key == "destination-port":
1369
+ destports += self.ios_port_str(arg)
1370
+ elif key == "source-address":
1371
+ sources += self.ios_address_str(arg)
1372
+ elif key == "destination-address":
1373
+ dests += self.ios_address_str(arg)
1374
+ elif key == "protocol":
1375
+ # Python 3: map() returns an iterator, convert to list
1376
+ protos += list(map(str, arg))
1377
+ elif key == "icmp-type":
1378
+ for type in arg.expanded():
1379
+ if "icmp-code" in self:
1380
+ for code in self["icmp-code"]:
1381
+ try:
1382
+ destports.append(ios_icmp_names[(type, code)])
1383
+ except KeyError:
1384
+ destports.append("%d %d" % (type, code))
1385
+ else:
1386
+ try:
1387
+ destports.append(ios_icmp_names[(type,)])
1388
+ except KeyError:
1389
+ destports.append(str(type))
1390
+ elif key == "icmp-code":
1391
+ if "icmp-type" not in self:
1392
+ raise exceptions.VendorSupportLacking("need ICMP code w/type")
1393
+ elif key == "tcp-flags":
1394
+ if arg != [tcp_flag_specials["tcp-established"]]:
1395
+ raise exceptions.VendorSupportLacking(
1396
+ 'IOS supports only "tcp-flags established"'
1397
+ )
1398
+ trailers += ["established"]
1399
+ else:
1400
+ raise exceptions.VendorSupportLacking(f'"{key}" not in IOS')
1401
+ if not protos:
1402
+ protos = ["ip"]
1403
+ if not sources:
1404
+ sources = ["any"]
1405
+ if not dests:
1406
+ dests = ["any"]
1407
+ if not sourceports:
1408
+ sourceports = [""]
1409
+ if not destports:
1410
+ destports = [""]
1411
+ if not trailers:
1412
+ trailers = [""]
1413
+ a = []
1414
+
1415
+ # There is no mercy in this Dojo!!
1416
+ for proto in protos:
1417
+ for source in sources:
1418
+ for sourceport in sourceports:
1419
+ for dest in dests:
1420
+ for destport in destports:
1421
+ for trailer in trailers:
1422
+ s = proto + " " + source
1423
+ if sourceport:
1424
+ s += " " + sourceport
1425
+ s += " " + dest
1426
+ if destport:
1427
+ s += " " + destport
1428
+ if trailer:
1429
+ s += " " + trailer
1430
+ a.append(s)
1431
+ return a