+

Last commit for mass_validate_email.py: bc4cf9904e7ba3f4aac9cdf5c4904bb48b05417f

Initial commit

Benjamin Renard [2013-05-29 22:45:50]
Initial commit
  1. #!/usr/bin/python
  2. #
  3. # Python module to mass validating email address
  4. #
  5. # This module was inspired by (and use) validate_email library
  6. # write by Syrus Akbary :
  7. #
  8. # https://github.com/SyrusAkbary/validate_email
  9. #
  10. # This main goal is to optimize mass validating using cache of
  11. # bad (or good) domain or MX server.
  12. #
  13. # Author: Benjamin Renard
  14. # Website: http://git.zionetrix.net/mass_validate_email
  15. # Licence: LGPL
  16.  
  17. import sys
  18. import logging
  19.  
  20. from validate_email import validate_email
  21. import smtplib
  22. import socket
  23. import DNS
  24.  
  25. try:
  26. DNS.DiscoverNameServers()
  27. except DNS.ServerError, e:
  28. logging.fatal("Error discovering DNS servers : %s" % e)
  29. sys.exit(1)
  30.  
  31. # options
  32. class OptionsClass(object):
  33.  
  34. def __init__(self):
  35. self.debug = False
  36. self.debugsmtp = False
  37. self.checkmx = False
  38. self.verifyaddress = False
  39. self.usesmtpvrfy = False
  40. self.refusemailifnotpermit = True
  41. self.refuseontemporaryerror = True
  42.  
  43. options=OptionsClass()
  44.  
  45. if options.verifyaddress:
  46. options.checkmx=True
  47.  
  48. def clean_mail(mail):
  49. mail=str(mail).lower().strip()
  50. return mail
  51.  
  52.  
  53. domain_mx={}
  54. valid_domain=[]
  55. invalid_domain=[]
  56. def check_mx(mail):
  57. dom = mail[mail.find('@')+1:]
  58. if not options.verifyaddress:
  59. if dom in valid_domain:
  60. return True
  61. elif dom in invalid_domain:
  62. return False
  63. else:
  64. try:
  65. mx_hosts = DNS.mxlookup(dom)
  66. if len(mx_hosts)>0:
  67. domain_mx[dom]=[]
  68. for mx in mx_hosts:
  69. domain_mx[dom].append(mx[1])
  70. logging.debug("MX of domain %s : %s" % (dom,','.join(domain_mx[dom])))
  71. valid_domain.append(dom)
  72. return True
  73. elif connect_to_mx(dom):
  74. domain_mx[dom]=[dom]
  75. logging.debug("MX of domain %s : %s" % (dom,','.join(domain_mx[dom])))
  76. valid_domain.append(dom)
  77. return True
  78. else:
  79. logging.debug("No valid MX of domain %s found" % dom)
  80. invalid_domain.append(dom)
  81. return False
  82. except DNS.ServerError, e:
  83. logging.debug('Error getting MX servers of domain %s : %s' % (dom,e))
  84. invalid_domain.append(dom)
  85. return False
  86. else:
  87. if dom in invalid_domain:
  88. return False
  89. if dom in domain_mx:
  90. for mx in domain_mx[dom]:
  91. con = connect_to_mx(mx)
  92. if not con:
  93. continue
  94. if check_mail_on_mx(dom,con,mail,ifNotPermit=(not options.refusemailifnotpermit)):
  95. return True
  96. return False
  97. else:
  98. try:
  99. mx_hosts = DNS.mxlookup(dom)
  100. if len(mx_hosts)>0:
  101. domain_mx[dom]=[]
  102. for mx in mx_hosts:
  103. domain_mx[dom].append(mx[1])
  104. valid_domain.append(dom)
  105. return check_mx(mail)
  106. else:
  107. # Directly check MX and mail
  108. con=connect_to_mx(dom)
  109. if not con:
  110. invalid_domain.append(dom)
  111. return False
  112. domain_mx[dom]=[dom]
  113. return check_mail_on_mx(dom,con,mail,ifNotPermit=(not options.refusemailifnotpermit))
  114. except DNS.ServerError, e:
  115. logging.debug('Error getting MX servers of domain %s : %s' % (dom,e))
  116. invalid_domain.append(dom)
  117. return False
  118.  
  119. valid_mx=[]
  120. invalid_mx=[]
  121. def verify_mx(mx,mail,check_mail=False):
  122. if not check_mail and mx in valid_mx:
  123. return True
  124. elif not check_mail and mx in invalid_mx:
  125. return False
  126.  
  127. def connect_to_mx(mx):
  128. if mx in invalid_mx:
  129. return False
  130. try:
  131. smtp = smtplib.SMTP(timeout=5)
  132. smtp.connect(mx)
  133. if options.debugsmtp:
  134. smtp.set_debuglevel(True)
  135. valid_mx.append(mx)
  136. return smtp
  137. except smtplib.SMTPConnectError:
  138. logging.debug("MX server %s does not respond from SMTP" % mx)
  139. except smtplib.SMTPServerDisconnected:
  140. logging.debug("MX server %s unexpectedly closed connection" % mx)
  141. except socket.gaierror:
  142. logging.debug("Can't resolv MX server %s" % mx)
  143. except socket.timeout:
  144. logging.debug("Connection timeout to SMTP server %s" % mx)
  145. except socket.error:
  146. logging.debug("Connection error on SMTP server %s" % mx)
  147. except Exception, e:
  148. logging.error("Unknown error (%s) connecting to SMTP server %s : %s" % (type(e),mx,e))
  149. invalid_mx.append(mx)
  150. return None
  151.  
  152. mx_refuse_check_mail=[]
  153. def check_mail_on_mx(mx,smtp,mail,ifNotPermit=False):
  154. if mx in mx_refuse_check_mail:
  155. return ifNotPermit
  156. try:
  157. status, _ = smtp.helo()
  158. if status != 250:
  159. mx_refuse_check_mail.append(mx)
  160. return ifNotPermit
  161.  
  162. if options.usesmtpvrfy:
  163. (status, msg) = smtp.verify(mail)
  164. if status >= 250 and status < 260:
  165. # Server normaly return an normalize email address
  166. for word in msg.split(' '):
  167. if validate_email(word):
  168. return True
  169. smtp.mail('')
  170. status, msg = smtp.rcpt(mail)
  171. if status >= 400 and status < 500:
  172. logging.debug('SMTP server return temporary error (code=%s) : %s' % (status,msg))
  173. return not options.refuseontemporaryerror
  174. elif status != 250:
  175. return False
  176. return True
  177. except smtplib.SMTPServerDisconnected:
  178. # Server not permits verify user
  179. mx_refuse_check_mail.append(mx)
  180. return ifNotPermit
  181. except smtplib.SMTPConnectError:
  182. return False
  183.  
  184. def mass_validate_email(mail,simple=False):
  185. mail=clean_mail(mail)
  186. if not validate_email(mail):
  187. return
  188. elif simple:
  189. return True
  190. elif options.checkmx:
  191. return check_mx(mail)
  192. else:
  193. return True
  194.  
  195. if __name__=='__main__':
  196.  
  197. if len(sys.argv)!=2:
  198. print "Usage : %s [email]" % sys.argv[0]
  199. sys.exit(0)
  200.  
  201. logging.basicConfig(level=logging.DEBUG)
  202. options.debugsmtp=True
  203.  
  204. mail=sys.argv[1]
  205.  
  206. print "Simple syntax validation :"
  207. print "=========================="
  208. print "Return : %s" % mass_validate_email(mail)
  209.  
  210. options.checkmx=True
  211. print "\n\n"
  212. print "Syntax validation and domain MX check :"
  213. print "======================================="
  214. print "Return : %s" % mass_validate_email(mail)
  215.  
  216. options.verifyaddress=True
  217. print "\n\n"
  218. print "Syntax validation, domain MX check and validation of email address by SMTP server :"
  219. print "==================================================================================="
  220. print "Return : %s" % mass_validate_email(mail)
  221.