|
@@ -12,18 +12,22 @@
|
|
|
-type connect_error() ::
|
|
|
invalid_authorization_specification
|
|
|
| invalid_password
|
|
|
- | {unsupported_auth_method, integer()}
|
|
|
+ | {unsupported_auth_method,
|
|
|
+ kerberosV5 | crypt | scm | gss | sspi | {unknown, integer()} | {sasl, [binary()]}}
|
|
|
+ | {sasl_server_final, any()}
|
|
|
| epgsql:query_error().
|
|
|
|
|
|
-include("epgsql.hrl").
|
|
|
-include("protocol.hrl").
|
|
|
|
|
|
+-type auth_fun() :: fun((init | binary(), _, _) -> {send, byte(), iodata(), any()} | ok | {error, any()}).
|
|
|
+
|
|
|
-record(connect,
|
|
|
{opts :: list(),
|
|
|
- auth_fun :: fun() | undefined,
|
|
|
+ auth_fun :: auth_fun() | undefined,
|
|
|
auth_state :: any() | undefined,
|
|
|
auth_send :: {integer(), iodata()} | undefined,
|
|
|
- stage = connect :: connect | auth | initialization}).
|
|
|
+ stage = connect :: connect | maybe_auth | auth | initialization}).
|
|
|
|
|
|
-define(SCRAM_AUTH_METHOD, <<"SCRAM-SHA-256">>).
|
|
|
-define(AUTH_OK, 0).
|
|
@@ -83,7 +87,7 @@ execute(PgSock, #connect{opts = Opts, stage = connect} = State) ->
|
|
|
undefined -> PgSock2;
|
|
|
Async -> epgsql_sock:set_attr(async, Async, PgSock2)
|
|
|
end,
|
|
|
- {ok, PgSock3, State#connect{stage = auth}};
|
|
|
+ {ok, PgSock3, State#connect{stage = maybe_auth}};
|
|
|
{error, Reason} = Error ->
|
|
|
{stop, Reason, Error, PgSock}
|
|
|
end;
|
|
@@ -116,27 +120,53 @@ maybe_ssl(S, Flag, Opts, PgSock) ->
|
|
|
end
|
|
|
end.
|
|
|
|
|
|
-
|
|
|
%% Auth sub-protocol
|
|
|
|
|
|
+auth_init(<<?AUTH_CLEARTEXT:?int32>>, Sock, St) ->
|
|
|
+ auth_init(fun auth_cleartext/3, undefined, Sock, St);
|
|
|
+auth_init(<<?AUTH_MD5:?int32, Salt:4/binary>>, Sock, St) ->
|
|
|
+ auth_init(fun auth_md5/3, Salt, Sock, St);
|
|
|
+auth_init(<<?AUTH_SASL:?int32, MethodsB/binary>>, Sock, St) ->
|
|
|
+ Methods = epgsql_wire:decode_strings(MethodsB),
|
|
|
+ case lists:member(?SCRAM_AUTH_METHOD, Methods) of
|
|
|
+ true ->
|
|
|
+ auth_init(fun auth_scram/3, undefined, Sock, St);
|
|
|
+ false ->
|
|
|
+ {stop, normal, {error, {unsupported_auth_method,
|
|
|
+ {sasl, lists:delete(<<>>, Methods)}}}}
|
|
|
+ end;
|
|
|
+auth_init(<<M:?int32, _/binary>>, Sock, _St) ->
|
|
|
+ Method = case M of
|
|
|
+ 2 -> kerberosV5;
|
|
|
+ 4 -> crypt;
|
|
|
+ 6 -> scm;
|
|
|
+ 7 -> gss;
|
|
|
+ 8 -> sspi;
|
|
|
+ _ -> {unknown, M}
|
|
|
+ end,
|
|
|
+ {stop, normal, {error, {unsupported_auth_method, Method}}, Sock}.
|
|
|
+
|
|
|
auth_init(Fun, InitState, PgSock, St) ->
|
|
|
auth_handle(init, PgSock, St#connect{auth_fun = Fun, auth_state = InitState,
|
|
|
- stage = auth}).
|
|
|
+ stage = auth}).
|
|
|
|
|
|
auth_handle(Data, PgSock, #connect{auth_fun = Fun, auth_state = AuthSt} = St) ->
|
|
|
case Fun(Data, AuthSt, St) of
|
|
|
{send, SendPacketId, SendData, AuthSt1} ->
|
|
|
{requeue, PgSock, St#connect{auth_state = AuthSt1,
|
|
|
auth_send = {SendPacketId, SendData}}};
|
|
|
- ok ->
|
|
|
- {noaction, PgSock, St}
|
|
|
+ ok -> {noaction, PgSock, St};
|
|
|
+ {error, Reason} ->
|
|
|
+ {stop, normal, {error, Reason}}
|
|
|
end.
|
|
|
|
|
|
+%% AuthenticationCleartextPassword
|
|
|
auth_cleartext(init, _AuthState, #connect{opts = Opts}) ->
|
|
|
Password = get_val(password, Opts),
|
|
|
{send, ?PASSWORD, [Password, 0], undefined};
|
|
|
auth_cleartext(_, _, _) -> unknown.
|
|
|
|
|
|
+%% AuthenticationMD5Password
|
|
|
auth_md5(init, Salt, #connect{opts = Opts}) ->
|
|
|
User = get_val(username, Opts),
|
|
|
Password = get_val(password, Opts),
|
|
@@ -145,7 +175,7 @@ auth_md5(init, Salt, #connect{opts = Opts}) ->
|
|
|
{send, ?PASSWORD, Str, undefined};
|
|
|
auth_md5(_, _, _) -> unknown.
|
|
|
|
|
|
-
|
|
|
+%% AuthenticationSASL
|
|
|
auth_scram(init, undefined, #connect{opts = Opts}) ->
|
|
|
User = get_val(username, Opts),
|
|
|
Nonce = epgsql_scram:get_nonce(10),
|
|
@@ -158,8 +188,11 @@ auth_scram(<<?AUTH_SASL_CONTINUE:?int32, ServerFirst/binary>>, {auth_request, No
|
|
|
ServerFirstParts = epgsql_scram:parse_server_first(ServerFirst, Nonce),
|
|
|
{ClientFinalMessage, ServerProof} = epgsql_scram:get_client_final(ServerFirstParts, Nonce, User, Password),
|
|
|
{send, ?SASL_ANY_RESPONSE, ClientFinalMessage, {server_final, ServerProof}};
|
|
|
-auth_scram(_Msg, {server_final, _ServerProof}, _Conn) ->
|
|
|
- ok.
|
|
|
+auth_scram(<<?AUTH_SASL_FINAL:?int32, ServerFinalMsg/binary>>, {server_final, ServerProof}, _Conn) ->
|
|
|
+ case epgsql_scram:parse_server_final(ServerFinalMsg) of
|
|
|
+ {ok, ServerProof} -> ok;
|
|
|
+ Other -> {error, {sasl_server_final, Other}}
|
|
|
+ end.
|
|
|
|
|
|
|
|
|
%% --- Auth ---
|
|
@@ -168,39 +201,12 @@ auth_scram(_Msg, {server_final, _ServerProof}, _Conn) ->
|
|
|
handle_message(?AUTHENTICATION_REQUEST, <<?AUTH_OK:?int32>>, Sock, State) ->
|
|
|
{noaction, Sock, State#connect{stage = initialization}};
|
|
|
|
|
|
-%% AuthenticationCleartextPassword
|
|
|
-handle_message(?AUTHENTICATION_REQUEST, <<?AUTH_CLEARTEXT:?int32>>, Sock, St) ->
|
|
|
- auth_init(fun auth_cleartext/3, undefined, Sock, St);
|
|
|
-
|
|
|
-%% AuthenticationMD5Password
|
|
|
-handle_message(?AUTHENTICATION_REQUEST, <<?AUTH_MD5:?int32, Salt:4/binary>>, Sock, St) ->
|
|
|
- auth_init(fun auth_md5/3, Salt, Sock, St);
|
|
|
-
|
|
|
-%% AuthenticationSASL
|
|
|
-handle_message(?AUTHENTICATION_REQUEST, <<?AUTH_SASL:?int32, MethodsB/binary>>, Sock, St) ->
|
|
|
- Methods = epgsql_wire:decode_strings(MethodsB),
|
|
|
- case lists:member(?SCRAM_AUTH_METHOD, Methods) of
|
|
|
- true ->
|
|
|
- auth_init(fun auth_scram/3, undefined, Sock, St);
|
|
|
- false ->
|
|
|
- {stop, normal, {error, {unsupported_auth_method,
|
|
|
- lists:delete(<<>>, Methods)}}}
|
|
|
- end;
|
|
|
+handle_message(?AUTHENTICATION_REQUEST, Message, Sock, #connect{stage = Stage} = St) when Stage =/= auth ->
|
|
|
+ auth_init(Message, Sock, St);
|
|
|
|
|
|
handle_message(?AUTHENTICATION_REQUEST, Packet, Sock, #connect{stage = auth} = St) ->
|
|
|
auth_handle(Packet, Sock, St);
|
|
|
|
|
|
-handle_message(?AUTHENTICATION_REQUEST, <<M:?int32, _/binary>>, Sock, _State) ->
|
|
|
- Method = case M of
|
|
|
- 2 -> kerberosV5;
|
|
|
- 4 -> crypt;
|
|
|
- 6 -> scm;
|
|
|
- 7 -> gss;
|
|
|
- 8 -> sspi;
|
|
|
- _ -> {unknown, M}
|
|
|
- end,
|
|
|
- {stop, normal, {error, {unsupported_auth_method, Method}}, Sock};
|
|
|
-
|
|
|
%% --- Initialization ---
|
|
|
|
|
|
%% BackendKeyData
|
|
@@ -215,7 +221,8 @@ handle_message(?READY_FOR_QUERY, _, Sock, _State) ->
|
|
|
|
|
|
|
|
|
%% ErrorResponse
|
|
|
-handle_message(?ERROR, Err, Sock, #connect{stage = auth} = _State) ->
|
|
|
+handle_message(?ERROR, Err, Sock, #connect{stage = Stage} = _State) when Stage == auth;
|
|
|
+ Stage == maybe_auth ->
|
|
|
Why = case Err#error.code of
|
|
|
<<"28000">> -> invalid_authorization_specification;
|
|
|
<<"28P01">> -> invalid_password;
|