Mini Shell
Direktori : /usr/sbin/ |
|
Current File : //usr/sbin/imunify360-webshield-ipdetect |
#!/opt/imunify360/venv/bin/python3
import glob
import ipaddress
import os
import random
import re
import shutil
import string
import subprocess
import sys
class Panel:
def __init__(self, host_addresses):
self.host_addresses = set()
if host_addresses:
self.host_addresses.update(host_addresses)
@classmethod
def detect(cls):
if not cls.detect_path:
return False
return os.path.exists(cls.detect_path)
def get_destinations(self):
data = {}
for ip, domains in self.additional.items():
for domain in domains:
data[domain] = ip
return data
def get_ssl_mapping(self):
data = {}
for ip, domains in self.additional.items():
data[ip] = sorted(domains)[0]
if self.main_ip and self.first_domain:
data[self.main_ip] = self.first_domain
return data
class cPanel(Panel):
detect_path = '/usr/local/cpanel/cpanel'
domain_ips_path = '/etc/domainips'
main_address_path = '/var/cpanel/mainip'
domain_users_path = '/etc/domainusers'
def prepare(self):
all_domains = self._get_domains()
self.main_ip = self._get_main_ip()
self.additional = self._get_additional()
if not all_domains:
self.first_domain = None
return
diff = sorted(
all_domains.difference(
*self.additional.values()))
self.first_domain = diff[0] if diff else None
@classmethod
def _get_domains(cls):
domains = set()
try:
with open(cls.domain_users_path) as f:
for line in f:
_, domain = [i.strip() for i in line.split(':')]
domains.add(domain)
except Exception:
pass
return domains
def _get_additional(self):
addresses = {}
if not os.path.exists(self.domain_ips_path):
return addresses
try:
with open(self.domain_ips_path) as f:
for line in f:
if line.startswith('#'):
continue
if ':' not in line:
continue
ip, domain = [i.strip() for i in line.split(':', 1)]
if not ip:
continue
if self.host_addresses and ip not in self.host_addresses:
continue
if ip not in addresses:
addresses[ip] = []
addresses[ip].append(domain)
except Exception:
pass
return addresses
@classmethod
def _get_main_ip(cls):
try:
with open(cls.main_address_path) as f:
return f.read().strip()
except Exception:
return
class Plesk(Panel):
detect_path = '/usr/sbin/plesk'
passwd_path = '/etc/psa/.psa.shadow'
@classmethod
def _get_plesk_passwd(cls):
try:
with open(cls.passwd_path) as f:
return f.read()
except Exception:
return
@classmethod
def _find_main_ip(cls, ip_domains):
"""
Attempt to detect main IP address
"""
# Find 'shared' ip with Plesk utility
ip_info = cls._get_ip_info()
if ip_info:
for ip in ip_domains.keys():
if ip_info.get(ip): # Current IP maps to True
return ip # OK. Found it. Exit
# Let the main ip be IP with max number of domains
max_count = 0
max_ip = None
for ip, domains in ip_domains.items():
count = len(domains)
if count > max_count:
max_count = count
max_ip = ip
return max_ip
def _get_domain_ips(self):
data = {}
passwd = self._get_plesk_passwd()
if not passwd:
return data
env = {'MYSQL_PWD': passwd}
query = ("""SELECT dom.name, iad.ip_address FROM domains """
"""dom LEFT JOIN DomainServices d ON (dom.id = d.dom_id """
"""AND d.type = 'web') LEFT JOIN IpAddressesCollections ia """
"""ON ia.ipCollectionId = d.ipCollectionId LEFT JOIN """
"""IP_Addresses iad ON iad.id = ia.ipAddressId""")
cmd = ['mysql', '-u', 'admin', '-Dpsa', '-e', query]
try:
p = subprocess.Popen(
cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
universal_newlines=True, env=env)
out, err = p.communicate()
except (subprocess.CalledProcessError, FileNotFoundError):
return data
if not out:
return data
for line in out.splitlines():
domain, ip = line.split()
if not ip:
continue
if self.host_addresses and ip not in self.host_addresses:
continue
if ip not in data:
data[ip] = []
data[ip].append(domain)
return data
@classmethod
def _get_ip_info(cls):
data = {}
patt = re.compile(
r"""[^:]+: # Interface name, e.g 'eth0:'
(?P<ip>[^/]+) # all symbols before forward slash
""", re.VERBOSE)
cmd = ['plesk', 'bin', 'ipmanage', '--ip_list']
try:
out = subprocess.check_output(
cmd, stderr=subprocess.DEVNULL, universal_newlines=True)
except subprocess.CalledProcessError:
return data
if not out:
return data
for line in out.splitlines():
splitted = line.split()
if not splitted:
continue
if len(splitted) < 3:
continue
match = patt.match(splitted[2]) # third field contains IP
if match and splitted[1] in ('S', 'E'):
data[match.group('ip')] = True if splitted[1] == 'S' else False
return data
def prepare(self):
all_domains = self._get_domain_ips()
self.main_ip = self._find_main_ip(all_domains)
if not all_domains or not self.main_ip:
self.first_domain = None
self.additional = {}
return
shared_ips = sorted(all_domains.get(self.main_ip, tuple()))
self.first_domain = shared_ips[0] if shared_ips else None
self.additional = {k: v for k, v in all_domains.items()
if k != self.main_ip}
class DirectAdmin(Panel):
detect_path = '/usr/local/directadmin/custombuild/build'
glob_path = '/usr/local/directadmin/data/users/*/domains/*.conf'
@classmethod
def _get_paths(cls):
paths = []
for path in glob.iglob(cls.glob_path):
paths.append(path)
return paths
@staticmethod
def _read_config(path):
domain, ip = None, None
domain_found, ip_found = False, False
with open(path) as f:
for line in f:
if domain_found and ip_found:
break
if line.startswith('#'):
continue
if '=' not in line:
continue
key, value = [i.strip() for i in line.split('=', 1)]
if key == 'domain':
domain = value
domain_found = True
elif key == 'ip':
ip = value
ip_found = True
else:
continue
return ip, domain
@staticmethod
def _find_main_ip(ip_map):
"""
The IP address with maximum domains is the main one
"""
max_count = 0
max_ip = None
for ip, domains in ip_map.items():
count = len(domains)
if count > max_count:
max_count = count
max_ip = ip
return max_ip
@classmethod
def _get_domains(cls):
domains = {}
for path in cls._get_paths():
try:
ip, domain = cls._read_config(path)
except UnicodeDecodeError:
continue
if ip and domain:
if ip not in domains:
domains[ip] = []
domains[ip].append(domain)
return domains
def prepare(self):
all_domains = self._get_domains()
self.main_ip = self._find_main_ip(all_domains)
if not all_domains or not self.main_ip:
self.first_domain = None
self.additional = {}
return
shared_ips = sorted(all_domains.get(self.main_ip, tuple()))
self.first_domain = shared_ips[0] if shared_ips else None
self.additional = {k: v for k, v in all_domains.items()
if k != self.main_ip}
class AddressHandler:
map_conf = '/etc/imunify360-webshield/backend-destinations.conf'
map_file = '/etc/imunify360-webshield/default-destinations.dat'
@staticmethod
def _generate(length=8):
sample = string.ascii_letters + string.digits
return ''.join(random.sample(sample, length))
@staticmethod
def _get_panel():
for panel in cPanel, Plesk, DirectAdmin:
if panel.detect():
return panel
@staticmethod
def _get_ip_addresses():
addresses = set()
patt = re.compile(
r"""(?:\d+:\s?)? # number (e.g. '1:') and optional space
(?P<if>\S+) # interface name (e.g. 'eth0')
\s+? # space(s)
inet6?\s # word 'inet' or 'inet6'
(?P<ip>(?: # start IP capturing
\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3} # for IPv4
| # or
[0-9a-fA-F:]+) # for IPv6
) # end capturing
(?:/(?P<mask>\d{1,3}))? # capture mask (e.g.'/24'), if any
""", re.VERBOSE)
p = subprocess.Popen(['ip', '-o', 'address', 'show'],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True)
out, err = p.communicate()
if not out:
return
for line in out.splitlines():
m = patt.match(line)
if not m:
continue
iface, ip, mask = m.group('if', 'ip', 'mask')
if iface == 'lo':
continue
if ':' in ip and ipaddress.IPv6Address(ip).is_link_local:
continue
addresses.add(ip)
return addresses
@classmethod
def _save(cls, data, path, semicolon=True):
if not data:
return
fmt = "{} {};\n" if semicolon else "{} {}\n"
tmp_path = '.'.join([path, cls._generate()])
with open(tmp_path, 'w') as f:
for key, val in data.items():
f.write(fmt.format(key, val))
shutil.move(tmp_path, path)
@classmethod
def run(cls):
addresses = cls._get_ip_addresses()
if len(addresses) < 2:
sys.stderr.write('No additional IP addresses found\n')
return
panel = cls._get_panel()
if panel is None:
sys.stderr.write('We cannot use unknown hosting panels. Skip\n')
return
p = panel(addresses)
p.prepare()
cls._save(p.get_ssl_mapping(), cls.map_file, False)
if __name__ == '__main__':
AddressHandler.run()
Zerion Mini Shell 1.0