diff options
Diffstat (limited to 'git-send-email.perl')
-rwxr-xr-x | git-send-email.perl | 120 |
1 files changed, 102 insertions, 18 deletions
diff --git a/git-send-email.perl b/git-send-email.perl index 798d59b84f..659e6c588b 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -41,6 +41,8 @@ git send-email --translate-aliases --subject <str> * Email "Subject:" --reply-to <str> * Email "Reply-To:" --in-reply-to <str> * Email "In-Reply-To:" + --[no-]outlook-id-fix * The SMTP host is an Outlook server that munges the + Message-ID. Retrieve it from the server. --[no-]xmailer * Add "X-Mailer:" header (default). --[no-]annotate * Review each patch that will be sent in an editor. --compose * Open an editor for introduction. @@ -68,7 +70,7 @@ git send-email --translate-aliases --smtp-auth <str> * Space-separated list of allowed AUTH mechanisms, or "none" to disable authentication. This setting forces to use one of the listed mechanisms. - --no-smtp-auth Disable SMTP authentication. Shorthand for + --no-smtp-auth * Disable SMTP authentication. Shorthand for `--smtp-auth=none` --smtp-debug <0|1> * Disable, enable Net::SMTP debug. @@ -290,6 +292,7 @@ my $validate = 1; my $mailmap = 0; my $target_xfer_encoding = 'auto'; my $forbid_sendmail_variables = 1; +my $outlook_id_fix = 'auto'; my %config_bool_settings = ( "thread" => \$thread, @@ -305,6 +308,7 @@ my %config_bool_settings = ( "xmailer" => \$use_xmailer, "forbidsendmailvariables" => \$forbid_sendmail_variables, "mailmap" => \$mailmap, + "outlookidfix" => \$outlook_id_fix, ); my %config_settings = ( @@ -551,6 +555,7 @@ my %options = ( "relogin-delay=i" => \$relogin_delay, "git-completion-helper" => \$git_completion_helper, "v=s" => \$reroll_count, + "outlook-id-fix!" => \$outlook_id_fix, ); $rc = GetOptions(%options); @@ -1354,7 +1359,9 @@ sub process_address_list { sub valid_fqdn { my $domain = shift; - return defined $domain && !($^O eq 'darwin' && $domain =~ /\.local$/) && $domain =~ /\./; + my $subdomain = '(?!-)[A-Za-z0-9-]{1,63}(?<!-)'; + return defined $domain && !($^O eq 'darwin' && $domain =~ /\.local$/) + && $domain =~ /^$subdomain(?:\.$subdomain)*$/; } sub maildomain_net { @@ -1386,8 +1393,22 @@ sub maildomain_mta { return $maildomain; } +sub maildomain_hostname_command { + my $maildomain; + + if ($^O eq 'linux' || $^O eq 'darwin') { + my $domain = `(hostname -f) 2>/dev/null`; + if (!$?) { + chomp($domain); + $maildomain = $domain if valid_fqdn($domain); + } + } + return $maildomain; +} + sub maildomain { - return maildomain_net() || maildomain_mta() || 'localhost.localdomain'; + return maildomain_net() || maildomain_mta() || + maildomain_hostname_command || 'localhost.localdomain'; } sub smtp_host_string { @@ -1419,7 +1440,7 @@ sub smtp_auth_maybe { die "invalid smtp auth: '${smtp_auth}'"; } - # TODO: Authentication may fail not because credentials were + # Authentication may fail not because credentials were # invalid but due to other reasons, in which we should not # reject credentials. $auth = Git::credential({ @@ -1431,24 +1452,61 @@ sub smtp_auth_maybe { 'password' => $smtp_authpass }, sub { my $cred = shift; + my $result; + my $error; + + # catch all SMTP auth error in a unified eval block + eval { + if ($smtp_auth) { + my $sasl = Authen::SASL->new( + mechanism => $smtp_auth, + callback => { + user => $cred->{'username'}, + pass => $cred->{'password'}, + authname => $cred->{'username'}, + } + ); + $result = $smtp->auth($sasl); + } else { + $result = $smtp->auth($cred->{'username'}, $cred->{'password'}); + } + 1; # ensure true value is returned if no exception is thrown + } or do { + $error = $@ || 'Unknown error'; + }; + + return ($error + ? handle_smtp_error($error) + : ($result ? 1 : 0)); + }); - if ($smtp_auth) { - my $sasl = Authen::SASL->new( - mechanism => $smtp_auth, - callback => { - user => $cred->{'username'}, - pass => $cred->{'password'}, - authname => $cred->{'username'}, - } - ); + return $auth; +} - return !!$smtp->auth($sasl); +sub handle_smtp_error { + my ($error) = @_; + + # Parse SMTP status code from error message in: + # https://www.rfc-editor.org/rfc/rfc5321.html + if ($error =~ /\b(\d{3})\b/) { + my $status_code = $1; + if ($status_code =~ /^4/) { + # 4yz: Transient Negative Completion reply + warn "SMTP transient error (status code $status_code): $error"; + return 1; + } elsif ($status_code =~ /^5/) { + # 5yz: Permanent Negative Completion reply + warn "SMTP permanent error (status code $status_code): $error"; + return 0; } + # If no recognized status code is found, treat as transient error + warn "SMTP unknown error: $error. Treating as transient failure."; + return 1; + } - return !!$smtp->auth($cred->{'username'}, $cred->{'password'}); - }); - - return $auth; + # If no status code is found, treat as transient error + warn "SMTP generic error: $error"; + return 1; } sub ssl_verify_params { @@ -1537,6 +1595,16 @@ Message-ID: $message_id return ($recipients_ref, $to, $date, $gitversion, $cc, $ccline, $header); } +sub is_outlook { + my ($host) = @_; + if ($outlook_id_fix eq 'auto') { + $outlook_id_fix = + ($host eq 'smtp.office365.com' || + $host eq 'smtp-mail.outlook.com') ? 1 : 0; + } + return $outlook_id_fix; +} + # Prepares the email, then asks the user what to do. # # If the user chooses to send the email, it's sent and 1 is returned. @@ -1700,6 +1768,22 @@ EOF $smtp->datasend("$line") or die $smtp->message; } $smtp->dataend() or die $smtp->message; + + # Outlook discards the Message-ID header we set while sending the email + # and generates a new random Message-ID. So in order to avoid breaking + # threads, we simply retrieve the Message-ID from the server response + # and assign it to the $message_id variable, which will then be + # assigned to $in_reply_to by the caller when the next message is sent + # as a response to this message. + if (is_outlook($smtp_server)) { + if ($smtp->message =~ /<([^>]+)>/) { + $message_id = "<$1>"; + printf __("Outlook reassigned Message-ID to: %s\n"), $message_id; + } else { + warn __("Warning: Could not retrieve Message-ID from server response.\n"); + } + } + $smtp->code =~ /250|200/ or die sprintf(__("Failed to send %s\n"), $subject).$smtp->message; } if ($quiet) { |