Merge branch 'rr/send-email-ssl-verify'
authorJunio C Hamano <gitster@pobox.com>
Mon, 22 Jul 2013 18:24:17 +0000 (11:24 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 22 Jul 2013 18:24:17 +0000 (11:24 -0700)
Newer Net::SMTP::SSL module does not want the user programs to use
the default behaviour to let server certificate go without
verification, so by default enable the verification with a
mechanism to turn it off if needed.

* rr/send-email-ssl-verify:
send-email: be explicit with SSL certificate verification

1  2 
Documentation/config.txt
git-send-email.perl
diff --combined Documentation/config.txt
index 50067ee15c516be0e1648dd81adbed667c94d4cb,4de154ca6714e24e532cee3a7f352d59682b6895..e0b923f428094f1303084778bee7ab9e502709a4
@@@ -199,9 -199,6 +199,9 @@@ advice.*:
        amWorkDir::
                Advice that shows the location of the patch file when
                linkgit:git-am[1] fails to apply it.
 +      rmHints::
 +              In case of failure in the output of linkgit:git-rm[1],
 +              show directions on how to proceed from the current state.
  --
  
  core.fileMode::
@@@ -879,17 -876,16 +879,17 @@@ The values of these variables may be sp
  
  color.interactive::
        When set to `always`, always use colors for interactive prompts
 -      and displays (such as those used by "git-add --interactive").
 -      When false (or `never`), never.  When set to `true` or `auto`, use
 -      colors only when the output is to the terminal. Defaults to false.
 +      and displays (such as those used by "git-add --interactive" and
 +      "git-clean --interactive"). When false (or `never`), never.
 +      When set to `true` or `auto`, use colors only when the output is
 +      to the terminal. Defaults to false.
  
  color.interactive.<slot>::
 -      Use customized color for 'git add --interactive'
 -      output. `<slot>` may be `prompt`, `header`, `help` or `error`, for
 -      four distinct types of normal output from interactive
 -      commands.  The values of these variables may be specified as
 -      in color.branch.<slot>.
 +      Use customized color for 'git add --interactive' and 'git clean
 +      --interactive' output. `<slot>` may be `prompt`, `header`, `help`
 +      or `error`, for four distinct types of normal output from
 +      interactive commands.  The values of these variables may be
 +      specified as in color.branch.<slot>.
  
  color.pager::
        A boolean to enable/disable colored output when the pager is in
@@@ -923,21 -919,17 +923,21 @@@ color.ui:
        as `color.diff` and `color.grep` that control the use of color
        per command family. Its scope will expand as more commands learn
        configuration to set a default for the `--color` option.  Set it
 -      to `always` if you want all output not intended for machine
 -      consumption to use color, to `true` or `auto` if you want such
 -      output to use color when written to the terminal, or to `false` or
 -      `never` if you prefer Git commands not to use color unless enabled
 -      explicitly with some other configuration or the `--color` option.
 +      to `false` or `never` if you prefer Git commands not to use
 +      color unless enabled explicitly with some other configuration
 +      or the `--color` option. Set it to `always` if you want all
 +      output not intended for machine consumption to use color, to
 +      `true` or `auto` (this is the default since Git 1.8.4) if you
 +      want such output to use color when written to the terminal.
  
  column.ui::
        Specify whether supported commands should output in columns.
        This variable consists of a list of tokens separated by spaces
        or commas:
  +
 +These options control when the feature should be enabled
 +(defaults to 'never'):
 ++
  --
  `always`;;
        always show in columns
        never show in columns
  `auto`;;
        show in columns if the output is to the terminal
 +--
 ++
 +These options control layout (defaults to 'column').  Setting any
 +of these implies 'always' if none of 'always', 'never', or 'auto' are
 +specified.
 ++
 +--
  `column`;;
 -      fill columns before rows (default)
 +      fill columns before rows
  `row`;;
        fill rows before columns
  `plain`;;
        show in one column
 +--
 ++
 +Finally, these options can be combined with a layout option (defaults
 +to 'nodense'):
 ++
 +--
  `dense`;;
        make unequal size columns to utilize more space
  `nodense`;;
        make equal size columns
  --
 -+
 -This option defaults to 'never'.
  
  column.branch::
        Specify whether to output branch listing in `git branch` in columns.
        See `column.ui` for details.
  
 +column.clean::
 +      Specify the layout when list items in `git clean -i`, which always
 +      shows files and directories in columns. See `column.ui` for details.
 +
  column.status::
        Specify whether to output untracked files in `git status` in columns.
        See `column.ui` for details.
@@@ -1849,59 -1826,39 +1849,59 @@@ pull.twohead:
        The default merge strategy to use when pulling a single branch.
  
  push.default::
 -      Defines the action `git push` should take if no refspec is given
 -      on the command line, no refspec is configured in the remote, and
 -      no refspec is implied by any of the options given on the command
 -      line. Possible values are:
 +      Defines the action `git push` should take if no refspec is
 +      explicitly given.  Different values are well-suited for
 +      specific workflows; for instance, in a purely central workflow
 +      (i.e. the fetch source is equal to the push destination),
 +      `upstream` is probably what you want.  Possible values are:
  +
  --
 -* `nothing` - do not push anything.
 -* `matching` - push all branches having the same name in both ends.
 -  This is for those who prepare all the branches into a publishable
 -  shape and then push them out with a single command.  It is not
 -  appropriate for pushing into a repository shared by multiple users,
 -  since locally stalled branches will attempt a non-fast forward push
 -  if other users updated the branch.
 -  +
 -  This is currently the default, but Git 2.0 will change the default
 -  to `simple`.
 -* `upstream` - push the current branch to its upstream branch
 -  (`tracking` is a deprecated synonym for this).
 -  With this, `git push` will update the same remote ref as the one which
 -  is merged by `git pull`, making `push` and `pull` symmetrical.
 -  See "branch.<name>.merge" for how to configure the upstream branch.
 -* `simple` - like `upstream`, but refuses to push if the upstream
 -  branch's name is different from the local one. This is the safest
 -  option and is well-suited for beginners. It will become the default
 -  in Git 2.0.
 -* `current` - push the current branch to a branch of the same name.
 ---
 +
 +* `nothing` - do not push anything (error out) unless a refspec is
 +  explicitly given. This is primarily meant for people who want to
 +  avoid mistakes by always being explicit.
 +
 +* `current` - push the current branch to update a branch with the same
 +  name on the receiving end.  Works in both central and non-central
 +  workflows.
 +
 +* `upstream` - push the current branch back to the branch whose
 +  changes are usually integrated into the current branch (which is
 +  called `@{upstream}`).  This mode only makes sense if you are
 +  pushing to the same repository you would normally pull from
 +  (i.e. central workflow).
 +
 +* `simple` - in centralized workflow, work like `upstream` with an
 +  added safety to refuse to push if the upstream branch's name is
 +  different from the local one.
 ++
 +When pushing to a remote that is different from the remote you normally
 +pull from, work as `current`.  This is the safest option and is suited
 +for beginners.
 ++
 +This mode will become the default in Git 2.0.
 +
 +* `matching` - push all branches having the same name on both ends.
 +  This makes the repository you are pushing to remember the set of
 +  branches that will be pushed out (e.g. if you always push 'maint'
 +  and 'master' there and no other branches, the repository you push
 +  to will have these two branches, and your local 'maint' and
 +  'master' will be pushed there).
 ++
 +To use this mode effectively, you have to make sure _all_ the
 +branches you would push out are ready to be pushed out before
 +running 'git push', as the whole point of this mode is to allow you
 +to push all of the branches in one go.  If you usually finish work
 +on only one branch and push out the result, while other branches are
 +unfinished, this mode is not for you.  Also this mode is not
 +suitable for pushing into a shared central repository, as other
 +people may add new branches there, or update the tip of existing
 +branches outside your control.
  +
 -The `simple`, `current` and `upstream` modes are for those who want to
 -push out a single branch after finishing work, even when the other
 -branches are not yet ready to be pushed out. If you are working with
 -other people to push into the same shared repository, you would want
 -to use one of these.
 +This is currently the default, but Git 2.0 will change the default
 +to `simple`.
 +
 +--
  
  rebase.stat::
        Whether to show a diffstat of what changed upstream since the last
  rebase.autosquash::
        If set to true enable '--autosquash' option by default.
  
 +rebase.autostash::
 +      When set to true, automatically create a temporary stash
 +      before the operation begins, and apply it after the operation
 +      ends.  This means that you can run rebase on a dirty worktree.
 +      However, use with care: the final stash application after a
 +      successful rebase might result in non-trivial conflicts.
 +      Defaults to false.
 +
  receive.autogc::
        By default, git-receive-pack will run "git-gc --auto" after
        receiving data from git-push and updating refs.  You can stop
@@@ -2073,6 -2022,10 +2073,10 @@@ sendemail.smtpencryption:
  sendemail.smtpssl::
        Deprecated alias for 'sendemail.smtpencryption = ssl'.
  
+ sendemail.smtpsslcertpath::
+       Path to ca-certificates (either a directory or a single file).
+       Set it to an empty string to disable certificate verification.
  sendemail.<identity>.*::
        Identity-specific versions of the 'sendemail.*' parameters
        found below, taking precedence over those when the this
@@@ -2117,14 -2070,6 +2121,14 @@@ status.relativePaths:
        relative to the repository root (this was the default for Git
        prior to v1.5.4).
  
 +status.short::
 +      Set to true to enable --short by default in linkgit:git-status[1].
 +      The option --no-short takes precedence over this variable.
 +
 +status.branch::
 +      Set to true to enable --branch by default in linkgit:git-status[1].
 +      The option --no-branch takes precedence over this variable.
 +
  status.showUntrackedFiles::
        By default, linkgit:git-status[1] and linkgit:git-commit[1] show
        files which are not currently tracked by Git. Directories which
diff --combined git-send-email.perl
index 45c386337777460ed77fd71026fcfca678b18037,60eaed32eab2ccb59bd00ac97adbf9c3e39244d6..2162478392728f156ba6c541cdc69447aa9b0119
@@@ -69,6 -69,9 +69,9 @@@ git send-email [options] <file | direct
      --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 +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,9 -206,11 +206,9 @@@ 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
@@@ -220,6 -225,7 +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,
@@@ -239,6 -245,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 {
  
@@@ -287,6 -306,7 +291,7 @@@ 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,
@@@ -745,11 -765,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)? ",
@@@ -1079,6 -1094,34 +1084,34 @@@ sub smtp_auth_maybe 
        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.
@@@ -1103,9 -1146,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
@@@ -1122,7 -1166,7 +1155,7 @@@ X-Mailer: git-send-email $gitversio
        }
  
        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;
        }
                        $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(smtp_host_string(),
 +                      $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)
+                                       $smtp = Net::SMTP::SSL->start_SSL($smtp,
+                                                                         ssl_verify_params())
                                                or die "STARTTLS failed! ".$smtp->message;
                                        $smtp_encryption = '';
                                        # Send EHLO again to receive fresh
@@@ -1261,7 -1305,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;
                        }
                        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;
                        }
                        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'});
                        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;
                $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) {
  
        # 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) {
  sub recipients_cmd {
        my ($prefix, $what, $cmd, $file) = @_;
  
 -      my $sanitized_sender = sanitize_address($sender);
        my @addresses = ();
        open my $fh, "-|", "$cmd \Q$file\E"
            or die "($prefix) Could not execute '$cmd'";
                $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;