PageRenderTime 43ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/subsystem/arnold/arnold.py

https://bitbucket.org/lkarsten/nav-lkarsten
Python | 420 lines | 350 code | 30 blank | 40 comment | 20 complexity | 27e4157428724a707bc44fb19041cfc4 MD5 | raw file
Possible License(s): AGPL-1.0, GPL-2.0, BSD-3-Clause, Apache-2.0
  1. #!/usr/bin/env python
  2. #
  3. # Copyright 2008 Norwegian University of Science and Technology
  4. #
  5. # This file is part of Network Administration Visualized (NAV)
  6. #
  7. # NAV is free software; you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License as published by
  9. # the Free Software Foundation; either version 2 of the License, or
  10. # (at your option) any later version.
  11. #
  12. # NAV is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU General Public License
  18. # along with NAV; if not, write to the Free Software
  19. # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  20. #
  21. # Authors: John-Magne Bredal <john.m.bredal@ntnu.no>
  22. # Credits
  23. #
  24. __copyright__ = "Copyright 2008 Norwegian University of Science and Technology"
  25. __license__ = "GPL"
  26. __author__ = "John-Magne Bredal (john.m.bredal@ntnu.no)"
  27. from optparse import OptionParser
  28. import ConfigParser
  29. import logging
  30. import os, sys, re
  31. import getpass
  32. import psycopg2.extras
  33. # NAV libraries
  34. import nav.buildconf
  35. import nav.arnold
  36. from nav.db import getConnection
  37. # Paths
  38. configfile = nav.buildconf.sysconfdir + "/arnold/arnold.conf"
  39. logfile = nav.buildconf.localstatedir + "/log/arnold/arnold.log"
  40. """
  41. The arnold-script is mainly made for emergencyuse only. It does not
  42. have all the functionality of the webinterface nor is it very
  43. userfriendly. We strongly recommend using the webinterface for serious
  44. arnolding. It is however good to use when running cron-jobs for
  45. blocking.
  46. """
  47. def main():
  48. # Read config
  49. config = ConfigParser.ConfigParser()
  50. config.read(configfile)
  51. # Define options
  52. usage = "usage: %prog [options] id"
  53. parser = OptionParser(usage)
  54. parser.add_option("-s", dest="state", help="state: enable, disable or \
  55. quarantine")
  56. parser.add_option("-f", "--file", dest="inputfile",
  57. help="File with stuff to disable.")
  58. parser.add_option("--listreasons", action="store_true", dest="listreasons",
  59. help="List reasons for blocking in database.")
  60. parser.add_option("-l", "--listblocked", action="store_true",
  61. dest="listblocked", help="List blocked ports.")
  62. parser.add_option("-v", dest="vlan", help="The vlan to change ports to - \
  63. must be set if state is quarantine")
  64. parser.add_option("-r", dest="reason", help="Reason for this action")
  65. parser.add_option("-c", dest="comment", help="Comment")
  66. parser.add_option("--autoenable", dest="autoenable",
  67. help="Days to autoenable")
  68. parser.add_option("--determined", action="store_true", dest="determined",
  69. help="Flag for determined blocking")
  70. (opts, args) = parser.parse_args()
  71. # Get loglevel from config-file
  72. loglevel = config.get('loglevel','arnold')
  73. if not loglevel.isdigit():
  74. loglevel = logging.getLevelName(loglevel)
  75. try:
  76. loglevel = int(loglevel)
  77. except ValueError:
  78. loglevel = 20 # default to INFO
  79. # Create logger, start logging
  80. filehandler = logging.FileHandler(logfile)
  81. formatter = logging.Formatter('[%(asctime)s] [%(levelname)s] ' \
  82. '[%(name)s] L%(lineno)d %(message)s')
  83. filehandler.setFormatter(formatter)
  84. logger = logging.getLogger('arnold')
  85. logger.addHandler(filehandler)
  86. logger.setLevel(loglevel)
  87. logger.info("Starting arnold")
  88. logger.info("Loglevel = %s" %loglevel)
  89. # If file is given, assume we are disabling everything in the
  90. # file. The file may contain a mixture of ip, mac or swportid's
  91. # (I admit I could have used more functions to clean up the code)
  92. if opts.inputfile:
  93. if not opts.reason:
  94. logger.info("User did not define reason for block")
  95. parser.error("Please specify a reason for the block")
  96. if opts.state == 'quarantine' and not opts.vlan:
  97. logger.info("User did not define vlan to change to")
  98. parser.error("Please specify a vlan to change to")
  99. try:
  100. # open file, get input
  101. f = file (opts.inputfile)
  102. handleFile(f, opts)
  103. except IOError, why:
  104. logger.error(why)
  105. print why
  106. sys.exit(1)
  107. elif opts.listreasons:
  108. try:
  109. reasons = nav.arnold.getReasons()
  110. for r in reasons:
  111. print "%2s: %s - %s" \
  112. %(r['blocked_reasonid'], r['name'], r['comment'])
  113. except nav.arnold.DbError, why:
  114. logger.error(why)
  115. print why
  116. sys.exit(1)
  117. elif opts.listblocked:
  118. conn = getConnection('default', 'arnold')
  119. c = conn.cursor(cursor_factory=psycopg2.extras.DictCursor)
  120. q = """SELECT identityid, mac, ip, netbios, blocked_status AS status
  121. FROM identity
  122. WHERE blocked_status IN ('disabled','quarantined')
  123. ORDER BY lastchanged"""
  124. try:
  125. c.execute(q)
  126. except nav.db.driver.ProgrammingError, why:
  127. logger.error(why)
  128. print why
  129. sys.exit(1)
  130. if c.rowcount > 0:
  131. rows = c.fetchall()
  132. format = "%-4s %-15s %-17s %-16s %s"
  133. print format %('ID','IP','MAC', 'NETBIOS','STATUS')
  134. for row in rows:
  135. print format %(row['identityid'], row['ip'],
  136. row['mac'], row['netbios'], row['status'])
  137. else:
  138. print "No blocked ports in arnold"
  139. elif opts.state:
  140. if len(args) < 1:
  141. logger.info("User did not specify any arguments")
  142. parser.error("I need an ip, mac or databaseid to have " \
  143. "something to do.")
  144. if not opts.state in ['enable','disable','quarantine']:
  145. logger.info("User did not specify correct state: %s" %opts.state)
  146. parser.error("State must be either enable, disable or quarantine")
  147. # Enable or disable interface based on input from user
  148. res = ""
  149. if opts.state == 'enable':
  150. for id in args:
  151. logger.info("Running openPort (%s, %s)" %(id, getpass.getuser()))
  152. # Open port
  153. try:
  154. nav.arnold.openPort(id, getpass.getuser())
  155. except (nav.arnold.NoDatabaseInformationError,
  156. nav.arnold.DbError,
  157. nav.arnold.ChangePortStatusError), why:
  158. print why
  159. logger.error(why)
  160. continue
  161. elif opts.state in ['disable','quarantine']:
  162. if not opts.reason:
  163. logger.info("User did not define reason for block")
  164. parser.error("Please specify a reason for the block")
  165. if opts.state == 'quarantine' and not opts.vlan:
  166. logger.info("User did not define vlan to change to")
  167. parser.error("Please specify a vlan to change to")
  168. # Loop through the id's to block
  169. for id in args:
  170. # Find information about switch and id in database
  171. try:
  172. res = nav.arnold.findIdInformation(id, 3)
  173. except (nav.arnold.NoDatabaseInformationError,
  174. nav.arnold.UnknownTypeError,
  175. nav.arnold.PortNotFoundError), why:
  176. logger.error(why)
  177. print why
  178. continue
  179. swportids = []
  180. counter = 1
  181. format = "%-2s %-19s %-15s %-17s %s (%s:%s)"
  182. print format %('ID','Lastseen','IP','MAC','Switch',
  183. 'module','port')
  184. # Store information about the switchport
  185. swinfo = {}
  186. # Print all ports the id has been active on
  187. for i in res:
  188. try:
  189. swinfo[counter] = nav.arnold.findSwportinfo(i['netboxid'],
  190. i['ifindex'],
  191. i['module'])
  192. except (nav.arnold.NoDatabaseInformationError,
  193. nav.arnold.UnknownTypeError,
  194. nav.arnold.PortNotFoundError), why:
  195. print why
  196. logger.error(why)
  197. continue
  198. swportids.append(swinfo[counter]['swportid'])
  199. print format %(counter, i['endtime'], i['ip'], i['mac'],
  200. i['sysname'], i['module'], i['port'])
  201. counter = counter + 1
  202. # If no port is found in database, report and exit
  203. if len(swportids) < 1:
  204. print "Could not find any port where %s has been active" \
  205. %id
  206. logger.info("Could not find any port where %s has \
  207. been active" %id)
  208. sys.exit()
  209. # If id is not active, ask user if he really wants to
  210. # block anyway
  211. swportids.sort()
  212. try:
  213. idstring = ", ".join([str(x) for x in range(1,counter)])
  214. answer = raw_input("Choose which one to block (%s) " \
  215. "0 = skip: " %idstring)
  216. except KeyboardInterrupt:
  217. print "\nExited by user"
  218. sys.exit(1)
  219. if not answer.isdigit() or int(answer) >= counter:
  220. print "No such id listed"
  221. continue
  222. elif int(answer) == 0:
  223. continue
  224. else:
  225. answer = int(answer) - 1
  226. logger.info("Blocking %s (%s:%s)" %(res[answer]['sysname'],
  227. res[answer]['module'],
  228. res[answer]['port']))
  229. print "Blocking %s (%s:%s)" %(res[answer]['sysname'],
  230. res[answer]['module'],
  231. res[answer]['port'])
  232. if opts.state == 'disable':
  233. # Do snmp-set to block port
  234. try:
  235. nav.arnold.blockPort(res[answer], swinfo[answer+1],
  236. opts.autoenable, 0,
  237. opts.determined,
  238. opts.reason, opts.comment,
  239. getpass.getuser(), 'block')
  240. except (nav.arnold.ChangePortStatusError,
  241. nav.arnold.AlreadyBlockedError,
  242. nav.arnold.FileError,
  243. nav.arnold.InExceptionListError,
  244. nav.arnold.WrongCatidError), why:
  245. print why
  246. logger.error(why)
  247. elif opts.state == 'quarantine':
  248. # Set vlan specified
  249. try:
  250. nav.arnold.blockPort(res[answer], swinfo[answer+1],
  251. opts.autoenable, 0,
  252. opts.determined,
  253. opts.reason, opts.comment,
  254. getpass.getuser(), 'quarantine',
  255. opts.vlan)
  256. except (nav.arnold.ChangePortVlanError,
  257. nav.arnold.AlreadyBlockedError,
  258. nav.arnold.FileError,
  259. nav.arnold.InExceptionListError,
  260. nav.arnold.WrongCatidError), why:
  261. print why
  262. logger.error(why)
  263. else:
  264. print "You must either choose state or give a file as input."
  265. sys.exit(1)
  266. # There are three ways to give input to arnold
  267. # 1. ip-address
  268. # 2. mac-address
  269. # 3. swportid
  270. # When done with disabling or enabling, do the following:
  271. # - send mail to those affected by the action if configured
  272. # - print status to STDOUT for reading from web
  273. def handleFile(file, opts):
  274. """
  275. Reads a file line by line. Parses the first word (everything that
  276. is not a space) of a file and tries to use that as an id in a
  277. block. NB: Make sure the first character of a line is not a space.
  278. """
  279. lines = file.readlines()
  280. for line in lines:
  281. # "chomp"
  282. if line and line[-1] == '\n':
  283. line = line[:-1]
  284. # Grab first part of line, run it through findIdInformation to
  285. # see if it is a valid id
  286. if re.match("[^ ]+", line):
  287. id = re.match("([^ ]+)", line).groups()[0]
  288. print "Trying to block id %s" %id
  289. try:
  290. info = nav.arnold.findIdInformation(id, 2)
  291. except (nav.arnold.UnknownTypeError,
  292. nav.arnold.NoDatabaseInformationError), why:
  293. print why
  294. continue
  295. if len(info) > 0:
  296. firstlist = info[0]
  297. # Check end-time of next list to see if this one is
  298. # also active. If both are active, continue as we
  299. # don't know what to block
  300. if info[1]['endtime'] == 'Still Active':
  301. print "Active on two or more ports, don't know " \
  302. "which one to block. Skipping this id."
  303. continue
  304. swlist = nav.arnold.findSwportinfo(firstlist['netboxid'],
  305. firstlist['ifindex'],
  306. firstlist['module'])
  307. autoenable = opts.autoenable
  308. autoenablestep = 0
  309. determined = opts.determined
  310. reason = opts.reason
  311. comment = opts.comment
  312. username = getpass.getuser()
  313. if opts.state == 'disable':
  314. try:
  315. nav.arnold.blockPort(firstlist, swlist, autoenable,
  316. autoenablestep, determined,
  317. reason, comment, username,
  318. 'block')
  319. except (nav.arnold.ChangePortStatusError,
  320. nav.arnold.InExceptionListError,
  321. nav.arnold.WrongCatidError,
  322. nav.arnold.DbError,
  323. nav.arnold.AlreadyBlockedError), why:
  324. print why
  325. elif opts.state == 'quarantine':
  326. try:
  327. nav.arnold.blockPort(firstlist, swlist, autoenable,
  328. autoenablestep, determined,
  329. reason, comment, username,
  330. 'quarantine', opts.vlan)
  331. except (nav.arnold.ChangePortStatusError,
  332. nav.arnold.InExceptionListError,
  333. nav.arnold.WrongCatidError,
  334. nav.arnold.DbError,
  335. nav.arnold.AlreadyBlockedError), why:
  336. print why
  337. if __name__ == '__main__':
  338. main()