1
0

test_dns.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691
  1. # -*- coding: utf-8 -*-
  2. """
  3. """
  4. from __future__ import absolute_import, print_function, unicode_literals
  5. # Python
  6. import socket
  7. import textwrap
  8. import salt.modules.cmdmod
  9. import salt.utils.dns
  10. # Salt
  11. from salt._compat import ipaddress
  12. from salt.ext.six.moves import zip # pylint: disable=redefined-builtin
  13. from salt.utils.dns import (
  14. _data2rec,
  15. _data2rec_group,
  16. _lookup_dig,
  17. _lookup_drill,
  18. _lookup_gai,
  19. _lookup_host,
  20. _lookup_nslookup,
  21. _to_port,
  22. _tree,
  23. _weighted_order,
  24. lookup,
  25. )
  26. from salt.utils.odict import OrderedDict
  27. # Testing
  28. from tests.support.helpers import requires_network
  29. from tests.support.mock import MagicMock, patch
  30. from tests.support.unit import TestCase, skipIf
  31. class DNShelpersCase(TestCase):
  32. """
  33. Tests for the parser helpers
  34. """
  35. def test_port(self):
  36. for right in (1, 42, "123", 65535):
  37. self.assertEqual(_to_port(right), int(right))
  38. for wrong in (0, 65536, 100000, "not-a-port"):
  39. self.assertRaises(ValueError, _to_port, wrong)
  40. def test_tree(self):
  41. test_map = (
  42. "ex1.nl",
  43. "o.1.example.eu",
  44. "a1a.b2b.c3c.example.com",
  45. "c3c.example.co.uk",
  46. "c3c.example.mil.ng",
  47. )
  48. res_map = (
  49. ["ex1.nl"],
  50. ["o.1.example.eu", "1.example.eu", "example.eu"],
  51. [
  52. "a1a.b2b.c3c.example.com",
  53. "b2b.c3c.example.com",
  54. "c3c.example.com",
  55. "example.com",
  56. ],
  57. ["c3c.example.co.uk", "example.co.uk"],
  58. ["c3c.example.mil.ng", "example.mil.ng"],
  59. )
  60. for domain, result in zip(test_map, res_map):
  61. self.assertEqual(_tree(domain), result)
  62. def test_weight(self):
  63. recs = [
  64. [],
  65. [{"weight": 100, "name": "nescio"}],
  66. [
  67. {"weight": 100, "name": "nescio1"},
  68. {"weight": 100, "name": "nescio2"},
  69. {"weight": 100, "name": "nescio3"},
  70. {"weight": 100, "name": "nescio4"},
  71. {"weight": 100, "name": "nescio5"},
  72. {"weight": 100, "name": "nescio6"},
  73. {"weight": 100, "name": "nescio7"},
  74. {"weight": 100, "name": "nescio8"},
  75. ],
  76. ]
  77. # What are the odds of this tripping over a build
  78. # 1/(8!^4) builds?
  79. self.assertNotEqual(
  80. _weighted_order(list(recs[-1])),
  81. _weighted_order(list(recs[-1])),
  82. _weighted_order(list(recs[-1])),
  83. )
  84. for recset in recs:
  85. rs_res = _weighted_order(list(recset))
  86. self.assertTrue(all(rec["name"] in rs_res for rec in recset))
  87. def test_data2rec(self):
  88. right = [
  89. "10.0.0.1",
  90. "10 mbox.example.com",
  91. "10 20 30 example.com",
  92. ]
  93. schemas = [
  94. OrderedDict((("address", ipaddress.IPv4Address),)),
  95. OrderedDict((("preference", int), ("name", str),)),
  96. OrderedDict(
  97. (("prio", int), ("weight", int), ("port", _to_port), ("name", str),)
  98. ),
  99. ]
  100. results = [
  101. ipaddress.IPv4Address(right[0]),
  102. {"preference": 10, "name": "mbox.example.com"},
  103. {"prio": 10, "weight": 20, "port": 30, "name": "example.com"},
  104. ]
  105. for rdata, rschema, res in zip(right, schemas, results):
  106. self.assertEqual(_data2rec(rschema, rdata), res)
  107. wrong = [
  108. "not-an-ip",
  109. "hundred 20 30 interror.example.com",
  110. "10 toolittle.example.com",
  111. ]
  112. for rdata, rschema in zip(wrong, schemas):
  113. self.assertRaises(ValueError, _data2rec, rschema, rdata)
  114. def test_data2group(self):
  115. right = [
  116. ["10 mbox.example.com"],
  117. [
  118. "10 mbox1.example.com",
  119. "20 mbox2.example.com",
  120. "20 mbox3.example.com",
  121. "30 mbox4.example.com",
  122. "30 mbox5.example.com",
  123. "30 mbox6.example.com",
  124. ],
  125. ]
  126. rschema = OrderedDict((("prio", int), ("srvr", str),))
  127. results = [
  128. OrderedDict([(10, ["mbox.example.com"])]),
  129. OrderedDict(
  130. [
  131. (10, ["mbox1.example.com"]),
  132. (20, ["mbox2.example.com", "mbox3.example.com"]),
  133. (
  134. 30,
  135. ["mbox4.example.com", "mbox5.example.com", "mbox6.example.com"],
  136. ),
  137. ]
  138. ),
  139. ]
  140. for rdata, res in zip(right, results):
  141. group = _data2rec_group(rschema, rdata, "prio")
  142. self.assertEqual(group, res)
  143. class DNSlookupsCase(TestCase):
  144. """
  145. Test the lookup result parsers
  146. Note that by far and large the parsers actually
  147. completely ignore the input name or output content
  148. only nslookup is bad enough to be an exception to that
  149. a lookup function
  150. - raises ValueError when an incorrect DNS type is given
  151. - returns False upon error
  152. - returns [*record-data] upon succes/no records
  153. """
  154. CMD_RET = {"pid": 12345, "retcode": 0, "stderr": "", "stdout": ""}
  155. RESULTS = {
  156. "A": [
  157. ["10.1.1.1"], # one-match
  158. ["10.1.1.1", "10.2.2.2", "10.3.3.3"], # multi-match
  159. ],
  160. "AAAA": [
  161. ["2a00:a00:b01:c02:d03:e04:f05:111"], # one-match
  162. [
  163. "2a00:a00:b01:c02:d03:e04:f05:111",
  164. "2a00:a00:b01:c02:d03:e04:f05:222",
  165. "2a00:a00:b01:c02:d03:e04:f05:333",
  166. ], # multi-match
  167. ],
  168. "CAA": [['0 issue "exampleca.com"', '0 iodef "mailto:sslabuse@example.com"']],
  169. "CNAME": [["web.example.com."]],
  170. "MX": [
  171. ["10 mx1.example.com."],
  172. ["10 mx1.example.com.", "20 mx2.example.eu.", "30 mx3.example.nl."],
  173. ],
  174. "SSHFP": [
  175. [
  176. "1 1 0aabda8af5418108e8a5d3f90f207226b2c89fbe",
  177. "1 2 500ca871d8e255e01f1261a2370c4e5406b8712f19916d3ab9f86344a67e5597",
  178. "3 1 a3b605ce6f044617c6077c46a7cd5d17a767f0d5",
  179. "4 2 0360d0a5a2fa550f972259e7374533add7ac8e5f303322a5b8e208bbc859ab1b",
  180. ]
  181. ],
  182. "TXT": [
  183. [
  184. "v=spf1 a include:_spf4.example.com include:mail.example.eu ip4:10.0.0.0/8 ip6:2a00:a00:b01::/48 ~all"
  185. ]
  186. ],
  187. }
  188. def _mock_cmd_ret(self, delta_res):
  189. """
  190. Take CMD_RET and update it w/(a list of ) delta_res
  191. Mock cmd.run_all w/it
  192. :param delta_res: list or dict
  193. :return: patched cmd.run_all
  194. """
  195. if isinstance(delta_res, (list, tuple)):
  196. test_res = []
  197. for dres in delta_res:
  198. tres = self.CMD_RET.copy()
  199. tres.update(dres)
  200. test_res.append(tres)
  201. cmd_mock = MagicMock(side_effect=test_res)
  202. else:
  203. test_res = self.CMD_RET.copy()
  204. test_res.update(delta_res)
  205. cmd_mock = MagicMock(return_value=test_res)
  206. return patch.dict(
  207. salt.utils.dns.__salt__, {"cmd.run_all": cmd_mock}, clear=True
  208. )
  209. def _test_cmd_lookup(
  210. self, lookup_cb, wrong_type, wrong, right, empty=None, secure=None
  211. ):
  212. """
  213. Perform a given battery of tests against a given lookup utilizing cmd.run_all
  214. :param wrong_type: delta cmd.run_all output for an incorrect DNS type
  215. :param wrong: delta cmd.run_all output for any error
  216. :param right: delta cmd.run_all output for outputs in RESULTS
  217. :param empty: delta cmd.run_all output for anything that won't return matches
  218. :param secure: delta cmd.run_all output for secured RESULTS
  219. """
  220. # wrong
  221. for wrong in wrong:
  222. with self._mock_cmd_ret(wrong):
  223. self.assertEqual(lookup_cb("mockq", "A"), False)
  224. # empty response
  225. if empty is None:
  226. empty = {}
  227. with self._mock_cmd_ret(empty):
  228. self.assertEqual(lookup_cb("mockq", "AAAA"), [])
  229. # wrong types
  230. with self._mock_cmd_ret(wrong_type):
  231. self.assertRaises(ValueError, lookup_cb, "mockq", "WRONG")
  232. # Regular outputs
  233. for rec_t, tests in right.items():
  234. with self._mock_cmd_ret([dict([("stdout", dres)]) for dres in tests]):
  235. for test_res in self.RESULTS[rec_t]:
  236. if rec_t in ("A", "AAAA", "CNAME", "SSHFP"):
  237. rec = "mocksrvr.example.com"
  238. else:
  239. rec = "example.com"
  240. lookup_res = lookup_cb(rec, rec_t)
  241. if rec_t == "SSHFP":
  242. # Some resolvers 'split' the output and/or capitalize differently.
  243. # So we need to workaround that here as well
  244. lookup_res = [
  245. res[:4] + res[4:].replace(" ", "").lower()
  246. for res in lookup_res
  247. ]
  248. self.assertEqual(
  249. lookup_res,
  250. test_res,
  251. # msg='Error parsing {0} returns'.format(rec_t)
  252. )
  253. if not secure:
  254. return
  255. # Regular outputs are insecure outputs (e.g. False)
  256. for rec_t, tests in right.items():
  257. with self._mock_cmd_ret([dict([("stdout", dres)]) for dres in tests]):
  258. for _ in self.RESULTS[rec_t]:
  259. self.assertEqual(
  260. lookup_cb("mocksrvr.example.com", rec_t, secure=True),
  261. False,
  262. msg="Insecure {0} returns should not be returned".format(rec_t),
  263. )
  264. for rec_t, tests in secure.items():
  265. with self._mock_cmd_ret([dict([("stdout", dres)]) for dres in tests]):
  266. for test_res in self.RESULTS[rec_t]:
  267. self.assertEqual(
  268. lookup_cb("mocksrvr.example.com", rec_t, secure=True),
  269. test_res,
  270. msg="Error parsing DNSSEC'd {0} returns".format(rec_t),
  271. )
  272. @skipIf(not salt.utils.dns.HAS_NSLOOKUP, "nslookup is not available")
  273. @requires_network()
  274. def test_lookup_with_servers(self):
  275. rights = {
  276. "A": [
  277. "Name:\tmocksrvr.example.com\nAddress: 10.1.1.1",
  278. "Name:\tmocksrvr.example.com\nAddress: 10.1.1.1\n"
  279. "Name:\tweb.example.com\nAddress: 10.2.2.2\n"
  280. "Name:\tweb.example.com\nAddress: 10.3.3.3",
  281. ],
  282. "AAAA": [
  283. "mocksrvr.example.com\thas AAAA address 2a00:a00:b01:c02:d03:e04:f05:111",
  284. "mocksrvr.example.com\tcanonical name = web.example.com.\n"
  285. "web.example.com\thas AAAA address 2a00:a00:b01:c02:d03:e04:f05:111\n"
  286. "web.example.com\thas AAAA address 2a00:a00:b01:c02:d03:e04:f05:222\n"
  287. "web.example.com\thas AAAA address 2a00:a00:b01:c02:d03:e04:f05:333",
  288. ],
  289. "CNAME": ["mocksrvr.example.com\tcanonical name = web.example.com."],
  290. "MX": [
  291. "example.com\tmail exchanger = 10 mx1.example.com.",
  292. "example.com\tmail exchanger = 10 mx1.example.com.\n"
  293. "example.com\tmail exchanger = 20 mx2.example.eu.\n"
  294. "example.com\tmail exchanger = 30 mx3.example.nl.",
  295. ],
  296. "TXT": [
  297. 'example.com\ttext = "v=spf1 a include:_spf4.example.com include:mail.example.eu ip4:10.0.0.0/8 ip6:2a00:a00:b01::/48 ~all"'
  298. ],
  299. }
  300. for rec_t, tests in rights.items():
  301. with self._mock_cmd_ret([dict([("stdout", dres)]) for dres in tests]):
  302. for test_res in self.RESULTS[rec_t]:
  303. if rec_t in ("A", "AAAA", "CNAME"):
  304. rec = "mocksrvr.example.com"
  305. else:
  306. rec = "example.com"
  307. self.assertEqual(
  308. lookup(rec, rec_t, method="nslookup", servers="8.8.8.8"),
  309. test_res,
  310. )
  311. @skipIf(not salt.utils.dns.HAS_DIG, "dig is not available")
  312. def test_dig_options(self):
  313. cmd = "dig {0} -v".format(salt.utils.dns.DIG_OPTIONS)
  314. cmd = salt.modules.cmdmod.retcode(
  315. cmd, python_shell=False, output_loglevel="quiet"
  316. )
  317. self.assertEqual(cmd, 0)
  318. def test_dig(self):
  319. wrong_type = {"retcode": 0, "stderr": ";; Warning, ignoring invalid type ABC"}
  320. wrongs = [
  321. {
  322. "retcode": 9,
  323. "stderr": ";; connection timed out; no servers could be reached",
  324. },
  325. ]
  326. # example returns for dig +search +fail +noall +answer +noclass +nosplit +nottl -t {rtype} {name}
  327. rights = {
  328. "A": [
  329. "mocksrvr.example.com.\tA\t10.1.1.1",
  330. "web.example.com.\t\tA\t10.1.1.1\n"
  331. "web.example.com.\t\tA\t10.2.2.2\n"
  332. "web.example.com.\t\tA\t10.3.3.3",
  333. ],
  334. "AAAA": [
  335. "mocksrvr.example.com.\tA\t2a00:a00:b01:c02:d03:e04:f05:111",
  336. "mocksrvr.example.com.\tCNAME\tweb.example.com.\n"
  337. "web.example.com.\t\tAAAA\t2a00:a00:b01:c02:d03:e04:f05:111\n"
  338. "web.example.com.\t\tAAAA\t2a00:a00:b01:c02:d03:e04:f05:222\n"
  339. "web.example.com.\t\tAAAA\t2a00:a00:b01:c02:d03:e04:f05:333",
  340. ],
  341. "CAA": [
  342. 'example.com.\t\tCAA\t0 issue "exampleca.com"\n'
  343. 'example.com.\t\tCAA\t0 iodef "mailto:sslabuse@example.com"'
  344. ],
  345. "CNAME": ["mocksrvr.example.com.\tCNAME\tweb.example.com."],
  346. "MX": [
  347. "example.com.\t\tMX\t10 mx1.example.com.",
  348. "example.com.\t\tMX\t10 mx1.example.com.\nexample.com.\t\tMX\t20 mx2.example.eu.\nexample.com.\t\tMX\t30 mx3.example.nl.",
  349. ],
  350. "SSHFP": [
  351. "mocksrvr.example.com.\tSSHFP\t1 1 0AABDA8AF5418108E8A5D3F90F207226B2C89FBE\n"
  352. "mocksrvr.example.com.\tSSHFP\t1 2 500CA871D8E255E01F1261A2370C4E5406B8712F19916D3AB9F86344A67E5597\n"
  353. "mocksrvr.example.com.\tSSHFP\t3 1 A3B605CE6F044617C6077C46A7CD5D17A767F0D5\n"
  354. "mocksrvr.example.com.\tSSHFP\t4 2 0360D0A5A2FA550F972259E7374533ADD7AC8E5F303322A5B8E208BBC859AB1B"
  355. ],
  356. "TXT": [
  357. 'example.com.\tTXT\t"v=spf1 a include:_spf4.example.com include:mail.example.eu ip4:10.0.0.0/8 ip6:2a00:a00:b01::/48 ~all"'
  358. ],
  359. }
  360. secure = {
  361. "A": [
  362. "mocksrvr.example.com.\tA\t10.1.1.1\n"
  363. "mocksrvr.example.com.\tRRSIG\tA 8 3 7200 20170420000000 20170330000000 1629 example.com. Hv4p37EF55LKBxUNYpnhWiEYqfmMct0z0WgDJyG5reqYfl+z4HX/kaoi Wr2iCYuYeB4Le7BgnMSb77UGHPWE7lCQ8z5gkgJ9rCDrooJzSTVdnHfw 1JQ7txRSp8Rj2GLf/L3Ytuo6nNZTV7bWUkfhOs61DAcOPHYZiX8rVhIh UAE=",
  364. "web.example.com.\t\tA\t10.1.1.1\n"
  365. "web.example.com.\t\tA\t10.2.2.2\n"
  366. "web.example.com.\t\tA\t10.3.3.3\n"
  367. "web.example.com.\tRRSIG\tA 8 3 7200 20170420000000 20170330000000 1629 example.com. Hv4p37EF55LKBxUNYpnhWiEYqfmMct0z0WgDJyG5reqYfl+z4HX/kaoi Wr2iCYuYeB4Le7BgnMSb77UGHPWE7lCQ8z5gkgJ9rCDrooJzSTVdnHfw 1JQ7txRSp8Rj2GLf/L3Ytuo6nNZTV7bWUkfhOs61DAcOPHYZiX8rVhIh UAE=",
  368. ]
  369. }
  370. self._test_cmd_lookup(
  371. _lookup_dig,
  372. wrong=wrongs,
  373. right=rights,
  374. wrong_type=wrong_type,
  375. secure=secure,
  376. )
  377. def test_drill(self):
  378. # all Drill returns look like this
  379. RES_TMPL = textwrap.dedent(
  380. """\
  381. ;; ->>HEADER<<- opcode: QUERY, rcode: NOERROR, id: 58233
  382. ;; flags: qr rd ra ; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
  383. ;; QUESTION SECTION:
  384. ;; mocksrvr.example.com.\tIN\tA
  385. ;; ANSWER SECTION:
  386. {}
  387. ;; AUTHORITY SECTION:
  388. ;; ADDITIONAL SECTION:
  389. ;; Query time: 37 msec
  390. ;; SERVER: 10.100.150.129
  391. ;; WHEN: Tue Apr 4 19:03:51 2017
  392. ;; MSG SIZE rcvd: 50
  393. """
  394. )
  395. # Not even a different retcode!?
  396. wrong_type = {
  397. "stdout": RES_TMPL.format("mocksrvr.example.com.\t4404\tIN\tA\t10.1.1.1\n")
  398. }
  399. wrongs = [
  400. {
  401. "retcode": 1,
  402. "stderr": "Error: error sending query: No (valid) nameservers defined in the resolver",
  403. }
  404. ]
  405. # example returns for drill {rtype} {name}
  406. rights = {
  407. "A": [
  408. "mocksrvr.example.com.\t4404\tIN\tA\t10.1.1.1\n",
  409. "web.example.com.\t4404\tIN\tA\t10.1.1.1\n"
  410. "web.example.com.\t4404\tIN\tA\t10.2.2.2\n"
  411. "web.example.com.\t4404\tIN\tA\t10.3.3.3",
  412. ],
  413. "AAAA": [
  414. "mocksrvr.example.com.\t4404\tIN\tAAAA\t2a00:a00:b01:c02:d03:e04:f05:111",
  415. "mocksrvr.example.com.\t4404\tIN\tCNAME\tweb.example.com.\n"
  416. "web.example.com.\t4404\tIN\tAAAA\t2a00:a00:b01:c02:d03:e04:f05:111\n"
  417. "web.example.com.\t4404\tIN\tAAAA\t2a00:a00:b01:c02:d03:e04:f05:222\n"
  418. "web.example.com.\t4404\tIN\tAAAA\t2a00:a00:b01:c02:d03:e04:f05:333",
  419. ],
  420. "CAA": [
  421. 'example.com.\t1144\tIN\tCAA\t0 issue "exampleca.com"\n'
  422. 'example.com.\t1144\tIN\tCAA\t0 iodef "mailto:sslabuse@example.com"'
  423. ],
  424. "CNAME": ["mocksrvr.example.com.\t4404\tIN\tCNAME\tweb.example.com."],
  425. "MX": [
  426. "example.com.\t4404\tIN\tMX\t10 mx1.example.com.",
  427. "example.com.\t4404\tIN\tMX\t10 mx1.example.com.\n"
  428. "example.com.\t4404\tIN\tMX\t20 mx2.example.eu.\n"
  429. "example.com.\t4404\tIN\tMX\t30 mx3.example.nl.",
  430. ],
  431. "SSHFP": [
  432. "mocksrvr.example.com.\t3339\tIN\tSSHFP\t1 1 0aabda8af5418108e8a5d3f90f207226b2c89fbe\n"
  433. "mocksrvr.example.com.\t3339\tIN\tSSHFP\t1 2 500ca871d8e255e01f1261a2370c4e5406b8712f19916d3ab9f86344a67e5597\n"
  434. "mocksrvr.example.com.\t3339\tIN\tSSHFP\t3 1 a3b605ce6f044617c6077c46a7cd5d17a767f0d5\n"
  435. "mocksrvr.example.com.\t3339\tIN\tSSHFP\t4 2 0360d0a5a2fa550f972259e7374533add7ac8e5f303322a5b8e208bbc859ab1b"
  436. ],
  437. "TXT": [
  438. 'example.com.\t4404\tIN\tTXT\t"v=spf1 a include:_spf4.example.com include:mail.example.eu ip4:10.0.0.0/8 ip6:2a00:a00:b01::/48 ~all"'
  439. ],
  440. }
  441. secure = {
  442. "A": [
  443. "mocksrvr.example.com.\t4404\tIN\tA\t10.1.1.1\n"
  444. "mocksrvr.example.com.\t4404\tIN\tRRSIG\tA 8 3 7200 20170420000000 20170330000000 1629 example.com. Hv4p37EF55LKBxUNYpnhWiEYqfmMct0z0WgDJyG5reqYfl+z4HX/kaoi Wr2iCYuYeB4Le7BgnMSb77UGHPWE7lCQ8z5gkgJ9rCDrooJzSTVdnHfw 1JQ7txRSp8Rj2GLf/L3Ytuo6nNZTV7bWUkfhOs61DAcOPHYZiX8rVhIh UAE=",
  445. "web.example.com.\t4404\tIN\tA\t10.1.1.1\n"
  446. "web.example.com.\t4404\tIN\tA\t10.2.2.2\n"
  447. "web.example.com.\t4404\tIN\tA\t10.3.3.3\n"
  448. "web.example.com.\t4404\tIN\tRRSIG\tA 8 3 7200 20170420000000 20170330000000 1629 example.com. Hv4p37EF55LKBxUNYpnhWiEYqfmMct0z0WgDJyG5reqYfl+z4HX/kaoi Wr2iCYuYeB4Le7BgnMSb77UGHPWE7lCQ8z5gkgJ9rCDrooJzSTVdnHfw 1JQ7txRSp8Rj2GLf/L3Ytuo6nNZTV7bWUkfhOs61DAcOPHYZiX8rVhIh UAE=",
  449. ]
  450. }
  451. for rec_d in rights, secure:
  452. for rec_t, tests in rec_d.items():
  453. for idx, test in enumerate(tests):
  454. rec_d[rec_t][idx] = RES_TMPL.format(test)
  455. self._test_cmd_lookup(
  456. _lookup_drill,
  457. wrong_type=wrong_type,
  458. wrong=wrongs,
  459. right=rights,
  460. secure=secure,
  461. )
  462. def test_gai(self):
  463. # wrong type
  464. self.assertRaises(ValueError, _lookup_gai, "mockq", "WRONG")
  465. # wrong
  466. with patch.object(
  467. socket, "getaddrinfo", MagicMock(side_effect=socket.gaierror)
  468. ):
  469. for rec_t in ("A", "AAAA"):
  470. self.assertEqual(False, _lookup_gai("mockq", rec_t))
  471. # example returns from getaddrinfo
  472. right = {
  473. "A": [
  474. [(2, 3, 3, "", ("10.1.1.1", 0))],
  475. [
  476. (2, 3, 3, "", ("10.1.1.1", 0)),
  477. (2, 3, 3, "", ("10.2.2.2", 0)),
  478. (2, 3, 3, "", ("10.3.3.3", 0)),
  479. ],
  480. ],
  481. "AAAA": [
  482. [(10, 3, 3, "", ("2a00:a00:b01:c02:d03:e04:f05:111", 0, 0, 0))],
  483. [
  484. (10, 3, 3, "", ("2a00:a00:b01:c02:d03:e04:f05:111", 0, 0, 0)),
  485. (10, 3, 3, "", ("2a00:a00:b01:c02:d03:e04:f05:222", 0, 0, 0)),
  486. (10, 3, 3, "", ("2a00:a00:b01:c02:d03:e04:f05:333", 0, 0, 0)),
  487. ],
  488. ],
  489. }
  490. for rec_t, tests in right.items():
  491. with patch.object(socket, "getaddrinfo", MagicMock(side_effect=tests)):
  492. for test_res in self.RESULTS[rec_t]:
  493. self.assertEqual(
  494. _lookup_gai("mockq", rec_t),
  495. test_res,
  496. msg="Error parsing {0} returns".format(rec_t),
  497. )
  498. def test_host(self):
  499. wrong_type = {"retcode": 9, "stderr": "host: invalid type: WRONG"}
  500. wrongs = [
  501. {
  502. "retcode": 9,
  503. "stderr": ";; connection timed out; no servers could be reached",
  504. }
  505. ]
  506. empty = {"stdout": "www.example.com has no MX record"}
  507. # example returns for host -t {rdtype} {name}
  508. rights = {
  509. "A": [
  510. "mocksrvr.example.com has address 10.1.1.1",
  511. "web.example.com has address 10.1.1.1\n"
  512. "web.example.com has address 10.2.2.2\n"
  513. "web.example.com has address 10.3.3.3",
  514. ],
  515. "AAAA": [
  516. "mocksrvr.example.com has IPv6 address 2a00:a00:b01:c02:d03:e04:f05:111",
  517. "mocksrvr.example.com is an alias for web.example.com.\n"
  518. "web.example.com has IPv6 address 2a00:a00:b01:c02:d03:e04:f05:111\n"
  519. "web.example.com has IPv6 address 2a00:a00:b01:c02:d03:e04:f05:222\n"
  520. "web.example.com has IPv6 address 2a00:a00:b01:c02:d03:e04:f05:333",
  521. ],
  522. "CAA": [
  523. 'example.com has CAA record 0 issue "exampleca.com"\n'
  524. 'example.com has CAA record 0 iodef "mailto:sslabuse@example.com"'
  525. ],
  526. "CNAME": ["mocksrvr.example.com is an alias for web.example.com."],
  527. "MX": [
  528. "example.com mail is handled by 10 mx1.example.com.",
  529. "example.com mail is handled by 10 mx1.example.com.\n"
  530. "example.com mail is handled by 20 mx2.example.eu.\n"
  531. "example.com mail is handled by 30 mx3.example.nl.",
  532. ],
  533. "SSHFP": [
  534. "mocksrvr.example.com has SSHFP record 1 1 0AABDA8AF5418108E8A5D3F90F207226B2C89FBE\n"
  535. "mocksrvr.example.com has SSHFP record 1 2 500CA871D8E255E01F1261A2370C4E5406B8712F19916D3AB9F86344 A67E5597\n"
  536. "mocksrvr.example.com has SSHFP record 3 1 A3B605CE6F044617C6077C46A7CD5D17A767F0D5\n"
  537. "mocksrvr.example.com has SSHFP record 4 2 0360D0A5A2FA550F972259E7374533ADD7AC8E5F303322A5B8E208BB C859AB1B"
  538. ],
  539. "TXT": [
  540. 'example.com descriptive text "v=spf1 a include:_spf4.example.com include:mail.example.eu ip4:10.0.0.0/8 ip6:2a00:a00:b01::/48 ~all"'
  541. ],
  542. }
  543. self._test_cmd_lookup(
  544. _lookup_host, wrong_type=wrong_type, wrong=wrongs, right=rights, empty=empty
  545. )
  546. def test_nslookup(self):
  547. # all nslookup returns look like this
  548. RES_TMPL = textwrap.dedent(
  549. """\
  550. Server:\t\t10.11.12.13
  551. Address:\t10.11.12.13#53
  552. Non-authoritative answer:
  553. {}
  554. Authoritative answers can be found from:
  555. """
  556. )
  557. wrong_type = {
  558. "stdout": "unknown query type: WRONG"
  559. + RES_TMPL.format("Name:\tmocksrvr.example.com\nAddress: 10.1.1.1")
  560. }
  561. wrongs = [
  562. {
  563. "retcode": 1,
  564. "stdout": ";; connection timed out; no servers could be reached",
  565. }
  566. ]
  567. empty = {"stdout": RES_TMPL.format("*** Can't find www.google.com: No answer")}
  568. # Example returns of nslookup -query={rdype} {name}
  569. rights = {
  570. "A": [
  571. "Name:\tmocksrvr.example.com\nAddress: 10.1.1.1",
  572. "Name:\tmocksrvr.example.com\nAddress: 10.1.1.1\n"
  573. "Name:\tweb.example.com\nAddress: 10.2.2.2\n"
  574. "Name:\tweb.example.com\nAddress: 10.3.3.3",
  575. ],
  576. "AAAA": [
  577. "mocksrvr.example.com\thas AAAA address 2a00:a00:b01:c02:d03:e04:f05:111",
  578. "mocksrvr.example.com\tcanonical name = web.example.com.\n"
  579. "web.example.com\thas AAAA address 2a00:a00:b01:c02:d03:e04:f05:111\n"
  580. "web.example.com\thas AAAA address 2a00:a00:b01:c02:d03:e04:f05:222\n"
  581. "web.example.com\thas AAAA address 2a00:a00:b01:c02:d03:e04:f05:333",
  582. ],
  583. "CAA": [
  584. 'example.com\trdata_257 = 0 issue "exampleca.com"\n'
  585. 'example.com\trdata_257 = 0 iodef "mailto:sslabuse@example.com"'
  586. ],
  587. "CNAME": ["mocksrvr.example.com\tcanonical name = web.example.com."],
  588. "MX": [
  589. "example.com\tmail exchanger = 10 mx1.example.com.",
  590. "example.com\tmail exchanger = 10 mx1.example.com.\n"
  591. "example.com\tmail exchanger = 20 mx2.example.eu.\n"
  592. "example.com\tmail exchanger = 30 mx3.example.nl.",
  593. ],
  594. "SSHFP": [
  595. "mocksrvr.example.com\trdata_44 = 1 1 0AABDA8AF5418108E8A5D3F90F207226B2C89FBE\n"
  596. "mocksrvr.example.com\trdata_44 = 1 2 500CA871D8E255E01F1261A2370C4E5406B8712F19916D3AB9F86344 A67E5597\n"
  597. "mocksrvr.example.com\trdata_44 = 3 1 A3B605CE6F044617C6077C46A7CD5D17A767F0D5\n"
  598. "mocksrvr.example.com\trdata_44 = 4 2 0360D0A5A2FA550F972259E7374533ADD7AC8E5F303322A5B8E208BB C859AB1B"
  599. ],
  600. "TXT": [
  601. 'example.com\ttext = "v=spf1 a include:_spf4.example.com include:mail.example.eu ip4:10.0.0.0/8 ip6:2a00:a00:b01::/48 ~all"'
  602. ],
  603. }
  604. for rec_t, tests in rights.items():
  605. for idx, test in enumerate(tests):
  606. rights[rec_t][idx] = RES_TMPL.format(test)
  607. self._test_cmd_lookup(
  608. _lookup_nslookup,
  609. wrong_type=wrong_type,
  610. wrong=wrongs,
  611. right=rights,
  612. empty=empty,
  613. )