mirror of
https://github.com/marcus-alicia/iRedAdmin-Pro-SQL.git
synced 2026-05-26 07:08:10 +00:00
316 lines
10 KiB
Python
316 lines
10 KiB
Python
# 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)
|