diff --git a/modoboa/admin/app_settings.py b/modoboa/admin/app_settings.py index f551790e3..bbfe2317a 100644 --- a/modoboa/admin/app_settings.py +++ b/modoboa/admin/app_settings.py @@ -61,6 +61,17 @@ class AdminParametersForm(parameters.AdminParametersForm): ) ) + valid_mxs = forms.CharField( + label=ugettext_lazy("Valid MXs"), + initial="", + help_text=ugettext_lazy( + "IP(s) or network(s) " + "of the MX that should match with domains' MX records. " + "On per line." + ), + widget=forms.Textarea + ) + enable_dnsbl_checks = YesNoField( label=ugettext_lazy("Enable DNSBL checks"), initial="yes", diff --git a/modoboa/admin/management/commands/subcommands/_mx.py b/modoboa/admin/management/commands/subcommands/_mx.py index 92dabb429..b16e48729 100644 --- a/modoboa/admin/management/commands/subcommands/_mx.py +++ b/modoboa/admin/management/commands/subcommands/_mx.py @@ -38,12 +38,15 @@ class CheckMXRecords(BaseCommand): """Return sender address for notifications.""" return parameters.get_admin("SENDER_ADDRESS", app="core") + @cached_property + def valid_mxs(self): + """Return valid MXs set in admin.""" + valid_mxs = parameters.get_admin("VALID_MXS", app="admin") + return [ipaddress.ip_network(u"{}".format(v.strip())) + for v in valid_mxs.split() if v.strip()] + def add_arguments(self, parser): """Add extra arguments to command.""" - parser.add_argument( - "--valid-mx", type=str, action="append", default=[], - help="Valid MX ip(s) or subnet(s) used to check that domain's MX " - "are your own.") parser.add_argument( "--no-dnsbl", action="store_true", default=False, help="Skip DNSBL queries.") @@ -158,7 +161,7 @@ class CheckMXRecords(BaseCommand): if not status: print(msg) - def check_valid_mx(self, domain, mx_list, valid_mx=None, **options): + def check_valid_mx(self, domain, mx_list, **options): """Check that domain's MX record exist. If `valid_mx` is provided, retrieved MX records must be @@ -166,14 +169,17 @@ class CheckMXRecords(BaseCommand): """ alerts = [] check = False - mxs = [ipaddress.ip_address(u"%s" % mx.address) for mx in mx_list] + mxs = [(mx, ipaddress.ip_address(u"%s" % mx.address)) + for mx in mx_list] + valid_mxs = self.valid_mxs if not mxs: alerts.append(_("Domain {} as no MX record").format(domain)) - elif valid_mx: - for subnet in valid_mx: - check = True in [mx in subnet for mx in mxs] - if check is True: - break + elif valid_mxs: + for subnet in valid_mxs: + for mx, addr in mxs: + if addr in subnet: + mx.managed = check = True + mx.save() if check is False: mx_names = [ "{0.name} ({0.address})".format(mx) for mx in mx_list] @@ -192,7 +198,7 @@ class CheckMXRecords(BaseCommand): return content = render_to_string( "admin/notifications/domain_invalid_mx.html", { - "domain": domain, "valid_mx": valid_mx, "alerts": alerts + "domain": domain, "alerts": alerts }) subject = _("[modoboa] MX issue(s) for domain {}").format( domain.name) @@ -227,11 +233,6 @@ class CheckMXRecords(BaseCommand): def handle(self, *args, **options): """Command entry point.""" - # Check that user provide valid network addresses - valid_mx = options["valid_mx"] - options["valid_mx"] = [ - ipaddress.ip_network(u"{}".format(v)) for v in valid_mx] - # Remove deprecated records first models.DNSBLResult.objects.exclude( provider__in=self.providers).delete() diff --git a/modoboa/admin/models/domain.py b/modoboa/admin/models/domain.py index e27023fd1..f38dd5977 100644 --- a/modoboa/admin/models/domain.py +++ b/modoboa/admin/models/domain.py @@ -224,6 +224,17 @@ class Domain(AdminObject): reversion.register(Domain) +class MXQuerySet(models.QuerySet): + """Custom manager for MXRecord.""" + + def has_valids(self): + """Return managed results.""" + if parameters.get_admin("ENABLE_MX_CHECKS"): + if parameters.get_admin("VALID_MXS", app="admin").strip(): + return self.filter(managed=True).exists() + return self.exists() + + class MXRecord(models.Model): """A model used to store MX records for Domain.""" @@ -233,6 +244,13 @@ class MXRecord(models.Model): managed = models.BooleanField(default=False) updated = models.DateTimeField() + objects = models.Manager.from_queryset(MXQuerySet)() + + def is_managed(self): + if not parameters.get_admin("ENABLE_MX_CHECKS"): + return False + return bool(parameters.get_admin("VALID_MXS", app="admin").strip()) + def __unicode__(self): return u'{0.name} ({0.address}) for {0.domain} '.format(self) diff --git a/modoboa/admin/templates/admin/domain_detail.html b/modoboa/admin/templates/admin/domain_detail.html index e464d57cc..20bd70225 100644 --- a/modoboa/admin/templates/admin/domain_detail.html +++ b/modoboa/admin/templates/admin/domain_detail.html @@ -23,7 +23,7 @@
{% trans "DNS status" %} - MX + MX DNSBL
diff --git a/modoboa/admin/templates/admin/mx_domain_detail.html b/modoboa/admin/templates/admin/mx_domain_detail.html index 56950ffdf..e5b5ed46c 100644 --- a/modoboa/admin/templates/admin/mx_domain_detail.html +++ b/modoboa/admin/templates/admin/mx_domain_detail.html @@ -15,14 +15,27 @@ {% for mx in domain.mxrecord_set.all %} {{ mx.name }} - {{ mx.address }} + + {% if mx.is_managed %} + {% if not mx.managed %} + {{ mx.address }} + {% else %} + {{ mx.address }} + {% endif %} + {% else %} + {{ mx.address }} + {% endif %} + {{ mx.updated|date:"SHORT_DATETIME_FORMAT" }} {% empty %} {% trans "No MX record found for this domain." %} - {% endfor %} + {% endfor %} {% endblock %} diff --git a/modoboa/admin/tests/test_mx.py b/modoboa/admin/tests/test_mx.py index 7fccef4a5..7b958aa71 100644 --- a/modoboa/admin/tests/test_mx.py +++ b/modoboa/admin/tests/test_mx.py @@ -5,6 +5,7 @@ from django.core.urlresolvers import reverse from django.test import override_settings from modoboa.lib.tests import ModoTestCase +from modoboa.lib import parameters from .. import factories from .. import models @@ -19,28 +20,26 @@ class MXTestCase(ModoTestCase): super(MXTestCase, cls).setUpTestData() cls.domain = factories.DomainFactory(name="modoboa.org") factories.DomainFactory(name="pouet.com") # should not exist + parameters.save_admin('VALID_MXS', '127.0.0.1', app='admin') def test_management_command(self): """Check that command works fine.""" self.assertEqual(models.MXRecord.objects.count(), 0) - management.call_command("modo", "check_mx", "--no-dnsbl", - "--valid-mx=127.0.0.1", "--ttl=0") + management.call_command("modo", "check_mx", "--no-dnsbl", "--ttl=0") self.assertTrue( models.MXRecord.objects.filter(domain=self.domain).exists()) # we passed a ttl to 0. this will reset the cache this time qs = models.MXRecord.objects.filter(domain=self.domain) id = qs[0].id - management.call_command("modo", "check_mx", "--no-dnsbl", - "--valid-mx=127.0.0.1", "--ttl=7200") + management.call_command("modo", "check_mx", "--no-dnsbl", "--ttl=7200") qs = models.MXRecord.objects.filter(domain=self.domain) self.assertNotEqual(id, qs[0].id) id = qs[0].id # assume that mxrecords ids are the same. means that we taking care of # ttl - management.call_command("modo", "check_mx", "--no-dnsbl", - "--valid-mx=127.0.0.1") + management.call_command("modo", "check_mx", "--no-dnsbl") qs = models.MXRecord.objects.filter(domain=self.domain) self.assertEqual(id, qs[0].id)