Skip to content

Instantly share code, notes, and snippets.

@m-manu
Last active July 18, 2017 19:15
Show Gist options
  • Save m-manu/6580231 to your computer and use it in GitHub Desktop.
Save m-manu/6580231 to your computer and use it in GitHub Desktop.
Command-line utility to reliably verify an e-mail address. (Python)
#!/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