|
|
import re |
|
|
import dns.resolver |
|
|
import smtplib |
|
|
import requests |
|
|
import threading |
|
|
import queue |
|
|
import dns.reversename |
|
|
|
|
|
CACHE_TTL = 600 |
|
|
|
|
|
|
|
|
resolver = dns.resolver.Resolver(configure=False) |
|
|
resolver.nameservers = ['8.8.8.8'] |
|
|
resolver.cache = dns.resolver.Cache() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def is_valid_email(email): |
|
|
|
|
|
pattern = r''' |
|
|
^ # Start of string |
|
|
(?!.*[._%+-]{2}) # No consecutive special characters |
|
|
[a-zA-Z0-9._%+-]{1,64} # Local part: allowed characters and length limit |
|
|
(?<![._%+-]) # No special characters at the end of local part |
|
|
@ # "@" symbol |
|
|
[a-zA-Z0-9.-]+ # Domain part: allowed characters |
|
|
(?<![.-]) # No special characters at the end of domain |
|
|
\.[a-zA-Z]{2,}$ # Top-level domain with minimum 2 characters |
|
|
''' |
|
|
|
|
|
|
|
|
return re.match(pattern, email, re.VERBOSE) is not None |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def query_dns(record_type, domain): |
|
|
try: |
|
|
|
|
|
record_name = domain if record_type == 'MX' else f'{domain}.' |
|
|
cache_result = resolver.cache.get((record_name, record_type)) |
|
|
if cache_result is not None and (dns.resolver.mtime() - cache_result.time) < CACHE_TTL: |
|
|
return True |
|
|
|
|
|
|
|
|
resolver.timeout = 2 |
|
|
resolver.lifetime = 2 |
|
|
resolver.resolve(record_name, record_type) |
|
|
return True |
|
|
except dns.resolver.NXDOMAIN: |
|
|
|
|
|
return False |
|
|
except dns.resolver.NoAnswer: |
|
|
|
|
|
return False |
|
|
except dns.resolver.Timeout: |
|
|
|
|
|
return False |
|
|
except: |
|
|
|
|
|
return False |
|
|
|
|
|
|
|
|
def has_valid_mx_record(domain): |
|
|
|
|
|
def query_mx(results_queue): |
|
|
results_queue.put(query_dns('MX', domain)) |
|
|
|
|
|
def query_a(results_queue): |
|
|
results_queue.put(query_dns('A', domain)) |
|
|
|
|
|
|
|
|
mx_queue = queue.Queue() |
|
|
a_queue = queue.Queue() |
|
|
mx_thread = threading.Thread(target=query_mx, args=(mx_queue,)) |
|
|
a_thread = threading.Thread(target=query_a, args=(a_queue,)) |
|
|
mx_thread.start() |
|
|
a_thread.start() |
|
|
|
|
|
|
|
|
mx_thread.join() |
|
|
a_thread.join() |
|
|
mx_result = mx_queue.get() |
|
|
a_result = a_queue.get() |
|
|
|
|
|
return mx_result or a_result |
|
|
|
|
|
|
|
|
|
|
|
def verify_email(email): |
|
|
|
|
|
domain = email.split('@')[1] |
|
|
|
|
|
|
|
|
try: |
|
|
mx_records = dns.resolver.resolve(domain, 'MX') |
|
|
except dns.resolver.NoAnswer: |
|
|
return False |
|
|
|
|
|
|
|
|
for mx in mx_records: |
|
|
try: |
|
|
smtp_server = smtplib.SMTP(str(mx.exchange)) |
|
|
smtp_server.ehlo() |
|
|
smtp_server.mail('') |
|
|
code, message = smtp_server.rcpt(str(email)) |
|
|
smtp_server.quit() |
|
|
if code == 250: |
|
|
return True |
|
|
except: |
|
|
pass |
|
|
|
|
|
return False |
|
|
|
|
|
|
|
|
|
|
|
def is_disposable(domain): |
|
|
blacklists = [ |
|
|
'https://raw.githubusercontent.com/andreis/disposable-email-domains/master/domains.txt', |
|
|
'https://raw.githubusercontent.com/wesbos/burner-email-providers/master/emails.txt' |
|
|
] |
|
|
|
|
|
for blacklist_url in blacklists: |
|
|
try: |
|
|
blacklist = set(requests.get(blacklist_url).text.strip().split('\n')) |
|
|
if domain in blacklist: |
|
|
return True |
|
|
except Exception as e: |
|
|
print(f'Error loading blacklist {blacklist_url}: {e}') |
|
|
return False |
|
|
|