git.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH] send-email: implement SMTP bearer authentication
@ 2024-02-25 10:34 Julian Swagemakers
  2024-02-28 17:53 ` M Hickford
                   ` (2 more replies)
  0 siblings, 3 replies; 11+ messages in thread
From: Julian Swagemakers @ 2024-02-25 10:34 UTC (permalink / raw)
  To: git; +Cc: Julian Swagemakers

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 = OAUTHBEARER
```

As 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

Signed-off-by: Julian Swagemakers <julian@swagemakers.org>
---
 git-send-email.perl | 65 +++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 63 insertions(+), 2 deletions(-)

diff --git a/git-send-email.perl b/git-send-email.perl
index 821b2b3a13..72d378f6fd 100755
--- a/git-send-email.perl
+++ b/git-send-email.perl
@@ -1359,6 +1359,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 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.
 
@@ -1392,8 +1449,12 @@ sub smtp_auth_maybe {
 		'password' => $smtp_authpass
 	}, sub {
 		my $cred = shift;
-
-		if ($smtp_auth) {
+		if ($smtp_auth eq "OAUTHBEARER" or $smtp_auth eq "XOAUTH2") {
+			# Since Authen:SASL does not support XOAUTH2 nor OAUTHBEARER we will
+			# manuall authenticate for tese 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 => {
-- 
2.43.2


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* Re: [PATCH] send-email: implement SMTP bearer authentication
  2024-02-25 10:34 [PATCH] send-email: implement SMTP bearer authentication Julian Swagemakers
@ 2024-02-28 17:53 ` M Hickford
  2024-10-11 17:48 ` Shengyu Qu
  2025-01-25 19:01 ` [PATCH v2] " Julian Swagemakers
  2 siblings, 0 replies; 11+ messages in thread
From: M Hickford @ 2024-02-28 17:53 UTC (permalink / raw)
  To: julian; +Cc: git

Neat idea. I recall it was awkward to configure git-send-email to send with
Gmail. I had to configure a security-compromising 'app password' [1][2].

OAuth is a great improvement.

[1] https://support.google.com/mail/answer/185833
[2] https://security.google.com/settings/security/apppasswords



^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH] send-email: implement SMTP bearer authentication
  2024-02-25 10:34 [PATCH] send-email: implement SMTP bearer authentication Julian Swagemakers
  2024-02-28 17:53 ` M Hickford
@ 2024-10-11 17:48 ` Shengyu Qu
  2024-10-11 18:05   ` Junio C Hamano
  2025-01-25 19:01 ` [PATCH v2] " Julian Swagemakers
  2 siblings, 1 reply; 11+ messages in thread
From: Shengyu Qu @ 2024-10-11 17:48 UTC (permalink / raw)
  To: Julian Swagemakers, git; +Cc: wiagn233

Hello,

Sorry to bother but what had happened to this patch? It is more useful now
since outlook also switched to oauth2 only mode.

Best regards,
Shengyu


在 2024/2/25 18:34, Julian Swagemakers 写道:
> 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 = OAUTHBEARER
> ```
>
> As 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
>
> Signed-off-by: Julian Swagemakers <julian@swagemakers.org>
> ---
>   git-send-email.perl | 65 +++++++++++++++++++++++++++++++++++++++++++--
>   1 file changed, 63 insertions(+), 2 deletions(-)
>
> diff --git a/git-send-email.perl b/git-send-email.perl
> index 821b2b3a13..72d378f6fd 100755
> --- a/git-send-email.perl
> +++ b/git-send-email.perl
> @@ -1359,6 +1359,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 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.
>   
> @@ -1392,8 +1449,12 @@ sub smtp_auth_maybe {
>   		'password' => $smtp_authpass
>   	}, sub {
>   		my $cred = shift;
> -
> -		if ($smtp_auth) {
> +		if ($smtp_auth eq "OAUTHBEARER" or $smtp_auth eq "XOAUTH2") {
> +			# Since Authen:SASL does not support XOAUTH2 nor OAUTHBEARER we will
> +			# manuall authenticate for tese 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 => {


^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH] send-email: implement SMTP bearer authentication
  2024-10-11 17:48 ` Shengyu Qu
