sisimai 5.1.0-java → 5.2.0-java

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.
Files changed (192) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rake-test.yml +1 -1
  3. data/ChangeLog.md +102 -0
  4. data/Makefile +4 -2
  5. data/README-JA.md +23 -16
  6. data/README.md +22 -15
  7. data/lib/sisimai/arf.rb +121 -210
  8. data/lib/sisimai/fact.rb +208 -158
  9. data/lib/sisimai/lda.rb +98 -0
  10. data/lib/sisimai/lhost/activehunter.rb +1 -1
  11. data/lib/sisimai/lhost/amazonses.rb +185 -301
  12. data/lib/sisimai/lhost/apachejames.rb +48 -51
  13. data/lib/sisimai/lhost/biglobe.rb +1 -2
  14. data/lib/sisimai/lhost/courier.rb +10 -8
  15. data/lib/sisimai/lhost/domino.rb +25 -25
  16. data/lib/sisimai/lhost/dragonfly.rb +3 -4
  17. data/lib/sisimai/lhost/einsundeins.rb +3 -4
  18. data/lib/sisimai/lhost/exchange2003.rb +6 -8
  19. data/lib/sisimai/lhost/exchange2007.rb +111 -101
  20. data/lib/sisimai/lhost/exim.rb +232 -242
  21. data/lib/sisimai/lhost/ezweb.rb +43 -51
  22. data/lib/sisimai/lhost/fml.rb +2 -3
  23. data/lib/sisimai/lhost/gmail.rb +32 -28
  24. data/lib/sisimai/lhost/gmx.rb +4 -16
  25. data/lib/sisimai/lhost/googlegroups.rb +9 -8
  26. data/lib/sisimai/lhost/googleworkspace.rb +94 -0
  27. data/lib/sisimai/lhost/imailserver.rb +7 -16
  28. data/lib/sisimai/lhost/interscanmss.rb +1 -1
  29. data/lib/sisimai/lhost/kddi.rb +3 -4
  30. data/lib/sisimai/lhost/mailfoundry.rb +2 -5
  31. data/lib/sisimai/lhost/mailmarshalsmtp.rb +1 -2
  32. data/lib/sisimai/lhost/messagingserver.rb +14 -13
  33. data/lib/sisimai/lhost/mfilter.rb +4 -3
  34. data/lib/sisimai/lhost/notes.rb +2 -4
  35. data/lib/sisimai/lhost/opensmtpd.rb +2 -2
  36. data/lib/sisimai/lhost/postfix.rb +25 -27
  37. data/lib/sisimai/lhost/qmail.rb +130 -106
  38. data/lib/sisimai/lhost/sendmail.rb +19 -18
  39. data/lib/sisimai/lhost/v5sendmail.rb +88 -60
  40. data/lib/sisimai/lhost/verizon.rb +2 -2
  41. data/lib/sisimai/lhost/x1.rb +1 -1
  42. data/lib/sisimai/lhost/x2.rb +1 -2
  43. data/lib/sisimai/lhost/x3.rb +2 -2
  44. data/lib/sisimai/lhost/x6.rb +1 -1
  45. data/lib/sisimai/lhost/zoho.rb +2 -2
  46. data/lib/sisimai/lhost.rb +18 -21
  47. data/lib/sisimai/message.rb +93 -146
  48. data/lib/sisimai/order.rb +21 -77
  49. data/lib/sisimai/reason/authfailure.rb +1 -4
  50. data/lib/sisimai/reason/badreputation.rb +2 -2
  51. data/lib/sisimai/reason/blocked.rb +7 -10
  52. data/lib/sisimai/reason/contenterror.rb +7 -1
  53. data/lib/sisimai/reason/exceedlimit.rb +1 -4
  54. data/lib/sisimai/reason/failedstarttls.rb +42 -0
  55. data/lib/sisimai/reason/filtered.rb +5 -4
  56. data/lib/sisimai/reason/hasmoved.rb +1 -2
  57. data/lib/sisimai/reason/hostunknown.rb +3 -3
  58. data/lib/sisimai/reason/mailboxfull.rb +2 -4
  59. data/lib/sisimai/reason/mailererror.rb +1 -2
  60. data/lib/sisimai/reason/mesgtoobig.rb +2 -4
  61. data/lib/sisimai/reason/norelaying.rb +2 -3
  62. data/lib/sisimai/reason/notaccept.rb +2 -3
  63. data/lib/sisimai/reason/notcompliantrfc.rb +10 -4
  64. data/lib/sisimai/reason/rejected.rb +1 -1
  65. data/lib/sisimai/reason/requireptr.rb +2 -2
  66. data/lib/sisimai/reason/securityerror.rb +1 -3
  67. data/lib/sisimai/reason/spamdetected.rb +6 -8
  68. data/lib/sisimai/reason/speeding.rb +1 -2
  69. data/lib/sisimai/reason/suppressed.rb +36 -0
  70. data/lib/sisimai/reason/suspend.rb +1 -3
  71. data/lib/sisimai/reason/systemerror.rb +5 -0
  72. data/lib/sisimai/reason/toomanyconn.rb +1 -2
  73. data/lib/sisimai/reason/userunknown.rb +1 -1
  74. data/lib/sisimai/reason/virusdetected.rb +5 -6
  75. data/lib/sisimai/reason.rb +77 -73
  76. data/lib/sisimai/rfc1123.rb +152 -0
  77. data/lib/sisimai/rfc1894.rb +102 -62
  78. data/lib/sisimai/rfc2045.rb +2 -1
  79. data/lib/sisimai/rfc3464/thirdparty.rb +102 -0
  80. data/lib/sisimai/rfc3464.rb +222 -343
  81. data/lib/sisimai/rfc3834.rb +1 -1
  82. data/lib/sisimai/rfc5322.rb +7 -17
  83. data/lib/sisimai/rfc791.rb +69 -0
  84. data/lib/sisimai/rhost/aol.rb +36 -0
  85. data/lib/sisimai/rhost/apple.rb +5 -2
  86. data/lib/sisimai/rhost/cox.rb +3 -2
  87. data/lib/sisimai/rhost/facebook.rb +100 -0
  88. data/lib/sisimai/rhost/franceptt.rb +3 -2
  89. data/lib/sisimai/rhost/godaddy.rb +3 -2
  90. data/lib/sisimai/rhost/google.rb +19 -17
  91. data/lib/sisimai/rhost/gsuite.rb +42 -0
  92. data/lib/sisimai/rhost/iua.rb +3 -3
  93. data/lib/sisimai/rhost/kddi.rb +3 -2
  94. data/lib/sisimai/rhost/messagelabs.rb +37 -0
  95. data/lib/sisimai/rhost/microsoft.rb +56 -49
  96. data/lib/sisimai/rhost/mimecast.rb +29 -27
  97. data/lib/sisimai/rhost/nttdocomo.rb +4 -3
  98. data/lib/sisimai/rhost/outlook.rb +36 -0
  99. data/lib/sisimai/rhost/spectrum.rb +3 -2
  100. data/lib/sisimai/rhost/tencent.rb +3 -2
  101. data/lib/sisimai/rhost/yahooinc.rb +4 -3
  102. data/lib/sisimai/rhost.rb +69 -39
  103. data/lib/sisimai/smtp/command.rb +31 -21
  104. data/lib/sisimai/smtp/failure.rb +103 -0
  105. data/lib/sisimai/smtp/reply.rb +29 -25
  106. data/lib/sisimai/smtp/status.rb +36 -19
  107. data/lib/sisimai/smtp/transcript.rb +15 -15
  108. data/lib/sisimai/string.rb +0 -46
  109. data/lib/sisimai/version.rb +1 -1
  110. data/set-of-emails/maildir/bsd/lhost-postfix-30.eml +81 -81
  111. data/set-of-emails/maildir/bsd/{lhost-aol-03.eml → rhost-aol-03.eml} +1264 -1264
  112. data/set-of-emails/maildir/bsd/{lhost-aol-04.eml → rhost-aol-04.eml} +1260 -1260
  113. data/set-of-emails/maildir/bsd/{lhost-aol-05.eml → rhost-aol-05.eml} +105 -105
  114. data/set-of-emails/maildir/bsd/{lhost-aol-06.eml → rhost-aol-06.eml} +105 -105
  115. data/set-of-emails/maildir/bsd/rhost-gsuite-01.eml +189 -0
  116. data/set-of-emails/maildir/bsd/rhost-gsuite-02.eml +180 -0
  117. data/set-of-emails/maildir/bsd/rhost-gsuite-03.eml +251 -0
  118. data/set-of-emails/maildir/bsd/rhost-gsuite-04.eml +211 -0
  119. data/set-of-emails/maildir/bsd/rhost-gsuite-05.eml +226 -0
  120. data/set-of-emails/maildir/bsd/rhost-gsuite-06.eml +257 -0
  121. data/set-of-emails/maildir/bsd/rhost-gsuite-07.eml +289 -0
  122. data/set-of-emails/maildir/bsd/rhost-gsuite-08.eml +231 -0
  123. data/set-of-emails/maildir/bsd/rhost-gsuite-09.eml +231 -0
  124. data/set-of-emails/maildir/bsd/rhost-gsuite-10.eml +254 -0
  125. data/set-of-emails/maildir/bsd/rhost-gsuite-11.eml +228 -0
  126. data/set-of-emails/maildir/bsd/rhost-gsuite-12.eml +271 -0
  127. data/set-of-emails/maildir/bsd/rhost-gsuite-13.eml +261 -0
  128. data/set-of-emails/maildir/bsd/rhost-gsuite-14.eml +273 -0
  129. data/set-of-emails/maildir/bsd/rhost-gsuite-15.eml +229 -0
  130. data/set-of-emails/maildir/bsd/{lhost-messagelabs-01.eml → rhost-messagelabs-01.eml} +93 -93
  131. data/set-of-emails/maildir/bsd/rhost-outlook-01.eml +72 -0
  132. data/set-of-emails/maildir/bsd/rhost-outlook-02.eml +72 -0
  133. data/set-of-emails/maildir/bsd/rhost-outlook-03.eml +72 -0
  134. data/set-of-emails/maildir/bsd/rhost-outlook-04.eml +79 -0
  135. data/set-of-emails/maildir/bsd/rhost-outlook-06.eml +75 -0
  136. data/set-of-emails/maildir/bsd/rhost-outlook-07.eml +70 -0
  137. data/set-of-emails/maildir/bsd/rhost-outlook-08.eml +70 -0
  138. data/set-of-emails/maildir/bsd/rhost-outlook-09.eml +56 -0
  139. data/set-of-emails/maildir/tmp/arf-22.eml +49 -0
  140. data/set-of-emails/maildir/tmp/arf-23.eml +49 -0
  141. data/set-of-emails/maildir/tmp/arf-24.eml +50 -0
  142. data/set-of-emails/maildir/tmp/lhost-exim-07.eml +28 -0
  143. metadata +73 -56
  144. data/lib/sisimai/lhost/amavis.rb +0 -163
  145. data/lib/sisimai/lhost/amazonworkmail.rb +0 -127
  146. data/lib/sisimai/lhost/aol.rb +0 -125
  147. data/lib/sisimai/lhost/barracuda.rb +0 -92
  148. data/lib/sisimai/lhost/bigfoot.rb +0 -125
  149. data/lib/sisimai/lhost/facebook.rb +0 -188
  150. data/lib/sisimai/lhost/gsuite.rb +0 -194
  151. data/lib/sisimai/lhost/mailru.rb +0 -214
  152. data/lib/sisimai/lhost/mcafee.rb +0 -109
  153. data/lib/sisimai/lhost/messagelabs.rb +0 -120
  154. data/lib/sisimai/lhost/mxlogic.rb +0 -198
  155. data/lib/sisimai/lhost/office365.rb +0 -252
  156. data/lib/sisimai/lhost/outlook.rb +0 -129
  157. data/lib/sisimai/lhost/powermta.rb +0 -118
  158. data/lib/sisimai/lhost/receivingses.rb +0 -126
  159. data/lib/sisimai/lhost/sendgrid.rb +0 -150
  160. data/lib/sisimai/lhost/surfcontrol.rb +0 -105
  161. data/lib/sisimai/lhost/x4.rb +0 -269
  162. data/lib/sisimai/lhost/x5.rb +0 -112
  163. data/lib/sisimai/lhost/yahoo.rb +0 -102
  164. data/lib/sisimai/lhost/yandex.rb +0 -118
  165. data/lib/sisimai/mda.rb +0 -121
  166. data/lib/sisimai/smtp/error.rb +0 -119
  167. /data/set-of-emails/maildir/bsd/{lhost-googlegroups-15.eml → lhost-googleworkspace-01.eml} +0 -0
  168. /data/set-of-emails/maildir/bsd/{lhost-x4-08.eml → lhost-x2-06.eml} +0 -0
  169. /data/set-of-emails/maildir/bsd/{lhost-gsuite-01.eml → rfc3464-51.eml} +0 -0
  170. /data/set-of-emails/maildir/bsd/{lhost-gsuite-03.eml → rfc3464-52.eml} +0 -0
  171. /data/set-of-emails/maildir/bsd/{lhost-gsuite-04.eml → rfc3464-53.eml} +0 -0
  172. /data/set-of-emails/maildir/bsd/{lhost-gsuite-05.eml → rfc3464-54.eml} +0 -0
  173. /data/set-of-emails/maildir/bsd/{lhost-gsuite-06.eml → rfc3464-55.eml} +0 -0
  174. /data/set-of-emails/maildir/bsd/{lhost-gsuite-07.eml → rfc3464-56.eml} +0 -0
  175. /data/set-of-emails/maildir/bsd/{lhost-gsuite-08.eml → rfc3464-57.eml} +0 -0
  176. /data/set-of-emails/maildir/bsd/{lhost-gsuite-09.eml → rfc3464-58.eml} +0 -0
  177. /data/set-of-emails/maildir/bsd/{lhost-gsuite-10.eml → rfc3464-59.eml} +0 -0
  178. /data/set-of-emails/maildir/bsd/{lhost-gsuite-11.eml → rfc3464-60.eml} +0 -0
  179. /data/set-of-emails/maildir/bsd/{lhost-gsuite-12.eml → rfc3464-61.eml} +0 -0
  180. /data/set-of-emails/maildir/bsd/{lhost-gsuite-13.eml → rfc3464-62.eml} +0 -0
  181. /data/set-of-emails/maildir/bsd/{lhost-gsuite-14.eml → rfc3464-63.eml} +0 -0
  182. /data/set-of-emails/maildir/bsd/{lhost-gsuite-15.eml → rfc3464-64.eml} +0 -0
  183. /data/set-of-emails/maildir/bsd/{lhost-gsuite-02.eml → rfc3464-65.eml} +0 -0
  184. /data/set-of-emails/maildir/bsd/{lhost-aol-01.eml → rhost-aol-01.eml} +0 -0
  185. /data/set-of-emails/maildir/bsd/{lhost-aol-02.eml → rhost-aol-02.eml} +0 -0
  186. /data/set-of-emails/maildir/bsd/{lhost-facebook-03.eml → rhost-facebook-03.eml} +0 -0
  187. /data/set-of-emails/maildir/bsd/{lhost-facebook-04.eml → rhost-facebook-04.eml} +0 -0
  188. /data/set-of-emails/maildir/bsd/{lhost-messagelabs-02.eml → rhost-messagelabs-02.eml} +0 -0
  189. /data/set-of-emails/maildir/bsd/{lhost-messagelabs-03.eml → rhost-messagelabs-03.eml} +0 -0
  190. /data/set-of-emails/maildir/{bsd → tmp}/rfc3464-37.eml +0 -0
  191. /data/set-of-emails/maildir/{bsd → tmp}/rfc3464-38.eml +0 -0
  192. /data/set-of-emails/maildir/{bsd → tmp}/rfc3464-39.eml +0 -0
