#! /usr/bin/python --

"""
usage: %(progname)s [domain...]

Version: %(version)s

Contacts the apropriate whois database for each domain and displays
the result.

class WhoisRecord:
    self.domain             -- Domain Name
    self.whoisserver        -- Whoisserver associated with domain
    self.page               -- raw whois record data

    public methods:

        def WhoisRecord(domain=None)
            Whois object constructor

        def whois(domainname=None, server=None, cache=0)
            Fetches whoisrecord and places result in self.page
            Raises NoSuchDomain if the domain doesn't exist.


class DomainRecord(WhoisRecord):
    self.domainid           -- domainid for this domain
    self.created            -- date in which the domain was created
    self.lastupdated        -- date in which the domain was last updated.
    self.expires            -- date in which the domain expires
    self.databaseupdated    -- date in which the database was last updated.
    self.servers            -- list of (hostname, ip) pairs of the nameservers. 
    self.registrant         -- ContactRecord of domain owner. 
    self.contacts           -- dictionary of contacts (ContactRecord objects)

    public methods:

    def DomainRecord(domain=None)
        Constructor for DomainRecord

    def Parse()
        Creates a parsed version of the information contained in 
        the whois record for domain from self.page
        raises NoParser if a parser does not exist for a registry.



class Contact:
    self.type               -- Type of contact
    self.organization       -- Organization associated with contact.
    self.person             -- Person associated with contact.
    self.handle             -- NIC Handle
    self.address            -- Street address of contact
    self.address2           --
    self.address3           --
    self.city               -- city of addr
    self.state              -- address state
    self.zip                -- zipcode
    self.country            -- country
    self.email              -- Email address of contact
    self.phone              -- Phone Number
    self.fax                -- Fax Number
    self.lastupdated        -- Last update of contact record
"""

_version = "1.5"

import os, sys, string, time, getopt, socket, select, re, errno, copy, signal

timeout=5

try:
    signal.SIGALRM
    HAS_ALARM = 1
except:
    HAS_ALARM = 0


