From Code to Community: Sponsoring The Perl and Raku Conference 2025 Learn more

use v5.26;
use strict;
# -------------------------------------------------------------------------------------------------
# 4.2.1. Reply Code Severities and Theory
#
# There are four values for the first digit of the reply code:
#
# 2yz Positive Completion reply
# The requested action has been successfully completed. A new request may be initiated.
#
# 3yz Positive Intermediate reply
# The command has been accepted, but the requested action is being held in abeyance, pending
# receipt of further information. The SMTP client should send another command specifying this
# information. This reply is used in command sequence groups (i.e., in DATA).
#
# 4yz Transient Negative Completion reply
# The command was not accepted, and the requested action did not occur. However, the error
# condition is temporary, and the action may be requested again. The sender should return to
# the beginning of the command sequence (if any). It is difficult to assign a meaning to
# "transient" when two different sites (receiver- and sender-SMTP agents) must agree on the
# interpretation. Each reply in this category might have a different time value, but the SMTP
# client SHOULD try again. A rule of thumb to determine whether a reply fits into the 4yz or
# the 5yz category (see below) is that replies are 4yz if they can be successful if repeated
# without any change in command form or in properties of the sender or receiver (that is, the
# command is repeated identically and the receiver does not put up a new implementation).
#
# 5yz Permanent Negative Completion reply
# The command was not accepted and the requested action did not occur. The SMTP client SHOULD
# NOT repeat the exact request (in the same sequence). Even some "permanent" error conditions
# can be corrected, so the human user may want to direct the SMTP client to reinitiate the
# command sequence by direct action at some point in the future (e.g., after the spelling has
# been changed, or the user has altered the account status).
#
# The second digit encodes responses in specific categories:
#
# x0z Syntax: These replies refer to syntax errors, syntactically correct commands that do
# not fit any functional category, and unimplemented or superfluous commands.
# x1z Information: These are replies to requests for information, such as status or help.
# x2z Connections: These are replies referring to the transmission channel.
# x3z Unspecified.
# x4z Unspecified.
# x5z Mail system: These replies indicate the status of the receiver mail system vis-a-vis
# the requested transfer or other mail system action.
state $ReplyCode2 = [
# 211 System status, or system help reply
# 214 Help message (Information on how to use the receiver or the meaning of a particular
# non-standard command; this reply is useful only to the human user)
# 220 <domain> Service ready
# 221 <domain> Service closing transmission channel
# 235 Authentication successful (See RFC2554)
# 250 Requested mail action okay, completed
# 251 User not local; will forward to <forward-path> (See Section 3.4)
# 252 Cannot VRFY user, but will accept message and attempt delivery (See Section 3.5.3)
# 253 OK, <n> pending messages for node <domain> started (See RFC1985)
# 354 Start mail input; end with <CRLF>.<CRLF>
211, 214, 220, 221, 235, 250, 251, 252, 253, 354
];
state $ReplyCode4 = [
# 421 <domain> Service not available, closing transmission channel (This may be a reply to
# any command if the service knows it must shut down)
# 422 (See RFC5248)
# 430 (See RFC5248)
# 432 A password transition is needed (See RFC4954)
# 450 Requested mail action not taken: mailbox unavailable (e.g., mailbox busy or temporarily
# blocked for policy reasons)
# 451 Requested action aborted: local error in processing
# 452 Requested action not taken: insufficient system storage
# 453 You have no mail (See RFC2645)
# 454 Temporary authentication failure (See RFC4954)
# 455 Server unable to accommodate parameters
# 456 please retry immediately the message over IPv4 because it fails SPF and DKIM (See
# 458 Unable to queue messages for node <domain> (See RFC1985)
# 459 Node <domain> not allowed: <reason> (See RFC51985)
421, 450, 451, 452, 422, 430, 432, 453, 454, 455, 456, 458, 459
];
state $ReplyCode5 = [
# 500 Syntax error, command unrecognized (This may include errors such as command line too long)
# 501 Syntax error in parameters or arguments
# 502 Command not implemented (see Section 4.2.4)
# 503 Bad sequence of commands
# 504 Command parameter not implemented
# 521 Host does not accept mail (See RFC7504)
# 523 Encryption Needed (See RFC5248)
# 524 (See RFC5248)
# 525 User Account Disabled (See RFC5248)
# 530 Authentication required (See RFC4954)
# 533 (See RFC5248)
# 534 Authentication mechanism is too weak (See RFC4954)
# 535 Authentication credentials invalid (See RFC4954)
# 538 Encryption required for requested authentication mechanism (See RFC4954)
# 550 Requested action not taken: mailbox unavailable (e.g., mailbox not found, no access, or
# command rejected for policy reasons)
# 551 User not local; please try <forward-path> (See Section 3.4)
# 552 Requested mail action aborted: exceeded storage allocation
# 553 Requested action not taken: mailbox name not allowed (e.g., mailbox syntax incorrect)
# 554 Transaction failed (Or, in the case of a connection-opening response, "No SMTP service here")
# 555 MAIL FROM/RCPT TO parameters not recognized or not implemented
# 556 Domain does not accept mail (See RFC7504)
# 557 draft-moore-email-addrquery-01
550, 552, 553, 551, 521, 525, 502, 520, 523, 524, 530, 533, 534, 535, 538, 551, 555, 556, 554,
557, 500, 501, 502, 503, 504,
];
state $CodeOfSMTP = { '2' => $ReplyCode2, '4' => $ReplyCode4, '5' => $ReplyCode5 };
sub test {
# Check whether a reply code is a valid code or not
# @param [String] argv1 Reply Code(DSN)
# @return [Boolean] 0 = Invalid reply code, 1 = Valid reply code
# @see code
# @since v5.0.0
my $class = shift;
my $argv1 = shift || return undef;
my $reply = int $argv1;
my $first = int($reply / 100);
return 0 if $reply < 211;
return 0 if $reply > 557;
return 0 if $reply % 100 > 59;
if( $first == 2 ) {
# 2yz
return 1 if $reply == 235; # 235 is a valid code for AUTH (RFC4954)
return 0 if $reply > 253; # The maximum code of 2xy is 253 (RFC5248)
return 0 if $reply > 221 && $reply < 250; # There is no reply code between 221 and 250
return 1;
}
if( $first == 3 ) {
# 3yz
return 0 unless $reply == 354;
return 1;
}
return 1;
}
sub find {
# Get an SMTP reply code from the given string
# @param [String] argv1 String including SMTP reply code like 550
# @param [String] argv2 Status code like 5.1.1 or 2 or 4 or 5
# @return [String] SMTP reply code or empty if the first argument
# did not include SMTP Reply Code value
# @since v4.14.0
my $class = shift;
my $argv1 = shift || return undef;
my $argv2 = shift || 0;
return '' if length $argv1 < 3;
return '' if index(uc($argv1), 'X-UNIX;') > -1;
my $esmtperror = ' '.$argv1.' ';
my $esmtpreply = '';
my $statuscode = substr($argv2, 0, 1) || '';
my $replycodes = $statuscode eq '5' || $statuscode eq '4' || $statuscode eq '2'
? $CodeOfSMTP->{ $statuscode }
: [$CodeOfSMTP->{'5'}->@*, $CodeOfSMTP->{'4'}->@*, $CodeOfSMTP->{'2'}->@*];
for my $e ( @$replycodes ) {
# Try to find an SMTP Reply Code from the given string
my $appearance = index($esmtperror, $e); next if $appearance == -1;
my $startingat = 1;
my $mesglength = length $esmtperror;
while( $startingat + 3 < $mesglength ) {
# Find all the reply code in the error message
my $replyindex = index($esmtperror, $e, $startingat); last if $replyindex == -1;
my $formerchar = ord(substr($esmtperror, $replyindex - 1, 1)) || 0;
my $latterchar = ord(substr($esmtperror, $replyindex + 3, 1)) || 0;
if( $formerchar > 45 && $formerchar < 58 ){ $startingat += $replyindex + 3; next }
if( $latterchar > 45 && $latterchar < 58 ){ $startingat += $replyindex + 3; next }
$esmtpreply = $e;
last;
}
last if $esmtpreply;
}
return $esmtpreply;
}
1;
__END__
=encoding utf-8
=head1 NAME
Sisimai::SMTP::Reply - SMTP reply code related class
=head1 SYNOPSIS
use Sisimai::SMTP::Reply;
print Sisimai::SMTP::Reply->find('550 5.1.1 Unknown user'); # 550
=head1 DESCRIPTION
C<Sisimai::SMTP::Reply> is a utility class for getting the SMTP reply code value from given error
message text.
=head1 CLASS METHODS
=head2 C<B<test(I<D.S.N.>)>>
C<test()> method checks whether the reply code is a valid code or not.
print Sisimai::SMTP::Reply->test('521'); # 1
print Sisimai::SMTP::Reply->test('386'); # 0
print Sisimai::SMTP::Reply->test('101'); # 0
print Sisimai::SMTP::Reply->test('640'); # 0
=head2 C<B<find(I<String>)>>
C<find()> method returns the SMTP reply code value.
print Sisimai::SMTP::Reply->find('5.0.0'); # ''
print Sisimai::SMTP::Reply->find('550 5.1.1 User unknown'); # 550
print Sisimai::SMTP::Reply->find('421 Delivery Expired'); # 421
=head1 AUTHOR
azumakuniyuki
=head1 COPYRIGHT
Copyright (C) 2015-2016,2018,2020,2021,2023,2024 azumakuniyuki, All rights reserved.
=head1 LICENSE
This software is distributed under The BSD 2-Clause License.
=cut