Add files via upload

This commit is contained in:
Harold Finch
2023-04-10 07:18:32 +02:00
committed by GitHub
parent 06ddbf431f
commit 65875d8fef
100 changed files with 84692 additions and 42 deletions

56
libs/amavisd/__init__.py Normal file
View File

@@ -0,0 +1,56 @@
# Author: Zhang Huangbin <zhb@iredmail.org>
from libs import iredutils
# mail_id and secret_id are composed of below characters:
# - Amavisd-new-2.7+: [ A-Z, a-z, 0-9, -, _ ]
# - Amavisd-new-2.6.x: [ A-Z, a-z, 0-9, +, - ]
MAIL_ID_CHARACTERS = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+-_'
WBLIST_FORM_INPUT_NAMES = {'wl_sender': 'whitelistSender',
'bl_sender': 'blacklistSender',
'wl_rcpt': 'whitelistRecipient',
'bl_rcpt': 'blacklistRecipient'}
# Available quarantined types in iRedAdmin web interface, and the short code
# in `amavisd.msgs` sql table.
QUARANTINE_TYPES = {'spam': 'S',
'virus': 'V',
'banned': 'B',
'clean': 'C',
'badheader': 'H',
'badmime': 'M'}
# Value of `msgs.content` and comment.
CONTENT_TYPES = {'B': 'Banned',
'C': 'Clean',
'H': 'Bad header',
'M': 'Bad mime',
'O': 'Oversized',
'S': 'Spam',
'T': 'MTA error',
'V': 'Virus',
'U': 'Unchecked'}
def get_wblist_from_form(form, form_input_name):
# Available form_input_name are listed in WBLIST_FORM_INPUT_NAMES
input_name = WBLIST_FORM_INPUT_NAMES[form_input_name]
addresses = []
for _line in form.get(input_name, '').splitlines():
if _line:
try:
_line = str(_line)
addresses.append(_line)
except:
pass
valid_addresses = []
for addr in addresses:
if iredutils.is_valid_wblist_address(addr) and (addr not in valid_addresses):
valid_addresses.append(addr)
else:
continue
return valid_addresses

572
libs/amavisd/log.py Normal file
View File

