#! /usr/local/bin/python --


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

Version: %(version)s

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

public methods:

  def whois(domainname, whoisserver=None, cache=0)
    raises NoSuchDomain if the domain doesn't exist.

    return the result of contacting NetworkSolutions

  def ParseWhois(page)
    returns a DomainRecord object that contains a parsed
    version of the information contained in 'page'.

class DomainRecord:
    self.domain             -- name of the domain
    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         -- name of the person or organization that 
                               registered the domain.
    self.registrant_address -- address of the person or organization that 
                               registered the domain.
    self.contacts           -- dictionary of contacts


"""

_version = "1.0"

import os, sys, string, time, getopt, socket, select, re

NoSuchDomain = "NoSuchDomain"

def whois(domainname, whoisserver=None, cache=0):
  if whoisserver is None:
    whoisserver = "whois.networksolutions.com"

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

  page = _whois(domainname, whoisserver)

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

  return page

def _whois(domainname, whoisserver):
  s = None

  ## try until we are connected
  while s == None:
    try:
      s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
      s.setblocking(0)
      try:
	s.connect(whoisserver, 43)
      except socket.error, (ecode, reason):
	if ecode in (115, 150): pass
	else:
	  raise socket.error, (ecode, reason)
      ret = select.select([s], [s], [], 30)

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

    except socket.error, (ecode, reason):
      print ecode, reason
      time.sleep(10)
      s = None


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

  s.close()

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

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

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

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


  return page

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

class DomainRecord:
  def __init__(self, domain):
    self.domain = domain
    self.domainid = None
    self.created = None
    self.lastupdated = None
    self.expires = None
    self.databaseupdated = None
    self.servers = None
    self.registrant = None
    self.registrant_address = None
    self.contacts = {}

  def __str__(self):
    return "%s (%s): (created:%s) (lastupdated:%s) (databaseupdated:%s) (servers:%s) (registrant:%s) (address:%s) (contacts:%s)" % (self.domain, self.domainid, self.created, self.lastupdated, self.databaseupdated, self.servers, self.registrant, repr(self.registrant_address), self.contacts)


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

def _ParseContacts_RegisterCOM(page):
  contactDict = {}
  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 = {}

    lines = string.split(part, "\n")
    m = re.search("(.+)  (.+@.+)", lines[0])
    if m:
      record['name'] = string.strip(m.group(1))
      record['handle'] = None
      record['email'] = string.lower(string.strip(m.group(2)))

    flag = 0
    phonelines = string.strip(lines[1])
    record['phone'] = phonelines
    record['address'] = []

    for contacttype in contacttypes:
      contacttype = string.lower(string.strip(contacttype))
      contacttype = string.replace(contacttype, " contact", "")
      contactDict[contacttype] = record

  return contactDict



def ParseWhois_RegisterCOM(page):

  m = re.search("Domain Name: (.+)", page)
  domain = m.group(1)
  rec = DomainRecord(domain)



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

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

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


  m = re.search("Registrant:", page)
  if m: 
    i = m.end()
    m = re.search("\n\n", page[i:])
    j = m.start()
    registrant = string.strip(page[i:i+j])
    lines = string.split(registrant, "\n")
    registrant = []
    for line in lines:
      line = string.strip(line)
      if not line: continue
      registrant.append(line)
    rec.registrant = registrant[0]
    rec.registrant_address = string.join(registrant[1:], "\n")

    m = re.search("(.+) \((.+)\)$", rec.registrant)
    if m: 
      rec.registrant = m.group(1)
      rec.domainid = m.group(2)

  m = re.search("Domain servers in listed order:\n\n", page)
  if m:
    i = m.end()
    m = re.search("\n\n", page[i:])
    j = m.start()
    servers = string.strip(page[i:i+j])
    lines = string.split(servers, "\n")
    servers = []
    for line in lines:
      parts = string.split(string.strip(line))
      if not parts: continue
      servers.append(parts[0], parts[1])
    rec.servers = servers

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

    rec.contacts = _ParseContacts_RegisterCOM(contacts)

  return rec

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

def _ParseContacts_NetworkSolutions(page):
  contactDict = {}
  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 = {}

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

    flag = 0
    addresslines = []
    phonelines = []
    for line in lines[1:]:
      line = string.strip(line)
      if not line: 
	flag = 1
	continue
      if flag == 0:
	addresslines.append(line)
      else:
	phonelines.append(line)
    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", "")
      contactDict[contacttype] = record

  return contactDict

def ParseWhois_NetworkSolutions(page):
  m = re.search("Domain Name: (.+)", page)
  domain = m.group(1)
  rec = DomainRecord(domain)

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

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

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

  m = re.search("Registrant:", page)
  if m: 
    i = m.end()
    m = re.search("\n\n", page[i:])
    j = m.start()
    registrant = string.strip(page[i:i+j])
    lines = string.split(registrant, "\n")
    registrant = []
    for line in lines:
      line = string.strip(line)
      if not line: continue
      registrant.append(line)
    rec.registrant = registrant[0]
    rec.registrant_address = string.join(registrant[1:], "\n")

    m = re.search("(.+) \((.+)\)$", rec.registrant)
    if m: 
      rec.registrant = m.group(1)
      rec.domainid = m.group(2)

  m = re.search("Domain servers in listed order:\n\n", page)
  if m:
    i = m.end()
    m = re.search("\n\n", page[i:])
    j = m.start()
    servers = string.strip(page[i:i+j])
    lines = string.split(servers, "\n")
    servers = []
    for line in lines:
      parts = string.split(string.strip(line))
      if not parts: continue
      servers.append(parts[0], parts[1])
    rec.servers = servers

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

    rec.contacts = _ParseContacts_NetworkSolutions(contacts)

  return rec


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

FailedTest = "FailedTest"

def _test_ParseWhois_RegisterCOM():
  data = """
