mirror of
https://github.com/marcus-alicia/iRedAdmin-Pro-SQL.git
synced 2026-05-30 00:49:42 +00:00
Add files via upload
This commit is contained in:
0
libs/iredapd/__init__.py
Normal file
0
libs/iredapd/__init__.py
Normal file
535
libs/iredapd/greylist.py
Normal file
535
libs/iredapd/greylist.py
Normal file
@@ -0,0 +1,535 @@
|
||||
# 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)
|
||||
286
libs/iredapd/log.py
Normal file
286
libs/iredapd/log.py
Normal file
@@ -0,0 +1,286 @@
|
||||
# Author: Zhang Huangbin <zhb@iredmail.org>
|
||||
|
||||
import time
|
||||
import web
|
||||
|
||||
import settings
|
||||
|
||||
from libs import iredutils
|
||||
from libs.logger import logger
|
||||
|
||||
|
||||
if settings.backend == 'ldap':
|
||||
from libs.ldaplib.admin import get_managed_domains
|
||||
else:
|
||||
from libs.sqllib.admin import get_managed_domains
|
||||
|
||||
session = web.config.get('_session')
|
||||
|
||||
|
||||
def __get_managed_domains():
|
||||
domains = []
|
||||
|
||||
kw = {'admin': session.get('username'),
|
||||
'domain_name_only': True,
|
||||
'conn': None}
|
||||
|
||||
if settings.backend != 'ldap':
|
||||
kw['listed_only'] = True
|
||||
|
||||
qr = get_managed_domains(**kw)
|
||||
|
||||
if qr[0]:
|
||||
domains = qr[1]
|
||||
|
||||
return domains
|
||||
|
||||
|
||||
def get_num_rejected(hours=None):
|
||||
"""Return amount of rejected mails in last given `hours`."""
|
||||
num = 0
|
||||
|
||||
if not hours:
|
||||
hours = 24
|
||||
|
||||
sql_vars = {
|
||||
"action": "REJECT",
|
||||
"time_num": (int(time.time()) - (hours * 3600)),
|
||||
}
|
||||
|
||||
sql_wheres = ["action = $action AND time_num >= $time_num"]
|
||||
|
||||
if not session.get('is_global_admin'):
|
||||
domains = __get_managed_domains()
|
||||
|
||||
if domains:
|
||||
sql_vars['domains'] = domains
|
||||
sql_wheres += ['(sender_domain IN $domains OR sasl_username IN $domains OR recipient_domain IN $domains)']
|
||||
else:
|
||||
return num
|
||||
|
||||
sql_where = ' AND '.join(sql_wheres)
|
||||
|
||||
try:
|
||||
qr = web.conn_iredapd.select(
|
||||
'smtp_sessions',
|
||||
vars=sql_vars,
|
||||
what="COUNT(id) AS total",
|
||||
where=sql_where,
|
||||
)
|
||||
if qr:
|
||||
num = qr[0]['total']
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
|
||||
return num
|
||||
|
||||
|
||||
def get_num_smtp_outbound_sessions(hours=None):
|
||||
"""Return amount of smtp authentications in last given `hours`."""
|
||||
num = 0
|
||||
|
||||
if not hours:
|
||||
hours = 24
|
||||
|
||||
sql_vars = {
|
||||
"time_num": (int(time.time()) - (hours * 3600)),
|
||||
}
|
||||
|
||||
sql_wheres = ["sasl_username <> '' AND time_num >= $time_num"]
|
||||
|
||||
if not session.get('is_global_admin'):
|
||||
domains = __get_managed_domains()
|
||||
|
||||
if domains:
|
||||
sql_vars['domains'] = domains
|
||||
sql_wheres += ['sasl_domain IN $domains']
|
||||
else:
|
||||
return num
|
||||
|
||||
sql_where = ' AND '.join(sql_wheres)
|
||||
|
||||
try:
|
||||
qr = web.conn_iredapd.select(
|
||||
'smtp_sessions',
|
||||
vars=sql_vars,
|
||||
what="COUNT(id) AS total",
|
||||
where=sql_where,
|
||||
)
|
||||
|
||||
if qr:
|
||||
num = qr[0]['total']
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
|
||||
return num
|
||||
|
||||
|
||||
def get_log_smtp_sessions(domains=None,
|
||||
sasl_usernames=None,
|
||||
senders=None,
|
||||
recipients=None,
|
||||
client_addresses=None,
|
||||
encryption_protocols=None,
|
||||
outbound_only=False,
|
||||
rejected_only=False,
|
||||
offset=None,
|
||||
limit=None):
|
||||
"""Return a dict with amount of smtp rejections and list of (SQL) rows."""
|
||||
result = {'total': 0, 'rows': []}
|
||||
|
||||
if not offset or not isinstance(offset, int):
|
||||
offset = 0
|
||||
|
||||
if not limit or not isinstance(limit, int):
|
||||
limit = settings.PAGE_SIZE_LIMIT
|
||||
|
||||
query_domains = []
|
||||
sql_vars = {}
|
||||
sql_wheres = []
|
||||
sql_where = None
|
||||
|
||||
if domains:
|
||||
query_domains = [str(i).lower() for i in domains if iredutils.is_domain(i)]
|
||||
|
||||
if session.get('is_global_admin'):
|
||||
if query_domains:
|
||||
sql_vars['domains'] = query_domains
|
||||
|
||||
if outbound_only:
|
||||
sql_wheres += ['sasl_domain IN $domains']
|
||||
else:
|
||||
sql_wheres += ['(sender_domain IN $domains OR sasl_domain IN $domains OR recipient_domain IN $domains)']
|
||||
else:
|
||||
if outbound_only:
|
||||
sql_wheres += ["sasl_username <> ''"]
|
||||
else:
|
||||
managed_domains = __get_managed_domains()
|
||||
if not managed_domains:
|
||||
return result
|
||||
|
||||
if domains:
|
||||
query_domains = [str(i).lower() for i in domains if i in managed_domains]
|
||||
|
||||
if not query_domains:
|
||||
return result
|
||||
else:
|
||||
query_domains = managed_domains
|
||||
|
||||
sql_vars['domains'] = query_domains
|
||||
if outbound_only:
|
||||
sql_wheres += ['sasl_domain in $domains']
|
||||
else:
|
||||
sql_wheres += ['(sender_domain IN $domains OR sasl_domain IN $domains OR recipient_domain IN $domains)']
|
||||
|
||||
if sasl_usernames:
|
||||
sql_vars['sasl_usernames'] = [str(i).lower() for i in sasl_usernames if iredutils.is_email(i)]
|
||||
sql_wheres += ['sasl_username IN $sasl_usernames']
|
||||
|
||||
if senders:
|
||||
sql_vars['senders'] = [str(i).lower() for i in senders if iredutils.is_email(i)]
|
||||
sql_wheres += ['sender IN $senders']
|
||||
|
||||
if recipients:
|
||||
sql_vars['recipients'] = [str(i).lower() for i in recipients if iredutils.is_email(i)]
|
||||
sql_wheres += ['recipient IN $recipients']
|
||||
|
||||
if client_addresses:
|
||||
sql_vars['client_addresses'] = [i for i in client_addresses if iredutils.is_strict_ip(i)]
|
||||
sql_wheres += ['client_address IN $client_addresses']
|
||||
|
||||
if encryption_protocols:
|
||||
sql_vars['encryption_protocols'] = encryption_protocols
|
||||
sql_wheres += ['encryption_protocol IN $encryption_protocols']
|
||||
|
||||
if rejected_only:
|
||||
sql_wheres += ["action='REJECT'"]
|
||||
|
||||
if sql_wheres:
|
||||
sql_where = ' AND '.join(sql_wheres)
|
||||
|
||||
try:
|
||||
qr = web.conn_iredapd.select(
|
||||
'smtp_sessions',
|
||||
vars=sql_vars,
|
||||
what='COUNT(id) AS total',
|
||||
where=sql_where,
|
||||
)
|
||||
if qr:
|
||||
result['total'] = qr[0].total
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
|
||||
columns = [
|
||||
'id', 'time', 'time_num',
|
||||
'action', 'reason', 'instance',
|
||||
'sasl_username', 'sender', 'recipient',
|
||||
'client_address', 'encryption_protocol',
|
||||
]
|
||||
|
||||
try:
|
||||
qr = web.conn_iredapd.select(
|
||||
'smtp_sessions',
|
||||
vars=sql_vars,
|
||||
what=','.join(columns),
|
||||
where=sql_where,
|
||||
order='time_num DESC',
|
||||
offset=offset,
|
||||
limit=limit,
|
||||
)
|
||||
|
||||
if qr:
|
||||
result['rows'] = list(qr)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def get_smtp_insecure_outbound(hours=None):
|
||||
"""
|
||||
Return info of insecure smtp outbound sessions in last given `hours`.
|
||||
|
||||
(True, {'total': '<int>', 'usernames': [<mail>, <mail>, ...]})
|
||||
(False, '<error>')
|
||||
"""
|
||||
result = {'total': 0, 'usernames': []}
|
||||
|
||||
if not isinstance(hours, int):
|
||||
hours = 24
|
||||
|
||||
sql_vars = {
|
||||
"time_num": (int(time.time()) - (hours * 3600)),
|
||||
}
|
||||
|
||||
sql_wheres = ["sasl_username <> '' AND encryption_protocol = '' AND time_num >= $time_num"]
|
||||
|
||||
if not session.get('is_global_admin'):
|
||||
domains = __get_managed_domains()
|
||||
|
||||
if domains:
|
||||
sql_vars['domains'] = domains
|
||||
sql_wheres += ['sasl_domain IN $domains']
|
||||
else:
|
||||
return True, result
|
||||
|
||||
sql_where = ' AND '.join(sql_wheres)
|
||||
|
||||
try:
|
||||
qr = web.conn_iredapd.select(
|
||||
'smtp_sessions',
|
||||
vars=sql_vars,
|
||||
what='sasl_username',
|
||||
where=sql_where,
|
||||
group='sasl_username',
|
||||
)
|
||||
|
||||
for row in qr:
|
||||
result['total'] += 1
|
||||
_email = str(row['sasl_username']).lower().strip()
|
||||
result['usernames'].append(_email)
|
||||
|
||||
result['usernames'].sort()
|
||||
return True, result
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
return False, repr(e)
|
||||
180
libs/iredapd/throttle.py
Normal file
180
libs/iredapd/throttle.py
Normal file
@@ -0,0 +1,180 @@
|
||||
# Author: Zhang Huangbin <zhb@iredmail.org>
|
||||
|
||||
import web
|
||||
from libs import iredutils
|
||||
|
||||
|
||||
def get_throttle_setting(account, inout_type='outbound'):
|
||||
"""Get throttle setting.
|
||||
|
||||
@account -- a valid throttling account
|
||||
@inout_type -- inbound, outbound
|
||||
"""
|
||||
setting = {}
|
||||
if not iredutils.is_valid_amavisd_address(account):
|
||||
return setting
|
||||
|
||||
qr = web.conn_iredapd.select(
|
||||
'throttle',
|
||||
vars={'account': account, 'inout_type': inout_type},
|
||||
where='kind=$inout_type AND account=$account',
|
||||
limit=1,
|
||||
)
|
||||
|
||||
if qr:
|
||||
setting = qr[0]
|
||||
|
||||
return setting
|
||||
|
||||
|
||||
def delete_throttle_setting(account, inout_type):
|
||||
if not iredutils.is_valid_amavisd_address(account):
|
||||
return False, 'INVALID_ACCOUNT'
|
||||
|
||||
if not (inout_type in ['inbound', 'outbound']):
|
||||
return False, 'INVALID_INOUT_TYPE'
|
||||
|
||||
if account and inout_type:
|
||||
web.conn_iredapd.delete(
|
||||
'throttle',
|
||||
vars={'account': account, 'inout_type': inout_type},
|
||||
where='account=$account AND kind=$inout_type',
|
||||
)
|
||||
|
||||
return True,
|
||||
|
||||
return True,
|
||||
|
||||
|
||||
def delete_throttle_tracking(account, inout_type):
|
||||
tid = get_throttle_id(account, inout_type)
|
||||
|
||||
if tid:
|
||||
try:
|
||||
web.conn_iredapd.delete(
|
||||
'throttle_tracking',
|
||||
vars={'tid': tid},
|
||||
where='tid=$tid',
|
||||
)
|
||||
except Exception as e:
|
||||
return False, repr(e)
|
||||
|
||||
return True,
|
||||
|
||||
|
||||
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:
|
||||
web.conn_iredapd.delete(
|
||||
'throttle',
|
||||
vars={'mails': mails},
|
||||
where="""account IN $mails""",
|
||||
)
|
||||
|
||||
web.conn_iredapd.delete(
|
||||
'throttle_tracking',
|
||||
vars={'mails': mails},
|
||||
where="""account 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(
|
||||
'throttle',
|
||||
vars={'domain': '@' + domain},
|
||||
where='account=$domain',
|
||||
)
|
||||
|
||||
# Delete settings for all users under this domain
|
||||
web.conn_iredapd.delete(
|
||||
'throttle',
|
||||
vars={'domain': '%@' + domain},
|
||||
where="""account LIKE $domain""")
|
||||
|
||||
web.conn_iredapd.delete(
|
||||
'throttle_tracking',
|
||||
vars={'domain': '%@' + domain},
|
||||
where="""account LIKE $domain""",
|
||||
)
|
||||
|
||||
return True,
|
||||
except Exception as e:
|
||||
return False, repr(e)
|
||||
|
||||
|
||||
def get_throttle_id(account, inout_type):
|
||||
tid = None
|
||||
|
||||
# get `throttle.id`
|
||||
qr = web.conn_iredapd.select(
|
||||
'throttle',
|
||||
vars={'account': account, 'inout_type': inout_type},
|
||||
where='account=$account AND kind=$inout_type',
|
||||
limit=1,
|
||||
)
|
||||
|
||||
if qr:
|
||||
tid = qr[0].id
|
||||
|
||||
return tid
|
||||
|
||||
|
||||
def add_throttle(account,
|
||||
setting,
|
||||
inout_type='inbound'):
|
||||
if not setting:
|
||||
# Delete tracking and setting
|
||||
delete_throttle_tracking(account=account, inout_type=inout_type)
|
||||
delete_throttle_setting(account=account, inout_type=inout_type)
|
||||
return True,
|
||||
|
||||
# Delete record if
|
||||
# - no period. (period == 0) means disabled
|
||||
# - account mismatch
|
||||
# - account is '@.' (global setting) and no valid setting (all are 0)
|
||||
# - account is not '@.' (not global setting) and no valid setting (all are -1)
|
||||
if (not setting.get('period', 0)) \
|
||||
or (account != setting.get('account')) \
|
||||
or (account == '@.'
|
||||
and (not setting.get('max_msgs'))
|
||||
and (not setting.get('msg_size'))
|
||||
and (not setting.get('max_quota'))
|
||||
and (not setting.get("max_rcpts"))) \
|
||||
or (account != '@.'
|
||||
and setting.get("max_msgs") == -1
|
||||
and setting.get("msg_size") == -1
|
||||
and setting.get("max_quota") == -1
|
||||
and setting.get("max_rcpts") in (None, -1)):
|
||||
delete_throttle_tracking(account=account, inout_type=inout_type)
|
||||
delete_throttle_setting(account=account, inout_type=inout_type)
|
||||
else:
|
||||
try:
|
||||
# Get `throttle.id` if there's a setting.
|
||||
tid = get_throttle_id(account=account, inout_type=inout_type)
|
||||
|
||||
if tid:
|
||||
# Update existing setting
|
||||
web.conn_iredapd.update(
|
||||
'throttle',
|
||||
vars={'tid': tid},
|
||||
where='id=$tid',
|
||||
**setting)
|
||||
else:
|
||||
# Add new throttle setting.
|
||||
web.conn_iredapd.insert('throttle', **setting)
|
||||
except Exception as e:
|
||||
return False, repr(e)
|
||||
|
||||
return True,
|
||||
28
libs/iredapd/utils.py
Normal file
28
libs/iredapd/utils.py
Normal file
@@ -0,0 +1,28 @@
|
||||
# Author: Zhang Huangbin <zhb@iredmail.org>
|
||||
|
||||
from libs import iredutils
|
||||
from libs.iredapd import throttle as iredapd_throttle
|
||||
from libs.iredapd import greylist as iredapd_greylist
|
||||
|
||||
|
||||
def delete_settings_for_removed_users(mails):
|
||||
try:
|
||||
iredapd_greylist.delete_settings_for_removed_users(mails=mails)
|
||||
iredapd_throttle.delete_settings_for_removed_users(mails=mails)
|
||||
|
||||
return True,
|
||||
except Exception as e:
|
||||
return False, repr(e)
|
||||
|
||||
|
||||
def delete_settings_for_removed_domains(domains):
|
||||
domains = [str(d).lower() for d in domains if iredutils.is_domain(d)]
|
||||
|
||||
if not domains:
|
||||
return True,
|
||||
|
||||
for d in domains:
|
||||
iredapd_throttle.delete_settings_for_removed_domain(domain=d)
|
||||
iredapd_greylist.delete_settings_for_removed_domain(domain=d)
|
||||
|
||||
return True,
|
||||
66
libs/iredapd/wblist_rdns.py
Normal file
66
libs/iredapd/wblist_rdns.py
Normal file
@@ -0,0 +1,66 @@
|
||||
# Author: Zhang Huangbin <zhb@iredmail.org>
|
||||
import web
|
||||
|
||||
|
||||
def get_wblist_rdns():
|
||||
"""Get wblist of rDNS."""
|
||||
whitelists = []
|
||||
blacklists = []
|
||||
|
||||
try:
|
||||
qr = web.conn_iredapd.select(
|
||||
'wblist_rdns',
|
||||
what='rdns,wb',
|
||||
order='rdns',
|
||||
)
|
||||
|
||||
for i in qr:
|
||||
_rdns = str(i.rdns).lower()
|
||||
if i.wb == 'W':
|
||||
whitelists.append(_rdns)
|
||||
elif i.wb == 'B':
|
||||
blacklists.append(_rdns)
|
||||
|
||||
return True, {'whitelists': whitelists, 'blacklists': blacklists}
|
||||
except Exception as e:
|
||||
return False, repr(e)
|
||||
|
||||
|
||||
def reset_wblist_rdns(whitelists=None, blacklists=None):
|
||||
"""Reset wblist rdns.
|
||||
|
||||
@whitelists -- a list/tuple/set of whitelist rdns domain names. Notes:
|
||||
- if it's None, no reset.
|
||||
- if it's empty list/tuple/set, all existing records will be
|
||||
removed.
|
||||
@blacklists -- a list/tuple/set of blacklist rdns domain names.
|
||||
@conn -- sql connection cursor
|
||||
"""
|
||||
if whitelists and blacklists:
|
||||
# Remove duplicate records
|
||||
blacklists = [i for i in blacklists if i not in whitelists]
|
||||
|
||||
# Delete first to avoid possible duplicate records while inserting new
|
||||
# records later.
|
||||
for (_lists, _wb) in [(whitelists, 'W'), (blacklists, 'B')]:
|
||||
if _lists is not None:
|
||||
try:
|
||||
# Delete all existing records first
|
||||
web.conn_iredapd.delete(
|
||||
'wblist_rdns',
|
||||
vars={'wb': _wb},
|
||||
where='WB=$wb',
|
||||
)
|
||||
except Exception as e:
|
||||
return False, repr(e)
|
||||
|
||||
# Insert new records
|
||||
for (_lists, _wb) in [(whitelists, 'W'), (blacklists, 'B')]:
|
||||
if _lists:
|
||||
for i in _lists:
|
||||
try:
|
||||
web.conn_iredapd.insert('wblist_rdns', rdns=i, wb=_wb)
|
||||
except:
|
||||
pass
|
||||
|
||||
return True, 'UPDATED'
|
||||
83
libs/iredapd/wblist_senderscore.py
Normal file
83
libs/iredapd/wblist_senderscore.py
Normal file
@@ -0,0 +1,83 @@
|
||||
import web
|
||||
|
||||
from libs import iredutils
|
||||
|
||||
# `4102444799` seconds since 1970-01-01 is '2099-12-31 23:59:59'.
|
||||
# It's a trick to use this time as whitelist and not cleaned by
|
||||
# script `tools/cleanup_db.py`.
|
||||
# It's ok to use any long epoch seconds to avoid cleanup, but we use this
|
||||
# hard-coded value for easier management.
|
||||
expire_epoch_seconds = 4102444799
|
||||
|
||||
|
||||
def get_whitelists():
|
||||
total = 0
|
||||
ips = []
|
||||
|
||||
try:
|
||||
qr = web.conn_iredapd.select(
|
||||
"senderscore_cache",
|
||||
vars={'seconds': expire_epoch_seconds},
|
||||
what='COUNT(client_address) AS total',
|
||||
where="time=$seconds",
|
||||
)
|
||||
total = qr[0].total
|
||||
|
||||
if total:
|
||||
qr = web.conn_iredapd.select(
|
||||
"senderscore_cache",
|
||||
vars={'seconds': expire_epoch_seconds},
|
||||
what='client_address',
|
||||
where="time=$seconds",
|
||||
)
|
||||
|
||||
ips = [i.client_address for i in qr]
|
||||
|
||||
return True, {'total': total, 'ips': ips}
|
||||
except Exception as e:
|
||||
return False, repr(e)
|
||||
|
||||
|
||||
def filter_whitelisted_ips(ips):
|
||||
# Return a list of whitelisted IP addresses of given ones.
|
||||
ips = [i for i in ips if iredutils.is_strict_ip(i)]
|
||||
|
||||
try:
|
||||
qr = web.conn_iredapd.select(
|
||||
"senderscore_cache",
|
||||
vars={'ips': ips, 'seconds': expire_epoch_seconds},
|
||||
what='client_address',
|
||||
where="client_address IN $ips AND time=$seconds",
|
||||
)
|
||||
|
||||
ips = [i.client_address for i in qr]
|
||||
return True, ips
|
||||
except Exception as e:
|
||||
return False, repr(e)
|
||||
|
||||
|
||||
def whitelist_ips(ips):
|
||||
# Whitelist given IP addresses.
|
||||
ips = [i for i in ips if iredutils.is_strict_ip(i)]
|
||||
|
||||
if not ips:
|
||||
return True,
|
||||
|
||||
# Remove existing records first.
|
||||
try:
|
||||
web.conn_iredapd.delete("senderscore_cache",
|
||||
vars={'ips': ips},
|
||||
where="client_address IN $ips")
|
||||
|
||||
rows = []
|
||||
for ip in ips:
|
||||
rows += [{'client_address': ip,
|
||||
'score': 100,
|
||||
'time': expire_epoch_seconds}]
|
||||
|
||||
# Insert whitelists.
|
||||
web.conn_iredapd.multiple_insert("senderscore_cache", rows)
|
||||
|
||||
return True,
|
||||
except Exception as e:
|
||||
return False, repr(e)
|
||||
Reference in New Issue
Block a user