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