Merge branch 'jv/send-email-selective-smtp-auth'
authorJunio C Hamano <gitster@pobox.com>
Wed, 26 Aug 2015 22:45:31 +0000 (15:45 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 26 Aug 2015 22:45:31 +0000 (15:45 -0700)
"git send-email" learned a new option --smtp-auth to limit the SMTP
AUTH mechanisms to be used to a subset of what the system library
supports.

* jv/send-email-selective-smtp-auth:
send-email: provide whitelist of SMTP AUTH mechanisms

1  2 
Documentation/git-send-email.txt
git-send-email.perl
index f14705ee04e491e698e63ab10d15d9ec427b56f4,631f6c8076c72947e468ad54d9cf3c1d9efa3061..b9134d234f538c5893fda4440d9ed7283ba8ba9a
@@@ -49,17 -49,17 +49,17 @@@ Composin
        of 'sendemail.annotate'. See the CONFIGURATION section for
        'sendemail.multiEdit'.
  
 ---bcc=<address>::
 +--bcc=<address>,...::
        Specify a "Bcc:" value for each email. Default is the value of
        'sendemail.bcc'.
  +
 -The --bcc option must be repeated for each user you want on the bcc list.
 +This option may be specified multiple times.
  
 ---cc=<address>::
 +--cc=<address>,...::
        Specify a starting "Cc:" value for each email.
        Default is the value of 'sendemail.cc'.
  +
 -The --cc option must be repeated for each user you want on the cc list.
 +This option may be specified multiple times.
  
  --compose::
        Invoke a text editor (see GIT_EDITOR in linkgit:git-var[1])
@@@ -110,13 -110,13 +110,13 @@@ is not set, this will be prompted for
        Only necessary if --compose is also set.  If --compose
        is not set, this will be prompted for.
  
 ---to=<address>::
 +--to=<address>,...::
        Specify the primary recipient of the emails generated. Generally, this
        will be the upstream maintainer of the project involved. Default is the
        value of the 'sendemail.to' configuration value; if that is unspecified,
        and --to-cmd is not specified, this will be prompted for.
  +
 -The --to option must be repeated for each user you want on the to list.
 +This option may be specified multiple times.
  
  --8bit-encoding=<encoding>::
        When encountering a non-ASCII message or subject that does not
@@@ -171,6 -171,19 +171,19 @@@ Sendin
        to determine your FQDN automatically.  Default is the value of
        'sendemail.smtpDomain'.
  
+ --smtp-auth=<mechanisms>::
+       Whitespace-separated list of allowed SMTP-AUTH mechanisms. This setting
+       forces using only the listed mechanisms. Example:
+ +
+ ------
+ $ git send-email --smtp-auth="PLAIN LOGIN GSSAPI" ...
+ ------
+ +
+ If at least one of the specified mechanisms matches the ones advertised by the
+ SMTP server and if it is supported by the utilized SASL library, the mechanism
+ is used for authentication. If neither 'sendemail.smtpAuth' nor '--smtp-auth'
+ is specified, all mechanisms supported by the SASL library can be used.
  --smtp-pass[=<password>]::
        Password for SMTP-AUTH. The argument is optional: If no
        argument is specified, then the empty string is used as
diff --combined git-send-email.perl
index b660cc238223b101762017c7effda67de6f70113,6e380a25be9e49f97644f6a8d9f835f9e42a628a..c5a3f766f7fd34a48b352f3cfbb901840b4c4d5b
@@@ -75,6 -75,8 +75,8 @@@ git send-email [options] <file | direct
                                       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:
@@@ -208,7 -210,7 +210,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);
@@@ -239,6 -241,7 +241,7 @@@ my %config_settings = 
      "smtppass" => \$smtp_authpass,
      "smtpsslcertpath" => \$smtp_ssl_cert_path,
      "smtpdomain" => \$smtp_domain,
+     "smtpauth" => \$smtp_auth,
      "to" => \@initial_to,
      "tocmd" => \$to_cmd,
      "cc" => \@initial_cc,
