Last active
July 18, 2017 19:15
-
-
Save m-manu/6580231 to your computer and use it in GitHub Desktop.
Command-line utility to reliably verify an e-mail address. (Python)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python | |
""" | |
Description: | |
Command-line tool to validate an email address. | |
After verifying e-mail address by pattern matching, checks the validity with the SMTP server. | |
Usage: | |
./email-verify [email protected] | |
# (c) 2013 Manu Manjunath | |
# Extended from (c) 2012 Syrus Akbary <[email protected]> | |
# Extended from (c) 2011 Noel Bush <[email protected]> | |
# This code is made available to you under the GNU LGPL v3. | |
""" | |
import sys, re | |
host_check = True | |
smtp_check = True | |
try: | |
import DNS, socket | |
except: | |
host_check = False | |
try: | |
import smtplib | |
except: | |
smtp_check = False | |
WSP = r'[ \t]' # see 2.2.2. Structured Header Field Bodies | |
CRLF = r'(?:\r\n)' # see 2.2.3. Long Header Fields | |
NO_WS_CTL = r'\x01-\x08\x0b\x0c\x0f-\x1f\x7f' # see 3.2.1. Primitive Tokens | |
QUOTED_PAIR = r'(?:\\.)' # see 3.2.2. Quoted characters | |
FWS = r'(?:(?:' + WSP + r'*' + CRLF + r')?' + WSP + r'+)' # see 3.2.3. Folding white space and comments | |
CTEXT = r'[' + NO_WS_CTL + r'\x21-\x27\x2a-\x5b\x5d-\x7e]' # see 3.2.3 | |
CCONTENT = r'(?:' + CTEXT + r'|' + QUOTED_PAIR + r')' # see 3.2.3 (NB: The RFC includes COMMENT here as well, but that would be circular.) | |
COMMENT = r'\((?:' + FWS + r'?' + CCONTENT + r')*' + FWS + r'?\)' # see 3.2.3 | |
CFWS = r'(?:' + FWS + r'?' + COMMENT + ')*(?:' + FWS + '?' + COMMENT + '|' + FWS + ')' # see 3.2.3 | |
ATEXT = r'[\w!#$%&\'\*\+\-/=\?\^`\{\|\}~]' # see 3.2.4. Atom | |
ATOM = CFWS + r'?' + ATEXT + r'+' + CFWS + r'?' # see 3.2.4 | |
DOT_ATOM_TEXT = ATEXT + r'+(?:\.' + ATEXT + r'+)*' # see 3.2.4 | |
DOT_ATOM = CFWS + r'?' + DOT_ATOM_TEXT + CFWS + r'?' # see 3.2.4 | |
QTEXT = r'[' + NO_WS_CTL + r'\x21\x23-\x5b\x5d-\x7e]' # see 3.2.5. Quoted strings | |
QCONTENT = r'(?:' + QTEXT + r'|' + QUOTED_PAIR + r')' # see 3.2.5 | |
QUOTED_STRING = CFWS + r'?' + r'"(?:' + FWS + r'?' + QCONTENT + r')*' + FWS + r'?' + r'"' + CFWS + r'?' | |
LOCAL_PART = r'(?:' + DOT_ATOM + r'|' + QUOTED_STRING + r')' # see 3.4.1. Addr-spec specification | |
DTEXT = r'[' + NO_WS_CTL + r'\x21-\x5a\x5e-\x7e]' # see 3.4.1 | |
DCONTENT = r'(?:' + DTEXT + r'|' + QUOTED_PAIR + r')' # see 3.4.1 | |
DOMAIN_LITERAL = CFWS + r'?' + r'\[' + r'(?:' + FWS + r'?' + DCONTENT + r')*' + FWS + r'?\]' + CFWS + r'?' # see 3.4.1 | |
DOMAIN = r'(?:' + DOT_ATOM + r'|' + DOMAIN_LITERAL + r')' # see 3.4.1 | |
ADDR_SPEC = LOCAL_PART + r'@' + DOMAIN # see 3.4.1 | |
# A valid address will match exactly the 3.4.1 addr-spec. | |
VALID_ADDRESS_REGEXP = '^' + ADDR_SPEC + '$' | |
if __name__ == '__main__': | |
if len(sys.argv)!=2: | |
print "Pass e-mail ID as param to this script" | |
quit() | |
email = sys.argv[1] | |
try: | |
print "Checking pattern...", | |
assert re.match(VALID_ADDRESS_REGEXP, email) is not None | |
print "[VALID]" | |
hostname = email[email.find('@')+1:] | |
if host_check: | |
try: | |
print "Verifying host...", | |
sys.stdout.flush() | |
result = socket.getaddrinfo(hostname, None) | |
print "[VALID]" | |
print "Checking if '%s' can receive e-mail..." % hostname, | |
sys.stdout.flush() | |
DNS.DiscoverNameServers() | |
mx_hosts = DNS.mxlookup(hostname) | |
if smtp_check: | |
if len(mx_hosts) > 0: | |
print "[Yes]" | |
print "Verifying email...", | |
sys.stdout.flush() | |
isValid = True | |
verificationRejected = False | |
connectionError = False | |
for mx in mx_hosts: | |
try: | |
smtp = smtplib.SMTP() | |
smtp.connect(mx[1]) | |
status, _ = smtp.helo() | |
if status != 250: | |
continue | |
smtp.mail('[email protected]') | |
status, _ = smtp.rcpt(email) | |
if status == 250: | |
print "[VALID, says %s]" % (mx[1]) | |
else: | |
print "[INVALID, says %s]" % (mx[1]) | |
sys.stdout.flush() | |
break | |
except smtplib.SMTPServerDisconnected: #Server not permits verify user | |
verificationRejected=True | |
continue | |
except smtplib.SMTPConnectError: | |
connectionError=True | |
continue | |
if connectionError: | |
print "Connection error. You may want to retry." | |
elif verificationRejected: | |
print "Server(s) refused to validate e-mail." | |
else: | |
print "[NOT FOUND]\n(Domain '%s' doesn't have any registered MX records)" % hostname | |
else: | |
print "Warning: Skipped e-mail verification for %s" % email | |
except socket.gaierror: | |
print "Invalid hostname '%s'" % hostname | |
except DNS.ServerError: | |
print "Host %s could not be reached/connected to" % hostname | |
except DNS.Base.TimeoutError: | |
print "Host %s did not respond within time" % hostname | |
else: | |
print "Warning: Skipped checking validity of domain '%s'\nInstall Python packages 'DNS' (http://sourceforge.net/projects/pydns/) and 'socket' for detailed verification" % hostname | |
except AssertionError: | |
print "Error: '%s' doesn't match a valid e-mail pattern" % email |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment