def get_account_outbound_wblist(conn, account, whitelist=True, blacklist=True): """Get outbound white/blacklists of specified account.""" sql_where = 'users.email=%s AND users.id=outbound_wblist.sid AND outbound_wblist.rid = mailaddr.id' % sqlquote(account) if whitelist and not blacklist: sql_where += " AND outbound_wblist.wb='W'" if not whitelist and blacklist: sql_where += " AND outbound_wblist.wb='B'" wl = [] bl = [] try: sql = """SELECT mailaddr.email, outbound_wblist.wb FROM mailaddr, users, outbound_wblist WHERE %s""" % sql_where qr = conn.execute(sql) sql_records = qr.fetchall() for r in sql_records: (_addr, _wb) = r if _wb == 'W': wl.append(utils.bytes2str(_addr)) else: bl.append(utils.bytes2str(_addr)) except Exception as e: return (False, e) return (True, {'whitelist': wl, 'blacklist': bl})
def __get_primary_and_alias_domains(domain, with_primary_domain=True, conn=None): """Get list of domainName and domainAliasName by quering domainName. >>> __get_primary_and_alias_domains(domain='example.com') (True, ['example.com', 'aliasdomain01.com', 'aliasdomain02.com', ...]) """ domain = domain.strip().lower() if not utils.is_domain(domain): return (False, 'INVALID_DOMAIN_NAME') try: if not conn: _wrap = LDAPWrap() conn = _wrap.conn dn = 'domainName=%s,%s' % (domain, settings.iredmail_ldap_basedn) qr = conn.search_s(dn, ldap.SCOPE_BASE, '(&(objectClass=mailDomain)(domainName=%s))' % domain, ['domainName', 'domainAliasName']) if qr: (_dn, _ldif) = qr[0] _ldif = utils.bytes2str(_ldif) all_domains = _ldif.get('domainName', []) + _ldif.get('domainAliasName', []) if not with_primary_domain: all_domains.remove(domain) return (True, all_domains) else: return (False, 'INVALID_DOMAIN_NAME') except Exception as e: return (False, repr(e))
def get_primary_and_alias_domains(conn, domain): """Query LDAP to get all available alias domain names of given domain. Return list of alias domain names. @conn -- ldap connection cursor @domain -- domain name """ if not utils.is_domain(domain): return [] try: _f = "(&(objectClass=mailDomain)(|(domainName={})(domainAliasName={})))".format(domain, domain) qr = conn.search_s(settings.ldap_basedn, 1, # 1 == ldap.SCOPE_ONELEVEL _f, ['domainName', 'domainAliasName']) if qr: (_dn, _ldif) = qr[0] _ldif = utils.bytes2str(_ldif) _all_domains = _ldif.get('domainName', []) + _ldif.get('domainAliasName', []) return list(set(_all_domains)) except Exception as e: # Log and return if LDAP error occurs logger.error("Error while querying alias domains of domain ({}): {}".format(domain, repr(e))) return []
def get_id_of_possible_cidr_network(conn, client_address): """Return list of `mailaddr.id` which are CIDR network addresses.""" ids = [] if not client_address: logger.debug("No client address.") return ids try: _ip = ipaddress.ip_address(client_address) if _ip.version == 4: first_field = client_address.split(".")[0] sql_cidr = first_field + r".%%" else: return ids except: return ids sql = """SELECT id, email FROM mailaddr WHERE email LIKE %s ORDER BY priority DESC""" % sqlquote(sql_cidr) logger.debug("[SQL] Query CIDR network: \n{}".format(sql)) try: qr = conn.execute(sql) qr_cidr = qr.fetchall() except Exception as e: logger.error("Error while querying CIDR network: {}, SQL: \n{}".format( repr(e), sql)) return ids if qr_cidr: _cidrs = [(int(r.id), utils.bytes2str(r.email)) for r in qr_cidr] # Get valid CIDR. _ip_networks = set() for (_id, _cidr) in _cidrs: # Verify whether client_address is in CIDR network try: _net = ipaddress.ip_network(_cidr) _ip_networks.add((_id, _net)) except: pass if _ip_networks: _ip = ipaddress.ip_address(client_address) for (_id, _net) in _ip_networks: if _ip in _net: ids.append(_id) logger.debug("IDs of CIDR network(s): {}".format(ids)) return ids
def get_account_ldif(conn, account, query_filter=None, attrs=None): logger.debug("[+] Getting LDIF data of account: {}".format(account)) if not query_filter: query_filter = '(&' + \ '(!(domainStatus=disabled))' + \ '(|(mail={account})(shadowAddress={account}))'.format(account=account) + \ '(|' + \ '(objectClass=mailUser)' + \ '(objectClass=mailList)' + \ '(objectClass=mailAlias)' + \ '))' logger.debug("search: base_dn={}, scope=SUBTREE, filter={}, " "attributes={}".format( settings.ldap_basedn, query_filter, attrs)) if not isinstance(attrs, list): # Attribute list must be None (search all attributes) or non-empty list attrs = None try: result = conn.search_s(settings.ldap_basedn, ldap.SCOPE_SUBTREE, query_filter, attrs) if result: logger.debug("result: {}".format(repr(result))) (_dn, _ldif) = result[0] _ldif = utils.bytes2str(_ldif) return (_dn, _ldif) else: logger.debug('No such account.') return (None, None) except Exception as e: logger.debug("<!> ERROR: {}".format(repr(e))) return (None, None)
def get_existing_maillists(domains=None, conn=None): """Get existing mailing lists. :param domains: a list/tuple/set of valid domain names. Used if you want to get mailing lists under given domains. :param conn: sql connection cursor. """ if domains: domains = [str(d).lower() for d in domains if utils.is_domain(d)] if not conn: _wrap = LDAPWrap() conn = _wrap.conn _filter = '(&(objectClass=mailList)(enabledService=mlmmj))' if domains: _f = '(|' for d in domains: _f += '(mail=*@%s)(shadowAddress=*@%s)' % (d, d) _f += ')' _filter = '(&' + _filter + _f + ')' existing_lists = set() try: qr = conn.search_s(settings.iredmail_ldap_basedn, ldap.SCOPE_SUBTREE, _filter, ['mail']) for (_dn, _ldif) in qr: _ldif = utils.bytes2str(_ldif) _addresses = _ldif.get('mail', []) _addresses = [str(i).lower() for i in _addresses] existing_lists.update(_addresses) return (True, existing_lists) except Exception as e: return (False, repr(e))
def get_alias_target_domain(alias_domain, conn, include_backupmx=True): """Query target domain of given alias domain name.""" alias_domain = str(alias_domain).lower() if not utils.is_domain(alias_domain): logger.debug("Given alias_domain {} is not an valid domain name.".format(alias_domain)) return None try: _filter = '(&(objectClass=mailDomain)(accountStatus=active)' _filter += '(domainAliasName=%s)' % alias_domain if not include_backupmx: _filter += '(!(domainBackupMX=yes))' _filter += ')' logger.debug("[LDAP] query target domain of given alias domain: {}\n" "[LDAP] query filter: {}".format(alias_domain, _filter)) qr = conn.search_s(settings.ldap_basedn, 1, # 1 == ldap.SCOPE_ONELEVEL _filter, ['domainName']) logger.debug("result: {}".format(repr(qr))) if qr: (_dn, _ldif) = qr[0] _ldif = utils.bytes2str(_ldif) _domain = _ldif['domainName'][0] return _domain except ldap.NO_SUCH_OBJECT: pass except Exception as e: logger.error("<!> Error while querying alias domain: {}".format(repr(e))) return None
args = sys.argv[1:] if "-A" in args: # Query db to get all mailing lists. if backend == "sql": qr = conn.select("maillists", what="address", where="active=1") for i in qr: addr = i["address"].lower() mls.append(addr) elif backend == "ldap": _filter = "(&(objectClass=mailList)(accountStatus=active)(enabledService=mlmmj))" try: qr = conn.search_s(settings.iredmail_ldap_basedn, ldap.SCOPE_SUBTREE, _filter, ["mail"]) for (_dn, _ldif) in qr: mls += [bytes2str(i).lower() for i in _ldif["mail"]] except Exception as e: print("Error while querying: {}".format(repr(e))) if not mls: print("No mailing list found. Abort.") sys.exit() else: # Get domain names and mailing lists. domains = [i.lower() for i in args if is_domain(i)] mls = [i.lower() for i in args if is_email(i)] # Query SQL/LDAP to get all mailing lists under specified domains. if domains: print("Querying mailing lists under given domain(s): {}".format( ", ".join(domains)))
def add_maillist(mail, form, conn=None): """Add required LDAP object to add a mailing list account.""" mail = str(mail).lower() (_, domain) = mail.split('@', 1) if not utils.is_email(mail): return (False, 'INVALID_EMAIL') if not conn: _wrap = LDAPWrap() conn = _wrap.conn if not is_domain_exists(domain=domain): return (False, 'NO_SUCH_DOMAIN') if is_email_exists(mail=mail): return (False, 'ALREADY_EXISTS') name = form.get('name', '') mlid = __get_new_mlid(conn=conn) domain_status = 'disabled' alias_domains = [] dn_domain = 'domainName=%s,%s' % (domain, settings.iredmail_ldap_basedn) # Get domain profile. try: qr = conn.search_s(dn_domain, ldap.SCOPE_BASE, '(objectClass=*)') if qr: (_dn, _ldif) = qr[0] _ldif = utils.bytes2str(_ldif) domain_status = _ldif.get('accountStatus', ['active'])[0] alias_domains = _ldif.get('domainAliasName', []) alias_domains = [str(i).lower() for i in alias_domains if utils.is_domain(i)] alias_domains = list(set(alias_domains)) if 'only_moderator_can_post' in form: access_policy = 'moderatorsonly' elif 'only_subscriber_can_post' in form: access_policy = 'moderatorsonly' else: access_policy = None max_message_size = form_utils.get_max_message_size(form) moderators = form.get('moderators', '').split(',') dn_ml = 'mail=%s,ou=Groups,domainName=%s,%s' % (mail, domain, settings.iredmail_ldap_basedn) ldif_ml = __ldif_ml(mail=mail, mlid=mlid, name=name, access_policy=access_policy, max_message_size=max_message_size, alias_domains=alias_domains, domain_status=domain_status, moderators=moderators) conn.add_s(dn_ml, ldif_ml) logger.info('Created: {0}.'.format(mail)) return (True,) except Exception as e: logger.error('Error while creating {0}: {1}'.format(mail, e)) return (False, repr(e))
def add_wblist(conn, 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 utils.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 utils.is_valid_amavisd_address(s)} else: wl_senders = set() # Whitelist has higher priority, don't include whitelisted sender. if bl_senders: bl_senders = {str(s).lower() for s in bl_senders if utils.is_valid_amavisd_address(s)} else: bl_senders = set() if wl_rcpts: wl_rcpts = {str(s).lower() for s in wl_rcpts if utils.is_valid_amavisd_address(s)} else: wl_rcpts = set() if bl_rcpts: bl_rcpts = {str(s).lower() for s in bl_rcpts if utils.is_valid_amavisd_address(s)} else: bl_rcpts = set() 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 = get_user_record(conn=conn, account=account) if qr[0]: user_id = qr[1]['id'] else: return qr # Delete old records if flush_before_import: # user_id = wblist.rid conn.execute('DELETE FROM wblist WHERE rid=%s' % sqlquote(user_id)) # user_id = outbound_wblist.sid conn.execute('DELETE FROM outbound_wblist WHERE sid=%s' % sqlquote(user_id)) if not all_addresses: return (True, ) # Insert all senders into `amavisd.mailaddr` create_mailaddr(conn=conn, addresses=all_addresses) # Get `mailaddr.id` of senders sender_records = {} if sender_addresses: sql = "SELECT id, email FROM mailaddr WHERE email IN %s" % sqlquote(list(sender_addresses)) qr = conn.execute(sql) sql_records = qr.fetchall() for r in sql_records: (_id, _email) = r sender_records[utils.bytes2str(_email)] = int(_id) del qr # Get `mailaddr.id` of recipients rcpt_records = {} if rcpt_addresses: sql = "SELECT id, email FROM mailaddr WHERE email IN %s" % sqlquote(list(rcpt_addresses)) qr = conn.execute(sql) sql_records = qr.fetchall() for r in sql_records: (_id, _email) = r rcpt_records[utils.bytes2str(_email)] = int(_id) del qr # Remove existing records of current submitted records then insert new. try: if sender_records: sql = "DELETE FROM wblist WHERE rid=%d AND sid IN %s" % (user_id, sqlquote(list(sender_records.values()))) conn.execute(sql) if rcpt_records: sql = "DELETE FROM outbound_wblist WHERE sid=%d AND rid IN %s" % (user_id, sqlquote(list(rcpt_records.values()))) conn.execute(sql) except Exception as e: return (False, 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: for v in values: try: conn.execute("INSERT INTO wblist (sid, rid, wb) VALUES ({}, {}, {})".format(sqlquote(v['sid']), sqlquote(v['rid']), sqlquote(v['wb']))) except Exception as e: logger.error(e) if rcpt_values: for v in rcpt_values: try: conn.execute("INSERT INTO outbound_wblist (sid, rid, wb) VALUES ({}, {}, {})".format(sqlquote(v['sid']), sqlquote(v['rid']), sqlquote(v['wb']))) except Exception as e: logger.error(e) except Exception as e: return (False, e) return (True, )
def delete_wblist(conn, account, wl_senders=None, bl_senders=None, wl_rcpts=None, bl_rcpts=None): if not utils.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 utils.is_valid_amavisd_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 utils.is_valid_amavisd_address(s)}) if wl_rcpts: wl_rcpts = list({str(s).lower() for s in wl_rcpts if utils.is_valid_amavisd_address(s)}) if bl_rcpts: bl_rcpts = list({str(s).lower() for s in bl_rcpts if utils.is_valid_amavisd_address(s)}) # Get account id from `amavisd.users` qr = get_user_record(conn=conn, 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). wl_smails = [] wl_rmails = [] bl_smails = [] bl_rmails = [] try: # Get `mailaddr.id` for wblist senders if wl_senders: sids = [] sql = "SELECT id, email FROM mailaddr WHERE email in %s" % sqlquote(wl_senders) qr = conn.execute(sql) sql_records = qr.fetchall() for r in sql_records: (_id, _email) = r sids.append(int(_id)) wl_smails.append(utils.bytes2str(_email)) if sids: conn.execute("DELETE FROM wblist WHERE rid={} AND sid IN {} AND wb='W'".format(sqlquote(user_id), sqlquote(sids))) if bl_senders: sids = [] sql = "SELECT id, email FROM mailaddr WHERE email IN %s" % sqlquote(bl_senders) qr = conn.execute(sql) sql_records = qr.fetchall() for r in sql_records: (_id, _email) = r sids.append(int(_id)) bl_smails.append(utils.bytes2str(_email)) if sids: conn.execute("DELETE FROM wblist WHERE rid={} AND sid IN {} AND wb='B'".format(sqlquote(user_id), sqlquote(sids))) if wl_rcpts: rids = [] sql = "SELECT id, email FROM mailaddr WHERE email IN %s" % sqlquote(wl_rcpts) qr = conn.execute(sql) sql_records = qr.fetchall() for r in sql_records: (_id, _email) = r rids.append(int(_id)) wl_rmails.append(utils.bytes2str(_email)) if rids: conn.execute("DELETE FROM outbound_wblist WHERE sid={} AND rid IN {} AND wb='W'".format(sqlquote(user_id), sqlquote(rids))) if bl_rcpts: rids = [] sql = "SELECT id, email FROM mailaddr WHERE email IN %s" % sqlquote(bl_rcpts) qr = conn.execute(sql) sql_records = qr.fetchall() for r in sql_records: (_id, _email) = r rids.append(int(_id)) bl_rmails.append(utils.bytes2str(_email)) if rids: conn.execute("DELETE FROM outbound_wblist WHERE sid={} AND rid IN {} AND wb='B'".format(sqlquote(user_id), sqlquote(rids))) except Exception as e: return (False, str(e)) return (True, {'wl_senders': wl_smails, 'wl_rcpts': wl_rmails, 'bl_senders': bl_smails, 'bl_rcpts': bl_rmails})
def restriction(**kwargs): sasl_username = kwargs['sasl_username'] recipient = kwargs['recipient_without_ext'] recipient_ldif = kwargs['recipient_ldif'] if sasl_username == recipient: return SMTP_ACTIONS[ 'default'] + ' (sasl_username == recipient, not a mail list account)' # Pass if recipient doesn't exist (no LDIF data) if not recipient_ldif: return SMTP_ACTIONS[ 'default'] + ' (Recipient is not a local account - no LDIF data)' # Pass if recipient is not a mailing list account if 'mailList' not in recipient_ldif['objectClass']: return SMTP_ACTIONS[ 'default'] + ' (Recipient is not a mailing list account)' # Reject if mailing list is disabled. # NOTE: Postfix doesn't query account status of mailing list, so we need # to do it here. if recipient_ldif.get('accountStatus', []) != ['active']: logger.debug('Recipient (mailing list) is disabled, message rejected.') return SMTP_ACTIONS['reject'] # Get access policy policy = recipient_ldif.get('accessPolicy', [MAILLIST_POLICY_PUBLIC])[0].lower() # Log access policy logger.debug('Access policy of mailing list ({}): {}'.format( recipient, policy)) if policy == MAILLIST_POLICY_PUBLIC: return SMTP_ACTIONS[ 'default'] + ' (Access policy: %s, no restriction)' % MAILLIST_POLICY_PUBLIC elif policy == 'allowedonly': # 'allowedonly' is policy name used by old iRedAPD releases. policy = MAILLIST_POLICY_MODERATORS if 'mlmmj' in recipient_ldif.get('enabledService', []): if policy in [MAILLIST_POLICY_MEMBERSONLY, MAILLIST_POLICY_MODERATORS]: logger.debug( 'Recipient is a mlmmj mailing list, let mlmmj handle the ACL.') return SMTP_ACTIONS['default'] conn = kwargs['conn_vmail'] sender = kwargs['sender_without_ext'] sender_domain = kwargs['sender_domain'] recipient_domain = kwargs['recipient_domain'] # Get primary recipient domain and all its alias domains valid_rcpt_domains = conn_utils.get_primary_and_alias_domains( conn=conn, domain=recipient_domain) logger.debug( 'Primary and all alias domain names of recipient domain ({}): {}'. format(recipient_domain, ', '.join(valid_rcpt_domains))) # # No matter what access policy it has, bypass explictly allowed senders # explicitly_allowed_senders = recipient_ldif.get('listAllowedUser', []) # Check sender and sender domains if sender in explicitly_allowed_senders: return SMTP_ACTIONS[ 'default'] + ' (Sender is allowed explicitly: %s)' % sender elif sender_domain in explicitly_allowed_senders or '*@' + sender_domain in explicitly_allowed_senders: return SMTP_ACTIONS[ 'default'] + ' (Sender domain is allowed explicitly: %s)' % sender_domain # Check all possible sender domains (without checking sender alias domains) _possible_sender_domains = [sender_domain] _domain_parts = sender_domain.split('.') for _ in _domain_parts: _possible_sender_domains += ['.' + '.'.join(_domain_parts)] _domain_parts.pop(0) logger.debug('Sender domain and sub-domains: %s' % ', '.join(_possible_sender_domains)) if set(_possible_sender_domains) & set(explicitly_allowed_senders): return SMTP_ACTIONS[ 'default'] + ' (Sender domain or its sub-domain is explicitly allowed)' logger.debug( 'Sender is not explicitly allowed, perform extra LDAP query to check access.' ) # Get domain dn. dn_rcpt_domain = 'domainName=' + recipient_domain + ',' + settings.ldap_basedn # Verify access policies if policy in [MAILLIST_POLICY_DOMAIN, MAILLIST_POLICY_SUBDOMAIN]: if policy == MAILLIST_POLICY_DOMAIN: # Bypass all users under the same domain. if sender_domain in valid_rcpt_domains: logger.info( 'Sender domain ({}) is allowed by access policy of mailing list: {}.' .format(sender_domain, policy)) return SMTP_ACTIONS['default'] elif policy == MAILLIST_POLICY_SUBDOMAIN: # Bypass all users under the same domain and all sub domains. for d in valid_rcpt_domains: if sender_domain == d or sender_domain.endswith('.' + d): logger.info( 'Sender domain ({}) is allowed by access policy of mailing list: {}.' .format(d, policy)) return SMTP_ACTIONS['default'] return SMTP_ACTIONS['reject_not_authorized'] elif policy == MAILLIST_POLICY_MEMBERSONLY: # Get all members of mailing list. _f = '(&' + \ '(accountStatus=active)(memberOfGroup=%s)' % (recipient) + \ '(|(objectclass=mailUser)(objectClass=mailExternalUser))' + \ ')' # Get both mail and shadowAddress. search_attrs = ['mail', 'shadowAddress'] logger.debug('search base dn: %s' % dn_rcpt_domain) logger.debug('search scope: SUBTREE') logger.debug('search filter: %s' % _f) logger.debug('search attributes: %s' % ', '.join(search_attrs)) qr = conn.search_s(dn_rcpt_domain, 2, _f, search_attrs) allowed_senders = [] for (_dn, _ldif) in qr: _ldif = utils.bytes2str(_ldif) for k in search_attrs: allowed_senders += _ldif.get(k, []) if sender in allowed_senders: logger.info( 'Sender ({}) is allowed by access policy of mailing list: {}.'. format(sender, policy)) return SMTP_ACTIONS['default'] return SMTP_ACTIONS['reject_not_authorized'] elif policy == MAILLIST_POLICY_MEMBERSANDMODERATORSONLY: # Get both members and moderators. _f = '(|' + \ '(&(memberOfGroup=%s)(|(objectClass=mailUser)(objectClass=mailExternalUser)))' % recipient + \ '(&(objectclass=mailList)(mail=%s))' % recipient + \ ')' search_attrs = ['mail', 'shadowAddress', 'listAllowedUser'] logger.debug('search base dn: %s' % dn_rcpt_domain) logger.debug('search scope: SUBTREE') logger.debug('search filter: %s' % _f) logger.debug('search attributes: %s' % ', '.join(search_attrs)) allowed_senders = [] try: qr = conn.search_s(dn_rcpt_domain, 2, _f, search_attrs) logger.debug('search result: %s' % repr(qr)) # Collect values of all search attributes for (_dn, _ldif) in qr: _ldif = utils.bytes2str(_ldif) for k in search_attrs: allowed_senders += _ldif.get(k, []) if sender in allowed_senders: logger.info( 'Sender ({}) is allowed by access policy of mailing list: {}.' .format(sender, policy)) return SMTP_ACTIONS['default'] except Exception as e: _msg = 'Error while querying allowed senders of mailing list (access policy: {}): {}'.format( MAILLIST_POLICY_MEMBERSANDMODERATORSONLY, repr(e)) logger.error(_msg) return SMTP_ACTIONS['default'] + ' (%s)' % _msg return SMTP_ACTIONS['reject_not_authorized'] elif policy == MAILLIST_POLICY_MODERATORS: # If sender is hosted on local server, check per-user alias addresses # and alias domains. # Already checked `listAllowedUser` above before checking any access # policy, so it's safe to reject here.. if not kwargs['sasl_username']: return SMTP_ACTIONS['reject_not_authorized'] # Remove '*@domain.com' allowed_senders = [ s for s in explicitly_allowed_senders if not s.startswith('*@') ] # Separate email addresses and domain names _users = [] _domains = [] for _as in allowed_senders: if utils.is_email(_as): if _as.endswith('@' + recipient_domain): _users.append(_as) # We will add both `_as` and its shadowAddress back later. allowed_senders.remove(_as) else: if _as.startswith('.'): _domains.append(_as.lstrip('.')) else: _domains.append(_as) # We will add both `_as` and its alias domains back later. allowed_senders.remove(_as) logger.debug('Allowed users: %s' % ', '.join(_users)) logger.debug('Allowed domains: %s' % ', '.join(_domains)) # Get per-user alias addresses. if _users: logger.debug( "[+] Getting per-account alias addresses of allowed senders.") _basedn = 'ou=Users,' + dn_rcpt_domain _f = '(&(objectClass=mailUser)(enabledService=shadowaddress)(|' for i in _users: _f += '(mail={})(shadowAddress={})'.format(i, i) _f += '))' _search_attrs = ['mail', 'shadowAddress'] logger.debug('base dn: %s' % _basedn) logger.debug('search scope: ONELEVEL') logger.debug('search filter: %s' % _f) logger.debug('search attributes: %s' % ', '.join(_search_attrs)) qr = conn.search_s(_basedn, 1, _f, _search_attrs) logger.debug('query result: %s' % str(qr)) for (_dn, _ldif) in qr: _ldif = utils.bytes2str(_ldif) for k in _search_attrs: allowed_senders += _ldif.get(k, []) if _domains: logger.debug( '[+] Getting alias domains of allowed sender (sub-)domains.') _basedn = settings.ldap_basedn _f = '(&(objectClass=mailDomain)(enabledService=domainalias)(|' for i in _domains: _f += '(domainName={})(domainAliasName={})'.format(i, i) _f += '))' _search_attrs = ['domainName', 'domainAliasName'] logger.debug('base dn: %s' % _basedn) logger.debug('search scope: ONELEVEL') logger.debug('search filter: %s' % _f) logger.debug('search attributes: %s' % ', '.join(_search_attrs)) qr = conn.search_s(_basedn, 1, _f, _search_attrs) logger.debug('result: %s' % str(qr)) for (_dn, _ldif) in qr: _all_domains = [] for k in _search_attrs: _all_domains += _ldif.get(k, []) for domain in _all_domains: if domain in _domains: # Add original domain and alias domains allowed_senders += [d for d in _all_domains] if sender in allowed_senders or sender_domain in allowed_senders: logger.info( 'Sender ({}) is allowed by access policy of mailing list: {}.'. format(sender, policy)) return SMTP_ACTIONS['default'] else: return SMTP_ACTIONS['reject_not_authorized'] return SMTP_ACTIONS[ 'default'] + ' (Unknown access policy: %s, no restriction)' % policy