Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion authserver/mailauth/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ class MailingListAdmin(admin.ModelAdmin):
@admin.register(EmailAlias)
class EmailAliasAdmin(admin.ModelAdmin):
search_fields = ('mailprefix', 'domain__name',)
list_filter = ('blacklisted', 'domain',)

def get_user(self, obj: EmailAlias) -> str:
ret = "[Error: Unassigned]"
Expand All @@ -181,7 +182,7 @@ def get_mailalias(self, obj: EmailAlias) -> str:
return "%s@%s" % (obj.mailprefix, obj.domain.name)
get_mailalias.short_description = "Mail alias" # type: ignore # (mypy#708)

list_display = ('get_mailalias', 'get_user',)
list_display = ('get_mailalias', 'get_user', 'blacklisted',)


@admin.register(MNApplicationPermission)
Expand Down
50 changes: 37 additions & 13 deletions authserver/mailauth/management/commands/spapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,43 @@ def _install(self, **options: Any) -> None:
cur = connection.cursor() # type: CursorWrapper
cur.execute("""
DROP FUNCTION IF EXISTS authserver_resolve_alias(varchar, boolean);
DROP FUNCTION IF EXISTS authserver_get_alias(varchar, varchar);
CREATE OR REPLACE FUNCTION authserver_get_alias(in_mailprefix varchar, in_domain_name varchar)
RETURNS TABLE (id integer, user_id uuid, domain_id integer,
forward_to_id integer, mailprefix varchar, blacklisted boolean) AS $$
BEGIN
RETURN QUERY SELECT "alias".id, "alias".user_id, "alias".domain_id,
"alias".forward_to_id, "alias".mailprefix, "alias".blacklisted
FROM
mailauth_emailalias AS "alias",
mailauth_domain AS "domain"
WHERE
"alias".domain_id="domain".id AND
"alias".mailprefix=in_mailprefix AND
"domain".name=in_domain_name;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
CREATE OR REPLACE FUNCTION authserver_resolve_alias(email varchar,
resolve_to_virtmail boolean DEFAULT FALSE)
RETURNS TABLE (alias varchar) AS $$
DECLARE
user_mailprefix varchar;
user_domain varchar;
original_mailprefix varchar;
primary_email varchar;
the_alias record;
the_domain record;
BEGIN
SELECT split_part(email, '@', 1) INTO user_mailprefix;
SELECT split_part(email, '@', 2) INTO user_domain;
original_mailprefix := user_mailprefix;

SELECT * INTO the_alias FROM
authserver_get_alias(original_mailprefix, user_domain);

IF the_alias.blacklisted IS TRUE THEN
RETURN;
END IF;

-- handle dashext by resolving it to plusext
IF position('-' in user_mailprefix) > 0 THEN
Expand Down Expand Up @@ -61,13 +86,17 @@ def _install(self, **options: Any) -> None:
END IF;
END IF;

SELECT alias.* INTO the_alias FROM
mailauth_emailalias AS "alias",
mailauth_domain AS "domain"
WHERE
"alias".domain_id="domain".id AND
"alias".mailprefix=user_mailprefix AND
"domain".name=user_domain;
-- block if the normalized alias is explicitly blacklisted
SELECT * INTO the_alias FROM
authserver_get_alias(user_mailprefix, user_domain);

IF the_alias.blacklisted IS TRUE THEN
RETURN;
END IF;

IF NOT FOUND THEN
RETURN;
END IF;

-- check for mailing lists (foreign keys to mailauth_mailinglist)
IF the_alias.forward_to_id IS NOT NULL THEN
Expand All @@ -82,19 +111,14 @@ def _install(self, **options: Any) -> None:
END IF;

SELECT primary_alias.mailprefix || '@' || primary_domain.name INTO primary_email FROM
mailauth_emailalias AS "alias",
mailauth_domain AS "domain",
mailauth_emailalias AS "primary_alias",
mailauth_domain AS "primary_domain",
mailauth_mnuser AS "user"
WHERE
"primary_alias".user_id="user".uuid AND
"primary_domain".id="primary_alias".domain_id AND
"user".delivery_mailbox_id="primary_alias".id AND
"user".uuid="alias".user_id AND
"alias".domain_id="domain".id AND
"alias".mailprefix=user_mailprefix AND
"domain".name=user_domain AND
"user".uuid=the_alias.user_id AND
"user".is_active=TRUE;

IF primary_email = email AND resolve_to_virtmail IS TRUE THEN
Expand Down
19 changes: 19 additions & 0 deletions authserver/mailauth/migrations/0022_emailalias_blacklisted.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('mailauth', '0021_alter_mnapplication_authorization_grant_type'),
]

operations = [
migrations.AddField(
model_name='emailalias',
name='blacklisted',
field=models.BooleanField(default=False),
),
]
1 change: 1 addition & 0 deletions authserver/mailauth/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ class Meta:
domain = models.ForeignKey(Domain, verbose_name="On domain",
on_delete=models.CASCADE)
mailprefix = models.CharField("Mail prefix", max_length=255)
blacklisted = models.BooleanField(default=False)
forward_to = models.ForeignKey(MailingList, verbose_name="Forward to list",
on_delete=models.CASCADE, null=True, blank=True)

Expand Down
28 changes: 28 additions & 0 deletions authserver/maildaemons/forwarder/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,20 @@ def add_rcptto(mfrom: str, rcpt: Union[str, List]) -> None:
rcptto = rcpttos[ix].lower()
rcptuser, rcptdomain = rcptto.split("@", 1)

# Drop explicitly blacklisted full aliases (including dash/plus extensions).
try:
if EmailAlias.objects.filter(
mailprefix__iexact=rcptuser,
domain__name__iexact=rcptdomain,
blacklisted=True,
).exists():
_log.info("Dropping blacklisted alias <%s> (from: %s)", rcptto, mailfrom)
del remaining_rcpttos[ix]
continue
except OperationalError:
_log.exception("Database unavailable.")
return "421 Processing problem. Please try again later."

# implement domain catch-all redirect
domain = None # type: Optional[Domain]
try:
Expand Down Expand Up @@ -93,6 +107,20 @@ def add_rcptto(mfrom: str, rcpt: Union[str, List]) -> None:
# if we had a dashext, or a plusext, we're left with just the prefix after this
user_mailprefix = user_mailprefix.split("+", 1)[0]

# Drop if the normalized alias is blacklisted.
try:
if EmailAlias.objects.filter(
mailprefix__iexact=user_mailprefix,
domain__name__iexact=rcptdomain,
blacklisted=True,
).exists():
_log.info("Dropping blacklisted alias <%s> (from: %s)", rcptto, mailfrom)
del remaining_rcpttos[ix]
continue
except OperationalError:
_log.exception("Database unavailable.")
return "421 Processing problem. Please try again later."

try:
alias = EmailAlias.objects.get(mailprefix__iexact=user_mailprefix,
domain__name__iexact=rcptdomain) # type: EmailAlias
Expand Down