@@ -0,0 +1,98 @@
1
+ module Sisimai
2
+ # Sisimai::LDA - Error message decoder for LDA
3
+ module LDA
4
+ class << self
5
+ LocalAgent = {
6
+ # Each error message should be a lower-cased string
7
+ # dovecot/src/deliver/deliver.c
8
+ # 11: #define DEFAULT_MAIL_REJECTION_HUMAN_REASON \
9
+ # 12: "Your message to <%t> was automatically rejected:%n%r"
10
+ "dovecot" => ["Your message to <", "> was automatically rejected:"],
11
+ "mail.local" => ["mail.local: "],
12
+ "procmail" => ["procmail: ", "/procmail "],
13
+ "maildrop" => ["maildrop: "],
14
+ "vpopmail" => ["vdelivermail: "],
15
+ "vmailmgr" => ["vdeliver: "],
16
+ }.freeze
17
+
18
+ MessagesOf = {
19
+ # Each error message should be a lower-cased string
20
+ "dovecot" => {
21
+ # dovecot/src/deliver/mail-send.c:94
22
+ "mailboxfull" => [
23
+ "not enough disk space",
24
+ "quota exceeded", # Dovecot 1.2 dovecot/src/plugins/quota/quota.c
25
+ "quota exceeded (mailbox for user is full)", # dovecot/src/plugins/quota/quota.c
26
+ ],
27
+ "userunknown" => ["mailbox doesn't exist: "],
28
+ },
29
+ "mail.local" => {
30
+ "mailboxfull" => [
31
+ "disc quota exceeded",
32
+ "mailbox full or quota exceeded",
33
+ ],
34
+ "systemerror" => ["temporary file write error"],
35
+ "userunknown" => [
36
+ ": invalid mailbox path",
37
+ ": unknown user:",
38
+ ": user missing home directory",
39
+ ": user unknown",
40
+ ],
41
+ },
42
+ "procmail" => {
43
+ "mailboxfull" => ["quota exceeded while writing", "user over quota"],
44
+ "systemerror" => ["service unavailable"],
45
+ "systemfull" => ["no space left to finish writing"],
46
+ },
47
+ "maildrop" => {
48
+ "userunknown" => ["cannot find system user", "invalid user specified."],
49
+ "mailboxfull" => ["maildir over quota."],
50
+ },
51
+ "vpopmail" => {
52
+ "filtered" => ["user does not exist, but will deliver to "],
53
+ "mailboxfull" => ["domain is over quota", "user is over quota"],
54
+ "suspend" => ["account is locked email bounced"],
55
+ "userunknown" => ["sorry, no mailbox here by that name."],
56
+ },
57
+ "vmailmgr" => {
58
+ "mailboxfull" => ["delivery failed due to system quota violation"],
59
+ "userunknown" => [
60
+ "invalid or unknown base user or domain",
61
+ "invalid or unknown virtual user",
62
+ "user name does not refer to a virtual user",
63
+ ],
64
+ },
65
+ }.freeze
66
+
67
+ # @abstract Decodes the message body and return the LDA name, the reason, and the error message
68
+ # @param [Sisimai::Fact] argvs Decoded email object
69
+ # @return [String] Bounce reason
70
+ def find(argvs)
71
+ return nil if argvs.nil?
72
+ return "" if argvs["diagnosticcode"].empty?
73
+ return "" if argvs["command"] != "" && argvs["command"] != "DATA"
74
+
75
+ deliversby = "" # [String] Local Delivery Agent name
76
+ reasontext = "" # [String] Error reason
77
+ issuedcode = argvs["diagnosticcode"].downcase
78
+
79
+ LocalAgent.each_key do |e|
80
+ # Find a lcoal delivery agent name from the entire message body
81
+ next unless LocalAgent[e].any? { |a| issuedcode.include?(a) }
82
+ deliversby = e; break
83
+ end
84
+ return "" if deliversby.empty?
85
+
86
+ MessagesOf[deliversby].each_key do |e|
87
+ # The key is a bounce reason name
88
+ next unless MessagesOf[deliversby][e].any? { |a| issuedcode.include?(a) }
89
+ reasontext = e; break
90
+ end
91
+
92
+ reasontext = "mailererror" if reasontext.empty?
93
+ return reasontext
94
+ end
95
+ end
96
+ end
97
+ end
98
+
@@ -48,7 +48,7 @@ module Sisimai::Lhost
48
48
 
