validate domain of email address in registration form, fixes #284

- via DNS (MX, A/AAAA records)
- against blacklist with regexes

This is implemented because without it, postmasters get tons of mailer daemon
emails each day, just because emails are invalid, domain has no mx, ...
This commit is contained in:
Thomas Waldmann 2018-10-03 01:40:08 +02:00
parent 90e59c7a89
commit 7dcf5a22bb
2 changed files with 91 additions and 0 deletions

View File

@ -0,0 +1,75 @@
# -*- coding: utf-8 -*-
import re
import logging
logger = logging.getLogger(__name__)
from django import forms
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from registration.forms import RegistrationForm
import dns.resolver
import dns.name
resolver = dns.resolver.Resolver()
resolver.search = [dns.name.root, ]
resolver.lifetime = 5.0
resolver.nameservers = settings.NAMESERVERS
maildomain_blacklist = settings.MAILDOMAIN_BLACKLIST.strip().splitlines()
def check_mx(domain):
valid = False
try:
mx_answers = resolver.query(domain, 'MX')
# domain exists in DNS, domain has MX
mx_entries = sorted([(mx_rdata.preference, mx_rdata.exchange) for mx_rdata in mx_answers])
for preference, mx in mx_entries:
try:
addr_answers = resolver.query(mx, 'A')
except dns.resolver.NoAnswer:
addr_answers = resolver.query(mx, 'AAAA')
# MX has IP addr
mx_addrs = [addr_rdata.address for addr_rdata in addr_answers]
for mx_addr in mx_addrs:
if mx_addr not in (u'127.0.0.1', u'::1', u'0.0.0.0', ):
valid = True
break
if valid:
break
except (dns.resolver.Timeout, dns.resolver.NoAnswer, dns.resolver.NoNameservers, dns.resolver.NXDOMAIN, ):
# expected exceptions (e.g. due to non-existing or misconfigured crap domains)
pass
return valid
def check_blacklist(domain):
for blacklisted_re in maildomain_blacklist:
if re.search(blacklisted_re, domain):
return False
return True
class RegistrationFormValidateEmail(RegistrationForm):
def clean_email(self):
"""
Validate the supplied email address to avoid undeliverable email and mailer daemon spam.
"""
email = self.cleaned_data.get('email')
valid_mx = False
try:
domain = email.split('@')[1]
valid_mx = check_mx(domain)
except Exception as e:
logger.exception('RegistrationFormValidateEmail raised an exception:')
not_blacklisted = check_blacklist(domain)
if valid_mx and not_blacklisted:
return email
logger.info('RegistrationFormValidateEmail: rejecting email address %r' % email)
raise forms.ValidationError(_("Enter a valid email address."))

View File

@ -51,6 +51,21 @@ BAD_AGENTS = set([]) # list can have str elements
from netaddr import IPSet, IPAddress, IPNetwork from netaddr import IPSet, IPAddress, IPNetwork
BAD_IPS_HOST = IPSet([]) # inner list can have IPAddress and IPNetwork elements BAD_IPS_HOST = IPSet([]) # inner list can have IPAddress and IPNetwork elements
# nameservers used e.g. for MX lookups in the registration email validation.
# google / cloudflare DNS IPs are only given as example / fallback -
# please configure your own nameservers in your local settings file.
NAMESERVERS = ['8.8.8.8', '1.1.1.1', ]
# registration email validation: disallow specific email domains,
# e.g. domains that have a non-working mx / that are frequently abused.
# we use a multiline string here with one regex per line (used with re.search).
# the domains given below are just examples, please configure your own
# regexes in your local settings file.
MAILDOMAIN_BLACKLIST = """
mailcatch\.com$
mailspam\.xyz$
"""
# Local time zone for this installation. Choices can be found here: # Local time zone for this installation. Choices can be found here:
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
# although not all choices may be available on all operating systems. # although not all choices may be available on all operating systems.
@ -245,6 +260,7 @@ SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
ACCOUNT_ACTIVATION_DAYS = 7 ACCOUNT_ACTIVATION_DAYS = 7
REGISTRATION_EMAIL_HTML = False # we override the text, but not the html email template REGISTRATION_EMAIL_HTML = False # we override the text, but not the html email template
REGISTRATION_FORM = 'nsupdate.accounts.registration_form.RegistrationFormValidateEmail'
LOGIN_REDIRECT_URL = '/overview/' LOGIN_REDIRECT_URL = '/overview/'
LOGOUT_REDIRECT_URL = '/' LOGOUT_REDIRECT_URL = '/'