adblock_manager.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. import os
  2. import re
  3. import time
  4. import datetime
  5. import subprocess
  6. import urllib.request
  7. import json
  8. import config
  9. from core.file_manager import read_file, write_file, load_adblock_lists, load_custom_rules, load_panel_settings, save_panel_settings
  10. from core.dns_manager import get_zone_block
  11. def is_valid_domain(d):
  12. if not d or len(d) > 253: return False
  13. if '..' in d or d.startswith('.'): return False
  14. # Επιτρέπει μόνο αλφαριθμητικά, παύλες, underscores και τελείες.
  15. if not re.match(r'^[a-zA-Z0-9\-_.]+$', d): return False
  16. if '.' not in d: return False
  17. return True
  18. def generate_and_apply_rpz():
  19. lists = load_adblock_lists()
  20. active_lists = [l for l in lists if l.get('enabled')]
  21. custom_rules = load_custom_rules()
  22. domains_set = set()
  23. try:
  24. # 1. Fetch Lists
  25. for lst in active_lists:
  26. req = urllib.request.Request(lst['url'], headers={'User-Agent': 'Mozilla/5.0'})
  27. try:
  28. with urllib.request.urlopen(req, timeout=15, context=config.ssl_ctx) as response:
  29. lines = response.read().decode('utf-8', errors='ignore').splitlines()
  30. for line in lines:
  31. l = line.split('#')[0].strip() # Κόβουμε inline comments
  32. # Γρήγορα φίλτρα (Αγνοούμε σχόλια, HTML, και Regex τύπου Pi-hole)
  33. if not l or l.startswith(('!', ':', '[', '@')): continue
  34. if '/' in l or '*' in l or '|' in l and not l.startswith('||'): continue
  35. d = ""
  36. if l.startswith('||'):
  37. d = re.split(r'[\^/\$:]', l[2:])[0]
  38. elif l.startswith(('0.0.0.0 ', '127.0.0.1 ')):
  39. parts = l.split()
  40. if len(parts) >= 2 and parts[1] not in ('localhost', 'broadcasthost', 'localhost.localdomain'):
  41. d = parts[1]
  42. elif ' ' not in l:
  43. d = re.split(r'[\^/\$:]', l)[0]
  44. d = d.strip().rstrip('.')
  45. # Αυστηρός Έλεγχος (Strict Validation)
  46. if is_valid_domain(d):
  47. domains_set.add(d)
  48. except Exception as e:
  49. print(f"Failed to fetch {lst['name']}: {e}")
  50. # 2. Add Custom Deny
  51. for d in custom_rules.get('deny', []):
  52. clean_d = d.strip().rstrip('.')
  53. if clean_d.startswith('*.'): clean_d = clean_d[2:]
  54. if is_valid_domain(clean_d):
  55. domains_set.add(clean_d)
  56. # 3. Create RPZ
  57. rpz_records = []
  58. # Μετατροπή allowed λίστας σε SET + καθάρισμα (Για ακαριαία ταχύτητα O(1))
  59. allowed_cleaned = set()
  60. for d in custom_rules.get('allowed', []):
  61. clean_d = d.strip().rstrip('.')
  62. if clean_d.startswith('*.'): clean_d = clean_d[2:]
  63. if is_valid_domain(clean_d):
  64. allowed_cleaned.add(clean_d)
  65. # ΠΡΩΤΑ τα Allowed (PASSTHRU)
  66. for d in allowed_cleaned:
  67. rpz_records.append(f"{d}\tCNAME\trpz-passthru.")
  68. rpz_records.append(f"*.{d}\tCNAME\trpz-passthru.")
  69. # ΜΕΤΑ τα Blocked
  70. for d in domains_set:
  71. # Ακαριαίος έλεγχος χάρη στο set()
  72. if d in allowed_cleaned:
  73. continue
  74. rpz_records.append(f"{d}\tCNAME\t.")
  75. rpz_records.append(f"*.{d}\tCNAME\t.")
  76. serial = time.strftime('%Y%m%d%H') # Ώρα αντί για 01 για σίγουρο increment
  77. header = f"$TTL 60\n$ORIGIN rpz.blacklist.\n@ IN SOA localhost. root.localhost. ({serial} 3h 1h 1w 1h)\n IN NS localhost.\n"
  78. content = header + "\n".join(rpz_records) + "\n"
  79. write_file(config.ADBLOCK_DB_PATH, content)
  80. subprocess.run(["chown", "bind:bind", config.ADBLOCK_DB_PATH])
  81. # Ενημέρωση named.conf.local
  82. conf_local = read_file(config.LOCAL_CONF)
  83. if 'zone "rpz.blacklist"' not in conf_local:
  84. conf_local += f'\nzone "rpz.blacklist" {{\n type master;\n file "{config.ADBLOCK_DB_PATH}";\n allow-query {{ any; }};\n allow-transfer {{ any; }};\n}};\n'
  85. write_file(config.LOCAL_CONF, conf_local)
  86. # Ενημέρωση named.conf.options
  87. options = read_file(config.OPTIONS_FILE)
  88. if "response-policy" not in options:
  89. options = re.sub(r'(options\s*\{)', r'\1\n response-policy { zone "rpz.blacklist"; };', options)
  90. write_file(config.OPTIONS_FILE, options)
  91. subprocess.run(["rndc", "reconfig"])
  92. subprocess.run(["rndc", "reload", "rpz.blacklist"])
  93. s = load_panel_settings()
  94. s['adblock_last_update'] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
  95. s['adblock_domains_count'] = len(domains_set)
  96. save_panel_settings(s)
  97. return {"status": "success", "count": len(domains_set), "time": s['adblock_last_update']}
  98. except Exception as e:
  99. return {"status": "error", "message": str(e)}
  100. def disable_rpz():
  101. conf_local = read_file(config.LOCAL_CONF)
  102. options = read_file(config.OPTIONS_FILE)
  103. block = get_zone_block(conf_local, "rpz.blacklist")
  104. if block:
  105. conf_local = conf_local.replace(block, "")
  106. write_file(config.LOCAL_CONF, conf_local)
  107. options = re.sub(r'\s*response-policy\s*\{\s*zone\s*"rpz\.blacklist";\s*\};\s*', '\n', options)
  108. write_file(config.OPTIONS_FILE, options)
  109. subprocess.run(["rndc", "reconfig"])
  110. return {"status": "success", "message": "AdBlock (RPZ) disabled."}