mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-17 05:41:25 +02:00
reset admin's migrations steps to avoid complex migration stuff; add moar options; improve coding style
This commit is contained in:
@@ -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')]),
|
||||
),
|
||||
]
|
||||
@@ -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'),
|
||||
),
|
||||
]
|
||||
Reference in New Issue
Block a user