def restriction(**kwargs): # Bypass outgoing emails. if kwargs['sasl_username']: logger.debug( 'Found SASL username, bypass greylisting for outbound email.') return SMTP_ACTIONS['default'] client_address = kwargs['client_address'] if utils.is_trusted_client(client_address): return SMTP_ACTIONS['default'] sender = kwargs['sender_without_ext'] sender_domain = kwargs['sender_domain'] recipient = kwargs['recipient_without_ext'] recipient_domain = kwargs['recipient_domain'] policy_recipients = utils.get_policy_addresses_from_email(mail=recipient) policy_senders = utils.get_policy_addresses_from_email(mail=sender) policy_senders += [client_address] # If recipient_domain is an alias domain name, we should check the target # domain. conn_vmail = kwargs['conn_vmail'] alias_target_rcpt_domain = get_alias_target_domain( alias_domain=recipient_domain, conn=conn_vmail) if alias_target_rcpt_domain: _addr = recipient.split('@', 1)[0] + '@' + alias_target_rcpt_domain policy_recipients += utils.get_policy_addresses_from_email(mail=_addr) if utils.is_ipv4(client_address): # Add wildcard ip address: xx.xx.xx.*. policy_senders += client_address.rsplit('.', 1)[0] + '.*' # Get object of IP address type _ip_object = ipaddress.ip_address(client_address) conn_iredapd = kwargs['conn_iredapd'] # Check greylisting whitelists if _is_whitelisted(conn=conn_iredapd, senders=policy_senders, recipients=policy_recipients, client_address=client_address, ip_object=_ip_object): return SMTP_ACTIONS['default'] # Check greylisting settings if not _should_be_greylisted_by_setting(conn=conn_iredapd, recipients=policy_recipients, senders=policy_senders, client_address=client_address, ip_object=_ip_object): return SMTP_ACTIONS['default'] # Bypass if sender server is listed in SPF DNS record of sender domain. if settings.GREYLISTING_BYPASS_SPF: if dnsspf.is_allowed_server_in_spf(sender_domain=sender_domain, ip=client_address): logger.info('[{}] Bypass greylisting due to SPF match ({})'.format( client_address, sender_domain)) return SMTP_ACTIONS['default'] if _client_address_passed_in_tracking(conn=conn_iredapd, client_address=client_address): # Update expire time _now = int(time.time()) _new_expire_time = _now + settings.GREYLISTING_AUTH_TRIPLET_EXPIRE * 24 * 60 * 60 _sql = """UPDATE greylisting_tracking SET record_expired=%d WHERE client_address=%s AND passed=1""" % ( _new_expire_time, sqlquote(client_address)) logger.debug('[SQL] Update expire time of passed client: \n%s' % _sql) conn_iredapd.execute(_sql) return SMTP_ACTIONS['default'] # check greylisting tracking. if _should_be_greylisted_by_tracking(conn=conn_iredapd, sender=sender, sender_domain=sender_domain, recipient=recipient, recipient_domain=recipient_domain, client_address=client_address): if settings.GREYLISTING_TRAINING_MODE: logger.debug("Running in greylisting training mode, bypass.") else: return action_greylisting return SMTP_ACTIONS['default']
def restriction(**kwargs): sasl_username = kwargs['sasl_username'] sasl_username_user = sasl_username.split('@', 1)[0] sasl_username_domain = kwargs['sasl_username_domain'] sender = kwargs['sender_without_ext'] sender_name = '' sender_domain = '' if sender: (sender_name, sender_domain) = sender.split('@', 1) # Leave this to plugin `reject_null_sender`. if sasl_username and not sender: return SMTP_ACTIONS['default'] recipient_domain = kwargs['recipient_domain'] client_address = kwargs['client_address'] real_sasl_username = sasl_username real_sender = sender conn = kwargs['conn_vmail'] # Check emails sent from external network. if not sasl_username: logger.debug('Not an authenticated sender (no sasl_username).') # Bypass trusted networks. # NOTE: if sender sent email through SOGo, smtp session may not # have `sasl_username`. if is_trusted_client(client_address): logger.debug('Bypass trusted client.') return SMTP_ACTIONS['default'] if not check_forged_sender: logger.debug('Skip forged sender checks.') return SMTP_ACTIONS['default'] # Bypass allowed forged sender. if sender in allowed_forged_senders or \ sender_domain in allowed_forged_senders or \ sender_name + '@*' in allowed_forged_senders: logger.debug('Bypass allowed forged sender.') return SMTP_ACTIONS['default'] _is_local_sender_domain = False if sender_domain == recipient_domain: logger.debug('Sender domain is same as recipient domain.') _is_local_sender_domain = True else: if is_local_domain(conn=conn, domain=sender_domain, include_backupmx=False): logger.debug( 'Sender domain is hosted locally, smtp authentication is required.' ) _is_local_sender_domain = True else: logger.debug('Sender domain is NOT hosted locally.') if _is_local_sender_domain: if settings.CHECK_SPF_IF_LOGIN_MISMATCH: logger.debug( 'Check whether client is allowed smtp server against DNS SPF record.' ) # Query DNS to get IP addresses/networks listed in SPF # record of sender domain, reject if not match. if dnsspf.is_allowed_server_in_spf(sender_domain=sender_domain, ip=client_address): logger.debug( 'Sender server is listed in DNS SPF record, bypassed.') return SMTP_ACTIONS['default'] else: logger.debug( 'Sender server is NOT listed in DNS SPF record.') logger.debug('Sender is considered as forged, rejecting') return SMTP_ACTIONS['reject_forged_sender'] else: return SMTP_ACTIONS['default'] # Check emails sent by authenticated users. logger.debug('Sender: %s, SASL username: %s' % (sender, sasl_username)) if sender == sasl_username: logger.debug('SKIP: sender == sasl username.') return SMTP_ACTIONS['default'] # # sender != sasl_username # # If no access settings available, reject directly. if not (allowed_senders or is_strict or allow_list_member): logger.debug('No allowed senders in config file.') return action_reject # Check explicitly allowed senders if allowed_senders: logger.debug('Allowed SASL senders: %s' % ', '.join(allowed_senders)) if sasl_username in allowed_senders: logger.debug('Sender SASL username is explicitly allowed.') return SMTP_ACTIONS['default'] elif sasl_username_domain in allowed_senders: logger.debug('Sender domain name is explicitly allowed.') return SMTP_ACTIONS['default'] elif ('@' + sasl_username_domain in allowed_senders) or ('@.' in allowed_senders): # Restrict to send as users under SAME domain if sasl_username_domain == sender_domain: return SMTP_ACTIONS['default'] else: # Note: not reject email here, still need to check other access settings. logger.debug( 'Sender is not allowed to send email as other user (ALLOWED_LOGIN_MISMATCH_SENDERS).' ) # Check whether sender is a member of mlmmj mailing list. _check_mlmmj_ml = False # Check alias domains and user alias addresses if is_strict or allow_list_member: if is_strict: logger.debug( 'Apply strict restriction (ALLOWED_LOGIN_MISMATCH_STRICTLY=True).' ) if allow_list_member: logger.debug( 'Apply list/alias member restriction (ALLOWED_LOGIN_MISMATCH_LIST_MEMBER=True).' ) if settings.backend == 'ldap': filter_user_alias = '(&(objectClass=mailUser)(mail=%s)(shadowAddress=%s))' % ( sasl_username, sender) filter_list_member = '(&(objectClass=mailUser)(|(mail=%s)(shadowAddress=%s))(memberOfGroup=%s))' % ( sasl_username, sasl_username, sender) filter_alias_member = '(&(objectClass=mailAlias)(|(mail=%s)(shadowAddress=%s))(mailForwardingAddress=%s))' % ( sender, sender, sasl_username) if is_strict and (not allow_list_member): # Query mail account directly query_filter = filter_user_alias success_msg = 'Sender is an user alias address.' elif (not is_strict) and allow_list_member: query_filter = '(|' + filter_list_member + filter_alias_member + ')' success_msg = 'Sender (%s) is member of mail list/alias (%s).' % ( sasl_username, sender) else: # (is_strict and allow_list_member) query_filter = '(|' + filter_user_alias + filter_list_member + filter_alias_member + ')' success_msg = 'Sender (%s) is an user alias address or list/alias member (%s).' % ( sasl_username, sender) qr = conn_utils.get_account_ldif(conn=conn, account=sasl_username, query_filter=query_filter, attrs=['dn']) (_dn, _ldif) = qr if _dn: logger.debug(success_msg) return SMTP_ACTIONS['default'] else: logger.debug( 'Sender is neither user alias address nor member of list/alias.' ) # Check mlmmj query_filter = "(&(objectClass=mailList)(enabledService=mlmmj)(accountStatus=active))" qr = conn_utils.get_account_ldif(conn=conn, account=sender, query_filter=query_filter, attrs=['dn']) (_dn, _ldif) = qr if _dn: _check_mlmmj_ml = True elif settings.backend in ['mysql', 'pgsql']: if is_strict: # Get per-user alias addresses sql = """SELECT address FROM forwardings WHERE address=%s AND forwarding=%s AND is_alias=1 LIMIT 1""" % (sqlquote(sender), sqlquote(real_sasl_username)) logger.debug('[SQL] query per-user alias address: \n%s' % sql) qr = conn.execute(sql) sql_record = qr.fetchone() logger.debug('SQL query result: %s' % str(sql_record)) if sql_record: logger.debug( 'Sender %s is an alias address of smtp auth username %s.' % (sender, real_sasl_username)) return SMTP_ACTIONS['default'] else: logger.debug('No per-user alias address found.') # Get alias domains if sender_domain != sasl_username_domain: sql = """SELECT alias_domain FROM alias_domain WHERE alias_domain=%s AND target_domain=%s LIMIT 1""" % (sqlquote(sender_domain), sqlquote(sasl_username_domain)) logger.debug('[SQL] query alias domains: \n%s' % sql) qr = conn.execute(sql) sql_record = qr.fetchone() logger.debug('SQL query result: %s' % str(sql_record)) if not sql_record: logger.debug('No alias domain found.') else: logger.debug( 'Sender domain %s is an alias domain of %s.' % (sender_domain, sasl_username_domain)) real_sasl_username = sasl_username_user + '@' + sasl_username_domain real_sender = sender_name + '@' + sasl_username_domain # sender_domain is one of alias domains if sender_name != sasl_username_user: logger.debug( 'Sender is not an user alias address.') else: logger.debug( 'Sender is an alias address of sasl username.') return SMTP_ACTIONS['default'] if allow_list_member: # Get alias members sql = """SELECT forwarding FROM forwardings WHERE address=%s AND forwarding=%s AND is_list=1 AND active=1 LIMIT 1""" % (sqlquote(real_sender), sqlquote(real_sasl_username)) logger.debug( '[SQL] query members of mail alias account (%s): \n%s' % (real_sender, sql)) qr = conn.execute(sql) sql_record = qr.fetchone() logger.debug('SQL query result: %s' % str(sql_record)) if sql_record: logger.debug( 'SASL username (%s) is a member of mail alias (%s).' % (sasl_username, sender)) return SMTP_ACTIONS['default'] else: logger.debug('No such mail alias account.') # Check subscribeable (mlmmj) mailing list. sql = """SELECT id FROM maillists WHERE address=%s AND active=1 LIMIT 1""" % sqlquote( real_sender) logger.debug('[SQL] query mailing list account (%s): \n%s' % (real_sender, sql)) qr = conn.execute(sql) sql_record = qr.fetchone() logger.debug('SQL query result: %s' % str(sql_record)) if sql_record: _check_mlmmj_ml = True else: logger.debug('No such mailing list account.') if _check_mlmmj_ml: # Perform mlmmjadmin query. api_auth_token = settings.mlmmjadmin_api_auth_token if api_auth_token and settings.mlmmjadmin_api_endpoint: _api_endpoint = '/'.join([ settings.mlmmjadmin_api_endpoint, real_sender, 'has_subscriber', sasl_username ]) api_headers = { settings.MLMMJADMIN_API_AUTH_TOKEN_HEADER_NAME: api_auth_token } logger.debug('mlmmjadmin api endpoint: {0}'.format(_api_endpoint)) logger.debug('mlmmjadmin api headers: {0}'.format(api_headers)) try: r = requests.get(_api_endpoint, headers=api_headers, verify=False) _json = r.json() if _json['_success']: logger.debug( 'SASL username (%s) is a member of mailing list (%s).' % (sasl_username, sender)) return SMTP_ACTIONS['default'] except Exception as e: logger.error( "Error while querying mlmmjadmin api: {0}".format(e)) return action_reject