Code

Ticket #3138: dns_check.2.py

File dns_check.2.py, 6.7 KB (added by Michael Radziej <mir@…>, 8 years ago)

... adding stuff I forgot before ...

Line 
1# -*- coding: UTF-8 -*-
2
3"""
4Demo code for a DNS resolving validator. You need to rearrange this for your needs.
5
6you'll need dnspython >= 1.4.0  from  http://www.dnspython.org/
7
8requires two settings: DNS_LIFETIME (time for a single dns query)
9and DNS_TIMEOUT (timeout for completely resolving one domain name).
10
11to make use of the asynchronous lookup feature, you need to call hasResolvableDomain.prepare()
12for each form field with this validator before validation starts.
13
14
15I don't recommend to use this as a validator
16since DNS can have transient errors.
17
18- mir@noris.de
19"""
20
21import re
22import dns.resolver, dns.name, dns.exception    # from dnspython
23
24
25
26def is_resolvable(domain_string, rdatatypes, resolver):
27    """Testet, ob der String in line auflösbar ist, und gibt entsprechend ein Boolean zurück."""
28    for rdatatype in rdatatypes:
29        answer = resolver.resolve(domain_string, rdatatype)
30        if answer != None:
31            return True
32    return False
33
34class hasResolvableDomain(object):
35    """Testet, ob ein Feld eine DNS-auflösbare Domain enthält.
36    Das funktioniert für Domains, Email-Adressen, auch für einen ganzen Text,
37    bei dem in jeder Zeile ein Eintrag steht.
38
39    >>> from kundebunt.popkern.async_dns import AsyncDNSResolver
40    >>> resolver=AsyncDNSResolver()
41    >>> nl=chr(10)
42    >>> validator = hasResolvableDomain(resolver)
43    >>> validator("noris.de")
44    >>> validator("blazlsprazl.noris.de")
45    Traceback (most recent call last):
46        ...
47    ValidationError: ['blazlsprazl.noris.de: Die angegebene Domain ist im DNS nicht vorhanden.']
48    >>> validator("braslfasl@noris.de")
49    >>> validator(nl.join(["noris.de","braslfasl@noris.de"]))
50    >>> validator("")
51    >>> validator("aosidjasoasdioj")
52    Traceback (most recent call last):
53        ...
54    ValidationError: ['aosidjasoasdioj: Die angegebene Domain ist im DNS nicht vorhanden.']
55    >>> validator("..")
56    Traceback (most recent call last):
57        ...
58    ValidationError: ['..: Die angegebene Domain ist im DNS nicht vorhanden.']
59    >>> validator = hasResolvableDomain(resolver, ["mx"],"fehlermeldung")
60    >>> validator("noris.de")
61    >>> validator(nl.join(["blablara@noris.de","service.noris.de"]))
62    Traceback (most recent call last):
63        ...
64    ValidationError: ['service.noris.de: fehlermeldung']
65
66    >>> validator = hasResolvableDomain(resolver, subdomain_field_name='subdomain')
67    >>> validator('service.noris.net', {'subdomain': 'raxlbratzl'})
68    Traceback (most recent call last):
69        ...
70    ValidationError: ['raxlbratzl.service.noris.net: Die angegebene Domain ist im DNS nicht vorhanden.']
71    >>> validator('raxlbratzl.noris.net', {'subdomain': '.'})
72    Traceback (most recent call last):
73        ...
74    ValidationError: ['raxlbratzl.noris.net: Die angegebene Domain ist im DNS nicht vorhanden.']
75
76    >>> validator('noris.net', {'subdomain': 'service'})
77    >>> validator('noris.net', {'subdomain': '.service'})
78    """
79
80    def __init__(self, resolver, rrtypes = None, error_message=gettext_lazy("Die angegebene Domain ist im DNS nicht vorhanden."), subdomain_field_name=None, allow_wildcard=False):
81        self.resolver = resolver
82        if rrtypes == None:
83            rrtypes = ["A", "MX"]
84        self.rrtypes = [dns.rdatatype.from_text(s) for s in rrtypes]
85        self.error_message = error_message
86        self.subdomain_field_name = subdomain_field_name
87        self.allow_wildcard = allow_wildcard
88
89    def _domains_to_check(self, field_data, all_data):
90        """generiert alle zu überprüfenden Domains."""
91        if not self.allow_wildcard or field_data!="*":
92            for line in field_data.split("\n"):
93                line = line.strip()
94                if line:
95                    at_pos = line.find('@')
96                    if at_pos != -1:
97                        # es ist eine email-adresse
98                        domain = line[at_pos+1:]
99                    else:
100                        domain = line
101                    if self.subdomain_field_name:
102                        subdomain = all_data.get(self.subdomain_field_name,'')
103                        if subdomain and subdomain[0] != '.':
104                            domain = "%s.%s" % (subdomain, domain)
105                    if len(domain)>=2 and domain[0] == '.' and domain[1] != '.':
106                        domain = domain[1:]
107                    elif len(domain)>=3 and domain[:2] == '*.' and domain[2] != '.':
108                        domain = domain[2:]
109                    yield domain
110
111
112    def __call__(self, field_data, all_data=None):
113        for domain in self._domains_to_check(field_data, all_data):
114            resolvable = is_resolvable(domain + ".", self.rrtypes, self.resolver)
115            if not resolvable:
116                raise validators.ValidationError, "%s: %s" % (domain,self.error_message)
117
118    def prepare(self, field_data, all_data=None):
119        """submits the dns query to the resolver so that the reply can be ready in __call__"""
120        for domain in self._domains_to_check(field_data, all_data):
121            for rrtype in self.rrtypes:
122                self.resolver.submit(domain + ".", rrtype)
123
124import dns.resolver, threading, time
125from django.conf import settings
126
127
128class AsyncDNSResolver(object):
129    def __init__(self):
130        self.threads = {}
131        self.answers = {}  # answers is modified within threads; apply locking!
132        self.lock = threading.Lock()
133
134    def submit(self, domain_string, rdatatype):
135        query = (domain_string, rdatatype)
136        if not query in self.threads and not query in self.answers:
137            thread = threading.Thread(target=_run_thread, args=(domain_string, rdatatype, self.answers, self.lock))
138            thread.setDaemon(True)
139            thread.start()
140            self.final_submit_time = time.time()
141            self.threads[query] = thread
142
143
144    def resolve(self, domain_string, rdatatype):
145        self.submit(domain_string, rdatatype)
146        query = (domain_string, rdatatype)
147        if self.threads[query].isAlive():
148            self.threads[query].join(self._compute_timeout())
149        self.lock.acquire()
150        try:
151            answer = self.answers.get(query)
152        finally:
153            self.lock.release()
154        return answer
155
156    def _compute_timeout(self):
157        return max(settings.DNS_LIFETIME - (time.time() - self.final_submit_time) , 0)
158
159
160def _run_thread(domain_string, rdatatype, answers, lock):
161    resolver = dns.resolver.Resolver()
162    resolver.timeout = settings.DNS_TIMEOUT
163    resolver.lifetime = settings.DNS_LIFETIME
164    try:
165        answer = resolver.query(domain_string, rdatatype)
166    except (dns.exception.Timeout, dns.resolver.NXDOMAIN, dns.resolver.NoAnswer, dns.name.EmptyLabel):
167        answer = None
168    lock.acquire()
169    try:
170        answers[(domain_string, rdatatype)] = answer
171    finally:
172        lock.release()