git-send-email: add option to specify sendmail command

The sendemail.smtpServer configuration option and --smtp-server command line option both support using a sendmail-like program to send emails by specifying an absolute file path. However, this is not ideal for the following reasons: 1. It overloads the meaning of smtpServer (now a program is being used for the server?) 2. It doesn't allow for non-absolute paths, arguments, or arbitrary scripting Requiring an absolute path is bad for portability, as the same program may be in different locations on different systems. If a user wishes to pass arguments to their program, they have to use the smtpServerOption option, which is cumbersome (as it must be repeated for each option) and doesn't adhere to normal git conventions. Introduce a new configuration option sendemail.sendmailCmd as well as a command line option --sendmail-cmd that can be used to specify a command (with or without arguments) or shell expression to run to send email. The name of this option is consistent with --to-cmd and --cc-cmd. This invocation honors the user's $PATH so that absolute paths are not necessary. Arbitrary shell expressions are also supported, allowing users to do basic scripting. Give this option a higher precedence over --smtp-server and sendemail.smtpServer, as the new interface is more flexible. For backward compatibility, continue to support absolute paths in --smtp-server and sendemail.smtpServer. Signed-off-by: Gregory Anders <greg@gpanders.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Gregory Anders committed May 14, 2021 at 09:15 UTC cd5b33fbdc0299765278d0b607b64202603f0882
3 files changed +76 -14
Documentation/git-send-email.txt
+18 -7
@@ -167,6 +167,14 @@ Sending
167 `sendemail.envelopeSender` configuration variable; if that is
168 unspecified, choosing the envelope sender is left to your MTA.
169
170 +--sendmail-cmd=<command>::
171 + Specify a command to run to send the email. The command should
172 + be sendmail-like; specifically, it must support the `-i` option.
173 + The command will be executed in the shell if necessary. Default
174 + is the value of `sendemail.sendmailcmd`. If unspecified, and if
175 + --smtp-server is also unspecified, git-send-email will search
176 + for `sendmail` in `/usr/sbin`, `/usr/lib` and $PATH.
177 +
178 --smtp-encryption=<encryption>::
179 Specify the encryption to use, either 'ssl' or 'tls'. Any other
180 value reverts to plain SMTP. Default is the value of
@@ -211,13 +219,16 @@ a password is obtained using 'git-credential'.
219
220 --smtp-server=<host>::
221 If set, specifies the outgoing SMTP server to use (e.g.
214 - `smtp.example.com` or a raw IP address). Alternatively it can
215 - specify a full pathname of a sendmail-like program instead;
216 - the program must support the `-i` option. Default value can
217 - be specified by the `sendemail.smtpServer` configuration
218 - option; the built-in default is to search for `sendmail` in
219 - `/usr/sbin`, `/usr/lib` and $PATH if such program is
220 - available, falling back to `localhost` otherwise.
222 + `smtp.example.com` or a raw IP address). If unspecified, and if
223 + `--sendmail-cmd` is also unspecified, the default is to search
224 + for `sendmail` in `/usr/sbin`, `/usr/lib` and $PATH if such a
225 + program is available, falling back to `localhost` otherwise.
226 ++
227 +For backward compatibility, this option can also specify a full pathname
228 +of a sendmail-like program instead; the program must support the `-i`
229 +option. This method does not support passing arguments or using plain
230 +command names. For those use cases, consider using `--sendmail-cmd`
231 +instead.
232
233 --smtp-server-port=<port>::
234 Specifies a port different from the default port (SMTP
git-send-email.perl
+27 -7
@@ -70,6 +70,7 @@ git send-email --dump-aliases
70
71 Sending:
72 --envelope-sender <str> * Email envelope sender.
73 + --sendmail-cmd <str> * Command to run to send email.
74 --smtp-server <str:int> * Outgoing SMTP server to use. The port
75 is optional. Default 'localhost'.
76 --smtp-server-option <str> * Outgoing SMTP server option to use.
@@ -243,6 +244,7 @@ my ($confirm);
244 my (@suppress_cc);
245 my ($auto_8bit_encoding);
246 my ($compose_encoding);
247 +my ($sendmail_cmd);
248 # Variables with corresponding config settings & hardcoded defaults
249 my ($debug_net_smtp) = 0; # Net::SMTP, see send_message()
250 my $thread = 1;
@@ -290,6 +292,7 @@ my %config_settings = (
292 "assume8bitencoding" => \$auto_8bit_encoding,
293 "composeencoding" => \$compose_encoding,
294 "transferencoding" => \$target_xfer_encoding,
295 + "sendmailcmd" => \$sendmail_cmd,
296 );
297
298 my %config_path_settings = (
@@ -423,6 +426,7 @@ $rc = GetOptions(
426 "no-bcc" => \$no_bcc,
427 "chain-reply-to!" => \$chain_reply_to,
428 "no-chain-reply-to" => sub {$chain_reply_to = 0},
429 + "sendmail-cmd=s" => \$sendmail_cmd,
430 "smtp-server=s" => \$smtp_server,
431 "smtp-server-option=s" => \@smtp_server_options,
432 "smtp-server-port=s" => \$smtp_server_port,
@@ -996,16 +1000,19 @@ if (defined $reply_to) {
1000 $reply_to = sanitize_address($reply_to);
1001 }
1002
999 -if (!defined $smtp_server) {
1003 +if (!defined $sendmail_cmd && !defined $smtp_server) {
1004 my @sendmail_paths = qw( /usr/sbin/sendmail /usr/lib/sendmail );
1005 push @sendmail_paths, map {"$_/sendmail"} split /:/, $ENV{PATH};
1006 foreach (@sendmail_paths) {
1007 if (-x $_) {
1004 - $smtp_server = $_;
1008 + $sendmail_cmd = $_;
1009 last;
1010 }
1011 }
1008 - $smtp_server ||= 'localhost'; # could be 127.0.0.1, too... *shrug*
1012 +
1013 + if (!defined $sendmail_cmd) {
1014 + $smtp_server = 'localhost'; # could be 127.0.0.1, too... *shrug*
1015 + }
1016 }
1017
1018 if ($compose && $compose > 0) {
@@ -1485,11 +1492,17 @@ EOF
1492
1493 if ($dry_run) {
1494 # We don't want to send the email.
1488 - } elsif (file_name_is_absolute($smtp_server)) {
1495 + } elsif (defined $sendmail_cmd || file_name_is_absolute($smtp_server)) {
1496 my $pid = open my $sm, '|-';
1497 defined $pid or die $!;
1498 if (!$pid) {
1492 - exec($smtp_server, @sendmail_parameters) or die $!;
1499 + if (defined $sendmail_cmd) {
1500 + exec ("sh", "-c", "$sendmail_cmd \"\$@\"", "-", @sendmail_parameters)
1501 + or die $!;
1502 + } else {
1503 + exec ($smtp_server, @sendmail_parameters)
1504 + or die $!;
1505 + }
1506 }
1507 print $sm "$header\n$message";
1508 close $sm or die $!;
@@ -1585,14 +1598,21 @@ EOF
1598 printf($dry_run ? __("Dry-Sent %s\n") : __("Sent %s\n"), $subject);
1599 } else {
1600 print($dry_run ? __("Dry-OK. Log says:\n") : __("OK. Log says:\n"));
1588 - if (!file_name_is_absolute($smtp_server)) {
1601 + if (!defined $sendmail_cmd && !file_name_is_absolute($smtp_server)) {
1602 print "Server: $smtp_server\n";
1603 print "MAIL FROM:<$raw_from>\n";
1604 foreach my $entry (@recipients) {
1605 print "RCPT TO:<$entry>\n";
1606 }
1607 } else {
1595 - print "Sendmail: $smtp_server ".join(' ',@sendmail_parameters)."\n";
1608 + my $sm;
1609 + if (defined $sendmail_cmd) {
1610 + $sm = $sendmail_cmd;
1611 + } else {
1612 + $sm = $smtp_server;
1613 + }
1614 +
1615 + print "Sendmail: $sm ".join(' ',@sendmail_parameters)."\n";
1616 }
1617 print $header, "\n";
1618 if ($smtp) {
t/t9001-send-email.sh
+31
@@ -2097,6 +2097,37 @@ test_expect_success $PREREQ 'leading and trailing whitespaces are removed' '
2097 test_cmp expected-list actual-list
2098 '
2099
2100 +test_expect_success $PREREQ 'test using command name with --sendmail-cmd' '
2101 + clean_fake_sendmail &&
2102 + PATH="$(pwd):$PATH" \
2103 + git send-email \
2104 + --from="Example <nobody@example.com>" \
2105 + --to=nobody@example.com \
2106 + --sendmail-cmd="fake.sendmail" \
2107 + HEAD^ &&
2108 + test_path_is_file commandline1
2109 +'
2110 +
2111 +test_expect_success $PREREQ 'test using arguments with --sendmail-cmd' '
2112 + clean_fake_sendmail &&
2113 + git send-email \
2114 + --from="Example <nobody@example.com>" \
2115 + --to=nobody@example.com \
2116 + --sendmail-cmd='\''"$(pwd)/fake.sendmail" -f nobody@example.com'\'' \
2117 + HEAD^ &&
2118 + test_path_is_file commandline1
2119 +'
2120 +
2121 +test_expect_success $PREREQ 'test shell expression with --sendmail-cmd' '
2122 + clean_fake_sendmail &&
2123 + git send-email \
2124 + --from="Example <nobody@example.com>" \
2125 + --to=nobody@example.com \
2126 + --sendmail-cmd='\''f() { "$(pwd)/fake.sendmail" "$@"; };f'\'' \
2127 + HEAD^ &&
2128 + test_path_is_file commandline1
2129 +'
2130 +
2131 test_expect_success $PREREQ 'invoke hook' '
2132 mkdir -p .git/hooks &&
2133