Skip to content

Commit

Permalink
Support Sendmail (#12)
Browse files Browse the repository at this point in the history
* Refactor Postfix pattern matching

* Preliminary support for Sendmail (fixes #11)

* Convert regexes into constants

* Update regex to support multiple recipients with Sendmail, add test

* Update README for Sendmail support
  • Loading branch information
GermanCoding authored Dec 27, 2022
1 parent 55123f8 commit 1a9b71d
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 28 deletions.
13 changes: 9 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
# Roundcube TLS Icon

Displays a small icon after the subject line that displays the (presumed) encryption state of received mails.
This plugin parses the "Received" header for the last hop and checks if TLS was used. This requires TLS logging in the receiving MTA.
This plugin parses the "Received" header for the last hop and checks if TLS was used. This requires TLS logging in the
receiving MTA.

In Postfix this can be enabled by setting [`smtpd_tls_received_header = yes`](https://www.postfix.org/postconf.5.html#smtpd_tls_received_header). The regex used to parse the header has only been tested against Postfix.
In Postfix this can be enabled by
setting [`smtpd_tls_received_header = yes`](https://www.postfix.org/postconf.5.html#smtpd_tls_received_header). Sendmail
should work out of the box. Other MTAs have not been explicitly tested.

Note that while this talks about "encryption", this does not imply security. An encrypted mail may still be insecure, mostly because mailservers generally use "opportunistic TLS", where MITM attacks are possible.
This also only validates the last hop of an email - some emails may run through multiple hops and we don't know anything about the security of these.
Note that while this talks about "encryption", this does not imply security. An encrypted mail may still be insecure,
mostly because mailservers generally use "opportunistic TLS", where MITM attacks are possible.
This also only validates the last hop of an email - some emails may run through multiple hops and we don't know anything
about the security of these.

Inspired by [roundcube-easy-unsubscribe](https://github.com/SS88UK/roundcube-easy-unsubscribe)

Expand Down
139 changes: 127 additions & 12 deletions test/TlsIconTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ final class TlsIconTest extends TestCase
/** @var string */
private $strInternal = '<img class="lock_icon" src="plugins/tls_icon/blue_lock.svg" title="Mail was internal" />';

/** @var string */
private $strSendmailCryptedTlsv13WithCipherNoVerify = '<img class="lock_icon" src="plugins/tls_icon/lock.svg" title="TLSv1.3 cipher=TLS_AES_256_GCM_SHA384 bits=256 verify=NO" />';

/** @var string */
private $strSendmailCryptedTlsv12WithCipherVerify = '<img class="lock_icon" src="plugins/tls_icon/lock.svg" title="TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=OK" />';


public function testInstance()
{
$o = new tls_icon();
Expand Down Expand Up @@ -62,7 +69,7 @@ public function testMessageHeadersNoMatching()
'value' => 'Sent to you',
],
],
'headers' => (object) [
'headers' => (object)[
'others' => [
'received' => 'my header',
]
Expand All @@ -75,7 +82,7 @@ public function testMessageHeadersNoMatching()
'html' => 1,
],
],
'headers' => (object) [
'headers' => (object)[
'others' => [
'received' => 'my header',
]
Expand All @@ -92,7 +99,7 @@ public function testMessageHeadersTlsWithCipher()
'value' => 'Sent to you',
],
],
'headers' => (object) [
'headers' => (object)[
'others' => [
'received' => 'from smtp.github.com (out-21.smtp.github.com [192.30.252.204])
(using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested)
Expand All @@ -108,7 +115,7 @@ public function testMessageHeadersTlsWithCipher()
'html' => 1,
],
],
'headers' => (object) [
'headers' => (object)[
'others' => [
'received' => 'from smtp.github.com (out-21.smtp.github.com [192.30.252.204])
(using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested)
Expand All @@ -128,7 +135,7 @@ public function testMessageHeadersTls()
'value' => 'Sent to you',
],
],
'headers' => (object) [
'headers' => (object)[
'others' => [
'received' => 'from smtp.github.com (out-21.smtp.github.com [192.30.252.204])
(using TLSv1.2) (No client certificate requested)
Expand All @@ -144,7 +151,7 @@ public function testMessageHeadersTls()
'html' => 1,
],
],
'headers' => (object) [
'headers' => (object)[
'others' => [
'received' => 'from smtp.github.com (out-21.smtp.github.com [192.30.252.204])
(using TLSv1.2) (No client certificate requested)
Expand All @@ -164,7 +171,7 @@ public function testMessageHeadersInternal()
'value' => 'Sent to you',
],
],
'headers' => (object) [
'headers' => (object)[
'others' => [
'received' => 'by aaa.bbb.ccc (Postfix, from userid 0)
id A70248414D5; Sun, 26 Apr 2020 16:49:01 +0200 (CEST)',
Expand All @@ -178,7 +185,7 @@ public function testMessageHeadersInternal()
'html' => 1,
],
],
'headers' => (object) [
'headers' => (object)[
'others' => [
'received' => 'by aaa.bbb.ccc (Postfix, from userid 0)
id A70248414D5; Sun, 26 Apr 2020 16:49:01 +0200 (CEST)',
Expand All @@ -205,7 +212,7 @@ public function testMessageHeadersMultiFromWithConfig()
'value' => 'Sent to you',
],
],
'headers' => (object) [
'headers' => (object)[
'others' => [
'received' => $inputHeaders,
]
Expand All @@ -218,7 +225,7 @@ public function testMessageHeadersMultiFromWithConfig()
'html' => 1,
],
],
'headers' => (object) [
'headers' => (object)[
'others' => [
'received' => $inputHeaders,
]
Expand All @@ -244,7 +251,7 @@ public function testMessageHeadersMultiFromWithBadConfig()
'value' => 'Sent to you',
],
],
'headers' => (object) [
'headers' => (object)[
'others' => [
'received' => $inputHeaders,
]
Expand All @@ -257,11 +264,119 @@ public function testMessageHeadersMultiFromWithBadConfig()
'html' => 1,
],
],
'headers' => (object) [
'headers' => (object)[
'others' => [
'received' => $inputHeaders,
]
]
], $headersProcessed);
}

public function testSendmailTLS13NoVerify()
{
$o = new tls_icon();
$headersProcessed = $o->message_headers([
'output' => [
'subject' => [
'value' => 'Sent to you',
],
],
'headers' => (object)[
'others' => [
'received' => 'from 69-171-232-143.mail-mail.facebook.com (69-171-232-143.mail-mail.facebook.com [69.171.232.143])
by mail.aegee.org (8.17.1/8.17.1) with ESMTPS id 2BI73F8b1489360
(version=TLSv1.3 cipher=TLS_AES_256_GCM_SHA384 bits=256 verify=NO)
for <my@address>; Sun, 18 Dec 2022 07:03:16 GMT',
]
]
]);
$this->assertEquals([
'output' => [
'subject' => [
'value' => 'Sent to you' . $this->strSendmailCryptedTlsv13WithCipherNoVerify,
'html' => 1,
],
],
'headers' => (object)[
'others' => [
'received' => 'from 69-171-232-143.mail-mail.facebook.com (69-171-232-143.mail-mail.facebook.com [69.171.232.143])
by mail.aegee.org (8.17.1/8.17.1) with ESMTPS id 2BI73F8b1489360
(version=TLSv1.3 cipher=TLS_AES_256_GCM_SHA384 bits=256 verify=NO)
for <my@address>; Sun, 18 Dec 2022 07:03:16 GMT',
]
]
], $headersProcessed);
}

public function testSendmailTLS12WithVerify()
{
$o = new tls_icon();
$headersProcessed = $o->message_headers([
'output' => [
'subject' => [
'value' => 'Sent to you',
],
],
'headers' => (object)[
'others' => [
'received' => 'from smtp.github.com (out-18.smtp.github.com [192.30.252.201])
by mail.aegee.org (8.17.1/8.17.1) with ESMTPS id 2BGMf4uY685293
(version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=OK)
for <my@address>; Fri, 16 Dec 2022 22:41:05 GMT',
]
]
]);
$this->assertEquals([
'output' => [
'subject' => [
'value' => 'Sent to you' . $this->strSendmailCryptedTlsv12WithCipherVerify,
'html' => 1,
],
],
'headers' => (object)[
'others' => [
'received' => 'from smtp.github.com (out-18.smtp.github.com [192.30.252.201])
by mail.aegee.org (8.17.1/8.17.1) with ESMTPS id 2BGMf4uY685293
(version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=OK)
for <my@address>; Fri, 16 Dec 2022 22:41:05 GMT',
]
]
], $headersProcessed);
}

public function testSendmailTLS13MultipleRecipients()
{
$o = new tls_icon();
$headersProcessed = $o->message_headers([
'output' => [
'subject' => [
'value' => 'Sent to you',
],
],
'headers' => (object)[
'others' => [
'received' => 'from mout.kundenserver.de (mout.kundenserver.de [212.227.126.134])
by mail.aegee.org (8.17.1/8.17.1) with ESMTPS id 2BLGrgYw3602565
(version=TLSv1.3 cipher=TLS_AES_256_GCM_SHA384 bits=256 verify=NO);
Wed, 21 Dec 2022 16:53:42 GMT',
]
]
]);
$this->assertEquals([
'output' => [
'subject' => [
'value' => 'Sent to you' . $this->strSendmailCryptedTlsv13WithCipherNoVerify,
'html' => 1,
],
],
'headers' => (object)[
'others' => [
'received' => 'from mout.kundenserver.de (mout.kundenserver.de [212.227.126.134])
by mail.aegee.org (8.17.1/8.17.1) with ESMTPS id 2BLGrgYw3602565
(version=TLSv1.3 cipher=TLS_AES_256_GCM_SHA384 bits=256 verify=NO);
Wed, 21 Dec 2022 16:53:42 GMT',
]
]
], $headersProcessed);
}
}
20 changes: 8 additions & 12 deletions tls_icon.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

class tls_icon extends rcube_plugin
{
const POSTFIX_TLS_REGEX = "/\(using (TLS.*)\) \(/im";
const POSTFIX_LOCAL_REGEX = "/\([a-zA-Z]*, from userid [0-9]*\)/im";
const SENDMAIL_TLS_REGEX = "/\(version=(TLS.*)\)(\s+for|;)/im";

private $message_headers_done = false;
private $icon_img;
private $rcmail;
Expand Down Expand Up @@ -55,19 +59,11 @@ public function message_headers($p)
return $p;
}

if (preg_match_all('/\(using TLS.*.*\) \(/im', $Received, $items, PREG_PATTERN_ORDER)) {
$data = $items[0][0];

$needle = '(using ';
$pos = strpos($data, $needle);
$data = substr_replace($data, '', $pos, strlen($needle));

$needle = ') (';
$pos = strrpos($data, $needle);
$data = substr_replace($data, '', $pos, strlen($needle));

if (preg_match_all(tls_icon::POSTFIX_TLS_REGEX, $Received, $items, PREG_PATTERN_ORDER) ||
preg_match_all(tls_icon::SENDMAIL_TLS_REGEX, $Received, $items, PREG_PATTERN_ORDER)) {
$data = $items[1][0];
$this->icon_img .= '<img class="lock_icon" src="plugins/tls_icon/lock.svg" title="' . htmlentities($data) . '" />';
} elseif (preg_match_all('/\([a-zA-Z]*, from userid [0-9]*\)/im', $Received, $items, PREG_PATTERN_ORDER)) {
} elseif (preg_match_all(tls_icon::POSTFIX_LOCAL_REGEX, $Received, $items, PREG_PATTERN_ORDER)) {
$this->icon_img .= '<img class="lock_icon" src="plugins/tls_icon/blue_lock.svg" title="' . $this->gettext('internal') . '" />';
} else {
// TODO: Mails received from localhost but without TLS are currently flagged insecure
Expand Down

0 comments on commit 1a9b71d

Please sign in to comment.