@@ -0,0 +1,572 @@
# Author: Zhang Huangbin <zhb@iredmail.org>
import time
import web
import settings
from libs import iredutils
from libs.logger import logger, log_traceback
from libs.amavisd import MAIL_ID_CHARACTERS
session = web.config.get('_session')
# Import backend related modules.
if settings.backend == 'ldap':
from libs.ldaplib import admin as ldap_lib_admin
elif settings.backend in ['mysql', 'pgsql']:
from libs.sqllib import admin as sql_lib_admin
def delete_all_records(log_type=None, account=None):
# Delete all records, or delete records older than one week.
# :param log_type: sent, received
# :param account: single email address, domain name, '@.'
account_is_email = False
account_is_domain = False
maddr_ids = []
managed_domains_reversed = []
if account:
if iredutils.is_email(account):
account_is_email = True
elif iredutils.is_domain(account):
account_is_domain = True
else:
if account == '@.':
pass
else:
return False, 'INVALID_ACCOUNT'
# get `maddr.id` of this account
if account_is_email:
# user
qr = web.conn_amavisd.select('maddr',
vars={'account': account},
what='id',
where='email=$account',
limit=1)
if qr:
maddr_ids.append(qr[0].id)
elif account_is_domain:
# domain
reversed_domain = iredutils.reverse_amavisd_domain_names([account])[0]
qr = web.conn_amavisd.select('maddr',
vars={'account': reversed_domain},
what='id',
where='domain=$account')
if qr:
for r in qr:
maddr_ids.append(r.id)
# no `maddr.id`, no mail log.
if not maddr_ids:
return True,
else:
if session.get('is_global_admin'):
web.conn_amavisd.delete('msgs', where='1=1')
web.conn_amavisd.delete('msgrcpt', where='1=1')
return True,
# Get all managed domains by normal admin.
managed_domains = []
if settings.backend == 'ldap':
_qr = ldap_lib_admin.get_managed_domains(admin=session.get('username'))
if _qr[0]:
managed_domains = _qr[1]
elif settings.backend in ['mysql', 'pgsql']:
qr = sql_lib_admin.get_managed_domains(admin=session.get('username'),
domain_name_only=True)
if qr[0]:
managed_domains = qr[1]
else:
return False, 'UNKNOWN_BACKEND'
managed_domains_reversed = iredutils.reverse_amavisd_domain_names(managed_domains)
if not managed_domains_reversed:
return True,
try:
# Delete records in tables: msgs, msgrcpt.
if log_type == 'sent':
if account:
# Delete all records sent by single user
web.conn_amavisd.delete('msgs',
vars={'maddr_ids': maddr_ids},
where='sid IN $maddr_ids')
else:
# Delete all records sent by domain users
web.conn_amavisd.delete('msgs',
vars={'managed_domains_reversed': managed_domains_reversed},
where='sid IN (SELECT id FROM maddr WHERE domain IN $managed_domains_reversed)')
elif log_type == 'received':
if account:
web.conn_amavisd.delete('msgs',
vars={'maddr_ids': maddr_ids},
where='mail_id IN (SELECT mail_id FROM msgrcpt WHERE rid IN $maddr_ids)')
web.conn_amavisd.delete('msgrcpt',
vars={'maddr_ids': maddr_ids},
where='rid IN $maddr_ids')
else:
all_rcpt_ids = [] # maddr.id
qr = web.conn_amavisd.select('maddr',
vars={'domains': managed_domains_reversed},
what='id',
where='domain IN $domains')
for i in qr:
all_rcpt_ids.append(i['id'])
del qr
web.conn_amavisd.delete('msgs',
vars={'ids': all_rcpt_ids},
where='mail_id IN (SELECT mail_id FROM msgrcpt WHERE rid IN $ids)')
web.conn_amavisd.delete('msgrcpt',
vars={'ids': all_rcpt_ids},
where='rid IN $ids')
del all_rcpt_ids
return True,
except Exception as e:
return False, repr(e)
def delete_records_by_mail_id(log_type='sent', mail_ids=None):
# log_type -- received, sent, quarantined, quarantine
if not isinstance(mail_ids, list):
return False, 'INCORRECT_MAILID'
# Filter unexpected mail_id strings.
mail_ids = [v for v in mail_ids if len(set(v) - set(MAIL_ID_CHARACTERS)) == 0]
if not mail_ids:
return True,
# Converted into SQL style list.
mail_ids = web.sqlquote(mail_ids)
if log_type in ['received', 'sent', 'quarantined', 'quarantine']:
try:
# Delete records in tables: msgs, msgrcpt.
web.conn_amavisd.delete('msgs', where='mail_id IN %s' % mail_ids)
web.conn_amavisd.delete('msgrcpt', where='mail_id IN %s' % mail_ids)
except Exception as e:
return False, repr(e)
if log_type in ['quarantined', 'quarantine']:
try:
web.conn_amavisd.delete('quarantine', where="mail_id IN %s" % mail_ids)
except Exception as e:
return False, repr(e)
return True,
def count_incoming_mails(reversedDomainNames=None,
timeLength=None,
sqlAppendWhere=None):
# timeLength is seconds.
total = 0
if not reversedDomainNames:
if not session.get('account_is_mail_user'):
return total
if sqlAppendWhere:
sql_append_where = sqlAppendWhere
else:
sql_append_where = ' AND recip.domain IN %s' % web.sqlquote(reversedDomainNames)
if isinstance(timeLength, int):
_now = int(time.time())
_length_seconds = _now - timeLength
sql_append_where += ' AND msgs.time_num > %d' % _length_seconds
try:
qr = web.conn_amavisd.query('''
-- Get number of incoming mails.
SELECT COUNT(msgs.mail_id) AS total
FROM msgs
LEFT JOIN msgrcpt ON (msgs.mail_id = msgrcpt.mail_id)
LEFT JOIN maddr AS sender ON (msgs.sid = sender.id)
LEFT JOIN maddr AS recip ON (msgrcpt.rid = recip.id)
WHERE msgs.quar_type <> 'Q' %s
''' % sql_append_where)
total = qr[0].total or 0
except Exception as e:
logger.error(e)
return total
def count_outgoing_mails(reversedDomainNames=None,
timeLength=None,
sqlAppendWhere=None):
# timeLength is seconds.
total = 0
sql_append_where = ''
if not reversedDomainNames:
return total
if sqlAppendWhere:
sql_append_where = sqlAppendWhere
else:
sql_append_where += ' AND sender.domain IN %s' % web.sqlquote(reversedDomainNames)
if isinstance(timeLength, int):
_now = int(time.time())
_length_seconds = _now - timeLength
sql_append_where += ' AND msgs.time_num > %d' % _length_seconds
try:
qr_count = web.conn_amavisd.query("""
-- Get number of outgoing mails.
SELECT COUNT(msgs.mail_id) AS total
FROM msgs
RIGHT JOIN msgrcpt ON (msgs.mail_id = msgrcpt.mail_id)
RIGHT JOIN maddr AS sender ON (msgs.sid = sender.id)
RIGHT JOIN maddr AS recip ON (msgrcpt.rid = recip.id)
WHERE msgs.quar_type <> 'Q' %s""" % sql_append_where)
total = qr_count[0].total or 0
except Exception:
pass
return total
def count_virus_mails(reversedDomainNames=None, timeLength=None):
# timeLength is seconds.
total = 0
sql_append_where = ''
if not reversedDomainNames:
return total
if session.get('is_global_admin') is not True:
sql_append_where += ' AND (sender.domain IN {} OR recip.domain IN {})'.format(
web.sqlquote(reversedDomainNames),
web.sqlquote(reversedDomainNames),
)
if isinstance(timeLength, int):
_now = int(time.time())
_length_seconds = _now - timeLength
sql_append_where += ' AND msgs.time_num > %d' % _length_seconds
try:
qr = web.conn_amavisd.query("""
SELECT COUNT(msgs.mail_id) AS total
FROM msgs
RIGHT JOIN msgrcpt ON (msgs.mail_id = msgrcpt.mail_id)
RIGHT JOIN maddr AS sender ON (msgs.sid = sender.id)
RIGHT JOIN maddr AS recip ON (msgrcpt.rid = recip.id)
WHERE msgs.content = 'V'
AND msgs.quar_type='Q'
%s
""" % sql_append_where)
total = qr[0].total or 0
except Exception:
pass
return total
def count_quarantined(reversedDomainNames=None, timeLength=None):
# timeLength is seconds.
total = 0
sql_append_where = ''
if not session.get('is_global_admin'):
sql_append_where += ' AND (sender.domain IN {} OR recip.domain IN {})'.format(
web.sqlquote(reversedDomainNames),
web.sqlquote(reversedDomainNames),
)
if isinstance(timeLength, int):
_now = int(time.time())
_length_seconds = _now - timeLength
sql_append_where += ' AND msgs.time_num > %d' % _length_seconds
try:
if session.get('is_global_admin'):
qr = web.conn_amavisd.query("""
SELECT COUNT(msgs.mail_id) AS total
FROM msgs
RIGHT JOIN maddr AS sender ON (msgs.sid = sender.id)
WHERE msgs.quar_type = 'Q' %s
""" % sql_append_where)
else:
qr = web.conn_amavisd.query("""
SELECT COUNT(msgs.mail_id) AS total
FROM msgs
RIGHT JOIN msgrcpt ON (msgs.mail_id = msgrcpt.mail_id)
RIGHT JOIN maddr AS sender ON (msgs.sid = sender.id)
RIGHT JOIN maddr AS recip ON (msgrcpt.rid = recip.id)
WHERE msgs.quar_type = 'Q' %s
""" % sql_append_where)
total = qr[0].total or 0
except:
log_traceback()
return total
def get_in_out_mails(log_type='sent',
cur_page=1,
account_type='',
account='',
page_size_limit=None):
"""
@account_type: 'domain', 'user', None
@log_type: 'sent', 'received', 'all'
@return (True, {'count': <int>, 'records': <list>}
"""
log_type = str(log_type)
cur_page = int(cur_page)
account_type = str(account_type) or None
account = str(account) or None
result = {'count': 0, 'records': []}
count = 0 # Number of total mails.
records = {} # Detail records.
sql_append_where = ''
reversed_account = ''
if not page_size_limit:
page_size_limit = settings.PAGE_SIZE_LIMIT
if account_type == 'domain':
reversed_account = iredutils.reverse_amavisd_domain_names([account])
# Get all managed domain names and reversed names.
all_domains = []
allReversedDomainNames = []
quoted_all_reversed_domain_names = []
sql_restricted_sender_domains = ''
sql_restricted_recip_domains = ''
if not session.get('account_is_mail_user'):
if settings.backend == 'ldap':
_qr = ldap_lib_admin.get_managed_domains(admin=session.get('username'))
if _qr[0]:
all_domains = _qr[1]
elif settings.backend in ['mysql', 'pgsql']:
qr_all_domains = sql_lib_admin.get_managed_domains(admin=session.get('username'),
domain_name_only=True)
if qr_all_domains[0]:
all_domains += qr_all_domains[1]
else:
result['count'] = count
result['records'] = list(records)
return True, result
allReversedDomainNames = iredutils.reverse_amavisd_domain_names(all_domains)
quoted_all_reversed_domain_names = web.sqlquote(allReversedDomainNames)
sql_restricted_sender_domains = ' AND sender.domain IN %s' % quoted_all_reversed_domain_names
sql_restricted_recip_domains = ' AND recip.domain IN %s' % quoted_all_reversed_domain_names
# restrict permission for per-account search
# @log_type == 'sent'
# - if domain is under control, no restriction
# - if domain is not under control, restrict recipient domain to managed domains
# @log_type == 'received'
# - if domain is under control, no restriction
# - if domain is not under control, restrict sender domain to managed domains
verify_domain = account
if account_type == 'user':
verify_domain = account.split('@', 1)[-1]
if log_type == 'received':
if account_type == 'domain':
if session.get('is_global_admin') or verify_domain in all_domains:
sql_append_where += ' AND recip.domain IN %s' % web.sqlquote(reversed_account)
else:
sql_append_where += ' {} AND recip.domain IN {}'.format(sql_restricted_sender_domains, web.sqlquote(reversed_account))
elif account_type == 'user':
if session.get('is_global_admin') or verify_domain in all_domains:
sql_append_where += ' AND recip.email=%s' % web.sqlquote(account)
else:
sql_append_where += ' {} AND recip.email={}'.format(sql_restricted_sender_domains, web.sqlquote(account))
else:
if settings.AMAVISD_SHOW_NON_LOCAL_DOMAINS:
if session.get('is_global_admin'):
pass
else:
if not quoted_all_reversed_domain_names:
return True, result
else:
sql_append_where += ' AND recip.domain IN %s' % quoted_all_reversed_domain_names
else:
if not quoted_all_reversed_domain_names:
return True, result
else:
sql_append_where += ' AND recip.domain IN %s' % quoted_all_reversed_domain_names
elif log_type == 'sent':
if account_type == 'domain':
if session.get('is_global_admin') or verify_domain in all_domains:
sql_append_where += ' AND sender.domain IN %s' % web.sqlquote(reversed_account)
else:
sql_append_where += ' {} AND sender.domain IN {}'.format(sql_restricted_recip_domains, web.sqlquote(reversed_account))
elif account_type == 'user':
if session.get('is_global_admin') or verify_domain in all_domains:
sql_append_where += ' AND sender.email = %s' % (web.sqlquote(account))
else:
sql_append_where += ' {} AND sender.email = {}'.format(sql_restricted_recip_domains, web.sqlquote(account))
else:
if settings.AMAVISD_SHOW_NON_LOCAL_DOMAINS:
if session.get('is_global_admin'):
pass
else:
if not quoted_all_reversed_domain_names:
return True, result
else:
sql_append_where += ' AND sender.domain IN %s' % quoted_all_reversed_domain_names
else:
if not quoted_all_reversed_domain_names:
return True, result
else:
sql_append_where += ' AND sender.domain IN %s' % quoted_all_reversed_domain_names
########################
# Get detail records.
#
try:
if log_type == 'received':
count = count_incoming_mails(allReversedDomainNames,
sqlAppendWhere=sql_append_where)
qr = web.conn_amavisd.query(
'''
-- Get records of received mails.
SELECT
msgs.mail_id, msgs.subject, msgs.time_num,
msgs.size, msgs.spam_level, msgs.client_addr, msgs.policy,
sender.email_raw AS sender_email,
recip.email_raw AS recipient
FROM msgs
LEFT JOIN msgrcpt ON (msgs.mail_id = msgrcpt.mail_id)
LEFT JOIN maddr AS sender ON (msgs.sid = sender.id)
LEFT JOIN maddr AS recip ON (msgrcpt.rid = recip.id)
WHERE msgs.quar_type <> 'Q' %s
ORDER BY msgs.time_num DESC
LIMIT %d
OFFSET %d
''' % (sql_append_where,
page_size_limit,
(cur_page - 1) * page_size_limit)
)
records = iredutils.bytes2str(qr)
elif log_type == 'sent':
count = count_outgoing_mails(allReversedDomainNames,
sqlAppendWhere=sql_append_where)
qr = web.conn_amavisd.query(
'''
-- Get records of sent mails.
SELECT
msgs.mail_id, msgs.subject, msgs.time_num,
msgs.size, msgs.client_addr, msgs.policy,
sender.email_raw AS sender_email,
recip.email_raw AS recipient
FROM msgs
RIGHT JOIN msgrcpt ON (msgs.mail_id = msgrcpt.mail_id)
RIGHT JOIN maddr AS sender ON (msgs.sid = sender.id)
RIGHT JOIN maddr AS recip ON (msgrcpt.rid = recip.id)
WHERE msgs.quar_type <> 'Q' %s
ORDER BY msgs.time_num DESC
LIMIT %d
OFFSET %d
''' % (sql_append_where,
page_size_limit,
(cur_page - 1) * page_size_limit)
)
records = iredutils.bytes2str(qr)
else:
records = {}
except:
pass
return True, {'count': count, 'records': list(records)}
def get_top_users(reversedDomainNames=None,
log_type='sent',
timeLength=None,
number=10):
records = {}
sql_append_where = ''
if settings.AMAVISD_SHOW_NON_LOCAL_DOMAINS:
if session.get('is_global_admin'):
pass
else:
if not reversedDomainNames:
return []
else:
if log_type == 'sent':
sql_append_where += ' AND sender.domain IN %s' % web.sqlquote(reversedDomainNames)
elif log_type == 'received':
sql_append_where += ' AND rcpt.domain IN %s' % web.sqlquote(reversedDomainNames)
else:
if log_type == 'sent':
sql_append_where += ' AND sender.domain IN %s' % web.sqlquote(reversedDomainNames)
elif log_type == 'received':
sql_append_where += ' AND rcpt.domain IN %s' % web.sqlquote(reversedDomainNames)
if isinstance(timeLength, int):
_now = int(time.time())
_length_seconds = _now - timeLength
sql_append_where += ' AND msgs.time_num > %d' % _length_seconds
# `msgs.policy` (Amavisd policy bank) is used to identify account type.
# for example, 'MLMMJ' means mlmmj mailing list.
if log_type == 'sent':
try:
result = web.conn_amavisd.query(
"""
-- Get top 10 senders.
SELECT COUNT(msgs.mail_id) AS total,
sender.email_raw AS mail,
msgs.policy AS policy
FROM msgs
RIGHT JOIN maddr AS sender ON (msgs.sid = sender.id)
WHERE 1=1 %s
GROUP BY mail, policy
ORDER BY total DESC
LIMIT %d
""" % (sql_append_where, number))
records = list(result)
except:
log_traceback()
elif log_type == 'received':
try:
result = web.conn_amavisd.query(
"""
-- Get top 10 recipients
SELECT COUNT(msgs.mail_id) AS total,
rcpt.email_raw AS mail
FROM msgs
RIGHT JOIN msgrcpt ON (msgs.mail_id = msgrcpt.mail_id)
RIGHT JOIN maddr AS sender ON (msgs.sid = sender.id)
RIGHT JOIN maddr AS rcpt ON (msgrcpt.rid = rcpt.id)
WHERE 1=1 %s
GROUP BY mail
ORDER BY total DESC
LIMIT %d
""" % (sql_append_where, number))
records = list(result)
except:
log_traceback()
records = iredutils.bytes2str(records)
return list(records)

