Initial commit
Benjamin Renard

Benjamin Renard commited on 2013-05-29 22:45:50
Showing 1 changed files, with 221 additions and 0 deletions.

... ...
@@ -0,0 +1,221 @@
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
+
0 222