@ 2024-10-11 18:05   ` Junio C Hamano
  2024-10-11 18:24     ` Shengyu Qu
  0 siblings, 1 reply; 11+ messages in thread
From: Junio C Hamano @ 2024-10-11 18:05 UTC (permalink / raw)
  To: Shengyu Qu; +Cc: Julian Swagemakers, git

Shengyu Qu <wiagn233@outlook.com> writes:

> Sorry to bother but what had happened to this patch? It is more useful now
> since outlook also switched to oauth2 only mode.

You are the second person to mention that what the change wants to
do is sensible, but nobody gave any review that verified that the
change does what the change says it wants to do, so it was left in
the mailing list archive.

Thanks for pinging.  Perhaps it would remind and encourage others
(or even better, yourself) to review the patch to help it move
forward.

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH] send-email: implement SMTP bearer authentication
  2024-10-11 18:05   ` Junio C Hamano
@ 2024-10-11 18:24     ` Shengyu Qu
       [not found]       ` < <TYCPR01MB8437CDD2208EA6555117E72C98792@TYCPR01MB8437.jpnprd01.prod.outlook.com>
  0 siblings, 1 reply; 11+ messages in thread
From: Shengyu Qu @ 2024-10-11 18:24 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: wiagn233, Julian Swagemakers, git

Hello Junio,

Seems you didn't CCed all relative people about this patch so that
maintainers about this file might didn't notice this patch, you can try
the script mentioned here[1] and resend this patch.

Best regards,
Shengyu

[1] https://git-scm.com/docs/SubmittingPatches#send-patches

在 2024/10/12 2:05, Junio C Hamano 写道:
> Shengyu Qu <wiagn233@outlook.com> writes:
> 
>> Sorry to bother but what had happened to this patch? It is more useful now
>> since outlook also switched to oauth2 only mode.
> 
> You are the second person to mention that what the change wants to
> do is sensible, but nobody gave any review that verified that the
> change does what the change says it wants to do, so it was left in
> the mailing list archive.
> 
> Thanks for pinging.  Perhaps it would remind and encourage others
> (or even better, yourself) to review the patch to help it move
> forward.


^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH] send-email: implement SMTP bearer authentication
       [not found]       ` < <TYCPR01MB8437CDD2208EA6555117E72C98792@TYCPR01MB8437.jpnprd01.prod.outlook.com>
@ 2024-10-12 18:43         ` Julian Swagemakers
  0 siblings, 0 replies; 11+ messages in thread
From: Julian Swagemakers @ 2024-10-12 18:43 UTC (permalink / raw)
  To: Shengyu Qu, Junio C Hamano; +Cc: git

Hi Shengyu,

> Seems you didn't CCed all relative people about this patch so that
> maintainers about this file might didn't notice this patch, you can try
> the script mentioned here[1] and resend this patch.

I did see that, but the output is just Junio, and I assumed as he is the
maintainer, he would be following the list and did not need the extra
CC. I don't know who else could be interested in this patch and is
willing to test and review it.

Regards Julian

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH] send-email: implement SMTP bearer authentication
@ 2025-01-07 19:49 M Hickford
  2025-01-11 19:06 ` Julian Swagemakers
  0 siblings, 1 reply; 11+ messages in thread
From: M Hickford @ 2025-01-07 19:49 UTC (permalink / raw)
  To: julian; +Cc: git, wiagn233, sandals

Hi Julian. The patch looks good. Please could you add instructions how to test it? Which servers have you tested?

I have a gmail.com account. I configured Git following https://git-scm.com/docs/git-send-email#_use_gmail_as_the_smtp_server

	[sendemail]
		smtpEncryption = tls
		smtpServer = smtp.gmail.com
		smtpUser = yourname@gmail.com
		smtpServerPort = 587