49
49
  if e.start_with?('>>> ') && e.index('@') > 1
50
50
  # >>> kijitora@example.org <kijitora@example.org>
51
- if v['recipient']
51
+ if v["recipient"] != ""
52
52
  # There are multiple recipient addresses in the message body.
53
53
  dscontents << Sisimai::Lhost.DELIVERYSTATUS
54
54
  v = dscontents[-1]
@@ -2,16 +2,57 @@ module Sisimai::Lhost
2
2
  # Sisimai::Lhost::AmazonSES decodes a bounce email which created by Amazon Simple Email Service
3
3
  # https://aws.amazon.com/ses/. Methods in the module are called from only Sisimai::Message.
4
4
  module AmazonSES
5
+ # ---------------------------------------------------------------------------------------------
6
+ # "notificationType": "Bounce"
7
+ # https://docs.aws.amazon.com/ses/latest/dg/notification-contents.html#bounce-object
8
+ #
9
+ # Bounce types
10
+ # The bounce object contains a bounce type of Undetermined, Permanent, or Transient. The
11
+ # Permanent and Transient bounce types can also contain one of several bounce subtypes.
12
+ #
13
+ # When you receive a bounce notification with a bounce type of Transient, you might be
14
+ # able to send email to that recipient in the future if the issue that caused the message
15
+ # to bounce is resolved.
16
+ #
17
+ # When you receive a bounce notification with a bounce type of Permanent, it's unlikely
18
+ # that you'll be able to send email to that recipient in the future. For this reason, you
19
+ # should immediately remove the recipient whose address produced the bounce from your
20
+ # mailing lists.
21
+ #
22
+ # "bounceType"/"bounceSubType" "Desription"
23
+ # Undetermined/Undetermined -- The bounce message didn't contain enough information for
24
+ # Amazon SES to determine the reason for the bounce.
25
+ #
26
+ # Permanent/General ---------- When you receive this type of bounce notification, you should
27
+ # immediately remove the recipient's email address from your
28
+ # mailing list.
29
+ # Permanent/NoEmail ---------- It was not possible to retrieve the recipient email address
30
+ # from the bounce message.
31
+ # Permanent/Suppressed ------- The recipient's email address is on the Amazon SES suppression
32
+ # list because it has a recent history of producing hard bounces.
33
+ # Permanent/OnAccountSuppressionList
34
+ # Amazon SES has suppressed sending to this address because it
35
+ # is on the account-level suppression list.
36
+ #
37
+ # Transient/General ---------- You might be able to send a message to the same recipient
38
+ # in the future if the issue that caused the message to bounce
39
+ # is resolved.
40
+ # Transient/MailboxFull ------ the recipient's inbox was full.
41
+ # Transient/MessageTooLarge -- message you sent was too large
42
+ # Transient/ContentRejected -- message you sent contains content that the provider doesn't allow
43
+ # Transient/AttachmentRejected the message contained an unacceptable attachment
5
44
  class << self
