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

657 lines
20 KiB
Python

# Author: Zhang Huangbin <zhb@iredmail.org>
import web
import settings
from libs import iredutils, form_utils
from libs.logger import logger, log_activity
from libs.sqllib import SQLWrap, decorators
from libs.sqllib import general as sql_lib_general
from libs.sqllib import domain as sql_lib_domain
session = web.config.get('_session')
@decorators.require_domain_access
def change_email(mail, new_mail, conn=None):
if not iredutils.is_email(mail):
return False, 'INVALID_OLD_EMAIL'
if not iredutils.is_email(new_mail):
return False, 'INVALID_NEW_EMAIL'
old_domain = mail.split('@', 1)[-1]
new_domain = new_mail.split('@', 1)[-1]
if old_domain != new_domain:
return False, 'PERMISSION_DENIED'
if not conn:
_wrap = SQLWrap()
conn = _wrap.conn
if not sql_lib_general.is_email_exists(mail=mail, conn=conn):
return False, 'OLD_EMAIL_NOT_EXIST'
if sql_lib_general.is_email_exists(mail=new_mail, conn=conn):
return False, 'NEW_EMAIL_ALREADY_EXISTS'
# Change email address
try:
sql_vars = {'mail': mail, 'new_mail': new_mail}
conn.update('alias',
vars=sql_vars,
address=new_mail,
where='address=$mail')
# Update per-user mail forwardings, alias memberships
conn.update('forwardings',
vars=sql_vars,
address=new_mail,
where='address=$mail')
conn.update('forwardings',
vars=sql_vars,
forwarding=new_mail,
where='forwarding=$mail')
# Update moderators
conn.update('moderators',
vars=sql_vars,
address=new_mail,
where='address=$mail')
conn.update('moderators',
vars=sql_vars,
moderator=new_mail,
where='moderator=$mail')
log_activity(event='update',
domain=old_domain,
msg="Change alias account email address: {} -> {}.".format(mail, new_mail))
return True,
except Exception as e:
return False, repr(e)
def add_alias_from_form(domain, form, conn=None):
# Get domain name, username, cn.
form_domain = form_utils.get_domain_name(form)
username = web.safestr(form.get('listname')).strip().lower()
mail = username + '@' + form_domain
if domain != form_domain:
return False, 'PERMISSION_DENIED'
if not iredutils.is_domain(domain):
return False, 'INVALID_DOMAIN_NAME'
if not iredutils.is_auth_email(mail):
return False, 'INVALID_MAIL'
if not conn:
_wrap = SQLWrap()
conn = _wrap.conn
# Check account existing.
if sql_lib_general.is_email_exists(mail=mail, conn=conn):
return False, 'ALREADY_EXISTS'
# Get domain profile.
qr_profile = sql_lib_domain.profile(conn=conn, domain=domain)
if qr_profile[0]:
domain_profile = qr_profile[1]
else:
return qr_profile
# Check account limit.
num_exist = num_aliases_under_domain(conn=conn, domain=domain)
if domain_profile.aliases == -1:
return False, 'NOT_ALLOWED'
elif domain_profile.aliases > 0:
if domain_profile.aliases <= num_exist:
return False, 'EXCEEDED_DOMAIN_ACCOUNT_LIMIT'
# Define columns and values used to insert.
columns = {
'address': mail, 'domain': domain,
'name': form_utils.get_name(form=form),
'created': iredutils.get_gmttime(), 'active': 1,
'accesspolicy': form_utils.get_list_access_policy(form=form,
input_name='accessPolicy',
default_value='public'),
}
# Get access policy
try:
conn.insert('alias', **columns)
log_activity(msg="Create mail alias: %s." % mail,
domain=domain,
event='create')
return True,
except Exception as e:
return False, repr(e)
def delete_aliases(accounts, conn=None):
"""Delete alias accounts under same domain."""
accounts = [str(i).lower() for i in accounts if iredutils.is_email(i)]
if not accounts:
return True,
# Get domain from first account
domain = accounts[0].split('@', 1)[-1]
if not iredutils.is_domain(domain):
return True,
sql_vars = {'domain': domain, 'accounts': accounts}
try:
if not conn:
_wrap = SQLWrap()
conn = _wrap.conn
conn.delete('alias',
vars=sql_vars,
where='address IN $accounts')
conn.delete('forwardings',
vars=sql_vars,
where='address IN $accounts OR forwarding IN $accounts')
log_activity(event='delete',
domain=accounts[0].split('@', 1)[-1],
msg="Delete alias: %s." % ', '.join(accounts))
except Exception as e:
return False, repr(e)
# Remove alias from domain.settings: default_groups
qr = sql_lib_domain.remove_default_maillists_in_domain_setting(domain=domain,
maillists=accounts,
conn=conn)
if not qr[0]:
return qr
return True,
@decorators.require_domain_access
def num_aliases_under_domain(conn, domain, disabled_only=False, first_char=None):
if not iredutils.is_domain(domain):
return False, 'INVALID_DOMAIN_NAME'
num = 0
sql_vars = {'domain': domain}
sql_where = ''
if disabled_only:
sql_where = ' AND active=0'
if first_char:
sql_where += ' AND address LIKE %s' % web.sqlquote(first_char.lower() + '%')
try:
qr = conn.select('alias',
vars=sql_vars,
what='COUNT(address) AS total',
where='domain=$domain %s' % sql_where)
num = qr[0].total or 0
except:
pass
return num
@decorators.require_domain_access
def get_basic_alias_profiles(domain,
columns=None,
first_char=None,
page=0,
email_only=False,
disabled_only=False,
conn=None):
"""Get all aliases under domain.
Return data:
(True, [{'mail': 'alias@domain.com',
'name': '...',
...other profiles in `vmail.alias` table...
'members', [...],
'moderators', [...]]
"""
domain = web.safestr(domain).lower()
if not iredutils.is_domain(domain):
raise web.seeother('/domains?msg=INVALID_DOMAIN_NAME')
sql_vars = {'domain': domain}
if columns:
sql_what = ','.join(columns)
else:
if email_only:
sql_what = 'address'
else:
sql_what = '*'
# Get alias members
additional_sql_where = ''
if first_char:
additional_sql_where = ' AND address LIKE %s' % web.sqlquote(first_char.lower() + '%')
if disabled_only:
additional_sql_where = ' AND active=0'
# Get basic alias profiles first
try:
if not conn:
_wrap = SQLWrap()
conn = _wrap.conn
if page:
qr = conn.select('alias',
vars=sql_vars,
what=sql_what,
where='domain=$domain %s' % additional_sql_where,
order='address ASC',
limit=settings.PAGE_SIZE_LIMIT,
offset=(page - 1) * settings.PAGE_SIZE_LIMIT)
else:
qr = conn.select('alias',
vars=sql_vars,
what=sql_what,
where='domain=$domain %s' % additional_sql_where,
order='address ASC')
if email_only:
emails = []
for r in qr:
email = str(r.address).lower()
emails.append(email)
emails.sort()
return True, emails
else:
return True, list(qr)
except Exception as e:
return False, repr(e)
@decorators.require_domain_access
def get_profile(mail,
with_members=True,
with_moderators=True,
conn=None):
if not iredutils.is_email(mail):
return False, 'INVALID_MAIL'
try:
if not conn:
_wrap = SQLWrap()
conn = _wrap.conn
qr = conn.select('alias',
vars={'address': mail},
where='address=$address',
limit=1)
if qr:
profile = list(qr)[0]
if with_members:
_qr = get_member_emails(mail=mail, conn=conn)
if _qr[0]:
profile['members'] = _qr[1]
profile['members'].sort()
else:
return _qr
if with_moderators:
_qr = get_moderators(mail=mail, conn=conn)
if _qr[0]:
profile['moderators'] = _qr[1]
profile['moderators'].sort()
else:
return _qr
return True, profile
else:
return False, 'NO_SUCH_ACCOUNT'
except Exception as e:
return False, repr(e)
@decorators.require_domain_access
def update(mail, profile_type, form, conn=None):
mail = web.safestr(mail).lower()
domain = mail.split('@', 1)[-1]
if not iredutils.is_email(mail):
return False, 'INVALID_MAIL'
if not conn:
_wrap = SQLWrap()
conn = _wrap.conn
# change email address
if profile_type == 'rename':
# new email address
new_mail = web.safestr(form.get('new_mail_username')).strip().lower() + '@' + domain
qr = change_email(mail=mail, new_mail=new_mail, conn=conn)
if qr[0]:
raise web.seeother('/profile/alias/general/%s?msg=EMAIL_CHANGED' % new_mail)
else:
raise web.seeother('/profile/alias/general/{}?msg={}'.format(new_mail, web.urlquote(qr[1])))
# Pre-defined.
values = {'modified': iredutils.get_gmttime()}
# Get cn.
cn = form.get('cn', '')
values['name'] = cn
# check account status.
values['active'] = 0
if 'accountStatus' in form:
# Enabled.
values['active'] = 1
# Get access policy.
access_policy = str(form.get('accessPolicy'))
if access_policy in iredutils.MAILLIST_ACCESS_POLICIES:
values['accesspolicy'] = access_policy
# Get members & moderators from web form.
_members = form_utils.get_multi_values_from_textarea(form=form,
input_name='members',
is_email=True)
_members = list({iredutils.lower_email_with_upper_ext_address(v) for v in _members})
_moderators = [str(v).strip().lower() for v in form.get('moderators', '').splitlines()]
_moderators = list({iredutils.lower_email_with_upper_ext_address(v)
for v in _moderators
if iredutils.is_email(v) or v.startswith('*@')})
_moderators_wildcard = [v for v in _moderators if iredutils.is_domain(v.split('@', 1)[-1])]
# Remove non-exist accounts in same domain.
# Get members & moderators which in same domain.
_members_in_domain = [i for i in _members if i.endswith('@' + domain)]
_members_not_in_domain = [i for i in _members if not i.endswith('@' + domain)]
_moderators_in_domain = [i for i in _moderators if i.endswith('@' + domain) and i not in _moderators_wildcard]
_moderators_not_in_domain = [i for i in _moderators if not (i.endswith('@' + domain) or i in _moderators_wildcard)]
# Verify internal users
addresses_in_domain = []
_addresses_in_domain = list(set(_members_in_domain + _moderators_in_domain))
if _addresses_in_domain:
try:
# Remove non-existing addresses
_qr = sql_lib_general.filter_existing_emails(mails=_addresses_in_domain, conn=conn)
addresses_in_domain = _qr['exist']
except Exception as e:
logger.error(e)
members_in_domain = [v for v in _members_in_domain if v in addresses_in_domain]
moderators_in_domain = [v for v in _moderators_in_domain if v in addresses_in_domain]
try:
# Update profile
conn.update('alias',
vars={'address': mail},
where='address=$address',
**values)
# Delete all members and moderators first
conn.delete('forwardings',
vars={'address': mail},
where='address=$address')
conn.delete('moderators',
vars={'address': mail},
where='address=$address')
# Add members by inserting new records
_all_members = members_in_domain + _members_not_in_domain
if _all_members:
v = []
for _member in _all_members:
v += [{'address': mail,
'forwarding': _member,
'domain': domain,
'dest_domain': _member.split('@', 1)[-1],
'active': values['active'],
'is_list': 1}]
conn.multiple_insert('forwardings', values=v)
# Add moderators by inserting new records
_all_moderators = moderators_in_domain + _moderators_not_in_domain + _moderators_wildcard
if _all_moderators:
v = []
for _moderator in _all_moderators:
v += [{'address': mail,
'moderator': _moderator,
'domain': domain,
'dest_domain': _moderator.split('@', 1)[-1]}]
conn.multiple_insert('moderators', values=v)
# Log changes.
msg = "Update alias profile (%s)." % mail
if access_policy:
msg += " Access policy: %s." % access_policy
if _all_members:
msg += " Members: %s." % (', '.join(_all_members))
else:
msg += " No members."
if _all_moderators:
msg += " Moderators: %s." % (', '.join(_all_moderators))
else:
msg += " No moderators."
log_activity(msg=msg, username=mail, domain=domain, event='update')
return True,
except Exception as e:
return False, repr(e)
def get_member_emails(mail, conn=None):
"""Get members of mail alias account. Return a list of mail addresses.
Return a list with all members' email addresses."""
if not conn:
_wrap = SQLWrap()
conn = _wrap.conn
try:
qr = conn.select(
'forwardings',
vars={'mail': mail},
what='forwarding',
where='address=$mail AND is_list=1',
)
_addresses = [iredutils.lower_email_with_upper_ext_address(i.forwarding)
for i in qr if iredutils.is_email(i.forwarding)]
_addresses.sort()
return True, _addresses
except Exception as e:
return False, repr(e)
def get_moderators(mail, conn=None):
"""Get moderators of given mail alias account.
Return a list with all moderators' email addresses."""
if not conn:
_wrap = SQLWrap()
conn = _wrap.conn
try:
qr = conn.select('moderators',
vars={'mail': mail},
what='moderator',
where='address=$mail')
_addresses = [iredutils.lower_email_with_upper_ext_address(i.moderator)
for i in qr
if iredutils.is_email(i.moderator) or i.moderator.startswith('*@')]
_addresses.sort()
return True, _addresses
except Exception as e:
return False, repr(e)
def reset_members(mail, members, conn=None):
"""Assign all given addresses specified in `@members` as members."""
_addresses = {iredutils.lower_email_with_upper_ext_address(i)
for i in members
if iredutils.is_email(i)}
domain = mail.split('@', 1)[-1]
_addresses_in_domain = [v for v in _addresses if v.endswith('@' + domain) and v != mail]
_addresses_not_in_domain = [v for v in _addresses if not v.endswith('@' + domain)]
del _addresses
if not conn:
_wrap = SQLWrap()
conn = _wrap.conn
# Verify existence of addresses in same domain
if _addresses_in_domain:
try:
# Remove non-existing addresses
qr = sql_lib_general.filter_existing_emails(mails=_addresses_in_domain, conn=conn)
_addresses_in_domain = qr['exist']
except Exception as e:
logger.error(e)
try:
# Delete all existing members first
conn.delete('forwardings',
vars={'mail': mail},
where='address=$mail AND is_list=1')
# Add member by inserting new record
_all_addresses = _addresses_in_domain + _addresses_not_in_domain
if _all_addresses:
v = []
for i in _all_addresses:
v += [{'address': mail,
'forwarding': i,
'domain': domain,
'dest_domain': i.split('@', 1)[-1],
'is_list': 1}]
conn.multiple_insert('forwardings', values=v)
log_activity(msg='Reset alias ({}) members to: {}'.format(mail, ', '.join(_all_addresses)),
admin=session.get('username'),
username=mail,
domain=domain,
event='update')
return True,
except Exception as e:
return False, repr(e)
def update_members(mail,
new_members=None,
removed_members=None,
conn=None):
"""Add new members to mail alias account, and remove removed_members."""
_new = []
if new_members:
_new = [iredutils.lower_email_with_upper_ext_address(i)
for i in new_members if iredutils.is_email(i)]
_removed = []
if removed_members:
_removed = [iredutils.lower_email_with_upper_ext_address(i)
for i in removed_members if iredutils.is_email(i)]
if not (_new or _removed):
return True, 'NO_VALID_MEMBERS'
domain = mail.split('@', 1)[-1]
if not conn:
_wrap = SQLWrap()
conn = _wrap.conn
# Verify existence of addresses in same domain
_new_in_domain = set()
_new_not_in_domain = set()
if _new:
for i in _new:
if i.endswith('@' + domain):
_new_in_domain.add(i)
else:
_new_not_in_domain.add(i)
# remove self
_new_in_domain.discard(mail)
if _new_in_domain:
try:
# Remove non-existing addresses
qr = sql_lib_general.filter_existing_emails(mails=_new_in_domain, conn=conn)
_new_in_domain = qr['exist']
except Exception as e:
logger.error(e)
# Get existing members
qr = get_member_emails(mail=mail, conn=conn)
if qr[0]:
_old_members = qr[1]
else:
return qr
# Add new, remove removed
_members = set(_old_members)
_members.update(_new_in_domain)
_members.update(_new_not_in_domain)
_members -= set(_removed)
try:
# Delete all existing members first
conn.delete('forwardings',
vars={'mail': mail},
where='address=$mail AND is_list=1')
# Add member by inserting new record
if _members:
v = []
for i in _members:
v += [{'address': mail,
'forwarding': i,
'domain': domain,
'dest_domain': i.split('@', 1)[-1],
'is_list': 1}]
conn.multiple_insert('forwardings', values=v)
log_activity(msg='Update alias ({}) members to: {}'.format(mail, ', '.join(_members)),
admin=session.get('username'),
username=mail,
domain=domain,
event='update')
return True,
except Exception as e:
return False, repr(e)