And configured git-credential-oauth following https://github.com/hickford/git-credential-oauth/issues/48#issuecomment-1966486513

	[credential "smtp://smtp.gmail.com:587"]
		oauthClientId = 406964657835-aq8lmia8j95dhl1a2bvharmfk3t1hgqj.apps.googleusercontent.com
		oauthClientSecret = kSmqreRr0qwBWJgbf5Y-PjSU
		oauthScopes = https://mail.google.com/
		oauthAuthURL = https://accounts.google.com/o/oauth2/auth
		oauthTokenURL = https://oauth2.googleapis.com/token

Then tested with: git send-email --smtp-auth=XOAUTH2 --smtp-debug=1 message.txt

"git credential" was queried for an OAuth access token as expected. Note that Gmail access tokens are very long (~220 characters).

However smtp.gmail.com responded authentication error "535-5.7.8 Username and Password not accepted".

Looking at the debug information, it looks like the SMTP command "AUTH XOAUTH2 <base64>" was corrupted by a space at column 241. Exactly one base64 string should follow "AUTH XOAUTH2 ", no spaces.

The same problem occurs with OAUTHBEARER: git send-email --smtp-auth=OAUTHBEARER --smtp-debug=1 message.txt

I can reproduce this with any sufficiently long combination of user and pass, such as: git send-email --smtp-auth=XOAUTH2 --smtp-user=tim.arsietonarsei@example.com --smtp-pass=arsteiarositanrestnerastarstarstarstarstarsetnrasetnearstrasitenarseitnerasntearnstenarsetnearstarstearsetnariestnearsntenarestnerast --smtp-encryption=tls --smtp-server-port=587 --smtp-server=smtp.gmail.com --smtp-debug=1 message.txt

Can you reproduce this problem?

Any ideas?

I think the bug must be in the call to Net::SMTP.

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH] send-email: implement SMTP bearer authentication
  2025-01-07 19:49 [PATCH] " M Hickford
@ 2025-01-11 19:06 ` Julian Swagemakers
  2025-01-11 21:19   ` M Hickford
  0 siblings, 1 reply; 11+ messages in thread
From: Julian Swagemakers @ 2025-01-11 19:06 UTC (permalink / raw)
  To: M Hickford; +Cc: git, wiagn233, sandals

Hi Mirth, thanks for taking a look and testing.

> Please could you add instructions how to test it?

Sure, below you can find steps which can be used for testing.

   # requirement git-credentials-oauth installed

   # build patched git
   git clone https://git.kernel.org/pub/scm/git/git.git tmp_git
   cd tmp_git
   curl https://lore.kernel.org/git/20240225103413.9845-1-julian@swagemakers.org/raw |git am
   make

   # backup gitconfig
   mv ~/.gitconfig{,_backup}

   # create minimal gitconfig
   cat << EOF >> ~/.gitconfig

   [credential]
      helper = cache --timeout 7200
      helper = oauth
   [credential "smtp://smtp.gmail.com:587"]
      oauthClientId = 406964657835-aq8lmia8j95dhl1a2bvharmfk3t1hgqj.apps.googleusercontent.com
      oauthClientSecret = kSmqreRr0qwBWJgbf5Y-PjSU
      oauthScopes = https://mail.google.com/
      oauthAuthURL = https://accounts.google.com/o/oauth2/auth
      oauthTokenURL = https://oauth2.googleapis.com/token
   [user]
      email = you@example.com
      name = Your Name

   EOF

   # create email to send
   cat << EOF >> message.txt
   Subject: test email from git

   Hay from git

   EOF

   # update PATH to use patched git
   export PATH="${PWD}:${PATH}"

   # confirm you are using the correct git version, should look
   # something like `git version 2.48.0.rc2.32.g5adec67521`
   git --version

   git send-email \
    --to=email@example.com \
    --smtp-auth=XOAUTH2 \
    --smtp-encryption=tls \
    --smtp-server-port=587 \
    --smtp-server=smtp.gmail.com \
    --smtp-debug=1 \
    --smtp-user=you@example.com \
    message.txt

    # now the browser should open with the oauth flow.

> Which servers have you tested?

I've tested this with gmail using a consumer google account as well as a
managed google account and with the office settings I've tested
office365.

