1package Sisimai::Lhost::Gmail; 2use parent 'Sisimai::Lhost'; 3use feature ':5.10'; 4use strict; 5use warnings; 6 7sub description { 'Gmail: https://mail.google.com' } 8sub make { 9 # Detect an error from Gmail 10 # @param [Hash] mhead Message headers of a bounce email 11 # @param [String] mbody Message body of a bounce email 12 # @return [Hash] Bounce data list and message/rfc822 part 13 # @return [Undef] failed to parse or the arguments are missing 14 # @since v4.0.0 15 my $class = shift; 16 my $mhead = shift // return undef; 17 my $mbody = shift // return undef; 18 19 # Google Mail 20 # From: Mail Delivery Subsystem <mailer-daemon@googlemail.com> 21 # Received: from vw-in-f109.1e100.net [74.125.113.109] by ... 22 # 23 # * Check the body part 24 # This is an automatically generated Delivery Status Notification 25 # Delivery to the following recipient failed permanently: 26 # 27 # recipient-address-here@example.jp 28 # 29 # Technical details of permanent failure: 30 # Google tried to deliver your message, but it was rejected by the 31 # recipient domain. We recommend contacting the other email provider 32 # for further information about the cause of this error. The error 33 # that the other server returned was: 34 # 550 550 <recipient-address-heare@example.jp>: User unknown (state 14). 35 # 36 # -- OR -- 37 # THIS IS A WARNING MESSAGE ONLY. 38 # 39 # YOU DO NOT NEED TO RESEND YOUR MESSAGE. 40 # 41 # Delivery to the following recipient has been delayed: 42 # 43 # mailboxfull@example.jp 44 # 45 # Message will be retried for 2 more day(s) 46 # 47 # Technical details of temporary failure: 48 # Google tried to deliver your message, but it was rejected by the recipient 49 # domain. We recommend contacting the other email provider for further infor- 50 # mation about the cause of this error. The error that the other server re- 51 # turned was: 450 450 4.2.2 <mailboxfull@example.jp>... Mailbox Full (state 14). 52 # 53 # -- OR -- 54 # 55 # Delivery to the following recipient failed permanently: 56 # 57 # userunknown@example.jp 58 # 59 # Technical details of permanent failure:=20 60 # Google tried to deliver your message, but it was rejected by the server for= 61 # the recipient domain example.jp by mx.example.jp. [192.0.2.59]. 62 # 63 # The error that the other server returned was: 64 # 550 5.1.1 <userunknown@example.jp>... User Unknown 65 # 66 return undef unless rindex($mhead->{'from'}, '<mailer-daemon@googlemail.com>') > -1; 67 return undef unless index($mhead->{'subject'}, 'Delivery Status Notification') > -1; 68 69 state $indicators = __PACKAGE__->INDICATORS; 70 state $rebackbone = qr/^[ ]*-----[ ](?:Original[ ]message|Message[ ]header[ ]follows)[ ]-----/m; 71 state $startingof = { 72 'message' => ['Delivery to the following recipient'], 73 'error' => ['The error that the other server returned was:'], 74 }; 75 state $markingsof = { 'start' => qr/Technical details of (?:permanent|temporary) failure:/ }; 76 state $messagesof = { 77 'expired' => [ 78 'DNS Error: Could not contact DNS servers', 79 'Delivery to the following recipient has been delayed', 80 'The recipient server did not accept our requests to connect', 81 ], 82 'hostunknown' => [ 83 'DNS Error: Domain name not found', 84 'DNS Error: DNS server returned answer with no data', 85 ], 86 }; 87 state $statetable = { 88 # Technical details of permanent failure: 89 # Google tried to deliver your message, but it was rejected by the recipient domain. 90 # We recommend contacting the other email provider for further information about the 91 # cause of this error. The error that the other server returned was: 92 # 500 Remote server does not support TLS (state 6). 93 '6' => { 'command' => 'MAIL', 'reason' => 'systemerror' }, 94 95 # https://www.google.td/support/forum/p/gmail/thread?tid=08a60ebf5db24f7b&hl=en 96 # Technical details of permanent failure: 97 # Google tried to deliver your message, but it was rejected by the recipient domain. 98 # We recommend contacting the other email provider for further information about the 99 # cause of this error. The error that the other server returned was: 100 # 535 SMTP AUTH failed with the remote server. (state 8). 101 '8' => { 'command' => 'AUTH', 'reason' => 'systemerror' }, 102 103 # https://www.google.co.nz/support/forum/p/gmail/thread?tid=45208164dbca9d24&hl=en 104 # Technical details of temporary failure: 105 # Google tried to deliver your message, but it was rejected by the recipient domain. 106 # We recommend contacting the other email provider for further information about the 107 # cause of this error. The error that the other server returned was: 108 # 454 454 TLS missing certificate: error:0200100D:system library:fopen:Permission denied (#4.3.0) (state 9). 109 '9' => { 'command' => 'AUTH', 'reason' => 'systemerror' }, 110 111 # https://www.google.com/support/forum/p/gmail/thread?tid=5cfab8c76ec88638&hl=en 112 # Technical details of permanent failure: 113 # Google tried to deliver your message, but it was rejected by the recipient domain. 114 # We recommend contacting the other email provider for further information about the 115 # cause of this error. The error that the other server returned was: 116 # 500 Remote server does not support SMTP Authenticated Relay (state 12). 117 '12' => { 'command' => 'AUTH', 'reason' => 'relayingdenied' }, 118 119 # Technical details of permanent failure: 120 # Google tried to deliver your message, but it was rejected by the recipient domain. 121 # We recommend contacting the other email provider for further information about the 122 # cause of this error. The error that the other server returned was: 123 # 550 550 5.7.1 <****@gmail.com>... Access denied (state 13). 124 '13' => { 'command' => 'EHLO', 'reason' => 'blocked' }, 125 126 # Technical details of permanent failure: 127 # Google tried to deliver your message, but it was rejected by the recipient domain. 128 # We recommend contacting the other email provider for further information about the 129 # cause of this error. The error that the other server returned was: 130 # 550 550 5.1.1 <******@*********.**>... User Unknown (state 14). 131 # 550 550 5.2.2 <*****@****.**>... Mailbox Full (state 14). 132 # 133 '14' => { 'command' => 'RCPT', 'reason' => 'userunknown' }, 134 135 # https://www.google.cz/support/forum/p/gmail/thread?tid=7090cbfd111a24f9&hl=en 136 # Technical details of permanent failure: 137 # Google tried to deliver your message, but it was rejected by the recipient domain. 138 # We recommend contacting the other email provider for further information about the 139 # cause of this error. The error that the other server returned was: 140 # 550 550 5.7.1 SPF unauthorized mail is prohibited. (state 15). 141 # 554 554 Error: no valid recipients (state 15). 142 '15' => { 'command' => 'DATA', 'reason' => 'filtered' }, 143 144 # https://www.google.com/support/forum/p/Google%20Apps/thread?tid=0aac163bc9c65d8e&hl=en 145 # Technical details of permanent failure: 146 # Google tried to deliver your message, but it was rejected by the recipient domain. 147 # We recommend contacting the other email provider for further information about the 148 # cause of this error. The error that the other server returned was: 149 # 550 550 <****@***.**> No such user here (state 17). 150 # 550 550 #5.1.0 Address rejected ***@***.*** (state 17). 151 '17' => { 'command' => 'DATA', 'reason' => 'filtered' }, 152 153 # Technical details of permanent failure: 154 # Google tried to deliver your message, but it was rejected by the recipient domain. 155 # We recommend contacting the other email provider for further information about the 156 # cause of this error. The error that the other server returned was: 157 # 550 550 Unknown user *****@***.**.*** (state 18). 158 '18' => { 'command' => 'DATA', 'reason' => 'filtered' }, 159 }; 160 161 my $dscontents = [__PACKAGE__->DELIVERYSTATUS]; 162 my $emailsteak = Sisimai::RFC5322->fillet($mbody, $rebackbone); 163 my $readcursor = 0; # (Integer) Points the current cursor position 164 my $recipients = 0; # (Integer) The number of 'Final-Recipient' header 165 my $v = undef; 166 167 for my $e ( split("\n", $emailsteak->[0]) ) { 168 # Read error messages and delivery status lines from the head of the email 169 # to the previous line of the beginning of the original message. 170 unless( $readcursor ) { 171 # Beginning of the bounce message or message/delivery-status part 172 $readcursor |= $indicators->{'deliverystatus'} if index($e, $startingof->{'message'}->[0]) == 0; 173 } 174 next unless $readcursor & $indicators->{'deliverystatus'}; 175 next unless length $e; 176 177 # Technical details of permanent failure:=20 178 # Google tried to deliver your message, but it was rejected by the recipient = 179 # domain. We recommend contacting the other email provider for further inform= 180 # ation about the cause of this error. The error that the other server return= 181 # ed was: 554 554 5.7.0 Header error (state 18). 182 # 183 # -- OR -- 184 # 185 # Technical details of permanent failure:=20 186 # Google tried to deliver your message, but it was rejected by the server for= 187 # the recipient domain example.jp by mx.example.jp. [192.0.2.49]. 188 # 189 # The error that the other server returned was: 190 # 550 5.1.1 <userunknown@example.jp>... User Unknown 191 # 192 $v = $dscontents->[-1]; 193 194 if( $e =~ /\A[ \t]+([^ ]+[@][^ ]+)\z/ ) { 195 # kijitora@example.jp: 550 5.2.2 <kijitora@example>... Mailbox Full 196 if( $v->{'recipient'} ) { 197 # There are multiple recipient addresses in the message body. 198 push @$dscontents, __PACKAGE__->DELIVERYSTATUS; 199 $v = $dscontents->[-1]; 200 } 201 202 my $r = Sisimai::Address->s3s4($1); 203 next unless Sisimai::RFC5322->is_emailaddress($r); 204 $v->{'recipient'} = $r; 205 $recipients++; 206 207 } else { 208 $v->{'diagnosis'} .= $e.' '; 209 } 210 } 211 return undef unless $recipients; 212 213 for my $e ( @$dscontents ) { 214 $e->{'diagnosis'} = Sisimai::String->sweep($e->{'diagnosis'}); 215 216 unless( $e->{'rhost'} ) { 217 # Get the value of remote host 218 if( $e->{'diagnosis'} =~ /[ \t]+by[ \t]+([^ ]+)[.][ \t]+\[(\d+[.]\d+[.]\d+[.]\d+)\][.]/ ) { 219 # Google tried to deliver your message, but it was rejected by # the server 220 # for the recipient domain example.jp by mx.example.jp. [192.0.2.153]. 221 my $hostname = $1; 222 my $ipv4addr = $2; 223 if( $hostname =~ /[-0-9a-zA-Z]+[.][a-zA-Z]+\z/ ) { 224 # Maybe valid hostname 225 $e->{'rhost'} = $hostname; 226 } else { 227 # Use IP address instead 228 $e->{'rhost'} = $ipv4addr; 229 } 230 } 231 } 232 233 my $statecode0 = $e->{'diagnosis'} =~ /[(]state[ ](\d+)[)][.]/ ? $1 : 0; 234 if( exists $statetable->{ $statecode0 } ) { 235 # (state *) 236 $e->{'reason'} = $statetable->{ $statecode0 }->{'reason'}; 237 $e->{'command'} = $statetable->{ $statecode0 }->{'command'}; 238 } else { 239 # No state code 240 SESSION: for my $r ( keys %$messagesof ) { 241 # Verify each regular expression of session errors 242 next unless grep { index($e->{'diagnosis'}, $_) > -1 } @{ $messagesof->{ $r } }; 243 $e->{'reason'} = $r; 244 last; 245 } 246 } 247 next unless $e->{'reason'}; 248 249 # Set pseudo status code and override bounce reason 250 $e->{'status'} = Sisimai::SMTP::Status->find($e->{'diagnosis'}) || ''; 251 next unless $e->{'status'} =~ /\A[45][.][1-7][.][1-9]\z/; 252 $e->{'reason'} = Sisimai::SMTP::Status->name($e->{'status'}) || ''; 253 } 254 return { 'ds' => $dscontents, 'rfc822' => $emailsteak->[1] }; 255} 256 2571; 258__END__ 259 260=encoding utf-8 261 262=head1 NAME 263 264Sisimai::Lhost::Gmail - bounce mail parser class for C<Gmail>. 265 266=head1 SYNOPSIS 267 268 use Sisimai::Lhost::Gmail; 269 270=head1 DESCRIPTION 271 272Sisimai::Lhost::Gmail parses a bounce email which created by C<Gmail>. 273Methods in the module are called from only Sisimai::Message. 274 275=head1 CLASS METHODS 276 277=head2 C<B<description()>> 278 279C<description()> returns description string of this module. 280 281 print Sisimai::Lhost::Gmail->description; 282 283=head2 C<B<make(I<header data>, I<reference to body string>)>> 284 285C<make()> method parses a bounced email and return results as a array reference. 286See Sisimai::Message for more details. 287 288=head1 AUTHOR 289 290azumakuniyuki 291 292=head1 COPYRIGHT 293 294Copyright (C) 2014-2020 azumakuniyuki, All rights reserved. 295 296=head1 LICENSE 297 298This software is distributed under The BSD 2-Clause License. 299 300=cut 301