sisimai 5.1.0 → 5.2.0

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
@@ -4,15 +4,26 @@ module Sisimai::Lhost
4
4
  module Exim
5
5
  class << self
6
6
  require 'sisimai/lhost'
7
+ require 'sisimai/rfc1894'
7
8
 
8
9
  Indicators = Sisimai::Lhost.INDICATORS
10
+ FieldTable = Sisimai::RFC1894.FIELDTABLE
9
11
  Boundaries = [
10
12
  # deliver.c:6423| if (bounce_return_body) fprintf(f,
11
13
  # deliver.c:6424|"------ This is a copy of the message, including all the headers. ------\n");
12
14
  # deliver.c:6425| else fprintf(f,
13
15
  # deliver.c:6426|"------ This is a copy of the message's headers. ------\n");
14
- '------ This is a copy of the message, including all the headers. ------',
15
- 'Content-Type: message/rfc822',
16
+ "------ This is a copy of the message, including all the headers. ------",
17
+ "Content-Type: message/rfc822",
18
+ "Included is a copy of the message header:\n-----------------------------------------", # MXLogic
19
+ ].freeze
20
+ EmailTitle = [
21
+ "Delivery Status Notification",
22
+ "Mail delivery failed",
23
+ "Mail failure",
24
+ "Message frozen",
25
+ "Warning: message ",
26
+ "error(s) in forwarding or filtering",
16
27
  ].freeze
