# hobo - portal to configure and deploy applications # Copyright (C) 2015-2016 Entr'ouvert # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU Affero General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . import smtplib import socket import urllib.parse import dns.resolver from django.conf import settings from django.core.exceptions import ValidationError from django.utils.encoding import force_bytes from django.utils.translation import gettext_lazy as _ from hobo.environment.utils import get_operational_services def validate_email_address(value): if not settings.HOBO_VALIDATE_EMAIL_WITH_SMTP: return email_domain = value.split('@')[-1] try: mx_server = sorted(dns.resolver.query(email_domain, 'MX'), key=lambda rdata: rdata.preference)[ 0 ].exchange.to_text() except dns.exception.DNSException as e: raise ValidationError(_('Error: %s') % str(e)) try: smtp = smtplib.SMTP(host=mx_server, timeout=10) try: status, msg = smtp.helo() if status != 250: raise OSError(msg) smtp.mail('') status, msg = smtp.rcpt(value) smtp.quit() finally: smtp.close() except OSError as e: raise ValidationError( _('Error while connecting to %(server)s: %(msg)s') % {'server': mx_server, 'msg': e} ) if status // 100 == 5: raise ValidationError(_('Email address not found on %s') % mx_server) def validate_email_spf(value, strict=False): allowed_records = settings.ALLOWED_SPF_RECORDS if not allowed_records: return email_domain = value.split('@')[-1] txt_records = [] try: for txt_record in dns.resolver.query(email_domain, 'TXT'): txt_records.extend(txt_record.strings) except dns.exception.DNSException: pass spf_records = [x for x in txt_records if x.startswith(b'v=spf1 ')] if not strict and not spf_records: return allowed_records = [force_bytes(x) for x in allowed_records] for spf_record in spf_records: if b'+all' in spf_record: return for allowed_record in allowed_records: if allowed_record in spf_record: return raise ValidationError(_('No suitable SPF record found for %s') % email_domain) def validate_email_domain(value): domain = value.split('@')[-1].strip().strip('.') domains = settings.EMAIL_FROM_ALLOWED_DOMAINS if '*' in domains or domain in domains: return for service in get_operational_services(): fqdn = urllib.parse.urlparse(service.base_url).netloc.split(':')[0] if fqdn == domain or fqdn == 'www.' + domain: return raise ValidationError(_('Domain %s is not allowed') % domain)