# Author: Zhang Huangbin import web import settings from libs import iredutils, form_utils from libs.logger import logger, log_activity from libs.sqllib import SQLWrap, decorators, sqlutils from libs.sqllib import general as sql_lib_general from libs.sqllib import admin as sql_lib_admin from libs.amavisd import get_wblist_from_form, wblist as lib_wblist from libs.panel import domain_ownership session = web.config.get('_session', {}) if settings.iredapd_enabled: from libs.iredapd import throttle as iredapd_throttle from libs.iredapd import greylist as iredapd_greylist from libs.iredapd import utils as iredapd_utils if settings.amavisd_enable_policy_lookup: from libs.amavisd.utils import delete_policy_accounts # Mail service names manageable in per-domain profile page. # must sync with # - Jinja2 template file: templates/default/macros/general.html # - libs/sqllib/domain.py # - libs/ldaplib/domain.py AVAILABLE_DOMAIN_DISABLED_MAIL_SERVICES = [ 'smtp', 'smtpsecured', 'pop3', 'pop3secured', 'imap', 'imapsecured', 'managesieve', 'managesievesecured', 'sogo', ] def get_all_domains(conn=None, columns=None, name_only=False): """Get all domains. Return (True, [records]).""" if columns: sql_what = ','.join(columns) else: if name_only: sql_what = 'domain' else: sql_what = '*' try: if not conn: _wrap = SQLWrap() conn = _wrap.conn result = conn.select('domain', what=sql_what, order='domain ASC') if name_only: domain_names = [str(r.domain).lower() for r in result] return True, domain_names else: return True, list(result) except Exception as e: return False, repr(e) def get_all_managed_domains(conn=None, columns=None, name_only=False, disabled_only=False): """Get all managed domains. Returned values: - (True, [records]) - (True, [, , ...]) - (False, ) """ if columns: sql_what = ','.join(columns) else: if name_only: sql_what = 'domain.domain' else: sql_what = 'domain.*' sql_where = None if disabled_only: sql_where = 'domain.active=0' try: if not conn: _wrap = SQLWrap() conn = _wrap.conn if session.get('is_global_admin'): qr = conn.select('domain', what=sql_what, where=sql_where, order='domain ASC') else: if sql_where: sql_where = ' AND ' + sql_where qr = conn.select(['domain', 'domain_admins'], vars={'admin': session.username}, what=sql_what, where='domain_admins.username=$admin AND domain_admins.domain=domain.domain %s' % sql_where, order='domain.domain ASC') if name_only: domain_names = [str(r.domain).lower() for r in qr] return True, domain_names else: return True, list(qr) except Exception as e: return False, repr(e) @decorators.require_domain_access def get_all_alias_domains(domain, name_only=False, conn=None): if not iredutils.is_domain(domain): return False, 'INVALID_DOMAIN_NAME' if not conn: _wrap = SQLWrap() conn = _wrap.conn try: qr = conn.select('alias_domain', vars={'domain': domain}, where='target_domain=$domain') if name_only is True: target_domains = [str(r.alias_domain).lower() for r in qr if iredutils.is_domain(r.alias_domain)] target_domains.sort() return True, target_domains else: return True, list(qr) except Exception as e: return False, repr(e) def exclude_not_managed_domains(domains, admin, conn=None): """Remove given domains not managed by given admin. @domains -- a list/tuple/set of mail domain names @admin -- email address of domain admin @conn -- sql connection cursor """ if not domains: return True, [] if not iredutils.is_email(admin): return False, 'INVALID_ADMIN' if admin == session.get('username'): if session.get('is_global_admin'): return True, domains if not conn: _wrap = SQLWrap() conn = _wrap.conn # Get managed domains qr = sql_lib_admin.get_managed_domains(conn=conn, admin=admin, domain_name_only=True) if qr[0]: managed_domains = qr[1] domains = list(set(domains) & set(managed_domains)) return True, domains else: return qr def enable_disable_domains(domains, action, conn=None): """Set account status. :param domains: a list/tuple/set of mail domain names :param action: enable, disable :param conn: sql connection cursor """ qr = exclude_not_managed_domains(domains=domains, admin=session.get('username'), conn=conn) if qr[0]: domains = qr[1] else: return qr action = action.lower() if action in ['enable', 'active']: active = 1 # Get pending domains required by ownership verification if sql_lib_general.require_domain_ownership_verification(admin=session['username'], conn=conn): qr = domain_ownership.get_pending_domains(domains=domains, domain_name_only=True) if qr[0]: for d in qr[1]: if d in domains: domains.remove(d) if not domains: return True, else: return qr else: active = 0 try: conn.update('domain', vars={'domains': domains}, where='domain IN $domains', active=active) if active: domain_ownership.remove_pending_domains(domains=domains) log_activity(event=action.lower(), msg="{} domain(s): {}.".format(action.title(), ', '.join(domains))) return True, except Exception as e: return False, repr(e) @decorators.require_domain_access def __get_sender_bcc_address(domain, conn=None): return sql_lib_general.get_bcc_address(account=domain, account_type='domain', bcc_type='sender', conn=conn) @decorators.require_domain_access def __get_recipient_bcc_address(domain, conn=None): return sql_lib_general.get_bcc_address(account=domain, account_type='domain', bcc_type='recipient', conn=conn) def __update_domain_bcc(domain, bcc_address=None, bcc_type='sender', remove_all_user_bcc=False, conn=None): """Update per-domain sender bcc. domain - the domain which needs to update bcc setting bcc_address - full email address of bcc destination. bcc setting will be removed if it's None. bcc_type - sender (sender bcc), recipient (recipient bcc) """ remove_bcc = False if not iredutils.is_email(bcc_address): remove_bcc = True if bcc_type == 'sender': tbl = 'sender_bcc_domain' tbl_user = 'sender_bcc_user' else: tbl = 'recipient_bcc_domain' tbl_user = 'recipient_bcc_user' if not conn: _wrap = SQLWrap() conn = _wrap.conn sql_vars = {'domain': domain} try: conn.delete(tbl, vars=sql_vars, where='domain=$domain') if not remove_bcc: conn.insert(tbl, domain=domain, bcc_address=bcc_address, created=iredutils.get_gmttime(), modified=iredutils.get_gmttime(), active=1) if remove_all_user_bcc: conn.delete(tbl_user, vars=sql_vars, where='domain=$domain') return True, except Exception as e: return False, repr(e) def __update_sender_bcc(domain, bcc_address=None, remove_all_user_bcc=False, conn=None): return __update_domain_bcc(domain=domain, bcc_address=bcc_address, bcc_type='sender', remove_all_user_bcc=remove_all_user_bcc, conn=conn) def __update_recipient_bcc(domain, bcc_address=None, remove_all_user_bcc=False, conn=None): return __update_domain_bcc(domain=domain, bcc_address=bcc_address, bcc_type='recipient', remove_all_user_bcc=remove_all_user_bcc, conn=conn) def __get_catchall(domain, conn=None): """Get a list of per-domain catch-all addresses. :param domain: the domain which needs to update bcc setting :param conn: sql connection cursor """ domain = str(domain).lower() if not iredutils.is_domain(domain): return False, 'INVALID_DOMAIN_NAME' if not conn: _wrap = SQLWrap() conn = _wrap.conn try: qr = conn.select('forwardings', vars={'domain': domain}, what='forwarding', where='address=$domain') addresses = [] for i in qr: addr = str(i.forwarding).lower() addresses.append(addr) addresses.sort() return True, addresses except Exception as e: return False, repr(e) def __update_catchall(domain, catchall=None, conn=None): """Update per-domain catch-all accounts. :param domain: the domain which needs to update bcc setting :param catchall: a list/tuple/set of email addresses which will receive emails sent to non-existing address under given domain :param conn: sql connection cursor """ domain = str(domain).lower() if not iredutils.is_domain(domain): return False, 'INVALID_DOMAIN_NAME' if not conn: _wrap = SQLWrap() conn = _wrap.conn try: sql_vars = {'domain': domain} # Remove existing one first conn.delete('forwardings', vars=sql_vars, where='address=$domain') if isinstance(catchall, (tuple, list, set)): _addresses = {str(i).lower() for i in catchall if iredutils.is_email(i)} # Filter non-existing users in same domain. _internal_addrs = {i for i in _addresses if i.endswith('@' + domain)} _external_addrs = {i for i in _addresses if not i.endswith('@' + domain)} if _internal_addrs: _qr = sql_lib_general.filter_existing_emails(mails=_internal_addrs, conn=conn) _external_addrs.update(_qr['exist']) _addresses = _external_addrs del _qr del _internal_addrs, _external_addrs if _addresses: for _addr in _addresses: try: conn.insert('forwardings', address=domain, forwarding=_addr, domain=domain, dest_domain=_addr.split('@', 1)[-1], is_list=0, is_forwarding=0, is_alias=0, active=1) except Exception as e: return False, repr(e) return True, except Exception as e: return False, repr(e) def __get_sender_dependent_relayhost(domain, conn=None): """Get per-domain sender dependent relayhost. :param domain: the domain which needs to update bcc setting :param conn: sql connection cursor """ domain = str(domain).lower() if not iredutils.is_domain(domain): return False, 'INVALID_DOMAIN_NAME' if not conn: _wrap = SQLWrap() conn = _wrap.conn try: qr = conn.select('sender_relayhost', vars={'account': '@' + domain}, what='relayhost', where='account=$account') if qr: relayhost = str(qr[0]['relayhost']).lower() else: relayhost = '' return True, relayhost except Exception as e: return False, repr(e) def __reset_alias_domains(domain, alias_domains, conn=None): """Remove all existing domains and use given domains as new alias domains. :param domain: the primary domain :param alias_domains: new alias domains. If empty or None, all existing alias domains will be removed. :param conn: sql connection cursor """ domain = str(domain).lower() if not iredutils.is_domain(domain): return False, 'INVALID_DOMAIN_NAME' if alias_domains: alias_domains = [str(d).lower() for d in alias_domains if iredutils.is_domain(d)] else: alias_domains = [] if not conn: _wrap = SQLWrap() conn = _wrap.conn try: conn.delete('alias_domain', vars={'domain': domain}, where='target_domain=$domain') if alias_domains: # Remove existing domains qr = sql_lib_general.filter_existing_domains(domains=alias_domains, conn=conn) alias_domains = qr['nonexist'] if alias_domains: v = [] for d in alias_domains: v += [{'alias_domain': d, 'target_domain': domain}] conn.multiple_insert('alias_domain', values=v) return True, except Exception as e: return False, repr(e) def __update_alias_domains(domain, new_alias_domains=None, removed_alias_domains=None, conn=None): """Add/remove alias domains. :param domain: the primary domain :param new_alias_domains: add new alias domains. :param removed_alias_domains: remove existing alias domains. :param conn: sql connection cursor """ domain = str(domain).lower() if not iredutils.is_domain(domain): return False, 'INVALID_DOMAIN_NAME' if new_alias_domains: new_alias_domains = [str(d).lower() for d in new_alias_domains if iredutils.is_domain(d)] else: new_alias_domains = [] if removed_alias_domains: removed_alias_domains = [str(d).lower() for d in removed_alias_domains if iredutils.is_domain(d)] else: removed_alias_domains = [] if not conn: _wrap = SQLWrap() conn = _wrap.conn sql_vars = { 'domain': domain, 'removed_alias_domains': removed_alias_domains, } try: if removed_alias_domains: conn.delete('alias_domain', vars=sql_vars, where='target_domain=$domain AND alias_domain IN $removed_alias_domains') if new_alias_domains: # Remove existing domains qr = sql_lib_general.filter_existing_domains(domains=new_alias_domains, conn=conn) new_alias_domains = qr['nonexist'] if new_alias_domains: for d in new_alias_domains: try: conn.insert('alias_domain', alias_domain=d, target_domain=domain) except: pass return True, except Exception as e: return False, repr(e) # Get used quota of domains. def get_domain_used_quota(conn, domains=None): used_quota = {} if not domains: return used_quota domains = [str(d).lower() for d in domains if iredutils.is_domain(d)] try: qr = conn.select( settings.SQL_TBL_USED_QUOTA, vars={'domains': domains}, where='domain IN $domains', what='domain,SUM(bytes) AS size, SUM(messages) AS messages', group='domain', order='domain', ) for r in qr: used_quota[str(r.domain)] = {'size': r.size, 'messages': r.messages} except: pass return used_quota def get_allocated_domain_quota(domains, conn=None): num = 0 if not isinstance(domains, (list, set, tuple)): return num if len(domains) == 0: return num if not conn: _wrap = SQLWrap() conn = _wrap.conn try: qr = conn.select('mailbox', vars={'domains': domains}, what='SUM(quota) AS total', where='domain IN $domains') if qr: num = int(qr[0].total) or 0 except: pass return num def delete_domains(domains, keep_mailbox_days=0, conn=None): domains = [str(d).lower() for d in domains if iredutils.is_domain(d)] if not domains: return True, if not conn: _wrap = SQLWrap() conn = _wrap.conn qr = exclude_not_managed_domains(domains=domains, admin=session.get('username'), conn=conn) if qr[0]: domains = qr[1] if not domains: return True, else: return qr try: keep_mailbox_days = abs(int(keep_mailbox_days)) except: keep_mailbox_days = 0 if not session.get('is_global_admin'): _max_days = max(settings.DAYS_TO_KEEP_REMOVED_MAILBOX) if keep_mailbox_days > _max_days: # Get the max days keep_mailbox_days = _max_days # Keep mailboxes 'forever', set to 100 years. if keep_mailbox_days == 0: sql_keep_days = web.sqlliteral('Null') else: if settings.backend == 'pgsql': sql_keep_days = web.sqlliteral("""CURRENT_TIMESTAMP + INTERVAL '%d DAYS'""" % keep_mailbox_days) else: # settings.backend == 'mysql' sql_keep_days = web.sqlliteral('DATE_ADD(CURDATE(), INTERVAL %d DAY)' % keep_mailbox_days) sql_vars = { 'domains': domains, 'admin': session.get('username'), 'sql_keep_days': sql_keep_days, } # Log maildir paths of existing users try: if settings.backend == 'pgsql': sql_raw = ''' INSERT INTO deleted_mailboxes (username, maildir, domain, admin, delete_date) SELECT username, \ storagebasedirectory || '/' || storagenode || '/' || maildir, \ domain, \ $admin, \ $sql_keep_days FROM mailbox WHERE domain IN $domains''' else: # settings.backend == 'mysql' sql_raw = ''' INSERT INTO deleted_mailboxes (username, maildir, domain, admin, delete_date) SELECT username, \ CONCAT(storagebasedirectory, '/', storagenode, '/', maildir) AS maildir, \ domain, \ $admin, \ $sql_keep_days FROM mailbox WHERE domain IN $domains''' conn.query(sql_raw, vars=sql_vars) except Exception as e: logger.error(e) try: # Delete domain name for tbl in ['domain', 'alias', 'domain_admins', 'mailbox', 'recipient_bcc_domain', 'recipient_bcc_user', 'sender_bcc_domain', 'sender_bcc_user', 'forwardings', 'moderators', settings.SQL_TBL_USED_QUOTA]: conn.delete(tbl, vars=sql_vars, where='domain IN $domains') # Delete alias domain conn.delete('alias_domain', vars=sql_vars, where='alias_domain IN $domains OR target_domain IN $domains') # Delete domain admins for d in domains: conn.delete('domain_admins', vars={'domain': '%%@' + d}, where='username LIKE $domain') except Exception as e: return False, repr(e) # Delete domains in Amavisd database: users, policy. if settings.amavisd_enable_policy_lookup: pdomains = ['@' + d for d in domains] delete_policy_accounts(accounts=pdomains) # Delete from `iredadmin.domain_ownership` domain_ownership.remove_pending_domains(domains=domains) # Delete throttling & greylisting settings. if settings.iredapd_enabled: iredapd_utils.delete_settings_for_removed_domains(domains=domains) for d in domains: log_activity(event='delete', domain=d, msg="Delete domain: %s." % d) return True, @decorators.require_domain_access def simple_profile(domain, columns=None, conn=None): if not iredutils.is_domain(domain): return False, 'INVALID_DOMAIN_NAME' sql_what = '*' if columns: sql_what = ','.join(columns) try: if not conn: _wrap = SQLWrap() conn = _wrap.conn qr = conn.select('domain', vars={'domain': domain}, what=sql_what, where='domain=$domain', limit=1) if qr: p = list(qr)[0] return True, p else: return False, 'INVALID_DOMAIN_NAME' except Exception as e: return False, repr(e) @decorators.require_domain_access def profile(domain, conn=None): domain = str(domain).lower() if not iredutils.is_domain(domain): return False, 'INVALID_DOMAIN_NAME' if not conn: _wrap = SQLWrap() conn = _wrap.conn try: # Get domain profile first. _qr = simple_profile(domain=domain, conn=conn) if _qr[0]: _profile = _qr[1] else: return _qr # Get BCC addresses _profile['sbcc_addr'] = '' _profile['rbcc_addr'] = '' _qr = __get_sender_bcc_address(domain=domain, conn=conn) if _qr[0]: _profile['sbcc_addr'] = _qr[1] else: return _qr _qr = __get_recipient_bcc_address(domain=domain, conn=conn) if _qr[0]: _profile['rbcc_addr'] = _qr[1] else: return _qr # catchall: per-domain catch-all addresses _profile['catchall'] = [] _qr = __get_catchall(domain=domain, conn=conn) if _qr[0]: _profile['catchall'] = _qr[1] else: return _qr # relayhost (sender_relayhost.relayhost) _profile['relayhost'] = '' _qr = __get_sender_dependent_relayhost(domain=domain, conn=conn) if _qr[0]: _profile['relayhost'] = _qr[1] else: return _qr # num_existing_users _profile['num_existing_users'] = sql_lib_general.num_users_under_domain(domain=domain, conn=conn) return True, _profile except Exception as e: return False, repr(e) # Do not apply @decorators.require_domain_access def get_domain_enabled_services(domain, conn=None): qr = sql_lib_general.get_domain_settings(domain=domain, conn=conn) if qr[0]: domain_settings = qr[1] enabled_services = domain_settings.get('enabled_services', []) return True, enabled_services else: return qr def add(form, conn=None): domain = form_utils.get_domain_name(form) # Check domain name. if not iredutils.is_domain(domain): return False, 'INVALID_DOMAIN_NAME' if not conn: _wrap = SQLWrap() conn = _wrap.conn # Check whether domain name already exist (domainName, domainAliasName). if sql_lib_general.is_domain_exists(domain=domain, conn=conn): return False, 'ALREADY_EXISTS' params = { 'domain': domain, 'mailboxes': 0, 'aliases': 0, 'disclaimer': '', 'created': iredutils.get_gmttime(), } # Quota domain_quota = form_utils.get_domain_quota_and_unit(form=form)['quota'] params['maxquota'] = domain_quota # Number of users kv = form_utils.get_form_dict(form=form, input_name='numberOfUsers', key_name='mailboxes', default_value=0, is_integer=True) params.update(kv) # Number of aliases kv = form_utils.get_form_dict(form=form, input_name='numberOfAliases', key_name='aliases', default_value=0, is_integer=True) params.update(kv) # Number of mailing lists kv = form_utils.get_form_dict(form=form, input_name='numberOfLists', key_name='maillists', default_value=0, is_integer=True) params.update(kv) # Name kv = form_utils.get_form_dict(form=form, input_name='cn', key_name='description') params.update(kv) # Transport kv = form_utils.get_form_dict(form=form, input_name='transport', default_value=settings.default_mta_transport, to_string=True) params.update(kv) # # Update domain account settings # domain_settings = {} kv = form_utils.get_form_dict(form=form, input_name='preferredLanguage', key_name='default_language', to_string=True) domain_settings.update(kv) kv = form_utils.get_form_dict(form=form, input_name='defaultQuota', key_name='default_user_quota', to_string=True) domain_settings.update(kv) _quota = form_utils.get_single_value(form=form, input_name='maxUserQuota', is_integer=True, default_value=0) if _quota: _unit = form_utils.get_single_value(form=form, input_name='maxUserQuotaUnit', to_string=True) if _unit == 'GB': _quota = _quota * 1024 elif _unit == 'TB': _quota = _quota * 1024 * 1024 domain_settings.update({'max_user_quota': _quota}) kv = form_utils.get_form_dict(form=form, input_name='timezone', to_string=True) if 'none' in list(kv.values()): pass else: domain_settings.update(kv) # for normal domain admin: check domain creation limitations qr = sql_lib_admin.get_per_admin_domain_creation_limits(admin=session.get('username'), conn=conn) if qr['error_code']: return False, repr(qr['error_code']) else: # Update number of quota, users, aliases _l = [('num_max_quota', 'num_spare_quota', 'maxquota'), ('num_max_users', 'num_spare_users', 'mailboxes'), ('num_max_aliases', 'num_spare_aliases', 'aliases'), ('num_max_lists', 'num_spare_lists', 'maillists')] for (_limit_max, _limit_spare, _key) in _l: if qr[_limit_max] > 0: _form_num = params.get(_key, 0) _num_spare = qr[_limit_spare] if _form_num == 0: params[_key] = _num_spare else: if _form_num >= _num_spare: params[_key] = _num_spare # max user quota _max_user_quota = domain_settings.get('max_user_quota', 0) if _max_user_quota > 0: if qr['num_max_quota'] > 0: if _max_user_quota >= qr['num_max_quota']: domain_settings['max_user_quota'] = qr['num_max_quota'] if settings.ADDITIONAL_DISABLED_DOMAIN_SERVICES: params['disabled_mail_services'] = settings.ADDITIONAL_DISABLED_DOMAIN_SERVICES params['settings'] = sqlutils.account_settings_dict_to_string(domain_settings) # Domain ownership verification is required if it was added by a normal # domain admin. And we cannot activate this domain immediately. params['active'] = 1 if sql_lib_general.require_domain_ownership_verification(admin=session['username'], conn=conn): qr = domain_ownership.get_verified_domains(domains=[domain], conn=None) if qr[0]: _verified_domains = qr[1] if domain in _verified_domains: params['active'] = 1 else: params['active'] = 0 else: return qr # Add domain in database. try: # Domain ownership verification required if sql_lib_general.require_domain_ownership_verification(admin=session['username'], conn=conn): qr = domain_ownership.set_verify_code_for_new_domains(primary_domain=domain) if not qr[0]: return qr conn.insert('domain', **params) log_activity(msg="New domain: %s." % domain, domain=domain, event='create') # If it's a normal domain admin with permission to create new domain, # assign current admin as admin of this newly created domain. if session.get('create_new_domains'): qr = assign_admins_to_domain(domain=domain, admins=[session.get('username')], conn=conn) if not qr[0]: return qr except Exception as e: return False, repr(e) return True, @decorators.require_domain_access def update(domain, profile_type, form, conn=None): profile_type = str(profile_type) domain = str(domain).lower() sql_vars = {'domain': domain} if not conn: _wrap = SQLWrap() conn = _wrap.conn db_settings = iredutils.get_settings_from_db() # Get current domain profile qr = simple_profile(conn=conn, domain=domain) if qr[0]: domain_profile = qr[1] domain_settings = sqlutils.account_settings_string_to_dict(domain_profile.get('settings', '')) del qr else: return qr # Check disabled domain profiles disabled_domain_profiles = [] if not session.get('is_global_admin'): disabled_domain_profiles = domain_settings.get('disabled_domain_profiles', []) if profile_type in disabled_domain_profiles: return False, 'PERMISSION_DENIED' # Pre-defined update key:value. updates = {'modified': iredutils.get_gmttime()} if profile_type == 'general': # Get name cn = form.get('cn', '') updates['description'] = cn # Get account status if not domain_ownership.is_pending_domain(domain=domain): updates['active'] = 0 if 'accountStatus' in form: updates['active'] = 1 domain_quota = int(domain_profile.get('maxquota', 0)) if session.get('is_global_admin') or session.get('create_new_domains'): # Get domain quota size. qr = form_utils.get_domain_quota_and_unit(form=form) domain_quota = qr['quota'] qr = assign_given_domain_quota(domain=domain, quota=domain_quota, domain_profile=domain_profile, conn=conn) if qr[0]: domain_quota = qr[1] else: return qr updates['maxquota'] = domain_quota # Get max user quota if session.get('is_global_admin'): max_user_quota = str(form.get('maxUserQuota', '0')) if max_user_quota.isdigit(): max_user_quota = int(max_user_quota) else: max_user_quota = 0 if max_user_quota >= 0: max_user_quota_unit = str(form.get('maxUserQuotaUnit', 'MB')) if max_user_quota_unit == 'GB': max_user_quota = max_user_quota * 1024 elif max_user_quota_unit == 'TB': max_user_quota = max_user_quota * 1024 * 1024 if domain_quota > 0: if max_user_quota <= domain_quota: domain_settings['max_user_quota'] = max_user_quota else: domain_settings['max_user_quota'] = domain_quota else: domain_settings['max_user_quota'] = max_user_quota # Get default quota for new user. default_user_quota = form_utils.get_single_value(form=form, input_name='defaultQuota', default_value=0, is_integer=True) if default_user_quota > 0: domain_settings['default_user_quota'] = default_user_quota else: if 'default_user_quota' in domain_settings: domain_settings.pop('default_user_quota') # re-update `default_user_quota` _max = domain_settings.get('max_user_quota', 0) _default = domain_settings.get('default_user_quota', 0) if _max and _default: if _default > _max: domain_settings['default_user_quota'] = _max elif profile_type == 'bcc': # BCC must handle alias domains. # Get all alias domains. bcc_alias_domains = [domain] qr = get_all_alias_domains(domain=domain, name_only=True, conn=conn) if qr[0]: bcc_alias_domains += qr[1] # Delete old records first. try: for d in bcc_alias_domains: _sql_vars = {'domain': d} conn.delete('sender_bcc_domain', vars=_sql_vars, where='domain=$domain') conn.delete('recipient_bcc_domain', vars=_sql_vars, where='domain=$domain') except Exception as e: return False, repr(e) sbcc = str(form.get('senderBccAddress', None)) rbcc = str(form.get('recipientBccAddress', None)) if iredutils.is_email(sbcc): address_domain = sbcc.split('@', -1)[-1] if sql_lib_general.is_domain_exists(domain=address_domain, conn=conn): # Is a hosted local domain if not sql_lib_general.is_email_exists(mail=sbcc, conn=conn): sbcc = None if sbcc: for d in bcc_alias_domains: try: # Add domain bcc conn.insert('sender_bcc_domain', domain=d, bcc_address=sbcc, created=iredutils.get_gmttime(), modified=iredutils.get_gmttime(), active=1) except Exception as e: return False, repr(e) if iredutils.is_email(rbcc): address_domain = rbcc.split('@', -1)[-1] if sql_lib_general.is_domain_exists(domain=address_domain, conn=conn): # Is a hosted internal domain if not sql_lib_general.is_email_exists(mail=rbcc, conn=conn): rbcc = None if rbcc: for d in bcc_alias_domains: try: conn.insert('recipient_bcc_domain', domain=d, bcc_address=rbcc, created=iredutils.get_gmttime(), modified=iredutils.get_gmttime(), active=1) except Exception as e: return False, repr(e) elif profile_type == 'relay': new_transport = str(form.get('mtaTransport', settings.default_mta_transport)) updates['transport'] = new_transport if 'relay_without_verify_local_recipient' in form: if new_transport.startswith(('smtp:', 'uucp:', ':')): updates['backupmx'] = 1 else: updates['backupmx'] = 0 # Get sender dependent relayhost relayhost = str(form.get('relayhost', '')) # Update relayhost _qr = sql_lib_general.update_sender_relayhost(account='@' + domain, relayhost=relayhost, conn=conn) if not _qr[0]: return _qr elif profile_type == 'catchall': # Get list of destination addresses. _addresses = {str(v).lower() for v in form.get('catchall_addresses', '').splitlines() if iredutils.is_email(v)} _qr = __update_catchall(domain=domain, catchall=_addresses, conn=conn) if not _qr[0]: return _qr elif profile_type == 'aliases': # Get old alias domains old_alias_domains = [] qr = get_all_alias_domains(domain=domain, name_only=True, conn=conn) if qr[0]: old_alias_domains = qr[1] # Delete old records first. try: conn.delete('alias_domain', vars=sql_vars, where='target_domain=$domain') except Exception as e: return False, repr(e) # Get domain aliases from web form and store in LDAP. all_alias_domains = {str(d).lower() for d in form.get('domainAliasName', []) if d != domain and not sql_lib_general.is_domain_exists(domain=d, conn=conn)} removed_alias_domains = [d for d in old_alias_domains if d not in all_alias_domains] if removed_alias_domains: for d in removed_alias_domains: # Delete all records in bcc tables. d_in_sql = web.sqlquote(d) conn.delete('sender_bcc_domain', where='domain=%s' % d_in_sql) conn.delete('recipient_bcc_domain', where='domain=%s' % d_in_sql) conn.delete('sender_bcc_user', where='domain=%s' % d_in_sql) conn.delete('recipient_bcc_user', where='domain=%s' % d_in_sql) # Remove pending domain ownership verification domain_ownership.remove_pending_domains(domains=removed_alias_domains) if all_alias_domains: v = [] qr = domain_ownership.get_pending_domains(domains=all_alias_domains, domain_name_only=True) if qr[0]: _pending_domains = qr[1] else: return qr for ad in all_alias_domains: _record = { 'alias_domain': ad, 'target_domain': domain, 'created': iredutils.get_gmttime(), 'active': 1, } # Require domain ownership verification _new_alias_domains = [] if ad in _pending_domains: # Existing pending domain if not session.get('is_global_admin'): _record['active'] = 0 else: # New domain if ad not in old_alias_domains: _new_alias_domains.append(ad) if not session.get('is_global_admin'): _record['active'] = 0 v += [_record] # Add verify codes for new domain if _new_alias_domains: if not session.get('is_global_admin'): domain_ownership.set_verify_code_for_new_domains(primary_domain=domain, alias_domains=_new_alias_domains) # Add alis domains. if v: try: conn.multiple_insert('alias_domain', values=v) log_activity(msg="Update alias domains of {} to: {}.".format(domain, ', '.join(all_alias_domains)), domain=domain, event='update') except Exception as e: return False, repr(e) # Update bcc records for existing accounts try: # Domain bcc for domain_bcc_table in ['sender_bcc_domain', 'recipient_bcc_domain']: # Get all bcc records of mail users, then add same bcc for alias domains qr = conn.select(domain_bcc_table, vars=sql_vars, what='domain,bcc_address,active', where='domain=$domain') new_bcc_records = [] for ad in all_alias_domains: for r in qr: new_bcc_records += [{ 'bcc_address': r.bcc_address, 'active': r.active, 'domain': ad, 'created': iredutils.get_gmttime(), }] if new_bcc_records: conn.multiple_insert(domain_bcc_table, values=new_bcc_records) del new_bcc_records # User bcc for user_bcc_table in ['sender_bcc_user', 'recipient_bcc_user']: qr = conn.select(user_bcc_table, vars=sql_vars, what='username,bcc_address,active', where='domain=$domain') new_bcc_records = [] for ad in all_alias_domains: for r in qr: new_bcc_records += [{ 'username': r.username.split('@', 1)[0] + '@' + ad, 'bcc_address': r.bcc_address, 'active': r.active, 'domain': ad, 'created': iredutils.get_gmttime(), }] if new_bcc_records: conn.multiple_insert(user_bcc_table, values=new_bcc_records) del new_bcc_records except: pass elif profile_type == 'wblist': if session.get('is_global_admin') or 'wblist' not in disabled_domain_profiles: if settings.amavisd_enable_policy_lookup: wl_senders = get_wblist_from_form(form, 'wl_sender') bl_senders = get_wblist_from_form(form, 'bl_sender') wl_rcpts = get_wblist_from_form(form, 'wl_rcpt') bl_rcpts = get_wblist_from_form(form, 'bl_rcpt') qr = lib_wblist.add_wblist(account='@' + domain, wl_senders=wl_senders, bl_senders=bl_senders, wl_rcpts=wl_rcpts, bl_rcpts=bl_rcpts, flush_before_import=True) return qr elif profile_type == 'throttle': if settings.iredapd_enabled: t_account = '@' + domain inbound_setting = form_utils.get_throttle_setting(form, account=t_account, inout_type='inbound') outbound_setting = form_utils.get_throttle_setting(form, account=t_account, inout_type='outbound') iredapd_throttle.add_throttle(account=t_account, setting=inbound_setting, inout_type='inbound') iredapd_throttle.add_throttle(account=t_account, setting=outbound_setting, inout_type='outbound') elif profile_type == 'greylisting': if settings.iredapd_enabled: qr = iredapd_greylist.update_greylist_settings_from_form(account='@' + domain, form=form) return qr elif profile_type == 'advanced': # Update min/max password length in domain setting if session.get('is_global_admin') or ('password_policies' not in disabled_domain_profiles): for (_input_name, _key_name) in [('minPasswordLength', 'min_passwd_length'), ('maxPasswordLength', 'max_passwd_length')]: try: _length = int(form.get(_input_name, 0)) except: _length = 0 if _length > 0: if not session.get('is_global_admin'): # Make sure domain setting doesn't exceed global setting. if _input_name == 'minPasswordLength': # Cannot be shorter than global setting. if _length < db_settings['min_passwd_length']: _length = db_settings['min_passwd_length'] elif _input_name == 'maxPasswordLength': # Cannot be longer than global setting. if (db_settings['max_passwd_length'] > 0) and \ (_length > db_settings['max_passwd_length'] or _length <= db_settings['min_passwd_length']): _length = db_settings['max_passwd_length'] domain_settings[_key_name] = _length else: if _key_name in domain_settings: domain_settings.pop(_key_name) # Update default mailing lists of newly created mail user default_mailing_lists = [str(v).lower() for v in form.get('default_mail_list', []) if iredutils.is_email(v)] if default_mailing_lists: domain_settings['default_mailing_lists'] = default_mailing_lists else: if 'default_mailing_lists' in domain_settings: domain_settings.pop('default_mailing_lists') # Update default groups of newly created mail user default_groups = [str(v).lower() for v in form.get('defaultList', []) if iredutils.is_email(v)] if default_groups: domain_settings['default_groups'] = default_groups else: if 'default_groups' in domain_settings: domain_settings.pop('default_groups') # Update default language for new user default_language = form_utils.get_language(form) if default_language in iredutils.get_language_maps(): domain_settings['default_language'] = default_language domain_settings['timezone'] = form_utils.get_timezone(form) # Default per-user bcc address if session.get('is_global_admin') or 'bcc' not in disabled_domain_profiles: rbcc = web.safestr(form.get('recipientBccAddress', '')) sbcc = web.safestr(form.get('senderBccAddress', '')) for (addr, name) in [(rbcc, 'default_recipient_bcc'), (sbcc, 'default_sender_bcc')]: if name in domain_settings: domain_settings.pop(name) if iredutils.is_email(addr): rbcc_domain = addr.split('@', 1)[-1] # Verify existence before saving if sql_lib_general.is_domain_exists(domain=rbcc_domain, conn=conn): if sql_lib_general.is_email_exists(mail=addr, conn=conn): domain_settings[name] = addr else: domain_settings[name] = addr # Get enabled_services. if form.get('enabledService', []): domain_settings['enabled_services'] = [str(v).lower() for v in form.get('enabledService', [])] else: if 'enabled_services' in domain_settings: domain_settings.pop('enabled_services') # Get disabled services if session.get('is_global_admin') or \ ('disabled_mail_services' not in disabled_domain_profiles): form_disabled_mail_services = [str(v).lower() for v in form.get('disabledMailService', []) if v in AVAILABLE_DOMAIN_DISABLED_MAIL_SERVICES] if form_disabled_mail_services: domain_settings['disabled_mail_services'] = form_disabled_mail_services else: if 'disabled_mail_services' in domain_settings: domain_settings.pop('disabled_mail_services') # Update disabled_mail_services for all existing mail users. if 'disable_services_for_existing_users' in form: _disabled_mail_services = [v for v in form_disabled_mail_services] _enabled_services = [v for v in AVAILABLE_DOMAIN_DISABLED_MAIL_SERVICES if v not in _disabled_mail_services] _update_services = {} for srv in _disabled_mail_services: _update_services['enable' + srv] = 0 for srv in _enabled_services: _update_services['enable' + srv] = 1 # Update all mail users. if _update_services: try: conn.update('mailbox', vars={'domain': domain}, where='domain=$domain', **_update_services) except Exception as e: return False, repr(e) del form_disabled_mail_services del _disabled_mail_services, _enabled_services, _update_services # Get disabled user preferences. if form.get('disabledUserPreference', []): domain_settings['disabled_user_preferences'] = [str(v).lower() for v in form.get('disabledUserPreference', [])] else: if 'disabled_user_preferences' in domain_settings: domain_settings.pop('disabled_user_preferences') if session.get('is_global_admin') or session.get('create_new_domains'): try: num_users = int(form.get('numberOfUsers', 0)) except: num_users = 0 try: num_aliases = int(form.get('numberOfAliases', 0)) except: num_aliases = 0 try: num_lists = int(form.get('numberOfLists', 0)) except: num_lists = 0 if session.get('is_global_admin'): updates['mailboxes'] = num_users updates['aliases'] = num_aliases updates['maillists'] = num_lists else: # Get per-admin settings used by normal admin to create new domains. qr = sql_lib_general.get_admin_settings(admin=session.get('username'), existing_settings=None, conn=conn) if qr[0]: admin_settings = qr[1] num_max_users = admin_settings.get('create_max_users', 0) num_max_aliases = admin_settings.get('create_max_aliases', 0) num_max_lists = admin_settings.get('create_max_lists', 0) if num_max_users: # managed users in all managed domains num_managed_users = sql_lib_admin.num_managed_users(admin=session.get('username'), listed_only=True, conn=conn) # managed users in current domain num_existing_users = sql_lib_admin.num_managed_users(admin=session.get('username'), domains=[domain], listed_only=True, conn=conn) # max number allowed to set num_spare_users = num_max_users - (num_managed_users - num_existing_users) if num_users <= num_spare_users: updates['mailboxes'] = num_users else: updates['mailboxes'] = num_spare_users else: updates['mailboxes'] = num_users if num_max_aliases: # managed aliases in all managed domains num_managed_aliases = sql_lib_admin.num_managed_aliases(admin=session.get('username'), listed_only=True, conn=conn) # managed aliases in current domain num_existing_aliases = sql_lib_admin.num_managed_aliases(admin=session.get('username'), domains=[domain], listed_only=True, conn=conn) num_spare_aliases = num_max_aliases - (num_managed_aliases - num_existing_aliases) if num_aliases <= num_spare_aliases: updates['aliases'] = num_aliases else: updates['aliases'] = num_spare_aliases else: updates['aliases'] = num_aliases if num_max_lists: num_managed_lists = sql_lib_admin.num_managed_lists(admin=session.get('username'), listed_only=True, conn=conn) num_existing_lists = sql_lib_admin.num_managed_lists(admin=session.get('username'), domains=[domain], listed_only=True, conn=conn) num_spare_lists = num_max_lists - (num_managed_lists - num_existing_lists) if num_lists <= num_spare_lists: updates['maillists'] = num_lists else: updates['maillists'] = num_spare_lists else: updates['maillists'] = num_lists if session.get('is_global_admin'): # Get disabled domain profiles. if form.get('disabledDomainProfile', []): domain_settings['disabled_domain_profiles'] = [str(v).lower() for v in form.get('disabledDomainProfile', [])] else: if 'disabled_domain_profiles' in domain_settings: domain_settings.pop('disabled_domain_profiles') # Get disabled user profiles. if form.get('disabledUserProfile', []): domain_settings['disabled_user_profiles'] = [str(v).lower() for v in form.get('disabledUserProfile', [])] else: if 'disabled_user_profiles' in domain_settings: domain_settings.pop('disabled_user_profiles') elif profile_type == 'backupmx': is_backupmx = ('backupmx' in form) if is_backupmx: updates['backupmx'] = 1 primary_mx = form_utils.get_single_value(form, input_name='primary_mx', to_string=True) if primary_mx: updates['transport'] = 'relay:%s' % primary_mx else: # Let postfix query DNS records to get primary mx. updates['transport'] = 'relay:%s' % domain else: updates['backupmx'] = 0 updates['transport'] = settings.default_mta_transport updates['settings'] = sqlutils.account_settings_dict_to_string(domain_settings) try: conn.update('domain', vars=sql_vars, where='domain=$domain', **updates) log_activity(msg="Update domain profile: {} ({}).".format(domain, profile_type), domain=domain, event='update') return True, except Exception as e: return False, repr(e) def get_paged_domains(first_char=None, cur_page=1, disabled_only=False, conn=None): admin = session.get('username') page = int(cur_page) or 1 # A dict used to store domain profiles. # Format: {'': {: , : , ...}} records = {} try: sql_where = '' if session.get('is_global_admin'): if first_char: sql_where = """ domain LIKE %s""" % web.sqlquote(first_char.lower() + '%') if disabled_only: if sql_where: sql_where += ' AND active=0' else: sql_where += ' active=0' if not sql_where: sql_where = None sql_what = 'domain, description, transport, backupmx, active, aliases, mailboxes, maillists, maxquota, quota' qr = conn.select('domain', what=sql_what, where=sql_where, limit=settings.PAGE_SIZE_LIMIT, order='domain', offset=(page - 1) * settings.PAGE_SIZE_LIMIT) else: sql_where = ' domain.domain = domain_admins.domain AND domain_admins.username = %s' % web.sqlquote(admin) if first_char: sql_where += """ AND domain.domain LIKE %s""" % web.sqlquote(first_char.lower() + '%') if disabled_only: if sql_where: sql_where += ' AND domain.active=0' else: sql_where += 'domain.active=0' sql_what = 'domain.domain, domain.description, domain.transport,' sql_what += 'domain.backupmx, domain.active, domain.aliases,' sql_what += 'domain.mailboxes, domain.maillists, domain.maxquota,' sql_what += 'domain.quota' qr = conn.select(['domain', 'domain_admins'], what=sql_what, where=sql_where, limit=settings.PAGE_SIZE_LIMIT, order='domain.domain', offset=(page - 1) * settings.PAGE_SIZE_LIMIT) if not qr: return True, {} for i in qr: _domain = str(i.domain).lower() records[_domain] = i sql_vars = {'domains': list(records.keys())} # Get num_existing_users qr = conn.select('mailbox', vars=sql_vars, what='domain, SUM(mailbox.quota) AS quota_count, COUNT(username) AS total', where='domain IN $domains', group='domain', limit=settings.PAGE_SIZE_LIMIT) for i in qr: _domain = str(i.domain).lower() records[_domain]['num_existing_users'] = i.total records[_domain]['quota_count'] = i.quota_count # Get num of existing aliases and mailing lists. for (_sql_table, k) in [('alias', 'num_existing_aliases'), ('maillists', 'num_existing_maillists')]: qr = conn.select(_sql_table, vars=sql_vars, what='domain, COUNT(address) AS total', where='domain IN $domains', group='domain', limit=settings.PAGE_SIZE_LIMIT) for i in qr: _domain = str(i.domain).lower() records[_domain][k] = i.total # Sort domains by domain name _domains = list(records.keys()) _domains.sort() _profiles = [records[k] for k in _domains] return True, _profiles except Exception as e: return False, repr(e) def assign_given_mailbox_quota(domain, quota, domain_profile=None, conn=None, reset_user_quota=False, user=None): """Check whether there's enough spare quota for creating new mail user. domain -- an existing domain name quota -- request new mailbox quota domain_profile -- existing domain profile (a dict) conn -- existing SQL connection cursor reset_user_quota -- Reset user quota. if it's trying to reset user quota, we should add old user quota before calculating a new one """ try: quota = abs(int(quota)) except: # Wait for setting to per-domain default user quota. quota = -1 if not conn: _wrap = SQLWrap() conn = _wrap.conn # Get domain profile first. if not domain_profile: qr = simple_profile(conn=conn, domain=domain) if qr[0]: domain_profile = qr[1] else: return qr # Get domain settings _ps = domain_profile.get('settings', '') ds = sqlutils.account_settings_string_to_dict(_ps) default_user_quota = ds.get('default_user_quota', 0) max_user_quota = ds.get('max_user_quota', 0) # Set to per-domain default user quota if quota is invalid if quota < 0: quota = default_user_quota # Re-calculate mail quota if this domain has limited max quota. domain_quota = domain_profile.get('maxquota', 0) if domain_quota > 0: # Get used quota. allocated_quota = get_allocated_domain_quota(domains=[domain], conn=conn) spare_quota = domain_profile.maxquota - allocated_quota # Add old quota before calculating new quota if reset_user_quota and user: try: qr = conn.select('mailbox', vars={'user': user}, what='quota', where='username=$user', limit=1) if qr: old_quota = int(qr[0].quota) spare_quota = spare_quota + old_quota except Exception as e: return False, repr(e) if spare_quota > 0: if spare_quota < quota: quota = spare_quota else: # No enough quota. return False, 'EXCEEDED_DOMAIN_QUOTA_SIZE' if quota == 0: if default_user_quota: quota = default_user_quota elif max_user_quota: quota = max_user_quota else: quota = spare_quota if max_user_quota > 0: if quota > max_user_quota: quota = max_user_quota return True, quota def assign_given_domain_quota(domain, quota, domain_profile=None, conn=None): """Check whether specified domain quota is allowed to use. domain -- an existing domain name quota -- new domain quota (in MB) domain_profile -- existing domain profile (a dict) conn -- existing SQL connection cursor """ try: quota = abs(int(quota)) except: return False, 'INVALID_QUOTA_SIZE' admin = session.get('username') if session.get("is_global_admin"): return True, quota elif session.get('create_new_domains'): if not conn: _wrap = SQLWrap() conn = _wrap.conn # Get max domain quota from per-admin settings limits = sql_lib_admin.get_per_admin_domain_creation_limits(admin=admin, conn=conn) num_max_quota = limits['num_max_quota'] allocated_quota = limits['num_allocated_quota'] if num_max_quota > 0: # Get quota of current domain if not domain_profile: qr = simple_profile(domain=domain, conn=conn, columns=['maxquota']) if qr[0]: domain_profile = qr[1] else: return qr current_quota = int(domain_profile.maxquota) spare_quota = num_max_quota - (allocated_quota - current_quota) if spare_quota >= 0: if quota == 0: quota = spare_quota elif quota >= spare_quota: quota = spare_quota else: # don't change quota = current_quota return True, quota else: return False, 'PERMISSION_DENIED' def get_domain_admin_addresses(domain, conn=None): """List email addresses of all domain admins (exclude global admins). >>> get_domain_admin_addresses(domain='abc.com') (True, ['user1@.com', 'user2@.com', ...]) >>> get_domain_admin_addresses(domain='xyz.com') (False, '') """ all_admins = set() sql_vars = {'domain': domain} try: if not conn: _wrap = SQLWrap() conn = _wrap.conn qr = conn.select('domain_admins', vars=sql_vars, what='username', where='domain=$domain') for i in qr: all_admins.add(str(i.username).lower()) return True, list(all_admins) except Exception as e: return False, repr(e) def assign_admins_to_domain(domain, admins, conn=None): """Assign list of NEW admins to specified mail domain. It doesn't remove existing admins.""" if not iredutils.is_domain(domain): return False, 'INVALID_DOMAIN_NAME' if not isinstance(admins, (list, tuple, set)): return False, 'NO_ADMINS' else: admins = [str(i).lower() for i in admins if iredutils.is_email(i)] if not admins: return False, 'NO_ADMINS' if not conn: _wrap = SQLWrap() conn = _wrap.conn for adm in admins: try: conn.insert('domain_admins', domain=domain, username=adm) except Exception as e: if e.__class__.__name__ == 'IntegrityError': pass else: return False, repr(e) return True, def remove_default_maillists_in_domain_setting(domain, maillists, conn=None): """Remove given mailing list from domain.settings: default_groups.""" domain = str(domain).lower() if not iredutils.is_domain(domain): return True, maillists = [str(i).lower() for i in maillists if iredutils.is_email(i)] if not maillists: return True, if not conn: _wrap = SQLWrap() conn = _wrap.conn # Get domain profile. qr = simple_profile(conn=conn, domain=domain, columns=['settings']) if qr[0]: domain_profile = qr[1] domain_settings = sqlutils.account_settings_string_to_dict(domain_profile['settings']) elif qr[1] == 'INVALID_DOMAIN_NAME': # No such domain, return earlier return True, else: return qr default_groups = domain_settings.get('default_groups', []) new_default_groups = [str(v).lower() for v in default_groups if v not in maillists] if default_groups != new_default_groups: if new_default_groups: domain_settings['default_groups'] = new_default_groups else: if 'default_groups' in domain_settings: domain_settings.pop('default_groups') new_domain_settings = sqlutils.account_settings_dict_to_string(domain_settings) try: conn.update('domain', vars={'domain': domain}, settings=new_domain_settings, modified=iredutils.get_gmttime(), where='domain=$domain') except Exception as e: return False, repr(e) return True, def update_ownership_verified_domain(primary_domain, alias_domain=None, conn=None): if not conn: _wrap = SQLWrap() conn = _wrap.conn is_alias_domain = False if primary_domain and alias_domain: is_alias_domain = True try: sql_vars = { 'primary_domain': primary_domain, 'alias_domain': alias_domain, } if is_alias_domain: conn.update('alias_domain', vars=sql_vars, active=1, where='alias_domain=$alias_domain AND target_domain=$primary_domain AND active=0') else: conn.update('domain', vars=sql_vars, active=1, where='domain=$primary_domain AND active=0') return True, except Exception as e: return False, repr(e) def enable_domain_without_ownership_verification(domains, conn=None): domains = list({str(d).lower() for d in domains if iredutils.is_domain(d)}) if not domains: return True, if not conn: _wrap = SQLWrap() conn = _wrap.conn _alias_domains = [] # Query all matched alias domains try: qr = conn.select('alias_domain', vars={'domains': domains}, what='alias_domain', where='alias_domain IN $domains') for i in qr: _alias_domains += [str(i.alias_domain).lower()] _primary_domains = [d for d in domains if d not in _alias_domains] except Exception as e: return False, repr(e) for d in _primary_domains: # Enable domain directly try: conn.update('domain', vars={'domain': d}, where="domain=$domain", active=1) # Mark domains as verified msg = 'passed by global admin: %s' % session.get('username') qr = domain_ownership.mark_ownership_as_verified(domain=d, message=msg) if not qr[0]: return qr except Exception as e: return False, repr(e) for d in _alias_domains: try: conn.update('alias_domain', vars={'domain': d}, where="alias_domain=$domain", active=1) # Mark domains as verified msg = 'passed by global admin: %s' % session.get('username') qr = domain_ownership.mark_ownership_as_verified(domain=d, message=msg) if not qr[0]: return qr except Exception as e: return False, repr(e) return True, @decorators.require_domain_access def api_update_profile(domain, form, conn=None): """Update domain profile. :param domain: domain name. :param form: dict of web form. :param conn: sql connection cursor. Form parameters: `name`: the short company/orgnization name `accountStatus`: enable or disable domain. possible value is: active, disabled. `quota`: Per-domain mailbox quota `transport`: Per-domain transport `language`: default preferred language for new user. e.g. en_US for English, de_DE for Deutsch. `minPasswordLength`: Minimal password length `maxPasswordLength`: Maximum password length `defaultQuota`: default mailbox quota for new user. `maxUserQuota`: Max mailbox quota of a single mail user `numberOfUsers`: Max number of mail user accounts `numberOfAliases`: Max number of mail alias accounts `senderBcc`: set bcc address for outgoing emails `recipientBcc`: set bcc address for incoming emails `catchall`: set per-domain catch-all account. catchall account is a list of email addresses which will receive emails sent to non-existing address under same domain `outboundRelay`: relay outgoing emails to specified host `addService`: enable new services. Multiple services must be separated by comma. `removeService`: disable existing services. Multiple services must be separated by comma. `services`: reset all services. If empty, all existing services will be removed. `disableDomainProfile`: disable given domain profiles. Normal admin cannot view and update disabled profiles in domain profile page. `enableDomainProfile`: enable given domain profiles. Normal admin can view and update disabled profiles in domain profile page. `disableUserProfile`: disable given user profiles. Normal admin cannot view and update disabled profiles in user profile page. `enableUserProfile`: enable given domain profiles. Normal admin can view and update disabled profiles in user profile page. `disableUserPreference`: disable given user preferences in self-service page. Normal mail user cannot view and update disabled preferences. `enableUserPreference`: disable given user preferences in self-service page. Normal mail user can view and update disabled preferences. `aliasDomains`: remove all existing alias domains and add given domains as alias domains. Multiple domains must be separated by comma. `addAliasDomain`: add new alias domains. Multiple domains must be separated by comma. `removeAliasDomain`: remove existing alias domains. Multiple domains must be separated by comma. """ domain = str(domain).lower() if not conn: _wrap = SQLWrap() conn = _wrap.conn params = {} # Name kv = form_utils.get_form_dict(form=form, input_name='name', key_name='description') params.update(kv) # Account status kv = form_utils.get_form_dict(form=form, input_name='accountStatus', key_name='active') params.update(kv) # Transport kv = form_utils.get_form_dict(form=form, input_name='transport', to_string=True) params.update(kv) # Backup MX (Require IP address or hostname of primary MX) if 'is_backupmx' in form: if form.get('is_backupmx') == 'yes': params.update({'backupmx': 1}) v = form_utils.get_single_value(form, input_name='primarymx', to_string=True) if v: params.update({'transport': 'relay:%s' % v}) else: params.update({'transport': 'relay:%s' % domain}) else: params['backupmx'] = 0 if 'transport' not in params: params.update({'transport': settings.default_mta_transport}) db_settings = iredutils.get_settings_from_db() # # Domain settings stored in column `settings` # if {'language', 'defaultQuota', 'maxUserQuota', 'minPasswordLength', 'maxPasswordLength', 'disableDomainProfile', 'enableDomainProfile', 'disableUserProfile', 'enableUserProfile', 'disableUserPreference', 'enableUserPreference', 'services', 'addService', 'removeService'} & set(form): # Get current account settings in dict qr = sql_lib_general.get_domain_settings(domain=domain, conn=conn) if qr[0]: _as = qr[1] else: return qr # Update settings kv = form_utils.get_form_dict(form=form, input_name='language', key_name='default_language', to_string=True) _as.update(kv) kv = form_utils.get_form_dict(form=form, input_name='minPasswordLength', key_name='min_passwd_length', is_integer=True) if not session.get('is_global_admin'): if kv: if kv['minPasswordLength'] < db_settings['min_passwd_length']: kv['minPasswordLength'] = db_settings['min_passwd_length'] _as.update(kv) kv = form_utils.get_form_dict(form=form, input_name='maxPasswordLength', key_name='max_passwd_length', is_integer=True) if not session.get('is_global_admin'): if kv: if kv['maxPasswordLength'] > db_settings['max_passwd_length'] or \ kv['maxPasswordLength'] <= db_settings['min_passwd_length']: kv['maxPasswordLength'] = db_settings['max_passwd_length'] _as.update(kv) kv = form_utils.get_form_dict(form=form, input_name='defaultQuota', key_name='default_user_quota', is_integer=True) _as.update(kv) kv = form_utils.get_form_dict(form=form, input_name='maxUserQuota', key_name='max_user_quota', is_integer=True) _as.update(kv) if session.get('is_global_admin'): # Enable/disabled mail services if 'addService' in form or 'removeService' in form: enabled_services = _as.get('enabled_services', []) new_enabled_services = set() if 'addService' in form: kv = form_utils.get_form_dict(form=form, input_name='addService') # Convert comma-separated services to list. if kv: _srvs = [str(srv).strip().lower() for srv in kv['addService'].split(',') if srv] new_enabled_services.update(_srvs) if 'removeService' in form: kv = form_utils.get_form_dict(form=form, input_name='removeService') # Convert comma-separated services to list. if kv: _srvs = [str(srv).strip().lower() for srv in kv['removeService'].split(',') if srv] new_enabled_services -= set(_srvs) if set(enabled_services) == new_enabled_services: return True, else: _as['enabled_services'] = list(new_enabled_services) if 'services' in form: _services = list({str(i).lower() for i in form.get('services', '').split(',') if i}) if _services: _as['enabled_services'] = _services else: _as['enabled_services'] = [] # # Enable/disabled domain profiles # _disabled = [('disableDomainProfile', 'disabled_domain_profiles'), ('disableUserProfile', 'disabled_user_profiles'), ('disableUserPreference', 'disabled_user_preferences')] _enabled = [('enableDomainProfile', 'disabled_domain_profiles'), ('enableUserProfile', 'disabled_user_profiles'), ('enableUserPreference', 'disabled_user_preferences')] for (_form_input_name, _key) in _disabled: # Disabled profiles if _form_input_name in form: _values = set(_as.get(_key, [])) _v = form_utils.get_multi_values_from_api(form=form, input_name=_form_input_name, to_string=True, to_lowercase=True) _values.update(_v) _as[_key] = _values for (_form_input_name, _key) in _enabled: # Enabled profiles if _form_input_name in form: _values = set(_as.get(_key, [])) _v = form_utils.get_multi_values_from_api(form=form, input_name=_form_input_name, to_string=True, to_lowercase=True) for i in _v: _values.discard(i) _as[_key] = _values _as = sqlutils.account_settings_dict_to_string(_as) params['settings'] = _as if session.get('is_global_admin'): # Quota kv = form_utils.get_form_dict(form=form, input_name='quota', key_name='maxquota', is_integer=True) params.update(kv) # Number of users kv = form_utils.get_form_dict(form=form, input_name='numberOfUsers', key_name='mailboxes', is_integer=True) params.update(kv) # Number of aliases kv = form_utils.get_form_dict(form=form, input_name='numberOfAliases', key_name='aliases', is_integer=True) params.update(kv) # # Sender/Recipient bcc # sbcc = None rbcc = None if 'senderBcc' in form: sbcc = form_utils.get_single_value(form=form, input_name='senderBcc', is_email=True, to_lowercase=True) if 'recipientBcc': rbcc = form_utils.get_single_value(form=form, input_name='recipientBcc', is_email=True, to_lowercase=True) # Catch-all _c = form_utils.get_single_value(form=form, input_name='catchall', to_lowercase=True) _addresses = _c.strip(' ').split(',') qr = __update_catchall(domain=domain, catchall=_addresses, conn=conn) if not qr[0]: return qr # Outbound relay _relay = form_utils.get_single_value(form=form, input_name='outboundRelay', to_lowercase=True) qr = sql_lib_general.update_sender_relayhost(account=domain, relayhost=_relay, conn=conn) if not qr[0]: return qr # # Alias domains # if 'aliasDomains' in form: # Reset alias domains _alias_domains = form_utils.get_multi_values_from_api(form=form, input_name='aliasDomains', is_domain=True) _qr = __reset_alias_domains(domain=domain, alias_domains=_alias_domains, conn=conn) if not _qr[0]: return _qr else: _new_alias_domains = [] _removed_alias_domains = [] if 'addAliasDomain' in form: _new_alias_domains = form_utils.get_multi_values_from_api(form=form, input_name='addAliasDomain', is_domain=True) if 'removeAliasDomain' in form: _removed_alias_domains = form_utils.get_multi_values_from_api(form=form, input_name='removeAliasDomain', is_domain=True) _qr = __update_alias_domains(domain=domain, new_alias_domains=_new_alias_domains, removed_alias_domains=_removed_alias_domains, conn=conn) if not _qr[0]: return _qr if not (params or sbcc or rbcc): return True, try: sql_vars = {'domain': domain} if params: params['modified'] = iredutils.get_gmttime() conn.update('domain', vars=sql_vars, where='domain=$domain', **params) msg = 'Update domain profile ({}): {}'.format(domain, ', '.join(list(params.keys()))) log_activity(msg=msg, admin=session.get('username'), domain=domain, event='update') if sbcc: qr = __update_sender_bcc(domain=domain, bcc_address=sbcc, conn=conn) if qr[0]: log_activity(msg='Update domain profile (%s): sender bcc' % domain, admin=session.get('username'), domain=domain, event='update') else: return qr if rbcc: qr = __update_recipient_bcc(domain=domain, bcc_address=rbcc, conn=conn) if qr[0]: log_activity(msg='Update domain profile (%s): recipient bcc' % domain, admin=session.get('username'), domain=domain, event='update') else: return qr return True, except Exception as e: return False, e @decorators.require_domain_access def api_update_domain_admins(domain, form, conn=None): """Update domain admins. @addAdmin - Add new domain admin. Multiple admins must be separated by comma. @removeAdmin - Remove existing domain admin. Multiple admins must be separated by comma. @removeAllAdmins - Remove all existing domain admins. Notes: - global admin can promote standalone admins to be a domain admin (or delete). - normal domain admin can promote mail user to be a domain admin (or delete). """ domain = str(domain).lower() if not conn: _wrap = SQLWrap() conn = _wrap.conn if 'removeAllAdmins' in form: try: if session.get('is_global_admin'): # Remove all directly conn.delete('domain_admins', vars={'domain': domain}, where='domain=$domain') return True, else: # Remove admins under control. managed_domains = [] _qr = sql_lib_admin.get_managed_domains(conn=conn, admin=session.get('username'), domain_name_only=True, listed_only=True) if _qr[0]: managed_domains = _qr[1] # Get all current domain admins all_existing_admins = [] _qr = get_domain_admin_addresses(domain=domain, conn=conn) if _qr[0]: all_existing_admins = _qr[1] # Remove admins under control removed_admins = [v for v in all_existing_admins if v.split('@', 1)[-1] in managed_domains] conn.delete('domain_admins', vars={'domain': domain, 'admins': removed_admins}, where='domain=$domain AND username IN $admins') for adm in removed_admins: sql_lib_admin.revoke_admin_privilege_if_no_managed_domains(admin=adm, conn=conn) log_activity(msg='Remove all domain (%s) admins' % domain, admin=session.get('username'), domain=domain, event='update') except Exception as e: return False, repr(e) else: if not ('addAdmin' in form or 'removeAdmin' in form): return True, # Get all standalone admins if API requestor is a global admin. existing_standalone_admins = [] # Get all managed domains if API requestor is not a global admin. managed_domains = [] if session.get('is_global_admin'): try: qr = conn.select('admin', what='username') for i in qr: existing_standalone_admins.append(str(i.username).lower()) except Exception as e: return False, repr(e) else: # Get managed domains qr = sql_lib_admin.get_managed_domains(conn=conn, admin=session.get('username'), domain_name_only=True, listed_only=True) if qr[0]: managed_domains = qr[1] # Get new separated admins new_standalone_admins = [] # Get new admins which are mail user accounts new_user_admins = [] if 'addAdmin' in form: kv = form_utils.get_form_dict(form=form, input_name='addAdmin') # Convert comma-separated values to list. if kv: _vs = [str(v).strip().lower() for v in kv['addAdmin'].split(',') if iredutils.is_email(v)] new_admins = [str(v).lower().strip() for v in _vs if iredutils.is_email(v)] if new_admins: sql_vars = { 'domain': domain, 'new_admins': new_admins, } if session.get('is_global_admin'): new_standalone_admins = [v for v in new_admins if v in existing_standalone_admins] qr = conn.select('mailbox', vars=sql_vars, what='username,isadmin', where='username IN $new_admins') else: qr = conn.select('mailbox', vars={'managed_domains': managed_domains, 'new_admins': new_admins}, what='username,isadmin', where='domain IN $managed_domains AND username IN $new_admins') for r in qr: _user = str(r.username).lower() new_user_admins.append(_user) if r.isadmin != 1: conn.update('mailbox', vars={'user': _user}, isadmin=1, where='username=$user') _all_new_admins = new_standalone_admins + new_user_admins for _admin in _all_new_admins: try: conn.insert('domain_admins', username=_admin, domain=domain, active=1) except Exception as e: if e.__class__.__name__ == 'IntegrityError': pass else: return False, repr(e) if _all_new_admins: msg = 'Add new domain ({}) admins: {}'.format(domain, ', '.join(_all_new_admins)) log_activity(msg=msg, admin=session.get('username'), domain=domain, event='update') if 'removeAdmin' in form: kv = form_utils.get_form_dict(form=form, input_name='removeAdmin') # Convert comma-separated services to list. if kv: _vs = [str(v).strip().lower() for v in kv['removeAdmin'].split(',') if iredutils.is_email(v)] removed_admins = [str(v).lower().strip() for v in _vs if iredutils.is_email(v)] if removed_admins: if session.get('is_global_admin'): # Remove from `vmail.domain_admins` directly. try: conn.delete('domain_admins', vars={'domain': domain, 'admins': removed_admins}, where='domain=$domain AND username IN $admins') msg = 'Remove domain ({}) admins: {}'.format(domain, ', '.join(removed_admins)) log_activity(msg=msg, admin=session.get('username'), domain=domain, event='update') except Exception as e: return False, repr(e) else: # Check whether admins exist try: qr = conn.select('mailbox', vars={'managed_domains': managed_domains, 'removed_admins': removed_admins}, what='username', where='domain IN $managed_domains AND username IN $removed_admins') removed_existing_admins = [] for i in qr: _user = str(i.username).lower() removed_existing_admins.append(_user) if removed_existing_admins: conn.delete('domain_admins', vars={'domain': domain, 'admins': removed_existing_admins}, where='domain=$domain AND username IN $admins') msg = 'Remove domain ({}) admins: {}'.format(domain, ', '.join(removed_existing_admins)) log_activity(msg=msg, admin=session.get('username'), domain=domain, event='update') except Exception as e: return False, repr(e) for adm in removed_admins: sql_lib_admin.revoke_admin_privilege_if_no_managed_domains(admin=adm, conn=conn) return True, def get_first_char_of_all_domains(conn=None): """Get first character of all domains.""" if not conn: _wrap = SQLWrap() conn = _wrap.conn admin = session.get('username') chars = [] try: if sql_lib_general.is_global_admin(admin=admin, conn=conn): qr = conn.select('domain', what='SUBSTRING(domain FROM 1 FOR 1) AS first_char', group='first_char') else: qr = conn.query("""SELECT SUBSTRING(domain.domain FROM 1 FOR 1) AS first_char FROM domain LEFT JOIN domain_admins ON (domain.domain=domain_admins.domain) WHERE domain_admins.username=$admin GROUP BY first_char""", vars={'admin': admin}) if qr: chars = [i.first_char.upper() for i in qr] chars.sort() return True, chars except Exception as e: logger.error(e) return False, repr(e)