17
28
  StartingOf = {
18
29
  # Error text strings which defined in exim/src/deliver.c
@@ -32,89 +43,86 @@ module Sisimai::Lhost
32
43
  # deliver.c:6304|"could not be delivered to one or more of its recipients. The following\n"
33
44
  # deliver.c:6305|"address(es) failed:\n", sender_address);
34
45
  # deliver.c:6306| }
35
- deliverystatus: ['Content-Type: message/delivery-status'],
36
- frozen: [' has been frozen', ' was frozen on arrival'],
37
- message: [
38
- 'This message was created automatically by mail delivery software.',
39
- 'A message that you sent was rejected by the local scannning code',
40
- 'A message that you sent contained one or more recipient addresses ',
41
- 'A message that you sent could not be delivered to all of its recipients',
42
- ' has been frozen',
43
- ' was frozen on arrival',
44
- ' router encountered the following error(s):',
46
+ "alias" => [" an undisclosed address"],
47
+ "command" => ["SMTP error from remote ", "LMTP error after "],
48
+ "deliverystatus" => ["Content-Type: message/delivery-status"],
49
+ "frozen" => [" has been frozen", " was frozen on arrival"],
50
+ "message" => [
51
+ "This message was created automatically by mail delivery software.",
52
+ "A message that you sent was rejected by the local scannning code",
53
+ "A message that you sent contained one or more recipient addresses ",
54
+ "A message that you sent could not be delivered to all of its recipients",
55
+ " has been frozen",
56
+ " was frozen on arrival",
57
+ " router encountered the following error(s):",
45
58
  ],
46
59
  }.freeze
47
- MarkingsOf = { alias: ' an undisclosed address' }.freeze
48
- ReCommands = [
49
- # transports/smtp.c:564| *message = US string_sprintf("SMTP error from remote mail server after %s%s: "
50
- # transports/smtp.c:837| string_sprintf("SMTP error from remote mail server after RCPT TO:<%s>: "
51
- %r/SMTP error from remote (?:mail server|mailer) after ([A-Za-z]{4})/,
52
- %r/SMTP error from remote (?:mail server|mailer) after end of ([A-Za-z]{4})/,
53
- %r/LMTP error after ([A-Za-z]{4})/,
54
- %r/LMTP error after end of ([A-Za-z]{4})/,
55
- ].freeze
56
60
  MessagesOf = {
57
61
  # find exim/ -type f -exec grep 'message = US' {} /dev/null \;
58
62
  # route.c:1158| DEBUG(D_uid) debug_printf("getpwnam() returned NULL (user not found)\n");
59
- 'userunknown' => ['user not found'],
63
+ # find exim/ -type f -exec grep 'message = US' {} /dev/null \;
64
+ # route.c:1158| DEBUG(D_uid) debug_printf("getpwnam() returned NULL (user not found)\n");
65
+ "userunknown" => ["user not found"],
60
66
  # transports/smtp.c:3524| addr->message = US"all host address lookups failed permanently";
61
67
  # routers/dnslookup.c:331| addr->message = US"all relevant MX records point to non-existent hosts";
62
68
  # route.c:1826| uschar *message = US"Unrouteable address";
63
- 'hostunknown' => [
64
- 'all host address lookups failed permanently',
65
- 'all relevant MX records point to non-existent hosts',
66
- 'Unrouteable address',
69
+ "hostunknown" => [
70
+ "all host address lookups failed permanently",
71
+ "all relevant MX records point to non-existent hosts",
72
+ "Unrouteable address",
67
73
  ],
68
74
  # transports/appendfile.c:2567| addr->user_message = US"mailbox is full";
69
75
  # transports/appendfile.c:3049| addr->message = string_sprintf("mailbox is full "
70
76
  # transports/appendfile.c:3050| "(quota exceeded while writing to file %s)", filename);
71
- 'mailboxfull' => ['mailbox is full', 'error: quota exceed'],
77
+ "mailboxfull" => [
78
+ "mailbox is full",
79
+ "error: quota exceed",
80
+ ],
72
81
  # routers/dnslookup.c:328| addr->message = US"an MX or SRV record indicated no SMTP service";
73
82
  # transports/smtp.c:3502| addr->message = US"no host found for existing SMTP connection";
74
- 'notaccept' => [
75
- 'an MX or SRV record indicated no SMTP service',
76
- 'no host found for existing SMTP connection',
83
+ "notaccept" => [
84
+ "an MX or SRV record indicated no SMTP service",
85
+ "no host found for existing SMTP connection",
77
86
  ],
78
87
  # parser.c:666| *errorptr = string_sprintf("%s (expected word or \"<\")", *errorptr);
79
88
  # parser.c:701| if(bracket_count++ > 5) FAILED(US"angle-brackets nested too deep");
80
89
  # parser.c:738| FAILED(US"domain missing in source-routed address");
81
90
  # parser.c:747| : string_sprintf("malformed address: %.32s may not follow %.*s",
82
- 'syntaxerror' => [
83
- 'angle-brackets nested too deep',
91
+ "syntaxerror" => [
92
+ "angle-brackets nested too deep",
84
93
  'expected word or "<"',
85
- 'domain missing in source-routed address',
86
- 'malformed address:',
94
+ "domain missing in source-routed address",
95
+ "malformed address:",
87
96
  ],
88
97
  # deliver.c:5614| addr->message = US"delivery to file forbidden";
89
98
  # deliver.c:5624| addr->message = US"delivery to pipe forbidden";
90
99
  # transports/pipe.c:1156| addr->user_message = US"local delivery failed";
91
- 'systemerror' => [
92
- 'delivery to file forbidden',
93
- 'delivery to pipe forbidden',
94
- 'local delivery failed',
95
- 'LMTP error after ',
100
+ "systemerror" => [
101
+ "delivery to file forbidden",
102
+ "delivery to pipe forbidden",
103
+ "local delivery failed",
104
+ "LMTP error after ",
96
105
  ],
97
106
  # deliver.c:5425| new->message = US"Too many \"Received\" headers - suspected mail loop";
98
- 'contenterror' => ['Too many "Received" headers'],
107
+ "contenterror" => ['Too many "Received" headers'],
99
108
  }.freeze
100
-
101
- # retry.c:902| addr->message = (addr->message == NULL)? US"retry timeout exceeded" :
102
- # deliver.c:7475| "No action is required on your part. Delivery attempts will continue for\n"
103
- # smtp.c:3508| US"retry time not reached for any host after a long failure period" :
104
- # smtp.c:3508| US"all hosts have been failing for a long time and were last tried "
105
- # "after this message arrived";
106
- # deliver.c:7459| print_address_error(addr, f, US"Delay reason: ");
107
- # deliver.c:7586| "Message %s has been frozen%s.\nThe sender is <%s>.\n", message_id,
108
- # receive.c:4021| moan_tell_someone(freeze_tell, NULL, US"Message frozen on arrival",
109
- # receive.c:4022| "Message %s was frozen on arrival by %s.\nThe sender is <%s>.\n",
110
109
  DelayedFor = [
111
- 'retry timeout exceeded',
112
- 'No action is required on your part',
113
- 'retry time not reached for any host after a long failure period',
114
- 'all hosts have been failing for a long time and were last tried',
115
- 'Delay reason: ',
116
- 'has been frozen',
117
- 'was frozen on arrival by ',
110
+ # retry.c:902| addr->message = (addr->message == NULL)? US"retry timeout exceeded" :
111
+ # deliver.c:7475| "No action is required on your part. Delivery attempts will continue for\n"
112
+ # smtp.c:3508| US"retry time not reached for any host after a long failure period" :
113
+ # smtp.c:3508| US"all hosts have been failing for a long time and were last tried "
114
+ # "after this message arrived";
115
+ # deliver.c:7459| print_address_error(addr, f, US"Delay reason: ");
116
+ # deliver.c:7586| "Message %s has been frozen%s.\nThe sender is <%s>.\n", message_id,
117
+ # receive.c:4021| moan_tell_someone(freeze_tell, NULL, US"Message frozen on arrival",
118
+ # receive.c:4022| "Message %s was frozen on arrival by %s.\nThe sender is <%s>.\n",
119
+ "retry timeout exceeded",
120
+ "No action is required on your part",
121
+ "retry time not reached for any host after a long failure period",
122
+ "all hosts have been failing for a long time and were last tried",
123
+ "Delay reason: ",
124
+ "has been frozen",
125
+ "was frozen on arrival by ",
118
126
  ].freeze
119
127
 
120
128
  # @abstract Decodes the bounce message from Exim
@@ -123,46 +131,67 @@ module Sisimai::Lhost
123
131
  # @return [Hash] Bounce data list and message/rfc822 part
124
132
  # @return [Nil] it failed to decode or the arguments are missing
125
133
  def inquire(mhead, mbody)
126
- return nil if mhead['from'].include?('.mail.ru')
127
-
128
134
  # Message-Id: <E1P1YNN-0003AD-Ga@example.org>
129
135
  # X-Failed-Recipients: kijitora@example.ed.jp
130
- match = 0
131
- msgid = mhead['message-id'] || ''
132
- match += 1 if mhead['from'].start_with?('Mail Delivery System')
133
- match += 1 if msgid.start_with?('<') && msgid.index('-') == 8 && msgid.index('@') == 18
134
- match += 1 if %w[
135
- 'Delivery Status Notification',
136
- 'Mail delivery failed',
137
- 'Mail failure',
138
- 'Message frozen',
139
- 'Warning: message ',
140
- 'error(s) in forwarding or filtering'].any? { |a| mhead['subject'].include?(a) }
141
- return nil if match < 2
142
-
143
- fieldtable = Sisimai::RFC1894.FIELDTABLE
136
+ thirdparty = false
137
+ proceedsto = 0
138
+ messageidv = mhead["message-id"] || ""
139
+
140
+ proceedsto += 1 if mhead["from"].include?("Mail Delivery System")
141
+ while messageidv != "" do
142
+ # Message-Id: <E1P1YNN-0003AD-Ga@example.org>
143
+ break if messageidv.index("<") != 0
144
+ break if messageidv.index("-") != 8
145
+ break if messageidv.index('@') != 18
146
+ proceedsto += 1; break
147
+ end
148
+ EmailTitle.each do |e|
149
+ # Subject: Mail delivery failed: returning message to sender
150
+ # Subject: Mail delivery failed
151
+ # Subject: Message frozen
152
+ next if mhead["subject"].include?(e) == false
153
+ proceedsto += 1; break
154
+ end
155
+
156
+ while true
157
+ # Exim clones of the third Parties
158
+ # 1. McAfee Saas (Formerly MXLogic)
159
+ if mhead.has_key?("x-mx-bounce") then thirdparty = true; break; end
160
+ if mhead.has_key?("x-mxl-hash") then thirdparty = true; break; end
161
+ if mhead.has_key?("x-mxl-notehash") then thirdparty = true; break; end
162
+ if messageidv.include?("<mxl~") then thirdparty = true; break; end
163
+ break
164
+ end
165
+ return nil if proceedsto < 2 && thirdparty == false
166
+
167
+ require "sisimai/address"
168
+ require "sisimai/rfc2045"
169
+ require "sisimai/smtp/command"
170
+
144
171
  dscontents = [Sisimai::Lhost.DELIVERYSTATUS]
145
172
  emailparts = Sisimai::RFC5322.part(mbody, Boundaries)
146
173
  bodyslices = emailparts[0].split("\n")
147
174
  readcursor = 0 # (Integer) Points the current cursor position
148
- nextcursor = 0
175
+ nextcursor = false
149
176
  recipients = 0 # (Integer) The number of 'Final-Recipient' header
150
- boundary00 = '' # (String) Boundary string
177
+ boundary00 = "" # (String) Boundary string
151
178
  v = nil
152
179
 
153
- if mhead['content-type']
180
+ if mhead["content-type"]
154
181
  # Get the boundary string and set regular expression for matching with the boundary string.
155
- boundary00 = Sisimai::RFC2045.boundary(mhead['content-type']) || ''
182
+ boundary00 = Sisimai::RFC2045.boundary(mhead["content-type"]) || ""
156
183
  end
157
184
 
185
+ p1 = -1; p2 = -1;
158
186
  while e = bodyslices.shift do
159
187
  # Read error messages and delivery status lines from the head of the email to the previous
160
188
  # line of the beginning of the original message.
161
189
  if readcursor == 0
162
190
  # Beginning of the bounce message or message/delivery-status part
163
- if StartingOf[:message].any? { |a| e.include?(a) }
191
+ if StartingOf["message"].any? { |a| e.include?(a) }
192
+ # Check the message defined in StartingOf["message"], ["frozen"]
164
193
  readcursor |= Indicators[:deliverystatus]
165
- next unless StartingOf[:frozen].any? { |a| e.include?(a) }
194
+ next unless StartingOf["frozen"].any? { |a| e.include?(a) }
166
195
  end
167
196
  end
168
197
  next if (readcursor & Indicators[:deliverystatus]) == 0
@@ -182,26 +211,27 @@ module Sisimai::Lhost
182
211
  ce = false
183
212
  while true
184
213
  # Check if the line matche the following patterns:
185
- break unless e.start_with?(' ') # The line should start with " " (2 spaces)
186
- break unless e.include?('@') # "@" should be included (email)
187
- break unless e.include?('.') # "." should be included (domain part)
188
- break unless e.include?('pipe to |') == false # Exclude "pipe to /path/to/prog" line
189
-
190
- break unless e[2, 1] != ' '
191
- break unless e[2, 1] != '<'
214
+ break if e.start_with?(" ") == false # The line should start with " " (2 spaces)
215
+ break if e.include?('@') == false # "@" should be included (email)
216
+ break if e.include?(".") == false # "." should be included (domain part)
217
+ break if e.include?("pipe to |") # Exclude "pipe to /path/to/prog" line
218
+
219
+ cx = e[2, 1]
220
+ break if cx == " " # The 3rd character is " "
221
+ break if thirdparty == false && cx == "<" # MXLogic returns " <neko@example.jp>:..."
192
222
  ce = true
193
223
  break
194
224
  end
195
225
 
196
- if ce || e.include?(MarkingsOf[:alias])
226
+ if ce == true || e.include?(StartingOf["alias"][0])
197
227
  # The line is including an email address
198
- if v['recipient']
228
+ if v["recipient"] != ""
199
229
  # There are multiple recipient addresses in the message body.
200
230
  dscontents << Sisimai::Lhost.DELIVERYSTATUS
201
231
  v = dscontents[-1]
202
232
  end
203
233
 
204
- if e.include?(MarkingsOf[:alias])
234
+ if e.include?(StartingOf["alias"][0])
205
235
  # The line does not include an email address
206
236
  # deliver.c:4549| printed = US"an undisclosed address";
207
237
  # an undisclosed address
@@ -211,8 +241,8 @@ module Sisimai::Lhost
211
241
  # kijitora@example.jp
212
242
  # sabineko@example.jp: forced freeze
213
243
  # mikeneko@example.jp <nekochan@example.org>: ...
214
- p1 = e.index(' <') || -1
215
- p2 = e.index('>:') || -1
244
+ p1 = e.index("<") || -1
245
+ p2 = e.index(">:") || -1
216
246
 
217
247
  if p1 > 1 && p2 > 1
218
248
  # There are an email address and an error message in the line
@@ -224,85 +254,78 @@ module Sisimai::Lhost
224
254
  # parser.c:748| s-1, (int)(s - US mailbox - 1), mailbox);
225
255
  # parser.c:749| goto PARSE_FAILED;
226
256
  # parser.c:750| }
227
- cv = Sisimai::Address.s3s4(e[p1 + 1, p2 - p1 - 1])
228
- v['diagnosis'] = Sisimai::String.sweep(e[p2 + 1, e.size])
257
+ cv = Sisimai::Address.s3s4(e[p1, p2 - p1 - 1])
258
+ v["diagnosis"] = Sisimai::String.sweep(e[p2 + 1, e.size])
229
259
  else
230
260
  # There is an email address only in the line
231
261
  # kijitora@example.jp
232
262
  cv = Sisimai::Address.s3s4(e[2, e.size])
233
263
  end
234
264
  end
235
- v['recipient'] = cv
265
+ v["recipient"] = cv
236
266
  recipients += 1
237
267
 
238
- elsif e.include?(' (generated from ') || e.include?(' generated by ')
268
+ elsif e.include?(" (generated from ") || e.include?(" generated by ")
239
269
  # (generated from kijitora@example.jp)
240
270
  # pipe to |/bin/echo "Some pipe output"
241
271
  # generated by userx@myhost.test.ex
242
- v['alias'] = Sisimai::Address.s3s4(e[e.rindex(' ') + 1, e.size])
243
-
272
+ e.split(" ").each do |f|
273
+ # Find the alias address
274
+ next unless f.include?('@')
275
+ v["alias"] = Sisimai::Address.s3s4(f)
276
+ end
244
277
  else
245
- next if e.empty?
246
-
247
- if StartingOf[:frozen].any? { |a| e.include?(a) }
278
+ if StartingOf["frozen"].any? { |a| e.include?(a) }
248
279
  # Message *** has been frozen by the system filter.
249
280
  # Message *** was frozen on arrival by ACL.
250
- v['alterrors'] ||= ''
251
- v['alterrors'] << e + ' '
252
- elsif !boundary00.empty?
281
+ v["alterrors"] ||= ""
282
+ v["alterrors"] << e + " "
283
+ elsif boundary00 != ""
253
284
  # --NNNNNNNNNN-eximdsn-MMMMMMMMMM
254
285
  # Content-type: message/delivery-status
255
286
  # ...
256
- if Sisimai::RFC1894.match(e)
287
+ if Sisimai::RFC1894.match(e) > 0
257
288
  # "e" matched with any field defined in RFC3464
258
289
  next unless o = Sisimai::RFC1894.field(e)
259
290
 
260
- if o[-1] == 'addr'
291
+ if o[3] == "addr"
261
292
  # Final-Recipient: rfc822; kijitora@example.jp
262
293
  # X-Actual-Recipient: rfc822; kijitora@example.co.jp
263
- next unless o[0] == 'final-recipient'
264
- v['spec'] ||= o[2].include?('@') ? 'SMTP' : 'X-UNIX'
294
+ next unless o[0] == "final-recipient"
295
+ if v["spec"].empty?
296
+ v["spec"] = o[2].include?('@') ? "SMTP" : "X-UNIX"
297
+ end
265
298
 
266
- elsif o[-1] == 'code'
299
+ elsif o[3] == "code"
267
300
  # Diagnostic-Code: SMTP; 550 5.1.1 <userunknown@example.jp>... User Unknown
268
- v['spec'] = o[1]
269
- v['diagnosis'] = o[2]
301
+ v["spec"] = o[1].upcase
302
+ v["diagnosis"] = o[2]
270
303
 
271
304
  else
272
305
  # Other DSN fields defined in RFC3464
273
- next unless fieldtable[o[0]]
274
- v[fieldtable[o[0]]] = o[2]
306
+ next unless FieldTable[o[0]]
307
+ v[FieldTable[o[0]]] = o[2]
275
308
  end
276
309
  else
277
310
  # Error message ?
278
- next if nextcursor == 1
311
+ next if nextcursor
279
312
 
280
313
  # Content-type: message/delivery-status
281
- nextcursor = 1 if e.start_with?(StartingOf[:deliverystatus][0])
282
- v['alterrors'] ||= ''
283
- if e.start_with?("\s", "\t")
284
- e.sub!(/\A[\s\t]+/, '')
285
- v['alterrors'] << e + ' ' unless v['alterrors'].include?(e)
286
- end
314
+ nextcursor = true if e.start_with?(StartingOf["deliverystatus"][0])
315
+ v["alterrors"] ||= ''
316
+ v["alterrors"] << e + " " unless v["alterrors"].include?(e) if e.start_with?(" ")
287
317
  end
288
318
  else
289
319
  # There is no boundary string in $boundary00
290
320
  if dscontents.size == recipients
291
321
  # Error message
292
- next if e.empty?
293
- v['diagnosis'] ||= ''
294
- v['diagnosis'] << e + ' '
322
+ v["diagnosis"] << e + " "
295
323
  else
296
- # Error message when email address above does not include '@' and domain part.
297
- if e.include?(' pipe to |/')
298
- # pipe to |/path/to/prog ...
299
- # generated by kijitora@example.com
300
- v['diagnosis'] = e
301
- else
302
- next unless e.start_with?(' ')
303
- v['alterrors'] ||= ''
304
- v['alterrors'] << e + ' '
305
- end
324
+ # Error message when email address above does not include '@' and domain part
325
+ # pipe to |/path/to/prog ...
326
+ # generated by kijitora@example.com
327
+ next unless e.start_with?(" ")
328
+ v["diagnosis"] << e + " "
306
329
  end
307
330
  end
308
331
  end
@@ -312,128 +335,121 @@ module Sisimai::Lhost
312
335
  # Check "an undisclosed address", "unroutable address"
313
336
  dscontents.each do |q|
314
337
  # Replace the recipient address with the value of "alias"
315
- next unless q['alias']
316
- next if q['alias'].empty?
317
- if q['recipient'].empty? || q['recipient'].include?('@') == false
338
+ next if q["alias"].empty?
339
+ if q["recipient"].empty? || q["recipient"].include?('@') == false
318
340
  # The value of "recipient" is empty or does not include "@"
319
- q['recipient'] = q['alias']
341
+ q["recipient"] = q["alias"]
320
342
  end
321
343
  end
322
344
  else
323
345
  # Fallback for getting recipient addresses
324
- if mhead['x-failed-recipients']
346
+ if mhead["x-failed-recipients"]
325
347
  # X-Failed-Recipients: kijitora@example.jp
326
- rcptinhead = mhead['x-failed-recipients'].split(',')
327
- rcptinhead.each do |a|
348
+ rcptinhead = mhead["x-failed-recipients"].split(",")
349
+ rcptinhead.each do |e|
328
350
  # Remove space characters
329
- a.lstrip!
330
- a.rstrip!
351
+ e.lstrip!
352
+ e.rstrip!
331
353
  end
332
354
  recipients = rcptinhead.size
333
355
 
334
356
  while e = rcptinhead.shift do
335
357
  # Insert each recipient address into dscontents
336
- dscontents[-1]['recipient'] = e
358
+ dscontents[-1]["recipient"] = e
337
359
  next if dscontents.size == recipients
338
360
  dscontents << Sisimai::Lhost.DELIVERYSTATUS
339
361
  end
340
362
  end
341
363
  end
342
- return nil unless recipients > 0
364
+ return nil if recipients == 0
343
365
 
344
366
  # Get the name of the local MTA
345
367
  # Received: from marutamachi.example.org (c192128.example.net [192.0.2.128])
346
- receivedby = mhead['received'] || []
368
+ receivedby = mhead["received"] || []
347
369
  recvdtoken = Sisimai::RFC5322.received(receivedby[-1])
348
370
 
349
371
  dscontents.each do |e|
350
372
  # Check the error message, the rhost, the lhost, and the smtp command.
351
- unless e['diagnosis']
373
+ e["alterrors"] = "" if e["alterrors"].nil?
374
+ if e["diagnosis"].empty? && boundary00.size > 0
352
375
  # Empty Diagnostic-Code: or error message
353
- unless boundary00.empty?
354
- # --NNNNNNNNNN-eximdsn-MMMMMMMMMM
355
- # Content-type: message/delivery-status
356
- #
357
- # Reporting-MTA: dns; the.local.host.name
358
- #
359
- # Action: failed
360
- # Final-Recipient: rfc822;/a/b/c
361
- # Status: 5.0.0
362
- #
363
- # Action: failed
364
- # Final-Recipient: rfc822;|/p/q/r
365
- # Status: 5.0.0
366
- e['diagnosis'] = dscontents[0]['diagnosis'] || ''
367
- e['spec'] ||= dscontents[0]['spec']
368
-
369
- unless dscontents[0]['alterrors'].to_s.empty?
370
- # The value of "alterrors" is also copied
371
- e['alterrors'] = dscontents[0]['alterrors']
372
- end
373
- end
376
+ # --NNNNNNNNNN-eximdsn-MMMMMMMMMM
377
+ # Content-type: message/delivery-status
378
+ #
379
+ # Reporting-MTA: dns; the.local.host.name
380
+ #
381
+ # Action: failed
382
+ # Final-Recipient: rfc822;/a/b/c
383
+ # Status: 5.0.0
384
+ #
385
+ # Action: failed
386
+ # Final-Recipient: rfc822;|/p/q/r
387
+ # Status: 5.0.0
388
+ e["diagnosis"] = dscontents[0]["diagnosis"]
389
+ e["spec"] = dscontents[0]["spec"] if e["spec"].empty?
390
+ e["alterrors"] = dscontents[0]["alterrors"] if dscontents[0]["alterrors"] != ""
374
391
  end
375
392
 
376
- unless e['alterrors'].to_s.empty?
393
+ if e["alterrors"]
377
394
  # Copy alternative error message
378
- if e['diagnosis'].nil? || e['diagnosis'].empty?
379
- e['diagnosis'] = e['alterrors']
380
- end
395
+ e["diagnosis"] = e["alterrors"] if e["diagnosis"].empty?
381
396
 
382
- if e['diagnosis'].start_with?('-') || e['diagnosis'].end_with?('__')
397
+ if e["diagnosis"].start_with?("-") || e["diagnosis"].end_with?("__")
383
398
  # Override the value of diagnostic code message
384
- e['diagnosis'] = e['alterrors'] unless e['alterrors'].empty?
399
+ e["diagnosis"] = e["alterrors"]
385
400
 
386
- elsif e['diagnosis'].size < e['alterrors'].size
387
- # Override the value of diagnostic code message because the value of alterrors includes
388
- # the value of diagnosis.
389
- e['diagnosis'] = e['alterrors'] if e['alterrors'].downcase.include?(e['diagnosis'].downcase)
401
+ elsif e["diagnosis"].size < e["alterrors"].size
402
+ # Override the value of diagnostic code message with the value of alterrors because
403
+ # the latter includes the former.
404
+ e["alterrors"].squeeze!(" ")
405
+ e["diagnosis"] = e["alterrors"] if e["alterrors"].downcase.include?(e["diagnosis"].downcase)
390
406
  end
391
- e.delete('alterrors')
407
+ e.delete("alterrors")
392
408
  end
393
- e['diagnosis'] = Sisimai::String.sweep(e['diagnosis']) || ''; p1 = e['diagnosis'].index('__') || -1
394
- e['diagnosis'] = e['diagnosis'][0, p1] if p1 > 1
409
+ e["diagnosis"] = Sisimai::String.sweep(e["diagnosis"]) || ""; p1 = e["diagnosis"].index("__") || -1
410
+ e["diagnosis"] = e["diagnosis"][0, p1] if p1 > 1
395
411
 
396
- unless e['rhost']
412
+ if e["rhost"].empty?
397
413
  # Get the remote host name
398
414
  # host neko.example.jp [192.0.2.222]: 550 5.1.1 <kijitora@example.jp>... User Unknown
399
- p1 = e['diagnosis'].index('host ') || -1
400
- p2 = e['diagnosis'].index(' ', p1 + 5) || -1
401
- e['rhost'] = e['diagnosis'][p1 + 5, p2 - p1 - 5] if p1 > -1
402
- e['rhost'] ||= recvdtoken[1]
415
+ p1 = e["diagnosis"].index("host ") || -1
416
+ p2 = e["diagnosis"].index(" ", p1 + 5) || -1
417
+ e["rhost"] = e["diagnosis"][p1 + 5, p2 - p1 - 5] if p1 > -1
418
+ e["rhost"] = recvdtoken[1] if e["rhost"].empty?
403
419
  end
404
- e['lhost'] ||= recvdtoken[0]
420
+ e["lhost"] = recvdtoken[0] if e["lhost"].empty?
405
421
 
406
- unless e['command']
422
+ if e["command"].empty?
407
423
  # Get the SMTP command name for the session
408
- ReCommands.each do |r|
424
+ StartingOf["command"].each do |r|
409
425
  # Verify each regular expression of SMTP commands
410
- if cv = e['diagnosis'].match(r)
411
- e['command'] = cv[1].upcase
412
- break
413
- end
426
+ next if e["diagnosis"].include?(r) == false
427
+ e["command"] = Sisimai::SMTP::Command.find(e["diagnosis"]); next if e["command"].empty?
428
+ break
414
429
  end
415
430
 
416
431
  # Detect the reason of bounce
417
- if %w[HELO EHLO].index(e['command'])
432
+ if %w[HELO EHLO].index(e["command"])
418
433
  # HELO | Connected to 192.0.2.135 but my name was rejected.
419
- e['reason'] = 'blocked'
434
+ e["reason"] = "blocked"
420
435
 
421
- elsif e['command'] == 'MAIL'
436
+ elsif e["command"] == "MAIL"
422
437
  # MAIL | Connected to 192.0.2.135 but sender was rejected.
423
- e['reason'] = 'onhold'
438
+ e["reason"] = "onhold"
439
+
424
440
  else
425
- # Verify each regular expression of session errors
441
+ # Try to match the error message with each message pattern
426
442
  MessagesOf.each_key do |r|
427
- # Check each regular expression
428
- next unless MessagesOf[r].any? { |a| e['diagnosis'].include?(a) }
429
- e['reason'] = r
443
+ # Check each message pattern
444
+ next unless MessagesOf[r].any? { |a| e["diagnosis"].include?(a) }
445
+ e["reason"] = r
430
446
  break
431
447
  end
432
448
 
433
- unless e['reason']
434
- # The reason "expired"
435
- e['reason'] = 'expired' if DelayedFor.any? { |a| e['diagnosis'].include?(a) }
436
- e['reason'] ||= 'mailererror' if e['diagnosis'].include?('pipe to |')
449
+ if e["reason"].empty?
450
+ # The reason "expired", or "mailererror"
451
+ e["reason"] = "expired" if DelayedFor.any? { |a| e["diagnosis"].include?(a) }
452
+ e["reason"] = "mailererror" if e["reason"].empty? && e["diagnosis"].include?("pipe to |")
437
453
  end
438
454
  end
439
455
  end
@@ -447,52 +463,26 @@ module Sisimai::Lhost
447
463
  #
448
464
  # The value of "Status:" indicates permanent error but the value of SMTP reply code in
449
465
  # Diagnostic-Code: field is "TEMPERROR"!!!!
450
- cs = e['status'] || Sisimai::SMTP::Status.find(e['diagnosis'])
451
- cr = e['replycode'] || Sisimai::SMTP::Reply.find(e['diagnosis'])
452
- s1 = 0 # First character of Status as integer
453
- r1 = 0 # First character of SMTP reply code as integer
454
-
455
- while true
456
- # "Status:" field did not exist in the bounce message
457
- break if cs
458
- break unless cr
459
-
460
- # Check SMTP reply code, Generate pseudo DSN code from SMTP reply code
461
- r1 = cr[0, 1].to_i
462
- if r1 == 4
463
- # Get the internal DSN(temporary error)
464
- cs = Sisimai::SMTP::Status.code(e['reason'], true)
465
-
466
- elsif r1 == 5
467
- # Get the internal DSN(permanent error)
468
- cs = Sisimai::SMTP::Status.code(e['reason'], false)
469
- end
470
- break
471
- end
466
+ re = e["reason"]
467
+ cr = Sisimai::SMTP::Reply.find(e["diagnosis"], e["status"])
468
+ cs = Sisimai::SMTP::Status.find(e["diagnosis"], cr)
469
+ cv = ""
472
470
 
473
- s1 = cs[0, 1].to_i if cs
474
- v1 = s1 + r1
475
- v1 << e['status'][0, 1].to_i if e['status']
471
+ if cr[0,1] == "4" || re == "expired" || re == "mailboxfull"
472
+ # Set the pseudo status code as a temporary error
473
+ cv = Sisimai::SMTP::Status.code(re, true)
476
474
 
477
- if v1 > 0
478
- # Status or SMTP reply code exists, Set pseudo DSN into the value of "status" accessor
479
- e['status'] = cs if r1 > 0
480
475
  else
481
- # Neither Status nor SMTP reply code exist
482
- cs = if %w[expired mailboxfull].include?(e['reason'])
483
- # Set pseudo DSN (temporary error)
484
- Sisimai::SMTP::Status.code(e['reason'], true)
485
- else
486
- # Set pseudo DSN (permanent error)
487
- Sisimai::SMTP::Status.code(e['reason'], false)
488
- end
476
+ # Set the pseudo status code as a permanent error
477
+ cv = Sisimai::SMTP::Status.code(re, false)
489
478
  end
490
- e['status'] ||= cs.to_s
479
+ e["replycode"] = cr if e["replycode"].empty?
480
+ e["status"] = Sisimai::SMTP::Status.prefer(cs, cv, cr) if e["status"].empty?
491
481
  end
492
482
 
493
- return { 'ds' => dscontents, 'rfc822' => emailparts[1] }
483
+ return { "ds" => dscontents, "rfc822" => emailparts[1] }
494
484
  end
495
- def description; return 'Exim'; end
485
+ def description; return "Exim"; end
496
486
  end
497
487
  end
498
488
  end