reset admin's migrations steps to avoid complex migration stuff; add moar options; improve coding style

This commit is contained in:
Gael Pasgrimaud
2016-09-14 12:13:23 +02:00
parent 8b914246cf
commit 05fcb53109
8 changed files with 142 additions and 173 deletions
+8
View File
@@ -53,6 +53,14 @@ class AdminParametersForm(parameters.AdminParametersForm):
dom_sep = SeparatorField(label=ugettext_lazy("Domains"))
enable_mx_checks = YesNoField(
label=ugettext_lazy("Enable MX checks"),
initial="yes",
help_text=ugettext_lazy(
"Check that every domain has a valid MX record"
)
)
enable_dnsbl_checks = YesNoField(
label=ugettext_lazy("Enable DNSBL checks"),
initial="yes",
@@ -1,8 +1,8 @@
"""Management command to check defined domains."""
import ipaddress
from datetime import datetime
from datetime import timedelta
import ipaddress
import dns.resolver
@@ -42,10 +42,18 @@ class CheckMXRecords(BaseCommand):
"""Add extra arguments to command."""
parser.add_argument(
"--valid-mx", type=str, action='append', default=[],
help="Valid MX ips.")
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.")
parser.add_argument(
"--email", type=str, action='append', default=[],
help="One or more email to notify")
parser.add_argument(
"--skip-admin-emails", action='store_true',
default=False,
help="Skip domain's admins email notification.")
parser.add_argument(
"--timeout", type=int, default=3,
help="Timeout used for queries.")
@@ -54,6 +62,8 @@ class CheckMXRecords(BaseCommand):
help="TTL for dns query.")
def get_mx_records_for_domain(self, domain, ttl=7200):
"""Return one or more `models.MXRecord` for `domain`. DNS queries are
not performed while `ttl` (in seconds) is still valid"""
try:
answers = dns.resolver.query(domain.name, "MX")
except dns.resolver.NoAnswer:
@@ -88,7 +98,7 @@ class CheckMXRecords(BaseCommand):
yield record
def query_dnsbl(self, mx_list, provider):
"""Check given IP against given provider."""
"""Check given IP against given DNSBL provider."""
results = {}
for mx in mx_list:
reverse = ".".join(reversed(mx.address.split(".")))
@@ -99,8 +109,8 @@ class CheckMXRecords(BaseCommand):
results[mx] = False
return provider, results
def store_dnsbl_result(self, domain, provider, results):
"""Store provider results for domain."""
def store_dnsbl_result(self, domain, provider, results, **options):
"""Store DNSBL provider results for domain."""
alerts = {}
to_create = []
for mx in results.keys():
@@ -123,64 +133,74 @@ class CheckMXRecords(BaseCommand):
models.DNSBLResult.objects.bulk_create(to_create)
if not alerts:
return
content = render_to_string(
"admin/notifications/domain_in_dnsbl.html", {
"domain": domain, "alerts": alerts
})
subject = _("[modoboa] DNSBL issue(s) for domain {}").format(
domain.name)
for admin in domain.admins:
if not admin.email:
continue
status, msg = email_utils.sendmail_simple(
self.sender, admin.email,
subject=subject, content=content)
if not status:
print(msg)
emails = options['email']
if not options['skip_admin_emails']:
emails.extend([a.email for a in domain.admins if a.email])
if emails:
content = render_to_string(
"admin/notifications/domain_in_dnsbl.html", {
"domain": domain, "alerts": alerts
})
subject = _("[modoboa] DNSBL issue(s) for domain {}").format(
domain.name)
for email in emails:
status, msg = email_utils.sendmail_simple(
self.sender, email,
subject=subject, content=content)
if not status:
print(msg)
def check_valid_mx(self, domain, mx_list, valid_mx=None, **options):
"""Check that domain's MX record exist and is contains in `valid_mx` if
the option is provided"""
alerts = []
check = False
mx_names = [mx.name for mx in mx_list]
mxs = mx_names + [mx.address for mx in mx_list]
mxs = [ipaddress.ip_address(u"%s" % mx.address) for mx in mx_list]
if valid_mx:
check = True in [mx in mxs for mx in valid_mx]
for subnet in valid_mx:
check = True in [mx in subnet for mx in mxs]
if check is True:
break
if not mxs:
alerts.append(_('Domain {} as no MX record').format(domain))
alerts.append(_("Domain {} as no MX record").format(domain))
elif valid_mx and check is False:
args = (domain, ', '.join(mx_names))
mx_names = ["{0.name} ({0.address})".format(mx) for mx in mx_list]
args = (domain, ", ".join(mx_names))
alerts.append(
_('Domain {0} got an invalid MX record. Got {1}').format(*args)
_("Domain {0} got an invalid MX record. Got {1}").format(*args)
)
if not alerts:
return
content = render_to_string(
"admin/notifications/domain_invalid_mx.html", {
"domain": domain, "valid_mx": valid_mx, "alerts": alerts
})
subject = _("[modoboa] MX issue(s) for domain {}").format(
domain.name)
for admin in domain.admins:
if not admin.email:
continue
status, msg = email_utils.sendmail_simple(
self.sender, admin.email,
subject=subject, content=content)
if not status:
print(msg)
emails = options['email']
if not options['skip_admin_emails']:
emails.extend([a.email for a in domain.admins if a.email])
if emails:
content = render_to_string(
"admin/notifications/domain_invalid_mx.html", {
"domain": domain, "valid_mx": valid_mx, "alerts": alerts
})
subject = _("[modoboa] MX issue(s) for domain {}").format(
domain.name)
for email in emails:
status, msg = email_utils.sendmail_simple(
self.sender, email,
subject=subject, content=content)
if not status:
print(msg)
def check_domain(self, domain, timeout=3, ttl=7200, **options):
"""Check specified domain."""
mx_list = list(self.get_mx_records_for_domain(domain, ttl=ttl))
self.check_valid_mx(domain, mx_list, **options)
if parameters.get_admin("ENABLE_MX_CHECKS") != "no":
self.check_valid_mx(domain, mx_list, **options)
if parameters.get_admin("ENABLE_DNSBL_CHECKS") == "no":
return
elif options['no_dnsbl'] is True:
return
else:
elif mx_list:
jobs = [
gevent.spawn(self.query_dnsbl, mx_list, provider)
for provider in self.providers]
@@ -189,10 +209,15 @@ class CheckMXRecords(BaseCommand):
if not job.successful():
continue
provider, results = job.value
self.store_dnsbl_result(domain, provider, results)
self.store_dnsbl_result(domain, provider, results, **options)
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"%s" % v)
for v in valid_mx]
# Remove deprecated records first
models.DNSBLResult.objects.exclude(
provider__in=self.providers).delete()
@@ -1,30 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.5 on 2016-06-08 12:23
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('admin', '0003_auto_20151118_1215'),
]
operations = [
migrations.CreateModel(
name='DNSBLResult',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('provider', models.CharField(max_length=254, db_index=True)),
('mx', models.GenericIPAddressField()),
('status', models.CharField(blank=True, max_length=45, db_index=True)),
('domain', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='admin.Domain')),
],
),
migrations.AlterUniqueTogether(
name='dnsblresult',
unique_together=set([('domain', 'provider', 'mx')]),
),
]
@@ -0,0 +1,67 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.9 on 2016-09-14 09:07
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('admin', '0003_auto_20151118_1215'),
]
operations = [
migrations.CreateModel(
name='DNSBLResult',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('provider', models.CharField(db_index=True, max_length=254)),
('status', models.CharField(blank=True, db_index=True, max_length=45)),
('domain', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='admin.Domain')),
],
),
migrations.CreateModel(
name='MXRecord',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=254)),
('address', models.GenericIPAddressField()),
('managed', models.BooleanField(default=False)),
('updated', models.DateTimeField()),
('domain', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='admin.Domain')),
],
),
migrations.CreateModel(
name='SenderAddress',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('address', models.EmailField(max_length=254)),
('mailbox', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='admin.Mailbox')),
],
),
migrations.AddField(
model_name='alias',
name='description',
field=models.TextField(blank=True),
),
migrations.AddField(
model_name='alias',
name='expire_at',
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name='dnsblresult',
name='mx',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='admin.MXRecord'),
),
migrations.AlterUniqueTogether(
name='senderaddress',
unique_together=set([('address', 'mailbox')]),
),
migrations.AlterUniqueTogether(
name='dnsblresult',
unique_together=set([('domain', 'provider', 'mx')]),
),
]
@@ -1,25 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.5 on 2016-07-19 07:03
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('admin', '0004_auto_20160608_1423'),
]
operations = [
migrations.AddField(
model_name='alias',
name='description',
field=models.TextField(blank=True),
),
migrations.AddField(
model_name='alias',
name='expire_at',
field=models.DateTimeField(blank=True, null=True),
),
]
@@ -1,28 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.5 on 2016-09-10 08:30
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('admin', '0005_auto_20160719_0903'),
]
operations = [
migrations.CreateModel(
name='SenderAddress',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('address', models.EmailField(max_length=254)),
('mailbox', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='admin.Mailbox')),
],
),
migrations.AlterUniqueTogether(
name='senderaddress',
unique_together=set([('address', 'mailbox')]),
),
]
-27
View File
@@ -1,27 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.9 on 2016-09-13 09:16
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('admin', '0006_auto_20160910_1030'),
]
operations = [
migrations.CreateModel(
name='MXRecord',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=254)),
('address', models.GenericIPAddressField()),
('managed', models.BooleanField(default=False)),
('updated', models.DateTimeField()),
('domain', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='admin.Domain')),
],
),
]
@@ -1,21 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.9 on 2016-09-13 14:14
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('admin', '0007_mxrecord'),
]
operations = [
migrations.AlterField(
model_name='dnsblresult',
name='mx',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='admin.MXRecord'),
),
]