diff --git a/docker/kolab/utils/23-patch-system.sh b/docker/kolab/utils/23-patch-system.sh index fa1d6849..71aa699d 100755 --- a/docker/kolab/utils/23-patch-system.sh +++ b/docker/kolab/utils/23-patch-system.sh @@ -1,9 +1,9 @@ #!/bin/bash PATCHPATH=$(pwd)/patches pushd /usr/share/roundcubemail/ || exit -# patch -p1 -l < "$PATCHPATH/0002-WOAT-support.patch" -# patch -p1 -l < "$PATCHPATH/0003-PROXY-protocol-support.patch" +patch -p1 -l < "$PATCHPATH/0002-WOAT-support.patch" +patch -p1 -l < "$PATCHPATH/0003-PROXY-protocol-support.patch" popd || exit -# systemctl restart httpd +systemctl restart httpd diff --git a/docker/kolab/utils/patches/0002-WOAT-support.patch b/docker/kolab/utils/patches/0002-WOAT-support.patch new file mode 100644 index 00000000..0a14f879 --- /dev/null +++ b/docker/kolab/utils/patches/0002-WOAT-support.patch @@ -0,0 +1,208 @@ +From d140792bdd838fc543128c977bfe47199c914e64 Mon Sep 17 00:00:00 2001 +From: Christian Mollekopf +Date: Thu, 14 Jul 2022 15:03:22 +0200 +Subject: [PATCH 2/3] WOAT support + +--- + plugins/enigma/config.inc.php.dist | 5 ++ + plugins/enigma/lib/enigma_engine.php | 93 +++++++++++++++++++++++-- + program/lib/Roundcube/rcube_message.php | 10 +-- + 3 files changed, 99 insertions(+), 9 deletions(-) + +diff --git a/plugins/enigma/config.inc.php.dist b/plugins/enigma/config.inc.php.dist +index fc620587e..f77ad26a8 100644 +--- a/plugins/enigma/config.inc.php.dist ++++ b/plugins/enigma/config.inc.php.dist +@@ -75,3 +75,8 @@ $config['enigma_password_time'] = 5; + // - enigma_options_lock = array('sign') + // - dont_override = array('enigma_sign_all') + $config['enigma_options_lock'] = array(); ++ ++// Enable Kolab's Web Of Anti-Trust feature ++// Fetches public keys from DNS. Default: false ++// To enable set it to True or an array of domain names. ++$config['enigma_woat'] = false; +diff --git a/plugins/enigma/lib/enigma_engine.php b/plugins/enigma/lib/enigma_engine.php +index 4dbb464a5..45c98caef 100644 +--- a/plugins/enigma/lib/enigma_engine.php ++++ b/plugins/enigma/lib/enigma_engine.php +@@ -28,6 +28,7 @@ class enigma_engine + private $pgp_driver; + private $smime_driver; + private $password_time; ++ private $sender; + private $cache = array(); + + public $decryptions = array(); +@@ -267,6 +268,9 @@ class enigma_engine + + $recipients = array_unique($recipients); + ++ // Fetch keys from external sources, if configured ++ $this->sync_keys($recipients); ++ + // find recipient public keys + foreach ((array) $recipients as $email) { + if ($email == $from && $sign_key) { +@@ -377,6 +381,17 @@ class enigma_engine + return; + } + ++ // Get the message/part sender ++ if (!empty($p['object']->sender) && !empty($p['object']->sender['mailto'])) { ++ $this->sender = $p['object']->sender['mailto']; ++ } ++ if (!empty($p['structure']->headers) && !empty($p['structure']->headers['from'])) { ++ $from = rcube_mime::decode_address_list($p['structure']->headers['from'], 1, false); ++ if (($from = current($from)) && !empty($from['mailto'])) { ++ $this->sender = $from['mailto']; ++ } ++ } ++ + // Don't be tempted to support encryption in text/html parts + // Because of EFAIL vulnerability we should never support this (#6289) + +@@ -875,6 +890,11 @@ class enigma_engine + { + // @TODO: Handle big bodies using (temp) files + ++ // Import sender's key from external sources, if configured ++ if ($this->sender) { ++ $this->sync_keys([$this->sender]); ++ } ++ + // Get rid of possible non-ascii characters (#5962) + $sig_body = preg_replace('/[^\x00-\x7F]/', '', $sig_body); + +@@ -899,6 +919,11 @@ class enigma_engine + { + // @TODO: Handle big bodies using (temp) files + ++ // Import sender's key from external sources, if configured ++ if ($this->sender) { ++ $this->sync_keys([$this->sender]); ++ } ++ + // Get rid of possible non-ascii characters (#5962) + $msg_body = preg_replace('/[^\x00-\x7F]/', '', $msg_body); + +@@ -1014,19 +1039,25 @@ class enigma_engine + return; + } + +- $mode = $can_sign ? enigma_key::CAN_SIGN : enigma_key::CAN_ENCRYPT; +- $ret = null; ++ $mode = $can_sign ? enigma_key::CAN_SIGN : enigma_key::CAN_ENCRYPT; ++ $found = []; + + // check key validity and type + foreach ($result as $key) { + if (($subkey = $key->find_subkey($email, $mode)) + && (!$can_sign || $key->get_type() == enigma_key::TYPE_KEYPAIR) + ) { +- $ret = $key; +- break; ++ $found[] = $key; //$found[$subkey->get_creation_date(true)] = $key; + } + } + ++ // Use the most recent one ++ //if (count($found) > 1) { ++ //ksort($found, SORT_NUMERIC); ++ //} ++ ++ $ret = count($found) > 0 ? array_pop($found) : null; ++ + // cache private key info for better performance + // we can skip one list_keys() call when signing and attaching a key + if ($can_sign) { +@@ -1422,4 +1453,58 @@ class enigma_engine + ), true, $abort); + } + } ++ ++ /** ++ * Import public keys from DNS according to Kolab Web-Of-Anti-Trust ++ * ++ * @param array $recipients List of email addresses ++ */ ++ protected function sync_keys($recipients) ++ { ++ $import = []; ++ $woat = $this->rc->config->get('enigma_woat'); ++ ++ if (empty($woat)) { ++ return; ++ } ++ ++ foreach ($recipients as $recipient) { ++ if (!strpos($recipient, '@')) { ++ continue; ++ } ++ ++ list($local, $domain) = explode('@', $recipient); ++ ++ // Do this for configured domains only ++ if (is_array($woat) && !in_array_nocase($domain, $woat)) { ++ continue; ++ } ++ ++ // remove parts behind a recipient delimiter ("jeroen+Trash" => "jeroen") ++ $local = preg_replace('/\+.*$/', '', $local); ++ ++ $fqdn = sha1($local) . '._woat.' . $domain; ++ ++ // Fetch the TXT record(s) ++ if (($records = dns_get_record($fqdn, DNS_TXT)) === false) { ++ continue; ++ } ++ ++ foreach ($records as $record) { ++ if (strpos($record['txt'], 'v=woat1,') === 0) { ++ $entry = explode('public_key=', $record['txt']); ++ if (count($entry) == 2) { ++ $import[] = $entry[1]; ++ // For now we support only one key ++ break; ++ } ++ } ++ } ++ } ++ ++ // Import the fetched keys ++ if (!empty($import)) { ++ $this->import_key(implode("\n", $import)); ++ } ++ } + } +diff --git a/program/lib/Roundcube/rcube_message.php b/program/lib/Roundcube/rcube_message.php +index 01b0e6dec..d5459bb50 100644 +--- a/program/lib/Roundcube/rcube_message.php ++++ b/program/lib/Roundcube/rcube_message.php +@@ -118,6 +118,11 @@ class rcube_message + false, false, true) + ); + ++ $this->mime = new rcube_mime($this->headers->charset); ++ $this->subject = str_replace("\n", '', $this->headers->get('subject')); ++ $from = $this->mime->decode_address_list($this->headers->from, 1); ++ $this->sender = current($from); ++ + if (!empty($this->headers->structure)) { + $this->get_mime_numbers($this->headers->structure); + $this->parse_structure($this->headers->structure); +@@ -126,11 +131,6 @@ class rcube_message + $this->body = $this->storage->get_body($uid); + } + +- $this->mime = new rcube_mime($this->headers->charset); +- $this->subject = str_replace("\n", '', $this->headers->get('subject')); +- $from = $this->mime->decode_address_list($this->headers->from, 1); +- $this->sender = current($from); +- + // notify plugins and let them analyze this structured message object + $this->app->plugins->exec_hook('message_load', array('object' => $this)); + } +-- +2.35.3 + diff --git a/docker/kolab/utils/patches/0003-PROXY-protocol-support.patch b/docker/kolab/utils/patches/0003-PROXY-protocol-support.patch new file mode 100644 index 00000000..913aba37 --- /dev/null +++ b/docker/kolab/utils/patches/0003-PROXY-protocol-support.patch @@ -0,0 +1,148 @@ +From f044424f99b8e33dbed1a00127bfcc5ad121d610 Mon Sep 17 00:00:00 2001 +From: Christian Mollekopf +Date: Thu, 14 Jul 2022 15:04:16 +0200 +Subject: [PATCH 3/3] PROXY protocol support + +--- + 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 20c1c6fd3..f705a47e8 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'] = array( + // 'ssl' => array( + // 'verify_peer' => true, + // 'verify_depth' => 3, + // 'cafile' => '/etc/openssl/certs/ca.crt', + // ), ++// 'proxy_protocol' => 1 | 2 | array ( // 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 9a398cdca..270be1201 100644 +--- a/program/lib/Roundcube/rcube_imap_generic.php ++++ b/program/lib/Roundcube/rcube_imap_generic.php +@@ -1035,6 +1035,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 d4273526b..3ad6422cb 100644 +--- a/program/lib/Roundcube/rcube_utils.php ++++ b/program/lib/Roundcube/rcube_utils.php +@@ -1458,4 +1458,83 @@ class rcube_utils + + return $temp_path; + } ++ ++ /** ++ * 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'] : self::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.35.3 +