send-email: finer-grained SMTP error handling

Code captured errors but did not process them further.
This treated all failures the same without distinguishing SMTP status.

Add handle-smtp_error to extract SMTP status codes using a regex (as
defined in RFC 5321) and handle errors as follows:

- No error present:
	- If a result is provided, return 1 to indicate success.
	- Otherwise, return 0 to indicate failure.

- Error present with a captured three-digit status code:
	- For 4yz (transient errors), return 1 and allow retries.
	- For 5yz (permanent errors), return 0 to indicate failure.
	- For any other recognized status code, return 1, treating it as
	a transient error.

- Error present but no status code found:
	- Return 1 as a transient error.

Signed-off-by: Zheng Yuting <05ZYT30@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Zheng Yuting
2025-03-26 15:52:46 +08:00
committed by Junio C Hamano
parent ce20dec4a4
commit 1ac402cdf3

View File

@@ -1454,14 +1454,40 @@ sub smtp_auth_maybe {
$error = $@ || 'Unknown error';
};
# NOTE: SMTP status code handling will be added in a subsequent commit,
# return 1 when failed due to non-credential reasons
return $error ? 1 : ($result ? 1 : 0);
return ($error
? handle_smtp_error($error)
: ($result ? 1 : 0));
});
return $auth;
}
sub handle_smtp_error {
my ($error) = @_;
# Parse SMTP status code from error message in:
# https://www.rfc-editor.org/rfc/rfc5321.html
if ($error =~ /\b(\d{3})\b/) {
my $status_code = $1;
if ($status_code =~ /^4/) {
# 4yz: Transient Negative Completion reply
warn "SMTP transient error (status code $status_code): $error";
return 1;
} elsif ($status_code =~ /^5/) {
# 5yz: Permanent Negative Completion reply
warn "SMTP permanent error (status code $status_code): $error";
return 0;
}
# If no recognized status code is found, treat as transient error
warn "SMTP unknown error: $error. Treating as transient failure.";
return 1;
}
# If no status code is found, treat as transient error
warn "SMTP generic error: $error";
return 1;
}
sub ssl_verify_params {
eval {
require IO::Socket::SSL;