introduce mx record

This commit is contained in:
Gael Pasgrimaud
2016-09-13 16:24:04 +02:00
parent 04ddbf590d
commit 28c99abb46
8 changed files with 145 additions and 40 deletions
+43 -1
View File
@@ -2,9 +2,15 @@
"""Internal library for admin."""
import socket
import ipaddress
from datetime import datetime
from datetime import timedelta
from functools import wraps
from itertools import chain
import dns.resolver
from django.contrib.contenttypes.models import ContentType
from django.db.models import Q
from django.utils.translation import ugettext as _
@@ -14,7 +20,7 @@ from modoboa.core import signals as core_signals
from modoboa.lib import events
from modoboa.lib.exceptions import PermDeniedException
from .models import Domain, DomainAlias, Alias
from .models import Domain, DomainAlias, Alias, MXRecord
def needs_mailbox():
@@ -100,6 +106,42 @@ def get_domains(user, domfilter=None, searchquery=None, **extrafilters):
return domains
def get_mx_records_for_domain(domain, ttl=7200):
try:
answers = dns.resolver.query(domain.name, "MX")
except dns.resolver.NoAnswer:
return
now = datetime.now()
delta = timedelta(seconds=ttl)
records = MXRecord.objects.filter(domain=domain,
updated__gte=now - delta)
if records.count():
print('skip update')
for record in records:
yield record
raise StopIteration()
MXRecord.objects.filter(domain=domain).delete()
for answer in answers:
address = None
try:
ipaddress.ip_address(str(answer.exchange))
except ValueError:
try:
address = socket.gethostbyname(str(answer.exchange))
except socket.gaierror:
pass
else:
address = str(answer.exchange)
finally:
if address is not None:
record = MXRecord.objects.create(
domain=domain,
name=str(answer.exchange).strip('.'),
address=address,
updated=now)
yield record
def check_if_domain_exists(name, extra_checks=None):
"""Check if a domain already exists.
@@ -3,6 +3,7 @@
from subcommand.base import SubcommandCommand
from .subcommands._dnsbl import CheckDNSBLCommand
from .subcommands._mx import CheckMXRecords
from .subcommands._export import ExportCommand
from .subcommands._import import ImportCommand
@@ -18,5 +19,6 @@ class Command(SubcommandCommand):
subcommands = {
"export": ExportCommand,
"import": ImportCommand,
"check_mx": CheckMXRecords,
"check_dnsbl": CheckDNSBLCommand,
}
@@ -1,8 +1,5 @@
"""Management command to check defined domains against DNSBL providers."""
import ipaddress
import dns.resolver
import gevent
from gevent import socket
@@ -14,6 +11,7 @@ from django.utils.translation import ugettext as _
from modoboa.admin import constants
from modoboa.admin import models
from modoboa.admin.lib import get_mx_records_for_domain
from modoboa.lib import parameters
from modoboa.lib import email_utils
@@ -41,37 +39,37 @@ class CheckDNSBLCommand(BaseCommand):
"--timeout", type=int, default=3,
help="Timeout used for queries.")
def query(self, ip_list, provider):
def query(self, mx_list, provider):
"""Check given IP against given provider."""
results = {}
for ip in ip_list:
reverse = ".".join(reversed(ip.split(".")))
for mx in mx_list:
reverse = ".".join(reversed(mx.address.split(".")))
pattern = "{}.{}".format(reverse, provider)
try:
results[ip] = socket.gethostbyname(pattern)
results[mx] = socket.gethostbyname(pattern)
except socket.gaierror:
results[ip] = False
results[mx] = False
return provider, results
def store_domain_result(self, domain, provider, results):
"""Store provider results for domain."""
alerts = {}
to_create = []
for ip in results.keys():
result = "" if not results[ip] else results[ip]
for mx in results.keys():
result = "" if not results[mx] else results[mx]
dnsbl_result = models.DNSBLResult.objects.filter(
domain=domain, provider=provider, mx=ip).first()
domain=domain, provider=provider, mx=mx).first()
if dnsbl_result is None:
to_create.append(
models.DNSBLResult(
domain=domain, provider=provider, mx=ip,
domain=domain, provider=provider, mx=mx,
status=result)
)
else:
if not dnsbl_result.status and result:
if domain not in alerts:
alerts[domain] = []
alerts[domain].append((provider, ip))
alerts[domain].append((provider, mx))
dnsbl_result.status = result
dnsbl_result.save()
models.DNSBLResult.objects.bulk_create(to_create)
@@ -90,34 +88,13 @@ class CheckDNSBLCommand(BaseCommand):
self.sender, admin.email,
domainsubject=subject, content=content)
if not status:
print msg
print(msg)
def check_domain(self, domain, timeout):
"""Check specified domain."""
resolver = dns.resolver.Resolver()
try:
answers = resolver.query(domain.name, "MX")
except dns.resolver.NoAnswer:
return
ip_list = []
for answer in answers:
address = None
try:
ipaddress.ip_address(str(answer.exchange))
except ValueError:
try:
address = socket.gethostbyname(str(answer.exchange))
except socket.gaierror:
pass
else:
address = str(answer.exchange)
finally:
if address is not None:
ip_list.append(address)
if len(ip_list) == 0:
return
mx_list = get_mx_records_for_domain(domain)
jobs = [
gevent.spawn(self.query, ip_list, provider)
gevent.spawn(self.query, mx_list, provider)
for provider in self.providers]
gevent.joinall(jobs, timeout)
for job in jobs:
@@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
"""Management command to check defined domains against MX records."""
from django.core.management.base import BaseCommand
from modoboa.admin import models
from modoboa.admin.lib import get_mx_records_for_domain
class CheckMXRecords(BaseCommand):
"""Command class."""
help = "Check defined domains against MX records."
def handle(self, *args, **options):
"""Command entry point."""
for domain in models.Domain.objects.all():
for record in get_mx_records_for_domain(domain):
print(record)
+27
View File
@@ -0,0 +1,27 @@
# -*- 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')),
],
),
]
@@ -0,0 +1,21 @@
# -*- 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'),
),
]
+2 -1
View File
@@ -1,7 +1,7 @@
"""Admin models."""
from .base import AdminObject
from .domain import Domain, DNSBLResult
from .domain import Domain, MXRecord, DNSBLResult
from .domain_alias import DomainAlias
from .mailbox import Mailbox, Quota, MailboxOperation, SenderAddress
from .alias import Alias, AliasRecipient
@@ -15,6 +15,7 @@ __all__ = [
"DomainAlias",
"Mailbox",
"MailboxOperation",
"MXRecord",
"Quota",
"SenderAddress",
]
+17 -1
View File
@@ -102,6 +102,10 @@ class Domain(AdminObject):
def aliases(self):
return self.domainalias_set
@property
def mx_records(self):
self.mxrecord_set
@cached_property
def dnsbl_status_color(self):
"""Shortcut to DNSBL results."""
@@ -220,6 +224,18 @@ class Domain(AdminObject):
reversion.register(Domain)
class MXRecord(models.Model):
domain = models.ForeignKey(Domain)
name = models.CharField(max_length=254)
address = models.GenericIPAddressField()
managed = models.BooleanField(default=False)
updated = models.DateTimeField()
def __unicode__(self):
return u'{0.name} ({0.address}) for {0.domain} '.format(self)
class DNSBLQuerySet(models.QuerySet):
"""Custom manager for DNSBLResultManager."""
@@ -233,7 +249,7 @@ class DNSBLResult(models.Model):
domain = models.ForeignKey(Domain)
provider = models.CharField(max_length=254, db_index=True)
mx = models.GenericIPAddressField()
mx = models.ForeignKey(MXRecord)
status = models.CharField(max_length=45, blank=True, db_index=True)
objects = models.Manager.from_queryset(DNSBLQuerySet)()