test_dns.py 26 KB

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