dhcp-hook.py 28 KB

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