You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by bt...@apache.org on 2022/08/20 06:02:17 UTC

[james-project] branch master updated: JAMES-3788 IMAP/SMTP support for incoming HAProxy connections (#1132)

This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git


The following commit(s) were added to refs/heads/master by this push:
     new a1d3322086 JAMES-3788 IMAP/SMTP support for incoming HAProxy connections (#1132)
a1d3322086 is described below

commit a1d3322086a57bb39f6a770ea929aa7d2e0f0387
Author: ouvtam <11...@users.noreply.github.com>
AuthorDate: Sat Aug 20 08:02:13 2022 +0200

    JAMES-3788 IMAP/SMTP support for incoming HAProxy connections (#1132)
---
 examples/proxy-smtp/README.md                      |  46 +++++++
 examples/proxy-smtp/docker-compose.yml             |  22 +++
 examples/proxy-smtp/haproxy.cfg                    |  17 +++
 examples/proxy-smtp/smtpserver.xml                 |  53 ++++++++
 .../james/protocols/api/ProtocolSession.java       |  16 ++-
 .../james/protocols/api/ProtocolSessionImpl.java   |  17 ++-
 .../james/protocols/api/ProtocolTransport.java     |   7 +-
 .../james/protocols/api/ProxyInformation.java      |  68 ++++++++++
 .../api/AbstractProtocolTransportTest.java         |   3 +
 .../james/protocols/api/ProxyInformationTest.java} |  76 +++++------
 protocols/netty/pom.xml                            |   7 +-
 .../netty/AbstractChannelPipelineFactory.java      |  17 ++-
 .../AbstractSSLAwareChannelPipelineFactory.java    |   7 +-
 .../netty/BasicChannelInboundHandler.java          |   9 +-
 .../protocols/netty/HAProxyMessageHandler.java     |  93 +++++++++++++
 .../james/protocols/netty/HandlerConstants.java    |   2 +
 .../protocols/netty/NettyProtocolTransport.java    |   8 +-
 .../apache/james/protocols/netty/NettyServer.java  |  20 ++-
 .../smtp/netty/NettyProxySMTPServerTest.java       | 149 +++++++++++++++++++++
 .../protocols/smtp/utils/BaseFakeSMTPSession.java  |  11 ++
 .../docs/modules/ROOT/pages/configure/imap.adoc    |   6 +
 .../docs/modules/ROOT/pages/configure/smtp.adoc    |   6 +
 .../helm-chart/james/configs/smtpserver.xml        |   3 +
 .../imapserver/netty/HAProxyMessageHandler.java    |  76 +++++++++++
 .../james/imapserver/netty/IMAPMDCContext.java     |  16 ++-
 .../apache/james/imapserver/netty/IMAPServer.java  |  16 +++
 .../james/imapserver/netty/IMAPServerTest.java     |  33 +++++
 .../src/test/resources/imapServerProxy.xml         |  15 +++
 .../lib/netty/AbstractConfigurableAsyncServer.java |  16 ++-
 .../lib/AbstractConfigurableAsyncServerTest.java   |   5 +
 .../apache/james/lmtpserver/netty/LMTPServer.java  |  13 ++
 .../managesieveserver/netty/ManageSieveServer.java |  11 ++
 .../apache/james/pop3server/netty/POP3Server.java  |   8 +-
 .../netty/SMTPChannelInboundHandler.java           |   4 +-
 .../apache/james/smtpserver/netty/SMTPServer.java  |   9 +-
 src/site/xdoc/server/config-imap4.xml              |   7 +
 src/site/xdoc/server/config-smtp-lmtp.xml          |   7 +
 37 files changed, 832 insertions(+), 67 deletions(-)

diff --git a/examples/proxy-smtp/README.md b/examples/proxy-smtp/README.md
new file mode 100644
index 0000000000..c4e50cfb64
--- /dev/null
+++ b/examples/proxy-smtp/README.md
@@ -0,0 +1,46 @@
+# Use one or more James instances behind a proxy
+
+It is common to run a proxy in front of a mail server such as James. The proxy receives incoming
+connections and forwards the request (e.g. SMTP) to James. The proxy passes the remote address to
+James, so James knows the origin ip address of the remote peer.
+
+So far SMTP has been tested successfully, but is not limited to. Since the proxy protocol
+implementation is implemented on Netty level, it can also be used for IMAP.
+
+## Proxy
+
+This example uses [HAProxy](https://www.haproxy.org/). Other proxy software that implement
+[HAProxy's proxy protocol](https://www.haproxy.org/download/2.7/doc/proxy-protocol.txt) can also be
+used (e.g. [traefik](https://traefik.io)).
+
+It is possible to run more than one James instance and let the proxy decide which connection it
+forwards to which instance (i.e. load balancing).
+
+## SMTP
+
+This docker example uses HAProxy exposes port 25 (SMTP)
+
+James might take a while to start. Thus start James first, then haproxy and finally helo.
+```shell
+docker-compose up -d james
+# wait until james is ready
+docker-compose up -d haproxy
+
+# sends a "HELO" SMTP command to haproxy that will be forwarded to james. James will answer
+# including the remote ip address of the helo container.
+docker-compose up helo
+```
+
+If you expose HAProxy's port 25 you can also send a HELO using telnet.
+
+## Limitations
+
+* Since HAProxy's protocol does not require to know the application layer protocol,
+it is not possible to load balance based on SMTP AUTH user or virtual domain. See 
+HAProxy's documenation about different load balancing strategies
+(e.g. round-robin, leastconn, source etc.)
+
+* When running James behind a proxy it is currently not possible to talk directly to
+the James instance (e.g. telnet). The connection will be closed and an exception is
+raise in James complaining no proxy is used. Be sure to talk to the proxy with telnet
+and everything is fine. This behaviour might change in future versions.
\ No newline at end of file
diff --git a/examples/proxy-smtp/docker-compose.yml b/examples/proxy-smtp/docker-compose.yml
new file mode 100644
index 0000000000..74623f80b4
--- /dev/null
+++ b/examples/proxy-smtp/docker-compose.yml
@@ -0,0 +1,22 @@
+version: '3.0'
+
+services:
+  haproxy:
+    image: haproxytech/haproxy-alpine:2.4
+    volumes:
+      - ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
+    depends_on:
+      - james
+
+  james:
+    image: apache/james:jpa-smtp-latest
+    container_name: james
+    hostname: james.local
+    volumes:
+      - ./smtpserver.xml:/root/conf/smtpserver.xml:ro
+
+  helo:
+    image: alpine:latest
+    command: "ash -c 'apk add --update --no-cache netcat-openbsd && echo \"HELO example.local\" | nc haproxy 25'"
+    depends_on:
+      - haproxy
\ No newline at end of file
diff --git a/examples/proxy-smtp/haproxy.cfg b/examples/proxy-smtp/haproxy.cfg
new file mode 100644
index 0000000000..c3983fb1cb
--- /dev/null
+++ b/examples/proxy-smtp/haproxy.cfg
@@ -0,0 +1,17 @@
+global
+  log stdout format raw local0 info
+
+defaults
+  mode tcp
+  timeout client 10s
+  timeout connect 5s
+  timeout server 10s
+  log global
+  option tcplog
+
+frontend smtp-frontend
+  bind :25
+  default_backend james-servers
+
+backend james-servers
+  server james1 james:2525 send-proxy
diff --git a/examples/proxy-smtp/smtpserver.xml b/examples/proxy-smtp/smtpserver.xml
new file mode 100644
index 0000000000..f4e21546d6
--- /dev/null
+++ b/examples/proxy-smtp/smtpserver.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0"?>
+
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+ -->
+
+<!-- Read https://james.apache.org/server/config-smtp-lmtp.html#SMTP_Configuration for further details -->
+
+<smtpservers>
+    <smtpserver enabled="true">
+        <jmxName>smtpserver-global</jmxName>
+        <bind>0.0.0.0:2525</bind>
+        <connectionBacklog>200</connectionBacklog>
+        <tls socketTLS="false" startTLS="false">
+            <privateKey>file://conf/private.key</privateKey>
+            <certificates>file://conf/private.csr</certificates>
+        </tls>
+        <connectiontimeout>360</connectiontimeout>
+        <connectionLimit>0</connectionLimit>
+        <connectionLimitPerIP>0</connectionLimitPerIP>
+        <auth>
+            <announce>never</announce>
+            <requireSSL>false</requireSSL>
+        </auth>
+        <authorizedAddresses>127.0.0.0/8</authorizedAddresses>
+        <verifyIdentity>false</verifyIdentity>
+        <proxyRequired>true</proxyRequired>
+        <maxmessagesize>0</maxmessagesize>
+        <addressBracketsEnforcement>true</addressBracketsEnforcement>
+        <smtpGreeting>Apache JAMES awesome SMTP Server</smtpGreeting>
+        <handlerchain>
+            <handler class="org.apache.james.smtpserver.fastfail.ValidRcptHandler"/>
+            <handler class="org.apache.james.smtpserver.CoreCmdHandlerLoader"/>
+        </handlerchain>
+    </smtpserver>
+</smtpservers>
+
+
diff --git a/protocols/api/src/main/java/org/apache/james/protocols/api/ProtocolSession.java b/protocols/api/src/main/java/org/apache/james/protocols/api/ProtocolSession.java
index 9410124fde..280e2784a8 100644
--- a/protocols/api/src/main/java/org/apache/james/protocols/api/ProtocolSession.java
+++ b/protocols/api/src/main/java/org/apache/james/protocols/api/ProtocolSession.java
@@ -148,10 +148,24 @@ public interface ProtocolSession extends CommandDetectionSession {
 
     
     /**
-     * Return the {@link InetSocketAddress} of the remote peer
+     * Return the {@link InetSocketAddress} of the remote peer. If proxy support
+     * is enabled, then it returns the remote peer given by the proxy.
      */
     InetSocketAddress getRemoteAddress();
 
+    /**
+     * Sets the proxy information if proxying is enabled.
+     *
+     * @param proxyInformation proxy information including source and destination addresses
+     */
+    void setProxyInformation(ProxyInformation proxyInformation);
+
+    /**
+     * Gets the proxy information if proxying is enabled.
+     * @return
+     */
+    Optional<ProxyInformation> getProxyInformation();
+
     /**
      * Return the {@link InetSocketAddress} of the local bound address
      */
diff --git a/protocols/api/src/main/java/org/apache/james/protocols/api/ProtocolSessionImpl.java b/protocols/api/src/main/java/org/apache/james/protocols/api/ProtocolSessionImpl.java
index 0750f82382..dd641a9cb3 100644
--- a/protocols/api/src/main/java/org/apache/james/protocols/api/ProtocolSessionImpl.java
+++ b/protocols/api/src/main/java/org/apache/james/protocols/api/ProtocolSessionImpl.java
@@ -42,6 +42,7 @@ public class ProtocolSessionImpl implements ProtocolSession {
     private final Map<AttachmentKey<?>, Object> connectionState;
     private final Map<AttachmentKey<?>, Object> sessionState;
     private Username username;
+    private ProxyInformation proxyInformation = null;
     protected final ProtocolConfiguration config;
     private boolean needsCommandInjectionDetection;
     private static final String DELIMITER = "\r\n";
@@ -76,9 +77,22 @@ public class ProtocolSessionImpl implements ProtocolSession {
 
     @Override
     public InetSocketAddress getRemoteAddress() {
+        if (proxyInformation != null) {
+            return proxyInformation.getSource();
+        }
         return transport.getRemoteAddress();
     }
 
+    @Override
+    public Optional<ProxyInformation> getProxyInformation() {
+        return Optional.of(proxyInformation);
+    }
+
+    @Override
+    public void setProxyInformation(ProxyInformation proxyInformation) {
+        this.proxyInformation = proxyInformation;
+    }
+
     @Override
     public Username getUsername() {
         return username;
@@ -110,8 +124,7 @@ public class ProtocolSessionImpl implements ProtocolSession {
     public String getSessionID() {
         return transport.getId();
     }
-    
-    
+
     @Override
     public Map<AttachmentKey<?>, Object> getConnectionState() {
         return connectionState;
diff --git a/protocols/api/src/main/java/org/apache/james/protocols/api/ProtocolTransport.java b/protocols/api/src/main/java/org/apache/james/protocols/api/ProtocolTransport.java
index 09f5377dd0..f0f743abf9 100644
--- a/protocols/api/src/main/java/org/apache/james/protocols/api/ProtocolTransport.java
+++ b/protocols/api/src/main/java/org/apache/james/protocols/api/ProtocolTransport.java
@@ -57,7 +57,12 @@ public interface ProtocolTransport {
      * Return <code>true</code> if <code>STARTTLS</code> is supported by this {@link ProtocolTransport}
      */
     boolean isStartTLSSupported();
-    
+
+    /**
+     * Return <code>true</code> if <code>PROXY</code> is required by this {@link ProtocolTransport}
+     */
+    boolean isProxyRequired();
+
     /**
      * Write the {@link Response} to the {@link ProtocolTransport} which will forward it to the connected
      * peer
diff --git a/protocols/api/src/main/java/org/apache/james/protocols/api/ProxyInformation.java b/protocols/api/src/main/java/org/apache/james/protocols/api/ProxyInformation.java
new file mode 100644
index 0000000000..d3a75983bf
--- /dev/null
+++ b/protocols/api/src/main/java/org/apache/james/protocols/api/ProxyInformation.java
@@ -0,0 +1,68 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.protocols.api;
+
+import java.net.InetSocketAddress;
+import java.util.Objects;
+
+import com.google.common.base.Preconditions;
+
+public class ProxyInformation {
+    private final InetSocketAddress source;
+    private final InetSocketAddress destination;
+
+    public ProxyInformation(InetSocketAddress source, InetSocketAddress destination) {
+        Preconditions.checkNotNull(source);
+        Preconditions.checkNotNull(destination);
+
+        this.source = source;
+        this.destination = destination;
+    }
+
+    /**
+     * Return the {@link InetSocketAddress} of the proxy peer or null if proxy support is disabled.
+     */
+    public InetSocketAddress getDestination() {
+        return destination;
+    }
+
+    /**
+     * Return the {@link InetSocketAddress} of the remote peer if proxy is enabled or null if proxy
+     * support is disabled.
+     */
+    public InetSocketAddress getSource() {
+        return source;
+    }
+
+    @Override
+    public final int hashCode() {
+        return Objects.hash(source, destination);
+    }
+
+    @Override
+    public final boolean equals(Object obj) {
+        if (obj instanceof ProxyInformation) {
+            ProxyInformation other = (ProxyInformation) obj;
+            return Objects.equals(this.source, other.source)
+                && Objects.equals(this.destination, other.destination);
+        }
+        return false;
+     }
+}
diff --git a/protocols/api/src/test/java/org/apache/james/protocols/api/AbstractProtocolTransportTest.java b/protocols/api/src/test/java/org/apache/james/protocols/api/AbstractProtocolTransportTest.java
index f6c4933aa4..910dccbfc6 100644
--- a/protocols/api/src/test/java/org/apache/james/protocols/api/AbstractProtocolTransportTest.java
+++ b/protocols/api/src/test/java/org/apache/james/protocols/api/AbstractProtocolTransportTest.java
@@ -85,6 +85,9 @@ public class AbstractProtocolTransportTest {
             public boolean isReadable() {
                 throw new UnsupportedOperationException();
             }
+
+            @Override
+            public boolean isProxyRequired() { throw new UnsupportedOperationException(); }
             
             @Override
             public InetSocketAddress getRemoteAddress() {
diff --git a/protocols/netty/src/main/java/org/apache/james/protocols/netty/HandlerConstants.java b/protocols/api/src/test/java/org/apache/james/protocols/api/ProxyInformationTest.java
similarity index 64%
copy from protocols/netty/src/main/java/org/apache/james/protocols/netty/HandlerConstants.java
copy to protocols/api/src/test/java/org/apache/james/protocols/api/ProxyInformationTest.java
index 5f699578e5..8331e29fb8 100644
--- a/protocols/netty/src/main/java/org/apache/james/protocols/netty/HandlerConstants.java
+++ b/protocols/api/src/test/java/org/apache/james/protocols/api/ProxyInformationTest.java
@@ -1,44 +1,32 @@
-/****************************************************************
- * Licensed to the Apache Software Foundation (ASF) under one   *
- * or more contributor license agreements.  See the NOTICE file *
- * distributed with this work for additional information        *
- * regarding copyright ownership.  The ASF licenses this file   *
- * to you under the Apache License, Version 2.0 (the            *
- * "License"); you may not use this file except in compliance   *
- * with the License.  You may obtain a copy of the License at   *
- *                                                              *
- *   http://www.apache.org/licenses/LICENSE-2.0                 *
- *                                                              *
- * Unless required by applicable law or agreed to in writing,   *
- * software distributed under the License is distributed on an  *
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
- * KIND, either express or implied.  See the License for the    *
- * specific language governing permissions and limitations      *
- * under the License.                                           *
- ****************************************************************/
-package org.apache.james.protocols.netty;
-
-import io.netty.channel.ChannelHandler;
-import io.netty.channel.ChannelPipeline;
-
-/**
- * Provide the keys under which the {@link ChannelHandler}'s are stored in the
- * {@link ChannelPipeline}
- */
-public interface HandlerConstants {
-
-    String SSL_HANDLER = "sslHandler";
-
-    String FRAMER = "framer";
-
-    String TIMEOUT_HANDLER = "timeoutHandler";
-
-    String CORE_HANDLER = "coreHandler";
-
-    String CHUNK_HANDLER = "chunkHandler";
-
-    String CONNECTION_LIMIT_PER_IP_HANDLER = "connectionPerIpLimitHandler";
-
-    String CONNECTION_LIMIT_HANDLER = "connectionLimitHandler";
-
-}
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.protocols.api;
+
+import org.junit.jupiter.api.Test;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+
+class ProxyInformationTest {
+    @Test
+    void shouldMatchBeanContract() {
+        EqualsVerifier.forClass(ProxyInformation.class)
+            .verify();
+    }
+}
\ No newline at end of file
diff --git a/protocols/netty/pom.xml b/protocols/netty/pom.xml
index 84de57fc42..827ff6f631 100644
--- a/protocols/netty/pom.xml
+++ b/protocols/netty/pom.xml
@@ -50,10 +50,15 @@
             <groupId>com.google.guava</groupId>
             <artifactId>guava</artifactId>
         </dependency>
+        <dependency>
+            <groupId>io.netty</groupId>
+            <artifactId>netty-codec-haproxy</artifactId>
+            <version>${netty.version}</version>
+        </dependency>
         <dependency>
             <groupId>io.netty</groupId>
             <artifactId>netty-handler</artifactId>
-            <version>4.1.72.Final</version>
+            <version>${netty.version}</version>
         </dependency>
         <dependency>
             <groupId>org.mockito</groupId>
diff --git a/protocols/netty/src/main/java/org/apache/james/protocols/netty/AbstractChannelPipelineFactory.java b/protocols/netty/src/main/java/org/apache/james/protocols/netty/AbstractChannelPipelineFactory.java
index e0d054fe5e..98a20e6f19 100644
--- a/protocols/netty/src/main/java/org/apache/james/protocols/netty/AbstractChannelPipelineFactory.java
+++ b/protocols/netty/src/main/java/org/apache/james/protocols/netty/AbstractChannelPipelineFactory.java
@@ -25,6 +25,7 @@ import io.netty.channel.ChannelInboundHandlerAdapter;
 import io.netty.channel.ChannelInitializer;
 import io.netty.channel.ChannelPipeline;
 import io.netty.channel.socket.SocketChannel;
+import io.netty.handler.codec.haproxy.HAProxyMessageDecoder;
 import io.netty.handler.stream.ChunkedWriteHandler;
 import io.netty.util.concurrent.EventExecutorGroup;
 
@@ -36,18 +37,20 @@ public abstract class AbstractChannelPipelineFactory<C extends SocketChannel> ex
     public static final int MAX_LINE_LENGTH = 8192;
 
     private final int timeout;
+    private final boolean proxyRequired;
     private final Optional<ConnectionLimitUpstreamHandler> connectionLimitUpstreamHandler;
     private final Optional<ConnectionPerIpLimitUpstreamHandler> connectionPerIpLimitUpstreamHandler;
     private final ChannelHandlerFactory frameHandlerFactory;
     private final EventExecutorGroup eventExecutorGroup;
 
     public AbstractChannelPipelineFactory(ChannelHandlerFactory frameHandlerFactory, EventExecutorGroup eventExecutorGroup) {
-        this(0, 0, 0, frameHandlerFactory, eventExecutorGroup);
+        this(0, 0, 0, false, frameHandlerFactory, eventExecutorGroup);
     }
 
-    public AbstractChannelPipelineFactory(int timeout, int maxConnections, int maxConnectsPerIp,
+    public AbstractChannelPipelineFactory(int timeout, int maxConnections, int maxConnectsPerIp, boolean proxyRequired,
                                           ChannelHandlerFactory frameHandlerFactory, EventExecutorGroup eventExecutorGroup) {
         this.timeout = timeout;
+        this.proxyRequired = proxyRequired;
         this.frameHandlerFactory = frameHandlerFactory;
         this.eventExecutorGroup = eventExecutorGroup;
         this.connectionLimitUpstreamHandler = ConnectionLimitUpstreamHandler.forCount(maxConnections);
@@ -63,6 +66,11 @@ public abstract class AbstractChannelPipelineFactory<C extends SocketChannel> ex
         connectionLimitUpstreamHandler.ifPresent(handler -> pipeline.addLast(HandlerConstants.CONNECTION_LIMIT_HANDLER, handler));
         connectionPerIpLimitUpstreamHandler.ifPresent(handler -> pipeline.addLast(HandlerConstants.CONNECTION_LIMIT_PER_IP_HANDLER, handler));
 
+        if (proxyRequired) {
+            pipeline.addLast(HandlerConstants.PROXY_HANDLER, new HAProxyMessageDecoder());
+            pipeline.addLast("proxyInformationHandler", createProxyHandler());
+        }
+
         // Add the text line decoder which limit the max line length, don't strip the delimiter and use CRLF as delimiter
         pipeline.addLast(eventExecutorGroup, HandlerConstants.FRAMER, frameHandlerFactory.create(pipeline));
        
@@ -81,4 +89,9 @@ public abstract class AbstractChannelPipelineFactory<C extends SocketChannel> ex
      */
     protected abstract ChannelInboundHandlerAdapter createHandler();
 
+    /**
+     * @return A handler that set HAProxy information on the underlying protocol objects.
+     */
+    protected abstract ChannelInboundHandlerAdapter createProxyHandler();
+
 }
diff --git a/protocols/netty/src/main/java/org/apache/james/protocols/netty/AbstractSSLAwareChannelPipelineFactory.java b/protocols/netty/src/main/java/org/apache/james/protocols/netty/AbstractSSLAwareChannelPipelineFactory.java
index 30a4ac914f..058a46b46b 100644
--- a/protocols/netty/src/main/java/org/apache/james/protocols/netty/AbstractSSLAwareChannelPipelineFactory.java
+++ b/protocols/netty/src/main/java/org/apache/james/protocols/netty/AbstractSSLAwareChannelPipelineFactory.java
@@ -34,15 +34,16 @@ public abstract class AbstractSSLAwareChannelPipelineFactory<C extends SocketCha
 
     public AbstractSSLAwareChannelPipelineFactory(int timeout,
                                                   int maxConnections, int maxConnectsPerIp,
+                                                  boolean proxyRequired,
                                                   ChannelHandlerFactory frameHandlerFactory,
                                                   EventExecutorGroup eventExecutorGroup) {
-        super(timeout, maxConnections, maxConnectsPerIp, frameHandlerFactory, eventExecutorGroup);
+        super(timeout, maxConnections, maxConnectsPerIp, proxyRequired, frameHandlerFactory, eventExecutorGroup);
     }
 
     public AbstractSSLAwareChannelPipelineFactory(int timeout,
-            int maxConnections, int maxConnectsPerIp, Encryption secure,
+            int maxConnections, int maxConnectsPerIp, boolean proxyRequired, Encryption secure,
             ChannelHandlerFactory frameHandlerFactory, EventExecutorGroup eventExecutorGroup) {
-        this(timeout, maxConnections, maxConnectsPerIp, frameHandlerFactory, eventExecutorGroup);
+        this(timeout, maxConnections, maxConnectsPerIp, proxyRequired, frameHandlerFactory, eventExecutorGroup);
 
         this.secure = secure;
     }
diff --git a/protocols/netty/src/main/java/org/apache/james/protocols/netty/BasicChannelInboundHandler.java b/protocols/netty/src/main/java/org/apache/james/protocols/netty/BasicChannelInboundHandler.java
index fd52184fdc..7be96cf6de 100644
--- a/protocols/netty/src/main/java/org/apache/james/protocols/netty/BasicChannelInboundHandler.java
+++ b/protocols/netty/src/main/java/org/apache/james/protocols/netty/BasicChannelInboundHandler.java
@@ -62,20 +62,22 @@ public class BasicChannelInboundHandler extends ChannelInboundHandlerAdapter imp
     protected final Protocol protocol;
     protected final ProtocolHandlerChain chain;
     protected final Encryption secure;
+    protected final boolean proxyRequired;
     private final ProtocolMDCContextFactory mdcContextFactory;
     private final Deque<ChannelInboundHandlerAdapter> behaviourOverrides = new ConcurrentLinkedDeque<>();
     private final Optional<LineHandler> lineHandler;
     protected final LinkedList<ProtocolHandlerResultHandler> resultHandlers;
 
     public BasicChannelInboundHandler(ProtocolMDCContextFactory mdcContextFactory, Protocol protocol) {
-        this(mdcContextFactory, protocol, null);
+        this(mdcContextFactory, protocol, null, false);
     }
 
-    public BasicChannelInboundHandler(ProtocolMDCContextFactory mdcContextFactory, Protocol protocol, Encryption secure) {
+    public BasicChannelInboundHandler(ProtocolMDCContextFactory mdcContextFactory, Protocol protocol, Encryption secure, boolean proxyRequired) {
         this.mdcContextFactory = mdcContextFactory;
         this.protocol = protocol;
         this.chain = protocol.getProtocolChain();
         this.secure = secure;
+        this.proxyRequired = proxyRequired;
         this.lineHandler = chain.getFirstHandler(LineHandler.class);
         this.resultHandlers = chain.getHandlers(ProtocolHandlerResultHandler.class);
     }
@@ -197,11 +199,12 @@ public class BasicChannelInboundHandler extends ChannelInboundHandlerAdapter imp
     
     
     protected ProtocolSession createSession(ChannelHandlerContext ctx) {
-        return protocol.newSession(new NettyProtocolTransport(ctx.channel(), secure));
+        return protocol.newSession(new NettyProtocolTransport(ctx.channel(), secure, proxyRequired));
     }
 
     @Override
     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+        cause.printStackTrace();
         try (Closeable closeable = mdc(ctx).build()) {
             Channel channel = ctx.channel();
             ProtocolSession session = (ProtocolSession) ctx.channel().attr(SESSION_ATTRIBUTE_KEY).get();
diff --git a/protocols/netty/src/main/java/org/apache/james/protocols/netty/HAProxyMessageHandler.java b/protocols/netty/src/main/java/org/apache/james/protocols/netty/HAProxyMessageHandler.java
new file mode 100644
index 0000000000..cb294c73c3
--- /dev/null
+++ b/protocols/netty/src/main/java/org/apache/james/protocols/netty/HAProxyMessageHandler.java
@@ -0,0 +1,93 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.protocols.netty;
+
+import static org.apache.james.protocols.api.ProtocolSession.State.Connection;
+import static org.apache.james.protocols.netty.BasicChannelInboundHandler.MDC_ATTRIBUTE_KEY;
+
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+
+import org.apache.james.protocols.api.CommandDetectionSession;
+import org.apache.james.protocols.api.Protocol;
+import org.apache.james.protocols.api.ProtocolSession;
+import org.apache.james.protocols.api.ProxyInformation;
+import org.apache.james.util.MDCBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import io.netty.handler.codec.haproxy.HAProxyMessage;
+import io.netty.handler.codec.haproxy.HAProxyProxiedProtocol;
+import io.netty.util.AttributeKey;
+
+public class HAProxyMessageHandler extends ChannelInboundHandlerAdapter {
+    private static final Logger LOGGER = LoggerFactory.getLogger(HAProxyMessageHandler.class);
+    private static final AttributeKey<CommandDetectionSession> SESSION_ATTRIBUTE_KEY = AttributeKey.valueOf("session");
+
+    private static String retrieveIp(ChannelHandlerContext ctx) {
+        SocketAddress remoteAddress = ctx.channel().remoteAddress();
+        if (remoteAddress instanceof InetSocketAddress) {
+            InetSocketAddress address = (InetSocketAddress) remoteAddress;
+            return address.getAddress().getHostAddress();
+        }
+        return remoteAddress.toString();
+    }
+
+    protected final Protocol protocol;
+    private final ProtocolMDCContextFactory mdcContextFactory;
+
+    public HAProxyMessageHandler(Protocol protocol, ProtocolMDCContextFactory mdcContextFactory) {
+        this.protocol = protocol;
+        this.mdcContextFactory = mdcContextFactory;
+    }
+
+    @Override
+    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+        if (msg instanceof HAProxyMessage) {
+            HAProxyMessage haproxyMsg = (HAProxyMessage) msg;
+
+            ProtocolSession pSession = (ProtocolSession) ctx.channel().attr(SESSION_ATTRIBUTE_KEY).get();
+            if (haproxyMsg.proxiedProtocol().equals(HAProxyProxiedProtocol.TCP4) || haproxyMsg.proxiedProtocol().equals(HAProxyProxiedProtocol.TCP6)) {
+
+                ProxyInformation proxyInformation = new ProxyInformation(
+                    new InetSocketAddress(haproxyMsg.sourceAddress(), haproxyMsg.sourcePort()),
+                    new InetSocketAddress(haproxyMsg.destinationAddress(), haproxyMsg.destinationPort()));
+                pSession.setProxyInformation(proxyInformation);
+
+                LOGGER.info("Connection from {} runs through {} proxy", haproxyMsg.sourceAddress(), haproxyMsg.destinationAddress());
+                // Refresh MDC info to account for proxying
+                MDCBuilder boundMDC = mdcContextFactory.onBound(protocol, ctx);
+                boundMDC.addToContext("proxy.source", proxyInformation.getSource().toString());
+                boundMDC.addToContext("proxy.destination", proxyInformation.getDestination().toString());
+                boundMDC.addToContext("proxy.ip", retrieveIp(ctx));
+                pSession.setAttachment(MDC_ATTRIBUTE_KEY, boundMDC, Connection);
+            } else {
+                throw new IllegalArgumentException("Only TCP4/TCP6 are supported when using PROXY protocol.");
+            }
+
+            haproxyMsg.release();
+            super.channelReadComplete(ctx);
+        } else {
+            super.channelRead(ctx, msg);
+        }
+    }
+}
diff --git a/protocols/netty/src/main/java/org/apache/james/protocols/netty/HandlerConstants.java b/protocols/netty/src/main/java/org/apache/james/protocols/netty/HandlerConstants.java
index 5f699578e5..7c6da2efeb 100644
--- a/protocols/netty/src/main/java/org/apache/james/protocols/netty/HandlerConstants.java
+++ b/protocols/netty/src/main/java/org/apache/james/protocols/netty/HandlerConstants.java
@@ -41,4 +41,6 @@ public interface HandlerConstants {
 
     String CONNECTION_LIMIT_HANDLER = "connectionLimitHandler";
 
+    String PROXY_HANDLER = "proxyHandler";
+
 }
diff --git a/protocols/netty/src/main/java/org/apache/james/protocols/netty/NettyProtocolTransport.java b/protocols/netty/src/main/java/org/apache/james/protocols/netty/NettyProtocolTransport.java
index 994708f5c1..0257c07a65 100644
--- a/protocols/netty/src/main/java/org/apache/james/protocols/netty/NettyProtocolTransport.java
+++ b/protocols/netty/src/main/java/org/apache/james/protocols/netty/NettyProtocolTransport.java
@@ -44,10 +44,12 @@ public class NettyProtocolTransport extends AbstractProtocolTransport {
     
     private final Channel channel;
     private final Encryption encryption;
+    private final boolean proxyRequired;
     
-    public NettyProtocolTransport(Channel channel, Encryption encryption) {
+    public NettyProtocolTransport(Channel channel, Encryption encryption, boolean proxyRequired) {
         this.channel = channel;
         this.encryption = encryption;
+        this.proxyRequired = proxyRequired;
     }
 
     @Override
@@ -70,6 +72,10 @@ public class NettyProtocolTransport extends AbstractProtocolTransport {
         return encryption != null && encryption.isStartTLS();
     }
 
+    @Override
+    public boolean isProxyRequired() {
+        return proxyRequired;
+    }
 
     @Override
     public void popLineHandler() {
diff --git a/protocols/netty/src/main/java/org/apache/james/protocols/netty/NettyServer.java b/protocols/netty/src/main/java/org/apache/james/protocols/netty/NettyServer.java
index a830adcc7d..6bafa394ec 100644
--- a/protocols/netty/src/main/java/org/apache/james/protocols/netty/NettyServer.java
+++ b/protocols/netty/src/main/java/org/apache/james/protocols/netty/NettyServer.java
@@ -36,6 +36,7 @@ import io.netty.channel.DefaultEventLoopGroup;
 public class NettyServer extends AbstractAsyncServer {
     public static class Factory {
         private Protocol protocol;
+        private boolean proxyRequired;
         private Optional<Encryption> secure;
         private Optional<ChannelHandlerFactory> frameHandlerFactory;
 
@@ -56,6 +57,11 @@ public class NettyServer extends AbstractAsyncServer {
             return this;
         }
 
+        public Factory proxyRequired(boolean proxyRequired) {
+            this.proxyRequired = proxyRequired;
+            return this;
+        }
+
         public Factory frameHandlerFactory(ChannelHandlerFactory frameHandlerFactory) {
             this.frameHandlerFactory = Optional.ofNullable(frameHandlerFactory);
             return this;
@@ -65,6 +71,7 @@ public class NettyServer extends AbstractAsyncServer {
             Preconditions.checkState(protocol != null, "'protocol' is mandatory");
             return new NettyServer(protocol, 
                     secure.orElse(null),
+                    proxyRequired,
                     frameHandlerFactory.orElse(new LineDelimiterBasedChannelHandlerFactory(AbstractChannelPipelineFactory.MAX_LINE_LENGTH)));
         }
     }
@@ -74,10 +81,12 @@ public class NettyServer extends AbstractAsyncServer {
     private final ChannelHandlerFactory frameHandlerFactory;
     private int maxCurConnections;
     private int maxCurConnectionsPerIP;
+    private boolean proxyRequired;
    
-    private NettyServer(Protocol protocol, Encryption secure, ChannelHandlerFactory frameHandlerFactory) {
+    private NettyServer(Protocol protocol, Encryption secure, boolean proxyRequired, ChannelHandlerFactory frameHandlerFactory) {
         this.protocol = protocol;
         this.secure = secure;
+        this.proxyRequired = proxyRequired;
         this.frameHandlerFactory = frameHandlerFactory;
     }
     
@@ -96,7 +105,8 @@ public class NettyServer extends AbstractAsyncServer {
     }
 
     protected ChannelInboundHandlerAdapter createCoreHandler() {
-        return new BasicChannelInboundHandler(new ProtocolMDCContextFactory.Standard(), protocol, secure);
+        return new BasicChannelInboundHandler(new ProtocolMDCContextFactory.Standard(), protocol, secure,
+            proxyRequired);
     }
     
     @Override
@@ -115,6 +125,7 @@ public class NettyServer extends AbstractAsyncServer {
             getTimeout(),
             maxCurConnections,
             maxCurConnectionsPerIP,
+            proxyRequired,
             secure,
             getFrameHandlerFactory(),
             new DefaultEventLoopGroup(16)) {
@@ -123,6 +134,11 @@ public class NettyServer extends AbstractAsyncServer {
             protected ChannelInboundHandlerAdapter createHandler() {
                 return createCoreHandler();
             }
+
+            @Override
+            protected ChannelInboundHandlerAdapter createProxyHandler() {
+                return new HAProxyMessageHandler(protocol, new ProtocolMDCContextFactory.Standard());
+            }
         };
 
     }
diff --git a/protocols/smtp/src/test/java/org/apache/james/protocols/smtp/netty/NettyProxySMTPServerTest.java b/protocols/smtp/src/test/java/org/apache/james/protocols/smtp/netty/NettyProxySMTPServerTest.java
new file mode 100644
index 0000000000..47bfa3aac6
--- /dev/null
+++ b/protocols/smtp/src/test/java/org/apache/james/protocols/smtp/netty/NettyProxySMTPServerTest.java
@@ -0,0 +1,149 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+package org.apache.james.protocols.smtp.netty;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import java.net.InetSocketAddress;
+import java.util.Optional;
+
+import org.apache.commons.net.smtp.SMTPConnectionClosedException;
+import org.apache.commons.net.smtp.SMTPReply;
+import org.apache.commons.net.smtp.SMTPSClient;
+import org.apache.james.metrics.tests.RecordingMetricFactory;
+import org.apache.james.protocols.api.Protocol;
+import org.apache.james.protocols.api.ProtocolServer;
+import org.apache.james.protocols.api.ProxyInformation;
+import org.apache.james.protocols.api.handler.ProtocolHandler;
+import org.apache.james.protocols.api.handler.WiringException;
+import org.apache.james.protocols.api.utils.BogusSslContextFactory;
+import org.apache.james.protocols.api.utils.BogusTrustManagerFactory;
+import org.apache.james.protocols.api.utils.ProtocolServerUtils;
+import org.apache.james.protocols.netty.NettyServer;
+import org.apache.james.protocols.smtp.SMTPConfigurationImpl;
+import org.apache.james.protocols.smtp.SMTPProtocol;
+import org.apache.james.protocols.smtp.SMTPProtocolHandlerChain;
+import org.apache.james.protocols.smtp.hook.HeloHook;
+import org.apache.james.protocols.smtp.hook.HookResult;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+class NettyProxySMTPServerTest {
+    private static final String LOCALHOST_IP = "127.0.0.1";
+    private static final int RANDOM_PORT = 0;
+
+    private SMTPSClient smptClient = null;
+    private ProtocolServer server = null;
+
+    @AfterEach
+    void tearDown() throws Exception {
+        if (smptClient != null) {
+            smptClient.disconnect();
+        }
+        if (server != null) {
+            server.unbind();
+        }
+    }
+
+    private ProtocolServer createServer(Protocol protocol) {
+        NettyServer server = new NettyServer.Factory()
+            .protocol(protocol)
+            .proxyRequired(true)
+            .build();
+        server.setListenAddresses(new InetSocketAddress(LOCALHOST_IP, RANDOM_PORT));
+        return server;
+    }
+
+    private SMTPSClient createClient() {
+        SMTPSClient client = new SMTPSClient(false, BogusSslContextFactory.getClientContext());
+        client.setTrustManager(BogusTrustManagerFactory.getTrustManagers()[0]);
+        return client;
+    }
+
+    private Protocol createProtocol(Optional<ProtocolHandler> handler) throws WiringException {
+        SMTPProtocolHandlerChain chain = new SMTPProtocolHandlerChain(new RecordingMetricFactory());
+        if (handler.isPresent()) {
+            chain.add(handler.get());
+        }
+        chain.wireExtensibleHandlers();
+        return new SMTPProtocol(chain, new SMTPConfigurationImpl());
+    }
+
+    @Test
+    void heloShouldReturnTrueWhenSendingTheCommandWithProxyCommandTCP4() throws Exception {
+        heloShouldReturnTrueWhenSendingTheCommandWithProxyCommand("TCP4", "255.255.255.254", "255.255.255.255");
+    }
+
+    @Test
+    void heloShouldReturnTrueWhenSendingTheCommandWithProxyCommandTCP6() throws Exception {
+        heloShouldReturnTrueWhenSendingTheCommandWithProxyCommand("TCP6", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff");
+    }
+
+    private void heloShouldReturnTrueWhenSendingTheCommandWithProxyCommand(String protocol, String source, String destination)
+        throws Exception {
+        server = createServer(createProtocol(Optional.of((HeloHook) (session, helo) -> {
+            ProxyInformation proxyInformation = session.getProxyInformation().orElseThrow();
+            assertThat(session.getRemoteAddress().getHostString()).isEqualTo(source);
+            assertThat(proxyInformation.getSource().getHostString()).isEqualTo(source);
+            assertThat(proxyInformation.getDestination().getHostString()).isEqualTo(destination);
+            return HookResult.OK;
+        })));
+
+        smptClient = createClient();
+
+        server.bind();
+        InetSocketAddress bindedAddress = new ProtocolServerUtils(server).retrieveBindedAddress();
+        smptClient.connect(bindedAddress.getAddress().getHostAddress(), bindedAddress.getPort());
+
+        smptClient.sendCommand(String.format("PROXY %s %s %s %d %d\r\nHELO localhost", protocol, source, destination, 65535, 65535));
+
+        assertThat(SMTPReply.isPositiveCompletion(smptClient.getReplyCode())).isTrue();
+        assertThat(smptClient.getReplyString()).contains(source);
+    }
+
+    @Test
+    void heloShouldReturnFalseWhenSendingCommandWithoutProxyCommand() throws Exception {
+        server = createServer(createProtocol(Optional.empty()));
+        smptClient = createClient();
+
+        server.bind();
+        InetSocketAddress bindedAddress = new ProtocolServerUtils(server).retrieveBindedAddress();
+        smptClient.connect(bindedAddress.getAddress().getHostAddress(), bindedAddress.getPort());
+
+        assertThatThrownBy(() -> smptClient.sendCommand("HELO localhost"))
+            .isInstanceOf(SMTPConnectionClosedException.class)
+            .hasMessage("Connection closed without indication.");
+    }
+
+    @Test
+    void heloShouldReturnTrueWhenSendingCommandWithProxyCommandUnknown() throws Exception {
+        server = createServer(createProtocol(Optional.empty()));
+        smptClient = createClient();
+
+        server.bind();
+        InetSocketAddress bindedAddress = new ProtocolServerUtils(server).retrieveBindedAddress();
+        smptClient.connect(bindedAddress.getAddress().getHostAddress(), bindedAddress.getPort());
+
+        smptClient.sendCommand("PROXY UNKNOWN\r\nHELO localhost");
+
+        assertThat(SMTPReply.isPositiveCompletion(smptClient.getReplyCode())).isFalse();
+    }
+
+}
diff --git a/protocols/smtp/src/test/java/org/apache/james/protocols/smtp/utils/BaseFakeSMTPSession.java b/protocols/smtp/src/test/java/org/apache/james/protocols/smtp/utils/BaseFakeSMTPSession.java
index 5089886f06..708c10298e 100644
--- a/protocols/smtp/src/test/java/org/apache/james/protocols/smtp/utils/BaseFakeSMTPSession.java
+++ b/protocols/smtp/src/test/java/org/apache/james/protocols/smtp/utils/BaseFakeSMTPSession.java
@@ -27,6 +27,7 @@ import java.util.Optional;
 
 import org.apache.james.core.Username;
 import org.apache.james.protocols.api.ProtocolSession;
+import org.apache.james.protocols.api.ProxyInformation;
 import org.apache.james.protocols.api.Response;
 import org.apache.james.protocols.api.handler.LineHandler;
 import org.apache.james.protocols.smtp.SMTPConfiguration;
@@ -146,6 +147,16 @@ public class BaseFakeSMTPSession implements SMTPSession {
         return new InetSocketAddress("localhost", 22);
     }
 
+    @Override
+    public Optional<ProxyInformation> getProxyInformation() {
+        throw new UnsupportedOperationException("Unimplemented Stub Method");
+    }
+
+    @Override
+    public void setProxyInformation(ProxyInformation proxyInformation) {
+        throw new UnsupportedOperationException("Unimplemented Stub Method");
+    }
+
     @Override
     public InetSocketAddress getLocalAddress() {
         throw new UnsupportedOperationException("Unimplemented Stub Method");
diff --git a/server/apps/distributed-app/docs/modules/ROOT/pages/configure/imap.adoc b/server/apps/distributed-app/docs/modules/ROOT/pages/configure/imap.adoc
index 646e74a061..75c22eb21e 100644
--- a/server/apps/distributed-app/docs/modules/ROOT/pages/configure/imap.adoc
+++ b/server/apps/distributed-app/docs/modules/ROOT/pages/configure/imap.adoc
@@ -107,6 +107,12 @@ will use the specified value.
 | connectionLimitPerIP
 | Set the maximum simultaneous incoming connections per IP for this service
 
+| proxyRequired
+| Enables proxy support for this service for incoming connections. HAProxy's protocol
+(https://www.haproxy.org/download/2.7/doc/proxy-protocol.txt) is used and might be compatible
+with other proxies (e.g. traefik). If enabled, it is *required* to initiate the connection
+using HAProxy's proxy protocol.
+
 | bossWorkerCount
 | Set the maximum count of boss threads. Boss threads are responsible for accepting incoming IMAP connections
 and initializing associated resources. Optional integer, by default, boss threads are not used and this responsibility is being dealt with
diff --git a/server/apps/distributed-app/docs/modules/ROOT/pages/configure/smtp.adoc b/server/apps/distributed-app/docs/modules/ROOT/pages/configure/smtp.adoc
index fc7dbd883f..38d7343929 100644
--- a/server/apps/distributed-app/docs/modules/ROOT/pages/configure/smtp.adoc
+++ b/server/apps/distributed-app/docs/modules/ROOT/pages/configure/smtp.adoc
@@ -54,6 +54,12 @@ this case, if no body is present, the value "localhost" will be used.
 | connectionLimitPerIP
 | Set the maximum simultaneous incoming connections per IP for this service.
 
+| proxyRequired
+| Enables proxy support for this service for incoming connections. HAProxy's protocol
+(https://www.haproxy.org/download/2.7/doc/proxy-protocol.txt) is used and might be compatible
+with other proxies (e.g. traefik). If enabled, it is *required* to initiate the connection
+using HAProxy's proxy protocol.
+
 | authRequired
 | (deprecated) use auth.announce instead.
 
diff --git a/server/apps/distributed-app/helm-chart/james/configs/smtpserver.xml b/server/apps/distributed-app/helm-chart/james/configs/smtpserver.xml
index c2d0dbc951..3ad644fdc7 100644
--- a/server/apps/distributed-app/helm-chart/james/configs/smtpserver.xml
+++ b/server/apps/distributed-app/helm-chart/james/configs/smtpserver.xml
@@ -34,6 +34,7 @@
         <connectionLimitPerIP>0</connectionLimitPerIP>
         <authRequired>false</authRequired>
         <verifyIdentity>false</verifyIdentity>
+        <proxyRequired>false</proxyRequired>
         <maxmessagesize>${env:JAMES_MESSAGE_SIZE}</maxmessagesize>
         <addressBracketsEnforcement>true</addressBracketsEnforcement>
         <smtpGreeting>${env:JAMES_SMTP_MESSAGE}</smtpGreeting>
@@ -56,6 +57,7 @@
         <connectionLimitPerIP>0</connectionLimitPerIP>
         <authRequired>true</authRequired>
         <verifyIdentity>true</verifyIdentity>
+        <proxyRequired>false</proxyRequired>
         <maxmessagesize>${env:JAMES_MESSAGE_SIZE}</maxmessagesize>
         <addressBracketsEnforcement>true</addressBracketsEnforcement>
         <smtpGreeting>${env:JAMES_SMTP_MESSAGE}</smtpGreeting>
@@ -78,6 +80,7 @@
         <connectionLimitPerIP>0</connectionLimitPerIP>
         <authRequired>true</authRequired>
         <verifyIdentity>true</verifyIdentity>
+        <proxyRequired>false</proxyRequired>
         <maxmessagesize>${env:JAMES_MESSAGE_SIZE}</maxmessagesize>
         <addressBracketsEnforcement>true</addressBracketsEnforcement>
         <smtpGreeting>${env:JAMES_SMTP_MESSAGE}</smtpGreeting>
diff --git a/server/protocols/protocols-imap4/src/main/java/org/apache/james/imapserver/netty/HAProxyMessageHandler.java b/server/protocols/protocols-imap4/src/main/java/org/apache/james/imapserver/netty/HAProxyMessageHandler.java
new file mode 100644
index 0000000000..7de5436dfe
--- /dev/null
+++ b/server/protocols/protocols-imap4/src/main/java/org/apache/james/imapserver/netty/HAProxyMessageHandler.java
@@ -0,0 +1,76 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.imapserver.netty;
+
+import static org.apache.james.imapserver.netty.ImapChannelUpstreamHandler.MDC_KEY;
+
+import java.net.InetSocketAddress;
+
+import org.apache.james.imap.api.process.ImapSession;
+import org.apache.james.protocols.api.CommandDetectionSession;
+import org.apache.james.protocols.api.ProxyInformation;
+import org.apache.james.util.MDCBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import io.netty.channel.ChannelPipeline;
+import io.netty.handler.codec.haproxy.HAProxyMessage;
+import io.netty.handler.codec.haproxy.HAProxyProxiedProtocol;
+import io.netty.util.AttributeKey;
+
+public class HAProxyMessageHandler extends ChannelInboundHandlerAdapter {
+    private static final Logger LOGGER = LoggerFactory.getLogger(HAProxyMessageHandler.class);
+    private static final AttributeKey<CommandDetectionSession> SESSION_ATTRIBUTE_KEY = AttributeKey.valueOf("ImapSession");
+    public static final AttributeKey<ProxyInformation> PROXY_INFO = AttributeKey.valueOf("proxyInfo");
+
+    @Override
+    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+        if (msg instanceof HAProxyMessage) {
+            HAProxyMessage haproxyMsg = (HAProxyMessage) msg;
+
+            ChannelPipeline pipeline = ctx.pipeline();
+            ImapSession imapSession = (ImapSession) pipeline.channel().attr(SESSION_ATTRIBUTE_KEY).get();
+            if (haproxyMsg.proxiedProtocol().equals(HAProxyProxiedProtocol.TCP4) || haproxyMsg.proxiedProtocol().equals(HAProxyProxiedProtocol.TCP6)) {
+
+                ctx.channel().attr(PROXY_INFO).set(
+                    new ProxyInformation(
+                        new InetSocketAddress(haproxyMsg.sourceAddress(), haproxyMsg.sourcePort()),
+                        new InetSocketAddress(haproxyMsg.destinationAddress(), haproxyMsg.destinationPort())));
+
+                LOGGER.info("Connection from {} runs through {} proxy", haproxyMsg.sourceAddress(), haproxyMsg.destinationAddress());
+                // Refresh MDC info to account for proxying
+                MDCBuilder boundMDC = IMAPMDCContext.boundMDC(ctx);
+
+                if (imapSession != null) {
+                    imapSession.setAttribute(MDC_KEY, boundMDC);
+                }
+            } else {
+                throw new IllegalArgumentException("Only TCP4/TCP6 are supported when using PROXY protocol.");
+            }
+
+            haproxyMsg.release();
+            super.channelReadComplete(ctx);
+        } else {
+            super.channelRead(ctx, msg);
+        }
+    }
+}
diff --git a/server/protocols/protocols-imap4/src/main/java/org/apache/james/imapserver/netty/IMAPMDCContext.java b/server/protocols/protocols-imap4/src/main/java/org/apache/james/imapserver/netty/IMAPMDCContext.java
index d12a39e41a..d484877857 100644
--- a/server/protocols/protocols-imap4/src/main/java/org/apache/james/imapserver/netty/IMAPMDCContext.java
+++ b/server/protocols/protocols-imap4/src/main/java/org/apache/james/imapserver/netty/IMAPMDCContext.java
@@ -33,8 +33,8 @@ import io.netty.channel.ChannelHandlerContext;
 public class IMAPMDCContext {
     public static MDCBuilder boundMDC(ChannelHandlerContext ctx) {
         MDCBuilder mdc = MDCBuilder.create()
-            .addToContext(MDCBuilder.PROTOCOL, "IMAP")
-            .addToContext(MDCBuilder.IP, retrieveIp(ctx));
+            .addToContext(MDCBuilder.PROTOCOL, "IMAP");
+        setIpAndProxyInformation(ctx, mdc);
 
         if (ProtocolMDCContextFactory.ADD_HOST_TO_MDC) {
             return mdc.addToContext(MDCBuilder.HOST, retrieveHost(ctx));
@@ -42,6 +42,18 @@ public class IMAPMDCContext {
         return mdc;
     }
 
+    private static void setIpAndProxyInformation(ChannelHandlerContext ctx, MDCBuilder mdcBuilder) {
+        Optional.ofNullable(ctx.channel().attr(HAProxyMessageHandler.PROXY_INFO)
+            .get())
+            .ifPresentOrElse(proxyInformation -> {
+                    mdcBuilder.addToContext(MDCBuilder.IP, proxyInformation.getSource().toString());
+                    mdcBuilder.addToContext("proxy.source", proxyInformation.getSource().toString());
+                    mdcBuilder.addToContext("proxy.destination", proxyInformation.getDestination().toString());
+                    mdcBuilder.addToContext("proxy.ip", retrieveIp(ctx));
+                },
+                () -> mdcBuilder.addToContext(MDCBuilder.IP, retrieveIp(ctx)));
+    }
+
     private static String retrieveIp(ChannelHandlerContext ctx) {
         SocketAddress remoteAddress = ctx.channel().remoteAddress();
         if (remoteAddress instanceof InetSocketAddress) {
diff --git a/server/protocols/protocols-imap4/src/main/java/org/apache/james/imapserver/netty/IMAPServer.java b/server/protocols/protocols-imap4/src/main/java/org/apache/james/imapserver/netty/IMAPServer.java
index ff39b32d44..6c5fd0ab4a 100644
--- a/server/protocols/protocols-imap4/src/main/java/org/apache/james/imapserver/netty/IMAPServer.java
+++ b/server/protocols/protocols-imap4/src/main/java/org/apache/james/imapserver/netty/IMAPServer.java
@@ -50,6 +50,7 @@ import com.google.common.collect.ImmutableSet;
 import io.netty.channel.Channel;
 import io.netty.channel.ChannelInboundHandlerAdapter;
 import io.netty.channel.ChannelPipeline;
+import io.netty.handler.codec.haproxy.HAProxyMessageDecoder;
 import io.netty.handler.stream.ChunkedWriteHandler;
 
 
@@ -222,6 +223,11 @@ public class IMAPServer extends AbstractConfigurableAsyncServer implements ImapC
                 return createCoreHandler();
             }
 
+            @Override
+            protected ChannelInboundHandlerAdapter createProxyHandler() {
+                return new HAProxyMessageHandler();
+            }
+
             @Override
             public void initChannel(Channel channel) {
                 ChannelPipeline pipeline = channel.pipeline();
@@ -230,6 +236,11 @@ public class IMAPServer extends AbstractConfigurableAsyncServer implements ImapC
                 connectionLimitUpstreamHandler.ifPresent(handler -> pipeline.addLast(HandlerConstants.CONNECTION_LIMIT_HANDLER, handler));
                 connectionPerIpLimitUpstreamHandler.ifPresent(handler -> pipeline.addLast(HandlerConstants.CONNECTION_LIMIT_PER_IP_HANDLER, handler));
 
+                if (proxyRequired) {
+                    pipeline.addLast(HandlerConstants.PROXY_HANDLER, new HAProxyMessageDecoder());
+                    pipeline.addLast("proxyInformationHandler", createProxyHandler());
+                }
+
                 // Add the text line decoder which limit the max line length,
                 // don't strip the delimiter and use CRLF as delimiter
                 // Use a SwitchableDelimiterBasedFrameDecoder, see JAMES-1436
@@ -272,6 +283,11 @@ public class IMAPServer extends AbstractConfigurableAsyncServer implements ImapC
             .build();
     }
 
+    @Override
+    protected ChannelInboundHandlerAdapter createProxyHandler() {
+        return new HAProxyMessageHandler();
+    }
+
     @Override
     protected ChannelHandlerFactory createFrameHandlerFactory() {
         return new SwitchableLineBasedFrameDecoderFactory(maxLineLength);
diff --git a/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java b/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
index 22581e3241..7671233d16 100644
--- a/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
+++ b/server/protocols/protocols-imap4/src/test/java/org/apache/james/imapserver/netty/IMAPServerTest.java
@@ -439,6 +439,39 @@ class IMAPServerTest {
         }
     }
 
+    @Nested
+    class Proxy {
+        IMAPServer imapServer;
+        private SocketChannel clientConnection;
+
+        @BeforeEach
+        void beforeEach() throws Exception {
+            imapServer = createImapServer("imapServerProxy.xml");
+            int port = imapServer.getListenAddresses().get(0).getPort();
+            memoryIntegrationResources.getMailboxManager()
+                .createMailbox(MailboxPath.inbox(USER), memoryIntegrationResources.getMailboxManager().createSystemSession(USER));
+            clientConnection = SocketChannel.open();
+            clientConnection.connect(new InetSocketAddress(LOCALHOST_IP, port));
+            readBytes(clientConnection);
+        }
+
+        @AfterEach
+        void tearDown() throws Exception {
+            clientConnection.close();
+            imapServer.destroy();
+        }
+
+        @Test
+        void shouldNotFailOnProxyInformation() throws Exception {
+            clientConnection.write(ByteBuffer.wrap(String.format("PROXY %s %s %s %d %d\r\na0 LOGIN %s %s\r\n",
+                "TCP4", "255.255.255.254", "255.255.255.255", 65535, 65535,
+                USER.asString(), USER_PASS).getBytes(StandardCharsets.UTF_8)));
+
+            assertThat(new String(readBytes(clientConnection), StandardCharsets.US_ASCII))
+                .startsWith("a0 OK");
+        }
+    }
+
     @Nested
     class Compress {
         IMAPServer imapServer;
diff --git a/server/protocols/protocols-imap4/src/test/resources/imapServerProxy.xml b/server/protocols/protocols-imap4/src/test/resources/imapServerProxy.xml
new file mode 100644
index 0000000000..59f810934d
--- /dev/null
+++ b/server/protocols/protocols-imap4/src/test/resources/imapServerProxy.xml
@@ -0,0 +1,15 @@
+
+<imapserver enabled="true">
+    <jmxName>imapserver</jmxName>
+    <bind>0.0.0.0:0</bind>
+    <connectionBacklog>200</connectionBacklog>
+    <connectionLimit>0</connectionLimit>
+    <connectionLimitPerIP>0</connectionLimitPerIP>
+    <idleTimeInterval>120</idleTimeInterval>
+    <idleTimeIntervalUnit>SECONDS</idleTimeIntervalUnit>
+    <enableIdle>true</enableIdle>
+    <inMemorySizeLimit>65536</inMemorySizeLimit> <!-- 64 KB -->
+    <plainAuthDisallowed>false</plainAuthDisallowed>
+    <gracefulShutdown>false</gracefulShutdown>
+    <proxyRequired>true</proxyRequired>
+</imapserver>
\ No newline at end of file
diff --git a/server/protocols/protocols-library/src/main/java/org/apache/james/protocols/lib/netty/AbstractConfigurableAsyncServer.java b/server/protocols/protocols-library/src/main/java/org/apache/james/protocols/lib/netty/AbstractConfigurableAsyncServer.java
index 18c30622dc..96b97b92fb 100644
--- a/server/protocols/protocols-library/src/main/java/org/apache/james/protocols/lib/netty/AbstractConfigurableAsyncServer.java
+++ b/server/protocols/protocols-library/src/main/java/org/apache/james/protocols/lib/netty/AbstractConfigurableAsyncServer.java
@@ -79,12 +79,17 @@ public abstract class AbstractConfigurableAsyncServer extends AbstractAsyncServe
     /** The name of the parameter defining the service hello name. */
     public static final String HELLO_NAME = "helloName";
 
+    /** The name of the parameter defining the service hello name. */
+    public static final String PROXY_REQUIRED = "proxyRequired";
+
     public static final int DEFAULT_MAX_EXECUTOR_COUNT = 16;
 
     private FileSystem fileSystem;
 
     private boolean enabled;
 
+    protected boolean proxyRequired;
+
     protected int connPerIP;
 
     protected int connectionLimit;
@@ -216,6 +221,8 @@ public abstract class AbstractConfigurableAsyncServer extends AbstractAsyncServe
 
         Optional.ofNullable(config.getBoolean("gracefulShutdown", null)).ifPresent(this::setGracefulShutdown);
 
+        proxyRequired = config.getBoolean(PROXY_REQUIRED, false);
+
         doConfigure(config);
 
     }
@@ -444,16 +451,23 @@ public abstract class AbstractConfigurableAsyncServer extends AbstractAsyncServe
     }
 
     protected abstract ChannelInboundHandlerAdapter createCoreHandler();
-    
+
+    protected abstract ChannelInboundHandlerAdapter createProxyHandler();
+
     @Override
     protected AbstractChannelPipelineFactory createPipelineFactory() {
         return new AbstractSSLAwareChannelPipelineFactory<>(getTimeout(), connectionLimit, connPerIP,
+            proxyRequired,
             getEncryption(), getFrameHandlerFactory(), getExecutorGroup()) {
 
             @Override
             protected ChannelInboundHandlerAdapter createHandler() {
                 return AbstractConfigurableAsyncServer.this.createCoreHandler();
+            }
 
+            @Override
+            protected ChannelInboundHandlerAdapter createProxyHandler() {
+                return AbstractConfigurableAsyncServer.this.createProxyHandler();
             }
         };
     }
diff --git a/server/protocols/protocols-library/src/test/java/org/apache/james/protocols/lib/AbstractConfigurableAsyncServerTest.java b/server/protocols/protocols-library/src/test/java/org/apache/james/protocols/lib/AbstractConfigurableAsyncServerTest.java
index eee4a1b6ed..c72878c2b6 100644
--- a/server/protocols/protocols-library/src/test/java/org/apache/james/protocols/lib/AbstractConfigurableAsyncServerTest.java
+++ b/server/protocols/protocols-library/src/test/java/org/apache/james/protocols/lib/AbstractConfigurableAsyncServerTest.java
@@ -109,6 +109,11 @@ class AbstractConfigurableAsyncServerTest {
             return null;
         }
 
+        @Override
+        protected ChannelInboundHandlerAdapter createProxyHandler() {
+            throw new NotImplementedException();
+        }
+
         // test accessors
 
         public int getConnPerIp () {
diff --git a/server/protocols/protocols-lmtp/src/main/java/org/apache/james/lmtpserver/netty/LMTPServer.java b/server/protocols/protocols-lmtp/src/main/java/org/apache/james/lmtpserver/netty/LMTPServer.java
index fcf15a105e..a92da42c83 100644
--- a/server/protocols/protocols-lmtp/src/main/java/org/apache/james/lmtpserver/netty/LMTPServer.java
+++ b/server/protocols/protocols-lmtp/src/main/java/org/apache/james/lmtpserver/netty/LMTPServer.java
@@ -33,8 +33,10 @@ import org.apache.james.protocols.lib.netty.AbstractProtocolAsyncServer;
 import org.apache.james.protocols.lmtp.LMTPConfiguration;
 import org.apache.james.protocols.netty.AbstractChannelPipelineFactory;
 import org.apache.james.protocols.netty.ChannelHandlerFactory;
+import org.apache.james.protocols.netty.HAProxyMessageHandler;
 import org.apache.james.protocols.netty.LineDelimiterBasedChannelHandlerFactory;
 import org.apache.james.protocols.smtp.SMTPProtocol;
+import org.apache.james.protocols.smtp.core.SMTPMDCContextFactory;
 import org.apache.james.smtpserver.ExtendedSMTPSession;
 import org.apache.james.smtpserver.netty.SMTPChannelInboundHandler;
 import org.slf4j.Logger;
@@ -153,6 +155,17 @@ public class LMTPServer extends AbstractProtocolAsyncServer implements LMTPServe
         return new SMTPChannelInboundHandler(transport, lmtpMetrics);
     }
 
+    @Override
+    protected ChannelInboundHandlerAdapter createProxyHandler() {
+        SMTPProtocol transport = new SMTPProtocol(getProtocolHandlerChain(), lmtpConfig) {
+            @Override
+            public ProtocolSession newSession(ProtocolTransport transport) {
+                return new ExtendedSMTPSession(lmtpConfig, transport);
+            }
+        };
+        return new HAProxyMessageHandler(transport, new SMTPMDCContextFactory());
+    }
+
     @Override
     protected Class<? extends HandlersPackage> getCoreHandlersPackage() {
         return CoreCmdHandlerLoader.class;
diff --git a/server/protocols/protocols-managesieve/src/main/java/org/apache/james/managesieveserver/netty/ManageSieveServer.java b/server/protocols/protocols-managesieve/src/main/java/org/apache/james/managesieveserver/netty/ManageSieveServer.java
index 93ab6ab22c..dc4f41df1c 100644
--- a/server/protocols/protocols-managesieve/src/main/java/org/apache/james/managesieveserver/netty/ManageSieveServer.java
+++ b/server/protocols/protocols-managesieve/src/main/java/org/apache/james/managesieveserver/netty/ManageSieveServer.java
@@ -28,6 +28,7 @@ import java.util.Optional;
 import org.apache.commons.configuration2.HierarchicalConfiguration;
 import org.apache.commons.configuration2.ex.ConfigurationException;
 import org.apache.commons.configuration2.tree.ImmutableNode;
+import org.apache.commons.lang3.NotImplementedException;
 import org.apache.james.managesieve.transcode.ManageSieveProcessor;
 import org.apache.james.protocols.lib.netty.AbstractConfigurableAsyncServer;
 import org.apache.james.protocols.netty.AbstractChannelPipelineFactory;
@@ -89,6 +90,11 @@ public class ManageSieveServer extends AbstractConfigurableAsyncServer implement
         return new ManageSieveChannelUpstreamHandler(manageSieveProcessor, getEncryption(), maxLineLength);
     }
 
+    @Override
+    protected ChannelInboundHandlerAdapter createProxyHandler() {
+        throw new NotImplementedException();
+    }
+
     @Override
     protected AbstractChannelPipelineFactory createPipelineFactory() {
 
@@ -99,6 +105,11 @@ public class ManageSieveServer extends AbstractConfigurableAsyncServer implement
                 return createCoreHandler();
             }
 
+            @Override
+            protected ChannelInboundHandlerAdapter createProxyHandler() {
+                throw new NotImplementedException();
+            }
+
             @Override
             public void initChannel(Channel channel) {
                 ChannelPipeline pipeline = channel.pipeline();
diff --git a/server/protocols/protocols-pop3/src/main/java/org/apache/james/pop3server/netty/POP3Server.java b/server/protocols/protocols-pop3/src/main/java/org/apache/james/pop3server/netty/POP3Server.java
index 89725cce3e..90a1118b9d 100644
--- a/server/protocols/protocols-pop3/src/main/java/org/apache/james/pop3server/netty/POP3Server.java
+++ b/server/protocols/protocols-pop3/src/main/java/org/apache/james/pop3server/netty/POP3Server.java
@@ -27,6 +27,7 @@ import org.apache.james.protocols.netty.AbstractChannelPipelineFactory;
 import org.apache.james.protocols.netty.AllButStartTlsLineChannelHandlerFactory;
 import org.apache.james.protocols.netty.BasicChannelInboundHandler;
 import org.apache.james.protocols.netty.ChannelHandlerFactory;
+import org.apache.james.protocols.netty.HAProxyMessageHandler;
 import org.apache.james.protocols.netty.ProtocolMDCContextFactory;
 import org.apache.james.protocols.pop3.POP3Protocol;
 
@@ -86,7 +87,12 @@ public class POP3Server extends AbstractProtocolAsyncServer implements POP3Serve
 
     @Override
     protected ChannelInboundHandlerAdapter createCoreHandler() {
-        return new BasicChannelInboundHandler(new ProtocolMDCContextFactory.Standard(), protocol, getEncryption());
+        return new BasicChannelInboundHandler(new ProtocolMDCContextFactory.Standard(), protocol, getEncryption(), false);
+    }
+
+    @Override
+    protected ChannelInboundHandlerAdapter createProxyHandler() {
+        return new HAProxyMessageHandler(protocol, new ProtocolMDCContextFactory.Standard());
     }
 
     @Override
diff --git a/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/netty/SMTPChannelInboundHandler.java b/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/netty/SMTPChannelInboundHandler.java
index 1d63b73601..d232a217e4 100644
--- a/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/netty/SMTPChannelInboundHandler.java
+++ b/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/netty/SMTPChannelInboundHandler.java
@@ -39,8 +39,8 @@ public class SMTPChannelInboundHandler extends BasicChannelInboundHandler {
 
     private final SmtpMetrics smtpMetrics;
 
-    public SMTPChannelInboundHandler(Protocol protocol, Encryption encryption, SmtpMetrics smtpMetrics) {
-        super(new SMTPMDCContextFactory(), protocol, encryption);
+    public SMTPChannelInboundHandler(Protocol protocol, Encryption encryption, boolean proxyRequired, SmtpMetrics smtpMetrics) {
+        super(new SMTPMDCContextFactory(), protocol, encryption, proxyRequired);
         this.smtpMetrics = smtpMetrics;
         this.resultHandlers.add(recordCommandCount(smtpMetrics));
     }
diff --git a/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/netty/SMTPServer.java b/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/netty/SMTPServer.java
index 1468edc620..c9dc5acdeb 100644
--- a/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/netty/SMTPServer.java
+++ b/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/netty/SMTPServer.java
@@ -40,8 +40,10 @@ import org.apache.james.protocols.lib.netty.AbstractProtocolAsyncServer;
 import org.apache.james.protocols.netty.AbstractChannelPipelineFactory;
 import org.apache.james.protocols.netty.AllButStartTlsLineChannelHandlerFactory;
 import org.apache.james.protocols.netty.ChannelHandlerFactory;
+import org.apache.james.protocols.netty.HAProxyMessageHandler;
 import org.apache.james.protocols.smtp.SMTPConfiguration;
 import org.apache.james.protocols.smtp.SMTPProtocol;
+import org.apache.james.protocols.smtp.core.SMTPMDCContextFactory;
 import org.apache.james.smtpserver.CoreCmdHandlerLoader;
 import org.apache.james.smtpserver.ExtendedSMTPSession;
 import org.apache.james.smtpserver.jmx.JMXHandlersLoader;
@@ -385,7 +387,12 @@ public class SMTPServer extends AbstractProtocolAsyncServer implements SMTPServe
 
     @Override
     protected ChannelInboundHandlerAdapter createCoreHandler() {
-        return new SMTPChannelInboundHandler(transport, getEncryption(), smtpMetrics);
+        return new SMTPChannelInboundHandler(transport, getEncryption(), proxyRequired, smtpMetrics);
+    }
+
+    @Override
+    protected ChannelInboundHandlerAdapter createProxyHandler() {
+        return new HAProxyMessageHandler(transport, new SMTPMDCContextFactory());
     }
 
     @Override
diff --git a/src/site/xdoc/server/config-imap4.xml b/src/site/xdoc/server/config-imap4.xml
index e3d971d692..0da6435b53 100644
--- a/src/site/xdoc/server/config-imap4.xml
+++ b/src/site/xdoc/server/config-imap4.xml
@@ -80,6 +80,13 @@
         <dd>Set the maximum simultaneous incoming connections for this service</dd>
         <dt><strong>handler.connectionLimitPerIP</strong></dt>
         <dd>Set the maximum simultaneous incoming connections per IP for this service</dd>
+        <dt><strong>handler.proxyRequired</strong></dt>
+        <dd>
+          Enables proxy support for this service for incoming connections. HAProxy's protocol
+          (https://www.haproxy.org/download/2.7/doc/proxy-protocol.txt) is used and might be compatible
+          with other proxies (e.g. traefik). If enabled, it is *required* to initiate the connection
+          using HAProxy's proxy protocol.
+        </dd>
         <dt><strong>handler.handlerchain</strong></dt>
         <dd>This loads the core CommandHandlers. Only remove this if you really 
              know what you are doing</dd>
diff --git a/src/site/xdoc/server/config-smtp-lmtp.xml b/src/site/xdoc/server/config-smtp-lmtp.xml
index d684c783af..de8c8da62e 100644
--- a/src/site/xdoc/server/config-smtp-lmtp.xml
+++ b/src/site/xdoc/server/config-smtp-lmtp.xml
@@ -74,6 +74,13 @@
       <dd>Set the maximum simultaneous incoming connections for this service.</dd>
       <dt><strong>handler.connectionLimitPerIP</strong></dt>
       <dd>Set the maximum simultaneous incoming connections per IP for this service.</dd>
+      <dt><strong>handler.proxyRequired</strong></dt>
+      <dd>
+        Enables proxy support for this service for incoming connections. HAProxy's protocol
+        (https://www.haproxy.org/download/2.7/doc/proxy-protocol.txt) is used and might be compatible
+        with other proxies (e.g. traefik). If enabled, it is *required* to initiate the connection
+        using HAProxy's proxy protocol.
+      </dd>
         <dt><strong>authRequired</strong></dt>
         <dd>(deprecated) use auth.announce instead.
             This is an optional tag with a boolean body.  If true, then the server will


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org