@@@ -310,6 -313,7 +313,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},
@@@ -460,11 -464,25 +464,11 @@@ my ($repoauthor, $repocommitter)
  ($repoauthor) = Git::ident_person(@repo, 'author');
  ($repocommitter) = Git::ident_person(@repo, 'committer');
  
 -# Verify the user input
 -
 -foreach my $entry (@initial_to) {
 -      die "Comma in --to entry: $entry'\n" unless $entry !~ m/,/;
 -}
 -
 -foreach my $entry (@initial_cc) {
 -      die "Comma in --cc entry: $entry'\n" unless $entry !~ m/,/;
 -}
 -
 -foreach my $entry (@bcclist) {
 -      die "Comma in --bcclist entry: $entry'\n" unless $entry !~ m/,/;
 -}
 -
  sub parse_address_line {
        if ($have_mail_address) {
                return map { $_->format } Mail::Address->parse($_[0]);
        } else {
 -              return split_addrs($_[0]);
 +              return Git::parse_mailboxes($_[0]);
        }
  }
  
@@@ -547,6 -565,8 +551,6 @@@ if (@alias_files and $aliasfiletype an
        }
  }
  
 -($sender) = expand_aliases($sender) if defined $sender;
 -
  # 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 {
@@@ -791,10 -811,7 +795,10 @@@ if (!$force) 
        }
  }
  
 -if (!defined $sender) {
 +if (defined $sender) {
 +      $sender =~ s/^\s+|\s+$//g;
 +      ($sender) = expand_aliases($sender);
 +} else {
        $sender = $repoauthor || $repocommitter || '';
  }
  
@@@ -826,9 -843,12 +830,9 @@@ sub expand_one_alias 
        return $aliases{$alias} ? expand_aliases(@{$aliases{$alias}}) : $alias;
  }
  
 -@initial_to = expand_aliases(@initial_to);
 -@initial_to = validate_address_list(sanitize_address_list(@initial_to));
 -@initial_cc = expand_aliases(@initial_cc);
 -@initial_cc = validate_address_list(sanitize_address_list(@initial_cc));
 -@bcclist = expand_aliases(@bcclist);
 -@bcclist = validate_address_list(sanitize_address_list(@bcclist));
 +@initial_to = process_address_list(@initial_to);
 +@initial_cc = process_address_list(@initial_cc);
 +@bcclist = process_address_list(@bcclist);
  
  if ($thread && !defined $initial_reply_to && $prompting) {
        $initial_reply_to = ask(
@@@ -1021,17 -1041,15 +1025,17 @@@ sub sanitize_address 
                return $recipient;
        }
  
 +      # remove non-escaped quotes
 +      $recipient_name =~ s/(^|[^\\])"/$1/g;
 +
        # rfc2047 is needed if a non-ascii char is included
        if ($recipient_name =~ /[^[:ascii:]]/) {
 -              $recipient_name =~ s/^"(.*)"$/$1/;
                $recipient_name = quote_rfc2047($recipient_name);
        }
  
        # double quotes are needed if specials or CTLs are included
        elsif ($recipient_name =~ /[][()<>@,;:\\".\000-\037\177]/) {
 -              $recipient_name =~ s/(["\\\r])/\\$1/g;
 +              $recipient_name =~ s/([\\\r])/\\$1/g;
                $recipient_name = qq["$recipient_name"];
        }
  
@@@ -1043,14 -1061,6 +1047,14 @@@ sub sanitize_address_list 
        return (map { sanitize_address($_) } @_);
  }
  
 +sub process_address_list {
 +      my @addr_list = map { parse_address_line($_) } @_;
 +      @addr_list = expand_aliases(@addr_list);
 +      @addr_list = sanitize_address_list(@addr_list);
 +      @addr_list = validate_address_list(@addr_list);
 +      return @addr_list;
 +}
 +
  # Returns the local Fully Qualified Domain Name (FQDN) if available.
  #
  # Tightly configured MTAa require that a caller sends a real DNS
@@@ -1130,6 -1140,12 +1134,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 !~ /^(\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.
                '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'});
        });
  
@@@ -1560,8 -1590,8 +1584,8 @@@ foreach my $t (@files) 
                ($confirm =~ /^(?:auto|compose)$/ && $compose && $message_num == 1));
        $needs_confirm = "inform" if ($needs_confirm && $confirm_unconfigured && @cc);
  
 -      @to = validate_address_list(sanitize_address_list(@to));
 -      @cc = validate_address_list(sanitize_address_list(@cc));
 +      @to = process_address_list(@to);
 +      @cc = process_address_list(@cc);
  
        @to = (@initial_to, @to);
        @cc = (@initial_cc, @cc);