File Coverage

lib/Sisimai/SMTP/Reply.pm
Criterion Covered Total %
statement 55 55 100.0
branch 30 32 93.7
condition 28 34 82.3
subroutine 6 6 100.0
pod 3 3 100.0
total 122 130 93.8


line stmt bran cond sub pod time code
1             package Sisimai::SMTP::Reply;
2 89     89   116921 use v5.26;
  89         323  
3 89     89   451 use strict;
  89         147  
  89         2329  
4 89     89   431 use warnings;
  89         146  
  89         76886  
5              
6             # http://www.ietf.org/rfc/rfc5321.txt
7             # RFC 1870: SMTP Service Extension for Message Size Declaration (SIZE)
8             # RFC 1985: SMTP Service Extension for Remote Message Queue Starting (ETRN)
9             # RFC 2645: Authenticated TURN for On-Demand Mail Relay (ATRN)
10             # RFC 3207: SMTP Service Extension for Secure SMTP over Transport Layer Security (STARTTLS) 1
11             # RFC 3030: SMTP Service Extension for Command Pipelining (CHUNKING)
12             # RFC 3461: Delivery Status Notifications (DSN)
13             # RFC 4954: SMTP Service Extension for Authentication (AUTH)
14             # RFC 5321: Simple Mail Transfer Protocol
15             # RFC 5336: SMTP Extension for Internationalized Email (UTF8SMTP)
16             # RFC 6531: SMTP Extension for Internationalized Email (SMTPUTF8)
17             # RFC 7504: SMTP 521 and 556 Reply Codes
18             # RFC 9422: The LIMITS SMTP Service Extension
19             # -------------------------------------------------------------------------------------------------
20             # 4.2.1. Reply Code Severities and Theory
21             #
22             # There are four values for the first digit of the reply code:
23             #
24             # 2yz Positive Completion reply
25             # The requested action has been successfully completed. A new request may be initiated.
26             #
27             # 3yz Positive Intermediate reply
28             # The command has been accepted, but the requested action is being held in abeyance, pending
29             # receipt of further information. The SMTP client should send another command specifying this
30             # information. This reply is used in command sequence groups (i.e., in DATA).
31             #
32             # 4yz Transient Negative Completion reply
33             # The command was not accepted, and the requested action did not occur. However, the error
34             # condition is temporary, and the action may be requested again. The sender should return to
35             # the beginning of the command sequence (if any). It is difficult to assign a meaning to
36             # "transient" when two different sites (receiver- and sender-SMTP agents) must agree on the
37             # interpretation. Each reply in this category might have a different time value, but the SMTP
38             # client SHOULD try again. A rule of thumb to determine whether a reply fits into the 4yz or
39             # the 5yz category (see below) is that replies are 4yz if they can be successful if repeated
40             # without any change in command form or in properties of the sender or receiver (that is, the
41             # command is repeated identically and the receiver does not put up a new implementation).
42             #
43             # 5yz Permanent Negative Completion reply
44             # The command was not accepted and the requested action did not occur. The SMTP client SHOULD
45             # NOT repeat the exact request (in the same sequence). Even some "permanent" error conditions
46             # can be corrected, so the human user may want to direct the SMTP client to reinitiate the
47             # command sequence by direct action at some point in the future (e.g., after the spelling has
48             # been changed, or the user has altered the account status).
49             #
50             # The second digit encodes responses in specific categories:
51             #
52             # x0z Syntax: These replies refer to syntax errors, syntactically correct commands that do
53             # not fit any functional category, and unimplemented or superfluous commands.
54             # x1z Information: These are replies to requests for information, such as status or help.
55             # x2z Connections: These are replies referring to the transmission channel.
56             # x3z Unspecified.
57             # x4z Unspecified.
58             # x5z Mail system: These replies indicate the status of the receiver mail system vis-a-vis
59             # the requested transfer or other mail system action.
60              
61             state $ReplyCode2 = [
62             # http://www.ietf.org/rfc/rfc5321.txt
63             # 211 System status, or system help reply
64             # 214 Help message (Information on how to use the receiver or the meaning of a particular
65             # non-standard command; this reply is useful only to the human user)
66             # 220 Service ready
67             # 221 Service closing transmission channel
68             # 235 Authentication successful (See RFC2554)
69             # 250 Requested mail action okay, completed
70             # 251 User not local; will forward to (See Section 3.4)
71             # 252 Cannot VRFY user, but will accept message and attempt delivery (See Section 3.5.3)
72             # 253 OK, pending messages for node started (See RFC1985)
73             # 334 A server challenge is sent as a 334 reply with the text part containing the [BASE64]
74             # encoded string supplied by the SASL mechanism. This challenge MUST NOT contain any text
75             # other than the BASE64 encoded challenge. (RFC4954)
76             # 354 Start mail input; end with .
77             211, 214, 220, 221, 235, 250, 251, 252, 253, 334, 354
78             ];
79             state $ReplyCode4 = [
80             # 421 Service not available, closing transmission channel (This may be a reply to
81             # any command if the service knows it must shut down)
82             # 422 (See RFC5248)
83             # 430 (See RFC5248)
84             # 432 A password transition is needed (See RFC4954)
85             # 450 Requested mail action not taken: mailbox unavailable (e.g., mailbox busy or temporarily
86             # blocked for policy reasons)
87             # 451 Requested action aborted: local error in processing
88             # 452 Requested action not taken: insufficient system storage
89             # 453 You have no mail (See RFC2645)
90             # 454 Temporary authentication failure (See RFC4954)
91             # 455 Server unable to accommodate parameters
92             # 458 Unable to queue messages for node (See RFC1985)
93             # 459 Node not allowed: (See RFC51985)
94             421, 450, 451, 452, 422, 430, 432, 453, 454, 455, 458, 459
95             ];
96             state $ReplyCode5 = [
97             # 500 Syntax error, command unrecognized (This may include errors such as command line too long)
98             # 501 Syntax error in parameters or arguments
99             # 502 Command not implemented (see Section 4.2.4)
100             # 503 Bad sequence of commands
101             # 504 Command parameter not implemented
102             # 521 Host does not accept mail (See RFC7504)
103             # 523 Encryption Needed (See RFC5248)
104             # 524 (See RFC5248)
105             # 525 User Account Disabled (See RFC5248)
106             # 530 Authentication required (See RFC4954)
107             # 533 (See RFC5248)
108             # 534 Authentication mechanism is too weak (See RFC4954)
109             # 535 Authentication credentials invalid (See RFC4954)
110             # 538 Encryption required for requested authentication mechanism (See RFC4954)
111             # 550 Requested action not taken: mailbox unavailable (e.g., mailbox not found, no access, or
112             # command rejected for policy reasons)
113             # 551 User not local; please try (See Section 3.4)
114             # 552 Requested mail action aborted: exceeded storage allocation
115             # 553 Requested action not taken: mailbox name not allowed (e.g., mailbox syntax incorrect)
116             # 554 Transaction failed (Or, in the case of a connection-opening response, "No SMTP service here")
117             # 555 MAIL FROM/RCPT TO parameters not recognized or not implemented
118             # 556 Domain does not accept mail (See RFC7504)
119             550, 552, 553, 551, 521, 525, 523, 524, 530, 533, 534, 535, 538, 555, 556, 554,
120             500, 501, 502, 503, 504,
121             ];
122             state $CodeOfSMTP = {'2' => $ReplyCode2, '4' => $ReplyCode4, '5' => $ReplyCode5};
123             state $Associated = {
124             "422" => ["AUTH", "4.7.12", "securityerror"], # RFC5238
125             "432" => ["AUTH", "4.7.12", "securityerror"], # RFC4954, RFC5321
126             "451" => ["", "", "systemerror"], # RFC2465, RFC5321
127             "452" => ["", "", "systemfull"], # RFC5321
128             "454" => ["AUTH", "4.7.0", "securityerror"], # RFC3027, RFC4954
129             "455" => ["", "", "syntaxerror"], # RFC5321
130             "500" => ["", "", "syntaxerror"], # RFC5321
131             "501" => ["", "", "syntaxerror"], # RFC5321
132             "502" => ["", "", "syntaxerror"], # RFC5321
133             "503" => ["", "", "syntaxerror"], # RFC5321
134             "504" => ["", "", "syntaxerror"], # RFC5321
135             "521" => ["CONN", "", "notaccept"], # RFC7504
136             "523" => ["AUTH", "5.7.10", "securityerror"], # RFC5248
137             "524" => ["AUTH", "5.7.11", "securityerror"], # RFC5248
138             "525" => ["AUTH", "5.7.13", "securityerror"], # RFC5248
139             "534" => ["AUTH", "5.7.9", "securityerror"], # RFC4954, RFC5248
140             "535" => ["AUTH", "5.7.8", "securityerror"], # RFC4954, RFC5248
141             "538" => ["AUTH", "5.7.11", "securityerror"], # RFC4954, RFC5248
142             "551" => ["", "", "hasmoved"], # RFC5321, RFC5336, RFC6531
143             "552" => ["", "", "mailboxfull"], # RFC5321
144             "555" => ["", "", "syntaxerror"], # RFC5321
145             "556" => ["RCPT", "", "notaccept"], # RFC7504
146             };
147              
148             sub test {
149             # Check whether a reply code is a valid code or not
150             # @param [String] argv1 Reply Code(DSN)
151             # @return [Boolean] 0 = Invalid reply code, 1 = Valid reply code
152             # @see code
153             # @since v5.0.0
154 243     243 1 413618 my $class = shift;
155 243   100     970 my $argv1 = shift || return 0;
156 242         535 my $reply = int $argv1;
157 242         773 my $first = int($reply / 100);
158              
159 242 100       955 return 0 if $reply < 211;
160 235 100       722 return 0 if $reply > 556;
161 232 100       956 return 0 if $reply % 100 > 59;
162              
163 228 100       697 if( $first == 2 ) {
164             # 2yz
165 4 100       15 return 1 if $reply == 235; # 235 is a valid code for AUTH (RFC4954)
166 3 50       7 return 0 if $reply > 253; # The maximum code of 2xy is 253 (RFC5248)
167 3 100 66     22 return 0 if $reply > 221 && $reply < 250; # There is no reply code between 221 and 250
168 1         6 return 1;
169             }
170              
171 224 100       583 if( $first == 3 ) {
172             # 3yz
173 2 50 66     12 return 0 unless $reply == 334 || $reply == 354;
174 2         8 return 1;
175             }
176 222         931 return 1;
177             }
178              
179             sub find {
180             # Get an SMTP reply code from the given string
181             # @param [String] argv1 String including SMTP reply code like 550
182             # @param [String] argv2 Status code like 5.1.1 or 2 or 4 or 5
183             # @return [String] SMTP reply code or empty if the first argument
184             # did not include SMTP Reply Code value
185             # @since v4.14.0
186 10500     10500 1 49087 my $class = shift;
187 10500 100 100     26095 my $argv1 = shift || return ""; return '' if length $argv1 < 3 || index(uc($argv1), 'X-UNIX;') > -1;
  10479   66     65406  
188 10478   100     28923 my $argv2 = shift || 0;
189              
190 10478         22163 my $esmtperror = ' '.$argv1.' ';
191 10478         16402 my $esmtpreply = '';
192 10478   100     36509 my $statuscode = substr($argv2, 0, 1) || '';
193             my $replycodes = $statuscode eq '5' || $statuscode eq '4' || $statuscode eq '2'
194             ? $CodeOfSMTP->{ $statuscode }
195 10478 100 100     148177 : [$CodeOfSMTP->{'5'}->@*, $CodeOfSMTP->{'4'}->@*, $CodeOfSMTP->{'2'}->@*];
196              
197 10478         22896 for my $e ( @$replycodes ) {
198             # Try to find an SMTP Reply Code from the given string
199 211671 100       373034 my $appearance = index($esmtperror, $e); next if $appearance == -1;
  211671         374648  
200 5416         7675 my $startingat = 1;
201 5416         9584 my $mesglength = length $esmtperror;
202              
203 5416         13156 while( $startingat + 3 < $mesglength ) {
204             # Find all the reply code in the error message
205 5573 100       12342 my $replyindex = index($esmtperror, $e, $startingat); last if $replyindex == -1;
  5573         12132  
206 5511   50     15373 my $formerchar = ord(substr($esmtperror, $replyindex - 1, 1)) || 0;
207 5511   50     14483 my $latterchar = ord(substr($esmtperror, $replyindex + 3, 1)) || 0;
208              
209 5511 100 100     18048 if( $formerchar > 45 && $formerchar < 58 ){ $startingat += $replyindex + 3; next }
  202         328  
  202         480  
210 5309 100 100     16698 if( $latterchar > 45 && $latterchar < 58 ){ $startingat += $replyindex + 3; next }
  40         54  
  40         95  
211 5269         9776 $esmtpreply = $e;
212 5269         9734 last;
213             }
214 5416 100       14705 last if $esmtpreply;
215             }
216 10478         85337 return $esmtpreply;
217             }
218              
219             sub associatedwith {
220             # associatedwith returns a slice associated with the SMTP reply code of the argument
221             # @param [String] argv1 SMTP reply code like 550
222             # @return [Array] ["SMTP Command", "DSN", "Reason"]
223             # @since v5.2.2
224 2579     2579 1 14973 my $class = shift;
225 2579   50     7969 my $argv1 = shift || return [];
226 2579   100     16803 return $Associated->{ $argv1 } || []
227             }
228              
229             1;
230             __END__