dhcp-hook.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615
  1. #!/usr/bin/env python3
  2. #
  3. # Copyright (c) 2017-2019 Joe Clarke <jclarke@cisco.com>
  4. # All rights reserved.
  5. #
  6. # Redistribution and use in source and binary forms, with or without
  7. # modification, are permitted provided that the following conditions
  8. # are met:
  9. # 1. Redistributions of source code must retain the above copyright
  10. # notice, this list of conditions and the following disclaimer.
  11. # 2. Redistributions in binary form must reproduce the above copyright
  12. # notice, this list of conditions and the following disclaimer in the
  13. # documentation and/or other materials provided with the distribution.
  14. #
  15. # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
  16. # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  17. # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  18. # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
  19. # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  20. # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  21. # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  22. # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  23. # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  24. # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  25. # SUCH DAMAGE.
  26. import sys
  27. import json
  28. from sparker import Sparker
  29. import re
  30. import requests
  31. from requests.packages.urllib3.exceptions import InsecureRequestWarning
  32. requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
  33. import time
  34. import traceback
  35. import socket
  36. import logging
  37. import CLEUCreds
  38. from cleu.config import Config as C
  39. AT_MACADDR = 9
  40. CNR_HEADERS = {
  41. 'Accept': 'application/json',
  42. 'Authorization': CLEUCreds.JCLARKE_BASIC
  43. }
  44. DEFAULT_INT_TYPE = 'GigabitEthernet'
  45. ALLOWED_TO_DELETE = ['jclarke@cisco.com',
  46. 'ksekula@cisco.com', 'anjesani@cisco.com']
  47. def is_ascii(s):
  48. return all(ord(c) < 128 for c in s)
  49. def get_from_cmx(**kwargs):
  50. marker = 'green'
  51. if 'user' in kwargs and kwargs['user'] == 'gru':
  52. marker = 'gru'
  53. if 'ip' in kwargs:
  54. url = '{}?ip={}&marker={}&size=1440'.format(
  55. CMX_GW, kwargs['ip'], marker)
  56. elif 'mac' in kwargs:
  57. url = '{}?mac={}&marker={}&size=1440'.format(
  58. C.CMX_GW, kwargs['mac'], marker)
  59. else:
  60. return None
  61. headers = {
  62. 'Accept': 'image/jpeg, application/json'
  63. }
  64. try:
  65. response = requests.request(
  66. 'GET', url, headers=headers, stream=True)
  67. response.raise_for_status()
  68. except Exception:
  69. logging.error('Encountered error getting data from cmx: {}'.format(
  70. traceback.format_exc()))
  71. return None
  72. if response.headers.get('content-type') == 'application/json':
  73. return None
  74. return response.raw.data
  75. def get_from_pi(**kwargs):
  76. if 'user' in kwargs:
  77. url = 'https://{}/webacs/api/v2/data/ClientDetails.json?.full=true&userName="{}"&status=ASSOCIATED'.format(
  78. C.PI, kwargs['user'])
  79. elif 'mac' in kwargs:
  80. url = 'https://{}/webacs/api/v2/data/ClientDetails.json?.full=true&macAddress="{}"&status=ASSOCIATED'.format(
  81. C.PI, kwargs['mac'])
  82. elif 'ip' in kwargs:
  83. url = 'https://{}/webacs/api/v2/data/ClientDetails.json?.full=true&ipAddress="{}"&status=ASSOCIATED'.format(
  84. C.PI, kwargs['ip'])
  85. else:
  86. return None
  87. headers = {
  88. 'Connection': 'close'
  89. }
  90. done = False
  91. first = 0
  92. code = 401
  93. i = 0
  94. while code != 200 and i < 10:
  95. response = requests.request(
  96. "GET", url, auth=(CLEUCreds.PI_USER, CLEUCreds.PI_PASS), headers=headers, verify=False)
  97. code = response.status_code
  98. if code != 200:
  99. i += 1
  100. time.sleep(3)
  101. if code == 200:
  102. j = json.loads(response.text)
  103. if j['queryResponse']['@count'] == 0:
  104. return None
  105. return j['queryResponse']['entity']
  106. else:
  107. logging.error('Failed to get a response from PI for {}: {}'.format(
  108. kwargs['user'], response.text))
  109. return None
  110. def parse_relay_info(outd):
  111. global DEFAULT_INT_TYPE
  112. res = {}
  113. if 'relayAgentCircuitId' in outd:
  114. octets = outd['relayAgentCircuitId'].split(':')
  115. res['vlan'] = int(''.join(octets[2:4]), 16)
  116. first_part = int(octets[4], 16)
  117. port = str(first_part)
  118. if first_part != 0:
  119. port = str(first_part) + '/0'
  120. res['port'] = DEFAULT_INT_TYPE + port + '/' + str(int(octets[5], 16))
  121. else:
  122. res['vlan'] = 'N/A'
  123. res['port'] = 'N/A'
  124. if 'relayAgentRemoteId' in outd:
  125. octets = outd['relayAgentRemoteId'].split(':')
  126. res['switch'] = ''.join(octets[2:]).decode('hex')
  127. if not is_ascii(res['switch']):
  128. res['switch'] = 'N/A'
  129. else:
  130. res['switch'] = 'N/A'
  131. return res
  132. def check_for_reservation(ip):
  133. global CNR_HEADERS
  134. res = {}
  135. url = '{}/Reservation/{}'.format(C.DHCP_BASE, ip)
  136. try:
  137. response = requests.request(
  138. 'GET', url, headers=CNR_HEADERS, verify=False)
  139. response.raise_for_status()
  140. except Exception as e:
  141. logging.warning(
  142. 'Did not get a good response from CNR for reservation {}: {}'.format(ip, e))
  143. return None
  144. rsvp = response.json()
  145. res['mac'] = ':'.join(rsvp['lookupKey'].split(':')[-6:])
  146. res['scope'] = rsvp['scope']
  147. return res
  148. def check_for_reservation_by_mac(mac):
  149. global CNR_HEADERS
  150. res = {}
  151. url = '{}/Reservation'.format(C.DHCP_BASE)
  152. try:
  153. response = requests.request(
  154. 'GET', url, headers=CNR_HEADERS, params={'lookupKey': mac}, verify=False)
  155. response.raise_for_status()
  156. except Exception as e:
  157. logging.warning(
  158. 'Did not get a good response from CNR for reservation {}: {}'.format(ip, e))
  159. return None
  160. j = response.json()
  161. if len(j) == 0:
  162. return None
  163. rsvp = j[0]
  164. res['mac'] = ':'.join(rsvp['lookupKey'].split(':')[-6:])
  165. res['scope'] = rsvp['scope']
  166. return res
  167. def create_reservation(ip, mac):
  168. global CNR_HEADERS, AT_MACADDR
  169. url = '{}/Reservation'.format(C.DHCP_BASE)
  170. payload = {
  171. 'ipaddr': ip,
  172. 'lookupKey': '01:06:' + mac,
  173. 'lookupKeyType': AT_MACADDR
  174. }
  175. response = requests.request(
  176. 'POST', url, headers=CNR_HEADERS, json=payload, verify=False)
  177. response.raise_for_status()
  178. def delete_reservation(ip):
  179. global DHCP_BASE, CNR_HEADERS
  180. url = '{}/Reservation/{}'.format(C.DHCP_BASE, ip)
  181. response = requests.request(
  182. 'DELETE', url, headers=CNR_HEADERS, verify=False)
  183. response.raise_for_status()
  184. def check_for_lease(ip):
  185. global CNR_HEADERS
  186. res = {}
  187. url = '{}/Lease/{}'.format(C.DHCP_BASE, ip)
  188. try:
  189. response = requests.request(
  190. 'GET', url, headers=CNR_HEADERS, verify=False)
  191. response.raise_for_status()
  192. except Exception as e:
  193. logging.warning(
  194. 'Did not get a good response from CNR for IP {}: {}'.format(ip, e))
  195. return None
  196. lease = response.json()
  197. if not 'clientMacAddr' in lease:
  198. return None
  199. relay = parse_relay_info(lease)
  200. if 'clientHostName' in lease:
  201. res['name'] = lease['clientHostName']
  202. elif 'client-dns-name' in lease:
  203. res['name'] = lease['clientDnsName']
  204. else:
  205. res['name'] = 'UNKNOWN'
  206. res['mac'] = lease['clientMacAddr'][lease['clientMacAddr'].rfind(',') + 1:]
  207. res['scope'] = lease['scopeName']
  208. res['state'] = lease['state']
  209. res['relay-info'] = relay
  210. return res
  211. def check_for_mac(mac):
  212. global CNR_HEADERS
  213. res = {}
  214. url = '{}/Lease'.format(C.DHCP_BASE)
  215. try:
  216. response = requests.request(
  217. 'GET', url, headers=CNR_HEADERS, verify=False, params={'clientMacAddr': mac})
  218. response.raise_for_status()
  219. except Exception as e:
  220. logging.warning(
  221. 'Did not get a good response from CNR for MAC {}: {}'.format(mac, e))
  222. return None
  223. j = response.json()
  224. if len(j) == 0:
  225. return None
  226. lease = j[0]
  227. relay = parse_relay_info(lease)
  228. if 'address' not in lease:
  229. return None
  230. res['ip'] = lease['address']
  231. if 'clientHostName' in lease:
  232. res['name'] = lease['clientHostName']
  233. elif 'clientDnsName' in lease:
  234. res['name'] = lease['clientDnsName']
  235. else:
  236. res['name'] = 'UNKNOWN'
  237. res['scope'] = lease['scopeName']
  238. res['state'] = lease['state']
  239. res['relay-info'] = relay
  240. return res
  241. def print_pi(spark, what, ents, msg):
  242. for ent in ents:
  243. res = ent['clientDetailsDTO']
  244. apdet = ''
  245. condet = ''
  246. vendet = ''
  247. if 'apName' in res:
  248. apdet = '**{}** via '.format(res['apName'])
  249. if 'connectionType' in res:
  250. condet = 'is a **{}** client'.format(res['connectionType'])
  251. if 'vendor' in res:
  252. vendet = 'of vendor type **{}**'.format(res['vendor'])
  253. spark.post_to_spark(C.SPARK_TEAM, SPARK_ROOM, '{} {} {} {}, connected to {}**{}** on interface **{}** with MAC address **{}** and IP address **{}** in **VLAN {}** located in **{}**.'.format(
  254. msg, what, condet, vendet, apdet, res['deviceName'], res['clientInterface'], res['macAddress'], res['ipAddress']['address'], res['vlan'], res['location']))
  255. spark = Sparker(token=CLEUCreds.SPARK_TOKEN, logit=True)
  256. SPARK_ROOM = 'DHCP Queries'
  257. if __name__ == '__main__':
  258. print('Content-type: application/json\r\n\r\n')
  259. output = sys.stdin.read()
  260. j = json.loads(output)
  261. logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s : %(message)s',
  262. filename='/var/log/dhcp-hook.log', level=logging.DEBUG)
  263. logging.debug(json.dumps(j, indent=4))
  264. message_from = j['data']['personEmail']
  265. if message_from == 'livenocbot@sparkbot.io':
  266. logging.debug('Person email is our bot')
  267. print('{"result":"success"}')
  268. sys.exit(0)
  269. tid = spark.get_team_id(C.SPARK_TEAM)
  270. if tid is None:
  271. logging.error('Failed to get Spark Team ID')
  272. print('{"result":"fail"}')
  273. sys.exit(0)
  274. rid = spark.get_room_id(tid, SPARK_ROOM)
  275. if rid is None:
  276. logging.error('Failed to get Spark Room ID')
  277. print('{"result":"fail"}')
  278. sys.exit(0)
  279. if rid != j['data']['roomId']:
  280. logging.error('Spark Room ID is not the same as in the message ({} vs. {})'.format(
  281. rid, j['data']['roomId']))
  282. print('{"result":"fail"}')
  283. sys.exit(0)
  284. mid = j['data']['id']
  285. msg = spark.get_message(mid)
  286. if msg is None:
  287. logging.error('Did not get a message')
  288. print('{"result":"error"}')
  289. sys.exit(0)
  290. txt = msg['text']
  291. found_hit = False
  292. if re.search(r'\bhelp\b', txt, re.I):
  293. spark.post_to_spark(C.SPARK_TEAM, SPARK_ROOM, 'To lookup a reservation, type `@Live NOC Bot reservation IP`. To lookup a lease by MAC, ask about the MAC. To lookup a lease by IP ask about the IP. To look up a user, ask about "user USERNAME".<br>Some question might be, `@Live NOC Bot who has lease 1.2.3.4` or `@Live NOC Bot what lease does 00:11:22:33:44:55 have` or `@Live NOC Bot tell me about user jsmith`.')
  294. found_hit = True
  295. try:
  296. m = re.search(
  297. r'user(name)?\s+\b(?P<uname>[A-Za-z][\w\-\.\d]+)([\s\?\.]|$)', txt, re.I)
  298. if not found_hit and not m:
  299. m = re.search(
  300. r'(who|where)\s+is\s+\b(?P<uname>[A-Za-z][\w\-\.\d]+)([\s\?\.]|$)', txt, re.I)
  301. if not found_hit and m:
  302. found_hit = True
  303. uname = m.group('uname')
  304. usecret = ''
  305. if re.search(r'gru', m.group('uname'), re.I):
  306. uname = 'rkamerma'
  307. usecret = 'gru'
  308. res = get_from_pi(user=uname)
  309. if res is None:
  310. res = get_from_pi(user=uname + '@{}'.format(AD_DOMAIN))
  311. if res is not None:
  312. print_pi(spark, m.group('uname'), res, '')
  313. for ent in res:
  314. cmxres = get_from_cmx(
  315. mac=ent['clientDetailsDTO']['macAddress'].lower(), user=usecret)
  316. if cmxres is not None:
  317. spark.post_to_spark_with_attach(
  318. C.SPARK_TEAM, SPARK_ROOM, '{}\'s location from CMX'.format(m.group('uname')), cmxres, '{}_location.jpg'.format(m.group('uname')), 'image/jpeg')
  319. else:
  320. spark.post_to_spark(C.SPARK_TEAM, SPARK_ROOM,
  321. 'Sorry, I can\'t find {}.'.format(m.group('uname')))
  322. m = re.search(
  323. r'(remove|delete)\s+reservation.*?([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)', txt, re.I)
  324. if not m:
  325. m = re.search(
  326. r'(unreserve).*?([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)', txt, re.I)
  327. if not found_hit and m:
  328. found_hit = True
  329. if message_from not in ALLOWED_TO_DELETE:
  330. spark.post_to_spark(
  331. C.SPARK_TEAM, SPARK_ROOM, 'I\'m sorry, {}. I can\'t do that for you.'.format(message_from))
  332. else:
  333. res = check_for_reservation(m.group(2))
  334. if res is None:
  335. spark.post_to_spark(
  336. C.SPARK_TEAM, SPARK_ROOM, 'I didn\'t find a reservation for {}.'.format(m.group(2)))
  337. else:
  338. try:
  339. delete_reservation(m.group(2))
  340. spark.post_to_spark(
  341. C.SPARK_TEAM, SPARK_ROOM, 'Reservation for {} deleted successfully.'.format(m.group(2)))
  342. except Exception as e:
  343. spark.post_to_spark(
  344. C.SPARK_TEAM, SPARK_ROOM, 'Failed to delete reservation for {}: {}'.format(m.group(2)))
  345. m = re.search(
  346. r'(make|create|add)\s+reservation.*?([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)', txt, re.I)
  347. if not found_hit and m:
  348. found_hit = True
  349. res = check_for_reservation(m.group(2))
  350. if res is not None:
  351. spark.post_to_spark(
  352. C.SPARK_TEAM, SPARK_ROOM, '_{}_ is already reserved by a client with MAC **{}**'.format(m.group(2), res['mac']))
  353. else:
  354. lres = check_for_lease(m.group(2))
  355. if lres is None:
  356. spark.post_to_spark(
  357. C.SPARK_TEAM, SPARK_ROOM, 'Did not find an existing lease for {}'.format(m.group(2)))
  358. else:
  359. try:
  360. rres = check_for_reservation_by_mac(lres['mac'])
  361. if rres is not None:
  362. spark.post_to_spark(C.SPARK_TEAM, SPARK_ROOM, '_{}_ already has a reservation for {} in scope {}.'.format(
  363. lres['mac'], rres['ip'], lres['scope']))
  364. else:
  365. create_reservation(m.group(2), lres['mac'])
  366. spark.post_to_spark(
  367. C.SPARK_TEAM, SPARK_ROOM, 'Successfully added reservation for {}.'.format(m.group(2)))
  368. except Exception as e:
  369. spark.post_to_spark(
  370. C.SPARK_TEAM, SPARK_ROOM, 'Failed to add reservation for {}: {}'.format(m.group(2), e))
  371. m = re.search(
  372. r'reservation.*?([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)', txt, re.I)
  373. if not found_hit and m:
  374. found_hit = True
  375. res = check_for_reservation(m.group(1))
  376. if res is not None:
  377. spark.post_to_spark(
  378. C.SPARK_TEAM, SPARK_ROOM, '_{}_ is reserved by a client with MAC **{}** in scope **{}**.'.format(m.group(1), res['mac'], res['scope']))
  379. else:
  380. spark.post_to_spark(
  381. C.SPARK_TEAM, SPARK_ROOM, 'I did not find a reservation for {}.'.format(m.group(1)))
  382. m = re.findall(r'\b([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)\b', txt)
  383. if not found_hit and len(m) > 0:
  384. found_hit = True
  385. for hit in m:
  386. res = check_for_lease(hit)
  387. pires = get_from_pi(ip=hit)
  388. cmxres = None
  389. if res is not None:
  390. cmxres = get_from_cmx(
  391. mac=re.sub(r'(\d+,)+', '', res['mac']))
  392. elif pires is not None:
  393. cmxres = get_from_cmx(
  394. mac=pires[0]['clientDetailsDTO']['macAddress'])
  395. if res is not None:
  396. if re.search(r'available', res['state']):
  397. port_info = res['relay-info']['port']
  398. if port_info != 'N/A':
  399. port_info = '<a href="{}switch_name={}&port_name={}">**{}**</a>'.format(
  400. TOOL_BASE, res['relay-info']['switch'], res['relay-info']['port'], res['relay-info']['port'])
  401. spark.post_to_spark(C.SPARK_TEAM, SPARK_ROOM, '_{}_ is no longer leased, but _WAS_ leased by a client with name **{}** and MAC **{}** in scope **{}** (state: **{}**) and was connected to switch **{}** on port {} in VLAN **{}**.'.format(
  402. hit, res['name'], res['mac'], res['scope'], res['state'], res['relay-info']['switch'], port_info, res['relay-info']['vlan']))
  403. else:
  404. port_info = res['relay-info']['port']
  405. if port_info != 'N/A':
  406. port_info = '<a href="{}switch_name={}&port_name={}">**{}**</a>'.format(
  407. TOOL_BASE, res['relay-info']['switch'], res['relay-info']['port'], res['relay-info']['port'])
  408. spark.post_to_spark(C.SPARK_TEAM, SPARK_ROOM, '_{}_ is leased by a client with name **{}** and MAC **{}** in scope **{}** (state: **{}**) and is connected to switch **{}** on port {} in VLAN **{}**.'.format(
  409. hit, res['name'], res['mac'], res['scope'], res['state'], res['relay-info']['switch'], port_info, res['relay-info']['vlan']))
  410. if pires is not None:
  411. print_pi(spark, hit, pires,
  412. 'I also found this from Prime Infra:')
  413. if cmxres is not None:
  414. spark.post_to_spark_with_attach(
  415. C.SPARK_TEAM, SPARK_ROOM, 'Location from CMX', cmxres, '{}_location.jpg'.format(hit), 'image/jpeg')
  416. else:
  417. spark.post_to_spark(C.SPARK_TEAM, SPARK_ROOM,
  418. 'I did not find a lease for {}.'.format(hit))
  419. if pires is not None:
  420. print_pi(spark, hit, pires,
  421. 'But I did get this from Prime Infra:')
  422. if cmxres is not None:
  423. spark.post_to_spark_with_attach(
  424. C.SPARK_TEAM, SPARK_ROOM, 'Location from CMX', cmxres, '{}_location.jpg'.format(hit), 'image/jpeg')
  425. m = re.findall('\\b(?:(?:[0-9A-Fa-f]{1,4}:){6}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|::(?:[0-9A-Fa-f]{1,4}:){5}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(?:[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){4}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){3}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(?:(?:[0-9A-Fa-f]{1,4}:){,2}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){2}(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(?:(?:[0-9A-Fa-f]{1,4}:){,3}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}:(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(?:(?:[0-9A-Fa-f]{1,4}:){,4}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))|(?:(?:[0-9A-Fa-f]{1,4}:){,5}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}|(?:(?:[0-9A-Fa-f]{1,4}:){,6}[0-9A-Fa-f]{1,4})?::)\\b', txt)
  426. if not found_hit and len(m) > 0:
  427. found_hit = True
  428. for hit in m:
  429. pires = get_from_pi(ip=hit)
  430. if pires is not None:
  431. print_pi(spark, hit, pires, '')
  432. cmxres = get_from_cmx(
  433. mac=pires[0]['clientDetailsDTO']['macAddress'])
  434. if cmxres is not None:
  435. spark.post_to_spark_with_attach(
  436. C.SPARK_TEAM, SPARK_ROOM, 'Location from CMX', cmxres, '{}_location.jpg'.format(hit), 'image/jpeg')
  437. else:
  438. spark.post_to_spark(
  439. C.SPARK_TEAM, SPARK_ROOM, 'I did not find anything about {} in Prime Infra.'.format(hit))
  440. m = re.findall(r'\b(([a-fA-F0-9]{1,2}:[a-fA-F0-9]{1,2}:[a-fA-F0-9]{1,2}:[a-fA-F0-9]{1,2}:[a-fA-F0-9]{1,2}:[a-fA-F0-9]{1,2})|([a-fA-F0-9]{4}\.[a-fA-F0-9]{4}\.[a-fA-F0-9]{4})|([a-fA-F0-9]{1,2}-[a-fA-F0-9]{1,2}-[a-fA-F0-9]{1,2}-[a-fA-F0-9]{1,2}-[a-fA-F0-9]{1,2}-[a-fA-F0-9]{1,2}))\b', txt)
  441. if not found_hit and len(m) > 0:
  442. found_hit = True
  443. for hit in m:
  444. res = check_for_mac(hit[0])
  445. pires = get_from_pi(mac=hit[0])
  446. cmxres = get_from_cmx(mac=re.sub(r'(\d+,)+', '', hit[0]))
  447. if res is not None:
  448. if re.search(r'available', res['state']):
  449. spark.post_to_spark(C.SPARK_TEAM, SPARK_ROOM, 'Client with MAC _{}_ no longer has a lease, but _USED TO HAVE_ lease **{}** (hostname: **{}**) in scope **{}** (state: **{}**) and was connected to switch **{}** on port **{}** in VLAN **{}**.'.format(
  450. hit[0], res['ip'], res['name'], res['scope'], res['state'], res['relay-info']['switch'], res['relay-info']['port'], res['relay-info']['vlan']))
  451. else:
  452. spark.post_to_spark(C.SPARK_TEAM, SPARK_ROOM, 'Client with MAC _{}_ has lease **{}** (hostname: **{}**) in scope **{}** (state: **{}**) and is connected to switch **{}** on port **{}** in VLAN **{}**.'.format(
  453. hit[0], res['ip'], res['name'], res['scope'], res['state'], res['relay-info']['switch'], res['relay-info']['port'], res['relay-info']['vlan']))
  454. if pires is not None:
  455. #spark.post_to_spark(C.SPARK_TEAM, SPARK_ROOM, '```\n{}\n```'.format(json.dumps(pires, indent=4)))
  456. print_pi(spark, hit[0], pires,
  457. 'I also found this from Prime Infra:')
  458. if cmxres is not None:
  459. spark.post_to_spark_with_attach(
  460. C.SPARK_TEAM, SPARK_ROOM, 'Location from CMX', cmxres, '{}_location.jpg'.format(hit[0]), 'image/jpeg')
  461. else:
  462. spark.post_to_spark(C.SPARK_TEAM, SPARK_ROOM,
  463. 'I did not find a lease for {}.'.format(hit[0]))
  464. if pires is not None:
  465. print_pi(spark, hit[0], pires,
  466. 'But I did get this from Prime Infra:')
  467. if cmxres is not None:
  468. spark.post_to_spark_with_attach(
  469. C.SPARK_TEAM, SPARK_ROOM, 'Location from CMX', cmxres, '{}_location.jpg'.format(hit[0]), 'image/jpeg')
  470. m = re.search(r'answer', txt, re.I)
  471. if not found_hit and m:
  472. found_hit = True
  473. spark.post_to_spark(C.SPARK_TEAM, SPARK_ROOM, 'The answer is 42.')
  474. m = re.findall(r'([\w\d\-\.]+)', txt)
  475. if not found_hit and len(m) > 0:
  476. found_hit = False
  477. for hit in m:
  478. ip = None
  479. try:
  480. ip = socket.gethostbyname(hit)
  481. except:
  482. pass
  483. if ip:
  484. res = check_for_lease(ip)
  485. pires = get_from_pi(ip=ip)
  486. if res is not None:
  487. if re.search(r'available', res['state']):
  488. found_hit = True
  489. spark.post_to_spark(C.SPARK_TEAM, SPARK_ROOM, 'Client with hostname _{}_ no longer has a lease, but _USED TO HAVE_ lease **{}** (hostname: **{}**) in scope **{}** (state: **{}**) and was connected to switch **{}** on port **{}** in VLAN **{}**.'.format(
  490. hit, ip, res['name'], res['scope'], res['state'], res['relay-info']['switch'], res['relay-info']['port'], res['relay-info']['vlan']))
  491. else:
  492. found_hit = True
  493. spark.post_to_spark(C.SPARK_TEAM, SPARK_ROOM, 'Client with hostname _{}_ has lease **{}** (hostname: **{}**) in scope **{}** (state: **{}**) and is connected to switch **{}** on port **{}** in VLAN **{}**.'.format(
  494. hit, ip, res['name'], res['scope'], res['state'], res['relay-info']['switch'], res['relay-info']['port'], res['relay-info']['vlan']))
  495. if pires is not None:
  496. found_hit = True
  497. #spark.post_to_spark(C.SPARK_TEAM, SPARK_ROOM, '```\n{}\n```'.format(json.dumps(pires, indent=4)))
  498. print_pi(spark, hit, pires,
  499. 'I also found this from Prime Infra:')
  500. cmxres = get_from_cmx(
  501. mac=pires[0]['clientDetailsDTO']['macAddress'])
  502. if cmxres is not None:
  503. spark.post_to_spark_with_attach(
  504. C.SPARK_TEAM, SPARK_ROOM, 'Location from CMX', cmxres, '{}_location.jpg'.format(hit), 'image/jpeg')
  505. else:
  506. found_hit = True
  507. spark.post_to_spark(C.SPARK_TEAM, SPARK_ROOM,
  508. 'I did not find a lease for {}.'.format(hit))
  509. if pires is not None:
  510. print_pi(spark, hit, pires,
  511. 'But I did get this from Prime Infra:')
  512. cmxres = get_from_cmx(
  513. mac=pires[0]['clientDetailsDTO']['macAddress'])
  514. if cmxres is not None:
  515. spark.post_to_spark_with_attach(
  516. C.SPARK_TEAM, SPARK_ROOM, 'Location from CMX', cmxres, '{}_location.jpg'.format(hit), 'image/jpeg')
  517. if not found_hit:
  518. spark.post_to_spark(C.SPARK_TEAM, SPARK_ROOM,
  519. 'Sorry, I didn\'t get that. Please give me a MAC or IP (or "reservation IP" or "user USER") or just ask for "help".')
  520. except Exception as e:
  521. logging.error('Error in obtaining data: {}'.format(
  522. traceback.format_exc()))
  523. spark.post_to_spark(C.SPARK_TEAM, SPARK_ROOM,
  524. 'Whoops, I encountered an error:<br>\n```\n{}\n```'.format(traceback.format_exc()))
  525. print('{"result":"success"}')