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
@ 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-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

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).