> Looking at the debug information, it looks like the SMTP command "AUTH
> XOAUTH2 <base64>" was corrupted by a space at column 241. Exactly one
> base64 string should follow "AUTH XOAUTH2 ", no spaces.

I can also see a space in the debug output, but the position depends on
my terminal size, and it is not interfering with authentication in my
case. I think it is caused by output formatting of the debug statement
and not the source of the issue you are having.

If you have a coded auth string you can use openssl client directly
to test it.

   openssl s_client -starttls smtp -connect smtp.gmail.com:587

   AUTH XOAUTH2 auth_sting

If you have the xoauth2 access token you can create the auth string with

   echo -n -e "user=${EMAIL}\x01auth=Bearer ${TOKEN}\x01\x01" | base64 -w0

If that does not help, can you give me some more details on your setup?
Then I'll try to reproduce the problem.

I've tested the steps above on Arch Linux with Perl v5.40.0, and
Ubuntu with Perl v5.38.2.

Regards Julian


^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH] send-email: implement SMTP bearer authentication
  2025-01-11 19:06 ` Julian Swagemakers
@ 2025-01-11 21:19   ` M Hickford
  0 siblings, 0 replies; 11+ messages in thread
From: M Hickford @ 2025-01-11 21:19 UTC (permalink / raw)
  To: Julian Swagemakers, git

On Sat, Jan 11, 2025 at 7:06 PM Julian Swagemakers
<julian@swagemakers.org> wrote:
>
> Hi Mirth, thanks for taking a look and testing.
>
> > Please could you add instructions how to test it?
>
> Sure, below you can find steps which can be used for testing.
>
>    # requirement git-credentials-oauth installed
>
>    # build patched git
>    git clone https://git.kernel.org/pub/scm/git/git.git tmp_git
>    cd tmp_git
>    curl https://lore.kernel.org/git/20240225103413.9845-1-julian@swagemakers.org/raw |git am
>    make
>
>    # backup gitconfig
>    mv ~/.gitconfig{,_backup}
>
>    # create minimal gitconfig
>    cat << EOF >> ~/.gitconfig
>
>    [credential]
>       helper = cache --timeout 7200
>       helper = oauth
>    [credential "smtp://smtp.gmail.com:587"]
>       oauthClientId = 406964657835-aq8lmia8j95dhl1a2bvharmfk3t1hgqj.apps.googleusercontent.com
>       oauthClientSecret = kSmqreRr0qwBWJgbf5Y-PjSU
>       oauthScopes = https://mail.google.com/
>       oauthAuthURL = https://accounts.google.com/o/oauth2/auth
>       oauthTokenURL = https://oauth2.googleapis.com/token
>    [user]
>       email = you@example.com
>       name = Your Name
>
>    EOF
>
>    # create email to send
>    cat << EOF >> message.txt
>    Subject: test email from git
>
>    Hay from git
>
>    EOF
>
>    # update PATH to use patched git
>    export PATH="${PWD}:${PATH}"
>
>    # confirm you are using the correct git version, should look
>    # something like `git version 2.48.0.rc2.32.g5adec67521`
>    git --version
>
>    git send-email \
>     --to=email@example.com \
>     --smtp-auth=XOAUTH2 \
>     --smtp-encryption=tls \
>     --smtp-server-port=587 \
>     --smtp-server=smtp.gmail.com \
>     --smtp-debug=1 \
>     --smtp-user=you@example.com \
>     message.txt
>
>     # now the browser should open with the oauth flow.
>
> > Which servers have you tested?
>
> I've tested this with gmail using a consumer google account as well as a
> managed google account and with the office settings I've tested
> office365.
>
> > Looking at the debug information, it looks like the SMTP command "AUTH
> > XOAUTH2 <base64>" was corrupted by a space at column 241. Exactly one
> > base64 string should follow "AUTH XOAUTH2 ", no spaces.
>
> I can also see a space in the debug output, but the position depends on
> my terminal size, and it is not interfering with authentication in my
> case. I think it is caused by output formatting of the debug statement
> and not the source of the issue you are having.
>
> If you have a coded auth string you can use openssl client directly
> to test it.
>
>    openssl s_client -starttls smtp -connect smtp.gmail.com:587
>
>    AUTH XOAUTH2 auth_sting
>
> If you have the xoauth2 access token you can create the auth string with
>
>    echo -n -e "user=${EMAIL}\x01auth=Bearer ${TOKEN}\x01\x01" | base64 -w0
>
> If that does not help, can you give me some more details on your setup?
> Then I'll try to reproduce the problem.

