1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2008-2020. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19%%
20%%
21%%----------------------------------------------------------------------
22%% Purpose: Handles an ssh connection, e.i. both the
23%% setup SSH Transport Layer Protocol (RFC 4253), Authentication
24%% Protocol (RFC 4252) and SSH connection Protocol (RFC 4255)
25%% Details of the different protocols are
26%% implemented in ssh_transport.erl, ssh_auth.erl and ssh_connection.erl
27%% ----------------------------------------------------------------------
28
29-module(ssh_fsm_userauth_client).
30
31-include("ssh.hrl").
32-include("ssh_transport.hrl").
33-include("ssh_auth.hrl").
34-include("ssh_connect.hrl").
35
36-include("ssh_fsm.hrl").
37
38%%====================================================================
39%%% Exports
40%%====================================================================
41
42%%% Behaviour callbacks
43-export([callback_mode/0, handle_event/4, terminate/3,
44	 format_status/2, code_change/4]).
45
46%%====================================================================
47%% gen_statem callbacks
48%%====================================================================
49
50callback_mode() ->
51    [handle_event_function,
52     state_enter].
53
54%%--------------------------------------------------------------------
55
56%%% ######## {userauth, client} ####
57
58%%---- #ssh_msg_ext_info could follow after the key exchange, both the intial and the re-negotiation
59handle_event(internal, #ssh_msg_ext_info{}=Msg, {userauth,client}, D0) ->
60    %% FIXME: need new state to receive this msg!
61    D = ssh_connection_handler:handle_ssh_msg_ext_info(Msg, D0),
62    {keep_state, D};
63
64%%---- recevied userauth success from the server
65handle_event(internal, #ssh_msg_userauth_success{}, {userauth,client}, D0=#data{ssh_params = Ssh}) ->
66    ssh_auth:ssh_msg_userauth_result(success),
67    ssh_connection_handler:handshake(ssh_connected, D0),
68    D = D0#data{ssh_params=Ssh#ssh{authenticated = true}},
69    {next_state, {connected,client}, D, {change_callback_module,ssh_connection_handler}};
70
71
72%%---- userauth failure response to clientfrom the server
73handle_event(internal, #ssh_msg_userauth_failure{}, {userauth,client}=StateName,
74	     #data{ssh_params = #ssh{userauth_methods = []}} = D0) ->
75    {Shutdown, D} =
76        ?send_disconnect(?SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE,
77                         io_lib:format("User auth failed for: ~p",[D0#data.auth_user]),
78                         StateName, D0),
79    {stop, Shutdown, D};
80
81handle_event(internal, #ssh_msg_userauth_failure{authentications = Methods}, StateName={userauth,client},
82	     D0 = #data{ssh_params = Ssh0}) ->
83    %% The prefered authentication method failed, try next method
84    Ssh1 = case Ssh0#ssh.userauth_methods of
85	       none ->
86		   %% Server tells us which authentication methods that are allowed
87		   Ssh0#ssh{userauth_methods = string:tokens(Methods, ",")};
88	       _ ->
89		   %% We already know...
90		   Ssh0
91	   end,
92    case ssh_auth:userauth_request_msg(Ssh1) of
93        {send_disconnect, Code, Ssh} ->
94            {Shutdown, D} =
95                ?send_disconnect(Code,
96                                 io_lib:format("User auth failed for: ~p",[D0#data.auth_user]),
97                                 StateName, D0#data{ssh_params = Ssh}),
98	    {stop, Shutdown, D};
99	{"keyboard-interactive", {Msg, Ssh}} ->
100            D = ssh_connection_handler:send_msg(Msg, D0#data{ssh_params = Ssh}),
101	    {next_state, {userauth_keyboard_interactive,client}, D};
102	{_Method, {Msg, Ssh}} ->
103            D = ssh_connection_handler:send_msg(Msg, D0#data{ssh_params = Ssh}),
104	    {keep_state, D}
105    end;
106
107%%---- banner to client
108handle_event(internal, #ssh_msg_userauth_banner{message = Msg}, {userauth,client}, D) ->
109    case D#data.ssh_params#ssh.userauth_quiet_mode of
110	false -> io:format("~s", [Msg]);
111	true -> ok
112    end,
113    keep_state_and_data;
114
115
116%%% ######## {userauth_keyboard_interactive, client}
117
118handle_event(internal, #ssh_msg_userauth_info_request{} = Msg, {userauth_keyboard_interactive, client},
119	     #data{ssh_params = Ssh0} = D0) ->
120    case ssh_auth:handle_userauth_info_request(Msg, Ssh0) of
121	{ok, {Reply, Ssh}} ->
122            D = ssh_connection_handler:send_msg(Reply, D0#data{ssh_params = Ssh}),
123	    {next_state, {userauth_keyboard_interactive_info_response,client}, D};
124	not_ok ->
125	    {next_state, {userauth,client}, D0, [postpone]}
126    end;
127
128handle_event(internal, #ssh_msg_userauth_failure{}, {userauth_keyboard_interactive, client},
129	     #data{ssh_params = Ssh0} = D0) ->
130    Prefs = [{Method,M,F,A} || {Method,M,F,A} <- Ssh0#ssh.userauth_preference,
131			       Method =/= "keyboard-interactive"],
132    D = D0#data{ssh_params = Ssh0#ssh{userauth_preference=Prefs}},
133    {next_state, {userauth,client}, D, [postpone]};
134
135handle_event(internal, #ssh_msg_userauth_failure{}, {userauth_keyboard_interactive_info_response, client},
136	     #data{ssh_params = Ssh0} = D0) ->
137    Opts = Ssh0#ssh.opts,
138    D = case ?GET_OPT(password, Opts) of
139	    undefined ->
140		D0;
141	    _ ->
142		D0#data{ssh_params =
143			    Ssh0#ssh{opts = ?PUT_OPT({password,not_ok}, Opts)}} % FIXME:intermodule dependency
144	end,
145    {next_state, {userauth,client}, D, [postpone]};
146
147handle_event(internal, #ssh_msg_ext_info{}=Msg, {userauth_keyboard_interactive_info_response, client}, D0) ->
148    %% FIXME: need new state to receive this msg!
149    D = ssh_connection_handler:handle_ssh_msg_ext_info(Msg, D0),
150    {keep_state, D};
151
152handle_event(internal, #ssh_msg_userauth_success{}, {userauth_keyboard_interactive_info_response, client}, D) ->
153    {next_state, {userauth,client}, D, [postpone]};
154
155handle_event(internal, #ssh_msg_userauth_info_request{}, {userauth_keyboard_interactive_info_response, client}, D) ->
156    {next_state, {userauth_keyboard_interactive,client}, D, [postpone]};
157
158
159%%% ######## UNHANDLED EVENT!
160handle_event(Type, Event, StateName, D) ->
161    ssh_connection_handler:handle_event(Type, Event, StateName, D).
162
163%%--------------------------------------------------------------------
164format_status(A, B) ->
165    ssh_connection_handler:format_status(A, B).
166
167%%--------------------------------------------------------------------
168terminate(Reason, StateName, D) ->
169    ssh_connection_handler:terminate(Reason, StateName, D).
170
171%%--------------------------------------------------------------------
172code_change(_OldVsn, StateName, State, _Extra) ->
173    {ok, StateName, State}.
174
175%%====================================================================
176%% Internal functions
177%%====================================================================
178
179
180