Skip to content

SMTP Protocol

This section provides an overview of the SMTPProtocol class and related functionality implemented in the smtp_protocol.py file. It describes how the SMTP protocol is handled, including command parsing, response formatting, and state management.

SMTPProtocol Class

The SMTPProtocol class manages the state and communication for the SMTP protocol. It is responsible for handling SMTP commands, managing session states, and generating appropriate responses.

smtp_protocol.SMTPProtocol

Bases: LineReceiver

Source code in src/smtp_protocol.py
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
class SMTPProtocol(LineReceiver):
    def __init__(self, factory, debug=False):
        self.factory = factory
        self.ip = None
        self.debug = debug
        self.ai_service = AIService(debug_mode=self.debug)
        self.responses = ResponseManager(self.ai_service, debug)
        self.state = 'INITIAL'
        self.data_buffer = []
        self.auth_step = None
        self.auth_username = None
        self.auth_password = None

    def connectionMade(self):
        self.ip = self.transport.getPeer().host
        if not self.factory.rate_limiter.allow_connection(self.ip):
            logger.info(f"Rate limit exceeded for IP: {self.ip}")
            self.transport.loseConnection()
            return

        banner = self.factory.banner.get_banner()
        self.sendLine(banner.encode('utf-8'))
        log_interaction(self.ip, 'WELCOME', banner)

    def lineReceived(self, line):
        try:
            command = line.decode('utf-8').strip()
            if self.state == 'DATA':
                if command == ".":
                    self.state = 'INITIAL'
                    data_message = "\n".join(self.data_buffer)
                    self.data_buffer = []
                    response = self.responses.get_response("250-DATA", "250 OK: Queued")
                else:
                    self.data_buffer.append(command)
                    return
            else:
                response = self._get_response(command)

            self.sendLine(response.encode('utf-8'))
            log_interaction(self.ip, command, response)

        except Exception as e:
            logger.error(f"Error processing command from {self.ip}: {e}")
            self.sendLine(b"500 Command unrecognized")

    def _get_response(self, command):
        command_upper = command.upper()
        if command_upper.startswith("EHLO"):
            return self._ehlo_response()
        elif command_upper.startswith("HELO"):
            return self.responses.get_response("250-HELO", "250 localhost")
        # Other SMTP commands here

    def _ehlo_response(self):
        response = [self.responses.get_response("250-EHLO", f"250-{self.factory.banner.domain_name} Hello [{self.ip}]")]
        capabilities = [
            "SIZE 37748736",
            "PIPELINING",
            "DSN",
            "ENHANCEDSTATUSCODES",
            "STARTTLS",
            "AUTH LOGIN PLAIN",
            "8BITMIME",
            "SMTPUTF8",
        ]
        response.extend([f"250-{cap}" for cap in capabilities[:-1]])
        response.append(f"250 {capabilities[-1]}")
        return "\n".join(response)

SMTPFactory Class

The SMTPFactory class is a factory for creating instances of the SMTPProtocol class. It initializes and builds new protocol instances for handling connections.

smtp_protocol.SMTPFactory

Bases: Factory

Source code in src/smtp_protocol.py
109
110
111
112
113
114
115
116
117
118
class SMTPFactory(protocol.Factory):
    def __init__(self, debug=False):
        self.config = ConfigManager()
        self.debug = debug or self.config.getboolean('server', 'debug')
        self.banner = SMTPBanner(self.config.get('server', 'domain', fallback='localhost'),
                                 self.config.get('server', 'technology', fallback='generic'))
        self.rate_limiter = RateLimiter(self.config.getint('server', 'rate_limit', fallback=5))

    def buildProtocol(self, addr):
        return SMTPProtocol(self, debug=self.debug)