315
libs/amavisd/quarantine.py Normal file
View File

@@ -0,0 +1,315 @@
# Author: Zhang Huangbin <zhb@iredmail.org>
import socket
import web
import settings
from libs import iredutils
from libs.amavisd import QUARANTINE_TYPES
session = web.config.get("_session")
# Import backend related modules.
if settings.backend == "ldap":
from libs.ldaplib.admin import get_managed_domains
elif settings.backend in ["mysql", "pgsql"]:
from libs.sqllib.admin import get_managed_domains
def get_raw_message(mail_id: str) -> bytes:
"""Get raw mail message of quarantined email specified by `mail_id`."""
# TODO Check domain access by sender/recipient of quarantined email
if not mail_id:
return False, "INVALID_MAILID"
try:
records = web.conn_amavisd.select(
"quarantine",
vars={"mail_id": mail_id},
what="mail_text",
where="mail_id=$mail_id",
order="chunk_ind ASC",
)
if not records:
return False, "INVALID_MAILID"
# Combine mail_text as RAW mail message.
# Note: `mail_text` is bytes type.
message = b""
records = list(records)
for i in records:
message += i['mail_text']
return True, message
except Exception as e:
return False, repr(e)
# If msgs.quar_type != "Q" (SQL), we can't get mail body.
def get_quarantined_mails(page=1,
account_type=None,
account="",
quarantined_type="",
size_limit=settings.PAGE_SIZE_LIMIT,
sort_by_score=False):
"""Return ([True | False], (total, records))"""
page = int(page)
account = str(account) or None
# Pre-defined values.
count = 0
records = []
sql_append_selection = ''
# Domain names under control.
all_domains = []
# Query SQL.
if session.get('is_normal_admin'):
# List all managed domains in query if admin is not global admin
qr = get_managed_domains(admin=session.get('username'), domain_name_only=True)
if qr[0]:
all_domains = qr[1]
all_reversed_domains = iredutils.reverse_amavisd_domain_names(all_domains)
if all_domains:
sql_append_selection += ' AND (sender.domain IN {} OR recip.domain IN {})'.format(
web.sqlquote(all_reversed_domains),
web.sqlquote(all_reversed_domains),
)
else:
return True, (0, {})
if account_type == 'domain':
if account:
reversed_account = iredutils.reverse_amavisd_domain_names([account])[0]
if not session.get('is_global_admin'):
# Make sure account is managed domain
if account not in all_domains:
# PERMISSION_DENIED
return True, (0, {})
sql_append_selection += ' AND (sender.domain={} OR recip.domain={})'.format(
web.sqlquote(reversed_account), web.sqlquote(reversed_account),
)
elif account_type == 'user':
if session.get('is_normal_admin'):
# Make sure account is under managed domains
if not account.split('@', 1)[-1] in all_domains:
# PERMISSION_DENIED
return True, (0, {})
elif session.get('account_is_mail_user'):
if account != session['username']:
return True, (0, {})
sql_append_selection += ' AND (sender.email={} OR recip.email={})'.format(
web.sqlquote(account),
web.sqlquote(account),
)
if quarantined_type == 'spam':
sql_append_selection += " AND msgs.content IN ('S', 's', 'Y')"
elif quarantined_type == 'virus':
sql_append_selection += " AND msgs.content = 'V'"
elif quarantined_type == 'banned':
sql_append_selection += " AND msgs.content = 'B'"
elif quarantined_type == 'badheader':
sql_append_selection += " AND msgs.content = 'H'"
elif quarantined_type == 'badmime':
sql_append_selection += " AND msgs.content = 'M'"
# Get number of total records. SQL table: amavisd.msgs
try:
# Refer to templates/default/macros/amavisd.html for more detail
# about msgs.content (content type, spam status), msgs.quar_type
# (quarantine type).
result = web.conn_amavisd.query(
"""
-- Get number of quarantined emails
SELECT COUNT(msgs.mail_id) AS total
FROM msgs
LEFT JOIN msgrcpt ON msgs.mail_id = msgrcpt.mail_id
LEFT JOIN maddr AS sender ON msgs.sid = sender.id
LEFT JOIN maddr AS recip ON msgrcpt.rid = recip.id
WHERE
-- msgs.content IN ('S', 's', 'Y', 'V', 'B', 'H')
-- AND msgs.quar_type = 'Q'
msgs.quar_type = 'Q'
%s
""" % sql_append_selection)
count = result[0].total or 0
except:
pass
# Get records of quarantined mails.
try:
# msgs.content:
# - S: spam(kill)
# - s: prior to 2.7.0 the CC_SPAMMY was logged as 's', now 'Y' is used.
# msgs.quar_type:
# - Q: sql
# - F: file
sort_column = 'msgs.time_num'
if sort_by_score:
sort_column = 'msgs.spam_level'
result = web.conn_amavisd.query(
'''
-- Get records of quarantined mails.
SELECT
msgs.mail_id, msgs.secret_id, msgs.subject, msgs.time_num,
msgs.content, msgs.size, msgs.spam_level,
sender.email AS sender_email,
recip.email AS recipient
FROM msgs
LEFT JOIN msgrcpt ON msgs.mail_id = msgrcpt.mail_id
LEFT JOIN maddr AS sender ON msgs.sid = sender.id
LEFT JOIN maddr AS recip ON msgrcpt.rid = recip.id
WHERE
-- msgs.content IN ('S', 's', 'Y', 'V', 'B', 'H')
-- AND msgs.quar_type = 'Q'
msgs.quar_type = 'Q'
%s
ORDER BY %s DESC
LIMIT %d
OFFSET %d
''' % (sql_append_selection, sort_column, size_limit, (page - 1) * size_limit)
)
records = iredutils.bytes2str(result)
except:
pass
return True, (count, records)
def delete_all_quarantined(quarantined_type=None):
if quarantined_type in QUARANTINE_TYPES:
_content = QUARANTINE_TYPES[quarantined_type]
# Delete them from `msgs`.
# Records in `quarantine` will be cleaned up by cron job
try:
web.conn_amavisd.delete(
'msgs',
vars={'quar_type': 'Q', 'content': _content},
where='quar_type=$quar_type AND content=$content',
)
return True,
except Exception as e:
return False, repr(e)
else:
try:
web.conn_amavisd.delete('quarantine', where='1=1')
web.conn_amavisd.delete('msgs', where="""quar_type='Q'""")
return True,
except Exception as e:
return False, repr(e)
def release_quarantined_mails(records=None):
# Release quarantined mails.
#
# records = [
# {'mail_id': 'xxx',
# 'secret_id': 'yyy',
# 'requested_by': session.get('username'),
# },
# [],
# ]
#
# Refer to amavisd doc 'README.protocol' for more detail:
# - Releasing a message from a quarantine
if not records:
return True,
# TODO Check domain_access
# - Get managed domains.
# - Check whether mail_id in `records` are one of managed domains.
# - Get allowed mail_id list.
# Pre-defined variables.
released_mail_ids = []
# Create socket.
try:
quar_server = settings.amavisd_db_host
quar_port = int(settings.amavisd_quarantine_port)
if settings.AMAVISD_QUARANTINE_HOST:
quar_server = settings.AMAVISD_QUARANTINE_HOST
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((quar_server, quar_port))
except Exception as e:
return False, repr(e)
# Generate commands from dict, used for socket communication.
# Note: We need to update Amavisd SQL database after mail was released
# with success, so do NOT send all release requests in ONE socket
# command although it will get better performance (a little).
for record in records:
# Skip record without 'mail_id'.
if 'mail_id' not in record:
continue
cmd_release = 'request=release\r\n'
for k in record:
if record[k] is not None and record[k] != '':
cmd_release += '{}={}\r\n'.format(k, record[k])
cmd_release += 'quar_type=Q\r\n\r\n'
try:
s.send(cmd_release.encode())
# Must wait for Amavisd's response before deleting SQL record,
# otherwise we may delete sql record BEFORE Amavisd releases
# quarantined email.
s.recv(1024)
released_mail_ids += [record.get('mail_id', 'NOT-EXIST')]
except Exception as e:
return False, repr(e)
# Close socket.
try:
s.close()
except Exception as e:
return False, repr(e)
# Return if no record was released successfully.
if len(released_mail_ids) == 0:
return True,
# Update Amavisd SQL database.
try:
# - Update msgs.content to 'C' (Clean)
# UPDATE msgs \
# SET msgs.content = 'C' \
# WHERE msgs.mail_id IN ('xxx', 'yyy', ..)
#
# - Delete records in 'quarantine':
# DELETE FROM quarantine \
# WHERE quarantine.partition_tag = msgs.partition_tag \
# AND quarantine.mail_id = msgs.mail_id
#
web.conn_amavisd.update(
'msgs',
where='mail_id IN ' + web.sqlquote(released_mail_ids),
quar_type='',
content='C',
)
web.conn_amavisd.delete(
'quarantine',
where='mail_id IN ' + web.sqlquote(released_mail_ids),
)
return True,
except Exception as e:
return False, repr(e)

