Newer
Older
abck / abck
  1. #!/usr/local/bin/python
  2. #
  3. # abck - Part of the ABMGMT package from TundraWare Inc.
  4. # Copyright (c) 2001, TundraWare Inc., All Rights Reserved.
  5. # See the accompanying file called, 1-ABMGMT-License.txt
  6. # for Licensing Terms
  7. #
  8. # Build a report of all unauthorized access attempts
  9. #
  10. # Usage: abck [String To Match In Log Record]
  11.  
  12. ##########
  13.  
  14. ####################
  15. # Imports
  16. ####################
  17.  
  18. import commands
  19. import re
  20. import sys
  21.  
  22. ####################
  23. # Booleans
  24. ####################
  25.  
  26. FALSE = 0 == 1
  27. TRUE = not FALSE
  28.  
  29. DONE = FALSE
  30.  
  31. ####################
  32. # Constants And Literals
  33. ####################
  34.  
  35. ANS = ";; ANSWER SECTION:"
  36. AUTH = ";; AUTHORITY SECTION:"
  37. DIG = "/usr/bin/dig -t ptr -x "
  38. LOG = "/var/log/messages"
  39. OUT = "./ABUSERS"
  40.  
  41. VERSION = "$Id: abck,v 1.6 2001/07/16 22:50:39 tundra Exp $"
  42.  
  43. ####################
  44. # Data Structures
  45. ####################
  46.  
  47. # Dictionary of keywords indicating attack, and position of host address/name
  48. # in their log records
  49.  
  50. AttackKeys = {
  51. "refused" : 8,
  52. "unauthorized" : 7
  53. }
  54.  
  55. # Cache dictionary of all attacking hosts discovered this run of the program
  56.  
  57. NameCache = {}
  58.  
  59. ####################
  60. # Regular Expression Handlers
  61. ####################
  62.  
  63. # Regular Expression which describes a legit IP quad address
  64.  
  65. IPQuad = r"(\d{1,3}\.){3}\d{1,3}$"
  66.  
  67. ####################
  68. # Function Definitions
  69. ####################
  70.  
  71. # Return the ending substring of a host name with 'depth' number of dots
  72. # in it
  73.  
  74. def HostDepth(host, depth=2):
  75.  
  76. # Break the address down into components
  77. components = host.split(".")
  78.  
  79. # And return the recombined pieces we want
  80. return '.'.join(components[-depth:])
  81.  
  82. ####################
  83.  
  84. # Check a name, see if it's an IP quad, and if so, return reverse.
  85. # If not, return the original name.
  86. #
  87. # This is better than a socket.gethostbyaddr() call because
  88. # this will return the authority information for an address
  89. # if no explicit reverse resolution is found.
  90.  
  91. def CheckIPReverse(hostquad):
  92. # If it's an IP address in quad form, reverse resolve it
  93. if re.match(IPQuad, hostquad):
  94.  
  95. DIGResults = commands.getoutput(DIG + hostquad).splitlines()
  96.  
  97. ansname = ""
  98. authname = hostquad
  99. # Results must either have an Answer or Authority record
  100. if ( DIGResults.count(ANS) + DIGResults.count(AUTH)):
  101.  
  102. i = 0
  103. while i < len(DIGResults):
  104.  
  105. if DIGResults[i].startswith(ANS):
  106. ansname = DIGResults[i+1].split()[4]
  107.  
  108. if DIGResults[i].startswith(AUTH):
  109. authname = DIGResults[i+1].split()[-2]
  110.  
  111. i += 1
  112.  
  113. if ansname:
  114. hostname = ansname
  115. else:
  116. hostname = authname
  117.  
  118. # Get rid of trailing dot, if any
  119. if hostname[-1] == '.':
  120. hostname = hostname[:-1]
  121.  
  122. else:
  123. hostname = hostquad
  124. return hostname
  125.  
  126.  
  127. ####################
  128.  
  129. # Paw through a log record, doing any reverse resolution needed,
  130. # confirm with user, and return name of the host to notify about
  131. # the instrusion attempt. A null return means the user want to
  132. # skip this record.
  133.  
  134.  
  135. def ProcessLogRecord(logrecord):
  136.  
  137. # Check for each known attack keyword
  138.  
  139. sendto = ""
  140. for attackkey in AttackKeys.keys():
  141.  
  142. logfield = logrecord.split()
  143. if logrecord.count(attackkey):
  144.  
  145. # Different attack records put the hostquad in different places
  146. hostquad = logfield[AttackKeys[attackkey]]
  147. if hostquad[-1] == ',':
  148. hostquad = hostquad[:-1] # Strip trailing dots
  149.  
  150. # Go do a reverse resolution if we need to
  151. hostname = CheckIPReverse(hostquad)
  152.  
  153. # Check for the case of getting a PTR record back
  154. hostname = ReversePTR(hostname)
  155.  
  156. # Check if we've seen this abuser before
  157. if NameCache.has_key(hostname):
  158. sendto = NameCache[hostname]
  159.  
  160. # New one
  161. else:
  162. depth = 2
  163. DONE=FALSE
  164. while not DONE:
  165.  
  166. # Set depth of default response
  167. default = HostDepth(hostname, depth)
  168.  
  169. # Ask the user about it
  170. st = raw_input("\nLog Record: %s\n Who Gets Message for: <%s>? %s [%s] " %
  171. (logrecord,
  172. hostname[-40:],
  173. " " * (40 - len(hostname)),
  174. default))
  175.  
  176. # Parse the response
  177. if st == "!": # Skip this record
  178. sendto = ""
  179. DONE = TRUE
  180.  
  181. elif st.lower() == "r": # Less depth in recipient name
  182. if depth > 2:
  183. depth -= 1
  184.  
  185. elif st.lower() == "l": # More depth in recipient name
  186. if depth < len(hostname.split('.')):
  187. depth += 1
  188.  
  189. else:
  190. if st: # User keyed in their own recipient
  191. sendto = st
  192. else: # User accepted the default
  193. sendto = default
  194. DONE = TRUE
  195. NameCache[hostname] = sendto # Cache it
  196. return sendto
  197.  
  198. ####################
  199.  
  200. # Check the passed hostname and see if it looks like a PTR record.
  201. # If so, strip out the address portion, reverse it, and trying
  202. # doing another reverse lookup. If not, just return the original hostname.
  203.  
  204. def ReversePTR(hostname):
  205.  
  206. tmp = hostname.split("in-addr.arpa")
  207.  
  208. if len(tmp) > 1: # Looks like a PTR record
  209.  
  210. tmp = tmp[0].split('.') # Get addr components
  211. tmp.reverse() # and reverse their order
  212. # Take the 1st four quads (some PTR records have more)
  213. # and see if we can dig out a reverse.
  214. hostname = CheckIPReverse('.'.join(tmp[1:5]))
  215.  
  216. return hostname
  217.  
  218.  
  219. #------------------------- Program Entry And Mail Loop -----------------------#
  220.  
  221. logfile = open(LOG)
  222. abuserfile = open(OUT, "w")
  223.  
  224. # Read in the whole log logfileile
  225. for logrecord in logfile.read().splitlines():
  226.  
  227. # Go check the record in no command line constraint given
  228. # or a constraint is given and exits in the record
  229. if (len(sys.argv) == 1) or logrecord.count(sys.argv[1]):
  230.  
  231. sendto = ProcessLogRecord(logrecord)
  232. if sendto:
  233. abuserfile.write("abnot \"" + logrecord + "\" " +
  234. sendto + "\n")
  235.  
  236. logfile.close()
  237. abuserfile.close()