aiosmtplib icon indicating copy to clipboard operation
aiosmtplib copied to clipboard

Please support PROXY header

Open davidmcnabnz opened this issue 3 months ago • 2 comments

Is your feature request related to a problem? Please describe. aiosmtplib needs to support the haproxy PROXY protocol, to allow an aiosmtplib.SMTP client to spoof a connecting IP to any SMTP server which knows the connecting client, and is pre-configured to trust and permit this.

Reference: https://www.haproxy.com/documentation/haproxy-configuration-tutorials/proxying-essentials/client-ip-preservation/enable-proxy-protocol/

aiosmtpd supports PROXY. If enabled, an aiosmtpd-based server will wait after connection for the PROXY header before sending its 220 greeting.

Describe the solution you'd like Ideally, the SMTP constructor needs to accept one or more keywords to configure it to send PROXY header after connecting.

In my production workflow, I've subclassed SMTP, and:

  1. Overridden the constructor to accept/validate/store a proxy parameter
  2. Overridden _create_connection(), copied/pasted the 'official' method's code, and inserted a couple of lines to send a proxy header before the read_response() stage. Here's an excerpt:
        # original code - after the connection succeeds
        self.protocol = protocol
        self.transport = transport

        # my insertion
        # send PROXY header if we have one
        if self.proxyHdr is not None:
            transport.write(self.proxyHdr)
            pass

        # back to original code
        try:
            response = await protocol.read_response(timeout=timeout)
        ... check exceptions etc etc

However, I'm nervous about continuing to use a copy/paste/patch of _create_connection() in my codebase, and would be much happier to upgrade my field to a version of aiosmtplib that has PROXY support officially baked in.

Note that in my current implementation, my constructor accepts a literal bytes parameter, comprising the exact PROXY header to send. This was just done in haste. But ideally, a constructor would instead accept a set of parameters from which the SMTP client would generate the PROXY header itself.

Describe alternatives you've considered I could shim the connect. Not pleasant. I could use other SMTP clients. Not pleasant either. Apart from the lack of PROXY support, aiosmtplib is best-of-breed in the asyncio SMTP client space.

Additional context I'm monitoring health of mail flow nodes, and need PROXY support in the SMTP client to be able to complete the health assessments.

davidmcnabnz avatar Sep 18 '25 01:09 davidmcnabnz

Thanks, I wasn't familiar with the PROXY protocol, will take a look.

cole avatar Sep 20 '25 04:09 cole

@cole PROXY is absolutely essential in situations where a node needs to act as a mail proxy, receiving submissions from upstream clients, then forwarding these downstream and preserving the upstream client's IP. Just one example of this is load-balancing setups.

Misuse of this by bad actors tends to be rare, happening only in cases where the trusting downstream server isn't enforcing an IP whitelist of who it allows to use for proxying.

Two approaches to PROXY implementation for an SMTP client:

  1. Once instantiated with PROXY enabled (via constructor params), then always send the proxy packet first before reading for a 220 greeting, versus
  2. Wait a period of time (constructor param, default maybe 1-3 seconds) for a greeting, and if it doesn't arrive, then send the proxy packet.

Option 2 is a bit controversial because network conditions could delay a greeting from a non-proxy-compatible server, and cause the client to mistakenly send the proxy packet, then suffer an adverse 5xx response, and then hang up.

For context, aiosmtpd listeners are constructed either in PROXY-only or non-PROXY mode, thus suffering no timing semantics.

davidmcnabnz avatar Sep 20 '25 23:09 davidmcnabnz