| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package Sisimai::Lhost::OpenSMTPD; | 
| 2 | 21 |  |  | 21 |  | 5310 | use parent 'Sisimai::Lhost'; | 
|  | 21 |  |  |  |  | 37 |  | 
|  | 21 |  |  |  |  | 97 |  | 
| 3 | 21 |  |  | 21 |  | 1108 | use feature ':5.10'; | 
|  | 21 |  |  |  |  | 40 |  | 
|  | 21 |  |  |  |  | 1219 |  | 
| 4 | 21 |  |  | 21 |  | 245 | use strict; | 
|  | 21 |  |  |  |  | 35 |  | 
|  | 21 |  |  |  |  | 514 |  | 
| 5 | 21 |  |  | 21 |  | 90 | use warnings; | 
|  | 21 |  |  |  |  | 36 |  | 
|  | 21 |  |  |  |  | 12304 |  | 
| 6 |  |  |  |  |  |  |  | 
| 7 | 2 |  |  | 2 | 1 | 1002 | sub description { 'OpenSMTPD' } | 
| 8 |  |  |  |  |  |  | sub make { | 
| 9 |  |  |  |  |  |  | # Detect an error from OpenSMTPD | 
| 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 |  |  |  |  |  |  |  | 
| 15 |  |  |  |  |  |  | # @since v4.0.0 | 
| 16 | 474 |  |  | 474 | 1 | 1163 | my $class = shift; | 
| 17 | 474 |  | 100 |  |  | 1153 | my $mhead = shift // return undef; | 
| 18 | 473 |  | 50 |  |  | 1106 | my $mbody = shift // return undef; | 
| 19 |  |  |  |  |  |  |  | 
| 20 | 473 | 100 |  |  |  | 1541 | return undef unless index($mhead->{'subject'}, 'Delivery status notification') > -1; | 
| 21 | 57 | 100 |  |  |  | 217 | return undef unless index($mhead->{'from'}, 'Mailer Daemon <') > -1; | 
| 22 | 31 | 50 |  |  |  | 51 | return undef unless grep { rindex($_, ' (OpenSMTPD) with ') > -1 } @{ $mhead->{'received'} }; | 
|  | 57 |  |  |  |  | 179 |  | 
|  | 31 |  |  |  |  | 73 |  | 
| 23 |  |  |  |  |  |  |  | 
| 24 | 31 |  |  |  |  | 67 | state $indicators = __PACKAGE__->INDICATORS; | 
| 25 | 31 |  |  |  |  | 50 | state $rebackbone = qr|^[ ]+Below is a copy of the original message:|m; | 
| 26 | 31 |  |  |  |  | 48 | state $startingof = { | 
| 27 |  |  |  |  |  |  | # http://www.openbsd.org/cgi-bin/man.cgi?query=smtpd&sektion=8 | 
| 28 |  |  |  |  |  |  | # opensmtpd-5.4.2p1/smtpd/ | 
| 29 |  |  |  |  |  |  | #   bounce.c/317:#define NOTICE_INTRO \ | 
| 30 |  |  |  |  |  |  | #   bounce.c/318:    "    Hi!\n\n"    \ | 
| 31 |  |  |  |  |  |  | #   bounce.c/319:    "    This is the MAILER-DAEMON, please DO NOT REPLY to this e-mail.\n" | 
| 32 |  |  |  |  |  |  | #   bounce.c/320: | 
| 33 |  |  |  |  |  |  | #   bounce.c/321:const char *notice_error = | 
| 34 |  |  |  |  |  |  | #   bounce.c/322:    "    An error has occurred while attempting to deliver a message for\n" | 
| 35 |  |  |  |  |  |  | #   bounce.c/323:    "    the following list of recipients:\n\n"; | 
| 36 |  |  |  |  |  |  | #   bounce.c/324: | 
| 37 |  |  |  |  |  |  | #   bounce.c/325:const char *notice_warning = | 
| 38 |  |  |  |  |  |  | #   bounce.c/326:    "    A message is delayed for more than %s for the following\n" | 
| 39 |  |  |  |  |  |  | #   bounce.c/327:    "    list of recipients:\n\n"; | 
| 40 |  |  |  |  |  |  | #   bounce.c/328: | 
| 41 |  |  |  |  |  |  | #   bounce.c/329:const char *notice_warning2 = | 
| 42 |  |  |  |  |  |  | #   bounce.c/330:    "    Please note that this is only a temporary failure report.\n" | 
| 43 |  |  |  |  |  |  | #   bounce.c/331:    "    The message is kept in the queue for up to %s.\n" | 
| 44 |  |  |  |  |  |  | #   bounce.c/332:    "    You DO NOT NEED to re-send the message to these recipients.\n\n"; | 
| 45 |  |  |  |  |  |  | #   bounce.c/333: | 
| 46 |  |  |  |  |  |  | #   bounce.c/334:const char *notice_success = | 
| 47 |  |  |  |  |  |  | #   bounce.c/335:    "    Your message was successfully delivered to these recipients.\n\n"; | 
| 48 |  |  |  |  |  |  | #   bounce.c/336: | 
| 49 |  |  |  |  |  |  | #   bounce.c/337:const char *notice_relay = | 
| 50 |  |  |  |  |  |  | #   bounce.c/338:    "    Your message was relayed to these recipients.\n\n"; | 
| 51 |  |  |  |  |  |  | #   bounce.c/339: | 
| 52 |  |  |  |  |  |  | 'message' => ['    This is the MAILER-DAEMON, please DO NOT REPLY to this'], | 
| 53 |  |  |  |  |  |  | }; | 
| 54 | 31 |  |  |  |  | 69 | state $messagesof = { | 
| 55 |  |  |  |  |  |  | # smtpd/queue.c:221|  envelope_set_errormsg(&evp, "Envelope expired"); | 
| 56 |  |  |  |  |  |  | 'expired'     => ['Envelope expired'], | 
| 57 |  |  |  |  |  |  | 'hostunknown' => [ | 
| 58 |  |  |  |  |  |  | # smtpd/mta.c:976|  relay->failstr = "Invalid domain name"; | 
| 59 |  |  |  |  |  |  | # smtpd/mta.c:980|  relay->failstr = "Domain does not exist"; | 
| 60 |  |  |  |  |  |  | 'Invalid domain name', | 
| 61 |  |  |  |  |  |  | 'Domain does not exist', | 
| 62 |  |  |  |  |  |  | ], | 
| 63 |  |  |  |  |  |  | # smtp/mta.c:1085|  relay->failstr = "Destination seem to reject all mails"; | 
| 64 |  |  |  |  |  |  | 'notaccept'   => ['Destination seem to reject all mails'], | 
| 65 |  |  |  |  |  |  | 'networkerror'=> [ | 
| 66 |  |  |  |  |  |  | #  smtpd/mta.c:972|  relay->failstr = "Temporary failure in MX lookup"; | 
| 67 |  |  |  |  |  |  | 'Address family mismatch on destination MXs', | 
| 68 |  |  |  |  |  |  | 'All routes to destination blocked', | 
| 69 |  |  |  |  |  |  | 'bad DNS lookup error code', | 
| 70 |  |  |  |  |  |  | 'Could not retrieve source address', | 
| 71 |  |  |  |  |  |  | 'Loop detected', | 
| 72 |  |  |  |  |  |  | 'Network error on destination MXs', | 
| 73 |  |  |  |  |  |  | 'No MX found for domain', | 
| 74 |  |  |  |  |  |  | 'No MX found for destination', | 
| 75 |  |  |  |  |  |  | 'No valid route to remote MX', | 
| 76 |  |  |  |  |  |  | 'No valid route to destination', | 
| 77 |  |  |  |  |  |  | 'Temporary failure in MX lookup', | 
| 78 |  |  |  |  |  |  | ], | 
| 79 |  |  |  |  |  |  | # smtpd/mta.c:1013|  relay->failstr = "Could not retrieve credentials"; | 
| 80 |  |  |  |  |  |  | 'securityerror' => ['Could not retrieve credentials'], | 
| 81 |  |  |  |  |  |  | }; | 
| 82 |  |  |  |  |  |  |  | 
| 83 | 31 |  |  |  |  | 119 | my $dscontents = [__PACKAGE__->DELIVERYSTATUS]; | 
| 84 | 31 |  |  |  |  | 164 | my $emailsteak = Sisimai::RFC5322->fillet($mbody, $rebackbone); | 
| 85 | 31 |  |  |  |  | 49 | my $readcursor = 0;     # (Integer) Points the current cursor position | 
| 86 | 31 |  |  |  |  | 70 | my $recipients = 0;     # (Integer) The number of 'Final-Recipient' header | 
| 87 | 31 |  |  |  |  | 62 | my $v = undef; | 
| 88 |  |  |  |  |  |  |  | 
| 89 | 31 |  |  |  |  | 162 | for my $e ( split("\n", $emailsteak->[0]) ) { | 
| 90 |  |  |  |  |  |  | # Read error messages and delivery status lines from the head of the email | 
| 91 |  |  |  |  |  |  | # to the previous line of the beginning of the original message. | 
| 92 | 303 | 100 |  |  |  | 470 | unless( $readcursor ) { | 
| 93 |  |  |  |  |  |  | # Beginning of the bounce message or message/delivery-status part | 
| 94 | 103 | 100 |  |  |  | 288 | $readcursor |= $indicators->{'deliverystatus'} if index($e, $startingof->{'message'}->[0]) == 0; | 
| 95 | 103 |  |  |  |  | 137 | next; | 
| 96 |  |  |  |  |  |  | } | 
| 97 | 200 | 50 |  |  |  | 289 | next unless $readcursor & $indicators->{'deliverystatus'}; | 
| 98 | 200 | 100 |  |  |  | 276 | next unless length $e; | 
| 99 |  |  |  |  |  |  |  | 
| 100 |  |  |  |  |  |  | #    Hi! | 
| 101 |  |  |  |  |  |  | # | 
| 102 |  |  |  |  |  |  | #    This is the MAILER-DAEMON, please DO NOT REPLY to this e-mail. | 
| 103 |  |  |  |  |  |  | # | 
| 104 |  |  |  |  |  |  | #    An error has occurred while attempting to deliver a message for | 
| 105 |  |  |  |  |  |  | #    the following list of recipients: | 
| 106 |  |  |  |  |  |  | # | 
| 107 |  |  |  |  |  |  | # kijitora@example.jp: 550 5.2.2 ... Mailbox Full | 
| 108 |  |  |  |  |  |  | # | 
| 109 |  |  |  |  |  |  | #    Below is a copy of the original message: | 
| 110 | 128 |  |  |  |  | 132 | $v = $dscontents->[-1]; | 
| 111 |  |  |  |  |  |  |  | 
| 112 | 128 | 100 |  |  |  | 347 | if( $e =~ /\A([^ ]+?[@][^ ]+?):?[ ](.+)\z/ ) { | 
| 113 |  |  |  |  |  |  | # kijitora@example.jp: 550 5.2.2 ... Mailbox Full | 
| 114 | 36 | 100 |  |  |  | 110 | if( $v->{'recipient'} ) { | 
| 115 |  |  |  |  |  |  | # There are multiple recipient addresses in the message body. | 
| 116 | 5 |  |  |  |  | 19 | push @$dscontents, __PACKAGE__->DELIVERYSTATUS; | 
| 117 | 5 |  |  |  |  | 10 | $v = $dscontents->[-1]; | 
| 118 |  |  |  |  |  |  | } | 
| 119 | 36 |  |  |  |  | 95 | $v->{'recipient'} = $1; | 
| 120 | 36 |  |  |  |  | 70 | $v->{'diagnosis'} = $2; | 
| 121 | 36 |  |  |  |  | 54 | $recipients++; | 
| 122 |  |  |  |  |  |  | } | 
| 123 |  |  |  |  |  |  | } | 
| 124 | 31 | 50 |  |  |  | 92 | return undef unless $recipients; | 
| 125 |  |  |  |  |  |  |  | 
| 126 | 31 |  |  |  |  | 70 | for my $e ( @$dscontents ) { | 
| 127 | 36 |  |  |  |  | 184 | $e->{'diagnosis'} = Sisimai::String->sweep($e->{'diagnosis'}); | 
| 128 |  |  |  |  |  |  |  | 
| 129 | 36 |  |  |  |  | 123 | SESSION: for my $r ( keys %$messagesof ) { | 
| 130 |  |  |  |  |  |  | # Verify each regular expression of session errors | 
| 131 | 135 | 100 |  |  |  | 153 | next unless grep { index($e->{'diagnosis'}, $_) > -1 } @{ $messagesof->{ $r } }; | 
|  | 402 |  |  |  |  | 736 |  | 
|  | 135 |  |  |  |  | 183 |  | 
| 132 | 20 |  |  |  |  | 46 | $e->{'reason'} = $r; | 
| 133 | 20 |  |  |  |  | 55 | last; | 
| 134 |  |  |  |  |  |  | } | 
| 135 |  |  |  |  |  |  | } | 
| 136 | 31 |  |  |  |  | 139 | return { 'ds' => $dscontents, 'rfc822' => $emailsteak->[1] }; | 
| 137 |  |  |  |  |  |  | } | 
| 138 |  |  |  |  |  |  |  | 
| 139 |  |  |  |  |  |  | 1; | 
| 140 |  |  |  |  |  |  | __END__ |