Example #1
0
 def query(self, start_response, req, path, client, format="", alt_resolver=None,
           do_dnssec=False, tcp=False, cd=False, edns_size=default_edns_size,
           reverse=False):
     """ path must starts with a /, then the domain name then an
     (optional) / followed by the QTYPE """
     if not path.startswith('/'):
         raise Exception("Internal error: no / at the beginning of %s" % path)
     plaintype = 'text/plain; charset=%s' % self.encoding
     if not format:
         mformat = req.accept.best_match(['text/html', 'application/xml',
                                         'application/json', 'text/dns',
                                         'text/plain'])
         if mformat == "text/html":
             format = "HTML"
         elif mformat == "application/xml":
             format = "XML"
         elif mformat == "application/json":
             format = "JSON"
         elif mformat == "text/dns":
             format = "ZONE"
         elif mformat == "text/plain":
             format = "TEXT"    
         if not mformat:
             output = "No suitable output format found\n" 
             send_response(start_response, '400 Bad request', output, plaintype)
             return [output]
         mtype = '%s; charset=%s' % (mformat, self.encoding)
     else:
         if format == "TEXT" or format == "TXT":
             format = "TEXT"
             mtype = 'text/plain; charset=%s' % self.encoding
         elif format == "HTML":
             mtype = 'text/html; charset=%s' % self.encoding
         elif format == "JSON":
             mtype = 'application/json'
         elif format == "ZONE":
             mtype = 'text/dns' # RFC 4027
         # TODO: application/dns, "detached" DNS (binary), see issue #20
         elif format == "XML":
             mtype = 'application/xml'
         else:
             output = "Unsupported format \"%s\"\n" % format
             send_response(start_response, '400 Bad request', output, plaintype)
             return [output]
     ip_client = netaddr.IPAddress(client)
     if ip_client.version == 4:
         ip_prefix = netaddr.IPNetwork(client + "/28")
     elif ip_client.version == 6:
         ip_prefix = netaddr.IPNetwork(client + "/64")
     else:
         output = "Unsupported address family \"%s\"\n" % ip_client.version
         send_response(start_response, '400 Unknown IP version', output, plaintype)
         return [output]
     if ip_client not in self.whitelist:
         if self.buckets.has_key(ip_prefix.cidr):
             if self.buckets[ip_prefix.cidr].full():
                 status = '429 Too many requests'
                 # 429 registered by RFC 6585 in april 2012
                 # http://www.iana.org/assignments/http-status-codes
                 # Already common
                 # http://www.flickr.com/photos/girliemac/6509400997/in/set-72157628409467125
                 output = "%s sent too many requests" % client # TODO: better message
                 send_response(start_response, status, output, plaintype)
                 return [output]
             else:
                 self.buckets[ip_prefix.cidr].add(1)
         else:
             self.buckets[ip_prefix.cidr] = LeakyBucket(size=self.bucket_size)
     args = path[1:]
     slashpos = args.find('/')
     if slashpos == -1:
         if reverse:
             domain = str(dns.reversename.from_address(args))
             qtype = 'PTR'
         else:
             domain = args
             qtype = 'ADDR'
         qclass = 'IN'
     else:
         if reverse:
             domain = str(dns.reversename.from_address(args[:slashpos]))
         else:
             domain = args[:slashpos]
         nextslashpos = args.find('/', slashpos+1)
         if nextslashpos == -1:
             requested_qtype = args[slashpos+1:].upper()
             qclass = 'IN'
         else:
             requested_qtype = args[slashpos+1:nextslashpos].upper()
             qclass = args[nextslashpos+1:].upper()
         # We do not test if the QTYPE exists. If it doesn't
         # dnspython will raise an exception. The formatter will
         # have to deal with the various records.
         if requested_qtype == "":
             if reverse:
                 qtype = 'PTR'
             else:
                 qtype = 'ADDR'
         else:
             qtype = requested_qtype
         if reverse and qtype != 'PTR':
             output = "You cannot ask for a query type other than PTR with reverse queries\n" 
             send_response(start_response, '400 Bad qtype with reverse',
                           output, plaintype)
             return [output]
         # Pseudo-qtype ADDR is handled specially later
     if not domain.endswith('.'):
         domain += '.'
     if domain == 'root.':
         domain = '.'
     domain = unicode(domain, self.encoding)
     for forbidden in self.forbidden_suffixes:
         if domain.endswith(forbidden):
             output = "You cannot query local domain %s" % forbidden
             send_response(start_response, '403 Local domain is private',
                           output, plaintype)
             return [output]
     punycode_domain = punycode_of(domain)
     if punycode_domain != domain:
         qdomain = punycode_domain.encode("US-ASCII")
     else:
         qdomain = domain.encode("US-ASCII")
     try:
         if format == "HTML":
             formatter = Formatter.HtmlFormatter(domain)
         elif format == "TEXT":
             formatter = Formatter.TextFormatter(domain)
         elif format == "JSON":
             formatter = Formatter.JsonFormatter(domain)
         elif format == "ZONE":
             formatter = Formatter.ZoneFormatter(domain)
         elif format == "XML":
             formatter = Formatter.XmlFormatter(domain)
         self.resolver.reset()
         if edns_size is None:
             self.resolver.set_edns(version=-1)
         else:
             if do_dnssec:
                 self.resolver.set_edns(payload=edns_size, dnssec=True)
             else:
                 self.resolver.set_edns(payload=edns_size)
         if alt_resolver:
             self.resolver.set_nameservers([alt_resolver,])
         query_start = datetime.now()
         if qtype != "ADDR":
             answer = self.resolver.query(qdomain, qtype, qclass, tcp=tcp, cd=cd)
         else:
             try:
                 answer = self.resolver.query(qdomain, "A", tcp=tcp, cd=cd)
             except dns.resolver.NoAnswer: 
                 answer = None
             try:
                 answer_bis = self.resolver.query(qdomain, "AAAA", tcp=tcp, cd=cd)
                 if answer_bis is not None:
                     for rrset in answer_bis.answer:
                         answer.answer.append(rrset)
             except dns.resolver.NoAnswer: 
                 pass  
             # TODO: what if flags are different with A and AAAA? (Should not happen)
             if answer is None:
                 query_end = datetime.now()
                 self.delay = query_end - query_start
                 formatter.format(None, qtype, qclass, 0, self)
                 output = formatter.result(self)
                 send_response(start_response, '200 OK', output, mtype)
                 return [output]
         query_end = datetime.now()
         self.delay = query_end - query_start
         formatter.format(answer, qtype, qclass, answer.flags, self)
         output = formatter.result(self)
         send_response(start_response, '200 OK', output, mtype)
     except Resolver.UnknownRRtype:
         output = "Record type %s does not exist\n" % qtype
         output = output.encode(self.encoding)
         send_response(start_response, '400 Unknown record type', output, 
                       plaintype)
     except Resolver.UnknownClass:
         output = "Class %s does not exist\n" % qclass
         output = output.encode(self.encoding)
         send_response(start_response, '400 Unknown class', output, 
                       plaintype)
     except Resolver.NoSuchDomainName:
         output = "Domain %s does not exist\n" % domain
         output = output.encode(self.encoding)
         # TODO send back in the requested format (see issue #11)
         send_response(start_response, '404 No such domain', output, plaintype)
     except Resolver.Refused:
         output = "Refusal to answer for all name servers for %s\n" % domain
         output = output.encode(self.encoding)
         send_response(start_response, '403 Refused', output, plaintype)
     except Resolver.Servfail:
         output = "Server failure for all name servers for %s (may be a DNSSEC validation error)\n" % domain
         output = output.encode(self.encoding)
         send_response(start_response, '504 Servfail', output, plaintype)
     except Resolver.Timeout: 
         output = "No server replies for domain %s\n" % domain
         output = output.encode(self.encoding)
         # TODO issue #11. In that case, do not serialize output.
         send_response(start_response, '504 Timeout', output,
                       "text/plain")
     except Resolver.NoPositiveAnswer: 
         output = "No server replies for domain %s\n" % domain
         output = output.encode(self.encoding)
         # TODO issue #11
         send_response(start_response, '504 No positive answer', output,
                       "text/plain")
     except Resolver.UnknownError as code:
         output = "Unknown error %s resolving %s\n" % (dns.rcode.to_text(int(str(code))), domain)
         output = output.encode(self.encoding)
         # TODO issue #11
         send_response(start_response, '500 Unknown server error', output, plaintype)
     return [output]