Skip to content

Commit 1a9b71d

Browse files
authored
Support Sendmail (#12)
* 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
1 parent 55123f8 commit 1a9b71d

File tree

3 files changed

+144
-28
lines changed

3 files changed

+144
-28
lines changed

README.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
# Roundcube TLS Icon
22

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

6-
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.
7+
In Postfix this can be enabled by
8+
setting [`smtpd_tls_received_header = yes`](https://www.postfix.org/postconf.5.html#smtpd_tls_received_header). Sendmail
9+
should work out of the box. Other MTAs have not been explicitly tested.
710

8-
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.
9-
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.
11+
Note that while this talks about "encryption", this does not imply security. An encrypted mail may still be insecure,
12+
mostly because mailservers generally use "opportunistic TLS", where MITM attacks are possible.
13+
This also only validates the last hop of an email - some emails may run through multiple hops and we don't know anything
14+
about the security of these.
1015

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

test/TlsIconTest.php

Lines changed: 127 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@ final class TlsIconTest extends TestCase
2424
/** @var string */
2525
private $strInternal = '<img class="lock_icon" src="plugins/tls_icon/blue_lock.svg" title="Mail was internal" />';
2626

27+
/** @var string */
28+
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" />';
29+
30+
/** @var string */
31+
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" />';
32+
33+
2734
public function testInstance()
2835
{
2936
$o = new tls_icon();
@@ -62,7 +69,7 @@ public function testMessageHeadersNoMatching()
6269
'value' => 'Sent to you',
6370
],
6471
],
65-
'headers' => (object) [
72+
'headers' => (object)[
6673
'others' => [
6774
'received' => 'my header',
6875
]
@@ -75,7 +82,7 @@ public function testMessageHeadersNoMatching()
7582
'html' => 1,
7683
],
7784
],
78-
'headers' => (object) [
85+
'headers' => (object)[
7986
'others' => [
8087
'received' => 'my header',
8188
]
@@ -92,7 +99,7 @@ public function testMessageHeadersTlsWithCipher()
9299
'value' => 'Sent to you',
93100
],
94101
],
95-
'headers' => (object) [
102+
'headers' => (object)[
96103
'others' => [
97104
'received' => 'from smtp.github.com (out-21.smtp.github.com [192.30.252.204])
98105
(using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested)
@@ -108,7 +115,7 @@ public function testMessageHeadersTlsWithCipher()
108115
'html' => 1,
109116
],
110117
],
111-
'headers' => (object) [
118+
'headers' => (object)[
112119
'others' => [
113120
'received' => 'from smtp.github.com (out-21.smtp.github.com [192.30.252.204])
114121
(using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested)
@@ -128,7 +135,7 @@ public function testMessageHeadersTls()
128135
'value' => 'Sent to you',
129136
],
130137
],
131-
'headers' => (object) [
138+
'headers' => (object)[
132139
'others' => [
133140
'received' => 'from smtp.github.com (out-21.smtp.github.com [192.30.252.204])
134141
(using TLSv1.2) (No client certificate requested)
@@ -144,7 +151,7 @@ public function testMessageHeadersTls()
144151
'html' => 1,
145152
],
146153
],
147-
'headers' => (object) [
154+
'headers' => (object)[
148155
'others' => [
149156
'received' => 'from smtp.github.com (out-21.smtp.github.com [192.30.252.204])
150157
(using TLSv1.2) (No client certificate requested)
@@ -164,7 +171,7 @@ public function testMessageHeadersInternal()
164171
'value' => 'Sent to you',
165172
],
166173
],
167-
'headers' => (object) [
174+
'headers' => (object)[
168175
'others' => [
169176
'received' => 'by aaa.bbb.ccc (Postfix, from userid 0)
170177
id A70248414D5; Sun, 26 Apr 2020 16:49:01 +0200 (CEST)',
@@ -178,7 +185,7 @@ public function testMessageHeadersInternal()
178185
'html' => 1,
179186
],
180187
],
181-
'headers' => (object) [
188+
'headers' => (object)[
182189
'others' => [
183190
'received' => 'by aaa.bbb.ccc (Postfix, from userid 0)
184191
id A70248414D5; Sun, 26 Apr 2020 16:49:01 +0200 (CEST)',
@@ -205,7 +212,7 @@ public function testMessageHeadersMultiFromWithConfig()
205212
'value' => 'Sent to you',
206213
],
207214
],
208-
'headers' => (object) [
215+
'headers' => (object)[
209216
'others' => [
210217
'received' => $inputHeaders,
211218
]
@@ -218,7 +225,7 @@ public function testMessageHeadersMultiFromWithConfig()
218225
'html' => 1,
219226
],
220227
],
221-
'headers' => (object) [
228+
'headers' => (object)[
222229
'others' => [
223230
'received' => $inputHeaders,
224231
]
@@ -244,7 +251,7 @@ public function testMessageHeadersMultiFromWithBadConfig()
244251
'value' => 'Sent to you',
245252
],
246253
],
247-
'headers' => (object) [
254+
'headers' => (object)[
248255
'others' => [
249256
'received' => $inputHeaders,
250257
]
@@ -257,11 +264,119 @@ public function testMessageHeadersMultiFromWithBadConfig()
257264
'html' => 1,
258265
],
259266
],
260-
'headers' => (object) [
267+
'headers' => (object)[
261268
'others' => [
262269
'received' => $inputHeaders,
263270
]
264271
]
265272
], $headersProcessed);
266273
}
274+
275+
public function testSendmailTLS13NoVerify()
276+
{
277+
$o = new tls_icon();
278+
$headersProcessed = $o->message_headers([
279+
'output' => [
280+
'subject' => [
281+
'value' => 'Sent to you',
282+
],
283+
],
284+
'headers' => (object)[
285+
'others' => [
286+
'received' => 'from 69-171-232-143.mail-mail.facebook.com (69-171-232-143.mail-mail.facebook.com [69.171.232.143])
287+
by mail.aegee.org (8.17.1/8.17.1) with ESMTPS id 2BI73F8b1489360
288+
(version=TLSv1.3 cipher=TLS_AES_256_GCM_SHA384 bits=256 verify=NO)
289+
for <my@address>; Sun, 18 Dec 2022 07:03:16 GMT',
290+
]
291+
]
292+
]);
293+
$this->assertEquals([
294+
'output' => [
295+
'subject' => [
296+
'value' => 'Sent to you' . $this->strSendmailCryptedTlsv13WithCipherNoVerify,
297+
'html' => 1,
298+
],
299+
],
300+
'headers' => (object)[
301+
'others' => [
302+
'received' => 'from 69-171-232-143.mail-mail.facebook.com (69-171-232-143.mail-mail.facebook.com [69.171.232.143])
303+
by mail.aegee.org (8.17.1/8.17.1) with ESMTPS id 2BI73F8b1489360
304+
(version=TLSv1.3 cipher=TLS_AES_256_GCM_SHA384 bits=256 verify=NO)
305+
for <my@address>; Sun, 18 Dec 2022 07:03:16 GMT',
306+
]
307+
]
308+
], $headersProcessed);
309+
}
310+
311+
public function testSendmailTLS12WithVerify()
312+
{
313+
$o = new tls_icon();
314+
$headersProcessed = $o->message_headers([
315+
'output' => [
316+
'subject' => [
317+
'value' => 'Sent to you',
318+
],
319+
],
320+
'headers' => (object)[
321+
'others' => [
322+
'received' => 'from smtp.github.com (out-18.smtp.github.com [192.30.252.201])
323+
by mail.aegee.org (8.17.1/8.17.1) with ESMTPS id 2BGMf4uY685293
324+
(version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=OK)
325+
for <my@address>; Fri, 16 Dec 2022 22:41:05 GMT',
326+
]
327+
]
328+
]);
329+
$this->assertEquals([
330+
'output' => [
331+
'subject' => [
332+
'value' => 'Sent to you' . $this->strSendmailCryptedTlsv12WithCipherVerify,
333+
'html' => 1,
334+
],
335+
],
336+
'headers' => (object)[
337+
'others' => [
338+
'received' => 'from smtp.github.com (out-18.smtp.github.com [192.30.252.201])
339+
by mail.aegee.org (8.17.1/8.17.1) with ESMTPS id 2BGMf4uY685293
340+
(version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=OK)
341+
for <my@address>; Fri, 16 Dec 2022 22:41:05 GMT',
342+
]
343+
]
344+
], $headersProcessed);
345+
}
346+
347+
public function testSendmailTLS13MultipleRecipients()
348+
{
349+
$o = new tls_icon();
350+
$headersProcessed = $o->message_headers([
351+
'output' => [
352+
'subject' => [
353+
'value' => 'Sent to you',
354+
],
355+
],
356+
'headers' => (object)[
357+
'others' => [
358+
'received' => 'from mout.kundenserver.de (mout.kundenserver.de [212.227.126.134])
359+
by mail.aegee.org (8.17.1/8.17.1) with ESMTPS id 2BLGrgYw3602565
360+
(version=TLSv1.3 cipher=TLS_AES_256_GCM_SHA384 bits=256 verify=NO);
361+
Wed, 21 Dec 2022 16:53:42 GMT',
362+
]
363+
]
364+
]);
365+
$this->assertEquals([
366+
'output' => [
367+
'subject' => [
368+
'value' => 'Sent to you' . $this->strSendmailCryptedTlsv13WithCipherNoVerify,
369+
'html' => 1,
370+
],
371+
],
372+
'headers' => (object)[
373+
'others' => [
374+
'received' => 'from mout.kundenserver.de (mout.kundenserver.de [212.227.126.134])
375+
by mail.aegee.org (8.17.1/8.17.1) with ESMTPS id 2BLGrgYw3602565
376+
(version=TLSv1.3 cipher=TLS_AES_256_GCM_SHA384 bits=256 verify=NO);
377+
Wed, 21 Dec 2022 16:53:42 GMT',
378+
]
379+
]
380+
], $headersProcessed);
381+
}
267382
}

tls_icon.php

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
class tls_icon extends rcube_plugin
44
{
5+
const POSTFIX_TLS_REGEX = "/\(using (TLS.*)\) \(/im";
6+
const POSTFIX_LOCAL_REGEX = "/\([a-zA-Z]*, from userid [0-9]*\)/im";
7+
const SENDMAIL_TLS_REGEX = "/\(version=(TLS.*)\)(\s+for|;)/im";
8+
59
private $message_headers_done = false;
610
private $icon_img;
711
private $rcmail;
@@ -55,19 +59,11 @@ public function message_headers($p)
5559
return $p;
5660
}
5761

58-
if (preg_match_all('/\(using TLS.*.*\) \(/im', $Received, $items, PREG_PATTERN_ORDER)) {
59-
$data = $items[0][0];
60-
61-
$needle = '(using ';
62-
$pos = strpos($data, $needle);
63-
$data = substr_replace($data, '', $pos, strlen($needle));
64-
65-
$needle = ') (';
66-
$pos = strrpos($data, $needle);
67-
$data = substr_replace($data, '', $pos, strlen($needle));
68-
62+
if (preg_match_all(tls_icon::POSTFIX_TLS_REGEX, $Received, $items, PREG_PATTERN_ORDER) ||
63+
preg_match_all(tls_icon::SENDMAIL_TLS_REGEX, $Received, $items, PREG_PATTERN_ORDER)) {
64+
$data = $items[1][0];
6965
$this->icon_img .= '<img class="lock_icon" src="plugins/tls_icon/lock.svg" title="' . htmlentities($data) . '" />';
70-
} elseif (preg_match_all('/\([a-zA-Z]*, from userid [0-9]*\)/im', $Received, $items, PREG_PATTERN_ORDER)) {
66+
} elseif (preg_match_all(tls_icon::POSTFIX_LOCAL_REGEX, $Received, $items, PREG_PATTERN_ORDER)) {
7167
$this->icon_img .= '<img class="lock_icon" src="plugins/tls_icon/blue_lock.svg" title="' . $this->gettext('internal') . '" />';
7268
} else {
7369
// TODO: Mails received from localhost but without TLS are currently flagged insecure

0 commit comments

Comments
 (0)