Skip to content

Commit 32de405

Browse files
Merge pull request #438 from sisimai/437-lhost-deutschetelekom
Sisimai::Lhost::DeutscheTelekom
2 parents d15d639 + 9dbe75f commit 32de405

9 files changed

Lines changed: 354 additions & 5 deletions

File tree

lib/sisimai/lhost.rb

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,34 @@ def INDICATORS
3636
}
3737
end
3838

39+
# @abstract Banners defined in Smail 3 and Deutsche Telekom
40+
# @return [Array] Banner strings
41+
# @private
42+
def BannerDTAG
43+
return [
44+
# smail-3.2.0.108/src/
45+
# notify.c:61|static char *log_banner = "\
46+
# notify.c:62||------------------------- Message log follows: -------------------------|\n";
47+
# notify.c:63|static char *addr_error_banner = "\
48+
# notify.c:64||------------------------- Failed addresses follow: ---------------------|\n";
49+
# notify.c:65|static char *text_banner = "\
50+
# notify.c:66||------------------------- Message text follows: ------------------------|\n";
51+
"|------------------------- Message log follows: -------------------------|", # 0. Smail 3
52+
"|------------------------- Failed addresses follow: ---------------------|", # 1. Smail 3
53+
"|------------------------- Message text follows: ------------------------|", # 2. Smail 3
54+
"|------------------------- Message header follows: ----------------------|", # 3. Deutsche Telekom
55+
# "|----------- Message text follows: (body too large, truncated) ----------|", # 4. Deutsche Telekom
56+
]
57+
end
58+
3959
# @abstract MTA list
4060
# @return [Array] MTA list with order
4161
def index
4262
return %w[
43-
Activehunter AmazonSES ApacheJames Biglobe Courier Domino DragonFly EZweb EinsUndEins Exchange2003
44-
Exchange2007 Exim FML GMX GoogleWorkspace GoogleGroups Gmail IMailServer KDDI MailFoundry Mimecast
45-
MailMarshal MessagingServer Notes OpenSMTPD Postfix Sendmail TrendMicro V5sendmail Verizon
46-
X1 X2 X3 X6 Zoho MFILTER Qmail
63+
Activehunter AmazonSES ApacheJames Biglobe Courier Domino DeutscheTelekom DragonFly EZweb
64+
EinsUndEins Exchange2003 Exchange2007 Exim FML GMX GoogleWorkspace GoogleGroups Gmail
65+
IMailServer KDDI MailFoundry Mimecast MailMarshal MessagingServer Notes OpenSMTPD Postfix
66+
Sendmail TrendMicro V5sendmail Verizon X1 X2 X3 X6 Zoho MFILTER Qmail
4767
]
4868
end
4969

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
module Sisimai::Lhost
2+
# Sisimai::Lhost::DeutscheTelekom decodes a bounce email which created by Deutsche Telekom or
3+
# T-Online. Methods in the module are called from only Sisimai::Message.
4+
module DeutscheTelekom
5+
class << self
6+
require 'sisimai/lhost'
7+
8+
Indicators = Sisimai::Lhost.INDICATORS
9+
BannerDTAG = Sisimai::Lhost.BannerDTAG
10+
StartingOf = { message: [BannerDTAG[1]] }.freeze
11+
12+
# @abstract Decodes the bounce message from DeutscheTelekom
13+
# @param [Hash] mhead Message headers of a bounce email
14+
# @param [String] mbody Message body of a bounce email
15+
# @return [Hash] Bounce data list and message/rfc822 part
16+
# @return [Nil] it failed to decode or the arguments are missing
17+
def inquire(mhead, mbody)
18+
# - T-Online: https://www.t-online.de/, @t-online.de, @magenta.de
19+
# - DeutscheTelekom: https://www.telekom.com/
20+
# - Based on the bounce format of Smail 3, the original design model for Exim
21+
# - Tailored for Deutsche Telekom's internal Smail 3 fork with custom banners
22+
# - Module name follows the infrastructure owner for cross-language compatibility
23+
# - Smail 3: http://www.weird.com/~woods/projects/smail.html
24+
return nil unless BannerDTAG.any? { |a| mbody.include?(a) }
25+
26+
# smail-3.2.0.108/src/
27+
# notify.c:1052|(void) fprintf(f, "Subject: mail failed, %s\nReference: <%s@%s>\n\n",
28+
# notify.c:1053| subject_to, message_id, primary_name);
29+
#
30+
# T-Online specific headers
31+
# Received: from mailin42.aul.t-online.de (mailin42.aul.t-online.de [192.51.100.1])
32+
# by mailout11.t-online.de (Postfix) with SMTP id 05E5A1CAC0
33+
# From: Mail Delivery System <Mailer-Daemon@t-online.de>
34+
# X-TOI-MSGID: c9412855-531f-497b-b007-5ffc033877a0
35+
dscontents = [Sisimai::Lhost.DELIVERYSTATUS]; v = nil
36+
emailparts = Sisimai::RFC5322.part(mbody, [BannerDTAG[3], BannerDTAG[2]])
37+
bodyslices = emailparts[0].split("\n")
38+
messagelog = ''
39+
readcursor = 0 # (Integer) Points the current cursor position
40+
recipients = 0 # (Integer) The number of 'Final-Recipient' header
41+
42+
while e = bodyslices.shift do
43+
# Read error messages and delivery status lines from the head of the email to the previous
44+
# line of the beginning of the original message.
45+
if readcursor == 0
46+
# Beginning of the bounce message or delivery status part
47+
if e.start_with?(StartingOf[:message][0])
48+
# |------------------------- Failed addresses follow: ---------------------|
49+
readcursor |= Indicators[:deliverystatus]
50+
else
51+
# |------------------------- Message log follows: -------------------------|
52+
# The line above may appears only in Smail 3.
53+
#
54+
# smail-3.2.0.108/src/
55+
# models.c:787| if (deliver == NULL && defer == NULL) {
56+
# models.c:788| write_log(WRITE_LOG_MLOG, "no valid recipients were found for this message");
57+
# models.c:789| return_to_sender = TRUE;
58+
# models.c:879| }
59+
messagelog += ' ' + e if e != "" && e.include?(BannerDTAG[0]) == false
60+
end
61+
next
62+
end
63+
next if (readcursor & Indicators[:deliverystatus]) == 0 || e.empty?
64+
65+
# |------------------------- Failed addresses follow: ---------------------|
66+
# <example@t-online.de>
67+
# 552 5.2.2 <example@t-online.de> Quota exceeded (mailbox for user is full)
68+
#
69+
# |------------------------- Message header follows: ----------------------|
70+
# Received: from mail.fragdenstaat.de ([94.130.55.89]) by mailin41.mgt.mul.t-online.de.example.com
71+
# with (TLSv1.3:TLS_AES_256_GCM_SHA384 encrypted)
72+
# ...
73+
v = dscontents[-1]
74+
75+
76+
if e.start_with?(' <') && e.end_with?('>') && e.count(' ') == 1
77+
# Deutsche Telekom: The recipient address is enclosed in angle brackets.
78+
# |------------------------- Failed addresses follow: ---------------------|
79+
if v["recipient"] != ""
80+
# There are multiple recipient addresses in the message body.
81+
dscontents << Sisimai::Lhost.DELIVERYSTATUS
82+
v = dscontents[-1]
83+
end
84+
v['recipient'] = e[2, e.size]
85+
recipients += 1
86+
87+
elsif Sisimai::String.aligned(e, [' ', '@', '.', ' ... '])
88+
# Smail 3:
89+
# - The recipient address is not enclosed in angle brackets.
90+
# - Error message begins with " ... failed:"
91+
# smail-3.2.0.108/src/
92+
# notify.c:845| if (cur->error) {
93+
# notify.c:846| (void) fprintf(f, " %s ... failed: %s\n",
94+
# notify.c:847| cur->in_addr ? cur->in_addr : "(unknown)",
95+
# notify.c:848| cur->error->message);
96+
# notify.c:849| }
97+
# |------------------------- Failed addresses follow: ---------------------|
98+
# kijitora@neko.nyaan.example.com ... unknown host
99+
if v["recipient"] != ""
100+
# There are multiple recipient addresses in the message body.
101+
dscontents << Sisimai::Lhost.DELIVERYSTATUS
102+
v = dscontents[-1]
103+
end
104+
v['recipient'] = e[1, e.index(' ... ')]
105+
v['diagnosis'] = messagelog << ' ' + e
106+
recipients += 1
107+
108+
else
109+
# 552 5.2.2 <example@t-online.de> Quota exceeded (mailbox for user is full)
110+
v['diagnosis'] = e
111+
end
112+
end
113+
return nil if recipients == 0
114+
return { 'ds' => dscontents, 'rfc822' => emailparts[1] }
115+
end
116+
def description; return 'DeutscheTelekom'; end
117+
end
118+
end
119+
end
120+

