aiosmtpd
aiosmtpd copied to clipboard
Unexpected server response in "DATA_RECEIVED" state
I am sending message requests to an SMTP server and analysing the response from the server. I have the following setup for aiosmtpd. server.py:
import asyncio
from aiosmtpd.controller import Controller
class CustomSMTPHandler:
async def handle_DATA(self, server, session, envelope):
print(f"Message from: {envelope.mail_from}")
print(f"Message to: {envelope.rcpt_tos}")
print(f"Message data:\n{envelope.content.decode('utf-8')}")
return '250 OK'
# Run the SMTP server
controller = Controller(CustomSMTPHandler(), hostname='127.0.0.1', port=8025)
controller.start()
print("SMTP server is running on port 8025...")
try:
asyncio.get_event_loop().run_forever()
except KeyboardInterrupt:
controller.stop()
print("SMTP server stopped.")
client.py:
import smtplib
import json
from tqdm import tqdm
def run_smtp_tests(server_address, port, tests_file, output_file):
# Load test cases from the JSON file
with open(tests_file, "r") as f:
test_cases = json.load(f)
results = []
count = 0
# Loop through each test case
for test in tqdm(test_cases):
count += 1
input_seq, state, test_input = test
print(f"Running test {count} for state: {state}, input: {test_input}")
print(f"test case: {test}\n")
total_input_seq = input_seq + [test_input]
try:
# Connect to the SMTP server
server = smtplib.SMTP(server_address, port)
# Execute input sequence to reach the desired state
for input in total_input_seq:
print(f"Input: {input}")
if input == "EHLO":
response = server.ehlo()
elif input == "HELO":
response = server.helo()
elif input == "MAIL FROM:":
response = server.mail("[email protected]")
elif input == "RCPT TO:":
response = server.rcpt("[email protected]")
elif input == "DATA":
response = server.docmd("DATA")
server.send("Hi\r\n.\r\n")
elif input == ".":
server.send("\r\n.\r\n")
response = server.getreply()
elif input == "QUIT":
response = server.quit()
else:
server.send(f"{input}\r\n")
response = server.getreply()
print(f"Response: {response}")
results.append((state, test_input, response))
except Exception as e:
results.append((state, test_input, str(e)))
finally:
# Quit the server connection
try:
server.quit()
except:
pass
# Save results to the output file
with open(output_file, "w") as f:
for result in results:
f.write(f"{result}\n")
# Example Usage
run_smtp_tests(
server_address="127.0.0.1",
port=8025,
tests_file="tests.json",
output_file="results.txt"
)
Save the following test case in tests.json in the same directory:
[
[
[
"HELO",
"MAIL FROM:",
"RCPT TO:",
"DATA"
],
"DATA_RECEIVED",
"."
]
]
I get the following responses from the aiosmtpd server:
Input: HELO
Response: (250, b'rajdeep')
Input: MAIL FROM:
Response: (250, b'OK')
Input: RCPT TO:
Response: (250, b'OK')
Input: DATA
Response: (354, b'End data with <CR><LF>.<CR><LF>')
Input: .
Response: (250, b'OK')
I also ran the test case on opensmtpd but got a different set of responses:
Input: HELO
Response: (250, b'rajdeep Hello [127.0.1.1] [127.0.0.1], pleased to meet you')
Input: MAIL FROM:
Response: (250, b'2.0.0 Ok')
Input: RCPT TO:
Response: (250, b'2.1.5 Destination address valid: Recipient ok')
Input: DATA
Response: (354, b'Enter mail, end with "." on a line by itself')
Input: .
Response: (550, b'5.7.1 Delivery not authorized, message refused: Message is not RFC 2822 compliant')
If you look at the final opensmtpd response, it returns an error code 550, but aiosmtpd accepts it. Why is there a discrepancy?