350
libs/amavisd/spampolicy.py Normal file
View File

@@ -0,0 +1,350 @@
# Author: Zhang Huangbin <zhb@iredmail.org>
import web
import settings
from libs import form_utils
from libs.iredutils import is_valid_amavisd_address
from libs.amavisd import utils
session = web.config.get('_session')
DEFAULT_SPAM_TAG_LEVEL = 2
DEFAULT_SPAM_TAG2_LEVEL = 6
# Builtin ban rule names.
BUILTIN_BAN_RULE_NAMES = [
"ALLOW_MS_OFFICE",
"ALLOW_MS_WORD",
"ALLOW_MS_EXCEL",
"ALLOW_MS_PPT",
]
def delete_spam_policy(account):
account = str(account).lower()
if not is_valid_amavisd_address(account):
return False, 'INVALID_ACCOUNT'
try:
web.conn_amavisd.delete('policy',
vars={'account': account},
where='policy_name=$account')
return True,
except Exception as e:
return False, repr(e)
def get_spam_policy(account='@.'):
account = str(account).lower()
if not is_valid_amavisd_address(account):
return False, 'INVALID_ACCOUNT'
try:
sql_where = 'users.policy_id=policy.id AND users.email=$account'
qr = web.conn_amavisd.select(
['policy', 'users'],
vars={'account': account},
what='policy.*, users.id AS users_id',
where=sql_where,
limit=1,
)
if qr:
policy = qr[0]
return True, policy
else:
return True, {}
except Exception as e:
return False, repr(e)
def get_global_spam_score():
score = DEFAULT_SPAM_TAG2_LEVEL
(success, policy) = get_spam_policy(account='@.')
if success and policy:
score = policy.get('spam_tag2_level', DEFAULT_SPAM_TAG2_LEVEL)
return score
def update_spam_policy(account, form):
account = str(account).lower()
if not is_valid_amavisd_address(account):
return False, 'INVALID_ACCOUNT'
if 'delete_policy' in form:
try:
web.conn_amavisd.delete(
'policy',
vars={'account': account},
where='policy_name=$account',
)
return True,
except Exception as e:
return False, repr(e)
qr = utils.get_policy_record(account=account, create_if_missing=True)
if qr[0]:
policy_id = qr[1].id
else:
return qr
# Update spam policy
updates = {
'spam_lover': 'N',
'virus_lover': 'N',
'banned_files_lover': 'N',
'bad_header_lover': 'N',
'bypass_spam_checks': 'N',
'bypass_virus_checks': 'N',
'bypass_banned_checks': 'N',
'bypass_header_checks': 'N',
'banned_rulenames': "",
}
if 'enable_spam_checks' not in form:
updates['bypass_spam_checks'] = 'Y'
if 'enable_virus_checks' not in form:
updates['bypass_virus_checks'] = 'Y'
if 'enable_banned_checks' not in form:
updates['bypass_banned_checks'] = 'Y'
if 'enable_header_checks' not in form:
updates['bypass_header_checks'] = 'Y'
updates['spam_quarantine_to'] = ''
updates['virus_quarantine_to'] = 'virus-quarantine'
updates['banned_quarantine_to'] = ''
updates['bad_header_quarantine_to'] = ''
if 'spam_quarantine_to' in form:
updates['spam_quarantine_to'] = 'spam-quarantine'
# else:
# updates['spam_lover'] = 'Y'
if 'virus_quarantine_to' not in form:
# Deliver virus to mailbox.
updates['virus_lover'] = 'Y'
updates['virus_quarantine_to'] = ''
if 'banned_quarantine_to' in form:
updates['banned_quarantine_to'] = 'banned-quarantine'
# else:
# updates['banned_files_lover'] = 'Y'
if 'bad_header_quarantine_to' in form:
updates['bad_header_quarantine_to'] = 'bad-header-quarantine'
else:
updates['bad_header_lover'] = 'Y'
# Modify spam subject
if 'modify_spam_subject' in form:
updates['spam_subject_tag2'] = settings.AMAVISD_SPAM_SUBJECT_PREFIX
else:
updates['spam_subject_tag2'] = None
updates['spam_tag_level'] = None
updates['spam_tag2_level'] = None
updates['spam_kill_level'] = None
if account == '@.' and 'always_insert_x_spam_headers' in form:
updates['spam_tag_level'] = -100
for p in ['spam_tag2_level', 'spam_kill_level']:
_score = form.get(p, '')
if _score:
try:
updates[p] = float(_score)
except:
pass
if "banned_rulenames" in form:
names = form.get("banned_rulenames", [])
new_names = set()
for n in names:
if (n in BUILTIN_BAN_RULE_NAMES) or (n in settings.AMAVISD_BAN_RULES):
new_names.add(n)
# Sort the result for easier unittest.
new_names = sorted(new_names)
updates["banned_rulenames"] = ",".join(new_names)
try:
web.conn_amavisd.update(
'policy',
vars={'id': policy_id},
where='id=$id',
**updates)
qr = utils.link_policy_to_user(account=account, policy_id=policy_id)
if not qr[0]:
return qr
# Update `policy.spam_tag3_level` and `policy.spam_subject_tag3`
# separately, these two columns don't exist in Amavisd-new-2.6.x.
try:
extra_updates = {'spam_tag3_level': updates['spam_tag2_level'],
'spam_subject_tag3': updates['spam_subject_tag2']}
web.conn_amavisd.update(
'policy',
vars={'id': policy_id},
where='id=$id',
**extra_updates)
except:
pass
return True,
except Exception as e:
return False, repr(e)
def api_update_spam_policy(account, form):
"""Create new spam policy or update existing policy."""
account = str(account).lower()
if not is_valid_amavisd_address(account):
return False, 'INVALID_ACCOUNT'
# Get current `amavisd.policy.id`, it will create a new one if not present.
qr = utils.get_policy_record(account=account, create_if_missing=True)
if not qr[0]:
return qr
# Set default policy
policy = {
'policy_name': account,
# Default check policy: don't bypass checks
'bypass_spam_checks': 'N',
'bypass_virus_checks': 'N',
'bypass_banned_checks': 'N',
'bypass_header_checks': 'N',
# Default quarantining policy: quarantine virus
'spam_quarantine_to': None,
'virus_quarantine_to': 'virus-quarantine',
'banned_quarantine_to': None,
'bad_header_quarantine_to': None,
# tags/scores
'spam_subject_tag': None,
'spam_subject_tag2': None,
'spam_tag_level': None,
'spam_kill_level': None,
# ban rules.
"banned_rulenames": "",
}
for k in ['spam', 'virus', 'banned', 'header']:
# Checks: bypass_<k>_checks
_chk = 'bypass_' + k + '_checks'
v = form_utils.get_single_value(form, input_name=_chk, to_string=True)
if v:
if v == 'yes':
v = 'Y' # Exclictly enable
elif v == 'no':
v = 'N' # Exclictly disable
else:
v = None # Don't set a value, use default policy.
policy[_chk] = v
# Quarantining: quarantine_<k>
_quar_input = 'quarantine_' + k
_quar_key = k + '_quarantine_to'
if k == 'header':
_quar_input = 'quarantine_bad_header'
_quar_key = 'bad_header_quarantine_to'
v = form_utils.get_single_value(form=form, input_name=_quar_input, to_string=True)
if v:
if v == 'yes':
v = k + '-quarantine'
if k == 'header':
v = 'bad-header-quarantine'
else:
v = None
policy[_quar_key] = v
# Modify spam subject
v = form_utils.get_single_value(form=form, input_name='prefix_spam_in_subject', to_string=True)
if v:
if v == 'yes':
policy['spam_subject_tag'] = settings.AMAVISD_SPAM_SUBJECT_PREFIX
policy['spam_subject_tag2'] = settings.AMAVISD_SPAM_SUBJECT_PREFIX
else:
policy['spam_subject_tag'] = None
policy['spam_subject_tag2'] = None
v = form_utils.get_single_value(form=form, input_name='always_insert_x_spam_headers', to_string=True)
if v:
if v == 'yes':
policy['spam_tag_level'] = -100
else:
policy['spam_tag_level'] = None
v = form_utils.get_single_value(form=form, input_name='spam_score', to_string=True)
if v.isdigit():
try:
_score = float(v)
policy['spam_tag2_level'] = _score
policy['spam_kill_level'] = _score
except:
return False, 'INVALID_SPAM_SCORE'
# Get ban rules.
names = form_utils.get_multi_values_from_api(form,
input_name="banned_rulenames",
to_string=True,
to_lowercase=False)
if names:
new_names = set()
for n in names:
if (n in BUILTIN_BAN_RULE_NAMES) or (n in settings.AMAVISD_BAN_RULES):
new_names.add(n)
policy["banned_rulenames"] = ",".join(new_names)
qr = delete_spam_policy(account=account)
if not qr[0]:
return qr
# column `users_id` is not a column name in `amavisd.policy` table,
# it's set by SQL statement `LEFT JOIN`.
if 'users_id' in policy:
policy.pop('users_id')
try:
policy_id = web.conn_amavisd.insert('policy', **policy)
qr = utils.link_policy_to_user(account=account, policy_id=policy_id)
if not qr[0]:
return qr
# Update `policy.spam_tag3_level` and `policy.spam_subject_tag3`
# separately, these two columns don't exist in Amavisd-new-2.6.x.
try:
extra_updates = {'spam_tag3_level': policy['spam_tag2_level'],
'spam_subject_tag3': policy['spam_subject_tag2']}
web.conn_amavisd.update(
'policy',
vars={'id': policy_id},
where='id=$id',
**extra_updates)
except:
pass
return True,
except Exception as e:
return False, repr(e)