lib/sisimai/lhost/exim.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ class << self
8989
def inquire(mhead, mbody)
9090
# Message-Id: <E1P1YNN-0003AD-Ga@example.org>
9191
# X-Failed-Recipients: kijitora@example.ed.jp
92+
return nil if Sisimai::Lhost.BannerDTAG.any? { |a| mbody.include?(a) }
93+
9294
thirdparty = false
9395
proceedsto = 0
9496
messageidv = mhead["message-id"] || ""

lib/sisimai/order.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,12 @@ class << self
1111
'Sisimai::Lhost::Sendmail',
1212
'Sisimai::Lhost::Exchange2007',
1313
'Sisimai::Lhost::Exchange2003',
14+
'Sisimai::Lhost::AmazonSES',
1415
'Sisimai::Lhost::TrendMicro',
16+
'Sisimai::Lhost::DeutscheTelekom',
1517
'Sisimai::Lhost::KDDI',
1618
'Sisimai::Lhost::FML',
1719
'Sisimai::Lhost::Verizon',
18-
'Sisimai::Lhost::AmazonSES',
1920
'Sisimai::Lhost::ApacheJames',
2021
'Sisimai::Lhost::X2',
2122
].freeze
@@ -83,8 +84,10 @@ class << self
8384
'Sisimai::Lhost::GMX',
8485
'Sisimai::Lhost::Zoho',
8586
'Sisimai::Lhost::EinsUndEins',
87+
'Sisimai::Lhost::DeutscheTelekom',
8688
],
8789
'mail-could' => ['Sisimai::Lhost::TrendMicro'],
90+
'mail-failed' => ['Sisimai::Lhost::DeutscheTelekom'],
8891
'mail-failure' => ['Sisimai::Lhost::Exim'],
8992
'mail-system' => ['Sisimai::Lhost::EZweb'],
9093
'message-delivery' => ['Sisimai::Lhost::MailFoundry'],
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
Received-SPF: None (no SPF record) identity=helo; client-ip=192.0.2.1; helo=mailout11.t-online.de; envelope-from=<>; receiver=mail.example.com
2+
Received: from mailout11.t-online.de (mailout11.t-online.de [192.0.2.1])
3+
by mail.example.com (Postfix) with ESMTPS id 2A29623F0
4+
for <returnpath@mail.example.com>; Wed, 03 Jun 2026 17:30:52 +0000 (UTC)
5+
Received: from mailin42.aul.t-online.de (mailin42.aul.t-online.de [192.51.100.1])
6+
by mailout11.t-online.de (Postfix) with SMTP id 05E5A1CAC0
7+
for <returnpath@mail.example.com>; Wed, 3 Jun 2026 19:23:42 +0200 (CEST)
8+
X-Failed-Recipients: example@t-online.de
9+
From: Mail Delivery System <Mailer-Daemon@t-online.de>
10+
To: returnpath@mail.example.com
11+
Subject: Mail delivery failed: returning message to sender
12+
Message-Id: <xxxyyyzzzaaabbccc.aul.t-online.de>
13+
Date: Wed, 3 Jun 2026 19:23:17 +0200
14+
X-TOI-MSGID: c9412855-531f-497b-b007-5ffc033877a0
15+
Authentication-Results: mailin42.aul.t-online.de; dkim=none;
16+
dkim-atps=neutral
17+
18+
|------------------------- Failed addresses follow: ---------------------|
19+
<example@t-online.de>
20+
552 5.2.2 <example@t-online.de> Quota exceeded (mailbox for user is full)
21+
22+
|------------------------- Message header follows: ----------------------|
23+
Received: from mail.example.com ([152.53.207.210]) by mailin42.t-online.de
24+
with (TLSv1.3:TLS_AES_256_GCM_SHA384 encrypted)
25+
esmtp id 1wUpJD-0ZWsnR0; Wed, 3 Jun 2026 19:23:15 +0200
26+
Received: by mail.example.com (Postfix)
27+
id AD20B29490; Tue, 02 Jun 2026 20:17:41 +0000 (UTC)
28+
Date: Tue, 02 Jun 2026 20:17:41 +0000
29+
From: some corp <somecorp@example.com>
30+
Reply-To: some corp <somecorp@example.com>
31+
To: example@t-online.de
32+
Message-ID: <outgoingmessageid>
33+
Subject: Subject omitted for privacy
34+
Mime-Version: 1.0
35+
Content-Type: multipart/mixed;
36+
boundary="--==_mimepart_6a1f3a659c89f_79afe920227275f";
37+
charset=UTF-8
38+
Content-Transfer-Encoding: 7bit
39+
40+
|----------- Message text follows: (body too large, truncated) ----------|
41+
42+
----==_mimepart_6a1f3a659c89f_79afe920227275f
43+
Content-Type: text/html;
44+
charset=UTF-8
45+
Content-Transfer-Encoding: quoted-printable
46+
47+
<!DOCTYPE html>
48+
<html>
49+
<head>
50+
<meta content=3D"text/html; charset=3Dutf-8" http-equiv=3D"Content-Type">=
51+
52+
<style>
53+
#footer {
54+
font-size: 11px;
55+
border-top: 1px solid #666;
56+
color: #333;
57+
margin-top: 1em;
58+
padding-top: 1em;
59+
}
60+
</style>
61+
62+
</head>
63+
<body>
64+
<p>Hello there, fellow some corp user!</p><p>I would like to inform you a=
65+
about the upcoming team event next we
66+
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
Received-SPF: None (no SPF record) identity=helo; client-ip=192.0.2.1; helo=mailout11.t-online.de; envelope-from=<>; receiver=mail.example.com
2+
Received: from mailout11.t-online.de (mailout11.t-online.de [192.0.2.1])
3+
by mail.example.com (Postfix) with ESMTPS id 2A29623F0
4+
for <returnpath@mail.example.com>; Wed, 03 Jun 2026 17:30:52 +0000 (UTC)
5+
Received: from mailin42.aul.t-online.de (mailin42.aul.t-online.de [192.51.100.1])
6+
by mailout11.t-online.de (Postfix) with SMTP id 05E5A1CAC0
7+
for <returnpath@mail.example.com>; Wed, 3 Jun 2026 19:23:42 +0200 (CEST)
8+
X-Failed-Recipients: example@t-online.de
9+
From: Mail Delivery System <Mailer-Daemon@t-online.de>
10+
To: returnpath@mail.example.com
11+
Subject: Mail delivery failed: returning message to sender
12+
Message-Id: <xxxyyyzzzaaabbccc.aul.t-online.de>
13+
Date: Wed, 3 Jun 2026 19:23:17 +0200
14+
X-TOI-MSGID: c9412855-531f-497b-b007-5ffc033877a0
15+
Authentication-Results: mailin42.aul.t-online.de; dkim=none;
16+
dkim-atps=neutral
17+
18+
|------------------------- Failed addresses follow: ---------------------|
19+
<example1@t-online.de>
20+
552 5.2.2 <example1@t-online.de> Quota exceeded (mailbox for user is full)
21+
<example2@t-online.de>
22+
550 5.1.1 <example2@t-online.de> unknown user
23+
24+
|------------------------- Message header follows: ----------------------|
25+
Received: from mail.example.com ([152.53.207.210]) by mailin42.t-online.de
26+
with (TLSv1.3:TLS_AES_256_GCM_SHA384 encrypted)
27+
esmtp id 1wUpJD-0ZWsnR0; Wed, 3 Jun 2026 19:23:15 +0200
28+
Received: by mail.example.com (Postfix)
29+
id AD20B29490; Tue, 02 Jun 2026 20:17:41 +0000 (UTC)
30+
Date: Tue, 02 Jun 2026 20:17:41 +0000
31+
From: some corp <somecorp@example.com>
32+
Reply-To: some corp <somecorp@example.com>
33+
To: <example1@t-online.de>, <example2@t-online.de>
34+
Message-ID: <outgoingmessageid>
35+
Subject: Subject omitted for privacy
36+
Mime-Version: 1.0
37+
Content-Type: multipart/mixed;
38+
boundary="--==_mimepart_6a1f3a659c89f_79afe920227275f";
39+
charset=UTF-8
40+
Content-Transfer-Encoding: 7bit
41+
42+
|----------- Message text follows: (body too large, truncated) ----------|
43+
44+
----==_mimepart_6a1f3a659c89f_79afe920227275f
45+
Content-Type: text/html;
46+
charset=UTF-8
47+
Content-Transfer-Encoding: quoted-printable
48+
49+
<!DOCTYPE html>
50+
<html>
51+
<head>
52+
<meta content=3D"text/html; charset=3Dutf-8" http-equiv=3D"Content-Type">=
53+
54+
<style>
55+
#footer {
56+
font-size: 11px;
57+
border-top: 1px solid #666;
58+
color: #333;
59+
margin-top: 1em;
60+
padding-top: 1em;
61+
}
62+
</style>
63+
64+
</head>
65+
<body>
66+
<p>Hello there, fellow some corp user!</p><p>I would like to inform you a=
67+
about the upcoming team event next we
68+
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
Return-Path: <kijitora-nyaan@example.org>
2+
Delivered-To: sironeko@example.jp
3+
Received: from example.com (unknown [192.0.2.250])
4+
by r1.example.co.jp (Postfix) with ESMTP id FFFFFFFF00
5+
for <sironeko@example.jp>; Thu, 29 Apr 2006 23:34:45 +0000 (UTC)
6+
Received: from r2.example.org (r2.example.org [192.0.2.25])
7+
by smtp.nyaan.example.net (8.10.1/8.10.1) with ESMTP id k3k000000000
8+
for <sironeko@nyaan.example.net>; Thu,29 Apr 2006 23:34:45 +0000
9+
Received: from [203.0.113.25] (helo=r9.example.net)
10+
by r2.example.org with esmtp (Exim 3.21 #1)
11+
id AAAAAA-000000-22
12+
for kijitora-nyaan@example.org; Thu, 29 Apr 2006 23:34:45 +0000
13+
Received: from smtp1.r.example.net ([209.196.123.3])
14+
by mx7.example.net with smtp (Exim 3.16 #10)
15+
id BBBBBB-111111-22
16+
for kijitora-nyaan@example.org; Thu, 29 Apr 2006 23:34:45 +0000
17+
Message-Id: <ffffffffffffffff1@smtp1.r.example.net>
18+
From: <MAILER-DAEMON@example.net>
19+
To: kijitora-nyaan@example.org
20+
Date: Thu, 29 Apr 2006 23:34:45 +0000 (UTC)
21+
Subject: mail failed, returning to sender
22+
23+
|------------------------- Message log follows: -------------------------|
24+
no valid recipients were found for this message
25+
|------------------------- Failed addresses follow: ---------------------|
26+
kijitora@neko.nyaan.example.com ... unknown host
27+
|------------------------- Message text follows: ------------------------|
28+
Received: from r2.example.org ([192.0.2.25])
29+
by mx1.example.net with esmtp (Exim 3.22 #2)
30+
id aaaaaa-444444-22
31+
for kijitora@neosoft.com; Thu, 29 Apr 2006 23:34:45 +0000
32+
Received: from localhost.localdomain ([127.0.0.1] helo=r2.example.org)
33+
by r2.example.org with esmtp (Exim 3.22 #2)
34+
id 000000-000000-00; Thu, 29 Apr 2006 23:34:45 +0000
35+
Received: from [203.0.113.1] (helo=[127.0.0.1])
36+
by r2.example.org with smtp (Exim 3.22 #2)
37+
id eeeeee-rrrrrr-22
38+
for neko-nyaan@example.org; Thu, 29 Apr 2006 23:34:45 +0000
39+
Message-Id: <ffffffffffff000000002222000000000@e3.example.com>
40+
To: kijitora@neko.nyaan.example.com
41+
From: Neko <sironeko@example.jp>
42+
Cc: neko-nyaan@example.org
43+
Mime-Version: 1.0
44+
Content-Type: text/plain; charset="us-ascii"; format=flowed
45+
Date: Thu, 29 Apr 2006 23:34:45 +0000
46+
Subject: Nyaan
47+
48+
Nyaan
49+
50+
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
module LhostEngineTest::Private
2+
module DeutscheTelekom
3+
IsExpected = {
4+
# INDEX => [['D.S.N.', 'replycode', 'REASON', 'hardbounce', 'toxic'], [...]]
5+
'1001' => [['5.2.2', '552', 'mailboxfull', false, -1]],
6+
}
7+
end
8+
end
9+

0 commit comments

Comments
 (0)