class WhoisRecord:      

    defaultserver='whois.verisign-grs.net'#'whois.networksolutions.com'
    whoismap={ 'com' : 'whois.internic.net' , \
               'org' : 'whois.pir.org' , \
               'net' : 'whois.internic.net' , \
               'edu' : 'whois.networksolutions.com' , \
               'biz' : 'whois.biz' , \
               'info': 'whois.afilias.info' , \
               'us'  : 'whois.nic.us', \
               'de'  : 'whois.denic.de' , \
               'gov' : 'whois.nic.gov' , \
               'name': 'whois.nic.name' , \
#???	       'pro': 'whois.nic.name' , \
               'museum': 'whois.museum' , \
               'int':  'whois.iana.org' , \
               'aero': 'whois.information.aero' , \
               'coop': 'whois.nic.coop' , \
               # See http://www.nic.gov/cgi-bin/whois 
               'mil' : 'whois.nic.mil' , \
               # See http://www.nic.mil/cgi-bin/whois
               'ca'  : 'whois.cdnnet.ca' , \
               'uk'  : 'whois.nic.uk' , \
               'au'  : 'whois.aunic.net' , \
               'hu'  : 'whois.nic.hu' , \

               # All the following are unverified/checked. 
               'be'  : 'whois.ripe.net',
               'it'  : 'whois.ripe.net' , \
               # also whois.nic.it
               'at'  : 'whois.ripe.net' , \
               # also www.nic.at, whois.aco.net
               'dk'  : 'whois.ripe.net' , \
               'fo'  : 'whois.ripe.net' , \
               'lt'  : 'whois.ripe.net' , \
               'no'  : 'whois.ripe.net' , \
               'sj'  : 'whois.ripe.net' , \
               'sk'  : 'whois.ripe.net' , \
               'tr'  : 'whois.ripe.net' , \
               # also whois.metu.edu.tr
               'il'  : 'whois.ripe.net' , \
               'bv'  : 'whois.ripe.net' , \
               'se'  : 'whois.nic-se.se' , \
               'br'  : 'whois.nic.br' , \
               # a.k.a. whois.fapesp.br?
               'fr'  : 'whois.nic.fr' , \
               'sg'  : 'whois.nic.net.sg' , \
               'hm'  : 'whois.registry.hm' , \
               # see also whois.nic.hm
               'nz'  : 'domainz.waikato.ac.nz' , \
               'nl'  : 'whois.domain-registry.nl' , \
               # RIPE also handles other countries
               # See  http://www.ripe.net/info/ncc/rir-areas.html
               'ru'  : 'whois.ripn.net' , \
               'ch'  : 'whois.nic.ch' , \
               # see http://www.nic.ch/whois_readme.html
               'jp'  : 'whois.nic.ad.jp' , \
               # (use DOM foo.jp/e for english; need to lookup !handles separately)
               'to'  : 'whois.tonic.to' , \
               'nu'  : 'whois.nic.nu' , \
               'fm'  : 'www.dot.fm' , \
               # http request http://www.dot.fm/search.html
               'am'  : 'whois.nic.am' , \
               'nu'  : 'www.nunames.nu' , \
               # http request
               # e.g. http://www.nunames.nu/cgi-bin/drill.cfm?domainname=nunames.nu
               #'cx'  : 'whois.nic.cx' , \	# no response from this server
               'af'  : 'whois.nic.af' , \
               'as'  : 'whois.nic.as' , \
               'li'  : 'whois.nic.li' , \
               'lk'  : 'whois.nic.lk' , \
               'mx'  : 'whois.nic.mx' , \
               'pw'  : 'whois.nic.pw' , \
               'sh'  : 'whois.nic.sh' , \
               # consistently resets connection
               'tj'  : 'whois.nic.tj' , \
               'tm'  : 'whois.nic.tm' , \
               'pt'  : 'whois.dns.pt' , \
               'kr'  : 'whois.nic.or.kr' , \
               # see also whois.krnic.net
               'kz'  : 'whois.nic.or.kr' , \
               # see also whois.krnic.net
               'al'  : 'whois.ripe.net' , \
               'az'  : 'whois.ripe.net' , \
               'ba'  : 'whois.ripe.net' , \
               'bg'  : 'whois.ripe.net' , \
               'by'  : 'whois.ripe.net' , \
               'cy'  : 'whois.ripe.net' , \
               'cz'  : 'whois.ripe.net' , \
               'dz'  : 'whois.ripe.net' , \
               'ee'  : 'whois.ripe.net' , \
               'eg'  : 'whois.ripe.net' , \
               'es'  : 'whois.ripe.net' , \
               'fi'  : 'whois.ripe.net' , \
               'gr'  : 'whois.ripe.net' , \
               'hr'  : 'whois.ripe.net' , \
               'lu'  : 'whois.ripe.net' , \
               'lv'  : 'whois.ripe.net' , \
               'ma'  : 'whois.ripe.net' , \
               'md'  : 'whois.ripe.net' , \
               'mk'  : 'whois.ripe.net' , \
               'mt'  : 'whois.ripe.net' , \
               'pl'  : 'whois.ripe.net' , \
               'ro'  : 'whois.ripe.net' , \
               'si'  : 'whois.ripe.net' , \
               'sm'  : 'whois.ripe.net' , \
               'su'  : 'whois.ripe.net' , \
               'tn'  : 'whois.ripe.net' , \
               'ua'  : 'whois.ripe.net' , \
               'va'  : 'whois.ripe.net' , \
               'yu'  : 'whois.ripe.net' , \
               # unchecked
               'ac'  : 'whois.nic.ac' , \
               'cc'  : 'whois.nic.cc' , \
               #'cn'  : 'whois.cnnic.cn' , \	# connection refused
               'gs'  : 'whois.adamsnames.tc' , \
               'hk'  : 'whois.apnic.net' , \
               #'ie'  : 'whois.ucd.ie' , \	# connection refused
               #'is'  : 'whois.isnet.is' , \# connection refused
               #'mm'  : 'whois.nic.mm' , \	# connection refused
               'ms'  : 'whois.adamsnames.tc' , \
               'my'  : 'whois.mynic.net' , \
               #'pe'  : 'whois.rcp.net.pe' , \	# connection refused
               'st'  : 'whois.nic.st' , \
               'tc'  : 'whois.adamsnames.tc' , \
               'tf'  : 'whois.adamsnames.tc' , \
               'th'  : 'whois.thnic.net' , \
               'tw'  : 'whois.twnic.net' , \
#	       'us'  : 'whois.isi.edu' , \
               'vg'  : 'whois.adamsnames.tc' , \
               #'za'  : 'whois.co.za'	# connection refused
               }



    def __init__(self,domain=None):
        self.domain=domain
        self.whoisserver=None	
        self.page=None

    def whois(self,domain=None, server=None, cache=0):
        if domain is not None:
            self.domain=domain

        if server is not None:
            self.whoisserver=server

        if self.domain is None:
            print "No Domain"
            raise "No Domain"

        if self.whoisserver is None:
            self.chooseserver()

        if self.whoisserver is None:
            print "No Server"
            raise "No Server"

        if cache:
            fn = "%s.dom" % domainname
            if os.path.exists(fn):
                return open(fn).read()

        self.page=self._whois()	

        if cache:
            open(fn, "w").write(page)


    def chooseserver(self):
        try:
            toplevel = string.split(self.domain, ".")[-1]

            self.whoisserver=WhoisRecord.whoismap.get(toplevel)
            #print toplevel, "---", self.whoisserver
            if self.whoisserver==None:
                self.whoisserver=WhoisRecord.defaultserver
                return
        except:
            self.whoisserver=WhoisRecord.defaultserver
            return

        if toplevel in ('com', 'org', 'net'):
            tmp=self._whois()
            m = re.search("Whois Server:(.+)", tmp)

            if m:
                self.whoisserver=string.strip(m.group(1))
                #print "server 2:", self.whoisserver
                return
            self.whoisserver='whois.networksolutions.com'
            tmp=self._whois()
            m=re.search("Whois Server:(.+)",tmp)
            if m:
                self.whoisserver=string.strip(m.group(1))
                #print "server 1:", self.whoisserver

                return
        #print "server 3:", self.whoisserver



    def _whois(self):
        def alrmhandler(signum,frame):
            raise "TimedOut", "on connect"

        s = None

        ## try until we timeout
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        if HAS_ALARM:
            s.setblocking(0)
            signal.signal(signal.SIGALRM,alrmhandler)
            signal.alarm(timeout)
        while 1:	
            try:
                s.connect((self.whoisserver, 43))
            except socket.error, (ecode, reason):
                if ecode==errno.EINPROGRESS: 
                    continue
                elif ecode==errno.EALREADY:
                    continue
                else:
                    raise socket.error, (ecode, reason)
                pass

            break
        if HAS_ALARM: signal.alarm(0)

        ret = select.select ([s], [s], [], 30)

        if len(ret[1])== 0 and len(ret[0]) == 0:
            s.close()
            raise TimedOut, "on data"

        s.setblocking(1)

        s.send("%s\n" % self.domain)
        page = ""
        while 1:
            data = s.recv(8196)
            if not data: break
            page = page + data
            pass

        s.close()

        if string.find(page, "No match for") != -1:
            raise 'NoSuchDomain', self.domain

        if string.find(page, "No entries found") != -1:
            raise 'NoSuchDomain', self.domain

        if string.find(page, "no domain specified") != -1:
            raise 'NoSuchDomain', self.domain

        if string.find(page, "NO MATCH:") != -1:
            raise 'NoSuchDomain', self.domain

        return page

