main.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-"
  3. """
  4. PyDog4Apache - 2015 - by psy (epsylon@riseup.net)
  5. You should have received a copy of the GNU General Public License along
  6. with PyDog4Apache; if not, write to the Free Software Foundation, Inc., 51
  7. Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
  8. """
  9. from options import PyDog4ApacheOptions
  10. from update import Updater
  11. import os, traceback, sys, re, gzip, datetime, string, stat
  12. try:
  13. from ipwhois import IPWhois
  14. except:
  15. print "\n[Warning] - Error importing: ipwhois lib. \n\n On Debian based systems:\n\n $ sudo apt-get install python-pip && sudo pip install ipwhois\n"
  16. print "[Source] - Pypi-ipwhois: https://pypi.python.org/pypi/ipwhois/\n"
  17. sys.exit(2)
  18. DEBUG = 0
  19. class PyDog4Apache(object):
  20. def __init__(self):
  21. self.visitants = {} # visitants
  22. self.visitants_ips = [] # used to not repeat checks
  23. def set_options(self, options):
  24. self.options = options
  25. def create_options(self, args=None):
  26. self.optionParser = PyDog4ApacheOptions()
  27. self.options = self.optionParser.get_options(args)
  28. if not self.options:
  29. return False
  30. return self.options
  31. def banner(self):
  32. print '='*75, "\n"
  33. print " ____ ____ _ _ _ _ "
  34. print "| _ \ _ _| _ \ ___ __ _| || | / \ _ __ __ _ ___| |__ ___ "
  35. print "| |_) | | | | | | |/ _ \ / _` | || |_ / _ \ | '_ \ / _` |/ __| '_ \ / _ |"
  36. print "| __/| |_| | |_| | (_) | (_| |__ _/ ___ \| |_) | (_| | (__| | | | __/"
  37. print "|_| \__, |____/ \___/ \__, | |_|/_/ \_\ .__/ \__,_|\___|_| |_|\___|"
  38. print " |___/ |___/ |_| "
  39. print self.optionParser.description, "\n"
  40. print '='*75
  41. def try_running(self, func, error, args=None):
  42. options = self.options
  43. args = args or []
  44. try:
  45. return func(*args)
  46. except Exception as e:
  47. print(error, "error")
  48. if DEBUG:
  49. traceback.print_exc()
  50. def check_root(self): # check root permissions
  51. if not os.geteuid()==0:
  52. print "[Error] - Some of your 'sources' need more rights to be accessed."
  53. sys.exit("\n[Info] - Try to launch it as root (ex: 'sudo ./pydog4apache')\n")
  54. def is_readable(self, folder): # check if logs are readable without root permissions
  55. try:
  56. st = os.stat(folder)
  57. root = st.st_uid
  58. except:
  59. root = None
  60. return root
  61. def check_access(self, logs): # check if logs required root
  62. for folder in logs:
  63. root = self.is_readable(folder)
  64. if root == None: # wrong folder
  65. print "[Error] - This source:", folder, "is not valid!. Passing..."
  66. if root is 0: # root needed
  67. check_perms = self.try_running(self.check_root, "\nInternal error checking root permissions.")
  68. def extract_logs(self): # extract logs from folder (ex: 'sources.txt')
  69. try:
  70. f = open('sources.txt')
  71. logs = f.readlines()
  72. logs = [ log.replace('\n','') for log in logs ]
  73. f.close()
  74. if not logs:
  75. print "\n[Error] - Imposible to retrieve 'sources' from file.\n"
  76. return
  77. else:
  78. return logs
  79. except:
  80. if os.path.exists('sources.txt') == True:
  81. print '\n[Error] - Cannot open:', 'sources.txt', "\n"
  82. sys.exit(2)
  83. else:
  84. print '\n[Error] - Cannot found:', 'sources.txt', "\n"
  85. sys.exit(2)
  86. def extract_whois(self, ip): # extract whois description
  87. try:
  88. if re.match(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', ip): # only IPs
  89. w = IPWhois(ip, timeout=5) # timeout 5
  90. res = w.lookup_whois(retry_count=2) # legacy whois / retries 2
  91. descr = res["nets"][0]['description']
  92. if self.options.verbose:
  93. print"[Verbose] - Resolving:", ip
  94. else:
  95. descr = None
  96. except:
  97. descr = None
  98. return descr
  99. def is_valid_ip(self, ip): # extract visitors IP / perform whois
  100. if not re.match(r'^127\.\d{1,3}\.\d{1,3}\.\d{1,3}', ip) or re.match(r'^10\.\d{1,3}\.\d{1,3}\.\d{1,3}', ip) or re.match(r'^192.168\.\d{1,3}\.\d{1,3}', ip) or re.match(r'^172.(1[6-9]|2[0-9]|3[0-1]).[0-9]{1,3}.[0-9]{1,3}', ip) or ip.startswith('localhost'): # non LAN IP
  101. if ip not in self.visitants_ips: # create a list with non repeated IPs
  102. if ip[0].isdigit(): # parse if IP starts with a number
  103. self.visitants_ips.append(ip)
  104. ip_found = True
  105. return ip_found
  106. def check_visitants(self, descr): # check visitors list
  107. try:
  108. for v in self.keys:
  109. if v.lower() in descr.lower(): # visitant found!
  110. key = v.lower()
  111. return key
  112. else:
  113. pass
  114. except:
  115. if os.path.exists('keywords.txt') == True:
  116. return
  117. else:
  118. print '\n[Error] - Cannot found:', 'keywords.txt', "\n"
  119. return
  120. def run(self, opts=None):
  121. if opts:
  122. options = self.create_options(opts)
  123. self.set_options(options)
  124. options = self.options
  125. self.banner()
  126. if options.update:
  127. try:
  128. print("\n[Info] - Trying to update automatically to the latest stable version.\n")
  129. Updater()
  130. except:
  131. print("\nSomething was wrong!. You should clone PyDog2Apache manually with:\n")
  132. print("$ git clone https://github.com/epsylon/pydog2apache\n")
  133. sys.exit(2)
  134. print "\n[Info] - Sending dogs to sniff logs... Please wait!\n"
  135. print "-"*22
  136. logs = self.try_running(self.extract_logs, "\nInternal error extracting logs.")
  137. access = self.check_access(logs)
  138. f = open('keywords.txt')
  139. self.keys = f.readlines()
  140. self.keys = [ self.key.replace('\n','') for self.key in self.keys ]
  141. f.close()
  142. if not self.keys:
  143. print "\n[Error] - Imposible to retrieve 'visitants' from file.\n"
  144. return
  145. for folder in logs:
  146. if not folder.endswith('/'):
  147. folder = folder + "/"
  148. try:
  149. listing = os.listdir(folder)
  150. for log in listing:
  151. if self.options.verbose:
  152. print "[Verbose] - Analyzing:", folder+log
  153. if log.endswith('.gz'): # also read on compressed logs
  154. with gzip.open(folder+log, 'rb') as f:
  155. dog_sniff = f.readlines()
  156. else:
  157. try:
  158. f = open(folder+log)
  159. except:
  160. return
  161. dog_sniff = f.readlines()
  162. sep = '-'
  163. sep2= '['
  164. for record in dog_sniff:
  165. ip = record.split(sep, 1)[0]
  166. ip = ''.join(ip.split())
  167. ip_found = self.is_valid_ip(ip)
  168. if ip_found is True: # extract info
  169. date = [record.split(']')[0] for p in record.split('[') if ']' in p]
  170. for d in date:
  171. date_visit = d.split(sep2, 1)[1]
  172. descr = self.extract_whois(ip)
  173. if descr is not None:
  174. key = self.check_visitants(descr)
  175. if key:
  176. self.visitants[str(ip)] = "[" + str(key.upper()) + "]" + "|" + str(str(descr) + "|" + str(date_visit) + "|" + str(folder+log))
  177. f.close()
  178. except:
  179. pass
  180. if self.options.verbose:
  181. print "-"*22
  182. if self.options.file: # export results to file
  183. namefile = str(self.options.file)
  184. self.report = open(namefile, 'w')
  185. self.report.write("# Apache web logs sneaker - GPLv3 - by psy\n")
  186. self.report.write("# Project: https://github.com/epsylon/pydog4apache - 03c8.net\n")
  187. self.report.write("# Reported at: " + str(datetime.datetime.now()) + "\n\n")
  188. for key,val in self.visitants.items():
  189. print("{} -> {}".format(key, val))
  190. print "-"*12
  191. if self.options.file:
  192. self.report.write(key + " -> " + val + "\n")
  193. self.report.write("-"*12 + "\n")
  194. if not self.visitants.items():
  195. print "[Info] - Not any 'keyword' found on your 'visitants'.\n"
  196. if self.options.file:
  197. self.report.write("Not any 'keyword' found on your 'visitants'.\n")
  198. if self.options.file: # close file containter
  199. self.report.close()
  200. if self.options.emails: # send results via email
  201. import smtplib, socket
  202. from email.mime.text import MIMEText
  203. self.notify = open('tempmail', 'w')
  204. self.notify.write("# Apache web logs sneaker - GPLv3 - by psy\n")
  205. self.notify.write("# Project: https://github.com/epsylon/pydog4apache - 03c8.net\n")
  206. self.notify.write("# Reported at: " + str(datetime.datetime.now()) + "\n\n")
  207. for key,val in self.visitants.items():
  208. self.notify.write(key + " -> " + val + "\n")
  209. self.notify.write("-"*12 + "\n")
  210. if not self.visitants.items():
  211. self.notify.write("Not any 'keyword' found on your 'visitants'.\n")
  212. self.notify.close()
  213. self.notify = open('tempmail', 'rb')
  214. msg = MIMEText(self.notify.read())
  215. self.notify.close()
  216. doggy = 'pydog4apache@'+str(socket.gethostname())
  217. herd = self.options.emails
  218. herd = herd.split(',')
  219. msg['Subject'] = '[pydog4apache] You have interesting visitants...'
  220. msg['From'] = doggy
  221. msg['To'] = ", ".join(herd)
  222. msg.preamble = 'You have a report with visitants...'
  223. try:
  224. s = smtplib.SMTP('localhost')
  225. s.sendmail(doggy, herd, msg.as_string())
  226. s.quit()
  227. except:
  228. print "[Error] - Not any SMTP server running on: '"+str(socket.gethostname())+"'. Aborting email report...\n"
  229. os.remove('tempmail') # remove temporal email container
  230. if __name__ == "__main__":
  231. app = PyDog4Apache()
  232. options = app.create_options()
  233. if options:
  234. app.set_options(options)
  235. app.run()