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
@@ -0,0 +1,88 @@
1
+ """
2
+ A collection of CLI tools and utilities used by Trigger.
3
+ """
4
+
5
+ __author__ = "Jathan McCollum, Mike Biancaniello"
6
+ __maintainer__ = "Jathan McCollum"
7
+ __email__ = "jathan.mccollum@teamaol.com"
8
+ __copyright__ = "Copyright 2008-2013, AOL Inc."
9
+
10
+ import re
11
+ from collections import namedtuple
12
+
13
+ from .cli import get_user
14
+
15
+
16
+ def crypt_md5(passwd):
17
+ """
18
+ Returns an md5-crypt hash of a clear-text password.
19
+
20
+ To get md5-crypt from ``crypt(3)`` you must pass an 8-char string starting with
21
+ '$1$' and ending with '$', resulting in a 12-char salt. This only works on
22
+ systems where md5-crypt is default and is currently assumed to be Linux.
23
+
24
+ :param passwd:
25
+ Password string to be encrypted
26
+ """
27
+ import platform
28
+
29
+ if platform.system() == "Linux":
30
+ import crypt
31
+ import hashlib
32
+ import time
33
+
34
+ salt = "$1$" + hashlib.md5(str(time.time())).hexdigest()[0:8] + "$"
35
+ crypted = crypt.crypt(passwd, salt)
36
+
37
+ else:
38
+ try:
39
+ from passlib.hash import md5_crypt
40
+ except ImportError:
41
+ raise RuntimeError(
42
+ """When not using Linux, generating md5-crypt password hashes requires the `passlib` module."""
43
+ )
44
+ else:
45
+ crypted = md5_crypt.encrypt(passwd)
46
+
47
+ return crypted
48
+
49
+
50
+ JuniperElement = namedtuple("JuniperElement", "key value")
51
+
52
+
53
+ def strip_juniper_namespace(path, key, value):
54
+ """
55
+ Given a Juniper XML element, strip the namespace and return a 2-tuple.
56
+
57
+ This is designed to be used as a ``postprocessor`` with
58
+ `~trigger.utils.xmltodict.parse()`.
59
+
60
+ :param key:
61
+ The attribute name of the element.
62
+
63
+ :param value:
64
+ The value of the element.
65
+ """
66
+ marr = re.match(r"(ns1:|ns0:)", key)
67
+ if marr:
68
+ ns = marr.group(0)
69
+ key = key.replace(ns, "")
70
+
71
+ return JuniperElement(key, value)
72
+
73
+
74
+ NodePort = namedtuple("HostPort", "nodeName nodePort")
75
+
76
+
77
+ def parse_node_port(nodeport, delimiter=":"):
78
+ """
79
+ Parse a string in format 'hostname' or 'hostname:port' and return them
80
+ as a 2-tuple.
81
+ """
82
+ node, _, port = nodeport.partition(delimiter)
83
+ if port.isdigit():
84
+ port = int(port)
85
+ else:
86
+ port = None
87
+
88
+ return NodePort(str(node), port)
trigger/utils/cli.py ADDED
@@ -0,0 +1,349 @@
1
+ """
2
+ Command-line interface utilities for Trigger tools. Intended for re-usable
3
+ pieces of code like user prompts, that don't fit in other utils modules.
4
+ """
5
+
6
+ __author__ = "Jathan McCollum"
7
+ __maintainer__ = "Jathan McCollum"
8
+ __email__ = "jathan.mccollum@teamaol.com"
9
+ __copyright__ = "Copyright 2006-2012, AOL Inc.; 2013 Salesforce.com"
10
+
11
+ import datetime
12
+ import os
13
+ import pwd
14
+ import struct
15
+ import sys
16
+ import termios
17
+ import time
18
+ import tty
19
+ from fcntl import ioctl
20
+
21
+ from pytz import UTC, timezone
22
+
23
+ # Exports
24
+ __all__ = (
25
+ "yesno",
26
+ "get_terminal_width",
27
+ "get_terminal_size",
28
+ "Whirlygig",
29
+ "NullDevice",
30
+ "print_severed_head",
31
+ "min_sec",
32
+ "pretty_time",
33
+ "proceed",
34
+ "get_user",
35
+ )
36
+
37
+
38
+ # Functions
39
+ def yesno(prompt, default=False, autoyes=False):
40
+ """
41
+ Present a yes-or-no prompt, get input, and return a boolean.
42
+
43
+ The ``default`` argument is ignored if ``autoyes`` is set.
44
+
45
+ :param prompt:
46
+ Prompt text
47
+
48
+ :param default:
49
+ Yes if True; No if False
50
+
51
+ :param autoyes:
52
+ Automatically return True
53
+
54
+ Default behavior (hitting "enter" returns ``False``)::
55
+
56
+ >>> yesno('Blow up the moon?')
57
+ Blow up the moon? (y/N)
58
+ False
59
+
60
+ Reversed behavior (hitting "enter" returns ``True``)::
61
+
62
+ >>> yesno('Blow up the moon?', default=True)
63
+ Blow up the moon? (Y/n)
64
+ True
65
+
66
+ Automatically return ``True`` with ``autoyes``; no prompt is displayed::
67
+
68
+ >>> yesno('Blow up the moon?', autoyes=True)
69
+ True
70
+ """
71
+ if autoyes:
72
+ return True
73
+
74
+ sys.stdout.write(prompt)
75
+ if default:
76
+ sys.stdout.write(" (Y/n) ")
77
+ else:
78
+ sys.stdout.write(" (y/N) ")
79
+ sys.stdout.flush()
80
+
81
+ fd = sys.stdin.fileno()
82
+ attr = termios.tcgetattr(fd)
83
+
84
+ try:
85
+ tty.setraw(fd)
86
+ yn = sys.stdin.read(1)
87
+ finally:
88
+ termios.tcsetattr(fd, termios.TCSANOW, attr)
89
+ print("")
90
+
91
+ if yn in ("y", "Y"):
92
+ return True
93
+ elif yn in ("n", "N"):
94
+ return False
95
+ else:
96
+ return default
97
+
98
+
99
+ def proceed():
100
+ """Present a proceed prompt. Return ``True`` if Y, else ``False``"""
101
+ return input("\nDo you wish to proceed? [y/N] ").lower().startswith("y")
102
+
103
+
104
+ def get_terminal_width():
105
+ """Find and return stdout's terminal width, if applicable."""
106
+ try:
107
+ width = struct.unpack("hhhh", ioctl(1, termios.TIOCGWINSZ, " " * 8))[1]
108
+ except OSError:
109
+ width = sys.maxsize
110
+
111
+ return width
112
+
113
+
114
+ def get_terminal_size():
115
+ """Find and return stdouts terminal size as (height, width)"""
116
+ rows, cols = os.popen("stty size", "r").read().split()
117
+ return rows, cols
118
+
119
+
120
+ def get_user():
121
+ """Return the name of the current user."""
122
+ return pwd.getpwuid(os.getuid())[0]
123
+
124
+
125
+ def print_severed_head():
126
+ """
127
+ Prints a demon holding a severed head. Best used when things go wrong, like
128
+ production-impacting network outages caused by fat-fingered ACL changes.
129
+
130
+ Thanks to Jeff Sullivan for this best error message ever.
131
+ """
132
+ print(r"""
133
+
134
+ _( (~\
135
+ _ _ / ( \> > \
136
+ -/~/ / ~\ :; \ _ > /(~\/
137
+ || | | /\ ;\ |l _____ |; ( \/ > >
138
+ _\\)\)\)/ ;;; `8o __-~ ~\ d| \ //
139
+ ///(())(__/~;;\ "88p;. -. _\_;.oP (_._/ /
140
+ (((__ __ \\ \ `>,% (\ (\./)8" ;:' i
141
+ )))--`.'-- (( ;,8 \ ,;%%%: ./V^^^V' ;. ;.
142
+ ((\ | /)) .,88 `: ..,,;;;;,-::::::'_::\ ||\ ;[8: ;
143
+ )| ~-~ |(|(888; ..``'::::8888oooooo. :\`^^^/,,~--._ |88:: |
144
+ |\ -===- /| \8;; ``:. oo.8888888888:`((( o.ooo8888Oo;:;:' |
145
+ |_~-___-~_| `-\. ` `o`88888888b` )) 888b88888P""' ;
146
+ ; ~~~~;~~ "`--_`. b`888888888;(.,"888b888" ..::;-'
147
+ ; ; ~"-.... b`8888888:::::.`8888. .:;;;''
148
+ ; ; `:::. `:::OOO:::::::.`OO' ;;;''
149
+ : ; `. "``::::::'' .'
150
+ ; `. \_ /
151
+ ; ; +: ~~-- `:' -'; ACL LOADS FAILED
152
+ `: : .::/
153
+ ; ;;+_ :::. :..;;; YOU LOSE
154
+ ;;;;;;,;;;;;;;;,;
155
+
156
+ """)
157
+
158
+
159
+ def pretty_time(t):
160
+ """
161
+ Print a pretty version of timestamp, including timezone info. Expects
162
+ the incoming datetime object to have proper tzinfo.
163
+
164
+ :param t:
165
+ A ``datetime.datetime`` object
166
+
167
+ >>> import datetime
168
+ >>> from pytz import timezone
169
+ >>> localzone = timezone('US/Eastern')
170
+ <DstTzInfo 'US/Eastern' EST-1 day, 19:00:00 STD>
171
+ >>> t = datetime.datetime.now(localzone)
172
+ >>> print t
173
+ 2011-07-19 12:40:30.820920-04:00
174
+ >>> print pretty_time(t)
175
+ 09:40 PDT
176
+ >>> t = localzone.localize(datetime.datetime(2011,07,20,04,13))
177
+ >>> print t
178
+ 2011-07-20 04:13:00-05:00
179
+ >>> print pretty_time(t)
180
+ tomorrow 02:13 PDT
181
+ """
182
+ from trigger.conf import settings
183
+
184
+ localzone = timezone(os.environ.get("TZ", settings.BOUNCE_DEFAULT_TZ))
185
+ t = t.astimezone(localzone)
186
+ ct = t.replace(tzinfo=None) # convert to naive time
187
+ # to make the following calculations easier
188
+ # calculate naive 'now' in local time
189
+ # passing localzone into datetime.now directly can cause
190
+ # problems, see the 'pytz' docs if curious
191
+ now = datetime.datetime.now(UTC)
192
+ now = now.astimezone(localzone)
193
+ now = now.replace(tzinfo=None)
194
+ # and compute midnight
195
+ midnight = datetime.datetime.combine(now, datetime.time())
196
+ midnight += datetime.timedelta(1)
197
+ tomorrow = midnight + datetime.timedelta(1)
198
+ thisweek = midnight + datetime.timedelta(6)
199
+ if ct < midnight:
200
+ return t.strftime("%H:%M %Z")
201
+ elif ct < tomorrow:
202
+ return t.strftime("tomorrow %H:%M %Z")
203
+ elif ct < thisweek:
204
+ return t.strftime("%A %H:%M %Z")
205
+ else:
206
+ return t.strftime("%Y-%m-%d %H:%M %Z")
207
+
208
+
209
+ def min_sec(secs):
210
+ """
211
+ Takes an epoch timestamp and returns string of minutes:seconds.
212
+
213
+ :param secs:
214
+ Timestamp (in seconds)
215
+
216
+ >>> import time
217
+ >>> start = time.time() # Wait a few seconds
218
+ >>> finish = time.time()
219
+ >>> min_sec(finish - start)
220
+ '0:11'
221
+ """
222
+ secs = int(secs)
223
+ return "%d:%02d" % (secs / 60, secs % 60)
224
+
225
+
226
+ def setup_tty_for_pty(func):
227
+ """
228
+ Sets up tty for raw mode while retaining original tty settings and then
229
+ starts the reactor to connect to the pty. Upon exiting pty, restores
230
+ original tty settings.
231
+
232
+ :param func:
233
+ The callable to run after the tty is ready, such as ``reactor.run``
234
+ """
235
+ # Preserve original tty settings
236
+ stdin_fileno = sys.stdin.fileno()
237
+ old_ttyattr = tty.tcgetattr(stdin_fileno)
238
+
239
+ try:
240
+ # Enter raw mode on the local tty.
241
+ tty.setraw(stdin_fileno)
242
+ raw_ta = tty.tcgetattr(stdin_fileno)
243
+ raw_ta[tty.LFLAG] |= tty.ISIG
244
+ raw_ta[tty.OFLAG] |= tty.OPOST | tty.ONLCR
245
+
246
+ # Pass ^C through so we can abort traceroute, etc.
247
+ raw_ta[tty.CC][tty.VINTR] = "\x18" # ^X is the new ^C
248
+
249
+ # Ctrl-Z is used by a lot of vendors to exit config mode
250
+ raw_ta[tty.CC][tty.VSUSP] = 0 # disable ^Z
251
+ tty.tcsetattr(stdin_fileno, tty.TCSANOW, raw_ta)
252
+
253
+ # Execute our callable here
254
+ func()
255
+
256
+ finally:
257
+ # Restore original tty settings
258
+ tty.tcsetattr(stdin_fileno, tty.TCSANOW, old_ttyattr)
259
+
260
+
261
+ def update_password_and_reconnect(hostname):
262
+ """
263
+ Prompts the user to update their password and reconnect to the target
264
+ device
265
+
266
+ :param hostname: Hostname of the device to connect to.
267
+ """
268
+ if yesno(
269
+ "Authentication failed, would you like to update your password?", default=True
270
+ ):
271
+ from trigger import tacacsrc
272
+
273
+ tacacsrc.update_credentials(hostname)
274
+ if yesno(f"\nReconnect to {hostname}?", default=True):
275
+ # Replaces the current process w/ same pid
276
+ args = [sys.argv[0]]
277
+ for arg in ("-o", "--oob"):
278
+ if arg in sys.argv:
279
+ idx = sys.argv.index(arg)
280
+ args.append(sys.argv[idx])
281
+ break
282
+ args.append(hostname)
283
+ os.execl(sys.executable, sys.executable, *args)
284
+
285
+
286
+ # Classes
287
+ class NullDevice:
288
+ """
289
+ Used to supress output to ``sys.stdout`` (aka ``print``).
290
+
291
+ Example::
292
+
293
+ >>> from trigger.utils.cli import NullDevice
294
+ >>> import sys
295
+ >>> print "1 - this will print to STDOUT"
296
+ 1 - this will print to STDOUT
297
+ >>> original_stdout = sys.stdout # keep a reference to STDOUT
298
+ >>> sys.stdout = NullDevice() # redirect the real STDOUT
299
+ >>> print "2 - this won't print"
300
+ >>>
301
+ >>> sys.stdout = original_stdout # turn STDOUT back on
302
+ >>> print "3 - this will print to SDTDOUT"
303
+ 3 - this will print to SDTDOUT
304
+ """
305
+
306
+ def write(self, s):
307
+ pass
308
+
309
+
310
+ class Whirlygig:
311
+ """
312
+ Prints a whirlygig for use in displaying pending operation in a command-line tool.
313
+ Guaranteed to make the user feel warm and fuzzy and be 1000% bug-free.
314
+
315
+ :param start_msg: The status message displayed to the user (e.g. "Doing stuff:")
316
+ :param done_msg: The completion message displayed upon completion (e.g. "Done.")
317
+ :param max: Integer of the number of whirlygig repetitions to perform
318
+
319
+ Example::
320
+
321
+ >>> Whirlygig("Doing stuff:", "Done.", 12).run()
322
+ """
323
+
324
+ def __init__(self, start_msg="", done_msg="", max=100):
325
+ self.unbuff = os.fdopen(sys.stdout.fileno(), "w", 0)
326
+ self.start_msg = start_msg
327
+ self.done_msg = done_msg
328
+ self.max = max
329
+ self.whirlygig = ["|", "/", "-", "\\"]
330
+ self.whirl = self.whirlygig[:]
331
+ self.first = False
332
+
333
+ def do_whirl(self, whirl):
334
+ if not self.first:
335
+ self.unbuff.write(self.start_msg + " ")
336
+ self.first = True
337
+ self.unbuff.write(f"\b{whirl.pop(0)}")
338
+
339
+ def run(self):
340
+ """Executes the whirlygig!"""
341
+ cnt = 1
342
+ while cnt <= self.max:
343
+ try:
344
+ self.do_whirl(self.whirl)
345
+ except IndexError:
346
+ self.whirl = self.whirlygig[:]
347
+ time.sleep(0.1)
348
+ cnt += 1
349
+ print("\b" + self.done_msg)
@@ -0,0 +1,77 @@
1
+ """
2
+ Utils to import modules.
3
+
4
+ Taken verbatim from ``django.utils.importlib`` in Django 1.4.
5
+ """
6
+
7
+ import os
8
+ import sys
9
+
10
+ # Exports
11
+ __all__ = ("import_module", "import_module_from_path")
12
+
13
+
14
+ # Functions
15
+ def _resolve_name(name, package, level):
16
+ """Return the absolute name of the module to be imported."""
17
+ if not hasattr(package, "rindex"):
18
+ raise ValueError("'package' not set to a string")
19
+ dot = len(package)
20
+ for x in range(level, 1, -1):
21
+ try:
22
+ dot = package.rindex(".", 0, dot)
23
+ except ValueError:
24
+ raise ValueError("attempted relative import beyond top-level package")
25
+ return f"{package[:dot]}.{name}"
26
+
27
+
28
+ def import_module(name, package=None):
29
+ """
30
+ Import a module and return the module object.
31
+
32
+ The ``package`` argument is required when performing a relative import. It
33
+ specifies the package to use as the anchor point from which to resolve the
34
+ relative import to an absolute import.
35
+
36
+ """
37
+ if name.startswith("."):
38
+ if not package:
39
+ raise TypeError("relative imports require the 'package' argument")
40
+ level = 0
41
+ for character in name:
42
+ if character != ".":
43
+ break
44
+ level += 1
45
+ name = _resolve_name(name[level:], package, level)
46
+ __import__(name)
47
+ return sys.modules[name]
48
+
49
+
50
+ def import_module_from_path(full_path, global_name):
51
+ """
52
+ Import a module from a file path and return the module object.
53
+
54
+ Allows one to import from anywhere, something ``__import__()`` does not do.
55
+ The module is added to ``sys.modules`` as ``global_name``.
56
+
57
+ :param full_path:
58
+ The absolute path to the module .py file
59
+
60
+ :param global_name:
61
+ The name assigned to the module in sys.modules. To avoid
62
+ confusion, the global_name should be the same as the variable to which
63
+ you're assigning the returned module.
64
+ """
65
+ path, filename = os.path.split(full_path)
66
+ module, ext = os.path.splitext(filename)
67
+ sys.path.append(path)
68
+
69
+ try:
70
+ mymodule = __import__(module)
71
+ sys.modules[global_name] = mymodule
72
+ except ImportError:
73
+ raise ImportError(f"Module could not be imported from {full_path}.")
74
+ finally:
75
+ del sys.path[-1]
76
+
77
+ return mymodule
@@ -0,0 +1,157 @@
1
+ """
2
+ Functions that perform network-based things like ping, port tests, etc.
3
+ """
4
+
5
+ import os
6
+ import shlex
7
+ import socket
8
+ import subprocess
9
+
10
+ from trigger.conf import settings
11
+
12
+ # Exports
13
+ __all__ = ("ping", "test_tcp_port", "test_ssh", "address_is_internal")
14
+
15
+
16
+ # Constants
17
+ # SSH version strings used to validate SSH banners.
18
+ SSH_VERSION_STRINGS = (
19
+ "SSH-1.99",
20
+ "SSH-2.0",
21
+ "dcos_sshd run in non-FIPS mode", # Cisco Nexus not in FIPS mode
22
+ )
23
+
24
+
25
+ # Functions
26
+ def ping(host, count=1, timeout=5):
27
+ """
28
+ Returns pass/fail for a ping. Supports POSIX only.
29
+
30
+ :param host:
31
+ Hostname or address
32
+
33
+ :param count:
34
+ Repeat count
35
+
36
+ :param timeout:
37
+ Timeout in seconds
38
+
39
+ >>> from trigger.utils import network
40
+ >>> network.ping('aol.com')
41
+ True
42
+ >>> network.ping('192.168.199.253')
43
+ False
44
+ """
45
+
46
+ ping_command = "ping -q -c%d -W%d %s" % (count, timeout, host)
47
+ status = None
48
+ with open(os.devnull, "w") as devnull_fd:
49
+ status = subprocess.call(
50
+ shlex.split(ping_command),
51
+ stdout=devnull_fd,
52
+ stderr=devnull_fd,
53
+ close_fds=True,
54
+ )
55
+
56
+ # Linux RC: 0 = success, 256 = failure, 512 = unknown host
57
+ # Darwin RC: 0 = success, 512 = failure, 17408 = unknown host
58
+ return status == 0
59
+
60
+
61
+ def test_tcp_port(host, port=23, timeout=5, check_result=False, expected_result=""):
62
+ """
63
+ Attempts to connect to a TCP port. Returns a Boolean.
64
+
65
+ If ``check_result`` is set, the first line of output is retreived from the
66
+ connection and the starting characters must match ``expected_result``.
67
+
68
+ :param host:
69
+ Hostname or address
70
+
71
+ :param port:
72
+ Destination port
73
+
74
+ :param timeout:
75
+ Timeout in seconds
76
+
77
+ :param check_result:
78
+ Whether or not to do a string check (e.g. version banner)
79
+
80
+ :param expected_result:
81
+ The expected result!
82
+
83
+ >>> test_tcp_port('aol.com', 80)
84
+ True
85
+ >>> test_tcp_port('aol.com', 12345)
86
+ False
87
+ """
88
+ # Python 3: Use socket instead of deprecated telnetlib for port testing
89
+ sock = None
90
+ try:
91
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
92
+ sock.settimeout(timeout)
93
+ sock.connect((host, port))
94
+
95
+ if check_result:
96
+ # Read the initial banner/response
97
+ sock.settimeout(2) # Short timeout for reading
98
+ result = sock.recv(1024)
99
+ return result.startswith(
100
+ expected_result.encode()
101
+ if isinstance(expected_result, str)
102
+ else expected_result
103
+ )
104
+
105
+ return True
106
+ except (TimeoutError, OSError):
107
+ return False
108
+ finally:
109
+ if sock:
110
+ sock.close()
111
+
112
+
113
+ def test_ssh(host, port=22, timeout=5, version=SSH_VERSION_STRINGS):
114
+ """
115
+ Connect to a TCP port and confirm the SSH version. Defaults to SSHv2.
116
+
117
+ Note that the default of ('SSH-1.99', 'SSH-2.0') both indicate SSHv2 per
118
+ RFC 4253. (Ref: http://en.wikipedia.org/wiki/Secure_Shell#Version_1.99)
119
+
120
+ :param host:
121
+ Hostname or address
122
+
123
+ :param port:
124
+ Destination port
125
+
126
+ :param timeout:
127
+ Timeout in seconds
128
+
129
+ :param version:
130
+ The SSH version prefix (e.g. "SSH-2.0"). This may also be a tuple of
131
+ prefixes.
132
+
133
+ >>> test_ssh('localhost')
134
+ True
135
+ >>> test_ssh('localhost', version='SSH-1.5')
136
+ False
137
+ """
138
+ return test_tcp_port(
139
+ host, port, timeout, check_result=True, expected_result=version
140
+ )
141
+
142
+
143
+ def address_is_internal(ip):
144
+ """
145
+ Determines if an IP address is internal to your network. Relies on
146
+ networks specified in :mod:`settings.INTERNAL_NETWORKS`.
147
+
148
+ :param ip:
149
+ IP address to test.
150
+
151
+ >>> address_is_internal('1.1.1.1')
152
+ False
153
+ """
154
+ for i in settings.INTERNAL_NETWORKS:
155
+ if ip in i:
156
+ return True
157
+ return False