From fd39fb27aee75a4fbdb688e49a651cd1b30043f3 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Wed, 3 Oct 2018 01:40:08 +0200 Subject: [PATCH 1/4] validate domain of email address in registration form, via DNS i get tons of mailer daemon emails each day, just because emails are invalid, domain has no mx, ... --- nsupdate/accounts/registration_form.py | 61 ++++++++++++++++++++++++++ nsupdate/settings/base.py | 1 + 2 files changed, 62 insertions(+) create mode 100644 nsupdate/accounts/registration_form.py diff --git a/nsupdate/accounts/registration_form.py b/nsupdate/accounts/registration_form.py new file mode 100644 index 00000000..351c1883 --- /dev/null +++ b/nsupdate/accounts/registration_form.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- + +import os + +import logging +logger = logging.getLogger(__name__) + +from django import forms +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 + + +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 + + +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:') + if valid_mx: + return email + raise forms.ValidationError(_("Enter a valid email address.")) diff --git a/nsupdate/settings/base.py b/nsupdate/settings/base.py index dcc23136..8608e3eb 100644 --- a/nsupdate/settings/base.py +++ b/nsupdate/settings/base.py @@ -245,6 +245,7 @@ ACCOUNT_ACTIVATION_DAYS = 7 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/' LOGOUT_REDIRECT_URL = '/' From f7af9bd17da51776aa6d8f423bf2a2bb9c041284 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Mon, 8 Oct 2018 00:56:22 +0200 Subject: [PATCH 2/4] more logging, add nsupdate.info site specific hack for nameservers --- nsupdate/accounts/registration_form.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/nsupdate/accounts/registration_form.py b/nsupdate/accounts/registration_form.py index 351c1883..7007c9ef 100644 --- a/nsupdate/accounts/registration_form.py +++ b/nsupdate/accounts/registration_form.py @@ -18,6 +18,12 @@ resolver.search = [dns.name.root, ] resolver.lifetime = 5.0 +# TODO: this is nsupdate.info site specific: +# - we only have ipv6 (thus transform the v4 addrs to v6) +# - dnspython stumbles over our own scoped ipv6 nameserver ip (fe80::1%eth0, +# see https://github.com/rthalley/dnspython/issues/283 ), so we use google / cloudflare :-| +resolver.nameservers = ['64:ff9b::8.8.8.8', '64:ff9b::1.1.1.1', ] + def check_mx(domain): valid = False @@ -58,4 +64,5 @@ def clean_email(self): logger.exception('RegistrationFormValidateEmail raised an exception:') if valid_mx: return email + logger.info('RegistrationFormValidateEmail: rejecting email address %r' % email) raise forms.ValidationError(_("Enter a valid email address.")) From a248b93a3a7a1acdc141d1bc94ddcdca96c3d589 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Wed, 10 Oct 2018 15:27:22 +0200 Subject: [PATCH 3/4] also check domains against a blacklist --- nsupdate/accounts/registration_form.py | 33 +++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/nsupdate/accounts/registration_form.py b/nsupdate/accounts/registration_form.py index 7007c9ef..d64c3016 100644 --- a/nsupdate/accounts/registration_form.py +++ b/nsupdate/accounts/registration_form.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- import os +import re import logging logger = logging.getLogger(__name__) @@ -25,6 +26,28 @@ resolver.nameservers = ['64:ff9b::8.8.8.8', '64:ff9b::1.1.1.1', ] +# TODO: move to a file +# domains that have a non-working mx / that are frequently abused: +maildomain_blacklist = """ +babau\.ga$ +bezeqint\.net$ +everytg\.ml$ +flu\.cc$ +goerieblog\.com$ +igg\.biz$ +lady-and-lunch\.xyz$ +mailcatch\.com$ +mailspam\.xyz$ +napalm51\.gq$ +nut\.cc$ +pine-and-onyx\.xyz$ +stars-and-glory\.top$ +usa\.cc$ +xoxy\.net$ +""" +maildomain_blacklist = maildomain_blacklist.strip().splitlines() + + def check_mx(domain): valid = False try: @@ -50,6 +73,13 @@ def check_mx(domain): 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): """ @@ -62,7 +92,8 @@ def clean_email(self): valid_mx = check_mx(domain) except Exception as e: logger.exception('RegistrationFormValidateEmail raised an exception:') - if valid_mx: + 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.")) From e3acaa461865a79ec59f478d0bc6f8c5845589f5 Mon Sep 17 00:00:00 2001 From: Thomas Waldmann Date: Sun, 14 Oct 2018 14:18:20 +0200 Subject: [PATCH 4/4] registration email validation: make hardcoded stuff configurable --- nsupdate/accounts/registration_form.py | 29 +++----------------------- nsupdate/settings/base.py | 26 +++++++++++++++++++++++ 2 files changed, 29 insertions(+), 26 deletions(-) diff --git a/nsupdate/accounts/registration_form.py b/nsupdate/accounts/registration_form.py index d64c3016..d7ab14ee 100644 --- a/nsupdate/accounts/registration_form.py +++ b/nsupdate/accounts/registration_form.py @@ -7,6 +7,7 @@ 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 @@ -18,34 +19,10 @@ resolver = dns.resolver.Resolver() resolver.search = [dns.name.root, ] resolver.lifetime = 5.0 +resolver.nameservers = settings.NAMESERVERS -# TODO: this is nsupdate.info site specific: -# - we only have ipv6 (thus transform the v4 addrs to v6) -# - dnspython stumbles over our own scoped ipv6 nameserver ip (fe80::1%eth0, -# see https://github.com/rthalley/dnspython/issues/283 ), so we use google / cloudflare :-| -resolver.nameservers = ['64:ff9b::8.8.8.8', '64:ff9b::1.1.1.1', ] - -# TODO: move to a file -# domains that have a non-working mx / that are frequently abused: -maildomain_blacklist = """ -babau\.ga$ -bezeqint\.net$ -everytg\.ml$ -flu\.cc$ -goerieblog\.com$ -igg\.biz$ -lady-and-lunch\.xyz$ -mailcatch\.com$ -mailspam\.xyz$ -napalm51\.gq$ -nut\.cc$ -pine-and-onyx\.xyz$ -stars-and-glory\.top$ -usa\.cc$ -xoxy\.net$ -""" -maildomain_blacklist = maildomain_blacklist.strip().splitlines() +maildomain_blacklist = settings.MAILDOMAIN_BLACKLIST.strip().splitlines() def check_mx(domain): diff --git a/nsupdate/settings/base.py b/nsupdate/settings/base.py index 8608e3eb..d4c8d554 100644 --- a/nsupdate/settings/base.py +++ b/nsupdate/settings/base.py @@ -51,6 +51,32 @@ from netaddr import IPSet, IPAddress, IPNetwork BAD_IPS_HOST = IPSet([]) # inner list can have IPAddress and IPNetwork elements +# nameservers used e.g. for MX lookups in the registration email validation +# TODO: this is nsupdate.info site specific: +# - we only have ipv6 (thus transform the v4 addrs to v6) +# - dnspython stumbles over our own scoped ipv6 nameserver ip (fe80::1%eth0, +# see https://github.com/rthalley/dnspython/issues/283 ), so we use google / cloudflare :-| +NAMESERVERS = ['64:ff9b::8.8.8.8', '64:ff9b::1.1.1.1', ] + +# domains that have a non-working mx / that are frequently abused: +MAILDOMAIN_BLACKLIST = """ +babau\.ga$ +bezeqint\.net$ +everytg\.ml$ +flu\.cc$ +goerieblog\.com$ +igg\.biz$ +lady-and-lunch\.xyz$ +mailcatch\.com$ +mailspam\.xyz$ +napalm51\.gq$ +nut\.cc$ +pine-and-onyx\.xyz$ +stars-and-glory\.top$ +usa\.cc$ +xoxy\.net$ +""" + # Local time zone for this installation. Choices can be found here: # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name # although not all choices may be available on all operating systems.