dhcp-hook.py 30 KB

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