aiosmtpd icon indicating copy to clipboard operation
aiosmtpd copied to clipboard

Unexpected server response in "DATA_RECEIVED" state

Open RajdeepMondal opened this issue 10 months ago • 0 comments

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?

RajdeepMondal avatar Jan 10 '25 17:01 RajdeepMondal