Module: Sisimai::Lhost::Qmail
- Defined in:
- lib/sisimai/lhost/qmail.rb
Overview
Sisimai::Lhost::Qmail decodes a bounce email which created by qmail cr.yp.to/qmail.html or qmail clones or notqmail notqmail.org/. Methods in the module are called from only Sisimai::Message.
Constant Summary collapse
- Indicators =
Sisimai::Lhost.INDICATORS
- Boundaries =
[ # qmail-send.c:qmail_puts(&qqt,*sender.s ? "--- Below this line is a copy of the message.\n\n" :... "--- Below this line is a copy of the message.", # qmail-1.03 "--- Below this line is a copy of the mail header.", "--- Below the next line is a copy of the message.", # The followings are the qmail clone "--- Mensaje original adjunto.", "Content-Type: message/rfc822", "Original message follows.", ].freeze
- EmailTitle =
[ "failure notice", # qmail-send.c:Subject: failure notice\n\ "Failure Notice", # Yahoo ].freeze
- StartingOf =
{ # qmail-remote.c:248| if (code >= 500) { # qmail-remote.c:249| out("h"); outhost(); out(" does not like recipient.\n"); # qmail-remote.c:265| if (code >= 500) quit("D"," failed on DATA command"); # qmail-remote.c:271| if (code >= 500) quit("D"," failed after I sent the message"); # # Characters: K,Z,D in qmail-qmqpc.c, qmail-send.c, qmail-rspawn.c # K = success, Z = temporary error, D = permanent error "error" => ["Remote host said:"], "message" => [ "Hi. This is the qmail", # qmail-send.c:Hi. This is the qmail-send program at "); "He/Her is not ", # The followings are the qmail clone "unable to deliver your message to the following addresses", "Su mensaje no pudo ser entregado", "Sorry, we were unable to deliver your message to the following address", "This is the machine generated message from mail service", "This is the mail delivery agent at", "Unable to deliver message to the following address", "unable to deliver your message to the following addresses", "Unfortunately, your mail was not delivered to the following address:", "Your mail message to the following address", "Your message to the following addresses", "We're sorry.", ], "rhost" => ['Giving up on ', 'Connected to ', 'remote host '], }.freeze
- CommandSet =
{ # qmail-remote.c:225| if (smtpcode() != 220) quit("ZConnected to "," but greeting failed"); "CONN" => [" but greeting failed."], # qmail-remote.c:231| if (smtpcode() != 250) quit("ZConnected to "," but my name was rejected"); "EHLO" => [" but my name was rejected."], # qmail-remote.c:238| if (code >= 500) quit("DConnected to "," but sender was rejected"); # reason = rejected "MAIL" => [" but sender was rejected."], # qmail-remote.c:249| out("h"); outhost(); out(" does not like recipient.\n"); # qmail-remote.c:253| out("s"); outhost(); out(" does not like recipient.\n"); # reason = userunknown "RCPT" => [" does not like recipient."], # qmail-remote.c:265| if (code >= 500) quit("D"," failed on DATA command"); # qmail-remote.c:266| if (code >= 400) quit("Z"," failed on DATA command"); # qmail-remote.c:271| if (code >= 500) quit("D"," failed after I sent the message"); # qmail-remote.c:272| if (code >= 400) quit("Z"," failed after I sent the message"); "DATA" => [" failed on DATA command", " failed after I sent the message"], }.freeze
- MessagesOf =
{ # notqmail 1.08 returns the following error message when the destination MX is NullMX "notaccept" => ["Sorry, I couldn't find a mail exchanger or IP address"], "userunknown" => ["no mailbox here by that name"], }.freeze
Class Method Summary collapse
Class Method Details
.description ⇒ Object
186 |
# File 'lib/sisimai/lhost/qmail.rb', line 186 def description; return 'qmail'; end |
.inquire(mhead, mbody) ⇒ Hash, Nil
This method is abstract.
Decodes the bounce message from qmail
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 |
# File 'lib/sisimai/lhost/qmail.rb', line 80 def inquire(mhead, mbody) # Pre process email headers and the body part of the message which generated # by qmail, see https://cr.yp.to/qmail.html # e.g.) Received: (qmail 12345 invoked for bounce); 29 Apr 2009 12:34:56 -0000 # Subject: failure notice proceedsto = false proceedsto = true if EmailTitle.any? { |a| mhead["subject"] == a } mhead["received"].each do |e| # Received: (qmail 2222 invoked for bounce);29 Apr 2017 23:34:45 +0900 # Received: (qmail 2202 invoked from network); 29 Apr 2018 00:00:00 +0900 proceedsto = true if Sisimai::String.aligned(e, ["(qmail", " invoked "]) end return nil if proceedsto == false require "sisimai/smtp/command" dscontents = [Sisimai::Lhost.DELIVERYSTATUS]; v = nil emailparts = Sisimai::RFC5322.part(mbody, Boundaries) bodyslices = emailparts[0].split("\n") readcursor = 0 # (Integer) Points the current cursor position recipients = 0 # (Integer) The number of 'Final-Recipient' header while e = bodyslices.shift do # Read error messages and delivery status lines from the head of the email to the previous # line of the beginning of the original message. if readcursor == 0 # Beginning of the bounce message or delivery status part readcursor |= Indicators[:deliverystatus] if StartingOf["message"].any? { |a| e.include?(a) } next end next if (readcursor & Indicators[:deliverystatus]) == 0 || e.empty? # <kijitora@example.jp>: # 192.0.2.153 does not like recipient. # Remote host said: 550 5.1.1 <kijitora@example.jp>... User Unknown # Giving up on 192.0.2.153. v = dscontents[-1] if e.start_with?('<') && Sisimai::String.aligned(e, ['<', '@', '>:']) # <kijitora@example.jp>: if v["recipient"] != "" # There are multiple recipient addresses in the message body. dscontents << Sisimai::Lhost.DELIVERYSTATUS v = dscontents[-1] end v["recipient"] = Sisimai::Address.s3s4(e[e.index("<"), e.size]) recipients += 1 elsif dscontents.size == recipients # Append error message v["diagnosis"] += "#{e} " v["alterrors"] = e if e.start_with?(StartingOf["error"][0]) next if v["rhost"] != "" StartingOf["rhost"].each do |r| # Find a remote host name p1 = e.index(r); next if p1.nil? cm = r.size p2 = e.index(" ", p1 + cm + 1) || p2 = e.rindex(".") v["rhost"] = e[p1 + cm, p2 - p1 - cm] break end end end return nil if recipients == 0 dscontents.each do |e| # Get the SMTP command name for the session CommandSet.each_key do |r| # Get the last SMTP command next if CommandSet[r].none? { |a| e["diagnosis"].include?(a) } e["command"] = r break end if e["diagnosis"].include?("Sorry, no SMTP connection got far enough") # Sorry, no SMTP connection got far enough; most progress was RCPT TO response; ... e["command"] = Sisimai::SMTP::Command.find(e["diagnosis"]) if e["command"].empty? end # Detect the reason of bounce if %w[HELO EHLO].index(e["command"]) # HELO | Connected to 192.0.2.135 but my name was rejected. e["reason"] = "blocked" else # Try to match with each error message in the table # Check that the error message includes any of message patterns or not [e["alterrors"], e["diagnosis"]].each do |f| # Try to detect an error reason break if e["reason"] != "" next if f.nil? MessagesOf.each_key do |r| # The key is a bounce reason name next if MessagesOf[r].none? { |a| f.include?(a) } e["reason"] = r break end break if e["reason"] end end e["command"] = Sisimai::SMTP::Command.find(e["diagnosis"]) if e["command"].empty? end return { "ds" => dscontents, "rfc822" => emailparts[1] } end |