From 2ca58464e185625cc922cb1549ae96df1c3e7ca4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= Date: Tue, 24 Mar 2020 20:31:25 +0100 Subject: [PATCH 1/2] validators: use only dnspython to resolve domains (#40989) Also fix SMTP error value check when testing email adresses with an RCPT check. --- src/authentic2/validators.py | 84 ++++++++++++++++++------------------ 1 file changed, 41 insertions(+), 43 deletions(-) diff --git a/src/authentic2/validators.py b/src/authentic2/validators.py index dc992b09..8856f2fc 100644 --- a/src/authentic2/validators.py +++ b/src/authentic2/validators.py @@ -19,11 +19,9 @@ from __future__ import unicode_literals import smtplib from django.utils.translation import ugettext_lazy as _ -from django.utils.encoding import force_text from django.core.exceptions import ValidationError -from django.core.validators import RegexValidator +from django.core.validators import RegexValidator, EmailValidator as DjangoEmailValidator -import socket import dns.resolver import dns.exception @@ -42,55 +40,55 @@ class EmailValidator(object): mxs = dns.resolver.query(domain, 'MX') mxs = [str(mx.exchange).rstrip('.') for mx in mxs] return mxs + except dns.resolver.NXDOMAIN: + return [] + except dns.resolver.NoAnswer: + pass except dns.exception.DNSException: + pass + + for record_type in ('AAAA', 'A'): try: - idna_encoded = force_text(domain).encode('idna') - except UnicodeError: + mxs = dns.resolver.query(domain, 'MX') + mxs = [str(mx.address).rstrip('.') for mx in mxs] + return mxs + except dns.resolver.NXDOMAIN: return [] - try: - socket.gethostbyname(idna_encoded) - return [domain] - except socket.error: + except dns.resolver.NoAnswer: + pass + except dns.exception.DNSException: pass return [] - def __call__(self, value): - try: - hostname = value.split('@')[-1] - except KeyError: - raise ValidationError(_('Enter a valid email address.'), code='invalid-email') - if not app_settings.A2_VALIDATE_EMAIL_DOMAIN: - return True + def check_rcpt(self, value, mxs): + for server in mxs: + try: + smtp = smtplib.SMTP() + smtp.connect(server) + status = smtp.helo() + if status[0] != 250: + continue + smtp.mail('') + status = smtp.rcpt(value) + if status[0] // 100 == 5: + raise ValidationError(_('Invalid email address.'), code='rcpt-check-failed') + break + except smtplib.SMTPServerDisconnected: + break + except smtplib.SMTPConnectError: + continue - mxs = self.check_mxs(hostname) - if not mxs: - raise ValidationError(_('Email domain is invalid'), code='invalid-domain') + def __call__(self, value): + DjangoEmailValidator()(value) - if not self.rcpt_check or not app_settings.A2_VALIDATE_EMAIL: - return + localpart, hostname = value.split('@', 1) + if app_settings.A2_VALIDATE_EMAIL_DOMAIN: + mxs = self.check_mxs(hostname) + if not mxs: + raise ValidationError(_('Email domain is invalid'), code='invalid-domain') + if self.rcpt_check and app_settings.A2_VALIDATE_EMAIL: + self.check_rcpt(value, mxs) - try: - for server in mxs: - try: - smtp = smtplib.SMTP() - smtp.connect(server) - status = smtp.helo() - if status[0] != 250: - continue - smtp.mail('') - status = smtp.rcpt(value) - if status[0] % 100 == 5: - raise ValidationError(_('Invalid email address.'), code='rcpt-check-failed') - break - except smtplib.SMTPServerDisconnected: - break - except smtplib.SMTPConnectError: - continue - # Should not happen ! - except dns.resolver.NXDOMAIN: - raise ValidationError(_('Nonexistent domain.')) - except dns.resolver.NoAnswer: - raise ValidationError(_('Nonexistent email address.')) email_validator = EmailValidator() -- 2.24.0