On Mon, Oct 06, 2008 at 04:09:38PM +0100,
 John L <[EMAIL PROTECTED]> wrote 
 a message of 22 lines which said:

> I'd suggest doing a lookup for A for _domainkey.$DOMAIN, and if you
> get NOERROR (that is, 0 records but no error code), then check
> *.$DOMAIN.

I've finally used such an algorithm, but more detailed (you can find a
lot of strange things in the DNS). I attach the Python module which
tests if a domain has a wildcard.

But it does not solve completely my "find DKIM records" problem since,
now, I find the records only in non-wildcarded domains. The wildcarded
domains may be have DKIM and may be not, there is no way of finding it
in the DNS. So, it seems you cannot really survey the deployment of
DKIM by just asking the DNS (unlike what happens with SPF).

Thanks for all those who helped.

#!/usr/bin/python

"""
 Tries to detect if a DNS domain uses DNS wildcards (records that
 match every name queried) or not.

 Code and bugs by Stephane Bortzmeyer <[EMAIL PROTECTED]>
 Advices by Joe Abley <[EMAIL PROTECTED]>

 Licence: do what you want, except if you work for Verisign, in which
 case contact the author for a specifically designed licence.

 The algorithm is a bit complicated because the DNS is a complicated
 world and almost every corner case is sure to happen one day or the
 other. So, the obvious algorithm (ask for the star) does not always
 work. That's also why there is an option 'analysis_depth' which
 allows the user to specify the level of "false positives" that is acceptable.
 
 Algorithm, with the maximum 'analysis_depth':

     1) Query a A record for the star (*). (.BZ used to reply to star but
        not to random names)
     
     2) Query a A record for one (or more if there was no reply to the
      star query above) random name (only LDH, 30 to 40
      characters). If there is NXDOMAIN or NOERROR with no answers
      (stupid but .VA does it), yield "no wildcard"
      
     3) Query a A record for another random name and check the reply
     is the same as before. Otherwise, yield"no wildcard"
      
     4) If all A record sets matches (some TLD have a set of records,
     unlike what Verisign did for .COM), yield "wildcard"
      
     5) Otherwise, yield "no wildcard"                        

 $Id: wildcards.py,v 1.2 2003-11-14 12:55:21 bortzmeyer Exp $
"""

import re
import sys
import os
import string
import random
import time
import getopt

# www.dnspython.org
import dns.resolver

# Defaults
qtype = 'A'
number_of_random_tests = 3
analysis_depth = 3
number_of_random_tests = 3
number_of_tests = 2 * number_of_random_tests   
    
class Wildcards:

    # Defaults
    min_domain_length = 40
    max_domain_length = 55
    verbosity = 0
    
    def __init__ (self, addresses = None):
        self.generator = random.Random (time.time())
        self.myresolver = dns.resolver.Resolver()
        if addresses is not None and addresses != []:
            self.myresolver.nameservers = addresses
    
    def random_name (self):
        """ Returns a legal DNS name, choosen randomly. """
        number = self.generator.randrange (self.min_domain_length,
                                           self.max_domain_length)
        name = ""
        for i in range (number):
            name = name + chr (self.generator.randrange (65, 89)) 
            # Only Letters
        return name

    def test_name (self, qname, qtype='A'):
        """ Test if there is a DNS record for this name and this
        type. Returns the values if there are some (or if there is no
        answer for this QTYPE but no error either) and None otherwise."""
        values = []
        if self.verbosity:
            print >>sys.stderr, ("Testing a %s record in %s..." % (qtype, qname))
        try:
            result = self.myresolver.query (qname, qtype)
        except dns.resolver.NXDOMAIN:
            if self.verbosity > 3:
                print >>sys.stderr, "No such domain %s" % qname
            return None
        except dns.resolver.NoAnswer:
            if self.verbosity > 3:
                print >>sys.stderr, "No answer for %s (rr type %s)" % (qname, qtype)
            return values # Empty array, not None
        except (dns.resolver.NoNameservers, dns.resolver.Timeout):
            if self.verbosity > 3:
                print >>sys.stderr, "Zone %s broken" % qname
            raise
        if qtype == 'A':
            values = [a.address for a in result]
        else:
            values = [a for a in result]
        return values
    
    def has_wildcard (self, domain, type='A'):   
        """ Test if the domain uses DNS wildcards and return the
        values if so (it may be an empty array, if there is no data
        for this resource record type), and None otherwise."""
        records_needed = 0
        replies_to_star = False
        records_star = self.test_name ("*." + domain, type)
        if records_star is not None:
            if self.verbosity:
                print >>sys.stderr, ("%s has probably a %s wildcard (replies to star)" % (domain, type))
            records_star.sort()
            replies_to_star = True
        if analysis_depth == 0:
            if records_star:
                return records_star
            else:
                return None
        records_needed = number_of_random_tests # If they do not reply to
                                 # star requests (to evade detection?), we need
                                 # more certainty
        records_array = []
        for i in range (records_needed):
            random_domain = self.random_name() + "." + domain
            records_random = self.test_name (random_domain, type)
            if records_random is not None:
                if self.verbosity:
                    print >>sys.stderr, ("%s has probably a %s wildcard" % (domain, type))
                records_random.sort()
                records_array.append (records_random)
            else:
                return None
        if replies_to_star:
            if records_array[0] == records_star:
                if self.verbosity:
                    print >>sys.stderr, ("%s has regular %s wildcards" % (domain, type))
                return records_star
        else:
            wildcard_seen = 0
            records_random = records_array[0]
            for i in range (records_needed-1):
                if records_random != records_array[i+1]:
                    if self.verbosity:
                        print >>sys.stderr, ("%s has different data for type %s" % (domain, type))
                    return None
            if self.verbosity:
                print >>sys.stderr, ("%s has %s wildcards (but does not reply to star)" % (domain, type))
            return records_random
        if self.verbosity:
            print >>sys.stderr, ("%s does not really have %s wildcards" % (domain, type))
            print records_random
            print records_array
            print records_star
        return None

def usage():
    print >>sys.stderr, ("Usage: %s [-v N] [-r addr1,addr2 ] [-t qtype] zone\n" % sys.argv[0])

if __name__ == '__main__':
    resolvers = []
    try:
        optlist, args = getopt.getopt (sys.argv[1:], "v:t:r:",
                                       ["verbosity=", "type=", "resolvers="])
        for option, value in optlist:
            if option == "--verbosity" or option == "-v":
                checker.verbosity = int(value)
            elif option == "--type" or option == "-t":
                qtype = string.upper(value)
            elif option == "--resolvers" or option == "-r":
                resolvers = string.split(value, ',')
    except getopt.error, reason:
        print >>sys.stderr, ("%s\n" % reason)
        usage()
        sys.exit(1)
    if len(args) < 1:
        print >>sys.stderr, ("Not enough arguments\n")
        usage()
        sys.exit(1)
    checker = Wildcards(resolvers)
    for domain in args:
        records = checker.has_wildcard (domain, type=qtype)
        if records is not None and len(records) == 0:
            print "%s has wildcards but no data for type %s" % (domain, qtype)
        elif records is not None:
            print "%s has %s wildcards (%s)" % (domain, qtype, str(records))
        else:
            print "%s does not have %s wildcards" % (domain, qtype)
        
_______________________________________________
NOTE WELL: This list operates according to 
http://mipassoc.org/dkim/ietf-list-rules.html

Reply via email to