File 0003-Introduce-optional-support-to-inject-PROXY-protocol-.patch of Package roundcubemail-selfcontained

From 70e23efafae75b5604fd5206e64571cee59ba71a Mon Sep 17 00:00:00 2001
From: Christian Mollekopf <mollekopf@apheleia-it.ch>
Date: Thu, 14 Jul 2022 15:04:16 +0200
Subject: [PATCH 3/6] Introduce optional support to inject PROXY protocol
 headers after opening IMAP TCP streams.

Version 1 (text based) and version 2 (binary) protocol header types are
supported. Supports both IPv4 and IPv6 style headers.

http://www.haproxy.org/download/1.6/doc/proxy-protocol.txt
---
 config/defaults.inc.php                      | 10 +++
 program/lib/Roundcube/rcube_imap_generic.php |  8 ++
 program/lib/Roundcube/rcube_utils.php        | 79 ++++++++++++++++++++
 3 files changed, 97 insertions(+)

diff --git a/config/defaults.inc.php b/config/defaults.inc.php
index 018ad9af3..a6cd3b9a5 100644
--- a/config/defaults.inc.php
+++ b/config/defaults.inc.php
@@ -157,12 +157,22 @@ $config['imap_auth_type'] = null;
 // IMAP socket context options
 // See http://php.net/manual/en/context.ssl.php
 // The example below enables server certificate validation
+//
+// proxy_protocol is used to inject HAproxy style headers in the TCP stream
+// See http://www.haproxy.org/download/1.6/doc/proxy-protocol.txt
 //$config['imap_conn_options'] = [
 //  'ssl'         => [
 //     'verify_peer'  => true,
 //     'verify_depth' => 3,
 //     'cafile'       => '/etc/openssl/certs/ca.crt',
 //   ],
+//  'proxy_protocol' => 1 | 2 | [ // required (either version number (1|2) or array with 'version' key)
+//       'version'       => 1 | 2, // required, if array
+//       'remote_addr'   => $_SERVER['REMOTE_ADDR'],
+//       'remote_port'   => $_SERVER['REMOTE_PORT'],
+//       'local_addr'    => $_SERVER['SERVER_ADDR'],
+//       'local_port'    => $_SERVER['SERVER_PORT'],
+//   ],
 // ];
 // Note: These can be also specified as an array of options indexed by hostname
 $config['imap_conn_options'] = null;
diff --git a/program/lib/Roundcube/rcube_imap_generic.php b/program/lib/Roundcube/rcube_imap_generic.php
index 8f1285368..d2f5cf410 100644
--- a/program/lib/Roundcube/rcube_imap_generic.php
+++ b/program/lib/Roundcube/rcube_imap_generic.php
@@ -1070,6 +1070,14 @@ class rcube_imap_generic
             return false;
         }
 
+        // insert proxy protocol header, if enabled
+        if (!empty($this->prefs['socket_options'])) {
+            $proxy_protocol_header = rcube_utils::proxy_protocol_header($this->prefs['socket_options'], $this->fp);
+            if (strlen($proxy_protocol_header) > 0) {
+                fwrite($this->fp, $proxy_protocol_header);
+            }
+        }
+        
         if ($this->prefs['timeout'] > 0) {
             stream_set_timeout($this->fp, $this->prefs['timeout']);
         }
diff --git a/program/lib/Roundcube/rcube_utils.php b/program/lib/Roundcube/rcube_utils.php
index eb51f98ed..ec31fa11b 100644
--- a/program/lib/Roundcube/rcube_utils.php
+++ b/program/lib/Roundcube/rcube_utils.php
@@ -1645,4 +1645,83 @@ class rcube_utils
 
         return trim($subject);
     }