6
45
  require 'sisimai/lhost'
7
46
 
8
- # https://aws.amazon.com/ses/
9
- Indicators = Sisimai::Lhost.INDICATORS
10
- Boundaries = ['Content-Type: message/rfc822'].freeze
11
- StartingOf = {
12
- message: ['The following message to <', 'An error occurred while trying to deliver the mail'],
47
+ ReasonPair = {
48
+ "Supressed" => "suppressed",
49
+ "OnAccountSuppressionList" => "suppressed",
50
+ "General" => "onhold",
51
+ "MailboxFull" => "mailboxfull",
52
+ "MessageTooLarge" => "mesgtoobig",
53
+ "ContentRejected" => "contenterror",
54
+ "AttachmentRejected" => "securityerror",
13
55
  }.freeze
14
- MessagesOf = { 'expired' => ['Delivery expired'] }.freeze
15
56
 
16
57
  # @abstract Decodes the bounce message from Amazon SES
17
58
  # @param [Hash] mhead Message headers of a bounce email
@@ -19,317 +60,160 @@ module Sisimai::Lhost
19
60
  # @return [Hash] Bounce data list and message/rfc822 part
20
61
  # @return [Nil] it failed to decode or the arguments are missing
21
62
  def inquire(mhead, mbody)
22
- dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
23
- recipients = 0 # (Integer) The number of 'Final-Recipient' header
24
-
25
- if mbody.start_with?('{')
26
- # The message body is JSON string
27
- return nil unless mhead['x-amz-sns-message-id']
28
- return nil if mhead['x-amz-sns-message-id'].empty?
29
-
30
- # https://docs.aws.amazon.com/en_us/ses/latest/DeveloperGuide/notification-contents.html
31
- bouncetype = {
32
- 'Permanent' => { 'General' => '', 'NoEmail' => '', 'Suppressed' => '' },
33
- 'Transient' => {
34
- 'General' => '',
35
- 'MailboxFull' => 'mailboxfull',
36
- 'MessageTooLarge' => 'mesgtoobig',
37
- 'ContentRejected' => '',
38
- 'AttachmentRejected' => '',
39
- },
40
- }.freeze
41
- jsonstring = ''
42
- sespayload = nil
43
- foldedline = false
44
- bodyslices = mbody.split("\n")
45
-
46
- while e = bodyslices.shift do
47
- # Find JSON string from the message body
48
- next if e.empty?
49
- break if e == '--'
50
-
51
- # The line starts with " ", continued from !\n.
52
- e.lstrip! if foldedline
53
- foldedline = false
54
-
55
- if e.end_with?('!')
56
- # ... long long line ...![\n]
57
- e.chomp!('!')
58
- foldedline = true
59
- end
60
- jsonstring << e
61
- end
62
-
63
- begin
64
- if RUBY_PLATFORM.start_with?('java')
65
- # java-based ruby environment like JRuby.
66
- require 'jrjackson'
67
- jsonobject = JrJackson::Json.load(jsonstring)
68
-
69
- # 'Message' => '{"notificationType":"Bounce",...
70
- sespayload = JrJackson::Json.load(jsonobject['Message']) if jsonobject['Message']
71
- else
72
- # Matz' Ruby Implementation
73
- require 'oj'
74
- jsonobject = Oj.load(jsonstring)
75
-
76
- # 'Message' => '{"notificationType":"Bounce",...
77
- sespayload = Oj.load(jsonobject['Message']) if jsonobject['Message']
78
- end
79
- sespayload ||= jsonobject
80
-
81
- rescue StandardError => ce
82
- # Something wrong in decoding JSON
83
- warn ' ***warning: Failed to decode JSON: ' << ce.to_s
84
- return nil
63
+ return nil if mbody.include?("{") == false
64
+ return nil if mhead.has_key?("x-amz-sns-message-id") == false
65
+ return nil if mhead["x-amz-sns-message-id"].empty?
66
+
67
+ proceedsto = false
68
+ sespayload = mbody
69
+ while true
70
+ # Remote the following string begins with "--"
71
+ # --
72
+ # If you wish to stop receiving notifications from this topic, please click or visit the link below to unsubscribe:
73
+ # https://sns.us-west-2.amazonaws.com/unsubscribe.html?SubscriptionArn=arn:aws:sns:us-west-2:1...
74
+ p1 = mbody.index("\n\n--\n")
75
+ sespayload = mbody[0, p1] if p1
76
+ sespayload = sespayload.gsub("!\n ", "")
77
+ p2 = sespayload.index('"Message"')
78
+
79
+ if p2
80
+ # The JSON included in the email is a format like the following:
81
+ # {
82
+ # "Type" : "Notification",
83
+ # "MessageId" : "02f86d9b-eecf-573d-b47d-3d1850750c30",
84
+ # "TopicArn" : "arn:aws:sns:us-west-2:123456789012:SES-EJ-B",
85
+ # "Message" : "{\"notificationType\"...
86
+ sespayload = sespayload.gsub("\\", "")
87
+ p3 = sespayload.index("{", p2 + 9)
88
+ p4 = sespayload.index("\n", p2 + 9)
89
+ sespayload = sespayload[p3, p4 - p3]
90
+ sespayload = sespayload.chop if sespayload[-1, 1] == ","
91
+ sespayload = sespayload.chop if sespayload[-1, 1] == '"'
85
92
  end
86
93
 
87
- rfc822head = {} # (Hash) Check flags for headers in RFC822 part
88
- labeltable = {
89
- 'Bounce' => 'bouncedRecipients',
90
- 'Complaint' => 'complainedRecipients',
91
- }
92
- p = sespayload
93
- v = nil
94
-
95
- if %w[Bounce Complaint].index(p['notificationType'])
96
- # { "notificationType":"Bounce", "bounce": { "bounceType":"Permanent",...
97
- o = p[p['notificationType'].downcase].dup
98
- r = o[labeltable[p['notificationType']]] || []
99
-
100
- while e = r.shift do
101
- # 'bouncedRecipients' => [ { 'emailAddress' => 'bounce@si...' }, ... ]
102
- # 'complainedRecipients' => [ { 'emailAddress' => 'complaint@si...' }, ... ]
103
- next unless Sisimai::Address.is_emailaddress(e['emailAddress'])
104
-
105
- v = dscontents[-1]
106
- if v['recipient']
107
- # There are multiple recipient addresses in the message body.
108
- dscontents << Sisimai::Lhost.DELIVERYSTATUS
109
- v = dscontents[-1]
110
- end
111
- recipients += 1
112
- v['recipient'] = e['emailAddress']
113
-
114
- if p['notificationType'] == 'Bounce'
115
- # 'bouncedRecipients => [ {
116
- # 'emailAddress' => 'bounce@simulator.amazonses.com',
117
- # 'action' => 'failed',
118
- # 'status' => '5.1.1',
119
- # 'diagnosticCode' => 'smtp; 550 5.1.1 user unknown'
120
- # }, ... ]
121
- v['action'] = e['action']
122
- v['status'] = e['status']
123
-
124
- if p0 = e['diagnosticCode'].index(';') || -1; p0 > 3
125
- # Diagnostic-Code: SMTP; 550 5.1.1 <userunknown@example.jp>... User Unknown
126
- v['spec'] = e['diagnosticCode'][0, p0].upcase
127
- v['diagnosis'] = e['diagnosticCode'][p0 + 2, e['diagnosticCode'].size]
128
- else
129
- v['diagnosis'] = e['diagnosticCode']
130
- end
131
-
132
- # 'reportingMTA' => 'dsn; a27-23.smtp-out.us-west-2.amazonses.com',
133
- p0 = o['reportingMTA'].index('dsn; ')
134
- v['lhost'] = Sisimai::String.sweep(o['reportingMTA'][p0 + 5, o['reportingMTA'].size]) if p0
135
-
136
- if bouncetype[o['bounceType']] && bouncetype[o['bounceType']][o['bounceSubType']]
137
- # 'bounce' => {
138
- # 'bounceType' => 'Permanent',
139
- # 'bounceSubType' => 'General'
140
- # },
141
- v['reason'] = bouncetype[o['bounceType']][o['bounceSubType']]
142
- end
143
- else
144
- # 'complainedRecipients' => [ {
145
- # 'emailAddress' => 'complaint@simulator.amazonses.com' }, ... ],
146
- v['reason'] = 'feedback'
147
- v['feedbacktype'] = o['complaintFeedbackType'] || ''
148
- end
149
-
150
- v['date'] = o['timestamp'] || p['mail']['timestamp']
151
- v['date'].sub!(/[.]\d+Z\z/, '')
152
- end
153
- elsif p['notificationType'] == 'Delivery'
154
- # { "notificationType":"Delivery", "delivery": { ...
155
- o = p['delivery'].dup
156
- r = o['recipients'] || []
157
-
158
- while e = r.shift do
159
- # 'delivery' => {
160
- # 'timestamp' => '2016-11-23T12:01:03.512Z',
161
- # 'processingTimeMillis' => 3982,
162
- # 'reportingMTA' => 'a27-29.smtp-out.us-west-2.amazonses.com',
163
- # 'recipients' => [
164
- # 'success@simulator.amazonses.com'
165
- # ],
166
- # 'smtpResponse' => '250 2.6.0 Message received'
167
- # },
168
- next unless Sisimai::Address.is_emailaddress(e)
169
-
170
- v = dscontents[-1]
171
- if v['recipient']
172
- # There are multiple recipient addresses in the message body.
173
- dscontents << Sisimai::Lhost.DELIVERYSTATUS
174
- v = dscontents[-1]
175
- end
176
- recipients += 1
177
- v['recipient'] = e
178
- v['lhost'] = o['reportingMTA'] || ''
179
- v['diagnosis'] = o['smtpResponse'] || ''
180
- v['status'] = Sisimai::SMTP::Status.find(v['diagnosis']) || ''
181
- v['reason'] = 'delivered'
182
- v['action'] = 'delivered'
183
-
184
- v['date'] = o['timestamp'] || p['mail']['timestamp']
185
- v['date'].sub!(/[.]\d+Z\z/, '')
186
- end
94
+ break if sespayload.include?("notificationType") == false
95
+ break if sespayload.start_with?("{") == false
96
+ break if sespayload.end_with?("}") == false
97
+ proceedsto = true; break
98
+ end
99
+ return nil if proceedsto == false
100
+
101
+ jsonobject = nil
102
+ begin
103
+ if RUBY_PLATFORM.start_with?("java")
104
+ # java-based ruby environment like JRuby.
105
+ require "jrjackson"
106
+ jsonobject = JrJackson::Json.load(sespayload)
187
107
  else
188
- # The value of "notificationType" is not any of "Bounce", "Complaint", or "Delivery".
189
- return nil
108
+ # Matz' Ruby Implementation
109
+ require "oj"
110
+ jsonobject = Oj.load(sespayload)
190
111
  end
191
- return nil unless recipients > 0
192
-
193
- if p['mail']['headers']
194
- # "headersTruncated":false,
195
- # "headers":[ { ...
196
- p['mail']['headers'].each do |e|
197
- # 'headers' => [ { 'name' => 'From', 'value' => 'neko@nyaan.jp' }, ... ],
198
- next unless %w[From To Subject Message-ID Date].index(e['name'])
199
- rfc822head[e['name'].downcase] = e['value']
112
+ rescue StandardError => ce
113
+ # Something wrong in decoding JSON
114
+ warn ' ***warning: Failed to decode JSON: ' << ce.to_s
115
+ return nil
116
+ end
117
+ return nil if jsonobject.has_key?("notificationType") == false
118
+
119
+ require "sisimai/string"
120
+ require "sisimai/rfc1123"
121
+ require "sisimai/smtp/reply"
122
+ require "sisimai/smtp/status"
123
+ require "sisimai/smtp/command"
124
+ dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
125
+ recipients = 0 # (Integer) The number of 'Final-Recipient' header
126
+ whatnotify = jsonobject["notificationType"][0, 1] || ""
127
+ v = dscontents[-1]
128
+
129
+ if whatnotify == "B"
130
+ # "notificationType":"Bounce"
131
+ p = jsonobject["bounce"]
132
+ r = p["bounceType"] == "Permanent" ? "5" : "4"
133
+
134
+ p["bouncedRecipients"].each do |e|
135
+ # {"emailAddress":"neko@example.jp", "action":"failed", "status":"5.1.1", "diagnosticCode": "..."}
136
+ if v["recipient"] != ""
137
+ # There are multiple recipient addresses in the message body.
138
+ dscontents << Sisimai::Lhost.DELIVERYSTATUS
139
+ v = dscontents[-1]
200
140
  end
141
+ v["recipient"] = e["emailAddress"]
142
+ v["diagnosis"] = Sisimai::String.sweep(e["diagnosticCode"])
143
+ v["command"] = Sisimai::SMTP::Command.find(v["diagnosis"])
144
+ v["action"] = e["action"]
145
+ v["status"] = Sisimai::SMTP::Status.find(v["diagnosis"], r)
146
+ v["replycode"] = Sisimai::SMTP::Reply.find(v["diagnosis"], v["status"])
147
+ v["date"] = p["timestamp"]
148
+ v["lhost"] = Sisimai::RFC1123.find(p["reportingMTA"])
149
+ recipients += 1
201
150
  end
202
151
 
203
- unless rfc822head['message-id']
204
- # Try to get the value of "Message-Id".
205
- if p['mail']['messageId']
206
- # 'messageId' => '01010157e48f9b9b-891e9a0e-9c9d-4773-9bfe-608f2ef4756d-000000'
207
- rfc822head['message-id'] = p['mail']['messageId']
208
- end
152
+ ReasonPair.each_key do |f|
153
+ # Try to find the bounce reason by "bounceSubType"
154
+ next unless ReasonPair[f] == p["bounceSubType"]
155
+ v["reason"] = f; break
209
156
  end
210
- return { 'ds' => dscontents, 'rfc822' => rfc822head }
211
157
 
212
- else
213
- # The message body is an email
214
- # :from => %r/\AMAILER-DAEMON[@]email[-]bounces[.]amazonses[.]com\z/,
215
- # :subject => %r/\ADelivery Status Notification [(]Failure[)]\z/,
216
- return nil if mhead['x-mailer'].to_s.start_with?('Amazon WorkMail')
217
-
218
- # X-SenderID: Sendmail Sender-ID Filter v1.0.0 nijo.example.jp p7V3i843003008
219
- # X-Original-To: 000001321defbd2a-788e31c8-2be1-422f-a8d4-cf7765cc9ed7-000000@email-bounces.amazonses.com
220
- # X-AWS-Outgoing: 199.255.192.156
221
- # X-SES-Outgoing: 2016.10.12-54.240.27.6
222
- match = 0
223
- match += 1 if mhead['x-aws-outgoing']
224
- match += 1 if mhead['x-ses-outgoing']
225
- return nil unless match > 0
226
-
227
- require 'sisimai/rfc1894'
228
- fieldtable = Sisimai::RFC1894.FIELDTABLE
229
- permessage = {} # (Hash) Store values of each Per-Message field
230
-
231
- emailparts = Sisimai::RFC5322.part(mbody, Boundaries)
232
- bodyslices = emailparts[0].split("\n")
233
- readslices = ['']
234
- readcursor = 0 # (Integer) Points the current cursor position
235
- v = nil
236
-
237
- while e = bodyslices.shift do
238
- # Read error messages and delivery status lines from the head of the email to the previous
239
- # line of the beginning of the original message.
240
- readslices << e # Save the current line for the next loop
241
-
242
- if readcursor == 0
243
- # Beginning of the bounce message or message/delivery-status part
244
- if e.start_with?(StartingOf[:message][0], StartingOf[:message][1])
245
- readcursor |= Indicators[:deliverystatus]
246
- next
247
- end
248
- end
249
- next if (readcursor & Indicators[:deliverystatus]) == 0
250
- next if e.empty?
251
-
252
- if f = Sisimai::RFC1894.match(e)
253
- # "e" matched with any field defined in RFC3464
254
- next unless o = Sisimai::RFC1894.field(e)
158
+ elsif whatnotify == "C"
159
+ # "notificationType":"Complaint"
160
+ p = jsonobject["complaint"]
161
+ p["complainedRecipients"].each do |e|
162
+ # {"emailAddress":"neko@example.jp"}
163
+ if v["recipient"] != ""
164
+ # There are multiple recipient addresses in the message body.
165
+ dscontents << Sisimai::Lhost.DELIVERYSTATUS
255
166
  v = dscontents[-1]
256
-
257
- if o[-1] == 'addr'
258
- # Final-Recipient: rfc822; kijitora@example.jp
259
- # X-Actual-Recipient: rfc822; kijitora@example.co.jp
260
- if o[0] == 'final-recipient'
261
- # Final-Recipient: rfc822; kijitora@example.jp
262
- if v['recipient']
263
- # There are multiple recipient addresses in the message body.
264
- dscontents << Sisimai::Lhost.DELIVERYSTATUS
265
- v = dscontents[-1]
266
- end
267
- v['recipient'] = o[2]
268
- recipients += 1
269
- else
270
- # X-Actual-Recipient: rfc822; kijitora@example.co.jp
271
- v['alias'] = o[2]
272
- end
273
- elsif o[-1] == 'code'
274
- # Diagnostic-Code: SMTP; 550 5.1.1 <userunknown@example.jp>... User Unknown
275
- v['spec'] = o[1]
276
- v['diagnosis'] = o[2]
277
- else
278
- # Other DSN fields defined in RFC3464
279
- next unless fieldtable[o[0]]
280
- v[fieldtable[o[0]]] = o[2]
281
-
282
- next unless f
283
- permessage[fieldtable[o[0]]] = o[2]
284
- end
285
- else
286
- # Continued line of the value of Diagnostic-Code field
287
- next unless readslices[-2].start_with?('Diagnostic-Code:')
288
- next unless e.start_with?(' ')
289
- v['diagnosis'] << ' ' << e
290
- readslices[-1] = 'Diagnostic-Code: ' << e
291
- end
292
- end
293
-
294
- if recipients == 0 && mbody =~ /notificationType/
295
- # Try to decode with Sisimai::Lhost::AmazonSES module
296
- j = Sisimai::Lhost::AmazonSES.json(mhead, mbody)
297
-
298
- if j['ds'].is_a? Array
299
- # Update dscontents
300
- dscontents = j['ds']
301
- recipients = j['ds'].size
302
167
  end
168
+ v["recipient"] = e["emailAddress"]
169
+ v["reason"] = "feedback"
170
+ v["feedbacktype"] = p["complaintFeedbackType"]
171
+ v["date"] = p["timestamp"]
172
+ v["diagnosis"] = sprintf('"feedbackid":"%s", "useragent":"%s"}', p["feedbackId"], p["userAgent"])
173
+ recipients += 1
303
174
  end
304
- return nil unless recipients > 0
305
175
 
306
- dscontents.each do |e|
307
- # Set default values if each value is empty.
308
- permessage.each_key { |a| e[a] ||= permessage[a] || '' }
309
-
310
- e['diagnosis'] = Sisimai::String.sweep(e['diagnosis'].to_s.tr("\n", ' '))
311
- if e['status'].to_s.end_with?('.0.0', '.1.0')
312
- # Get other D.S.N. value from the error message
313
- # 5.1.0 - Unknown address error 550-'5.7.1 ...
314
- errormessage = e['diagnosis']
315
- p1 = e['diagnosis'].index("-'"); p1 = e['diagnosis'].index('-"') unless p1
316
- p2 = e['diagnosis'].rindex("' "); p2 = e['diagnosis'].rindex('" ') unless p2
317
- errormessage = e['diagnosis'][p1 + 2, p2 - p1 - 2] if p1 && p2
318
- e['status'] = Sisimai::SMTP::Status.find(errormessage) || e['status']
319
- end
320
- e['replycode'] ||= Sisimai::SMTP::Reply.find(e['diagnosis'], e['status'])
321
-
322
- MessagesOf.each_key do |r|
323
- # Verify each regular expression of session errors
324
- next unless MessagesOf[r].any? { |a| e['diagnosis'].include?(a) }
325
- e['reason'] = r
326
- break
176
+ elsif whatnotify == "D"
177
+ # "notificationType":"Delivery"
178
+ p = jsonobject["delivery"]
179
+ p["recipients"].each do |e|
180
+ # {"recipients":["neko@example.jp"]}
181
+ if v["recipient"] != ""
182
+ # There are multiple recipient addresses in the message body.
183
+ dscontents << Sisimai::Lhost.DELIVERYSTATUS
184
+ v = dscontents[-1]
327
185
  end
186
+ v["recipient"] = e
187
+ v["reason"] = "delivered"
188
+ v["action"] = "delivered"
189
+ v["date"] = p["timestamp"]
190
+ v["lhost"] = Sisimai::RFC1123.find(p["reportingMTA"])
191
+ v["diagnosis"] = Sisimai::String.sweep(p["smtpResponse"])
192
+ v["command"] = Sisimai::SMTP::Command.find(v["diagnosis"])
193
+ v["status"] = Sisimai::SMTP::Status.find(v["diagnosis"], "2")
194
+ v["replycode"] = Sisimai::SMTP::Reply.find(v["diagnosis"], "2")
195
+ recipients += 1
328
196
  end
329
197
 
330
- return { 'ds' => dscontents, 'rfc822' => emailparts[1] }
331
- end # END of the decoder for the email message
332
-
198
+ else
199
+ # Unknown "notificationType" value
200
+ warn sprintf(" ***warning: There is no notificationType field or unknown type of notificationType field")
201
+ return nil
202
+ end
203
+ return nil if recipients == 0
204
+
205
+ # Date::Time.strptime() cannot parse "2016-11-25T01:49:01.000Z" format
206
+ dscontents.each { |e| e["date"] = e["date"].sub("T", " ").sub(/[.]\d{3}Z/, "") }
207
+
208
+ cv = ""
209
+ jsonobject["mail"]["headers"].each do |e|
210
+ cv << sprintf("%s: %s\n", e["name"], e["value"])
211
+ end
212
+ %w[date subject].each do |e|
213
+ next if jsonobject["mail"]["commonHeaders"].has_key?(e) == false
214
+ cv << sprintf("%s: %s\n", e.capitalize, jsonobject["mail"]["commonHeaders"][e])
215
+ end
216
+ return { "ds" => dscontents, "rfc822" => cv }
333
217
  end
334
218
  def description; return 'Amazon SES(Sending): https://aws.amazon.com/ses/'; end
335
219
  end