[PATCH v2] send-email: implement SMTP bearer authentication
Manually send SMTP AUTH command for auth type OAUTHBEARER and XOAUTH2. This is necessary since they are currently not supported by the Perls Authen::SASL module.
The bearer token needs to be passed in as the password. This can be done with git-credential-oauth[0] after minor modifications[1]. Which will allow using git send-email with Gmail and oauth2 authentication:
[credential]
helper = cache --timeout 7200 # two hours
helper = oauth
[sendemail]
smtpEncryption = tls
smtpServer = smtp.gmail.com
smtpUser = example@gmail.com
smtpServerPort = 587
smtpauth = OAUTHBEARERAs well as Office 365 accounts:
[credential]
helper = cache --timeout 7200 # two hours
helper = oauth
[sendemail]
smtpEncryption = tls
smtpServer = smtp.office365.com
smtpUser = example@example.com
smtpServerPort = 587
smtpauth = XOAUTH2[0] https://github.com/hickford/git-credential-oauth [1] https://github.com/hickford/git-credential-oauth/issues/48
Tested-by: M Hickford <mirth.hickford@gmail.com>
Signed-off-by: Julian Swagemakers <julian@swagemakers.org>
---
Documentation/git-send-email.txt | 5 ++-
git-send-email.perl | 65 +++++++++++++++++++++++++++++++-
2 files changed, 67 insertions(+), 3 deletions(-)
Show changes to 2 files +67 −3
Documentation/git-send-email.txt, git-send-email.perl
diff --git a/Documentation/git-send-email.txt b/Documentation/git-send-email.txt
index bc3ef45acb..b1972d99bf 100644
--- a/Documentation/git-send-email.txt
+++ b/Documentation/git-send-email.txt
@@ -213,7 +213,10 @@ 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. The
special value 'none' maybe specified to completely disable authentication
-independently of `--smtp-user`
+independently of `--smtp-user`. Specifying `OAUTHBEARER` or `XOAUTH2` will
+bypass SASL negotiation and force bearer authentication. In this case the
+bearer token must be provided with `--smtp-pass` or using a credential helper
+and `--smtp-encryption=tls` must be set.
--smtp-pass[=<password>]::
Password for SMTP-AUTH. The argument is optional: If no
diff --git a/git-send-email.perl b/git-send-email.perl
index 798d59b84f..a78159971b 100755
--- a/git-send-email.perl
+++ b/git-send-email.perl
@@ -1398,6 +1398,63 @@ sub smtp_host_string {
}
}
+sub generate_oauthbearer_string {
+ # This will generate the oauthbearer string used for authentication.
+ #
+ # "n,a=" {User} ",^Ahost=" {Host} "^Aport=" {Port} "^Aauth=Bearer " {Access Token} "^A^A
+ #
+ # The first part `n,a=" {User} ",` is the gs2 header described in RFC5801.
+ # * gs2-cb-flag `n` -> client does not support CB
+ # * gs2-authzid `a=" {User} "`
+ #
+ # The second part are key value pairs containing host, port and auth as
+ # described in RFC7628.
+ #
+ # https://datatracker.ietf.org/doc/html/rfc5801
+ # https://datatracker.ietf.org/doc/html/rfc7628
+ my $username = shift;
+ my $token = shift;
+ return "n,a=$username,\001port=$smtp_server_port\001auth=Bearer $token\001\001";
+}
+
+sub generate_xoauth2_string {
+ # "user=" {User} "^Aauth=Bearer " {Access Token} "^A^A"
+ # https://developers.google.com/gmail/imap/xoauth2-protocol#initial_client_response
+ my $username = shift;
+ my $token = shift;
+ return "user=$username\001auth=Bearer $token\001\001";
+}
+
+sub smtp_bearer_auth {
+ my $username = shift;
+ my $token = shift;
+ my $auth_string;
+ if ($smtp_encryption ne "tls") {
+ # As described in RFC7628 TLS is required and will be enforced
+ # at this point.
+ #
+ # https://datatracker.ietf.org/doc/html/rfc7628#section-3
+ die __("For $smtp_auth TLS is required.")
+ }
+ if ($smtp_auth eq "OAUTHBEARER") {
+ $auth_string = generate_oauthbearer_string($username, $token);
+ } elsif ($smtp_auth eq "XOAUTH2") {
+ $auth_string = generate_xoauth2_string($username, $token);
+ }
+ my $encoded_auth_string = MIME::Base64::encode($auth_string, "");
+ $smtp->command("AUTH $smtp_auth $encoded_auth_string\r\n");
+ use Net::Cmd qw(CMD_OK);
+ if ($smtp->response() == CMD_OK){
+ return 1;
+ } else {
+ # Send dummy request on authentication failure according to rfc7628.
+ # https://datatracker.ietf.org/doc/html/rfc7628#section-3.2.3
+ $smtp->command(MIME::Base64::encode("\001"));
+ $smtp->response();
+ return 0;
+ }
+}
+
# Returns 1 if authentication succeeded or was not necessary
# (smtp_user was not specified), and 0 otherwise.
@@ -1431,8 +1488,12 @@ sub smtp_auth_maybe {
'password' => $smtp_authpass
}, sub {
my $cred = shift;
-
- if ($smtp_auth) {
+ if (defined $smtp_auth && ($smtp_auth eq "OAUTHBEARER" || $smtp_auth eq "XOAUTH2")) {
+ # Since Authen:SASL does not support XOAUTH2 nor OAUTHBEARER we will
+ # manually authenticate for these types. The password field should
+ # contain the auth token at this point.
+ return smtp_bearer_auth($cred->{'username'}, $cred->{'password'});
+ } elsif ($smtp_auth) {
my $sasl = Authen::SASL->new(
mechanism => $smtp_auth,
callback => {
Range-diff against v1:
1: ab3ba94099 ! 1: 7532a1ee0a send-email: implement SMTP bearer authentication
@@ Commit message
with git-credential-oauth[0] after minor modifications[1]. Which will
allow using git send-email with Gmail and oauth2 authentication:
- ```
- [credential]
- helper = cache --timeout 7200 # two hours
+ [credential]
+ helper = cache --timeout 7200 # two hours
helper = oauth
- [sendemail]
- smtpEncryption = tls
- smtpServer = smtp.gmail.com
- smtpUser = example@gmail.com
- smtpServerPort = 587
- smtpauth = OAUTHBEARER
- ```
+ [sendemail]
+ smtpEncryption = tls
+ smtpServer = smtp.gmail.com
+ smtpUser = example@gmail.com
+ smtpServerPort = 587
+ smtpauth = OAUTHBEARER
As well as Office 365 accounts:
- ```
- [credential]
+ [credential]
helper = cache --timeout 7200 # two hours
helper = oauth
- [sendemail]
- smtpEncryption = tls
- smtpServer = smtp.office365.com
- smtpUser = example@example.com
- smtpServerPort = 587
- smtpauth = XOAUTH2
- ```
+ [sendemail]
+ smtpEncryption = tls
+ smtpServer = smtp.office365.com
+ smtpUser = example@example.com
+ smtpServerPort = 587
+ smtpauth = XOAUTH2
[0] https://github.com/hickford/git-credential-oauth
[1] https://github.com/hickford/git-credential-oauth/issues/48
+ Tested-by: M Hickford <mirth.hickford@gmail.com>
Signed-off-by: Julian Swagemakers <julian@swagemakers.org>
+ ## Documentation/git-send-email.txt ##
+@@ Documentation/git-send-email.txt: 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. The
+ special value 'none' maybe specified to completely disable authentication
+-independently of `--smtp-user`
++independently of `--smtp-user`. Specifying `OAUTHBEARER` or `XOAUTH2` will
++bypass SASL negotiation and force bearer authentication. In this case the
++bearer token must be provided with `--smtp-pass` or using a credential helper
++and `--smtp-encryption=tls` must be set.
+
+ --smtp-pass[=<password>]::
+ Password for SMTP-AUTH. The argument is optional: If no
+
## git-send-email.perl ##
@@ git-send-email.perl: sub smtp_host_string {
}
@@ git-send-email.perl: sub smtp_host_string {
+ my $token = shift;
+ my $auth_string;
+ if ($smtp_encryption ne "tls") {
-+ # As described in RFC7628 TLS is required and will be will
-+ # be enforced at this point.
++ # As described in RFC7628 TLS is required and will be enforced
++ # at this point.
+ #
+ # https://datatracker.ietf.org/doc/html/rfc7628#section-3
+ die __("For $smtp_auth TLS is required.")
@@ git-send-email.perl: sub smtp_auth_maybe {
my $cred = shift;
-
- if ($smtp_auth) {
-+ if ($smtp_auth eq "OAUTHBEARER" or $smtp_auth eq "XOAUTH2") {
++ if (defined $smtp_auth && ($smtp_auth eq "OAUTHBEARER" || $smtp_auth eq "XOAUTH2")) {
+ # Since Authen:SASL does not support XOAUTH2 nor OAUTHBEARER we will
-+ # manuall authenticate for tese types. The password field should
++ # manually authenticate for these types. The password field should
+ # contain the auth token at this point.
+ return smtp_bearer_auth($cred->{'username'}, $cred->{'password'});
+ } elsif ($smtp_auth) {
--
2.48.1