Registrant:
   Google Inc.
   2400 Bayshore Pkwy
   Mountain View, CA 94043
   US

   Registrar..: Register.com (http://www.register.com)
   Domain Name: IGOOGLE.COM
      Created on..............: Thu, Oct 14, 1999
      Expires on..............: Sat, Oct 13, 2001
      Record last updated on..: Thu, Oct 14, 1999

   Administrative Contact:
      Kamangar, Salar  salar@google.com
      650-318-0200
   Technical Contact, Zone Contact:
      Internic, Registrar  internic-free@register.com
      212-594-9880

   Domain servers in listed order:

   DNS1.REGISTER.COM                                 209.67.50.220     
   DNS2.REGISTER.COM                                 209.67.50.241     

Register your domain name at http://www.register.com

"""

  rec = ParseWhois_RegisterCOM(data)
  if rec.domain != "IGOOGLE.COM": raise FailedTest, "domain"
  if rec.created != "Thu, Oct 14, 1999": raise FailedTest, "created"
  if rec.expires != "Sat, Oct 13, 2001": raise FailedTest, "expires"
  if rec.lastupdated != "Thu, Oct 14, 1999": raise FailedTest, "lastupdated"

  if rec.registrant != "Google Inc.": raise FailedTest, "registrant"
  if rec.registrant_address != '2400 Bayshore Pkwy\012Mountain View, CA 94043\012US': raise FailedTest, "registrant_address"
  if rec.servers[0][0] != "DNS1.REGISTER.COM": raise FailedTest, "primary"
  if rec.servers[1][0] != "DNS2.REGISTER.COM": raise FailedTest, "secondary"
  if rec.servers[0][1] != "209.67.50.220": raise FailedTest, "primary ip"
  if rec.servers[1][1] != "209.67.50.241": raise FailedTest, "secondary ip"

  try:
    c = rec.contacts['administrative']
  except KeyError:
    raise FailedTest, 'administrative'

  if c['email'] != 'salar@google.com': raise FailedTest, 'email'
  if c['name'] != 'Kamangar, Salar': raise FailedTest, 'name'
  if c['phone'] != '650-318-0200': raise FailedTest, 'phone'
  return
  


def _test_ParseWhois_NetworkSolutions():
  data = """
  Registrant:
Scott Hassan (DOTFUNK-DOM)
   XXX Duncan St.
   San Francisco, CA 94XXX
   US

   Domain Name: DOTFUNK.COM

   Administrative Contact, Technical Contact, Zone Contact:
      Hassan, Scott  (SH177)  hassan-nic2@DOTFUNK.COM
      DotFunk Communications
5214-F Diamond Heights Blvd, #731
San Francisco, CA 94XXX

      (415) XXX XXXX
   Billing Contact:
      Hassan, Scott  (SH177)  hassan-nic2@DOTFUNK.COM
      DotFunk Communications
5214-F Diamond Heights Blvd, #731
San Francisco, CA 94XXX

      (415) XXX XXXX

   Record last updated on 14-Feb-2000.
   Record created on 11-Nov-1999.
   Database last updated on 30-Mar-2000 13:40:27 EST.

   Domain servers in listed order:

   NS1.DOTFUNK.COM              206.16.70.82
   NS2.DOTFUNK.COM              206.16.70.84

"""

  rec = ParseWhois_NetworkSolutions(data)

  if rec.domain != "DOTFUNK.COM": raise FailedTest, "domain"
  if rec.created != "11-Nov-1999": raise FailedTest, "created"
  if rec.databaseupdated != "30-Mar-2000 13:40:27 EST": raise FailedTest, "databaseupdated"
  if rec.lastupdated != "14-Feb-2000": raise FailedTest, "lastupdated"

  if rec.registrant != "Scott Hassan": raise FailedTest
  if rec.registrant_address != 'XXX Duncan St.\012San Francisco, CA 94XXX\012US': raise FailedTest
  if rec.servers[0][0] != "NS1.DOTFUNK.COM": raise FailedTest, "primary"
  if rec.servers[1][0] != "NS2.DOTFUNK.COM": raise FailedTest, "secondary"
  if rec.servers[0][1] != "206.16.70.82": raise FailedTest, "primary ip"
  if rec.servers[1][1] != "206.16.70.84": raise FailedTest, "secondary ip"

  try:
    c = rec.contacts['administrative']
  except KeyError:
    raise FailedTest, "administrative"

  if c['email'] != 'hassan-nic2@dotfunk.com': raise FailedTest, "email"
  if c['name'] != 'Hassan, Scott': raise FailedTest, "name"
  if c['address'] != 'DotFunk Communications\0125214-F Diamond Heights Blvd, #731\012San Francisco, CA 94XXX': raise FailedTest, "name"
  if c['phone'] != '(415) XXX XXXX': raise FailedTest, "phone"

  return
##
## ----------------------------------------------------------------------
##

def ParseWhois(page):
  if string.find(page, "Registrar..: Register.com (http://www.register.com)") != -1:
    return ParseWhois_RegisterCOM(page)
  else:
    return ParseWhois_NetworkSolutions(page)
  

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

def test():
  import traceback
  try:
    _test_ParseWhois_NetworkSolutions()
    print "test_ParseWhois_RegisterCOM() passed"
  except:
    print "test_ParseWhois_RegisterCOM() failed"
    traceback.print_exc()

  try:
    _test_ParseWhois_RegisterCOM()
    print "test_ParseWhois_RegisterCOM() passed"
  except:
    print "test_ParseWhois_RegisterCOM() failed"
    traceback.print_exc()

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

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

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

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


  for domain in args:
    try:
      page = whois(domain)
      print page
    except NoSuchDomain, reason:
      print "ERROR: no such domain %s" % domain

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