summaryrefslogtreecommitdiff
path: root/git-send-email.perl
diff options
context:
space:
mode:
Diffstat (limited to 'git-send-email.perl')
-rwxr-xr-xgit-send-email.perl154
1 files changed, 140 insertions, 14 deletions
diff --git a/git-send-email.perl b/git-send-email.perl
index 62fc7d65e8..da81be40cb 100755
--- a/git-send-email.perl
+++ b/git-send-email.perl
@@ -19,10 +19,10 @@
use 5.008;
use strict;
use warnings;
+use POSIX qw/strftime/;
use Term::ReadLine;
use Getopt::Long;
use Text::ParseWords;
-use Data::Dumper;
use Term::ANSIColor;
use File::Temp qw/ tempdir tempfile /;
use File::Spec::Functions qw(catfile);
@@ -46,6 +46,7 @@ package main;
sub usage {
print <<EOT;
git send-email [options] <file | directory | rev-list options >
+git send-email --dump-aliases
Composing:
--from <str> * Email From:
@@ -75,6 +76,8 @@ git send-email [options] <file | directory | rev-list options >
Pass an empty string to disable certificate
verification.
--smtp-domain <str> * The domain name sent to HELO/EHLO handshake
+ --smtp-auth <str> * Space-separated list of allowed AUTH mechanisms.
+ This setting forces to use one of the listed mechanisms.
--smtp-debug <0|1> * Disable, enable Net::SMTP debug.
Automating:
@@ -99,6 +102,9 @@ git send-email [options] <file | directory | rev-list options >
`git format-patch` ones.
--force * Send even if safety checks would prevent it.
+ Information:
+ --dump-aliases * Dump configured aliases and exit.
+
EOT
exit(1);
}
@@ -178,6 +184,7 @@ my ($quiet, $dry_run) = (0, 0);
my $format_patch;
my $compose_filename;
my $force = 0;
+my $dump_aliases = 0;
# Handle interactive edition of files.
my $multiedit;
@@ -208,7 +215,7 @@ my ($cover_cc, $cover_to);
my ($to_cmd, $cc_cmd);
my ($smtp_server, $smtp_server_port, @smtp_server_options);
my ($smtp_authuser, $smtp_encryption, $smtp_ssl_cert_path);
-my ($identity, $aliasfiletype, @alias_files, $smtp_domain);
+my ($identity, $aliasfiletype, @alias_files, $smtp_domain, $smtp_auth);
my ($validate, $confirm);
my (@suppress_cc);
my ($auto_8bit_encoding);
@@ -237,8 +244,8 @@ my %config_settings = (
"smtpserveroption" => \@smtp_server_options,
"smtpuser" => \$smtp_authuser,
"smtppass" => \$smtp_authpass,
- "smtpsslcertpath" => \$smtp_ssl_cert_path,
"smtpdomain" => \$smtp_domain,
+ "smtpauth" => \$smtp_auth,
"to" => \@initial_to,
"tocmd" => \$to_cmd,
"cc" => \@initial_cc,
@@ -256,6 +263,7 @@ my %config_settings = (
my %config_path_settings = (
"aliasesfile" => \@alias_files,
+ "smtpsslcertpath" => \$smtp_ssl_cert_path,
);
# Handle Uncouth Termination
@@ -288,6 +296,11 @@ $SIG{INT} = \&signal_handler;
my $help;
my $rc = GetOptions("h" => \$help,
+ "dump-aliases" => \$dump_aliases);
+usage() unless $rc;
+die "--dump-aliases incompatible with other options\n"
+ if !$help and $dump_aliases and @ARGV;
+$rc = GetOptions(
"sender|from=s" => \$sender,
"in-reply-to=s" => \$initial_reply_to,
"subject=s" => \$initial_subject,
@@ -310,6 +323,7 @@ my $rc = GetOptions("h" => \$help,
"smtp-ssl-cert-path=s" => \$smtp_ssl_cert_path,
"smtp-debug:i" => \$debug_net_smtp,
"smtp-domain:s" => \$smtp_domain,
+ "smtp-auth=s" => \$smtp_auth,
"identity=s" => \$identity,
"annotate!" => \$annotate,
"no-annotate" => sub {$annotate = 0},
@@ -473,17 +487,53 @@ sub split_addrs {
}
my %aliases;
+
+sub parse_sendmail_alias {
+ local $_ = shift;
+ if (/"/) {
+ print STDERR "warning: sendmail alias with quotes is not supported: $_\n";
+ } elsif (/:include:/) {
+ print STDERR "warning: `:include:` not supported: $_\n";
+ } elsif (/[\/|]/) {
+ print STDERR "warning: `/file` or `|pipe` redirection not supported: $_\n";
+ } elsif (/^(\S+?)\s*:\s*(.+)$/) {
+ my ($alias, $addr) = ($1, $2);
+ $aliases{$alias} = [ split_addrs($addr) ];
+ } else {
+ print STDERR "warning: sendmail line is not recognized: $_\n";
+ }
+}
+
+sub parse_sendmail_aliases {
+ my $fh = shift;
+ my $s = '';
+ while (<$fh>) {
+ chomp;
+ next if /^\s*$/ || /^\s*#/;
+ $s .= $_, next if $s =~ s/\\$// || s/^\s+//;
+ parse_sendmail_alias($s) if $s;
+ $s = $_;
+ }
+ $s =~ s/\\$//; # silently tolerate stray '\' on last line
+ parse_sendmail_alias($s) if $s;
+}
+
my %parse_alias = (
# multiline formats can be supported in the future
mutt => sub { my $fh = shift; while (<$fh>) {
if (/^\s*alias\s+(?:-group\s+\S+\s+)*(\S+)\s+(.*)$/) {
my ($alias, $addr) = ($1, $2);
$addr =~ s/#.*$//; # mutt allows # comments
- # commas delimit multiple addresses
- $aliases{$alias} = [ split_addrs($addr) ];
+ # commas delimit multiple addresses
+ my @addr = split_addrs($addr);
+
+ # quotes may be escaped in the file,
+ # unescape them so we do not double-escape them later.
+ s/\\"/"/g foreach @addr;
+ $aliases{$alias} = \@addr
}}},
mailrc => sub { my $fh = shift; while (<$fh>) {
- if (/^alias\s+(\S+)\s+(.*)$/) {
+ if (/^alias\s+(\S+)\s+(.*?)\s*$/) {
# spaces delimit multiple addresses
$aliases{$1} = [ quotewords('\s+', 0, $2) ];
}}},
@@ -501,7 +551,7 @@ my %parse_alias = (
$aliases{$alias} = [ split_addrs($addr) ];
}
} },
-
+ sendmail => \&parse_sendmail_aliases,
gnus => sub { my $fh = shift; while (<$fh>) {
if (/\(define-mail-alias\s+"(\S+?)"\s+"(\S+?)"\)/) {
$aliases{$1} = [ $2 ];
@@ -516,6 +566,11 @@ if (@alias_files and $aliasfiletype and defined $parse_alias{$aliasfiletype}) {
}
}
+if ($dump_aliases) {
+ print "$_\n" for (sort keys %aliases);
+ exit(0);
+}
+
# is_format_patch_arg($f) returns 0 if $f names a patch, or 1 if
# $f is a revision list specification to be passed to format-patch.
sub is_format_patch_arg {
@@ -566,6 +621,8 @@ if (@rev_list_opts) {
push @files, $repo->command('format-patch', '-o', tempdir(CLEANUP => 1), @rev_list_opts);
}
+@files = handle_backup_files(@files);
+
if ($validate) {
foreach my $f (@files) {
unless (-p $f) {
@@ -772,9 +829,10 @@ if (defined $sender) {
# But it's a no-op to run sanitize_address on an already sanitized address.
$sender = sanitize_address($sender);
+my $to_whom = "To whom should the emails be sent (if anyone)?";
my $prompting = 0;
if (!@initial_to && !defined $to_cmd) {
- my $to = ask("Who should the emails be sent to (if any)? ",
+ my $to = ask("$to_whom ",
default => "",
valid_re => qr/\@.*\./, confirm_only => 1);
push @initial_to, parse_address_line($to) if defined $to; # sanitized/validated later
@@ -869,7 +927,7 @@ sub validate_address {
cleanup_compose_files();
exit(0);
}
- $address = ask("Who should the email be sent to (if any)? ",
+ $address = ask("$to_whom ",
default => "",
valid_re => qr/\@.*\./, confirm_only => 1);
}
@@ -894,7 +952,7 @@ my ($message_id_stamp, $message_id_serial);
sub make_message_id {
my $uniq;
if (!defined $message_id_stamp) {
- $message_id_stamp = sprintf("%s-%s", time, $$);
+ $message_id_stamp = strftime("%Y%m%d%H%M%S.$$", gmtime(time));
$message_id_serial = 0;
}
$message_id_serial++;
@@ -909,7 +967,7 @@ sub make_message_id {
require Sys::Hostname;
$du_part = 'user@' . Sys::Hostname::hostname();
}
- my $message_id_template = "<%s-git-send-email-%s>";
+ my $message_id_template = "<%s-%s>";
$message_id = sprintf($message_id_template, $uniq, $du_part);
#print "new message id = $message_id\n"; # Was useful for debugging
}
@@ -1099,6 +1157,12 @@ sub smtp_auth_maybe {
Authen::SASL->import(qw(Perl));
};
+ # Check mechanism naming as defined in:
+ # https://tools.ietf.org/html/rfc4422#page-8
+ if ($smtp_auth && $smtp_auth !~ /^(\b[A-Z0-9-_]{1,20}\s*)*$/) {
+ die "invalid smtp auth: '${smtp_auth}'";
+ }
+
# TODO: Authentication may fail not because credentials were
# invalid but due to other reasons, in which we should not
# reject credentials.
@@ -1111,6 +1175,20 @@ sub smtp_auth_maybe {
'password' => $smtp_authpass
}, sub {
my $cred = shift;
+
+ if ($smtp_auth) {
+ my $sasl = Authen::SASL->new(
+ mechanism => $smtp_auth,
+ callback => {
+ user => $cred->{'username'},
+ pass => $cred->{'password'},
+ authname => $cred->{'username'},
+ }
+ );
+
+ return !!$smtp->auth($sasl);
+ }
+
return !!$smtp->auth($cred->{'username'}, $cred->{'password'});
});
@@ -1141,8 +1219,7 @@ sub ssl_verify_params {
return (SSL_verify_mode => SSL_VERIFY_PEER(),
SSL_ca_file => $smtp_ssl_cert_path);
} else {
- print STDERR "Not using SSL_VERIFY_PEER because the CA path does not exist.\n";
- return (SSL_verify_mode => SSL_VERIFY_NONE());
+ die "CA path \"$smtp_ssl_cert_path\" does not exist";
}
}
@@ -1263,6 +1340,13 @@ Message-Id: $message_id
require Net::SMTP::SSL;
$smtp_domain ||= maildomain();
require IO::Socket::SSL;
+
+ # Suppress "variable accessed once" warning.
+ {
+ no warnings 'once';
+ $IO::Socket::SSL::DEBUG = 1;
+ }
+
# Net::SMTP::SSL->new() does not forward any SSL options
IO::Socket::SSL::set_client_defaults(
ssl_verify_params());
@@ -1310,7 +1394,11 @@ Message-Id: $message_id
$smtp->mail( $raw_from ) or die $smtp->message;
$smtp->to( @recipients ) or die $smtp->message;
$smtp->data or die $smtp->message;
- $smtp->datasend("$header\n$message") or die $smtp->message;
+ $smtp->datasend("$header\n") or die $smtp->message;
+ my @lines = split /^/, $message;
+ foreach my $line (@lines) {
+ $smtp->datasend("$line") or die $smtp->message;
+ }
$smtp->dataend() or die $smtp->message;
$smtp->code =~ /250|200/ or die "Failed to send $subject\n".$smtp->message;
}
@@ -1641,6 +1729,44 @@ sub validate_patch {
return;
}
+sub handle_backup {
+ my ($last, $lastlen, $file, $known_suffix) = @_;
+ my ($suffix, $skip);
+
+ $skip = 0;
+ if (defined $last &&
+ ($lastlen < length($file)) &&
+ (substr($file, 0, $lastlen) eq $last) &&
+ ($suffix = substr($file, $lastlen)) !~ /^[a-z0-9]/i) {
+ if (defined $known_suffix && $suffix eq $known_suffix) {
+ print "Skipping $file with backup suffix '$known_suffix'.\n";
+ $skip = 1;
+ } else {
+ my $answer = ask("Do you really want to send $file? (y|N): ",
+ valid_re => qr/^(?:y|n)/i,
+ default => 'n');
+ $skip = ($answer ne 'y');
+ if ($skip) {
+ $known_suffix = $suffix;
+ }
+ }
+ }
+ return ($skip, $known_suffix);
+}
+
+sub handle_backup_files {
+ my @file = @_;
+ my ($last, $lastlen, $known_suffix, $skip, @result);
+ for my $file (@file) {
+ ($skip, $known_suffix) = handle_backup($last, $lastlen,
+ $file, $known_suffix);
+ push @result, $file unless $skip;
+ $last = $file;
+ $lastlen = length($file);
+ }
+ return @result;
+}
+
sub file_has_nonascii {
my $fn = shift;
open(my $fh, '<', $fn)