Merge branch 'dt/send-email-client-cert' into jch

"git send-email" learns to support use of client-side certificates.

* dt/send-email-client-cert:
  send-email: add client certificate options
This commit is contained in:
Junio C Hamano
2026-03-03 11:08:29 -08:00
3 changed files with 71 additions and 11 deletions

View File

@@ -12,6 +12,22 @@ sendemail.smtpSSLCertPath::
Path to ca-certificates (either a directory or a single file).
Set it to an empty string to disable certificate verification.
sendemail.smtpSSLClientCert::
Path to the client certificate file to present if requested by the
server. This is required when the server is set up to verify client
certificates. If the corresponding private key is not included in the
file, it must be supplied using `sendemail.smtpSSLClientKey` or the
`--smtp-ssl-client-key` option.
sendemail.smtpSSLClientKey::
Path to the client private key file that corresponds to the client
certificate. To avoid misconfiguration, this configuration must be used
in conjunction with `sendemail.smtpSSLClientKey` or the
`--smtp-ssl-client-cert` option. If the client key is included in the
client certificate, the choice of private key depends on the format of
the certificate. Visit https://metacpan.org/pod/IO::Socket::SSL for more
details.
sendemail.<identity>.*::
Identity-specific versions of the `sendemail.*` parameters
found below, taking precedence over those when this

View File

@@ -290,6 +290,25 @@ must be used for each option.
variable, if set, or the backing SSL library's compiled-in default
otherwise (which should be the best choice on most platforms).
--smtp-ssl-client-cert <path>::
Path to the client certificate file to present if requested by the
server. This option is required when the server is set up to verify
client certificates. If the corresponding private key is not included in
the file, it must be supplied using the `sendemail.smtpSSLClientKey`
configuration variable or the `--smtp-ssl-client-key` option. Defaults
to the value of the `sendemail.smtpSSLClientCert` configuration
variable, if set.
--smtp-ssl-client-key <path>::
Path to the client private key file that corresponds to the client
certificate. To avoid misconfiguration, this option must be used in
conjunction with the `sendemail.smtpSSLClientKey` configuration variable
or the `--smtp-ssl-client-cert` option. If the client key is included in
the client certificate, the choice of private key depends on the format
of the certificate. Visit https://metacpan.org/pod/IO::Socket::SSL for
more details. Defaults to the value of the `sendemail.smtpSSLClientKey`
configuration variable, if set.
--smtp-user=<user>::
Username for SMTP-AUTH. Default is the value of `sendemail.smtpUser`;
if a username is not specified (with `--smtp-user` or `sendemail.smtpUser`),

View File

@@ -66,6 +66,8 @@ git send-email --translate-aliases
--smtp-ssl-cert-path <str> * Path to ca-certificates (either directory or file).
Pass an empty string to disable certificate
verification.
--smtp-ssl-client-cert <str> * Path to the client certificate file
--smtp-ssl-client-key <str> * Path to the private key file for the client certificate
--smtp-domain <str> * The domain name sent to HELO/EHLO handshake
--smtp-auth <str> * Space-separated list of allowed AUTH mechanisms, or
"none" to disable authentication.
@@ -279,6 +281,7 @@ my ($cover_cc, $cover_to);
my ($to_cmd, $cc_cmd, $header_cmd);
my ($smtp_server, $smtp_server_port, @smtp_server_options);
my ($smtp_authuser, $smtp_encryption, $smtp_ssl_cert_path);
my ($smtp_ssl_client_cert, $smtp_ssl_client_key);
my ($batch_size, $relogin_delay);
my ($identity, $aliasfiletype, @alias_files, $smtp_domain, $smtp_auth);
my ($imap_sent_folder);
@@ -350,6 +353,8 @@ my %config_settings = (
my %config_path_settings = (
"aliasesfile" => \@alias_files,
"smtpsslcertpath" => \$smtp_ssl_cert_path,
"smtpsslclientcert" => \$smtp_ssl_client_cert,
"smtpsslclientkey" => \$smtp_ssl_client_key,
"mailmap.file" => \$mailmap_file,
"mailmap.blob" => \$mailmap_blob,
);
@@ -531,6 +536,8 @@ my %options = (
"smtp-ssl" => sub { $smtp_encryption = 'ssl' },
"smtp-encryption=s" => \$smtp_encryption,
"smtp-ssl-cert-path=s" => \$smtp_ssl_cert_path,
"smtp-ssl-client-cert=s" => \$smtp_ssl_client_cert,
"smtp-ssl-client-key=s" => \$smtp_ssl_client_key,
"smtp-debug:i" => \$debug_net_smtp,
"smtp-domain:s" => \$smtp_domain,
"smtp-auth=s" => \$smtp_auth,
@@ -1520,6 +1527,8 @@ sub handle_smtp_error {
}
sub ssl_verify_params {
my %ret = ();
eval {
require IO::Socket::SSL;
IO::Socket::SSL->import(qw/SSL_VERIFY_PEER SSL_VERIFY_NONE/);
@@ -1531,20 +1540,36 @@ sub ssl_verify_params {
if (!defined $smtp_ssl_cert_path) {
# use the OpenSSL defaults
return (SSL_verify_mode => SSL_VERIFY_PEER());
$ret{SSL_verify_mode} = SSL_VERIFY_PEER();
}
else {
if ($smtp_ssl_cert_path eq "") {
$ret{SSL_verify_mode} = SSL_VERIFY_NONE();
} elsif (-d $smtp_ssl_cert_path) {
$ret{SSL_verify_mode} = SSL_VERIFY_PEER();
$ret{SSL_ca_path} = $smtp_ssl_cert_path;
} elsif (-f $smtp_ssl_cert_path) {
$ret{SSL_verify_mode} = SSL_VERIFY_PEER();
$ret{SSL_ca_file} = $smtp_ssl_cert_path;
} else {
die sprintf(__("CA path \"%s\" does not exist"), $smtp_ssl_cert_path);
}
}
if ($smtp_ssl_cert_path eq "") {
return (SSL_verify_mode => SSL_VERIFY_NONE());
} elsif (-d $smtp_ssl_cert_path) {
return (SSL_verify_mode => SSL_VERIFY_PEER(),
SSL_ca_path => $smtp_ssl_cert_path);
} elsif (-f $smtp_ssl_cert_path) {
return (SSL_verify_mode => SSL_VERIFY_PEER(),
SSL_ca_file => $smtp_ssl_cert_path);
} else {
die sprintf(__("CA path \"%s\" does not exist"), $smtp_ssl_cert_path);
if (defined $smtp_ssl_client_cert) {
$ret{SSL_cert_file} = $smtp_ssl_client_cert;
}
if (defined $smtp_ssl_client_key) {
if (!defined $smtp_ssl_client_cert) {
# Accept the client key only when a certificate is given.
# We die here because this case is a user error.
die sprintf(__("Only client key \"%s\" specified"),
$smtp_ssl_client_key);
}
$ret{SSL_key_file} = $smtp_ssl_client_key;
}
return %ret;
}
sub file_name_is_absolute {