diff --git a/nsupdate/accounts/registration_form.py b/nsupdate/accounts/registration_form.py new file mode 100644 index 0000000..99e2a74 --- /dev/null +++ b/nsupdate/accounts/registration_form.py @@ -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.")) diff --git a/nsupdate/settings/base.py b/nsupdate/settings/base.py index dcc2313..44fe1ff 100644 --- a/nsupdate/settings/base.py +++ b/nsupdate/settings/base.py @@ -51,6 +51,21 @@ BAD_AGENTS = set([]) # list can have str elements 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. +# 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: # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name # 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 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 = '/'