mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-17 05:41:25 +02:00
introduce mx record
This commit is contained in:
+43
-1
@@ -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)
|
||||
@@ -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'),
|
||||
),
|
||||
]
|
||||
@@ -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",
|
||||
]
|
||||
|
||||
@@ -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)()
|
||||
|
||||
Reference in New Issue
Block a user