Skip to content

Instantly share code, notes, and snippets.

@RobinMcCorkell
Last active August 29, 2015 14:20
Show Gist options
  • Save RobinMcCorkell/e6ff4242668872c0f6e3 to your computer and use it in GitHub Desktop.
Save RobinMcCorkell/e6ff4242668872c0f6e3 to your computer and use it in GitHub Desktop.
Test an SMTP server
#!/usr/bin/env python3
import random
import string
import smtplib
import argparse
import sys
import getpass
parser = argparse.ArgumentParser(description='Test an SMTP server for security')
parser.add_argument('--host', required=True, help='SMTP server')
parser.add_argument('--port', type=int, default=25, help='Port to connect to')
parser.add_argument('--username', required=True, help='Valid SMTP user to test with')
parser.add_argument('--password', help='Password for SMTP user')
parser.add_argument('--security', choices=['none', 'ssl', 'starttls'], default='starttls', help='Security mode of connection')
parser.add_argument('--local-mail', required=True, help='Local email address corresponding to SMTP user')
parser.add_argument('--unauth-mail', required=True, help='Local email address invalid for the SMTP user')
parser.add_argument('--remote-mail', default='[email protected]', help='Remote email address')
parser.add_argument('--non-fqdn-mail', default='test@example', help='Non-fully qualified email address, for sanity testing')
parser.add_argument('--invalid-domain-mail', default='[email protected]', help='Unknown domain email address, for sanity testing')
args = parser.parse_args()
if not args.password:
args.password = getpass.getpass('Password for SMTP user %s: ' % args.username)
randstring = ''.join([random.choice(string.ascii_letters + string.digits) for n in range(6)])
test_msg = 'From: Automated SMTP Test <%s>\r\nTo: <%s>\r\nSubject: SMTP server test %s: %s\r\n\r\nAutomated test email'
testSuccess = True
mailsSent = 0
def testFailed(expected, ref, notice):
if ref == None:
ref = ''
else:
ref = ' | ref: %s' % (ref)
if notice == None:
notice = ''
else:
notice = ' | %s' % (notice)
if expected:
expected = 'success'
else:
expected = 'rejection'
return 'failed! Expected %s%s%s' % (expected, notice, ref)
def runTest(sender, recipient, successExpected=True, auth=False, msg='', ref=None, notice=None):
global testSuccess
global mailsSent
print('Test %s... ' % (msg), end='')
sys.stdout.flush()
if args.security == 'ssl':
smtp = smtplib.SMTP_SSL(args.host, args.port)
elif args.security == 'starttls':
smtp = smtplib.SMTP(args.host, args.port)
smtp.starttls()
else:
smtp = smtplib.SMTP(args.host, args.port)
authUser = None
if auth:
smtp.login(args.username, args.password)
authUser = args.username
try:
smtp.sendmail(sender, recipient, test_msg % (sender, recipient, randstring, msg))
if not successExpected:
print(testFailed(successExpected, ref, notice))
testSuccess = False
else:
print('success')
if sender == args.local_mail:
mailsSent += 1
except smtplib.SMTPException as e:
if successExpected:
print(testFailed(successExpected, ref, notice))
testSuccess = False
else:
print('success')
smtp.quit()
# non auth tests
runTest(args.local_mail, args.local_mail, successExpected=False, auth=False,
msg='non-auth local -> local', ref='permit_sasl_authenticated')
runTest(args.local_mail, args.remote_mail, successExpected=False, auth=False,
msg='non-auth local -> remote', ref='permit_sasl_authenticated')
runTest(args.remote_mail, args.local_mail, successExpected=True, auth=False,
msg='non-auth remote -> local', notice='soft fail - DNS blocklists?')
runTest(args.remote_mail, args.remote_mail, successExpected=False, auth=False,
msg='non-auth remote -> remote', ref='reject_unauth_destination',
notice='SMTP SERVER ACTING AS OPEN RELAY')
print('')
# auth tests
runTest(args.local_mail, args.local_mail, successExpected=True, auth=True,
msg='authenticated local -> local', ref='permit_sasl_authenticated')
runTest(args.unauth_mail, args.local_mail, successExpected=False, auth=True,
msg='incorrectly authenticated local -> local', ref='reject_sender_login_mismatch')
runTest(args.local_mail, args.remote_mail, successExpected=True, auth=True,
msg='authenticated local -> remote', ref='permit_sasl_authenticated')
runTest(args.unauth_mail, args.remote_mail, successExpected=False, auth=True,
msg='incorrectly authenticated local -> remote', ref='reject_sender_login_mismatch')
runTest(args.remote_mail, args.remote_mail, successExpected=False, auth=True,
msg='authenticated remote -> remote',
ref='reject_sender_login_mismatch/reject_unauth_destination',
notice='SMTP SERVER ACTING AS AUTHENTICATED RELAY')
print('')
# santity tests
runTest(args.non_fqdn_mail, args.local_mail, successExpected=False, auth=True,
msg='non-fqdn sender sanity', ref='reject_non_fqdn_sender')
runTest(args.invalid_domain_mail, args.local_mail, successExpected=False, auth=True,
msg='unknown sender domain sanity', ref='reject_unknown_sender_domain')
runTest(args.local_mail, args.invalid_domain_mail, successExpected=False, auth=True,
msg='unknown recipient domain sanity', ref='reject_unknown_recipient_domain')
if testSuccess:
print('')
print('All tests successful! Ensure local mails were delivered: identifier %s' % (randstring))
print('There should be %s emails in the inbox of %s' % (mailsSent, args.local_mail))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment