Files
iRedAdmin-Pro-SQL/libs/iredapd/greylist.py
2023-04-10 07:18:32 +02:00

536 lines
14 KiB
Python

# Author: Zhang Huangbin <zhb@iredmail.org>
import web
from libs import iredutils
from libs.logger import logger
def get_all_greylist_settings():
"""Return all existing greylisting settings."""
gl_settings = {}
try:
qr = web.conn_iredapd.select(
'greylisting',
what='id, account, sender, active',
)
if qr:
gl_settings = list(qr)
except Exception as e:
logger.error(e)
return gl_settings
def get_greylist_setting(account=None):
"""Return greylisting setting of specified account."""
gl_setting = {}
if not account:
account = '@.'
if not iredutils.is_valid_amavisd_address(account):
return gl_setting
try:
qr = web.conn_iredapd.select(
'greylisting',
vars={'account': account},
what='id, account, sender, active',
where="""account = $account AND sender='@.'""",
limit=1,
)
if qr:
gl_setting = qr[0]
except Exception as e:
logger.error(e)
return gl_setting
def get_greylist_whitelists(account, address_only=False):
"""Return greylisting whitelists of specified account."""
if not iredutils.is_valid_amavisd_address(account):
return []
whitelists = []
try:
qr = web.conn_iredapd.select(
'greylisting_whitelists',
vars={'account': account},
what='id, sender, comment',
where='account = $account',
order='sender',
)
if qr:
whitelists = list(qr)
# Don't explore SQL structure, just export the sender addresses
if address_only and whitelists:
wl = []
for i in whitelists:
wl.append(i.sender.lower())
whitelists = wl
except Exception as e:
logger.error(e)
return whitelists
def get_greylist_whitelist_domains():
"""Return greylisting whitelist domains of specified account."""
domains = []
try:
qr = web.conn_iredapd.select(
'greylisting_whitelist_domains',
what='domain',
order='domain',
)
if qr:
for i in qr:
domains.append(str(i.domain).lower())
except Exception as e:
logger.error(e)
return domains
def delete_greylist_setting(account, senders=None):
"""Delete greylisting setting of specified account."""
if not iredutils.is_valid_amavisd_address(account):
return True
try:
if senders:
web.conn_iredapd.delete(
'greylisting',
vars={'account': account, 'senders': senders},
where="""account = $account AND sender IN $sender""",
)
else:
web.conn_iredapd.delete(
'greylisting',
vars={'account': account},
where="""account = $account""",
)
return True,
except Exception as e:
return False, repr(e)
def enable_disable_greylist_setting(account, enable=False):
"""Update (or create) greylisting setting of specified account."""
account_type = iredutils.is_valid_amavisd_address(account)
if not account_type:
return False, 'INVALID_ACCOUNT'
active = 0
if enable:
active = 1
gl_setting = {'account': account,
'priority': iredutils.IREDAPD_ACCOUNT_PRIORITIES.get(account_type, 0),
'sender': '@.',
'sender_priority': 0,
'active': active}
try:
# Delete existing record first.
web.conn_iredapd.delete(
'greylisting',
vars={'account': account, 'sender': gl_setting['sender']},
where='account = $account AND sender = $sender',
)
# Create new record
web.conn_iredapd.insert('greylisting', **gl_setting)
except Exception as e:
return False, repr(e)
return True,
def reset_greylist_whitelist_domains(domains=None):
"""Update greylisting whitelist domains for specified account.
@domains -- must be a list/tuple/set
@conn -- sql connection cursor
"""
# Delete existing records first
try:
web.conn_iredapd.delete('greylisting_whitelist_domains', where='1=1')
except Exception as e:
return False, repr(e)
# Insert new records
if domains:
values = []
for d in domains:
values += [{'domain': d}]
try:
web.conn_iredapd.multiple_insert('greylisting_whitelist_domains', values=values)
except Exception as e:
return False, repr(e)
return True, 'GL_WLD_UPDATED'
def update_greylist_whitelist_domains(new=None, removed=None):
"""Add new or remove existing whitelist SPF domains for greylisting service.
@new - must be a list/tuple/set of sender domains
@removed - must be a list/tuple/set of sender domains
@conn - sql connection cursor
"""
_new = []
if new:
_new = [str(i).lower()
for i in new
if iredutils.is_domain(i)]
_new = list(set(_new))
_removed = []
if removed:
_removed = [str(i).lower()
for i in removed
if iredutils.is_domain(i)]
_removed = list(set(_removed))
# Remove duplicates
_removed = [i for i in _removed if i not in _new]
if not (_new or _removed):
return True,
# Insert new whitelists
if _new:
for i in _new:
try:
web.conn_iredapd.insert('greylisting_whitelist_domains', domain=i)
except Exception as e:
logger.error(e)
# Remove existing ones
if _removed:
try:
web.conn_iredapd.delete(
'greylisting_whitelist_domains',
vars={'removed': _removed},
where='domain IN $removed',
)
except Exception as e:
logger.error(e)
return True,
def reset_greylist_whitelists(account, whitelists=None):
"""Reset greylisting whitelists for specified account.
If `whitelists` is empty, all existing whitelists will be removed.
@whitelists - must be a list/tuple/set of whitelist senders, or a list of
dict which maps to sql column/value pairs. e.g.
[{'account': '@.',
'sender': '192.168.1.1',
'comment': ''},
...]
"""
if not iredutils.is_valid_amavisd_address(account):
return False, 'INVALID_ACCOUNT'
# Delete existing whitelists first
try:
web.conn_iredapd.delete(
'greylisting_whitelists',
vars={'account': account},
where='account = $account',
)
except Exception as e:
return False, repr(e)
# Insert new whitelists
if whitelists:
for w in whitelists:
if isinstance(w, dict):
try:
web.conn_iredapd.insert('greylisting_whitelists', **w)
except:
pass
elif isinstance(w, str):
try:
web.conn_iredapd.insert(
'greylisting_whitelists',
account=account,
sender=w,
)
except:
pass
return True,
def update_greylist_whitelists(account, new=None, removed=None):
"""Add new or remove existing greylisting whitelists for specified account.
:param account: must be an valid iRedAPD account
:param new: must be a list/tuple/set of whitelist senders
:param removed: must be a list/tuple/set of whitelist senders
"""
if not iredutils.is_valid_amavisd_address(account):
return False, 'INVALID_ACCOUNT'
_new = []
if new:
_new = [str(i).lower()
for i in new
if iredutils.is_valid_wblist_address(i)]
_new = list(set(_new))
_removed = []
if removed:
_removed = [str(i).lower()
for i in removed
if iredutils.is_valid_wblist_address(i)]
_removed = list(set(_removed))
# Remove duplicates
_removed = [i for i in _removed if i not in _new]
if not (_new or _removed):
return True,
# Insert new whitelists
if _new:
for w in _new:
try:
web.conn_iredapd.insert(
'greylisting_whitelists',
account=account,
sender=w,
)
except:
pass
# Remove existing ones
if _removed:
try:
web.conn_iredapd.delete(
'greylisting_whitelists',
vars={'removed': removed},
where='sender IN $removed',
)
except:
pass
return True,
def update_greylist_settings_from_form(account, form):
# Enable/disable greylisting
# @inherit - inherit from global setting
# @enable - explicitly enable
# @disable - explicitly disable
_gl_value = form.get('greylisting', 'inherit')
if _gl_value == 'inherit':
# Delete greylisting setting
qr = delete_greylist_setting(account=account)
elif _gl_value == 'enable':
qr = enable_disable_greylist_setting(account=account, enable=True)
elif _gl_value == 'disable':
qr = enable_disable_greylist_setting(account=account, enable=False)
else:
return True, 'GL_UPDATED'
if qr[0] is not True:
return qr
# Update greylisting whitelist domains.
if account == '@.':
wl_domains = set()
lines = form.get('whitelist_domains', '').splitlines()
for line in lines:
if iredutils.is_domain(line):
wl_domains.add(str(line).lower())
qr = reset_greylist_whitelist_domains(domains=wl_domains)
if not qr[0]:
return qr
# Update greylisting whitelists.
whitelists = []
# Store senders to avoid duplicate
_senders = set()
lines = form.get('whitelists', '').splitlines()
for line in lines:
# Split sender and comment with '#'
wl = line.split('#', 1)
sender = ''
comment = ''
if len(wl) == 1:
sender = str(wl[0]).strip()
comment = ''
elif len(wl) == 2:
sender = str(wl[0]).strip()
comment = wl[1].strip()
# Validate sender.
if not iredutils.is_valid_wblist_address(sender):
continue
if sender not in _senders:
whitelists += [{'account': account, 'sender': sender, 'comment': comment}]
_senders.add(sender)
qr = reset_greylist_whitelists(account=account, whitelists=whitelists)
if qr[0]:
return True, 'GL_UPDATED'
else:
return qr
def delete_settings_for_removed_users(mails):
mails = [str(v).lower() for v in mails if iredutils.is_email(v)]
if not mails:
return True,
try:
# Delete settings for user
web.conn_iredapd.delete(
'greylisting',
vars={'mails': mails},
where="""account IN $mails""",
)
# Delete whitelists
web.conn_iredapd.delete(
'greylisting_whitelists',
vars={'mails': mails},
where='account IN $mails',
)
# Delete greylisting tracking
web.conn_iredapd.delete(
'greylisting_tracking',
vars={'mails': mails},
where="""recipient IN $mails""",
)
return True,
except Exception as e:
return False, repr(e)
def delete_settings_for_removed_domain(domain):
if not iredutils.is_domain(domain):
return True,
try:
# Delete settings for domain ('@domain.com')
web.conn_iredapd.delete(
'greylisting',
vars={'domain': '@' + domain},
where='account=$domain',
)
# Delete settings for all users under this domain
web.conn_iredapd.delete(
'greylisting',
vars={'domain': '%@' + domain},
where="""account LIKE $domain""",
)
# Delete whitelists
web.conn_iredapd.delete(
'greylisting_whitelists',
vars={'domain': '@' + domain},
where='account=$domain',
)
web.conn_iredapd.delete(
'greylisting_whitelists',
vars={'domain': '%@' + domain},
where='account LIKE $domain',
)
# Delete greylisting tracking
web.conn_iredapd.delete(
'greylisting_tracking',
vars={'domain': domain},
where='rcpt_domain=$domain',
)
return True,
except Exception as e:
return False, repr(e)
def get_tracking_data(account):
"""Get tracking data of given local account."""
_account_type = iredutils.is_valid_amavisd_address(account)
if not _account_type:
return True, []
try:
if _account_type == 'catchall':
# account = '@.'
qr = web.conn_iredapd.select(
'greylisting_tracking',
what='COUNT(blocked_count) AS total, sender_domain',
where='passed=0',
group='sender_domain',
order='total DESC',
)
elif _account_type == 'domain':
domain = account.lstrip('@')
qr = web.conn_iredapd.select(
'greylisting_tracking',
vars={'domain': domain},
where='sender_domain=$domain AND passed=0',
order='init_time DESC',
)
else:
return False, 'INVALID_ACCOUNT'
return True, list(qr)
except Exception as e:
return False, repr(e)
def get_domain_tracking_data(domain):
"""Get tracking data of given domain."""
domain = str(domain).lower()
return get_tracking_data(account='@' + domain)
def filter_whitelisted_ips(ips):
"""Return list of (globally) whitelisted IPs."""
ips = [i for i in ips if iredutils.is_strict_ip(i)]
if not ips:
return True, []
try:
qr = web.conn_iredapd.select(
'greylisting_whitelists',
vars={'account': '@.', 'ips': ips},
what='sender',
where='account=$account AND sender IN $ips',
order='sender',
)
whitelisted_ips = [i.sender for i in qr]
return True, whitelisted_ips
except Exception as e:
logger.error(e)
return False, repr(e)