diff options
Diffstat (limited to 'git-send-email.perl')
-rwxr-xr-x | git-send-email.perl | 188 |
1 files changed, 118 insertions, 70 deletions
diff --git a/git-send-email.perl b/git-send-email.perl index be809e5b59..3782c3b0cb 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -54,7 +54,7 @@ git send-email [options] <file | directory | rev-list options > --[no-]bcc <str> * Email Bcc: --subject <str> * Email "Subject:" --in-reply-to <str> * Email "In-Reply-To:" - --annotate * Review each patch that will be sent in an editor. + --[no-]annotate * Review each patch that will be sent in an editor. --compose * Open an editor for introduction. --compose-encoding <str> * Encoding to assume for introduction. --8bit-encoding <str> * Encoding to assume 8bit mails if undeclared @@ -69,6 +69,9 @@ git send-email [options] <file | directory | rev-list options > --smtp-pass <str> * Password for SMTP-AUTH; not necessary. --smtp-encryption <str> * tls or ssl; anything else disables. --smtp-ssl * Deprecated. Use '--smtp-encryption ssl'. + --smtp-ssl-cert-path <str> * Path to ca-certificates (either directory or file). + Pass an empty string to disable certificate + verification. --smtp-domain <str> * The domain name sent to HELO/EHLO handshake --smtp-debug <0|1> * Disable, enable Net::SMTP debug. @@ -194,7 +197,7 @@ sub do_edit { my ($thread, $chain_reply_to, $suppress_from, $signed_off_by_cc); my ($to_cmd, $cc_cmd); my ($smtp_server, $smtp_server_port, @smtp_server_options); -my ($smtp_authuser, $smtp_encryption); +my ($smtp_authuser, $smtp_encryption, $smtp_ssl_cert_path); my ($identity, $aliasfiletype, @alias_files, $smtp_domain); my ($validate, $confirm); my (@suppress_cc); @@ -203,16 +206,15 @@ my ($compose_encoding); my ($debug_net_smtp) = 0; # Net::SMTP, see send_message() -my $not_set_by_user = "true but not set by the user"; - my %config_bool_settings = ( "thread" => [\$thread, 1], - "chainreplyto" => [\$chain_reply_to, $not_set_by_user], + "chainreplyto" => [\$chain_reply_to, 0], "suppressfrom" => [\$suppress_from, undef], "signedoffbycc" => [\$signed_off_by_cc, undef], "signedoffcc" => [\$signed_off_by_cc, undef], # Deprecated "validate" => [\$validate, 1], - "multiedit" => [\$multiedit, undef] + "multiedit" => [\$multiedit, undef], + "annotate" => [\$annotate, undef] ); my %config_settings = ( @@ -221,6 +223,7 @@ my %config_settings = ( "smtpserveroption" => \@smtp_server_options, "smtpuser" => \$smtp_authuser, "smtppass" => \$smtp_authpass, + "smtpsslcertpath" => \$smtp_ssl_cert_path, "smtpdomain" => \$smtp_domain, "to" => \@initial_to, "tocmd" => \$to_cmd, @@ -240,19 +243,6 @@ my %config_path_settings = ( "aliasesfile" => \@alias_files, ); -# Help users prepare for 1.7.0 -sub chain_reply_to { - if (defined $chain_reply_to && - $chain_reply_to eq $not_set_by_user) { - print STDERR - "In git 1.7.0, the default has changed to --no-chain-reply-to\n" . - "Set sendemail.chainreplyto configuration variable to true if\n" . - "you want to keep --chain-reply-to as your default.\n"; - $chain_reply_to = 0; - } - return $chain_reply_to; -} - # Handle Uncouth Termination sub signal_handler { @@ -301,10 +291,11 @@ my $rc = GetOptions("h" => \$help, "smtp-pass:s" => \$smtp_authpass, "smtp-ssl" => sub { $smtp_encryption = 'ssl' }, "smtp-encryption=s" => \$smtp_encryption, + "smtp-ssl-cert-path" => \$smtp_ssl_cert_path, "smtp-debug:i" => \$debug_net_smtp, "smtp-domain:s" => \$smtp_domain, "identity=s" => \$identity, - "annotate" => \$annotate, + "annotate!" => \$annotate, "compose" => \$compose, "quiet" => \$quiet, "cc-cmd=s" => \$cc_cmd, @@ -512,8 +503,9 @@ if (@alias_files and $aliasfiletype and defined $parse_alias{$aliasfiletype}) { ($sender) = expand_aliases($sender) if defined $sender; -# returns 1 if the conflict must be solved using it as a format-patch argument -sub check_file_rev_conflict($) { +# 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 { return unless $repo; my $f = shift; try { @@ -529,6 +521,7 @@ to produce patches for. Please disambiguate by... * Giving --format-patch option if you mean a range. EOF } catch Git::Error::Command with { + # Not a valid revision. Treat it as a filename. return 0; } } @@ -540,14 +533,14 @@ while (defined(my $f = shift @ARGV)) { if ($f eq "--") { push @rev_list_opts, "--", @ARGV; @ARGV = (); - } elsif (-d $f and !check_file_rev_conflict($f)) { + } elsif (-d $f and !is_format_patch_arg($f)) { opendir my $dh, $f or die "Failed to opendir $f: $!"; push @files, grep { -f $_ } map { catfile($f, $_) } sort readdir $dh; closedir $dh; - } elsif ((-f $f or -p $f) and !check_file_rev_conflict($f)) { + } elsif ((-f $f or -p $f) and !is_format_patch_arg($f)) { push @files, $f; } else { push @rev_list_opts, $f; @@ -711,7 +704,7 @@ sub ask { } } } - return undef; + return; } my %broken_encoding; @@ -757,6 +750,11 @@ if (!defined $sender) { $sender = $repoauthor || $repocommitter || ''; } +# $sender could be an already sanitized address +# (e.g. sendemail.from could be manually sanitized by user). +# But it's a no-op to run sanitize_address on an already sanitized address. +$sender = sanitize_address($sender); + my $prompting = 0; if (!@initial_to && !defined $to_cmd) { my $to = ask("Who should the emails be sent to (if any)? ", @@ -833,7 +831,7 @@ sub extract_valid_address { # less robust/correct than the monster regexp in Email::Valid, # but still does a 99% job, and one less dependency return $1 if $address =~ /($local_part_regexp\@$domain_regexp)/; - return undef; + return; } sub extract_valid_address_or_die { @@ -1045,6 +1043,75 @@ sub maildomain { return maildomain_net() || maildomain_mta() || 'localhost.localdomain'; } +sub smtp_host_string { + if (defined $smtp_server_port) { + return "$smtp_server:$smtp_server_port"; + } else { + return $smtp_server; + } +} + +# Returns 1 if authentication succeeded or was not necessary +# (smtp_user was not specified), and 0 otherwise. + +sub smtp_auth_maybe { + if (!defined $smtp_authuser || $auth) { + return 1; + } + + # Workaround AUTH PLAIN/LOGIN interaction defect + # with Authen::SASL::Cyrus + eval { + require Authen::SASL; + Authen::SASL->import(qw(Perl)); + }; + + # TODO: Authentication may fail not because credentials were + # invalid but due to other reasons, in which we should not + # reject credentials. + $auth = Git::credential({ + 'protocol' => 'smtp', + 'host' => smtp_host_string(), + 'username' => $smtp_authuser, + # if there's no password, "git credential fill" will + # give us one, otherwise it'll just pass this one. + 'password' => $smtp_authpass + }, sub { + my $cred = shift; + return !!$smtp->auth($cred->{'username'}, $cred->{'password'}); + }); + + return $auth; +} + +sub ssl_verify_params { + eval { + require IO::Socket::SSL; + IO::Socket::SSL->import(qw/SSL_VERIFY_PEER SSL_VERIFY_NONE/); + }; + if ($@) { + print STDERR "Not using SSL_VERIFY_PEER due to out-of-date IO::Socket::SSL.\n"; + return; + } + + if (!defined $smtp_ssl_cert_path) { + $smtp_ssl_cert_path = "/etc/ssl/certs"; + } + + 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 { + print STDERR "Not using SSL_VERIFY_PEER because the CA path does not exist.\n"; + return (SSL_verify_mode => SSL_VERIFY_NONE()); + } +} + # Returns 1 if the message was sent, and 0 otherwise. # In actuality, the whole program dies when there # is an error sending a message. @@ -1069,10 +1136,9 @@ sub send_message { if ($cc ne '') { $ccline = "\nCc: $cc"; } - my $sanitized_sender = sanitize_address($sender); make_message_id() unless defined($message_id); - my $header = "From: $sanitized_sender + my $header = "From: $sender To: $to${ccline} Subject: $subject Date: $date @@ -1089,7 +1155,7 @@ X-Mailer: git-send-email $gitversion } my @sendmail_parameters = ('-i', @recipients); - my $raw_from = $sanitized_sender; + my $raw_from = $sender; if (defined $envelope_sender && $envelope_sender ne "auto") { $raw_from = $envelope_sender; } @@ -1150,23 +1216,25 @@ X-Mailer: git-send-email $gitversion $smtp_domain ||= maildomain(); $smtp ||= Net::SMTP::SSL->new($smtp_server, Hello => $smtp_domain, - Port => $smtp_server_port); + Port => $smtp_server_port, + ssl_verify_params()); } else { require Net::SMTP; $smtp_domain ||= maildomain(); - $smtp ||= Net::SMTP->new((defined $smtp_server_port) - ? "$smtp_server:$smtp_server_port" - : $smtp_server, + $smtp_server_port ||= 25; + $smtp ||= Net::SMTP->new($smtp_server, Hello => $smtp_domain, - Debug => $debug_net_smtp); + Debug => $debug_net_smtp, + Port => $smtp_server_port); if ($smtp_encryption eq 'tls' && $smtp) { require Net::SMTP::SSL; $smtp->command('STARTTLS'); $smtp->response(); if ($smtp->code == 220) { - $smtp = Net::SMTP::SSL->start_SSL($smtp) - or die "STARTTLS failed! ".$smtp->message; + $smtp = Net::SMTP::SSL->start_SSL($smtp, + ssl_verify_params()) + or die "STARTTLS failed! ".IO::Socket::SSL::errstr(); $smtp_encryption = ''; # Send EHLO again to receive fresh # supported commands @@ -1185,31 +1253,7 @@ X-Mailer: git-send-email $gitversion defined $smtp_server_port ? " port=$smtp_server_port" : ""; } - if (defined $smtp_authuser) { - # Workaround AUTH PLAIN/LOGIN interaction defect - # with Authen::SASL::Cyrus - eval { - require Authen::SASL; - Authen::SASL->import(qw(Perl)); - }; - - if (!defined $smtp_authpass) { - - system "stty -echo"; - - do { - print "Password: "; - $_ = <STDIN>; - print "\n"; - } while (!defined $_); - - chomp($smtp_authpass = $_); - - system "stty echo"; - } - - $auth ||= $smtp->auth( $smtp_authuser, $smtp_authpass ) or die $smtp->message; - } + smtp_auth_maybe or die $smtp->message; $smtp->mail( $raw_from ) or die $smtp->message; $smtp->to( @recipients ) or die $smtp->message; @@ -1252,6 +1296,7 @@ foreach my $t (@files) { open my $fh, "<", $t or die "can't open file $t"; my $author = undef; + my $sauthor = undef; my $author_encoding; my $has_content_type; my $body_encoding; @@ -1290,8 +1335,9 @@ foreach my $t (@files) { } elsif (/^From:\s+(.*)$/i) { ($author, $author_encoding) = unquote_rfc2047($1); + $sauthor = sanitize_address($author); next if $suppress_cc{'author'}; - next if $suppress_cc{'self'} and $author eq $sender; + next if $suppress_cc{'self'} and $sauthor eq $sender; printf("(mbox) Adding cc: %s from line '%s'\n", $1, $_) unless $quiet; push @cc, $1; @@ -1305,7 +1351,9 @@ foreach my $t (@files) { } elsif (/^Cc:\s+(.*)$/i) { foreach my $addr (parse_address_line($1)) { - if (unquote_rfc2047($addr) eq $sender) { + my $qaddr = unquote_rfc2047($addr); + my $saddr = sanitize_address($qaddr); + if ($saddr eq $sender) { next if ($suppress_cc{'self'}); } else { next if ($suppress_cc{'cc'}); @@ -1352,7 +1400,8 @@ foreach my $t (@files) { chomp; my ($what, $c) = ($1, $2); chomp $c; - if ($c eq $sender) { + my $sc = sanitize_address($c); + if ($sc eq $sender) { next if ($suppress_cc{'self'}); } else { next if $suppress_cc{'sob'} and $what =~ /Signed-off-by/i; @@ -1382,7 +1431,7 @@ foreach my $t (@files) { $subject = quote_subject($subject, $auto_8bit_encoding); } - if (defined $author and $author ne $sender) { + if (defined $sauthor and $sauthor ne $sender) { $message = "From: $author\n\n$message"; if (defined $author_encoding) { if ($has_content_type) { @@ -1419,7 +1468,7 @@ foreach my $t (@files) { # set up for the next message if ($thread && $message_was_sent && - (chain_reply_to() || !defined $reply_to || length($reply_to) == 0 || + ($chain_reply_to || !defined $reply_to || length($reply_to) == 0 || $message_num == 1)) { $reply_to = $message_id; if (length $references > 0) { @@ -1436,15 +1485,14 @@ foreach my $t (@files) { sub recipients_cmd { my ($prefix, $what, $cmd, $file) = @_; - my $sanitized_sender = sanitize_address($sender); my @addresses = (); - open my $fh, "$cmd \Q$file\E |" + open my $fh, "-|", "$cmd \Q$file\E" or die "($prefix) Could not execute '$cmd'"; while (my $address = <$fh>) { $address =~ s/^\s*//g; $address =~ s/\s*$//g; $address = sanitize_address($address); - next if ($address eq $sanitized_sender and $suppress_from); + next if ($address eq $sender and $suppress_cc{'self'}); push @addresses, $address; printf("($prefix) Adding %s: %s from: '%s'\n", $what, $address, $cmd) unless $quiet; @@ -1484,7 +1532,7 @@ sub validate_patch { return "$.: patch contains a line longer than 998 characters"; } } - return undef; + return; } sub file_has_nonascii { |