File Coverage

lib/Sisimai/Lhost/Exim.pm
Criterion Covered Total %
statement 178 183 97.2
branch 116 132 87.8
condition 71 95 74.7
subroutine 6 6 100.0
pod 2 2 100.0
total 373 418 89.2


line stmt bran cond sub pod time code
1             package Sisimai::Lhost::Exim;
2 50     50   6567 use parent 'Sisimai::Lhost';
  50         103  
  50         497  
3 50     50   4592 use v5.26;
  50         192  
4 50     50   269 use strict;
  50         190  
  50         1517  
5 50     50   262 use warnings;
  50         104  
  50         156507  
6              
7 1     1 1 5 sub description { 'Exim' }
8             sub inquire {
9             # Detect an error from Exim Internet Mailer
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 decode or the arguments are missing
14             # @since v4.0.0
15 1401     1401 1 8966 my $class = shift;
16 1401   100     4775 my $mhead = shift // return undef;
17 1398   100     4180 my $mbody = shift // return undef;
18              
19             # Message-Id:
20             # X-Failed-Recipients: kijitora@example.ed.jp
21 1395   100     5999 my $messageidv = $mhead->{"message-id"} || "";
22 1395         6665 my $emailtitle = [
23             "Delivery Status Notification",
24             "Mail delivery failed",
25             "Mail failure",
26             "Message frozen",
27             "Warning: message ",
28             "error(s) in forwarding or filtering",
29             ];
30 1395 100       2769 my $proceedsto = 0; $proceedsto++ if index($mhead->{"from"}, "Mail Delivery System") > -1;
  1395         7605  
31              
32 1395         4133 while( $messageidv ne "" ) {
33             # Message-Id:
34 1291 100 100     11472 last if index($messageidv, '<') != 0 || index($messageidv, '-') != 8 || index($messageidv, '@') != 18;
      66        
35 223         583 $proceedsto++; last;
  223         556  
36             }
37 1395         3911 for my $e ( @$emailtitle ) {
38             # Subject: Mail delivery failed: returning message to sender
39             # Subject: Mail delivery failed
40             # Subject: Message frozen
41 4528 100       11744 next if index($mhead->{"subject"}, $e) < 0;
42 879         1522 $proceedsto++; last;
  879         1834  
43             }
44              
45             # Exim clones of the third Parties
46             # 1. McAfee Saas (Formerly MXLogic)
47 1395 100 50     2597 my $thirdparty = 0; $thirdparty ||= 1 if exists $mhead->{"x-mx-bounce"};
  1395         4544  
48 1395 50 0     4445 $thirdparty ||= 1 if exists $mhead->{"x-mxl-hash"};
49 1395 100 50     3734 $thirdparty ||= 1 if exists $mhead->{"x-mxl-notehash"};
50 1395 100 100     4368 $thirdparty ||= 1 if index($messageidv, " -1;
51 1395 100 66     8376 return undef if $proceedsto < 2 && $thirdparty == 0;
52              
53 301         1639 require Sisimai::Reason;
54 301         1094 require Sisimai::Address;
55 301         1312 require Sisimai::SMTP::Command;
56 301         1138 require Sisimai::SMTP::Failure;
57 301         624 state $indicators = __PACKAGE__->INDICATORS;
58 301         601 state $boundaries = [
59             # deliver.c:6423| if (bounce_return_body) fprintf(f,
60             # deliver.c:6424|"------ This is a copy of the message, including all the headers. ------\n");
61             # deliver.c:6425| else fprintf(f,
62             # deliver.c:6426|"------ This is a copy of the message's headers. ------\n");
63             '------ This is a copy of the message, including all the headers. ------',
64             'Content-Type: message/rfc822',
65             "Included is a copy of the message header:\n-----------------------------------------", # MXLogic
66             ];
67 301         675 state $startingof = {
68             # Error text strings which defined in exim/src/deliver.c
69             #
70             # deliver.c:6292| fprintf(f,
71             # deliver.c:6293|"This message was created automatically by mail delivery software.\n");
72             # deliver.c:6294| if (to_sender)
73             # deliver.c:6295| {
74             # deliver.c:6296| fprintf(f,
75             # deliver.c:6297|"\nA message that you sent could not be delivered to one or more of its\n"
76             # deliver.c:6298|"recipients. This is a permanent error. The following address(es) failed:\n");
77             # deliver.c:6299| }
78             # deliver.c:6300| else
79             # deliver.c:6301| {
80             # deliver.c:6302| fprintf(f,
81             # deliver.c:6303|"\nA message sent by\n\n <%s>\n\n"
82             # deliver.c:6304|"could not be delivered to one or more of its recipients. The following\n"
83             # deliver.c:6305|"address(es) failed:\n", sender_address);
84             # deliver.c:6306| }
85             "alias" => [" an undisclosed address"],
86             "command" => ["SMTP error from remote ", "LMTP error after "],
87             'deliverystatus' => ["Content-Type: message/delivery-status"],
88             'frozen' => [" has been frozen", " was frozen on arrival"],
89             'message' => [
90             "This message was created automatically by mail delivery software.",
91             "A message that you sent was rejected by the local scannning code",
92             "A message that you sent contained one or more recipient addresses ",
93             "A message that you sent could not be delivered to all of its recipients",
94             " has been frozen",
95             " was frozen on arrival",
96             " router encountered the following error(s):",
97             ],
98             };
99 301         657 state $messagesof = {
100             # find exim/ -type f -exec grep 'message = US' {} /dev/null \;
101             # route.c:1158| DEBUG(D_uid) debug_printf("getpwnam() returned NULL (user not found)\n");
102             "userunknown" => ["user not found"],
103             # transports/smtp.c:3524| addr->message = US"all host address lookups failed permanently";
104             # routers/dnslookup.c:331| addr->message = US"all relevant MX records point to non-existent hosts";
105             # route.c:1826| uschar *message = US"Unrouteable address";
106             "hostunknown" => [
107             "all host address lookups failed permanently",
108             "all relevant MX records point to non-existent hosts",
109             "Unrouteable address",
110             ],
111             # transports/appendfile.c:2567| addr->user_message = US"mailbox is full";
112             # transports/appendfile.c:3049| addr->message = string_sprintf("mailbox is full "
113             # transports/appendfile.c:3050| "(quota exceeded while writing to file %s)", filename);
114             "mailboxfull" => [
115             "mailbox is full",
116             "error: quota exceed",
117             ],
118             # routers/dnslookup.c:328| addr->message = US"an MX or SRV record indicated no SMTP service";
119             # transports/smtp.c:3502| addr->message = US"no host found for existing SMTP connection";
120             "notaccept" => [
121             "an MX or SRV record indicated no SMTP service",
122             "no host found for existing SMTP connection",
123             ],
124             # parser.c:666| *errorptr = string_sprintf("%s (expected word or \"<\")", *errorptr);
125             # parser.c:701| if(bracket_count++ > 5) FAILED(US"angle-brackets nested too deep");
126             # parser.c:738| FAILED(US"domain missing in source-routed address");
127             # parser.c:747| : string_sprintf("malformed address: %.32s may not follow %.*s",
128             "syntaxerror" => [
129             "angle-brackets nested too deep",
130             'expected word or "<"',
131             "domain missing in source-routed address",
132             "malformed address:",
133             ],
134             # deliver.c:5614| addr->message = US"delivery to file forbidden";
135             # deliver.c:5624| addr->message = US"delivery to pipe forbidden";
136             # transports/pipe.c:1156| addr->user_message = US"local delivery failed";
137             "systemerror" => [
138             "delivery to file forbidden",
139             "delivery to pipe forbidden",
140             "local delivery failed",
141             "LMTP error after ",
142             ],
143             # deliver.c:5425| new->message = US"Too many \"Received\" headers - suspected mail loop";
144             "contenterror" => ['Too many "Received" headers'],
145             };
146 301         561 state $delayedfor = [
147             # retry.c:902| addr->message = (addr->message == NULL)? US"retry timeout exceeded" :
148             # deliver.c:7475| "No action is required on your part. Delivery attempts will continue for\n"
149             # smtp.c:3508| US"retry time not reached for any host after a long failure period" :
150             # smtp.c:3508| US"all hosts have been failing for a long time and were last tried "
151             # "after this message arrived";
152             # deliver.c:7459| print_address_error(addr, f, US"Delay reason: ");
153             # deliver.c:7586| "Message %s has been frozen%s.\nThe sender is <%s>.\n", message_id,
154             # receive.c:4021| moan_tell_someone(freeze_tell, NULL, US"Message frozen on arrival",
155             # receive.c:4022| "Message %s was frozen on arrival by %s.\nThe sender is <%s>.\n",
156             "retry timeout exceeded",
157             "No action is required on your part",
158             "retry time not reached for any host after a long failure period",
159             "all hosts have been failing for a long time and were last tried",
160             "Delay reason: ",
161             "has been frozen",
162             "was frozen on arrival by ",
163             ];
164              
165 301 100       1593 if( index($$mbody, "\n----- This is a copy ") > -1 ) {
166             # There are extremely rare cases where there are only five hyphens.
167             # https://github.com/sisimai/set-of-emails/blob/master/maildir/bsd/lhost-exim-05.eml
168             # ----- This is a copy of the message, including all the headers. ------
169 5         20 my $p0 = index($$mbody, "\n----- This is a copy ");
170 5         32 substr($$mbody, $p0 + 1, 1, "--");
171             }
172              
173 301         2157 my $fieldtable = Sisimai::RFC1894->FIELDTABLE;
174 301         1771 my $dscontents = [__PACKAGE__->DELIVERYSTATUS]; my $v = undef;
  301         791  
175 301         1835 my $emailparts = Sisimai::RFC5322->part($mbody, $boundaries);
176 301         560 my $readcursor = 0; # Points the current cursor position
177 301         504 my $nextcursor = 0;
178 301         464 my $recipients = 0; # The number of 'Final-Recipient' header
179 301         560 my $boundary00 = ''; # Boundary string
180              
181             # Get the boundary string and set regular expression for matching with the boundary string.
182 301 100       1469 $boundary00 = Sisimai::RFC2045->boundary($mhead->{'content-type'}) if $mhead->{'content-type'};
183              
184 301         539 my $p1 = -1; my $p2 = -1;
  301         606  
185 301         2651 for my $e ( split("\n", $emailparts->[0]) ) {
186             # Read error messages and delivery status lines from the head of the email to the previous
187             # line of the beginning of the original message.
188 4434 100       7787 unless( $readcursor ) {
189             # Beginning of the bounce message or message/delivery-status part
190 1039 100       2109 if( grep { index($e, $_) > -1 } $startingof->{'message'}->@* ) {
  7273         11114  
191             # Check the message defined in $startingof->{"message"}, {"frozen"}
192 296         960 $readcursor |= $indicators->{'deliverystatus'};
193 296 50       969 next unless grep { index($e, $_) > -1 } $startingof->{'frozen'}->@*;
  592         2281  
194             }
195             }
196 4138 100 100     13751 next if ($readcursor & $indicators->{'deliverystatus'}) == 0 || $e eq "";
197              
198             # This message was created automatically by mail delivery software.
199             #
200             # A message that you sent could not be delivered to one or more of its
201             # recipients. This is a permanent error. The following address(es) failed:
202             #
203             # kijitora@example.jp
204             # SMTP error from remote mail server after RCPT TO::
205             # host neko.example.jp [192.0.2.222]: 550 5.1.1 ... User Unknown
206 2584         4425 $v = $dscontents->[-1];
207              
208 2584         3348 my $cv = "";
209 2584         3534 my $ce = 0;
210 2584         3221 while(1) {
211             # Check if the line matche the following patterns:
212 2584 100       5525 last if index($e, ' ') != 0; # The line should start with " " (2 spaces)
213 1037 100       2570 last if index($e, '@' ) < 2; # "@" should be included (email)
214 554 50       1306 last if index($e, '.' ) < 2; # "." should be included (domain part)
215 554 100       1445 last if index($e, 'pipe to |') > -1; # Exclude "pipe to /path/to/prog" line
216              
217 544         1164 my $cx = substr($e, 2, 1);
218 544 100       3075 last if $cx eq " "; # The 3rd character is " "
219 274 100 100     1470 last if $thirdparty == 0 && $cx eq "<"; # MXLogic returns " :..."
220              
221 264         457 $ce = 1; last;
  264         575  
222             }
223              
224 2584 100 66     6661 if( $ce == 1 || grep { index($e, $_) > 0 } $startingof->{"alias"}->@* ) {
  2320 100 100     13344  
225             # The line is including an email address
226 264 100       1075 if( $v->{'recipient'} ) {
227             # There are multiple recipient addresses in the message body.
228 10         50 push @$dscontents, __PACKAGE__->DELIVERYSTATUS;
229 10         29 $v = $dscontents->[-1];
230             }
231              
232 264 50       695 if( grep { index($e, $_) > 0 } $startingof->{"alias"}->@* ) {
  264         1123  
233             # The line does not include an email address
234             # deliver.c:4549| printed = US"an undisclosed address";
235             # an undisclosed address
236             # (generated from kijitora@example.jp)
237 0         0 $cv = substr($e, 2,);
238              
239             } else {
240             # kijitora@example.jp
241             # sabineko@example.jp: forced freeze
242             # mikeneko@example.jp : ...
243 264         533 $p1 = index($e, "<");
244 264         549 $p2 = index($e, ">:");
245              
246 264 100 66     1137 if( $p1 > 1 && $p2 > 1 ) {
247             # There are an email address and an error message in the line
248             # parser.c:743| while (bracket_count-- > 0) if (*s++ != '>')
249             # parser.c:744| {
250             # parser.c:745| *errorptr = s[-1] == 0
251             # parser.c:746| ? US"'>' missing at end of address"
252             # parser.c:747| : string_sprintf("malformed address: %.32s may not follow %.*s",
253             # parser.c:748| s-1, (int)(s - US mailbox - 1), mailbox);
254             # parser.c:749| goto PARSE_FAILED;
255             # parser.c:750| }
256 21         224 $cv = Sisimai::Address->s3s4(substr($e, $p1, $p2 - $p1 - 1));
257 21         220 $v->{'diagnosis'} = Sisimai::String->sweep(substr($e, $p2 + 1,));
258              
259             } else {
260             # There is an email address only in the line
261             # kijitora@example.jp
262 243         2527 $cv = Sisimai::Address->s3s4(substr($e, 2,));
263             }
264 264 50       1648 next unless Sisimai::Address->is_emailaddress($cv);
265             }
266 264         1214 $v->{'recipient'} = $cv;
267 264         668 $recipients++;
268              
269             } elsif( index($e, " (generated from ") > 0 || index($e, " generated by ") > 0 ) {
270             # (generated from kijitora@example.jp)
271             # pipe to |/bin/echo "Some pipe output"
272             # generated by userx@myhost.test.ex
273 36         182 for my $f ( split(" ", $e) ) {
274             # Find the alias address
275 118 100       347 next if index($f, '@') < 0;
276 31         222 $v->{'alias'} = Sisimai::Address->s3s4($f);
277             }
278             } else {
279 2284 50       3837 if( grep { index($e, $_) > -1 } $startingof->{'frozen'}->@* ) {
  4568 100       15141  
280             # Message *** has been frozen by the system filter.
281             # Message *** was frozen on arrival by ACL.
282 0         0 $v->{'alterrors'} .= $e.' ';
283              
284             } elsif( $boundary00 ) {
285             # --NNNNNNNNNN-eximdsn-MMMMMMMMMM
286             # Content-type: message/delivery-status
287             # ...
288 355 100       1233 if( Sisimai::RFC1894->match($e) ) {
289             # $e matched with any field defined in RFC3464
290 160 50       464 next unless my $o = Sisimai::RFC1894->field($e);
291              
292 160 100       529 if( $o->[3] eq 'addr' ) {
    100          
293             # Final-Recipient: rfc822;|/bin/echo "Some pipe output"
294 30 50       117 next unless $o->[0] eq 'final-recipient';
295 30 100 33     312 $v->{'spec'} ||= rindex($o->[2], '@') > -1 ? 'SMTP' : 'X-UNIX';
296              
297             } elsif( $o->[3] eq 'code' ) {
298             # Diagnostic-Code: SMTP; 550 5.1.1 ... User Unknown
299 20         62 $v->{'spec'} = uc $o->[1];
300 20         77 $v->{'diagnosis'} = $o->[2];
301              
302             } else {
303             # Other DSN fields defined in RFC3464
304 110 50       364 next unless exists $fieldtable->{ $o->[0] };
305 110         496 $v->{ $fieldtable->{ $o->[0] } } = $o->[2];
306             }
307             } else {
308             # Error message ?
309 195 100       447 next if $nextcursor;
310              
311             # Content-type: message/delivery-status
312 190 100       626 $nextcursor = 1 if index($e, $startingof->{'deliverystatus'}->[0]) == 0;
313 190 100       784 $v->{'alterrors'} .= $e.' ' if index($e, ' ') == 0;
314             }
315             } else {
316             # There is no boundary string in $boundary00
317 1929 100       3187 if( scalar @$dscontents == $recipients ) {
318             # Error message
319 711         2446 $v->{'diagnosis'} .= $e.' ';
320              
321             } else {
322             # Error message when email address above does not include '@' and domain part.
323             # pipe to |/path/to/prog ...
324             # generated by kijitora@example.com
325 1218 100       3201 next unless index($e, " ") == 0;
326 46         166 $v->{"diagnosis"} .= $e." ";
327             }
328             }
329             }
330             }
331              
332 301 100       1447 if( $recipients ) {
333             # Check "an undisclosed address", "unroutable address"
334 254         711 for my $q ( @$dscontents ) {
335             # Replace the recipient address with the value of "alias"
336 264 100       1056 next unless $q->{'alias'};
337 6 50 33     50 if( ! $q->{'recipient'} || rindex($q->{'recipient'}, '@') == -1 ) {
338             # The value of "recipient" is empty or does not include "@"
339 0         0 $q->{'recipient'} = $q->{'alias'};
340             }
341             }
342             } else {
343             # Fallback for getting recipient addresses
344 47 100       179 if( defined $mhead->{'x-failed-recipients'} ) {
345             # X-Failed-Recipients: kijitora@example.jp
346 25         137 my @rcptinhead = split(',', $mhead->{'x-failed-recipients'});
347 25         60 for my $e ( @rcptinhead ) { s/\A[ ]+//, s/[ ]+\z// for $e }
  25         120  
348 25         50 $recipients = scalar @rcptinhead;
349              
350 25         59 for my $e ( @rcptinhead ) {
351             # Insert each recipient address into @$dscontents
352 25         83 $dscontents->[-1]->{'recipient'} = $e;
353 25 50       118 next if scalar @$dscontents == $recipients;
354 0         0 push @$dscontents, __PACKAGE__->DELIVERYSTATUS;
355             }
356             }
357             }
358 301 100       1143 return undef unless $recipients;
359              
360             # Get the name of the local MTA
361             # Received: from marutamachi.example.org (c192128.example.net [192.0.2.128])
362 279   50     986 my $receivedby = $mhead->{'received'} || [];
363 279         1765 my $recvdtoken = Sisimai::RFC5322->received($receivedby->[-1]);
364              
365 279         825 for my $e ( @$dscontents ) {
366             # Check the error message, the rhost, the lhost, and the smtp command.
367 289   100     1879 $e->{"alterrors"} ||= "";
368 289 100 66     1237 if( ! $e->{'diagnosis'} && length($boundary00) > 0 ) {
369             # Empty Diagnostic-Code: or error message
370             # --NNNNNNNNNN-eximdsn-MMMMMMMMMM
371             # Content-type: message/delivery-status
372             #
373             # Reporting-MTA: dns; the.local.host.name
374             #
375             # Action: failed
376             # Final-Recipient: rfc822;/a/b/c
377             # Status: 5.0.0
378             #
379             # Action: failed
380             # Final-Recipient: rfc822;|/p/q/r
381             # Status: 5.0.0
382 10   50     64 $e->{'diagnosis'} = $dscontents->[0]->{'diagnosis'} || '';
383 10   33     42 $e->{'spec'} ||= $dscontents->[0]->{'spec'};
384 10 50       50 $e->{'alterrors'} = $dscontents->[0]->{'alterrors'} if $dscontents->[0]->{'alterrors'};
385             }
386              
387 289 100       845 if( $e->{'alterrors'} ) {
388             # Copy alternative error message
389 30   66     135 $e->{'diagnosis'} ||= $e->{'alterrors'};
390              
391 30 50 33     309 if( index($e->{'diagnosis'}, '-') == 0 || substr($e->{'diagnosis'}, -2, 2) eq '__' ) {
    100          
392             # Override the value of diagnostic code message
393 0         0 $e->{'diagnosis'} = $e->{'alterrors'};
394              
395             } elsif( length($e->{'diagnosis'}) < length($e->{'alterrors'}) ) {
396             # Override the value of diagnostic code message with the value of alterrors because
397             # the latter includes the former.
398 20         82 $e->{'alterrors'} =~ y/ //s;
399 20 50       128 $e->{'diagnosis'} = $e->{'alterrors'} if index(lc $e->{'alterrors'}, lc $e->{'diagnosis'}) > -1;
400             }
401 30         74 delete $e->{'alterrors'};
402             }
403 289         1668 $e->{'diagnosis'} = Sisimai::String->sweep($e->{'diagnosis'}); $p1 = index($e->{'diagnosis'}, '__');
  289         860  
404 289 50       916 $e->{'diagnosis'} = substr($e->{'diagnosis'}, 0, $p1) if $p1 > 1;
405              
406 289 100       876 unless( $e->{'rhost'} ) {
407             # Get the remote host name
408             # host neko.example.jp [192.0.2.222]: 550 5.1.1 ... User Unknown
409 269         799 $p1 = index($e->{'diagnosis'}, 'host ');
410 269         713 $p2 = index($e->{'diagnosis'}, ' ', $p1 + 5);
411 269 100       1186 $e->{'rhost'} = substr($e->{'diagnosis'}, $p1 + 5, $p2 - $p1 - 5) if $p1 > -1;
412 269   66     885 $e->{'rhost'} ||= $recvdtoken->[1];
413             }
414 289   100     1916 $e->{'lhost'} ||= $recvdtoken->[0];
415              
416 289 50       890 unless( $e->{'command'} ) {
417             # Get the SMTP command name for the session
418 289         974 SMTP: for my $r ( $startingof->{"command"}->@* ) {
419             # Verify each regular expression of SMTP commands
420 395 100       1549 next if index($e->{'diagnosis'}, $r) < 0;
421 213   100     2442 $e->{'command'} = Sisimai::SMTP::Command->find($e->{'diagnosis'}) || next;
422 183         364 last;
423             }
424              
425             # Detect the reason of bounce
426 289 100 66     2074 if( $e->{'command'} eq 'HELO' || $e->{'command'} eq 'EHLO' ) {
    100          
427             # HELO | Connected to 192.0.2.135 but my name was rejected.
428 5         17 $e->{'reason'} = 'blocked';
429              
430             } elsif( $e->{'command'} eq 'MAIL' ) {
431             # MAIL | Connected to 192.0.2.135 but sender was rejected.
432 76         238 $e->{'reason'} = 'onhold';
433              
434             } else {
435             # Try to match the error message with each message pattern
436 208         1158 SESSION: for my $r ( keys %$messagesof ) {
437             # Check each message pattern
438 1396 100       2483 next unless grep { index($e->{'diagnosis'}, $_) > -1 } $messagesof->{ $r }->@*;
  3362         7190  
439 25         64 $e->{'reason'} = $r;
440 25         57 last;
441             }
442              
443 208 100       798 unless( $e->{'reason'} ) {
444             # The reason "expired", or "mailererror"
445 183 100       469 $e->{'reason'} = 'expired' if grep { index($e->{'diagnosis'}, $_) > -1 } @$delayedfor;
  1281         2690  
446 183 100 50     710 $e->{'reason'} ||= 'mailererror' if index($e->{'diagnosis'}, 'pipe to |') > -1;
447             }
448             }
449             }
450              
451             STATUS: {
452             # Prefer the value of smtp reply code in Diagnostic-Code: field
453             # See set-of-emails/maildir/bsd/exim-20.eml
454             #
455             # Action: failed
456             # Final-Recipient: rfc822;userx@test.ex
457             # Status: 5.0.0
458             # Remote-MTA: dns; 127.0.0.1
459             # Diagnostic-Code: smtp; 450 TEMPERROR: retry timeout exceeded
460             #
461             # The value of "Status:" indicates permanent error but the value of SMTP reply code in
462             # Diagnostic-Code: field is "TEMPERROR"!!!!
463 289   100     466 my $cr = Sisimai::SMTP::Reply->find($e->{'diagnosis'}, $e->{'status'}) || '';
  289         2735  
464 289   100     2694 my $cs = Sisimai::SMTP::Status->find($e->{'diagnosis'}, $cr) || '';
465 289   100     1399 my $re = $e->{'reason'} || '';
466 289         568 my $cv = "";
467              
468 289 100 100     2438 if( Sisimai::SMTP::Failure->is_temporary($cr) || $re eq 'expired' || $re eq 'mailboxfull' ) {
      100        
469             # Set the pseudo status code as a temporary error
470 35 100       275 $cv = Sisimai::SMTP::Status->code($re, 1) if Sisimai::Reason->is_explicit($re);
471             }
472 289   66     1502 $e->{'replycode'} ||= $cr;
473 289   100     4679 $e->{'status'} ||= Sisimai::SMTP::Status->prefer($cv, $cs, $cr);
474             }
475 289   100     1248 $e->{'command'} ||= '';
476             }
477 279         3204 return {"ds" => $dscontents, "rfc822" => $emailparts->[1]};
478             }
479              
480             1;
481             __END__