Return 550 instead of 421 when rate limited. Rename greylisting to rate limit
This commit is contained in:
parent
6d67c02311
commit
6532e0de93
|
@ -265,7 +265,7 @@ PAGE_LIMIT = 20
|
||||||
LOCAL_FILE_UPLOAD = "LOCAL_FILE_UPLOAD" in os.environ
|
LOCAL_FILE_UPLOAD = "LOCAL_FILE_UPLOAD" in os.environ
|
||||||
UPLOAD_DIR = None
|
UPLOAD_DIR = None
|
||||||
|
|
||||||
# Greylisting features
|
# Rate Limiting
|
||||||
# nb max of activity (forward/reply) an alias can have during 1 min
|
# nb max of activity (forward/reply) an alias can have during 1 min
|
||||||
MAX_ACTIVITY_DURING_MINUTE_PER_ALIAS = 10
|
MAX_ACTIVITY_DURING_MINUTE_PER_ALIAS = 10
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ from app.log import LOG
|
||||||
from app.models import Alias, EmailLog, Contact
|
from app.models import Alias, EmailLog, Contact
|
||||||
|
|
||||||
|
|
||||||
def greylisting_needed_for_alias(alias: Alias) -> bool:
|
def rate_limited_for_alias(alias: Alias) -> bool:
|
||||||
min_time = arrow.now().shift(minutes=-1)
|
min_time = arrow.now().shift(minutes=-1)
|
||||||
|
|
||||||
# get the nb of activity on this alias
|
# get the nb of activity on this alias
|
||||||
|
@ -37,7 +37,7 @@ def greylisting_needed_for_alias(alias: Alias) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def greylisting_needed_for_mailbox(alias: Alias) -> bool:
|
def rate_limited_for_mailbox(alias: Alias) -> bool:
|
||||||
min_time = arrow.now().shift(minutes=-1)
|
min_time = arrow.now().shift(minutes=-1)
|
||||||
|
|
||||||
# get nb of activity on this mailbox
|
# get nb of activity on this mailbox
|
||||||
|
@ -65,13 +65,11 @@ def greylisting_needed_for_mailbox(alias: Alias) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def greylisting_needed_forward_phase(alias_address: str) -> bool:
|
def rate_limited_forward_phase(alias_address: str) -> bool:
|
||||||
alias = Alias.get_by(email=alias_address)
|
alias = Alias.get_by(email=alias_address)
|
||||||
|
|
||||||
if alias:
|
if alias:
|
||||||
return greylisting_needed_for_alias(alias) or greylisting_needed_for_mailbox(
|
return rate_limited_for_alias(alias) or rate_limited_for_mailbox(alias)
|
||||||
alias
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
LOG.d(
|
LOG.d(
|
||||||
|
@ -80,29 +78,29 @@ def greylisting_needed_forward_phase(alias_address: str) -> bool:
|
||||||
)
|
)
|
||||||
alias = try_auto_create(alias_address)
|
alias = try_auto_create(alias_address)
|
||||||
if alias:
|
if alias:
|
||||||
return greylisting_needed_for_mailbox(alias)
|
return rate_limited_for_mailbox(alias)
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def greylisting_needed_reply_phase(reply_email: str) -> bool:
|
def rate_limited_reply_phase(reply_email: str) -> bool:
|
||||||
contact = Contact.get_by(reply_email=reply_email)
|
contact = Contact.get_by(reply_email=reply_email)
|
||||||
if not contact:
|
if not contact:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
alias = contact.alias
|
alias = contact.alias
|
||||||
return greylisting_needed_for_alias(alias) or greylisting_needed_for_mailbox(alias)
|
return rate_limited_for_alias(alias) or rate_limited_for_mailbox(alias)
|
||||||
|
|
||||||
|
|
||||||
def greylisting_needed(mail_from: str, rcpt_tos: [str]) -> bool:
|
def rate_limited(mail_from: str, rcpt_tos: [str]) -> bool:
|
||||||
for rcpt_to in rcpt_tos:
|
for rcpt_to in rcpt_tos:
|
||||||
if is_reply_email(rcpt_to):
|
if is_reply_email(rcpt_to):
|
||||||
if greylisting_needed_reply_phase(rcpt_to):
|
if rate_limited_reply_phase(rcpt_to):
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
# Forward phase
|
# Forward phase
|
||||||
address = rcpt_to # alias@SL
|
address = rcpt_to # alias@SL
|
||||||
if greylisting_needed_forward_phase(address):
|
if rate_limited_forward_phase(address):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
|
@ -10,10 +10,10 @@ E206 = "250 SL E206 Out of office"
|
||||||
|
|
||||||
# 4** errors
|
# 4** errors
|
||||||
# E401 = "421 SL E401 Retry later"
|
# E401 = "421 SL E401 Retry later"
|
||||||
E402 = "421 SL E402 Retry later"
|
E402 = "421 SL E402 Encryption failed - Retry later"
|
||||||
E403 = "421 SL E403 Retry later"
|
# E403 = "421 SL E403 Retry later"
|
||||||
E404 = "421 SL E404 Retry later"
|
E404 = "421 SL E404 Unexpected error - Retry later"
|
||||||
E405 = "421 SL E405 Retry later"
|
E405 = "421 SL E405 Mailbox domain problem - Retry later"
|
||||||
E406 = "421 SL E406 Retry later"
|
E406 = "421 SL E406 Retry later"
|
||||||
|
|
||||||
# 5** errors
|
# 5** errors
|
||||||
|
@ -38,3 +38,7 @@ E518 = "550 SL E518 Disabled mailbox"
|
||||||
E519 = "550 SL E519 Email detected as spam"
|
E519 = "550 SL E519 Email detected as spam"
|
||||||
E520 = "550 SL E520 Email cannot be sent to contact"
|
E520 = "550 SL E520 Email cannot be sent to contact"
|
||||||
E521 = "550 SL E521 Cannot reach mailbox"
|
E521 = "550 SL E521 Cannot reach mailbox"
|
||||||
|
E522 = (
|
||||||
|
"550 SL E522 The user you are trying to contact is receiving mail "
|
||||||
|
"at a rate that prevents additional messages from being delivered."
|
||||||
|
)
|
||||||
|
|
|
@ -110,7 +110,7 @@ from app.email_utils import (
|
||||||
get_queue_id,
|
get_queue_id,
|
||||||
)
|
)
|
||||||
from app.extensions import db
|
from app.extensions import db
|
||||||
from app.greylisting import greylisting_needed
|
from app.email.rate_limit import rate_limited
|
||||||
from app.log import LOG, set_message_id
|
from app.log import LOG, set_message_id
|
||||||
from app.models import (
|
from app.models import (
|
||||||
Alias,
|
Alias,
|
||||||
|
@ -1636,10 +1636,9 @@ def handle(envelope: Envelope) -> str:
|
||||||
)
|
)
|
||||||
return handle_bounce(envelope, email_log, msg)
|
return handle_bounce(envelope, email_log, msg)
|
||||||
|
|
||||||
# Whether it's necessary to apply greylisting
|
if rate_limited(mail_from, rcpt_tos):
|
||||||
if greylisting_needed(mail_from, rcpt_tos):
|
LOG.w("Rate Limiting applied for mail_from:%s rcpt_tos:%s", mail_from, rcpt_tos)
|
||||||
LOG.w("Grey listing applied for mail_from:%s rcpt_tos:%s", mail_from, rcpt_tos)
|
return status.E522
|
||||||
return status.E403
|
|
||||||
|
|
||||||
# Handle "out of office" auto notice. An automatic response is sent for every forwarded email
|
# Handle "out of office" auto notice. An automatic response is sent for every forwarded email
|
||||||
# todo: remove logging
|
# todo: remove logging
|
||||||
|
|
|
@ -3,27 +3,27 @@ from app.config import (
|
||||||
MAX_ACTIVITY_DURING_MINUTE_PER_MAILBOX,
|
MAX_ACTIVITY_DURING_MINUTE_PER_MAILBOX,
|
||||||
)
|
)
|
||||||
from app.extensions import db
|
from app.extensions import db
|
||||||
from app.greylisting import (
|
from app.email.rate_limit import (
|
||||||
greylisting_needed_forward_phase,
|
rate_limited_forward_phase,
|
||||||
greylisting_needed_for_alias,
|
rate_limited_for_alias,
|
||||||
greylisting_needed_for_mailbox,
|
rate_limited_for_mailbox,
|
||||||
greylisting_needed_reply_phase,
|
rate_limited_reply_phase,
|
||||||
)
|
)
|
||||||
from app.models import User, Alias, EmailLog, Contact
|
from app.models import User, Alias, EmailLog, Contact
|
||||||
|
|
||||||
|
|
||||||
def test_greylisting_needed_forward_phase_for_alias(flask_client):
|
def test_rate_limited_forward_phase_for_alias(flask_client):
|
||||||
user = User.create(
|
user = User.create(
|
||||||
email="a@b.c", password="password", name="Test User", activated=True
|
email="a@b.c", password="password", name="Test User", activated=True
|
||||||
)
|
)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
# no greylisting for a new alias
|
# no rate limiting for a new alias
|
||||||
alias = Alias.create_new_random(user)
|
alias = Alias.create_new_random(user)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
assert not greylisting_needed_for_alias(alias)
|
assert not rate_limited_for_alias(alias)
|
||||||
|
|
||||||
# greylisting when there's a previous activity on alias
|
# rate limit when there's a previous activity on alias
|
||||||
contact = Contact.create(
|
contact = Contact.create(
|
||||||
user_id=user.id,
|
user_id=user.id,
|
||||||
alias_id=alias.id,
|
alias_id=alias.id,
|
||||||
|
@ -35,10 +35,10 @@ def test_greylisting_needed_forward_phase_for_alias(flask_client):
|
||||||
EmailLog.create(user_id=user.id, contact_id=contact.id)
|
EmailLog.create(user_id=user.id, contact_id=contact.id)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
assert greylisting_needed_for_alias(alias)
|
assert rate_limited_for_alias(alias)
|
||||||
|
|
||||||
|
|
||||||
def test_greylisting_needed_forward_phase_for_mailbox(flask_client):
|
def test_rate_limited_forward_phase_for_mailbox(flask_client):
|
||||||
user = User.create(
|
user = User.create(
|
||||||
email="a@b.c", password="password", name="Test User", activated=True
|
email="a@b.c", password="password", name="Test User", activated=True
|
||||||
)
|
)
|
||||||
|
@ -61,20 +61,20 @@ def test_greylisting_needed_forward_phase_for_mailbox(flask_client):
|
||||||
EmailLog.create(user_id=user.id, contact_id=contact.id)
|
EmailLog.create(user_id=user.id, contact_id=contact.id)
|
||||||
|
|
||||||
# Create another alias with the same mailbox
|
# Create another alias with the same mailbox
|
||||||
# will be greylisted as there's a previous activity on mailbox
|
# will be rate limited as there's a previous activity on mailbox
|
||||||
alias2 = Alias.create_new_random(user)
|
alias2 = Alias.create_new_random(user)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
assert greylisting_needed_for_mailbox(alias2)
|
assert rate_limited_for_mailbox(alias2)
|
||||||
|
|
||||||
|
|
||||||
def test_greylisting_needed_forward_phase(flask_client):
|
def test_rate_limited_forward_phase(flask_client):
|
||||||
# no greylisting when alias not exist
|
# no rate limiting when alias does not exist
|
||||||
assert not greylisting_needed_forward_phase("not-exist@alias.com")
|
assert not rate_limited_forward_phase("not-exist@alias.com")
|
||||||
|
|
||||||
|
|
||||||
def test_greylisting_needed_reply_phase(flask_client):
|
def test_rate_limited_reply_phase(flask_client):
|
||||||
# no greylisting when reply_email not exist
|
# no rate limiting when reply_email does not exist
|
||||||
assert not greylisting_needed_reply_phase("not-exist-reply@alias.com")
|
assert not rate_limited_reply_phase("not-exist-reply@alias.com")
|
||||||
|
|
||||||
user = User.create(
|
user = User.create(
|
||||||
email="a@b.c", password="password", name="Test User", activated=True
|
email="a@b.c", password="password", name="Test User", activated=True
|
||||||
|
@ -95,4 +95,4 @@ def test_greylisting_needed_reply_phase(flask_client):
|
||||||
EmailLog.create(user_id=user.id, contact_id=contact.id)
|
EmailLog.create(user_id=user.id, contact_id=contact.id)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
assert greylisting_needed_reply_phase("rep@sl.local")
|
assert rate_limited_reply_phase("rep@sl.local")
|
Loading…
Reference in New Issue