From 60376459c183543f5767e8cf0a93690cf52d6e24 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stephen=20R=C3=B6ttger?= <stephen.roettger@zero-entropy.de>
Date: Thu, 25 Apr 2013 16:34:45 +0200
Subject: [PATCH] SCRAM optional parameter parsing bugfix

The server gave an authentication error, if optional parameters
were present in the GS2 Header. Specifically, the "a=" parameter,
that can be used by admins to login as a different user.

Backport of 9e9b0eae802ee0508db6780426954efd048e7976 to 2.1.10
---
 src/cyrsasl_scram.erl | 58 ++++++++++++++++++++++++++++++---------------------
 1 file changed, 34 insertions(+), 24 deletions(-)

diff --git a/src/cyrsasl_scram.erl b/src/cyrsasl_scram.erl
index 002d6e4..efc63c8 100644
--- a/src/cyrsasl_scram.erl
+++ b/src/cyrsasl_scram.erl
@@ -34,6 +34,8 @@
 
 -include("ejabberd.hrl").
 
+-include("jlib.hrl").
+
 -behaviour(cyrsasl).
 
 -record(state, {step, stored_key, server_key, username, get_password, check_password,
@@ -52,8 +54,12 @@ mech_new(_Host, GetPassword, _CheckPassword, _CheckPasswordDigest) ->
     {ok, #state{step = 2, get_password = GetPassword}}.
 
 mech_step(#state{step = 2} = State, ClientIn) ->
-	case string:tokens(ClientIn, ",") of
-	[CBind, UserNameAttribute, ClientNonceAttribute] when (CBind == "y") or (CBind == "n") ->
+	case re:split(ClientIn, ",", [{return, list}]) of
+	[_CBind, _AuthorizationIdentity, _UserNameAttribute, _ClientNonceAttribute, ExtensionAttribute | _]
+	when ExtensionAttribute /= [] ->
+		{error, <<"protocol-error-extension-not-supported">>};
+	[CBind, _AuthorizationIdentity, UserNameAttribute, ClientNonceAttribute | _]
+	when (CBind == "y") or (CBind == "n") ->
 		case parse_attribute(UserNameAttribute) of
                 {error, Reason} ->
 			{error, Reason};
@@ -100,32 +106,36 @@ mech_step(#state{step = 4} = State, ClientIn) ->
 	case string:tokens(ClientIn, ",") of
 	[GS2ChannelBindingAttribute, NonceAttribute, ClientProofAttribute] ->
 		case parse_attribute(GS2ChannelBindingAttribute) of
-		{$c, CVal} when (CVal == "biws") or (CVal == "eSws") ->
-		    %% biws is base64 for n,, => channelbinding not supported
-		    %% eSws is base64 for y,, => channelbinding supported by client only
- 			Nonce = State#state.client_nonce ++ State#state.server_nonce,
-			case parse_attribute(NonceAttribute) of
-			{$r, CompareNonce} when CompareNonce == Nonce ->
-				case parse_attribute(ClientProofAttribute) of
-				{$p, ClientProofB64} ->
-					ClientProof = base64:decode(ClientProofB64),
-					AuthMessage = State#state.auth_message ++ "," ++ string:substr(ClientIn, 1, string:str(ClientIn, ",p=")-1),
-					ClientSignature = scram:client_signature(State#state.stored_key, AuthMessage),
-					ClientKey = scram:client_key(ClientProof, ClientSignature),
-					CompareStoredKey = scram:stored_key(ClientKey),
-					if CompareStoredKey == State#state.stored_key ->
-						ServerSignature = scram:server_signature(State#state.server_key, AuthMessage),
-						{ok, [{username, State#state.username}], "v=" ++ base64:encode_to_string(ServerSignature)};
-					true ->
-						{error, "bad-auth"}
+		{$c, CVal} ->
+			ChannelBindingSupport = string:left(jlib:decode_base64(CVal), 1),
+			if (ChannelBindingSupport == "n")
+			or (ChannelBindingSupport == "y") ->
+				Nonce = State#state.client_nonce ++ State#state.server_nonce,
+				case parse_attribute(NonceAttribute) of
+				{$r, CompareNonce} when CompareNonce == Nonce ->
+					case parse_attribute(ClientProofAttribute) of
+					{$p, ClientProofB64} ->
+						ClientProof = base64:decode(ClientProofB64),
+						AuthMessage = State#state.auth_message ++ "," ++ string:substr(ClientIn, 1, string:str(ClientIn, ",p=")-1),
+						ClientSignature = scram:client_signature(State#state.stored_key, AuthMessage),
+						ClientKey = scram:client_key(ClientProof, ClientSignature),
+						CompareStoredKey = scram:stored_key(ClientKey),
+						if CompareStoredKey == State#state.stored_key ->
+							ServerSignature = scram:server_signature(State#state.server_key, AuthMessage),
+							{ok, [{username, State#state.username}], "v=" ++ base64:encode_to_string(ServerSignature)};
+						true ->
+							{error, "bad-auth"}
+						end;
+					_Else ->
+						{error, "bad-protocol"}
 					end;
+				{$r, _} ->
+					{error, "bad-nonce"};
 				_Else ->
 					{error, "bad-protocol"}
 				end;
-			{$r, _} ->
-				{error, "bad-nonce"};
-			_Else ->
-				{error, "bad-protocol"}
+			true ->
+				{error, "bad-channel-binding"}
 			end;
 		_Else ->
 	   		{error, "bad-protocol"}
-- 
1.8.1.4