207
libs/amavisd/utils.py Normal file
View File

@@ -0,0 +1,207 @@
# Author: Zhang Huangbin <zhb@iredmail.org>
import web
import settings
from libs import iredutils
from libs.iredutils import is_valid_amavisd_address
def create_mailaddr(addresses):
for addr in addresses:
addr_type = iredutils.is_valid_amavisd_address(addr)
if addr_type in iredutils.MAILADDR_PRIORITIES:
try:
web.conn_amavisd.insert(
'mailaddr',
priority=iredutils.MAILADDR_PRIORITIES[addr_type],
email=addr,
)
except:
pass
return True
def create_user(account, return_record=True):
# Create a new record in `amavisd.users`
addr_type = is_valid_amavisd_address(account)
try:
# Use policy_id=0 to make sure it's not linked to any policy.
web.conn_amavisd.insert(
'users',
policy_id=0,
email=account,
priority=iredutils.MAILADDR_PRIORITIES[addr_type],
)
if return_record:
qr = web.conn_amavisd.select(
'users',
vars={'account': account},
what='*',
where='email=$account',
limit=1,
)
return True, qr[0]
else:
return True,
except Exception as e:
return False, repr(e)
def get_user_record(account, create_if_missing=True):
try:
qr = web.conn_amavisd.select(
'users',
vars={'email': account},
what='*',
where='email=$email',
limit=1,
)
if qr:
return True, qr[0]
else:
if create_if_missing:
qr = create_user(account=account, return_record=True)
if qr[0]:
return True, qr[1]
else:
return qr
else:
return False, 'ACCOUNT_NOT_EXIST'
except Exception as e:
return False, repr(e)
def create_policy(account, return_record=True):
# Create a new record in `amavisd.policy`
try:
values = {
'policy_name': account,
'spam_quarantine_to': 'spam-quarantine',
'virus_quarantine_to': 'virus-quarantine',
'spam_subject_tag2': settings.AMAVISD_SPAM_SUBJECT_PREFIX,
}
web.conn_amavisd.insert('policy', **values)
# Update `policy.spam_tag3_level` and `policy.spam_subject_tag3`
# separately, these two columns don't exist in Amavisd-new-2.6.x.
try:
extra_values = {'spam_subject_tag3': settings.AMAVISD_SPAM_SUBJECT_PREFIX}
web.conn_amavisd.update(
'policy',
vars={'policy_name': account},
where='policy_name=$policy_name',
**extra_values)
except:
pass
if return_record:
qr = web.conn_amavisd.select(
'policy',
vars={'account': account},
what='*',
where='policy_name=$account',
limit=1,
)
return True, qr[0]
else:
return True,
except Exception as e:
return False, repr(e)
def get_policy_record(account, create_if_missing=False):
try:
qr = web.conn_amavisd.select(
'policy',
vars={'account': account},
what='id',
where='policy_name=$account',
limit=1,
)
if qr:
return True, qr[0]
else:
if create_if_missing:
qr = create_policy(account=account, return_record=True)
if qr[0]:
return True, qr[1]
else:
return qr
else:
return True, {}
except Exception as e:
return False, repr(e)
def link_policy_to_user(account, policy_id):
qr = get_user_record(account)
if qr[0]:
user_id = qr[1].id
else:
return qr
try:
web.conn_amavisd.update(
'users',
vars={'id': user_id},
policy_id=policy_id,
where='id=$id',
)
return True,
except Exception as e:
return False, repr(e)
def delete_policy_accounts(accounts):
sqlvars = {'accounts': accounts}
try:
# Get mailaddr.id of accounts
qr = web.conn_amavisd.select(
'users',
vars=sqlvars,
what='id',
where='email IN $accounts',
)
ids = []
for i in qr:
ids.append(i.id)
# Delete wblist
web.conn_amavisd.delete(
'wblist',
vars={'ids': ids},
where='rid IN $ids',
)
# Delete outbound wblist
web.conn_amavisd.delete(
'outbound_wblist',
vars={'ids': ids},
where='sid IN $ids',
)
# Delete policy
web.conn_amavisd.delete(
'policy',
vars=sqlvars,
where='policy_name IN $accounts',
)
# Delete users
web.conn_amavisd.delete(
'users',
vars=sqlvars,
where='email IN $accounts',
)
except Exception as e:
return False, repr(e)
return True,