Oops I had my email accounts mixed up.

I confirm both XOAUTH2 and OAUTHBEARER work to send email with gmail.com

Tested-by: mirth.hickford@gmail.com

>
> I've tested the steps above on Arch Linux with Perl v5.40.0, and
> Ubuntu with Perl v5.38.2.
>
> Regards Julian
>

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH] send-email: implement SMTP bearer authentication
@ 2025-01-11 21:27 M Hickford
  0 siblings, 0 replies; 11+ messages in thread
From: M Hickford @ 2025-01-11 21:27 UTC (permalink / raw)
  To: julian; +Cc: git, wiagn233, sandals

> 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

Please add documentation to git-send-email.txt

> 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 = OAUTHBEARER
> ```
> 
> As 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
> 
> Signed-off-by: Julian Swagemakers <julian@swagemakers.org>
> ---
>  git-send-email.perl | 65 +++++++++++++++++++++++++++++++++++++++++++--
>  1 file changed, 63 insertions(+), 2 deletions(-)
> 
> diff --git a/git-send-email.perl b/git-send-email.perl
> index 821b2b3a13..72d378f6fd 100755
> --- a/git-send-email.perl
> +++ b/git-send-email.perl
> @@ -1359,6 +1359,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 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.
>  
> @@ -1392,8 +1449,12 @@ sub smtp_auth_maybe {
>  		'password' => $smtp_authpass
>  	}, sub {
>  		my $cred = shift;
> -
> -		if ($smtp_auth) {
> +		if ($smtp_auth eq "OAUTHBEARER" or $smtp_auth eq "XOAUTH2") {

Be careful to check that $smtp_auth is initialized to avoid warnings:

Use of uninitialized value $smtp_auth in string eq at /home/matt/libexec/git-core/git-send-email line 1493.
Use of uninitialized value $smtp_auth in string eq at /home/matt/libexec/git-core/git-send-email line 1493.

> +			# Since Authen:SASL does not support XOAUTH2 nor OAUTHBEARER we will
> +			# manuall authenticate for tese 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 => {

^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH v2] send-email: implement SMTP bearer authentication
  2024-02-25 10:34 [PATCH] send-email: implement SMTP bearer authentication Julian Swagemakers
  2024-02-28 17:53 ` M Hickford
  2024-10-11 17:48 ` Shengyu Qu
@ 2025-01-25 19:01 ` Julian Swagemakers
  2 siblings, 0 replies; 11+ messages in thread
From: Julian Swagemakers @ 2025-01-25 19:01 UTC (permalink / raw)
  To: git; +Cc: mirth.hickford, julian, sandals, wiagn233

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 = OAUTHBEARER

As 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(-)

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


^ permalink raw reply related	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2025-01-25 19:02 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-02-25 10:34 [PATCH] send-email: implement SMTP bearer authentication Julian Swagemakers
2024-02-28 17:53 ` M Hickford
2024-10-11 17:48 ` Shengyu Qu
2024-10-11 18:05   ` Junio C Hamano
2024-10-11 18:24     ` Shengyu Qu
     [not found]       ` < <TYCPR01MB8437CDD2208EA6555117E72C98792@TYCPR01MB8437.jpnprd01.prod.outlook.com>
2024-10-12 18:43         ` Julian Swagemakers
2025-01-25 19:01 ` [PATCH v2] " Julian Swagemakers
  -- strict thread matches above, loose matches on Subject: below --
2025-01-07 19:49 [PATCH] " M Hickford
2025-01-11 19:06 ` Julian Swagemakers
2025-01-11 21:19   ` M Hickford
2025-01-11 21:27 M Hickford

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).