Merge branch 'zy/send-email-error-handling'

Auth-related (and unrelated) error handling in send-email has been
made more robust.

* zy/send-email-error-handling:
  send-email: finer-grained SMTP error handling
  send-email: capture errors in an eval {} block
maint
Junio C Hamano 2025-04-16 13:54:19 -07:00
commit 4c58159add
1 changed files with 51 additions and 14 deletions

View File

@ -1419,7 +1419,7 @@ sub smtp_auth_maybe {
die "invalid smtp auth: '${smtp_auth}'";
}

# TODO: Authentication may fail not because credentials were
# Authentication may fail not because credentials were
# invalid but due to other reasons, in which we should not
# reject credentials.
$auth = Git::credential({
@ -1431,26 +1431,63 @@ sub smtp_auth_maybe {
'password' => $smtp_authpass
}, sub {
my $cred = shift;
my $result;
my $error;

if ($smtp_auth) {
my $sasl = Authen::SASL->new(
mechanism => $smtp_auth,
callback => {
user => $cred->{'username'},
pass => $cred->{'password'},
authname => $cred->{'username'},
}
);
# catch all SMTP auth error in a unified eval block
eval {
if ($smtp_auth) {
my $sasl = Authen::SASL->new(
mechanism => $smtp_auth,
callback => {
user => $cred->{'username'},
pass => $cred->{'password'},
authname => $cred->{'username'},
}
);
$result = $smtp->auth($sasl);
} else {
$result = $smtp->auth($cred->{'username'}, $cred->{'password'});
}
1; # ensure true value is returned if no exception is thrown
} or do {
$error = $@ || 'Unknown error';
};

return !!$smtp->auth($sasl);
}

return !!$smtp->auth($cred->{'username'}, $cred->{'password'});
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;