main.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-"
  3. """
  4. PyDog4Apache - 2016/2022 - 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. import os, traceback, sys, re, gzip, datetime, string, stat
  10. from .options import PyDog4ApacheOptions
  11. from .update import Updater
  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 python3-pip && sudo pip3 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 != 0:
  49. traceback.print_exc()
  50. def check_root(self): # check root permissions
  51. if not os.geteuid()==0:
  52. sys.exit("[Info] You need to launch it as root (ex: 'sudo python3 pydog4apache')...\n")
  53. def is_readable(self, folder): # check if logs are readable without root permissions
  54. try:
  55. st = os.stat(folder)
  56. root = st.st_uid
  57. except:
  58. root = None
  59. return root
  60. def check_access(self, logs): # check if logs required root
  61. for folder in logs:
  62. root = self.is_readable(folder)
  63. if root == None: # wrong folder
  64. print("\n[Error] This source: [", folder, "] is not valid... [Passing!]")
  65. if root is 0: # root needed
  66. check_perms = self.try_running(self.check_root, "\n[Error] Internal error checking root permissions.")
  67. def extract_logs(self): # extract logs from folder (ex: 'sources.txt')
  68. try:
  69. f = open('sources.txt')
  70. logs = f.readlines()
  71. logs = [ log.replace('\n','') for log in logs ]
  72. f.close()
  73. if not logs:
  74. print("\n[Error] Imposible to retrieve 'sources' from file... [Aborting!]\n")
  75. return
  76. else:
  77. return logs
  78. except:
  79. if os.path.exists('sources.txt') == True:
  80. print('\n[Error] Cannot open:', 'sources.txt', "[Aborting!]\n")
  81. sys.exit(2)
  82. else:
  83. print('\n[Error] Cannot found:', 'sources.txt', "[Aborting!]\n")
  84. sys.exit(2)
  85. def extract_whois(self, ip): # extract whois description
  86. try:
  87. if re.match(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', ip): # only IPs
  88. w = IPWhois(ip, timeout=5) # timeout 5
  89. res = w.lookup_whois(retry_count=2) # legacy whois / retries 2
  90. descr = res["nets"][0]['description']
  91. else:
  92. descr = None
  93. except:
  94. descr = None
  95. return descr
  96. def is_valid_ip(self, ip): # extract visitors IP / perform whois
  97. 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
  98. if ip not in self.visitants_ips: # create a list with non repeated IPs
  99. if ip[0].isdigit(): # parse if IP starts with a number
  100. self.visitants_ips.append(ip)
  101. ip_found = True
  102. return ip_found
  103. def check_visitants(self, descr): # check visitors list
  104. try:
  105. for v in self.keys:
  106. v = v.rstrip()
  107. if v.lower() in descr.lower(): # visitant found!
  108. key = v.lower()
  109. return key
  110. else:
  111. pass
  112. except:
  113. if os.path.exists('keywords.txt') == True:
  114. return
  115. else:
  116. print('\n[Error] Cannot found:', 'keywords.txt', "[Aborting!]\n")
  117. return
  118. def run(self, opts=None):
  119. if opts:
  120. options = self.create_options(opts)
  121. self.set_options(options)
  122. options = self.options
  123. self.banner()
  124. if options.update: # auto-update tool
  125. print("\n[Info] Trying to update automatically to the latest stable version...\n")
  126. Updater()
  127. sys.exit()
  128. print("\n[Info] Sending 'dogs' to sniff 'logs'... [Waiting!]")
  129. if self.options.verbose:
  130. print("\n"+"="*40+"\n")
  131. logs = self.try_running(self.extract_logs, "\nInternal error extracting logs.")
  132. access = self.check_access(logs)
  133. f = open('keywords.txt')
  134. self.keys = f.readlines()
  135. f.close()
  136. if not self.keys:
  137. print("\n[Error] Imposible to retrieve 'visitants' from file... [Aborting!]\n")
  138. return
  139. for folder in logs:
  140. if not folder.endswith('/'):
  141. folder = folder + "/"
  142. try:
  143. listing = os.listdir(folder)
  144. for log in listing:
  145. if self.options.verbose:
  146. print("[Info] Analyzing:", folder+log)
  147. if log.endswith('.gz'): # also read on compressed logs
  148. with gzip.open(folder+log, 'r') as f:
  149. dog_sniff = f.readlines()
  150. dog_sniff = [ dog_sniff[0].decode() for sniff in dog_sniff ]
  151. else:
  152. try:
  153. f = open(folder+log)
  154. dog_sniff = f.readlines()
  155. except:
  156. return
  157. if dog_sniff:
  158. sep = '-'
  159. sep2= '['
  160. for record in dog_sniff:
  161. ip = record.split(sep, 1)[0]
  162. ip = ''.join(ip.split())
  163. ip_found = self.is_valid_ip(ip)
  164. if ip_found is True: # extract info
  165. if self.options.verbose:
  166. print(" |-> IP Found:", ip)
  167. date = [record.split(']')[0] for p in record.split('[') if ']' in p]
  168. for d in date:
  169. date_visit = d.split(sep2, 1)[1]
  170. descr = self.extract_whois(ip)
  171. if descr is not None:
  172. if self.options.verbose:
  173. print(" |-> WHOIS Description:", descr)
  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. print("\n"+"="*40+"\n")
  181. if self.options.file: # export results to file
  182. namefile = str(self.options.file)
  183. self.report = open(namefile, 'w')
  184. self.report.write("# Apache web logs sneaker - 2016/2022 - by psy (https://03c8.net)\n")
  185. self.report.write("# Project: https://code.03c8.net/epsylon/pydog4apache\n")
  186. self.report.write("# Reported at: " + str(datetime.datetime.now()) + "\n\n")
  187. for key,val in list(self.visitants.items()):
  188. print(("{} -> {}".format(key, val))+"\n")
  189. if self.options.file:
  190. self.report.write("Logs Analyzed:\n\n")
  191. for log in listing:
  192. self.report.write(" -"+log+"\n")
  193. self.report.write("\nResults:\n\n")
  194. self.report.write(key + " -> " + val + "\n")
  195. if not list(self.visitants.items()):
  196. print("[Info] Not any 'keyword' found on your 'visitants'... [Exiting!]\n")
  197. if self.options.file:
  198. self.report.write("Logs Analyzed:\n\n")
  199. for log in listing:
  200. self.report.write(" -"+log+"\n")
  201. self.report.write("\nResults:\n\n")
  202. self.report.write(" -Not any 'keyword' found on your 'visitants'...\n")
  203. if self.options.file: # close file containter
  204. self.report.close()
  205. if self.options.emails: # send results via email
  206. print("-"*22+"\n")
  207. import smtplib, socket
  208. from email.mime.text import MIMEText
  209. self.notify = open('tempmail', 'w')
  210. self.notify.write("# Apache web logs sneaker - 2016/2022 - by psy (https://03c8.net)\n")
  211. self.notify.write("# Project: https://code.03c8.net/epsylon/pydog4apache\n")
  212. self.notify.write("# Reported at: " + str(datetime.datetime.now()) + "\n\n")
  213. self.notify.write("Logs Analyzed:\n\n")
  214. for log in listing:
  215. self.notify.write(" -"+log+"\n")
  216. self.notify.write("\nResults:\n\n")
  217. for key,val in list(self.visitants.items()):
  218. self.notify.write(key + " -> " + val + "\n")
  219. if not list(self.visitants.items()):
  220. self.notify.write("Not any 'keyword' found on your 'visitants'...\n")
  221. self.notify.close()
  222. self.notify = open('tempmail', 'rb')
  223. msg = MIMEText(self.notify.read().decode('utf-8'))
  224. self.notify.close()
  225. doggy = 'pydog4apache@'+str(socket.gethostname())
  226. herd = self.options.emails
  227. herd = herd.split(',')
  228. msg['Subject'] = '[pydog4apache] You have interesting visitants...'
  229. msg['From'] = doggy
  230. msg['To'] = ", ".join(herd)
  231. msg.preamble = 'You have a report with visitants...'
  232. try:
  233. s = smtplib.SMTP('localhost')
  234. s.sendmail(doggy, herd, msg.as_string())
  235. s.quit()
  236. except:
  237. print("[Error] Not any SMTP (email) server running on: '"+str(socket.gethostname())+"'... [Passing!]\n")
  238. os.remove('tempmail') # remove temporal email container
  239. if __name__ == "__main__":
  240. app = PyDog4Apache()
  241. options = app.create_options()
  242. if options:
  243. app.set_options(options)
  244. app.run()