+
+    /**
+     * When proxy_protocol is configured for a connection type,
+     * generate the HAproxy style PROXY protocol header for injection
+     * into the TCP stream.
+     * http://www.haproxy.org/download/1.6/doc/proxy-protocol.txt
+     * 
+     * PROXY protocol headers must be sent before any other data is sent on the TCP socket.
+     *
+     * @param array $conn_options preferences array which may contain proxy_protocol (generally {driver}_conn_options)
+     *
+     * @return string proxy protocol header data, if enabled, otherwise empty string
+     */
+    public static function proxy_protocol_header($conn_options = null)
+    {
+        if ($conn_options === null)
+        {
+            return "";
+        }
+        // verify that proxy_protocol option is present
+        if (is_array($conn_options) && array_key_exists('proxy_protocol', $conn_options)) {
+            if (is_array($conn_options['proxy_protocol'])) {
+                $proxy_protocol_version = $conn_options['proxy_protocol']['version'];
+                $proxy_protocol_options = $conn_options['proxy_protocol'];
+            }
+            else {
+                $proxy_protocol_version = $conn_options['proxy_protocol'];
+                $proxy_protocol_options = null;
+            }
+
+            $proxy_protocol_remote_addr = (array_key_exists('remote_addr', $proxy_protocol_options) ? $proxy_protocol_options['remote_addr'] : $_SERVER['REMOTE_ADDR'] );
+            $proxy_protocol_remote_port = (array_key_exists('remote_port', $proxy_protocol_options) ? $proxy_protocol_options['remote_port'] : $_SERVER['REMOTE_PORT'] );
+            $proxy_protocol_local_addr = (array_key_exists('local_addr' ,$proxy_protocol_options) ? $proxy_protocol_options['local_addr'] : $_SERVER['SERVER_ADDR'] );
+            $proxy_protocol_local_port = (array_key_exists('local_port', $proxy_protocol_options) ? $proxy_protocol_options['local_port'] : $_SERVER['SERVER_PORT'] );
+            $proxy_protocol_ip_version = (strpos($proxy_protocol_remote_addr, ":") === false ? 4 : 6);
+
+            if ($proxy_protocol_version === 1) {
+                // text based PROXY protocol
+
+                // PROXY protocol does not support dual IPv6+IPv4 type addresses, e.g. ::127.0.0.1
+                if ($proxy_protocol_ip_version === 6 && strpos($proxy_protocol_remote_addr, ".") !== false) {
+                    $proxy_protocol_remote_addr = inet_ntop(inet_pton($proxy_protocol_remote_addr));
+                }
+                if ($proxy_protocol_ip_version === 6 && strpos($proxy_protocol_local_addr, ".") !== false) {
+                    $proxy_protocol_local_addr = inet_ntop(inet_pton($proxy_protocol_local_addr));
+                }
+
+                $proxy_protocol_text = "PROXY " . // protocol header
+                    ($proxy_protocol_ip_version === 6 ? "TCP6 " : "TCP4 ") . // IP version type
+                    $proxy_protocol_remote_addr .
+                    " " .
+                    $proxy_protocol_local_addr .
+                    " " .
+                    $proxy_protocol_remote_port .
+                    " " .
+                    $proxy_protocol_local_port .
+                    "\r\n";
+                return $proxy_protocol_text;
+            }
+            else if ($proxy_protocol_version === 2) {
+                // binary PROXY protocol
+                $proxy_protocol_bin = pack("H*", "0D0A0D0A000D0A515549540A" . // protocol header
+                    "21" . // protocol version and command
+                    ($proxy_protocol_ip_version === 6 ? "2" : "1") . // IP version type
+                    "1"); // TCP
+                $proxy_protocol_addr = inet_pton($proxy_protocol_remote_addr) .
+                    inet_pton($proxy_protocol_local_addr) .
+                    pack("n", $proxy_protocol_remote_port) .
+                    pack("n", $proxy_protocol_local_port);
+                $proxy_protocol_bin .= pack("n", strlen($proxy_protocol_addr)) . $proxy_protocol_addr;
+                return $proxy_protocol_bin;
+            }
+            else {
+                // unknown proxy protocol version
+                return "";
+            }
+        }
+        return "";
+    }
 }
-- 
2.37.1