454
libs/amavisd/wblist.py Normal file
View File

@@ -0,0 +1,454 @@
# Author: Zhang Huangbin <zhb@iredmail.org>
import web
from libs import iredutils
from libs.logger import log_activity
from libs.amavisd import utils
session = web.config.get('_session')
def get_wblist(account,
whitelist=True,
blacklist=True,
outbound_whitelist=True,
outbound_blacklist=True):
"""Get white/blacklists of specified account."""
inbound_sql_where = 'users.email=$user AND users.id=wblist.rid AND wblist.sid = mailaddr.id'
if whitelist and not blacklist:
inbound_sql_where += ' AND wblist.wb=%s' % web.sqlquote('W')
if not whitelist and blacklist:
inbound_sql_where += ' AND wblist.wb=%s' % web.sqlquote('B')
outbound_sql_where = 'users.email=$user AND users.id=outbound_wblist.sid AND outbound_wblist.rid = mailaddr.id'
if outbound_whitelist and not outbound_blacklist:
outbound_sql_where += ' AND outbound_wblist.wb=%s' % web.sqlquote('W')
if not whitelist and blacklist:
outbound_sql_where += ' AND outbound_wblist.wb=%s' % web.sqlquote('B')
wl = []
bl = []
outbound_wl = []
outbound_bl = []
try:
qr = web.conn_amavisd.select(
['mailaddr', 'users', 'wblist'],
vars={'user': account},
what='mailaddr.email AS address, wblist.wb AS wb',
where=inbound_sql_where,
)
for r in qr:
if r.wb == 'W':
wl.append(iredutils.bytes2str(r.address))
else:
bl.append(iredutils.bytes2str(r.address))
qr = web.conn_amavisd.select(
['mailaddr', 'users', 'outbound_wblist'],
vars={'user': account},
what='mailaddr.email AS address, outbound_wblist.wb AS wb',
where=outbound_sql_where,
)
for r in qr:
if r.wb == 'W':
outbound_wl.append(iredutils.bytes2str(r.address))
else:
outbound_bl.append(iredutils.bytes2str(r.address))
except Exception as e:
return False, e
wl.sort()
bl.sort()
outbound_wl.sort()
outbound_bl.sort()
return (True, {'inbound_whitelists': wl,
'inbound_blacklists': bl,
'outbound_whitelists': outbound_wl,
'outbound_blacklists': outbound_bl})
def add_wblist(account,
wl_senders=None,
bl_senders=None,
wl_rcpts=None,
bl_rcpts=None,
flush_before_import=False):
"""Add white/blacklists for specified account.
wl_senders -- whitelist senders (inbound)
bl_senders -- blacklist senders (inbound)
wl_rcpts -- whitelist recipients (outbound)
bl_rcpts -- blacklist recipients (outbound)
flush_before_import -- Delete all existing wblist before importing
new wblist
"""
if not iredutils.is_valid_amavisd_address(account):
return False, 'INVALID_ACCOUNT'
# Remove duplicate.
if wl_senders:
wl_senders = {str(s).lower()
for s in wl_senders
if iredutils.is_valid_wblist_address(s)}
else:
wl_senders = []
# Whitelist has higher priority, don't include whitelisted sender.
if bl_senders:
bl_senders = {str(s).lower()
for s in bl_senders
if iredutils.is_valid_wblist_address(s)}
else:
bl_senders = []
if wl_rcpts:
wl_rcpts = {str(s).lower()
for s in wl_rcpts
if iredutils.is_valid_wblist_address(s)}
else:
wl_rcpts = []
if bl_rcpts:
bl_rcpts = {str(s).lower()
for s in bl_rcpts
if iredutils.is_valid_wblist_address(s)}
else:
bl_rcpts = []
if flush_before_import:
if wl_senders:
bl_senders = {s for s in bl_senders if s not in wl_senders}
if wl_rcpts:
bl_rcpts = {s for s in bl_rcpts if s not in wl_rcpts}
sender_addresses = set(wl_senders) | set(bl_senders)
rcpt_addresses = set(wl_rcpts) | set(bl_rcpts)
all_addresses = list(sender_addresses | rcpt_addresses)
# Get current user's id from `amavisd.users`
qr = utils.get_user_record(account=account)
if qr[0]:
user_id = qr[1].id
else:
return qr
# Delete old records
if flush_before_import:
# user_id = wblist.rid
web.conn_amavisd.delete(
'wblist',
vars={'rid': user_id},
where='rid=$rid',
)
# user_id = outbound_wblist.sid
web.conn_amavisd.delete(
'outbound_wblist',
vars={'sid': user_id},
where='sid=$sid',
)
if not all_addresses:
return True,
# Insert all senders into `amavisd.mailaddr`
utils.create_mailaddr(addresses=all_addresses)
# Get `mailaddr.id` of senders
sender_records = {}
if sender_addresses:
qr = web.conn_amavisd.select(
'mailaddr',
vars={'addresses': list(sender_addresses)},
what='id, email',
where='email IN $addresses',
)
for r in qr:
sender_records[iredutils.bytes2str(r.email)] = r.id
del qr
# Get `mailaddr.id` of recipients
rcpt_records = {}
if rcpt_addresses:
qr = web.conn_amavisd.select(
'mailaddr',
vars={'addresses': list(rcpt_addresses)},
what='id, email',
where='email IN $addresses',
)
for r in qr:
rcpt_records[iredutils.bytes2str(r.email)] = r.id
del qr
# Remove existing records of current submitted records before inserting new.
try:
if sender_records:
web.conn_amavisd.delete(
'wblist',
vars={'rid': user_id, 'sid': list(sender_records.values())},
where='rid=$rid AND sid IN $sid',
)
if rcpt_records:
web.conn_amavisd.delete(
'outbound_wblist',
vars={'sid': user_id, 'rid': list(rcpt_records.values())},
where='sid=$sid AND rid IN $rid',
)
except Exception as e:
return False, repr(e)
# Generate dict used to build SQL statements for importing wblist
values = []
if sender_addresses:
for s in wl_senders:
if sender_records.get(s):
values.append({'rid': user_id, 'sid': sender_records[s], 'wb': 'W'})
for s in bl_senders:
# Filter out same record in blacklist
if sender_records.get(s) and s not in wl_senders:
values.append({'rid': user_id, 'sid': sender_records[s], 'wb': 'B'})
rcpt_values = []
if rcpt_addresses:
for s in wl_rcpts:
if rcpt_records.get(s):
rcpt_values.append({'sid': user_id, 'rid': rcpt_records[s], 'wb': 'W'})
for s in bl_rcpts:
# Filter out same record in blacklist
if rcpt_records.get(s) and s not in wl_rcpts:
rcpt_values.append({'sid': user_id, 'rid': rcpt_records[s], 'wb': 'B'})
try:
if values:
web.conn_amavisd.multiple_insert('wblist', values)
if rcpt_values:
web.conn_amavisd.multiple_insert('outbound_wblist', rcpt_values)
# Log
if values:
if flush_before_import:
log_activity(msg='Update whitelists and/or blacklists for %s.' % account,
admin=session['username'],
event='update_wblist')
else:
if wl_senders:
log_activity(msg='Add whitelists for {}: {}.'.format(account, ', '.join(wl_senders)),
admin=session['username'],
event='update_wblist')
if bl_senders:
log_activity(msg='Add blacklists for {}: {}.'.format(account, ', '.join(bl_senders)),
admin=session['username'],
event='update_wblist')
if rcpt_values:
if flush_before_import:
log_activity(msg='Update outbound whitelists and/or blacklists for %s.' % account,
admin=session['username'],
event='update_wblist')
else:
if wl_rcpts:
log_activity(msg='Add outbound whitelists for {}: {}.'.format(account, ', '.join(wl_senders)),
admin=session['username'],
event='update_wblist')
if bl_rcpts:
log_activity(msg='Add outbound blacklists for {}: {}.'.format(account, ', '.join(bl_senders)),
admin=session['username'],
event='update_wblist')
except Exception as e:
return False, repr(e)
return True,
def delete_wblist(account,
wl_senders=None,
bl_senders=None,
wl_rcpts=None,
bl_rcpts=None):
if not iredutils.is_valid_amavisd_address(account):
return False, 'INVALID_ACCOUNT'
# Remove duplicate.
if wl_senders:
wl_senders = list({str(s).lower()
for s in wl_senders
if iredutils.is_valid_wblist_address(s)})
# Whitelist has higher priority, don't include whitelisted sender.
if bl_senders:
bl_senders = list({str(s).lower()
for s in bl_senders
if iredutils.is_valid_wblist_address(s)})
if wl_rcpts:
wl_rcpts = list({str(s).lower()
for s in wl_rcpts
if iredutils.is_valid_wblist_address(s)})
if bl_rcpts:
bl_rcpts = list({str(s).lower()
for s in bl_rcpts
if iredutils.is_valid_wblist_address(s)})
# Get account id from `amavisd.users`
qr = utils.get_user_record(account=account)
if qr[0]:
user_id = qr[1].id
else:
return qr
# Remove wblist.
# No need to remove unused senders in `mailaddr` table, because we
# have daily cron job to delete them (tools/cleanup_amavisd_db.py).
try:
# Get `mailaddr.id` for wblist senders
if wl_senders:
sids = []
qr = web.conn_amavisd.select(
'mailaddr',
vars={'addresses': wl_senders},
what='id',
where='email IN $addresses',
)
for r in qr:
sids.append(r.id)
if sids:
web.conn_amavisd.delete(
'wblist',
vars={'user_id': user_id, 'sids': sids},
where="rid=$user_id AND sid IN $sids AND wb='W'",
)
if bl_senders:
sids = []
qr = web.conn_amavisd.select(
'mailaddr',
vars={'addresses': bl_senders},
what='id',
where='email IN $addresses',
)
for r in qr:
sids.append(r.id)
if sids:
web.conn_amavisd.delete(
'wblist',
vars={'user_id': user_id, 'sids': sids},
where="rid=$user_id AND sid IN $sids AND wb='B'",
)
if wl_rcpts:
rids = []
qr = web.conn_amavisd.select(
'mailaddr',
vars={'addresses': wl_rcpts},
what='id',
where='email IN $addresses',
)
for r in qr:
rids.append(r.id)
if rids:
web.conn_amavisd.delete(
'outbound_wblist',
vars={'user_id': user_id, 'rids': rids},
where="sid=$user_id AND rid IN $rids AND wb='W'",
)
if bl_rcpts:
rids = []
qr = web.conn_amavisd.select(
'mailaddr',
vars={'addresses': bl_rcpts},
what='id',
where='email IN $addresses',
)
for r in qr:
rids.append(r.id)
if rids:
web.conn_amavisd.delete(
'outbound_wblist',
vars={'user_id': user_id, 'rids': rids},
where="sid=$user_id AND rid IN $rids AND wb='B'",
)
except Exception as e:
return False, repr(e)
return True,
def delete_all_wblist(account,
wl_senders=False,
bl_senders=False,
wl_rcpts=False,
bl_rcpts=False):
if not iredutils.is_valid_amavisd_address(account):
return False, 'INVALID_ACCOUNT'
# Get account id from `amavisd.users`
qr = utils.get_user_record(account=account)
if qr[0]:
user_id = qr[1].id
else:
return qr
# Remove ALL wblist.
# No need to remove unused senders in `mailaddr` table, because we
# have daily cron job to delete them (tools/cleanup_amavisd_db.py).
try:
if wl_senders:
web.conn_amavisd.delete(
'wblist',
vars={'user_id': user_id},
where="rid=$user_id AND wb='W'",
)
if bl_senders:
web.conn_amavisd.delete(
'wblist',
vars={'user_id': user_id},
where="rid=$user_id AND wb='B'",
)
if wl_rcpts:
web.conn_amavisd.delete(
'outbound_wblist',
vars={'user_id': user_id},
where="sid=$user_id AND wb='W'",
)
if bl_rcpts:
web.conn_amavisd.delete(
'outbound_wblist',
vars={'user_id': user_id},
where="sid=$user_id AND wb='B'",
)
except Exception as e:
return False, repr(e)
return True,