sisimai 5.6.0 → 5.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/rake-test.yml +0 -4
- data/ChangeLog.md +47 -0
- data/README-JA.md +19 -16
- data/README.md +23 -21
- data/lib/sisimai/address.rb +8 -31
- data/lib/sisimai/arf.rb +5 -6
- data/lib/sisimai/eb.rb +116 -0
- data/lib/sisimai/fact.rb +33 -51
- data/lib/sisimai/lda.rb +20 -18
- data/lib/sisimai/lhost/activehunter.rb +0 -2
- data/lib/sisimai/lhost/amazonses.rb +17 -18
- data/lib/sisimai/lhost/apachejames.rb +0 -1
- data/lib/sisimai/lhost/biglobe.rb +0 -16
- data/lib/sisimai/lhost/courier.rb +9 -7
- data/lib/sisimai/lhost/deutschetelekom.rb +120 -0
- data/lib/sisimai/lhost/domino.rb +2 -4
- data/lib/sisimai/lhost/dragonfly.rb +0 -27
- data/lib/sisimai/lhost/einsundeins.rb +4 -12
- data/lib/sisimai/lhost/exchange2003.rb +12 -11
- data/lib/sisimai/lhost/exchange2007.rb +17 -17
- data/lib/sisimai/lhost/exim.rb +34 -81
- data/lib/sisimai/lhost/ezweb.rb +15 -51
- data/lib/sisimai/lhost/fml.rb +8 -32
- data/lib/sisimai/lhost/gmail.rb +10 -32
- data/lib/sisimai/lhost/gmx.rb +7 -24
- data/lib/sisimai/lhost/googlegroups.rb +6 -5
- data/lib/sisimai/lhost/googleworkspace.rb +2 -5
- data/lib/sisimai/lhost/imailserver.rb +4 -9
- data/lib/sisimai/lhost/kddi.rb +8 -21
- data/lib/sisimai/lhost/mailfoundry.rb +0 -2
- data/lib/sisimai/lhost/mailmarshal.rb +1 -3
- data/lib/sisimai/lhost/messagingserver.rb +4 -15
- data/lib/sisimai/lhost/mfilter.rb +0 -1
- data/lib/sisimai/lhost/mimecast.rb +0 -1
- data/lib/sisimai/lhost/notes.rb +3 -3
- data/lib/sisimai/lhost/opensmtpd.rb +0 -40
- data/lib/sisimai/lhost/postfix.rb +14 -14
- data/lib/sisimai/lhost/qmail.rb +23 -89
- data/lib/sisimai/lhost/sendmail.rb +8 -7
- data/lib/sisimai/lhost/trendmicro.rb +5 -4
- data/lib/sisimai/lhost/v5sendmail.rb +2 -2
- data/lib/sisimai/lhost/verizon.rb +4 -4
- data/lib/sisimai/lhost/x1.rb +1 -2
- data/lib/sisimai/lhost/x2.rb +0 -2
- data/lib/sisimai/lhost/x3.rb +4 -9
- data/lib/sisimai/lhost/x6.rb +0 -1
- data/lib/sisimai/lhost/zoho.rb +0 -12
- data/lib/sisimai/lhost.rb +38 -19
- data/lib/sisimai/message.rb +1 -1
- data/lib/sisimai/order.rb +4 -1
- data/lib/sisimai/reason/authfailure.rb +8 -7
- data/lib/sisimai/reason/badreputation.rb +5 -4
- data/lib/sisimai/reason/blocked.rb +6 -5
- data/lib/sisimai/reason/contenterror.rb +8 -5
- data/lib/sisimai/reason/delivered.rb +3 -2
- data/lib/sisimai/reason/emailtoolarge.rb +7 -6
- data/lib/sisimai/reason/expired.rb +16 -4
- data/lib/sisimai/reason/failedstarttls.rb +5 -3
- data/lib/sisimai/reason/feedback.rb +2 -1
- data/lib/sisimai/reason/filtered.rb +9 -8
- data/lib/sisimai/reason/hasmoved.rb +5 -4
- data/lib/sisimai/reason/hostunknown.rb +14 -4
- data/lib/sisimai/reason/mailboxfull.rb +10 -8
- data/lib/sisimai/reason/mailererror.rb +3 -2
- data/lib/sisimai/reason/networkerror.rb +16 -3
- data/lib/sisimai/reason/norelaying.rb +8 -6
- data/lib/sisimai/reason/notaccept.rb +15 -7
- data/lib/sisimai/reason/notcompliantrfc.rb +5 -3
- data/lib/sisimai/reason/onhold.rb +8 -7
- data/lib/sisimai/reason/policyviolation.rb +11 -5
- data/lib/sisimai/reason/ratelimited.rb +5 -4
- data/lib/sisimai/reason/rejected.rb +24 -14
- data/lib/sisimai/reason/requireptr.rb +5 -4
- data/lib/sisimai/reason/securityerror.rb +5 -3
- data/lib/sisimai/reason/spamdetected.rb +6 -5
- data/lib/sisimai/reason/suppressed.rb +4 -3
- data/lib/sisimai/reason/suspend.rb +8 -3
- data/lib/sisimai/reason/syntaxerror.rb +5 -11
- data/lib/sisimai/reason/systemerror.rb +21 -2
- data/lib/sisimai/reason/systemfull.rb +4 -3
- data/lib/sisimai/reason/undefined.rb +2 -1
- data/lib/sisimai/reason/userunknown.rb +15 -9
- data/lib/sisimai/reason/vacation.rb +2 -1
- data/lib/sisimai/reason/virusdetected.rb +6 -5
- data/lib/sisimai/reason.rb +47 -42
- data/lib/sisimai/rfc1123.rb +1 -1
- data/lib/sisimai/rfc1894.rb +15 -8
- data/lib/sisimai/rfc2045.rb +8 -5
- data/lib/sisimai/rfc3464/thirdparty.rb +11 -10
- data/lib/sisimai/rfc3464.rb +10 -14
- data/lib/sisimai/rfc3834.rb +6 -5
- data/lib/sisimai/rfc791.rb +3 -38
- data/lib/sisimai/rhost/aol.rb +3 -2
- data/lib/sisimai/rhost/apple.rb +12 -11
- data/lib/sisimai/rhost/cloudflare.rb +3 -2
- data/lib/sisimai/rhost/cox.rb +38 -37
- data/lib/sisimai/rhost/facebook.rb +17 -16
- data/lib/sisimai/rhost/franceptt.rb +39 -38
- data/lib/sisimai/rhost/godaddy.rb +35 -34
- data/lib/sisimai/rhost/google.rb +22 -21
- data/lib/sisimai/rhost/gsuite.rb +8 -7
- data/lib/sisimai/rhost/iua.rb +11 -10
- data/lib/sisimai/rhost/kddi.rb +3 -2
- data/lib/sisimai/rhost/messagelabs.rb +13 -12
- data/lib/sisimai/rhost/microsoft.rb +37 -32
- data/lib/sisimai/rhost/mimecast.rb +18 -17
- data/lib/sisimai/rhost/nttdocomo.rb +10 -9
- data/lib/sisimai/rhost/outlook.rb +3 -2
- data/lib/sisimai/rhost/spectrum.rb +19 -18
- data/lib/sisimai/rhost/tencent.rb +10 -9
- data/lib/sisimai/rhost/yahooinc.rb +11 -10
- data/lib/sisimai/rhost/zoho.rb +8 -7
- data/lib/sisimai/rhost.rb +1 -1
- data/lib/sisimai/smtp/command.rb +13 -8
- data/lib/sisimai/smtp/failure.rb +9 -8
- data/lib/sisimai/smtp/reply.rb +23 -22
- data/lib/sisimai/smtp/status.rb +164 -159
- data/lib/sisimai/smtp/transcript.rb +3 -2
- data/lib/sisimai/string.rb +0 -12
- data/lib/sisimai/version.rb +1 -1
- data/lib/sisimai.rb +2 -1
- data/set-of-emails/maildir/bsd/lhost-deutschetelekom-01.eml +66 -0
- data/set-of-emails/maildir/bsd/lhost-deutschetelekom-02.eml +68 -0
- data/set-of-emails/maildir/bsd/lhost-deutschetelekom-03.eml +50 -0
- data/set-of-emails/maildir/bsd/lhost-postfix-81.eml +130 -0
- data/set-of-emails/should-not-crash/go-423-deeply-nested-mime-parts.eml +20050 -0
- data/set-of-emails/should-not-crash/p5-664-iomart-mail-filter.eml +258 -0
- data/set-of-emails/to-be-debugged-because/sisimai-cannot-parse-yet/.gitkeep +0 -0
- metadata +11 -2
data/lib/sisimai/fact.rb
CHANGED
|
@@ -15,11 +15,13 @@ module Sisimai
|
|
|
15
15
|
require 'sisimai/string'
|
|
16
16
|
require 'sisimai/rhost'
|
|
17
17
|
require 'sisimai/lda'
|
|
18
|
+
require 'sisimai/eb'
|
|
18
19
|
|
|
19
20
|
@@rwaccessors = [
|
|
20
21
|
:action, # [String] The value of Action: header
|
|
21
22
|
:addresser, # [Sisimai::Address] From address
|
|
22
23
|
:alias, # [String] Alias of the recipient address
|
|
24
|
+
:bogus, # [Integer] EXPERIMENTAL
|
|
23
25
|
:catch, # [?] Results generated by hook method
|
|
24
26
|
:command, # [String] The last SMTP command
|
|
25
27
|
:decodedby, # [String] MTA module name since v5.2.0
|
|
@@ -43,7 +45,7 @@ module Sisimai
|
|
|
43
45
|
:timestamp, # [Sisimai::Time] Date: header in the original message
|
|
44
46
|
:timezoneoffset, # [Integer] Time zone offset(seconds)
|
|
45
47
|
:token, # [String] Message token/MD5 Hex digest value
|
|
46
|
-
:toxic, # [
|
|
48
|
+
:toxic, # [Integer] EXPERIMENTAL
|
|
47
49
|
]
|
|
48
50
|
attr_accessor(*@@rwaccessors)
|
|
49
51
|
|
|
@@ -71,6 +73,7 @@ module Sisimai
|
|
|
71
73
|
@alias = argvs['alias'] || ''
|
|
72
74
|
@addresser = argvs['addresser']
|
|
73
75
|
@action = argvs['action']
|
|
76
|
+
@bogus = argvs['bogus']
|
|
74
77
|
@catch = argvs['catch']
|
|
75
78
|
@command = argvs['command']
|
|
76
79
|
@decodedby = argvs['decodedby']
|
|
@@ -120,7 +123,7 @@ module Sisimai
|
|
|
120
123
|
while e = deliveries.shift do
|
|
121
124
|
# Create parameters for each Sisimai::Fact object
|
|
122
125
|
next if e['recipient'].size < 5
|
|
123
|
-
next if ! argvs[:vacation] && e['reason'] ==
|
|
126
|
+
next if ! argvs[:vacation] && e['reason'] == Sisimai::Eb::ReAWAY
|
|
124
127
|
next if ! argvs[:delivered] && e['status'].start_with?('2.')
|
|
125
128
|
|
|
126
129
|
thing = {} # To be passed to each accessor of Sisimai::Fact
|
|
@@ -141,7 +144,6 @@ module Sisimai
|
|
|
141
144
|
"replycode" => e["replycode"],
|
|
142
145
|
"rhost" => e["rhost"],
|
|
143
146
|
"decodedby" => e["agent"],
|
|
144
|
-
"toxic" => e["toxic"],
|
|
145
147
|
}
|
|
146
148
|
|
|
147
149
|
# EMAILADDRESS: Detect an email address from message/rfc822 part
|
|
@@ -344,7 +346,7 @@ module Sisimai
|
|
|
344
346
|
p1 = dc.index('<html>')
|
|
345
347
|
p2 = dc.index('</html>')
|
|
346
348
|
piece['diagnosticcode'][p1, p2 + 7 - p1] = '' if p1 && p2
|
|
347
|
-
piece['diagnosticcode'] =
|
|
349
|
+
piece['diagnosticcode'] = piece['diagnosticcode'].split.join(" ")
|
|
348
350
|
end
|
|
349
351
|
|
|
350
352
|
if Sisimai::String.is_8bit(piece['diagnosticcode'])
|
|
@@ -352,13 +354,17 @@ module Sisimai
|
|
|
352
354
|
piece['diagnosticcode'] = piece['diagnosticcode'].force_encoding('UTF-8').scrub('?')
|
|
353
355
|
end
|
|
354
356
|
|
|
355
|
-
piece["diagnostictype"] = "X-UNIX" if piece["reason"] ==
|
|
357
|
+
piece["diagnostictype"] = "X-UNIX" if piece["reason"] == Sisimai::Eb::ReUNIX
|
|
356
358
|
if piece["diagnostictype"].empty?
|
|
357
359
|
piece["diagnostictype"] = "SMTP" if %w[feedback vacation].include?(piece["reason"]) == false
|
|
358
360
|
end
|
|
359
361
|
|
|
360
|
-
#
|
|
362
|
+
# When "RCPT first" in the error message, set "RCPT" as the last command.
|
|
363
|
+
# - <<< 503 RCPT first (#5.5.1)
|
|
364
|
+
# - <<< 503-5.5.1 RCPT first. A mail transaction protocol command was issued ...
|
|
365
|
+
# - RCPT first (in reply to DATA command)
|
|
361
366
|
piece['command'] = '' if Sisimai::SMTP::Command.test(piece['command']) == false
|
|
367
|
+
piece['command'] = Sisimai::Eb::CeRCPT if piece['diagnosticcode'].include?('RCPT first')
|
|
362
368
|
|
|
363
369
|
# Create parameters for the constructor
|
|
364
370
|
as = Sisimai::Address.new(piece['addresser']) || next; next if as.void
|
|
@@ -379,13 +385,14 @@ module Sisimai
|
|
|
379
385
|
ea.each { |q| thing[q] = piece[q] if thing[q].nil? || thing[q].empty? }
|
|
380
386
|
|
|
381
387
|
# Other accessors
|
|
388
|
+
thing['bogus'] = -1
|
|
382
389
|
thing['catch'] = piece['catch'] || nil
|
|
383
390
|
thing["feedbackid"] = ""
|
|
384
391
|
thing['hardbounce'] = piece['hardbounce']
|
|
385
|
-
thing['toxic'] = piece['toxic']
|
|
386
392
|
thing['replycode'] = Sisimai::SMTP::Reply.find(piece['diagnosticcode']) if thing['replycode'].empty?
|
|
387
393
|
thing['timestamp'] = TimeModule.parse(::Time.at(piece['timestamp']).to_s)
|
|
388
394
|
thing['timezoneoffset'] = piece['timezoneoffset'] || '+0000'
|
|
395
|
+
thing['toxic'] = -1
|
|
389
396
|
ea.each { |q| thing[q] = piece[q] if thing[q].empty? }
|
|
390
397
|
|
|
391
398
|
# ALIAS
|
|
@@ -416,25 +423,30 @@ module Sisimai
|
|
|
416
423
|
while true
|
|
417
424
|
if thing["reason"].empty? || RetryIndex[thing["reason"]]
|
|
418
425
|
# The value of "reason" is empty or is needed to check with other values again
|
|
419
|
-
re = thing["reason"].empty? ?
|
|
426
|
+
re = thing["reason"].empty? ? Sisimai::Eb::Re___0 : thing["reason"]
|
|
420
427
|
cr = Sisimai::LDA.find(thing); if Sisimai::Reason.is_explicit(cr) then thing["reason"] = cr; break; end
|
|
421
428
|
cr = Sisimai::Rhost.find(thing); if Sisimai::Reason.is_explicit(cr) then thing["reason"] = cr; break; end
|
|
422
429
|
cr = Sisimai::Reason.find(thing); if Sisimai::Reason.is_explicit(cr) then thing["reason"] = cr; break; end
|
|
423
|
-
thing["reason"] = thing["diagnosticcode"].size > 0 ?
|
|
430
|
+
thing["reason"] = thing["diagnosticcode"].size > 0 ? Sisimai::Eb::Re___1 : re
|
|
424
431
|
break
|
|
425
432
|
end
|
|
426
433
|
break
|
|
427
434
|
end
|
|
428
435
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
thing['
|
|
433
|
-
|
|
434
|
-
|
|
436
|
+
while true
|
|
437
|
+
# HARDBOUNCE: Set the value of "hardbounce", default value of "bouncebounce" is false
|
|
438
|
+
break if thing['reason'] == Sisimai::Eb::ReSENT
|
|
439
|
+
if thing['reason'] == Sisimai::Eb::ReFEED || thing['reason'] == Sisimai::Eb::ReAWAY
|
|
440
|
+
# Delete the value of ReplyCode when the Reason is "Feedback" or "Vacation"
|
|
441
|
+
thing['replycode'] = ""
|
|
442
|
+
break
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
# The reason is not "Delivered", or "Feedback", or "Vacation"
|
|
435
446
|
smtperrors = "#{piece['deliverystatus']} #{piece['diagnosticcode']}"
|
|
436
447
|
smtperrors = '' if smtperrors.size < 4
|
|
437
448
|
thing['hardbounce'] = Sisimai::SMTP::Failure.is_hardbounce(thing['reason'], smtperrors)
|
|
449
|
+
break
|
|
438
450
|
end
|
|
439
451
|
|
|
440
452
|
# DELIVERYSTATUS: Set a pseudo status code if the value of "deliverystatus" is empty
|
|
@@ -466,10 +478,10 @@ module Sisimai
|
|
|
466
478
|
thing['action'] = ox[2]
|
|
467
479
|
end
|
|
468
480
|
end
|
|
469
|
-
thing["action"] = ""
|
|
470
|
-
thing["action"] =
|
|
471
|
-
thing["action"] =
|
|
472
|
-
thing["action"] =
|
|
481
|
+
thing["action"] = "" if thing["action"].nil?
|
|
482
|
+
thing["action"] = Sisimai::Eb::AeSENT if thing["action"].empty? && thing["reason"] == Sisimai::Eb::ReSENT
|
|
483
|
+
thing["action"] = Sisimai::Eb::AeSTAY if thing["action"].empty? && thing["reason"] == Sisimai::Eb::ReTIME
|
|
484
|
+
thing["action"] = Sisimai::Eb::AeFAIL if thing["action"].empty? && cx[0] == "4" || cx[0] == "5"
|
|
473
485
|
|
|
474
486
|
if thing["replycode"].size > 0
|
|
475
487
|
# Fill empty values: ["SMTP Command", "DSN", "Reason"]
|
|
@@ -483,44 +495,13 @@ module Sisimai
|
|
|
483
495
|
|
|
484
496
|
# Feedback-ID: 1.us-west-2.QHuyeCQrGtIIMGKQfVdUhP9hCQR2LglVOrRamBc+Prk=:AmazonSES
|
|
485
497
|
thing["feedbackid"] = rfc822data["feedback-id"] || ""
|
|
486
|
-
thing["
|
|
498
|
+
thing["reason"] = thing["reason"].downcase
|
|
487
499
|
|
|
488
500
|
listoffact << Sisimai::Fact.new(thing)
|
|
489
501
|
end
|
|
490
502
|
return listoffact
|
|
491
503
|
end
|
|
492
504
|
|
|
493
|
-
def self.is_toxic(thing = nil)
|
|
494
|
-
return false unless thing
|
|
495
|
-
cr = thing['reason'] || 'undefined'
|
|
496
|
-
cv = thing['replycode'] || ''
|
|
497
|
-
cw = thing['deliverystatus'] || ''
|
|
498
|
-
|
|
499
|
-
# 1. Hard bounces or some soft bounces with a permanent error.
|
|
500
|
-
# 1-1. Hard bounce: UserUnknown, HostUnknown, HasMoved, NotAccept
|
|
501
|
-
# 1-2. Almost hard bounce: Suspend, Suppressed
|
|
502
|
-
return false if cv.start_with?('4') || cw.start_with?('4')
|
|
503
|
-
return true if %w[userunknown hostunknown hasmoved notaccept suspend suppressed].any? { |a| cr == a }
|
|
504
|
-
|
|
505
|
-
if %w[mailboxfull filtered norelaying].any? { |a| cr == a }
|
|
506
|
-
# 2. Several softbounces: MailboxFull, Filtered, NoRelaying
|
|
507
|
-
# 2-1. The SMTP command is "RCPT" except "MailboxFull".
|
|
508
|
-
# 2-2. The SMTP reply code begins with "5" such as "550".
|
|
509
|
-
# 2-3. The SMTP status code is explicit code (not empty, not 5.9.***).
|
|
510
|
-
# 2-4. The SMTP status code begins with "5." such as "5.1.1".
|
|
511
|
-
return true if cr != 'mailboxfull' && thing['command'] == "RCPT"
|
|
512
|
-
return true if cv.start_with?('5')
|
|
513
|
-
return false if Sisimai::SMTP::Status.is_explicit(cw) == false
|
|
514
|
-
return true if cw.start_with?('5.')
|
|
515
|
-
|
|
516
|
-
elsif cr == 'feedback'
|
|
517
|
-
# 3. Feedback Loop
|
|
518
|
-
# 3-1. The Feedback Type is any of "abuse", "fraud", "opt-out"
|
|
519
|
-
return true if %w[abuse fraud opt-out].any? { |a| thing['feedbacktype'] == a }
|
|
520
|
-
end
|
|
521
|
-
return false
|
|
522
|
-
end
|
|
523
|
-
|
|
524
505
|
# Create message token from addresser and recipient
|
|
525
506
|
# @param [String] addr1 Sender address
|
|
526
507
|
# @param [String] addr2 Recipient address
|
|
@@ -551,6 +532,7 @@ module Sisimai
|
|
|
551
532
|
stringdata.each { |e| v[e] = self.send(e.to_sym) || '' }
|
|
552
533
|
v['hardbounce'] = self.hardbounce
|
|
553
534
|
v['toxic'] = self.toxic
|
|
535
|
+
v['bogus'] = self.bogus
|
|
554
536
|
v['addresser'] = self.addresser.address
|
|
555
537
|
v['recipient'] = self.recipient.address
|
|
556
538
|
v['timestamp'] = self.timestamp.to_time.to_i
|
data/lib/sisimai/lda.rb
CHANGED
|
@@ -2,6 +2,8 @@ module Sisimai
|
|
|
2
2
|
# Sisimai::LDA - Error message decoder for LDA
|
|
3
3
|
module LDA
|
|
4
4
|
class << self
|
|
5
|
+
require 'sisimai/eb'
|
|
6
|
+
|
|
5
7
|
LocalAgent = {
|
|
6
8
|
# Each error message should be a lower-cased string
|
|
7
9
|
# dovecot/src/deliver/deliver.c
|
|
@@ -19,20 +21,20 @@ module Sisimai
|
|
|
19
21
|
# Each error message should be a lower-cased string
|
|
20
22
|
"dovecot" => {
|
|
21
23
|
# dovecot/src/deliver/mail-send.c:94
|
|
22
|
-
|
|
24
|
+
Sisimai::Eb::ReFULL => [
|
|
23
25
|
"not enough disk space",
|
|
24
26
|
"quota exceeded", # Dovecot 1.2 dovecot/src/plugins/quota/quota.c
|
|
25
27
|
"quota exceeded (mailbox for user is full)", # dovecot/src/plugins/quota/quota.c
|
|
26
28
|
],
|
|
27
|
-
|
|
29
|
+
Sisimai::Eb::ReUSER => ["mailbox doesn't exist: "],
|
|
28
30
|
},
|
|
29
31
|
"mail.local" => {
|
|
30
|
-
|
|
32
|
+
Sisimai::Eb::ReFULL => [
|
|
31
33
|
"disc quota exceeded",
|
|
32
34
|
"mailbox full or quota exceeded",
|
|
33
35
|
],
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
Sisimai::Eb::RePROC => ["temporary file write error"],
|
|
37
|
+
Sisimai::Eb::ReUSER => [
|
|
36
38
|
": invalid mailbox path",
|
|
37
39
|
": unknown user:",
|
|
38
40
|
": user missing home directory",
|
|
@@ -40,23 +42,23 @@ module Sisimai
|
|
|
40
42
|
],
|
|
41
43
|
},
|
|
42
44
|
"procmail" => {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
45
|
+
Sisimai::Eb::ReFULL => ["quota exceeded while writing", "user over quota"],
|
|
46
|
+
Sisimai::Eb::RePROC => ["service unavailable"],
|
|
47
|
+
Sisimai::Eb::ReDISK => ["no space left to finish writing"],
|
|
46
48
|
},
|
|
47
49
|
"maildrop" => {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
+
Sisimai::Eb::ReUSER => ["cannot find system user", "invalid user specified."],
|
|
51
|
+
Sisimai::Eb::ReFULL => ["maildir over quota."],
|
|
50
52
|
},
|
|
51
53
|
"vpopmail" => {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
Sisimai::Eb::ReFILT => ["user does not exist, but will deliver to "],
|
|
55
|
+
Sisimai::Eb::ReFULL => ["domain is over quota", "user is over quota"],
|
|
56
|
+
Sisimai::Eb::ReQUIT => ["account is locked email bounced"],
|
|
57
|
+
Sisimai::Eb::ReUSER => ["sorry, no mailbox here by that name."],
|
|
56
58
|
},
|
|
57
59
|
"vmailmgr" => {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
+
Sisimai::Eb::ReFULL => ["delivery failed due to system quota violation"],
|
|
61
|
+
Sisimai::Eb::ReUSER => [
|
|
60
62
|
"invalid or unknown base user or domain",
|
|
61
63
|
"invalid or unknown virtual user",
|
|
62
64
|
"user name does not refer to a virtual user",
|
|
@@ -70,7 +72,7 @@ module Sisimai
|
|
|
70
72
|
def find(argvs)
|
|
71
73
|
return "" if argvs.nil?
|
|
72
74
|
return "" if argvs["diagnosticcode"].empty?
|
|
73
|
-
return "" if argvs["command"] != "" && argvs["command"] !=
|
|
75
|
+
return "" if argvs["command"] != "" && argvs["command"] != Sisimai::Eb::CeDATA
|
|
74
76
|
|
|
75
77
|
deliversby = "" # [String] Local Delivery Agent name
|
|
76
78
|
reasontext = "" # [String] Error reason
|
|
@@ -89,7 +91,7 @@ module Sisimai
|
|
|
89
91
|
reasontext = e; break
|
|
90
92
|
end
|
|
91
93
|
|
|
92
|
-
reasontext =
|
|
94
|
+
reasontext = Sisimai::Eb::ReUNIX if reasontext.empty?
|
|
93
95
|
return reasontext
|
|
94
96
|
end
|
|
95
97
|
end
|
|
@@ -63,8 +63,6 @@ module Sisimai::Lhost
|
|
|
63
63
|
end
|
|
64
64
|
end
|
|
65
65
|
return nil if recipients == 0
|
|
66
|
-
|
|
67
|
-
dscontents.each { |e| e['diagnosis'] = Sisimai::String.sweep(e['diagnosis']) }
|
|
68
66
|
return {"ds" => dscontents, "rfc822" => emailparts[1]}
|
|
69
67
|
end
|
|
70
68
|
def description; return 'TransWARE Active!hunter'; end
|
|
@@ -42,16 +42,17 @@ module Sisimai::Lhost
|
|
|
42
42
|
# Transient/ContentRejected -- message you sent contains content that the provider doesn't allow
|
|
43
43
|
# Transient/AttachmentRejected the message contained an unacceptable attachment
|
|
44
44
|
class << self
|
|
45
|
+
require 'sisimai/eb'
|
|
45
46
|
require 'sisimai/lhost'
|
|
46
47
|
|
|
47
48
|
ReasonPair = {
|
|
48
|
-
"Suppressed" =>
|
|
49
|
-
"OnAccountSuppressionList" =>
|
|
50
|
-
"General" =>
|
|
51
|
-
"MailboxFull" =>
|
|
52
|
-
"MessageTooLarge" =>
|
|
53
|
-
"ContentRejected" =>
|
|
54
|
-
"AttachmentRejected" =>
|
|
49
|
+
"Suppressed" => Sisimai::Eb::ReSTOP,
|
|
50
|
+
"OnAccountSuppressionList" => Sisimai::Eb::ReSTOP,
|
|
51
|
+
"General" => Sisimai::Eb::Re___1,
|
|
52
|
+
"MailboxFull" => Sisimai::Eb::ReFULL,
|
|
53
|
+
"MessageTooLarge" => Sisimai::Eb::ReSIZE,
|
|
54
|
+
"ContentRejected" => Sisimai::Eb::ReBODY,
|
|
55
|
+
"AttachmentRejected" => Sisimai::Eb::ReSAFE,
|
|
55
56
|
}.freeze
|
|
56
57
|
|
|
57
58
|
# @abstract Decodes the bounce message from Amazon SES
|
|
@@ -124,7 +125,8 @@ module Sisimai::Lhost
|
|
|
124
125
|
recipients = 0 # (Integer) The number of 'Final-Recipient' header
|
|
125
126
|
whatnotify = jsonobject["notificationType"][0, 1] || ""
|
|
126
127
|
|
|
127
|
-
|
|
128
|
+
case whatnotify
|
|
129
|
+
when "B"
|
|
128
130
|
# "notificationType":"Bounce"
|
|
129
131
|
p = jsonobject["bounce"]
|
|
130
132
|
r = p["bounceType"] == "Permanent" ? "5" : "4"
|
|
@@ -137,7 +139,7 @@ module Sisimai::Lhost
|
|
|
137
139
|
v = dscontents[-1]
|
|
138
140
|
end
|
|
139
141
|
v["recipient"] = e["emailAddress"]
|
|
140
|
-
v["diagnosis"] =
|
|
142
|
+
v["diagnosis"] = e["diagnosticCode"]
|
|
141
143
|
v["command"] = Sisimai::SMTP::Command.find(v["diagnosis"])
|
|
142
144
|
v["action"] = e["action"]
|
|
143
145
|
v["status"] = Sisimai::SMTP::Status.find(v["diagnosis"], r)
|
|
@@ -152,8 +154,7 @@ module Sisimai::Lhost
|
|
|
152
154
|
next if ReasonPair[f] != p["bounceSubType"]
|
|
153
155
|
v["reason"] = f; break
|
|
154
156
|
end
|
|
155
|
-
|
|
156
|
-
elsif whatnotify == "C"
|
|
157
|
+
when "C"
|
|
157
158
|
# "notificationType":"Complaint"
|
|
158
159
|
p = jsonobject["complaint"]
|
|
159
160
|
p["complainedRecipients"].each do |e|
|
|
@@ -164,14 +165,13 @@ module Sisimai::Lhost
|
|
|
164
165
|
v = dscontents[-1]
|
|
165
166
|
end
|
|
166
167
|
v["recipient"] = e["emailAddress"]
|
|
167
|
-
v["reason"] =
|
|
168
|
+
v["reason"] = Sisimai::Eb::ReFEED
|
|
168
169
|
v["feedbacktype"] = p["complaintFeedbackType"]
|
|
169
170
|
v["date"] = p["timestamp"]
|
|
170
171
|
v["diagnosis"] = sprintf('"feedbackid":"%s", "useragent":"%s"}', p["feedbackId"], p["userAgent"])
|
|
171
172
|
recipients += 1
|
|
172
173
|
end
|
|
173
|
-
|
|
174
|
-
elsif whatnotify == "D"
|
|
174
|
+
when "D"
|
|
175
175
|
# "notificationType":"Delivery"
|
|
176
176
|
p = jsonobject["delivery"]
|
|
177
177
|
p["recipients"].each do |e|
|
|
@@ -182,17 +182,16 @@ module Sisimai::Lhost
|
|
|
182
182
|
v = dscontents[-1]
|
|
183
183
|
end
|
|
184
184
|
v["recipient"] = e
|
|
185
|
-
v["reason"] =
|
|
186
|
-
v["action"] =
|
|
185
|
+
v["reason"] = Sisimai::Eb::ReSENT
|
|
186
|
+
v["action"] = Sisimai::Eb::AeSENT
|
|
187
187
|
v["date"] = p["timestamp"]
|
|
188
188
|
v["lhost"] = Sisimai::RFC1123.find(p["reportingMTA"])
|
|
189
|
-
v["diagnosis"] =
|
|
189
|
+
v["diagnosis"] = p["smtpResponse"]
|
|
190
190
|
v["command"] = Sisimai::SMTP::Command.find(v["diagnosis"])
|
|
191
191
|
v["status"] = Sisimai::SMTP::Status.find(v["diagnosis"], "2")
|
|
192
192
|
v["replycode"] = Sisimai::SMTP::Reply.find(v["diagnosis"], "2")
|
|
193
193
|
recipients += 1
|
|
194
194
|
end
|
|
195
|
-
|
|
196
195
|
else
|
|
197
196
|
# Unknown "notificationType" value
|
|
198
197
|
warn sprintf(" ***warning: There is no notificationType field or unknown type of notificationType field")
|
|
@@ -103,7 +103,6 @@ module Sisimai::Lhost
|
|
|
103
103
|
emailparts[1] += sprintf("Subject: %s\n", alternates[3]) if alternates[3] != ""
|
|
104
104
|
end
|
|
105
105
|
|
|
106
|
-
dscontents.each { |e| e["diagnosis"] = Sisimai::String.sweep(e["diagnosis"]) }
|
|
107
106
|
return { "ds" => dscontents, "rfc822" => emailparts[1] }
|
|
108
107
|
end
|
|
109
108
|
def description; return 'Java Apache Mail Enterprise Server'; end
|
|
@@ -11,10 +11,6 @@ module Sisimai::Lhost
|
|
|
11
11
|
message: [' ----- The following addresses had delivery problems -----'],
|
|
12
12
|
error: [' ----- Non-delivered information -----'],
|
|
13
13
|
}.freeze
|
|
14
|
-
MessagesOf = {
|
|
15
|
-
'filtered' => ['Mail Delivery Failed... User unknown'],
|
|
16
|
-
'mailboxfull' => ["The number of messages in recipient's mailbox exceeded the local limit."],
|
|
17
|
-
}.freeze
|
|
18
14
|
|
|
19
15
|
# @asbtract Decodes the bounce message from Biglobe
|
|
20
16
|
# @param [Hash] mhead Message headers of a bounce email
|
|
@@ -76,18 +72,6 @@ module Sisimai::Lhost
|
|
|
76
72
|
end
|
|
77
73
|
end
|
|
78
74
|
return nil if recipients == 0
|
|
79
|
-
|
|
80
|
-
dscontents.each do |e|
|
|
81
|
-
e['diagnosis'] = Sisimai::String.sweep(e['diagnosis'])
|
|
82
|
-
|
|
83
|
-
MessagesOf.each_key do |r|
|
|
84
|
-
# Verify each regular expression of session errors
|
|
85
|
-
next if MessagesOf[r].none? { |a| e['diagnosis'].include?(a) }
|
|
86
|
-
e['reason'] = r
|
|
87
|
-
break
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
|
|
91
75
|
return { 'ds' => dscontents, 'rfc822' => emailparts[1] }
|
|
92
76
|
end
|
|
93
77
|
def description; return 'BIGLOBE: https://www.biglobe.ne.jp'; end
|
|
@@ -3,6 +3,7 @@ module Sisimai::Lhost
|
|
|
3
3
|
# Methods in the module are called from only Sisimai::Message.
|
|
4
4
|
module Courier
|
|
5
5
|
class << self
|
|
6
|
+
require 'sisimai/eb'
|
|
6
7
|
require 'sisimai/lhost'
|
|
7
8
|
|
|
8
9
|
# https://www.courier-mta.org/courierdsn.html
|
|
@@ -14,12 +15,12 @@ module Sisimai::Lhost
|
|
|
14
15
|
}.freeze
|
|
15
16
|
MessagesOf = {
|
|
16
17
|
# courier/module.esmtp/esmtpclient.c:526| hard_error(del, ctf, "No such domain.");
|
|
17
|
-
|
|
18
|
+
Sisimai::Eb::ReHOST => ['No such domain.'],
|
|
18
19
|
# courier/module.esmtp/esmtpclient.c:531| hard_error(del, ctf,
|
|
19
20
|
# courier/module.esmtp/esmtpclient.c:532| "This domain's DNS violates RFC 1035.");
|
|
20
|
-
|
|
21
|
+
Sisimai::Eb::RePROC => ["This domain's DNS violates RFC 1035."],
|
|
21
22
|
# courier/module.esmtp/esmtpclient.c:535| soft_error(del, ctf, "DNS lookup failed.");
|
|
22
|
-
|
|
23
|
+
Sisimai::Eb::ReINET => ['DNS lookup failed.'],
|
|
23
24
|
}.freeze
|
|
24
25
|
|
|
25
26
|
# @abstract Decodes the bounce message from Courier MTA
|
|
@@ -72,7 +73,8 @@ module Sisimai::Lhost
|
|
|
72
73
|
next unless o = Sisimai::RFC1894.field(e)
|
|
73
74
|
v = dscontents[-1]
|
|
74
75
|
|
|
75
|
-
|
|
76
|
+
case o[3]
|
|
77
|
+
when "addr"
|
|
76
78
|
# Final-Recipient: rfc822; kijitora@example.jp
|
|
77
79
|
# X-Actual-Recipient: rfc822; kijitora@example.co.jp
|
|
78
80
|
if o[0] == 'final-recipient'
|
|
@@ -88,7 +90,7 @@ module Sisimai::Lhost
|
|
|
88
90
|
# X-Actual-Recipient: rfc822; kijitora@example.co.jp
|
|
89
91
|
v['alias'] = o[2]
|
|
90
92
|
end
|
|
91
|
-
|
|
93
|
+
when "code"
|
|
92
94
|
# Diagnostic-Code: SMTP; 550 5.1.1 <userunknown@example.jp>... User Unknown
|
|
93
95
|
v['spec'] = o[1]
|
|
94
96
|
v['diagnosis'] = o[2]
|
|
@@ -116,7 +118,7 @@ module Sisimai::Lhost
|
|
|
116
118
|
# Continued line of the value of Diagnostic-Code field
|
|
117
119
|
next if readslices[-2].start_with?('Diagnostic-Code:') == false
|
|
118
120
|
next if e.start_with?(' ') == false
|
|
119
|
-
v['diagnosis'] += "
|
|
121
|
+
v['diagnosis'] += " " + e
|
|
120
122
|
readslices[-1] = "Diagnostic-Code: #{e}"
|
|
121
123
|
end
|
|
122
124
|
end
|
|
@@ -127,7 +129,7 @@ module Sisimai::Lhost
|
|
|
127
129
|
# Set default values if each value is empty.
|
|
128
130
|
permessage.each_key { |a| e[a] ||= permessage[a] || '' }
|
|
129
131
|
e['command'] = thecommand if e["command"].empty?
|
|
130
|
-
e['diagnosis'] =
|
|
132
|
+
e['diagnosis'] = e['diagnosis'] || ''
|
|
131
133
|
|
|
132
134
|
MessagesOf.each_key do |r|
|
|
133
135
|
# Verify each regular expression of session errors
|
|
@@ -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
|
+
|
data/lib/sisimai/lhost/domino.rb
CHANGED
|
@@ -3,15 +3,14 @@ module Sisimai::Lhost
|
|
|
3
3
|
# Methods in the module are called from only Sisimai::Message.
|
|
4
4
|
module Domino
|
|
5
5
|
class << self
|
|
6
|
+
require 'sisimai/eb'
|
|
6
7
|
require 'sisimai/lhost'
|
|
7
8
|
|
|
8
9
|
Indicators = Sisimai::Lhost.INDICATORS
|
|
9
10
|
Boundaries = ['Content-Type: message/rfc822'].freeze
|
|
10
11
|
StartingOf = {message: ['Your message']}.freeze
|
|
11
12
|
MessagesOf = {
|
|
12
|
-
|
|
13
|
-
"systemerror" => ["Several matches found in Domino Directory"],
|
|
14
|
-
"userunknown" => [
|
|
13
|
+
Sisimai::Eb::ReUSER => [
|
|
15
14
|
"not listed in Domino Directory",
|
|
16
15
|
"not listed in public Name & Address Book",
|
|
17
16
|
"non répertorié dans l'annuaire Domino",
|
|
@@ -120,7 +119,6 @@ module Sisimai::Lhost
|
|
|
120
119
|
return nil if recipients == 0
|
|
121
120
|
|
|
122
121
|
dscontents.each do |e|
|
|
123
|
-
e['diagnosis'] = Sisimai::String.sweep(e['diagnosis'])
|
|
124
122
|
e['recipient'] = Sisimai::Address.s3s4(e['recipient'])
|
|
125
123
|
permessage.each_key { |a| e[a] ||= permessage[a] || '' }
|
|
126
124
|
|