module Sisimai::Lhost::MailRu
Sisimai::Lhost::MailRu
parses a bounce email which created by @mail.ru. Methods in the module are called from only Sisimai::Message
.
Constants
- Indicators
- MessagesOf
- ReBackbone
- ReCommands
- StartingOf
Public Class Methods
description()
click to toggle source
# File lib/sisimai/lhost/mailru.rb, line 213 def description; return '@mail.ru: https://mail.ru'; end
make(mhead, mbody)
click to toggle source
Parse bounce messages from @mail.ru @param [Hash] mhead Message
headers of a bounce email @param [String] mbody Message
body of a bounce email @return [Hash] Bounce data list and message/rfc822 part @return [Nil] it failed to parse or the arguments are missing
# File lib/sisimai/lhost/mailru.rb, line 53 def make(mhead, mbody) return nil unless mhead['from'] =~ /[<]?mailer-daemon[@].*mail[.]ru[>]?/i return nil unless mhead['message-id'].end_with?('.mail.ru>', 'smailru.net>') return nil unless mhead['subject'] =~ %r{(?: Mail[ ]delivery[ ]failed(:[ ]returning[ ]message[ ]to[ ]sender)? |Warning:[ ]message[ ].+[ ]delayed[ ]+ |Delivery[ ]Status[ ]Notification |Mail[ ]failure |Message[ ]frozen |error[(]s[)][ ]in[ ]forwarding[ ]or[ ]filtering ) }x dscontents = [Sisimai::Lhost.DELIVERYSTATUS] emailsteak = Sisimai::RFC5322.fillet(mbody, ReBackbone) bodyslices = emailsteak[0].split("\n") readcursor = 0 # (Integer) Points the current cursor position recipients = 0 # (Integer) The number of 'Final-Recipient' header localhost0 = '' # (String) Local MTA v = nil 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 e.start_with?(StartingOf[:message][0]) next end next if (readcursor & Indicators[:deliverystatus]) == 0 next if e.empty? # Это письмо создано автоматически # сервером Mail.Ru, # отвечать на него не # нужно. # # К сожалению, Ваше письмо не может # быть# доставлено одному или нескольким # получателям: # # ********************** # # This message was created automatically by mail delivery software. # # A message that you sent could not be delivered to one or more of its # recipients. This is a permanent error. The following address(es) failed: # # kijitora@example.jp # SMTP error from remote mail server after RCPT TO:<kijitora@example.jp>: # host neko.example.jp [192.0.2.222]: 550 5.1.1 <kijitora@example.jp>... User Unknown v = dscontents[-1] if cv = e.match(/\A[ \t]+([^ \t]+[@][^ \t]+[.][a-zA-Z]+)\z/) # 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'] = cv[1] recipients += 1 elsif dscontents.size == recipients # Error message next if e.empty? v['diagnosis'] ||= '' v['diagnosis'] << e + ' ' else # Error message when email address above does not include '@' # and domain part. next unless e.start_with?(' ', "\t") v['alterrors'] ||= '' v['alterrors'] << e + ' ' end end unless recipients > 0 # Fallback for getting recipient addresses if mhead['x-failed-recipients'] # X-Failed-Recipients: kijitora@example.jp rcptinhead = mhead['x-failed-recipients'].split(',') rcptinhead.each { |a| a.delete(' ') } recipients = rcptinhead.size while e = rcptinhead.shift do # Insert each recipient address into dscontents dscontents[-1]['recipient'] = e next if dscontents.size == recipients dscontents << Sisimai::Lhost.DELIVERYSTATUS end end end return nil unless recipients > 0 unless mhead['received'].empty? # Get the name of local MTA # Received: from marutamachi.example.org (c192128.example.net [192.0.2.128]) if cv = mhead['received'][-1].match(/from[ \t]([^ ]+)/) then localhost0 = cv[1] end end dscontents.each do |e| # Set default values if each value is empty. e['lhost'] ||= localhost0 unless e['alterrors'].to_s.empty? # Copy alternative error message e['diagnosis'] ||= e['alterrors'] if e['diagnosis'].start_with?('-') || e['diagnosis'].end_with?('__') # Override the value of diagnostic code message e['diagnosis'] = e['alterrors'] unless e['alterrors'].empty? end e.delete('alterrors') end e['diagnosis'] = Sisimai::String.sweep(e['diagnosis']) || '' e['diagnosis'].sub!(/\b__.+\z/, '') unless e['rhost'] # Get the remote host name # host neko.example.jp [192.0.2.222]: 550 5.1.1 <kijitora@example.jp>... User Unknown if cv = e['diagnosis'].match(/host[ ]+([^ \t]+)[ ]\[.+\]:[ ]/) then e['rhost'] = cv[1] end unless e['rhost'] # Get localhost and remote host name from Received header. e['rhost'] = Sisimai::RFC5322.received(mhead['received'][-1]).pop unless mhead['received'].empty? end end unless e['command'] # Get the SMTP command name for the session ReCommands.each do |r| # Verify each regular expression of SMTP commands if cv = e['diagnosis'].match(r) e['command'] = cv[1].upcase break end 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' elsif e['command'] == 'MAIL' # MAIL | Connected to 192.0.2.135 but sender was rejected. e['reason'] = 'rejected' else # Verify each regular expression of session errors MessagesOf.each_key do |r| # Check each regular expression next unless MessagesOf[r].any? { |a| e['diagnosis'].include?(a) } e['reason'] = r break end end end e['command'] ||= '' end return { 'ds' => dscontents, 'rfc822' => emailsteak[1] } end