##
## ----------------------------------------------------------------------
##
class ContactRecord:
    def __init__(self):
        self.type=None
        self.organization=None
        self.person=None
        self.handle=None
        self.address=None
        self.address2=None
        self.address3=None
        self.city=None
        self.country=None
        self.state=None
        self.zip=None
        self.email=None
        self.phone=None
        self.fax=None
        self.lastupdated=None
        return
    def __str__(self):
        return "Type: %s\nOrganization: %s\nPerson: %s\nHandle: %s\nAddress: %s\nAddress2: %s\nAddress3: %s\nCity: %s\nState: %s\nZip: %s\nCountry: %s\nEmail: %s\nPhone: %s\nFax: %s\nLastupdate: %s\n" % (self.type,self.organization,self.person,self.handle,self.address, self.address2, self.address3, self.city, self.state, self.zip, self.country, self.email,self.phone,self.fax,self.lastupdated)


class DomainRecord(WhoisRecord):

    parsemap={ 'whois.networksolutions.com' : 'ParseWhois_NetworkSolutions',
               'whois.opensrs.net'          : 'ParseWhois_NetworkSolutions', #needs to be customized!
               'whois.register.com'         : 'ParseWhois_RegisterCOM',
               'whois.alldomains.com'       : 'ParseWhois_AllDomainsCOM',
               'whois.iana.org'             : 'ParseWhois_INT', 
               'whois.nic.coop'             : 'ParseWhois_COOP',
               'whois.biz'                  : 'ParseWhois_BIZ',
               'whois.nic.us'               : 'ParseWhois_BIZ',
               'whois.information.aero'     : 'ParseWhois_INFO',
               'whois.nic.name'             : 'ParseWhois_INFO',
               'whois.pir.org'              : 'ParseWhois_INFO',
               'whois.museum'               : 'ParseWhois_INFO',
               'whois.afilias.info'         : 'ParseWhois_INFO' }

    def __init__(self,domain=None):
        WhoisRecord.__init__(self,domain)
        self.domainid = None
        self.created = None
        self.lastupdated = None
        self.expires = None
        self.databaseupdated = None
        self.servers = None
        self.registrant = ContactRecord()
        self.registrant.type='registrant'
        self.contacts = {}
        return

    def __str__(self):
        con=''
        for (k,v) in self.contacts.items():
            con=con + str(v) +'\n'
        return "%s (%s):\nWhoisServer: %s\nCreated : %s\nLastupdated : %s\nDatabaseupdated : %s\nExpires : %s\nServers : %s\nRegistrant >>\n\n%s\nContacts >>\n\n%s\n" % (self.domain, self.domainid,self.whoisserver,self.created, self.lastupdated, self.databaseupdated, self.expires,self.servers, self.registrant, con)

    def Parse(self):
        self._ParseWhois()
        return

    def _ParseWhois(self):
        m = re.search("Registrar Whois: (.*)", self.page)
        if m:
            self.whoisserver = m.group(1)
        
        parser=DomainRecord.parsemap.get(self.whoisserver)

        if parser==None:
            parser = 'ParseWhois_Generic'
            #raise 'NoParser for', self.whoisserver

        #print "---> PARSER:", self.whoisserver, "-",  parser

        parser='self.'+parser+'()'
        eval(parser)
        return

    ##
    ## ----------------------------------------------------------------------
    ##
    def _ParseContacts_RegisterCOM(self,page):

        parts = re.split("((?:(?:Administrative|Billing|Technical|Zone) Contact,?[ ]*)+:)\n", page)

        contacttypes = None
        for part in parts:
            if string.find(part, "Contact:") != -1:
                if part[-1] == ":": part = part[:-1]
                contacttypes = string.split(part, ",")
                continue
            part = string.strip(part)
            if not part: continue

            contact=ContactRecord()

            m = re.search("Email: (.+@.+)", part)
            if m:
                contact.email=string.lower(string.strip(m.group(1)))

            m = re.search("\s+Fax\.\.: (.+)", part)
            if m:
                contact.fax=string.lower(string.strip(m.group(1)))                		
            m = re.search("\s+Phone: (.+)", part)
            if m:
                contact.phone=m.group(1)
                end=m.start(0)

            start=0

            lines = string.split(part[start:end], "\n")
            lines = map(string.strip,lines)

            contact.organization = lines.pop(0)
            contact.person = lines.pop(0)

            contact.address=string.join(lines,'\n')

            for contacttype in contacttypes:
                contacttype = string.lower(string.strip(contacttype))
                contacttype = string.replace(contacttype, " contact", "")
                contact.type=contacttype
                self.contacts[contacttype] = copy.copy(contact)


    def ParseWhois_NetworkSolutions(self):
        m = re.search("Record last updated on (.+)\.", self.page)
        if m: self.lastupdated = m.group(1)

        m = re.search("Record created on (.+)\.", self.page)
        if m: self.created = m.group(1)

        m = re.search("Database last updated on (.+)\.", self.page)
        if m: self.databaseupdated = m.group(1)

        m = re.search("Record expires on (.+)\.",self.page)
        if m: self.expires=m.group(1)

        m = re.search("Registrant:(.+?)\n\n", self.page, re.S)
        if m: 
            start= m.start(1)
            end = m.end(1)
            reg = string.strip(self.page[start:end])

            reg = string.split(reg, "\n")
            reg = map(string.strip,reg)
            self.registrant.organization = reg[0]
            self.registrant.address = string.join(reg[1:],'\n')

            m = re.search("(.+) \((.+)\)", self.registrant.organization)
            if m: 
                self.domainid   = m.group(2)

        m = re.search("Domain servers in listed order:\n\n", self.page)
        if m:
            i = m.end()
            m = re.search("\n\n", self.page[i:])
            j = m.start()
            servers = string.strip(self.page[i:i+j])
            lines = string.split(servers, "\n")
            self.servers = []
            for line in lines:
                m=re.search("([\w|\.]+?)\s*(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})",string.strip(line))
                if m:
                    self.servers.append((m.group(1), m.group(2)))

        m = re.search("((?:(?:Administrative|Billing|Technical|Zone) Contact,?[ ]*)+:)\n", self.page)
        if m:
            i = m.start()
            m = re.search("Record expires on", self.page)
            j = m.start()
            contacts = string.strip(self.page[i:j])

        self._ParseContacts_NetworkSolutions(contacts)


    def ParseWhois_RegisterCOM(self):
        m = re.search("Record last updated on.*: (.+)", self.page)
        if m: self.lastupdated = m.group(1)

        m = re.search("Created on.*: (.+)", self.page)
        if m: self.created = m.group(1)

        m = re.search("Expires on.*: (.+)", self.page)
        if m: self.expires = m.group(1)

        m = re.search("Phone: (.+)", self.page)
        if m: self.registrant.phone=m.group(1)

        m = re.search("Email: (.+@.+)",self.page)
        if m: self.registrant.email=m.group(1)

        m = re.search("Fax\.\.: (.+@.+)",self.page)
        if m: self.registrant.fax=m.group(1)

        m = re.search("Organization:(.+?)Phone:",self.page,re.S)
        if m: 
            start=m.start(1)
            end=m.end(1)
            registrant = string.strip(self.page[start:end])
            registrant = string.split(registrant, "\n")
            registrant = map(string.strip,registrant)

            self.registrant.organization = registrant[0]
            self.registrant.person =registrant[1]
            self.registrant.address = string.join(registrant[2:], "\n")

        m = re.search("Domain servers in listed order:\n\n(.+?)\n\n", self.page, re.S)
        if m:
            start = m.start(1)
            end = m.end(1)
            servers = string.strip(self.page[start:end])
            lines = string.split(servers, "\n")


            self.servers = []
            for line in lines:
                m=re.search("([\w|\.]+?)\s*(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})",string.strip(line))
                if m:
                    self.servers.append((m.group(1), m.group(2)))

        m = re.search("((?:(?:Administrative|Billing|Technical|Zone) Contact,?[ ]*)+:)\n", self.page)
        if m:
            i = m.start()
            m = re.search("Domain servers in listed order", self.page)
            j = m.start()
            contacts = string.strip(self.page[i:j])
        self._ParseContacts_RegisterCOM(contacts)


    def _ParseContacts_NetworkSolutions(self,page):
        parts = re.split("((?:(?:Administrative|Billing|Technical|Zone) Contact,?[ ]*)+:)\n", page)

        contacttypes = None
        for part in parts:
            if string.find(part, "Contact:") != -1:
                if part[-1] == ":": part = part[:-1]
                contacttypes = string.split(part, ",")
                continue
            part = string.strip(part)
            if not part: continue

            record=ContactRecord()

            lines = string.split(part, "\n")
            m = re.search("(.+) \((.+)\) (.+@.+)", lines.pop(0))
            if m:
                record.person = string.strip(m.group(1))
                record.handle = string.strip(m.group(2))
                record.email = string.lower(string.strip(m.group(3)))
                pass

            record.organization=string.strip(lines.pop(0))

            flag = 0
            addresslines = []
            phonelines = []
            phonelines.append(string.strip(lines.pop()))
            for line in lines:
                line = string.strip(line)
                #m=re.search("^(\d|-|\+|\s)+$",line)
                #if m: flag = 1
                if flag == 0:
                    addresslines.append(line)
                else:
                    phonelines.append(line)
                    pass
                pass
            record.phone = string.join(phonelines, "\n")
            record.address = string.join(addresslines, "\n")

            for contacttype in contacttypes:
                contacttype = string.lower(string.strip(contacttype))
                contacttype = string.replace(contacttype, " contact", "")
                record.type=contacttype
                self.contacts.update({contacttype:copy.copy(record)})
        return


    def ParseWhois_AllDomainsCOM(self):
        m = re.search("Record last updated on\.*: (.+)\.", self.page)
        if m: self.lastupdated = m.group(1)

        m = re.search("Created on\.*: (.+)\.", self.page)
        if m: self.created = m.group(1)

        m = re.search("Expires on\.*: (.+)\.",self.page)
        if m: self.expires=m.group(1)

        rawstr = r'Registrant:\n(?P<org>.*?)\s*\((?P<id>DOM-.*)\)'
        m = re.search(rawstr, self.page)

        if m:
            self.registrant.organization = m.group('org')

            idx = string.find(self.page, "\n\n", m.end(2))
            self.registrant.address = string.strip(self.page[m.end(2)+1:idx])

            m = re.search("(.+) \((.+)\)", self.registrant.organization)
            if m: 
                self.domainid   = m.group('id')

        m = re.search("Domain servers in listed order:\n\n", self.page)
        if m:
            i = m.end()
            m = re.search("\n\n", self.page[i:])
            j = m.start()
            servers = string.strip(self.page[i:i+j])
            lines = string.split(servers, "\n")
            self.servers = []
            for line in lines:
                m=re.search("([\w|\.]+?)\s*(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})",string.strip(line))
                if m:
                    self.servers.append((m.group(1), m.group(2)))

        m = re.search("((?:(?:Administrative|Billing|Technical|Zone) Contact,?[ ]*)+:)\n", self.page)
        if m:
            i = m.start()
            m = re.search("Created on", self.page)
            try:
                j = m.start()
            except:
                j = len(self.page)
            contacts = string.strip(self.page[i:j])

        self._ParseContacts_AllDomainsCOM(contacts)


    def _ParseContacts_AllDomainsCOM(self,page):
        parts = re.split("((?:(?:Administrative|Billing|Technical|Zone) Contact,?[ ]*)+:)\n", page)

        contacttypes = None
        for part in parts:
            if string.find(part, "Contact:") != -1:
                if part[-1] == ":": part = part[:-1]
                contacttypes = string.split(part, ",")
                continue
            part = string.strip(part)
            if not part: continue

            record=ContactRecord()

            rawstr = r'(?P<person>.*?)\s*\((?P<handle>NIC-.*)\)\s*(?P<org>.*?)\n'
            m = re.search(rawstr, part, re.DOTALL)
            if m:
                record.handle = m.group('handle')
                record.organization = m.group('org')
                record.person = m.group('person')
                part = part[m.end('org'):]

            rawstr = r'.*\s(?P<email>.*?@.*?)\s(?P<phone>.*?)\sFAX-\s(?P<fax>.*)'
            m = re.search(rawstr, part, re.IGNORECASE | re.DOTALL)
            if m:
                record.email = m.group('email')
                record.phone = string.strip(m.group('phone'))
                record.fax   = m.group('fax')
                part = part[:m.start('email')]

            lines = string.split(part, "\n")
            addresslines = map(string.strip, lines)
            record.address = string.strip(string.join(addresslines, "\n"))

            for contacttype in contacttypes:
                contacttype = string.lower(string.strip(contacttype))
                contacttype = string.replace(contacttype, " contact", "")
                record.type=contacttype
                self.contacts.update({contacttype:copy.copy(record)})


    def ParseWhois_BIZ(self):
        biz_contacts = (
            {"page_field": "Administrative",  "rec_field": "administrative"},
            {"page_field": "Billing",         "rec_field": "billing"},
            {"page_field": "Technical",       "rec_field": "technical"},
            {"page_field": "Registrant",      "rec_field": "registrant"}
            )

        # map of each page field name to the corresponding field of a ContactRecord()
        # struct is a tuple of dicts where each dict has "page_field" and "rec_field" key/vals
        biz_contact_fields = (
            {"page_field": "ID",             "rec_field": "handle"},
            {"page_field": "Organization",   "rec_field": "organization"},
            {"page_field": "Name",           "rec_field": "person"},
            {"page_field": "Address1",       "rec_field": "address"},
            {"page_field": "City",           "rec_field": "city"},
            {"page_field": "State/Province", "rec_field": "state"},
            {"page_field": "Postal Code",    "rec_field": "zip"},
            {"page_field": "Country",        "rec_field": "country"},
            {"page_field": "Phone Number",   "rec_field": "phone"},
            {"page_field": "Facsimile",      "rec_field": "fax"},
            {"page_field": "Email",          "rec_field": "email"}
            )

        self._ParseContacts_Generic(biz_contacts, biz_contact_fields)

        biz_fields = (
            {'page_field': "Domain ID",                  "rec_field": "domainid"},
            {'page_field': "Domain Registration Date",   "rec_field": "created"},
            {'page_field': "Domain Expiration Date",     "rec_field": "expires"},
            {'page_field': "Name Server",                "rec_field": "servers"}
            )

        self._ParseWhois_Generic(biz_fields)



    def ParseWhois_INFO(self):
        info_contacts = (
            {"page_field": "Admin",       "rec_field": "administrative"},
            {"page_field": "Billing",     "rec_field": "billing"},
            {"page_field": "Tech",        "rec_field": "technical"},
            {"page_field": "Registrant",  "rec_field": "registrant"}
            )

        # map of each page field name to the corresponding field of a ContactRecord()
        # struct is a tuple of dicts where each dict has "page_field" and "rec_field" key/vals
        info_contact_fields = (
            {"page_field": "ID",             "rec_field": "handle"},
            {"page_field": "Organization",   "rec_field": "organization"},
            {"page_field": "Name",           "rec_field": "person"},
            {"page_field": "Address",        "rec_field": "address"}, # for NAME
            {"page_field": "Adress",         "rec_field": "address"}, # for MUSEUM (mispelled!)
            {"page_field": "Street1",        "rec_field": "address"}, # for INFO
            {"page_field": "Street2",        "rec_field": "address2"},
            {"page_field": "Street3",        "rec_field": "address3"},
            {"page_field": "City",           "rec_field": "city"},
            {"page_field": "State/Province", "rec_field": "state"},
            {"page_field": "Postal Code",    "rec_field": "zip"},
            {"page_field": "Country",        "rec_field": "country"},
            {"page_field": "Phone",          "rec_field": "phone"},
            {"page_field": "Phone Number",   "rec_field": "phone"}, # for AERO, MUSEUM
            {"page_field": "Facsimile",      "rec_field": "fax"},
            {"page_field": "Fax Number",     "rec_field": "fax"}, # for AERO, MUSEUM
            {"page_field": "Email",          "rec_field": "email"}
            )

        self._ParseContacts_Generic(info_contacts, info_contact_fields)

        info_fields = (
            {'page_field': "Domain ID",           "rec_field": "domainid"},
            {'page_field': "Created On",          "rec_field": "created"},
            {'page_field': "Creation Date",       "rec_field": "created"},
            {'page_field': "Expires On",          "rec_field": "expires"}, #NAME
            {'page_field': "Expiration Date",     "rec_field": "expires"}, #INFO. #AERO
            {'page_field': "Last Updated",        "rec_field": "lastupdated"},
            {'page_field': "Updated On",          "rec_field": "lastupdated"}, #MUSEUM
            {'page_field': "Name Server",         "rec_field": "servers"}
            )

        self._ParseWhois_Generic(info_fields)


    def ParseWhois_COOP(self):
        coop_contacts = (
            {"page_field": "domain:Admin-",       "rec_field": "administrative"},
            {"page_field": "domain:Billing-",     "rec_field": "billing"},
            {"page_field": "domain:Tech-",        "rec_field": "technical"},
            {"page_field": "domain:Registrant-",  "rec_field": "registrant"}
            )

        # map of each page field name to the corresponding field of a ContactRecord()
        # struct is a tuple of dicts where each dict has "page_field" and "rec_field" key/vals
        coop_contact_fields = (
            {"page_field": "ID",                    "rec_field": "handle"},
            {"page_field": "Organization",          "rec_field": "organization"},
            {"page_field": "Name",                  "rec_field": "person"},
            {"page_field": "Address1",              "rec_field": "address"},
            {"page_field": "Address2",              "rec_field": "address2"},
            {"page_field": "City",                  "rec_field": "city"},
            {"page_field": "State/Province",        "rec_field": "state"},
            {"page_field": "Postcode",              "rec_field": "zip"},
            {"page_field": "Country",               "rec_field": "country"},
            {"page_field": "Phone-Number",          "rec_field": "phone"},
            {"page_field": "Facsimile-Number",      "rec_field": "fax"},
            {"page_field": "Email",                 "rec_field": "email"}
            )

        self._ParseContacts_Generic(coop_contacts, coop_contact_fields)

        coop_fields = (
            {'page_field': "domain:ID",           "rec_field": "domainid"},
            {'page_field': "domain:Registration", "rec_field": "created"},
            {'page_field': "domain:Expiration",   "rec_field": "expires"}, 
            {'page_field': "domain:Name-Server",  "rec_field": "servers"}
            )

        self._ParseWhois_Generic(coop_fields)


    def ParseWhois_Generic(self):
        refer_cre = re.compile(r'ReferralServer: whois://(\S+)',re.I)
        m = refer_cre.search(self.page)
        while m:
            self.whois(self.domain,m.group(1))
            m = refer_cre.search(self.page)

        apnic_fields = (
            {'page_field': "netname",           "rec_field": "domainid"},
            {'page_field': "country",           "rec_field": "country"},
            )

        self._ParseWhois_Generic(apnic_fields)


    def _ParseWhois_Generic(self, fields):
        for field in fields:
            regex = "%s: *(.+)" % field['page_field']
            if field['rec_field'] == "servers":
                self.servers = []
                servers = re.findall(regex, self.page)
                for server in servers:
                    try:
                        server = string.strip(server)
                        ip = socket.gethostbyname(server)
                    except:
                        ip = "?"
                    self.servers.append((server, ip))
            else:
                m = re.search(regex, self.page, re.I)
                #if m: print m.group(1)
                if m: setattr(self, field['rec_field'], string.strip(m.group(1)))



    def _ParseContacts_Generic(self, contacts, fields):
        for contact in contacts:
            if contact['rec_field'] == 'registrant':
                self._ParseGenericContact(self.registrant, contact['page_field'], fields)
            else:
                self.contacts[contact['rec_field']] = ContactRecord()
                self.contacts[contact['rec_field']].type = contact['rec_field']
                self._ParseGenericContact(self.contacts[contact['rec_field']], contact['page_field'], fields)


    def _ParseGenericContact(self, contact_rec, contact, fields):
        for field in fields:
            m = re.search("%s *.*%s: *(.+)" % (contact, field['page_field']), self.page)
            if not m: continue

            setattr(contact_rec, field['rec_field'], string.strip(m.group(1)))


    def ParseWhois_INT(self):
        int_contacts = (
            {"page_field": "Registrant", "rec_field": "registrant"},
            {"page_field": "Administrative Contact", "rec_field": "administrative"},
            {"page_field": "Technical Contact", "rec_field": "technical"})

        page = string.replace(self.page, "\r\n", "\n")
        for contact in int_contacts:
            page_field = contact['page_field']
            s = "%s:(.*)\n\W" % page_field
            m = re.search(s,  page, re.DOTALL)
            #if m: print m.group(1)
            print "-------------------"

##
## ----------------------------------------------------------------------
##




##
## ----------------------------------------------------------------------
##

def usage(progname):
    version = _version
    print __doc__ % vars()

def main(argv, stdout, environ):
    progname = argv[0]
    list, args = getopt.getopt(argv[1:], "", ["help", "version"])

    for (field, val) in list:
        if field == "--help":
            usage(progname)
            return
        elif field == "--version":
            print progname, _version
            return

    #rec=WhoisRecord()
    #rec=DomainRecord()

    for domain in args:
        whoisserver=None
        if string.find(domain,'@')!=-1:
            (domain, whoisserver)=string.split(domain,'@')
        try:
            d = DomainRecord(domain)
            d.whois(domain, whoisserver)
            #print "---> PAGE:",  d.page
            d.Parse()
            print str(d)
            #print rec.page
        except 'NoSuchDomain', reason:
            print "ERROR: no such domain %s" % domain
#	except socket.error, (ecode,reason):
            print reason
        except "TimedOut", reason:
            print "Timed out", reason

if __name__ == "__main__":
    main(sys.argv, sys.stdout, os.environ)
