diff options
Diffstat (limited to 'lib/ssh/src')
-rw-r--r-- | lib/ssh/src/Makefile | 4 | ||||
-rw-r--r-- | lib/ssh/src/ssh.app.src | 6 | ||||
-rw-r--r-- | lib/ssh/src/ssh.appup.src | 22 | ||||
-rw-r--r-- | lib/ssh/src/ssh.erl | 55 | ||||
-rw-r--r-- | lib/ssh/src/ssh_auth.erl | 26 | ||||
-rw-r--r-- | lib/ssh/src/ssh_auth.hrl | 4 | ||||
-rw-r--r-- | lib/ssh/src/ssh_channel.erl | 37 | ||||
-rw-r--r-- | lib/ssh/src/ssh_client_key.erl | 34 | ||||
-rw-r--r-- | lib/ssh/src/ssh_client_key_api.erl | 35 | ||||
-rw-r--r-- | lib/ssh/src/ssh_connection_handler.erl | 26 | ||||
-rw-r--r-- | lib/ssh/src/ssh_connection_manager.erl | 22 | ||||
-rw-r--r-- | lib/ssh/src/ssh_file.erl | 29 | ||||
-rw-r--r-- | lib/ssh/src/ssh_key_api.erl | 45 | ||||
-rw-r--r-- | lib/ssh/src/ssh_server_key.erl | 33 | ||||
-rw-r--r-- | lib/ssh/src/ssh_server_key_api.erl | 30 | ||||
-rw-r--r-- | lib/ssh/src/ssh_sftpd.erl | 89 | ||||
-rw-r--r-- | lib/ssh/src/ssh_subsystem.erl | 47 | ||||
-rw-r--r-- | lib/ssh/src/ssh_transport.erl | 4 | ||||
-rw-r--r-- | lib/ssh/src/ssh_xfer.erl | 5 | ||||
-rw-r--r-- | lib/ssh/src/ssh_xfer.hrl | 12 |
20 files changed, 386 insertions, 179 deletions
diff --git a/lib/ssh/src/Makefile b/lib/ssh/src/Makefile index b8eecd3fa2..323f0af191 100644 --- a/lib/ssh/src/Makefile +++ b/lib/ssh/src/Makefile @@ -41,7 +41,9 @@ RELSYSDIR = $(RELEASE_PATH)/lib/ssh-$(VSN) BEHAVIOUR_MODULES= \ ssh_sftpd_file_api \ ssh_channel \ - ssh_key_api + ssh_subsystem \ + ssh_client_key_api \ + ssh_server_key_api MODULES= \ ssh \ diff --git a/lib/ssh/src/ssh.app.src b/lib/ssh/src/ssh.app.src index 316c09eb06..a0ba7cf7d9 100644 --- a/lib/ssh/src/ssh.app.src +++ b/lib/ssh/src/ssh.app.src @@ -10,6 +10,7 @@ ssh_auth, ssh_bits, ssh_cli, + ssh_client_key_api, ssh_channel, ssh_channel_sup, ssh_connection, @@ -21,13 +22,14 @@ sshd_sup, ssh_file, ssh_io, - ssh_key_api, ssh_math, ssh_no_io, + ssh_server_key_api, ssh_sftp, ssh_sftpd, ssh_sftpd_file, ssh_sftpd_file_api, + ssh_subsystem, ssh_subsystem_sup, ssh_sup, ssh_system_sup, @@ -35,7 +37,7 @@ ssh_userreg, ssh_xfer]}, {registered, []}, - {applications, [kernel, stdlib, crypto]}, + {applications, [kernel, stdlib, crypto, public_key]}, {env, []}, {mod, {ssh_app, []}}]}. diff --git a/lib/ssh/src/ssh.appup.src b/lib/ssh/src/ssh.appup.src index 6ba32e018f..826a11f1f4 100644 --- a/lib/ssh/src/ssh.appup.src +++ b/lib/ssh/src/ssh.appup.src @@ -19,29 +19,15 @@ {"%VSN%", [ - {<<"2.1.1">>, [{restart_application, ssh}]}, - {<<"2.1">>, [{load_module, ssh_sftpd_file_api, soft_purge, soft_purge, []}, - {load_module, ssh_connection, soft_purge, soft_purge, []}, - {load_module, ssh_connection_manager, soft_purge, soft_purge, []}, - {load_module, ssh_auth, soft_purge, soft_purge, []}, - {load_module, ssh_connection_handler, soft_purge, soft_purge, []}, - {load_module, ssh_channel, soft_purge, soft_purge, []}, - {load_module, ssh_file, soft_purge, soft_purge, []}]}, - {load_module, ssh, soft_purge, soft_purge, []}]}, + {<<"2.1.1">>, [{restart_application, ssh}]}, + {<<"2.1">>, [{restart_application, ssh}]}, {<<"2.0\\.*">>, [{restart_application, ssh}]}, {<<"1\\.*">>, [{restart_application, ssh}]} ], [ {<<"2.1.1">>, [{restart_application, ssh}]}, - {<<"2.1">>,[{load_module, ssh_sftpd_file_api, soft_purge, soft_purge, []}, - {load_module, ssh_connection, soft_purge, soft_purge, []}, - {load_module, ssh_connection_manager, soft_purge, soft_purge, []}, - {load_module, ssh_auth, soft_purge, soft_purge, []}, - {load_module, ssh_connection_handler, soft_purge, soft_purge, []}, - {load_module, ssh_channel, soft_purge, soft_purge, []}, - {load_module, ssh_file, soft_purge, soft_purge, []}]}, - {load_module, ssh, soft_purge, soft_purge, []}]}, + {<<"2.1">>,[{restart_application, ssh}]}, {<<"2.0\\.*">>, [{restart_application, ssh}]}, {<<"1\\.*">>, [{restart_application, ssh}]} ] -}.
\ No newline at end of file +}. diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 719c97e940..3ef26b1678 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -41,19 +41,23 @@ %% %% Type = permanent | transient | temporary %% -%% Description: Starts the inets application. Default type +%% Description: Starts the ssh application. Default type %% is temporary. see application(3) %%-------------------------------------------------------------------- start() -> + application:start(crypto), + application:start(public_key), application:start(ssh). start(Type) -> + application:start(crypto, Type), + application:start(public_key, Type), application:start(ssh, Type). %%-------------------------------------------------------------------- %% Function: stop() -> ok %% -%% Description: Stops the inets application. +%% Description: Stops the ssh application. %%-------------------------------------------------------------------- stop() -> application:stop(ssh). @@ -76,7 +80,7 @@ connect(Host, Port, Options, Timeout) -> {error, _Reason} = Error -> Error; {SocketOptions, SshOptions} -> - DisableIpv6 = proplists:get_value(ip_v6_disabled, SshOptions, false), + DisableIpv6 = proplists:get_value(ipv6_disabled, SshOptions, false), Inet = inetopt(DisableIpv6), do_connect(Host, Port, [Inet | SocketOptions], [{user_pid, self()}, {host, Host} | fix_idle_time(SshOptions)], Timeout, DisableIpv6) @@ -169,7 +173,7 @@ daemon(HostAddr, Port, Options0) -> _ -> Options0 end, - DisableIpv6 = proplists:get_value(ip_v6_disabled, Options0, false), + DisableIpv6 = proplists:get_value(ipv6_disabled, Options0, false), {Host, Inet, Options} = case HostAddr of any -> {ok, Host0} = inet:gethostname(), @@ -264,7 +268,7 @@ start_daemon(Host, Port, Options, Inet) -> do_start_daemon(Host, Port, Options, SocketOptions) -> case ssh_system_sup:system_supervisor(Host, Port) of undefined -> - %% TODO: It would proably make more sense to call the + %% It would proably make more sense to call the %% address option host but that is a too big change at the %% monent. The name is a legacy name! try sshd_sup:start_child([{address, Host}, @@ -325,8 +329,6 @@ handle_option([{user_passwords, _} = Opt | Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{pwdfun, _} = Opt | Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{user_auth, _} = Opt | Rest],SocketOptions, SshOptions ) -> - handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{key_cb, _} = Opt | Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{role, _} = Opt | Rest], SocketOptions, SshOptions) -> @@ -344,7 +346,10 @@ handle_option([{disconnectfun, _} = Opt | Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{failfun, _} = Opt | Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); -handle_option([{ip_v6_disabled, _} = Opt | Rest], SocketOptions, SshOptions) -> +%%Backwards compatibility should not be underscore between ip and v6 in API +handle_option([{ip_v6_disabled, Value} | Rest], SocketOptions, SshOptions) -> + handle_option(Rest, SocketOptions, [handle_ssh_option({ipv6_disabled, Value}) | SshOptions]); +handle_option([{ipv6_disabled, _} = Opt | Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{transport, _} = Opt | Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); @@ -364,6 +369,8 @@ handle_option([{quiet_mode, _} = Opt|Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{idle_time, _} = Opt | Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); +handle_option([{rekey_limit, _} = Opt|Rest], SocketOptions, SshOptions) -> + handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([Opt | Rest], SocketOptions, SshOptions) -> handle_option(Rest, [handle_inet_option(Opt) | SocketOptions], SshOptions). @@ -377,10 +384,14 @@ handle_ssh_option({silently_accept_hosts, Value} = Opt) when Value == true; Valu Opt; handle_ssh_option({user_interaction, Value} = Opt) when Value == true; Value == false -> Opt; -handle_ssh_option({public_key_alg, Value} = Opt) when Value == ssh_rsa; Value == ssh_dsa -> +handle_ssh_option({public_key_alg, ssh_dsa}) -> + {public_key_alg, 'ssh-dss'}; +handle_ssh_option({public_key_alg, ssh_rsa}) -> + {public_key_alg, 'ssh-rsa'}; +handle_ssh_option({public_key_alg, Value} = Opt) when Value == 'ssh-rsa'; Value == 'ssh-dss' -> Opt; handle_ssh_option({pref_public_key_algs, Value} = Opt) when is_list(Value), length(Value) >= 1 -> - case check_pref_algs(Value) of + case handle_pref_algs(Value, []) of true -> Opt; _ -> @@ -400,8 +411,6 @@ handle_ssh_option({user_passwords, Value} = Opt) when is_list(Value)-> Opt; handle_ssh_option({pwdfun, Value} = Opt) when is_function(Value) -> Opt; -handle_ssh_option({user_auth, Value} = Opt) when is_function(Value) -> - Opt; handle_ssh_option({key_cb, Value} = Opt) when is_atom(Value) -> Opt; handle_ssh_option({compression, Value} = Opt) when is_atom(Value) -> @@ -420,7 +429,9 @@ handle_ssh_option({disconnectfun , Value} = Opt) when is_function(Value) -> Opt; handle_ssh_option({failfun, Value} = Opt) when is_function(Value) -> Opt; -handle_ssh_option({ip_v6_disabled, Value} = Opt) when is_boolean(Value) -> + +handle_ssh_option({ipv6_disabled, Value} = Opt) when Value == true; + Value == false -> Opt; handle_ssh_option({transport, {Protocol, Cb, ClosTag}} = Opt) when is_atom(Protocol), is_atom(Cb), @@ -440,6 +451,8 @@ handle_ssh_option({quiet_mode, Value} = Opt) when Value == true; Opt; handle_ssh_option({idle_time, Value} = Opt) when is_integer(Value), Value > 0 -> Opt; +handle_ssh_option({rekey_limit, Value} = Opt) when is_integer(Value) -> + Opt; handle_ssh_option(Opt) -> throw({error, {eoptions, Opt}}). @@ -448,7 +461,7 @@ handle_inet_option({active, _} = Opt) -> "and activ is handled internaly user is not allowd" "to specify this option"}}); handle_inet_option({inet, _} = Opt) -> - throw({error, {{eoptions, Opt},"Is set internaly use ip_v6_disabled to" + throw({error, {{eoptions, Opt},"Is set internaly use ipv6_disabled to" " enforce iv4 in the server, client will fallback to ipv4 if" " it can not use ipv6"}}); handle_inet_option({reuseaddr, _} = Opt) -> @@ -458,14 +471,18 @@ handle_inet_option({reuseaddr, _} = Opt) -> handle_inet_option(Opt) -> Opt. %% Check preferred algs -check_pref_algs([]) -> - true; -check_pref_algs([H|T]) -> +handle_pref_algs([], Acc) -> + {true, lists:reverse(Acc)}; +handle_pref_algs([H|T], Acc) -> case H of ssh_dsa -> - check_pref_algs(T); + handle_pref_algs(T, ['ssh-dss'| Acc]); ssh_rsa -> - check_pref_algs(T); + handle_pref_algs(T, ['ssh-rsa'| Acc]); + 'ssh-dss' -> + handle_pref_algs(T, ['ssh-dss'| Acc]); + 'ssh-rsa' -> + handle_pref_algs(T, ['ssh-rsa'| Acc]); _ -> false end. diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl index c436793dc4..cb0c7751f0 100644 --- a/lib/ssh/src/ssh_auth.erl +++ b/lib/ssh/src/ssh_auth.erl @@ -48,17 +48,18 @@ publickey_msg([Alg, #ssh{user = User, case KeyCb:user_key(Alg, Opts) of {ok, Key} -> + StrAlgo = algorithm_string(Alg), PubKeyBlob = encode_public_key(Key), SigData = build_sig_data(SessionId, - User, Service, PubKeyBlob, Alg), + User, Service, PubKeyBlob, StrAlgo), Sig = ssh_transport:sign(SigData, Hash, Key), - SigBlob = list_to_binary([?string(Alg), ?binary(Sig)]), + SigBlob = list_to_binary([?string(StrAlgo), ?binary(Sig)]), ssh_transport:ssh_packet( #ssh_msg_userauth_request{user = User, service = Service, method = "publickey", data = [?TRUE, - ?string(Alg), + ?string(StrAlgo), ?binary(PubKeyBlob), ?binary(SigBlob)]}, Ssh); @@ -120,8 +121,7 @@ init_userauth_request_msg(#ssh{opts = Opts} = Ssh) -> data = <<>>}, case proplists:get_value(pref_public_key_algs, Opts, false) of false -> - FirstAlg = algorithm(proplists:get_value(public_key_alg, Opts, - ?PREFERRED_PK_ALG)), + FirstAlg = proplists:get_value(public_key_alg, Opts, ?PREFERRED_PK_ALG), SecondAlg = other_alg(FirstAlg), AllowUserInt = proplists:get_value(user_interaction, Opts, true), Prefs = method_preference(FirstAlg, SecondAlg, AllowUserInt), @@ -130,7 +130,7 @@ init_userauth_request_msg(#ssh{opts = Opts} = Ssh) -> userauth_methods = none, service = "ssh-connection"}); Algs -> - FirstAlg = algorithm(lists:nth(1, Algs)), + FirstAlg = lists:nth(1, Algs), case length(Algs) =:= 2 of true -> SecondAlg = other_alg(FirstAlg), @@ -358,7 +358,7 @@ verify_sig(SessionId, User, Service, Alg, KeyBlob, SigWLen, Opts) -> {ok, Key} = decode_public_key_v2(KeyBlob, Alg), KeyCb = proplists:get_value(key_cb, Opts, ssh_file), - case KeyCb:is_auth_key(Key, User, Alg, Opts) of + case KeyCb:is_auth_key(Key, User, Opts) of true -> PlainText = build_sig_data(SessionId, User, Service, KeyBlob, Alg), @@ -381,9 +381,9 @@ build_sig_data(SessionId, User, Service, KeyBlob, Alg) -> ?binary(KeyBlob)], list_to_binary(Sig). -algorithm(ssh_rsa) -> +algorithm_string('ssh-rsa') -> "ssh-rsa"; -algorithm(ssh_dsa) -> +algorithm_string('ssh-dss') -> "ssh-dss". decode_keyboard_interactive_prompts(NumPrompts, Data) -> @@ -457,10 +457,10 @@ userauth_pk_messages() -> binary]} % key blob ]. -other_alg("ssh-rsa") -> - "ssh-dss"; -other_alg("ssh-dss") -> - "ssh-rsa". +other_alg('ssh-rsa') -> + 'ssh-dss'; +other_alg('ssh-dss') -> + 'ssh-rsa'. decode_public_key_v2(K_S, "ssh-rsa") -> case ssh_bits:decode(K_S,[string,mpint,mpint]) of ["ssh-rsa", E, N] -> diff --git a/lib/ssh/src/ssh_auth.hrl b/lib/ssh/src/ssh_auth.hrl index e74ee10041..6cd8e6bf14 100644 --- a/lib/ssh/src/ssh_auth.hrl +++ b/lib/ssh/src/ssh_auth.hrl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2010. All Rights Reserved. +%% Copyright Ericsson AB 2008-2012. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -23,7 +23,7 @@ -define(SUPPORTED_AUTH_METHODS, "publickey,keyboard-interactive,password"). --define(PREFERRED_PK_ALG, ssh_rsa). +-define(PREFERRED_PK_ALG, 'ssh-rsa'). -define(SSH_MSG_USERAUTH_REQUEST, 50). -define(SSH_MSG_USERAUTH_FAILURE, 51). diff --git a/lib/ssh/src/ssh_channel.erl b/lib/ssh/src/ssh_channel.erl index 1938858420..4e8f8538c2 100644 --- a/lib/ssh/src/ssh_channel.erl +++ b/lib/ssh/src/ssh_channel.erl @@ -23,14 +23,35 @@ -include("ssh_connect.hrl"). -%%% Optional callbacks handle_call/3, handle_cast/2, handle_msg/2, -%%% code_change/3 -%% Should be further specified later --callback init(Options::list()) -> - {ok, State::term()} | {ok, State::term(), Timeout::timeout()} | - {stop, Reason ::term()}. - --callback terminate(term(), term()) -> term(). +-callback init(Args :: term()) -> + {ok, State :: term()} | {ok, State :: term(), timeout() | hibernate} | + {stop, Reason :: term()} | ignore. +-callback handle_call(Request :: term(), From :: {pid(), Tag :: term()}, + State :: term()) -> + {reply, Reply :: term(), NewState :: term()} | + {reply, Reply :: term(), NewState :: term(), timeout() | hibernate} | + {noreply, NewState :: term()} | + {noreply, NewState :: term(), timeout() | hibernate} | + {stop, Reason :: term(), Reply :: term(), NewState :: term()} | + {stop, Reason :: term(), NewState :: term()}. +-callback handle_cast(Request :: term(), State :: term()) -> + {noreply, NewState :: term()} | + {noreply, NewState :: term(), timeout() | hibernate} | + {stop, Reason :: term(), NewState :: term()}. + +-callback terminate(Reason :: (normal | shutdown | {shutdown, term()} | + term()), + State :: term()) -> + term(). +-callback code_change(OldVsn :: (term() | {down, term()}), State :: term(), + Extra :: term()) -> + {ok, NewState :: term()} | {error, Reason :: term()}. + +-callback handle_msg(Msg ::term(), State :: term()) -> + {noreply, NewState :: term()} | + {noreply, NewState :: term(), timeout() | hibernate} | + {stop, Reason :: term(), NewState :: term()}. + -callback handle_ssh_msg({ssh_cm, ConnectionRef::term(), SshMsg::term()}, State::term()) -> {ok, State::term()} | diff --git a/lib/ssh/src/ssh_client_key.erl b/lib/ssh/src/ssh_client_key.erl new file mode 100644 index 0000000000..2c48884dc2 --- /dev/null +++ b/lib/ssh/src/ssh_client_key.erl @@ -0,0 +1,34 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2011-2012. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +-module(ssh_client_key). + +-include_lib("public_key/include/public_key.hrl"). +-include("ssh.hrl"). + +-callback is_host_key(Key :: public_key(), Host :: string(), + Algorithm :: 'ssh-rsa'| 'ssh-dsa'| atom(), Options :: proplists:proplist()) -> + boolean(). + +-callback user_key(Algorithm :: 'ssh-rsa'| 'ssh-dsa'| atom(), Options :: list()) -> + {ok, PrivateKey :: term()} | {error, string()}. + + +-callback add_host_key(Host :: string(), PublicKey :: term(), Options :: list()) -> + ok | {error, Error::term()}. diff --git a/lib/ssh/src/ssh_client_key_api.erl b/lib/ssh/src/ssh_client_key_api.erl new file mode 100644 index 0000000000..eed0b85f47 --- /dev/null +++ b/lib/ssh/src/ssh_client_key_api.erl @@ -0,0 +1,35 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2011-2012. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +-module(ssh_client_key_api). + +-include_lib("public_key/include/public_key.hrl"). +-include("ssh.hrl"). + +-callback is_host_key(PublicKey :: #'RSAPublicKey'{}| {integer(), #'Dss-Parms'{}}| term() , Host :: string(), + Algorithm :: 'ssh-rsa'| 'ssh-dss'| atom(), ConnectOptions :: proplists:proplist()) -> + boolean(). + +-callback user_key(Algorithm :: 'ssh-rsa'| 'ssh-dss'| atom(), ConnectOptions :: proplists:proplists()) -> + {ok, PrivateKey :: #'RSAPrivateKey'{}| #'DSAPrivateKey'{} | term()} | {error, string()}. + + +-callback add_host_key(Host :: string(), PublicKey :: #'RSAPublicKey'{}| {integer(), #'Dss-Parms'{}}| term(), + Options :: list()) -> + ok | {error, Error::term()}. diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index d8950a7b67..88b45111ff 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -35,7 +35,8 @@ -export([start_link/4, send/2, renegotiate/1, send_event/2, connection_info/3, - peer_address/1]). + peer_address/1, + renegotiate_data/1]). %% gen_fsm callbacks -export([hello/2, kexinit/2, key_exchange/2, new_keys/2, @@ -85,6 +86,8 @@ send(ConnectionHandler, Data) -> renegotiate(ConnectionHandler) -> send_all_state_event(ConnectionHandler, renegotiate). +renegotiate_data(ConnectionHandler) -> + send_all_state_event(ConnectionHandler, data_size). connection_info(ConnectionHandler, From, Options) -> send_all_state_event(ConnectionHandler, {info, From, Options}). @@ -500,7 +503,22 @@ handle_event(renegotiate, StateName, State) -> handle_event({info, From, Options}, StateName, #state{ssh_params = Ssh} = State) -> spawn(?MODULE, ssh_info_handler, [Options, Ssh, From]), {next_state, StateName, State}; - +handle_event(data_size, connected, #state{ssh_params = Ssh0} = State) -> + Sent = inet:getstat(State#state.socket, [send_oct]), + MaxSent = proplists:get_value(rekey_limit, State#state.opts, 1024000000), + case Sent >= MaxSent of + true -> + {KeyInitMsg, SshPacket, Ssh} = ssh_transport:key_exchange_init_msg(Ssh0), + send_msg(SshPacket, State), + {next_state, connected, + next_packet(State#state{ssh_params = Ssh, + key_exchange_init_msg = KeyInitMsg, + renegotiate = true})}; + _ -> + {next_state, connected, next_packet(State)} + end; +handle_event(data_size, StateName, State) -> + {next_state, StateName, State}; handle_event({unknown, Data}, StateName, State) -> Msg = #ssh_msg_unimplemented{sequence = Data}, send_msg(Msg, State), @@ -749,9 +767,9 @@ extract_algs([], NewList) -> lists:reverse(NewList); extract_algs([H|T], NewList) -> case H of - ssh_dsa -> + 'ssh-dss' -> extract_algs(T, ["ssh-dss"|NewList]); - ssh_rsa -> + 'ssh-rsa' -> extract_algs(T, ["ssh-rsa"|NewList]) end. available_host_key(KeyCb, "ssh-dss"= Alg, Opts) -> diff --git a/lib/ssh/src/ssh_connection_manager.erl b/lib/ssh/src/ssh_connection_manager.erl index 0c1eee5186..94a9ed505f 100644 --- a/lib/ssh/src/ssh_connection_manager.erl +++ b/lib/ssh/src/ssh_connection_manager.erl @@ -125,7 +125,8 @@ info(ConnectionManager, ChannelProcess) -> %% or amount of data sent counter! renegotiate(ConnectionManager) -> cast(ConnectionManager, renegotiate). - +renegotiate_data(ConnectionManager) -> + cast(ConnectionManager, renegotiate_data). connection_info(ConnectionManager, Options) -> call(ConnectionManager, {connection_info, Options}). @@ -481,7 +482,9 @@ handle_cast({global_request, _, _, _, _} = Request, State0) -> handle_cast(renegotiate, #state{connection = Pid} = State) -> ssh_connection_handler:renegotiate(Pid), {noreply, State}; - +handle_cast(renegotiate_data, #state{connection = Pid} = State) -> + ssh_connection_handler:renegotiate_data(Pid), + {noreply, State}; handle_cast({adjust_window, ChannelId, Bytes}, #state{connection = Pid, connection_state = #connection{channel_cache = Cache}} = State) -> @@ -520,6 +523,8 @@ handle_info({start_connection, server, Exec = proplists:get_value(exec, Options), CliSpec = proplists:get_value(ssh_cli, Options, {ssh_cli, [Shell]}), ssh_connection_handler:send_event(Connection, socket_control), + erlang:send_after(3600000, self(), rekey), + erlang:send_after(60000, self(), rekey_data), {noreply, State#state{connection = Connection, connection_state = CState#connection{address = Address, @@ -536,6 +541,8 @@ handle_info({start_connection, client, case (catch ssh_transport:connect(Parent, Address, Port, SocketOpts, Options)) of {ok, Connection} -> + erlang:send_after(60000, self(), rekey_data), + erlang:send_after(3600000, self(), rekey), {noreply, State#state{connection = Connection}}; Reason -> Pid ! {self(), not_connected, Reason}, @@ -568,8 +575,15 @@ handle_info({'DOWN', _Ref, process, ChannelPid, _Reason}, State) -> %%% So that terminate will be run when supervisor is shutdown handle_info({'EXIT', _Sup, Reason}, State) -> - {stop, Reason, State}. - + {stop, Reason, State}; +handle_info(rekey, State) -> + renegotiate(self()), + erlang:send_after(3600000, self(), rekey), + {noreply, State}; +handle_info(rekey_data, State) -> + renegotiate_data(self()), + erlang:send_after(60000, self(), rekey_data), + {noreply, State}. handle_password(Opts) -> handle_rsa_password(handle_dsa_password(handle_normal_password(Opts))). handle_normal_password(Opts) -> diff --git a/lib/ssh/src/ssh_file.erl b/lib/ssh/src/ssh_file.erl index a6b82a7a13..f115a32710 100644 --- a/lib/ssh/src/ssh_file.erl +++ b/lib/ssh/src/ssh_file.erl @@ -23,7 +23,8 @@ -module(ssh_file). --behaviour(ssh_key_api). +-behaviour(ssh_server_key_api). +-behaviour(ssh_client_key_api). -include_lib("public_key/include/public_key.hrl"). -include_lib("kernel/include/file.hrl"). @@ -34,7 +35,7 @@ user_key/2, is_host_key/4, add_host_key/3, - is_auth_key/4]). + is_auth_key/3]). -define(PERM_700, 8#700). @@ -53,8 +54,8 @@ host_key(Algorithm, Opts) -> decode(File, Password). -is_auth_key(Key, User, Alg, Opts) -> - case lookup_user_key(Key, User, Alg, Opts) of +is_auth_key(Key, User,Opts) -> + case lookup_user_key(Key, User, Opts) of {ok, Key} -> true; _ -> @@ -138,13 +139,13 @@ add_host_key(Host, Key, Opts) -> Error end. -lookup_user_key(Key, User, Alg, Opts) -> +lookup_user_key(Key, User, Opts) -> SshDir = ssh_dir({remoteuser,User}, Opts), - case lookup_user_key_f(Key, User, SshDir, Alg, "authorized_keys", Opts) of + case lookup_user_key_f(Key, User, SshDir, "authorized_keys", Opts) of {ok, Key} -> {ok, Key}; _ -> - lookup_user_key_f(Key, User, SshDir, Alg, "authorized_keys2", Opts) + lookup_user_key_f(Key, User, SshDir, "authorized_keys2", Opts) end. @@ -213,9 +214,9 @@ do_lookup_host_key(Host, Alg, Opts) -> Error -> Error end. -identity_key_filename("ssh-dss") -> +identity_key_filename('ssh-dss') -> "id_dsa"; -identity_key_filename("ssh-rsa") -> +identity_key_filename('ssh-rsa') -> "id_rsa". identity_pass_phrase("ssh-dss") -> @@ -261,9 +262,9 @@ host_name(Atom) when is_atom(Atom) -> host_name(List) -> List. -key_match(#'RSAPublicKey'{}, "ssh-rsa") -> +key_match(#'RSAPublicKey'{}, 'ssh-rsa') -> true; -key_match({_, #'Dss-Parms'{}}, "ssh-dss") -> +key_match({_, #'Dss-Parms'{}}, 'ssh-dss') -> true; key_match(_, _) -> false. @@ -272,11 +273,11 @@ add_key_fd(Fd, Host,Key) -> SshBin = public_key:ssh_encode([{Key, [{hostnames, [Host]}]}], known_hosts), file:write(Fd, SshBin). -lookup_user_key_f(_, _User, [], _Alg, _F, _Opts) -> +lookup_user_key_f(_, _User, [], _F, _Opts) -> {error, nouserdir}; -lookup_user_key_f(_, _User, nouserdir, _Alg, _F, _Opts) -> +lookup_user_key_f(_, _User, nouserdir, _F, _Opts) -> {error, nouserdir}; -lookup_user_key_f(Key, _User, Dir, _Alg, F, _Opts) -> +lookup_user_key_f(Key, _User, Dir, F, _Opts) -> FileName = filename:join(Dir, F), case file:open(FileName, [read, binary]) of {ok, Fd} -> diff --git a/lib/ssh/src/ssh_key_api.erl b/lib/ssh/src/ssh_key_api.erl deleted file mode 100644 index 8085c12e21..0000000000 --- a/lib/ssh/src/ssh_key_api.erl +++ /dev/null @@ -1,45 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2011-2012. All Rights Reserved. -%% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. -%% -%% %CopyrightEnd% -%% - --module(ssh_key_api). - --include_lib("public_key/include/public_key.hrl"). --include("ssh.hrl"). - --type ssh_algorithm() :: string(). --type file_error() :: file:posix() | badarg | system_limit | terminated. - --callback host_key(Algorithm :: ssh_algorithm(), Options :: list()) -> - {ok, [{public_key(), Attributes::list()}]} | public_key() - | {error, string()}. - --callback user_key(Algorithm :: ssh_algorithm(), Options :: list()) -> - {ok, [{public_key(), Attributes::list()}]} | public_key() - | {error, string()}. - --callback is_host_key(Key :: public_key(), PeerName :: string(), - Algorithm :: ssh_algorithm(), Options :: list()) -> - boolean(). - --callback add_host_key(Host :: string(), Key :: public_key(), Options :: list()) -> - ok | {error, file_error()}. - --callback is_auth_key(Key :: public_key(), User :: string(), - Algorithm :: ssh_algorithm(), Options :: list()) -> - boolean(). diff --git a/lib/ssh/src/ssh_server_key.erl b/lib/ssh/src/ssh_server_key.erl new file mode 100644 index 0000000000..8140114990 --- /dev/null +++ b/lib/ssh/src/ssh_server_key.erl @@ -0,0 +1,33 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2011-2012. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +-module(ssh_server_key). + +-include_lib("public_key/include/public_key.hrl"). +-include("ssh.hrl"). + +-type ssh_algorithm() :: string(). + +-callback host_key(Algorithm :: ssh_algorithm(), Options :: list()) -> + {ok, [{public_key(), Attributes::list()}]} | public_key() + | {error, string()}. + +-callback is_auth_key(Key :: public_key(), User :: string(), + Algorithm :: ssh_algorithm(), Options :: list()) -> + boolean(). diff --git a/lib/ssh/src/ssh_server_key_api.erl b/lib/ssh/src/ssh_server_key_api.erl new file mode 100644 index 0000000000..4fd660ecb5 --- /dev/null +++ b/lib/ssh/src/ssh_server_key_api.erl @@ -0,0 +1,30 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2011-2012. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +-module(ssh_server_key_api). + +-include_lib("public_key/include/public_key.hrl"). +-include("ssh.hrl"). + +-callback host_key(Algorithm :: 'ssh-rsa'| 'ssh-dss'| atom(), DaemonOptions :: proplists:proplist()) -> + {ok, PrivateKey :: #'RSAPrivateKey'{}| #'DSAPrivateKey'{} | term()} | {error, string()}. + +-callback is_auth_key(PublicKey :: #'RSAPublicKey'{}| {integer(), #'Dss-Parms'{}}| term(), + User :: string(), DaemonOptions :: proplists:proplist()) -> + boolean(). diff --git a/lib/ssh/src/ssh_sftpd.erl b/lib/ssh/src/ssh_sftpd.erl index ec7b76b0b3..c7e8373840 100644 --- a/lib/ssh/src/ssh_sftpd.erl +++ b/lib/ssh/src/ssh_sftpd.erl @@ -24,7 +24,7 @@ -module(ssh_sftpd). %%-behaviour(gen_server). --behaviour(ssh_channel). +-behaviour(ssh_subsystem). -include_lib("kernel/include/file.hrl"). @@ -36,7 +36,7 @@ -export([subsystem_spec/1, listen/1, listen/2, listen/3, stop/1]). --export([init/1, handle_ssh_msg/2, handle_msg/2, terminate/2, code_change/3]). +-export([init/1, handle_ssh_msg/2, handle_msg/2, terminate/2]). -record(state, { xf, % [{channel,ssh_xfer states}...] @@ -119,23 +119,13 @@ init(Options) -> {Root0, State0} end, MaxLength = proplists:get_value(max_files, Options, 0), - - Vsn = proplists:get_value(vsn, Options, 5), - + Vsn = proplists:get_value(sftpd_vsn, Options, 5), {ok, State#state{cwd = CWD, root = Root, max_files = MaxLength, handles = [], pending = <<>>, xf = #ssh_xfer{vsn = Vsn, ext = []}}}. %%-------------------------------------------------------------------- -%% Function: code_change(OldVsn, State, Extra) -> {ok, NewState} -%% Description: -%%-------------------------------------------------------------------- -code_change(_OldVsn, State, _Extra) -> - {ok, State}. - - -%%-------------------------------------------------------------------- %% Function: handle_ssh_msg(Args) -> {ok, State} | {stop, ChannelId, State} %% %% Description: Handles channel messages @@ -369,17 +359,21 @@ handle_op(?SSH_FXP_FSETSTAT, ReqId, <<?UINT32(HLen), BinHandle:HLen/binary, State0 end; handle_op(?SSH_FXP_REMOVE, ReqId, <<?UINT32(PLen), BPath:PLen/binary>>, - State0 = #state{file_handler = FileMod, file_state = FS0}) -> + State0 = #state{file_handler = FileMod, file_state = FS0, xf = #ssh_xfer{vsn = Vsn}}) -> Path = relate_file_name(BPath, State0), - %% case FileMod:is_dir(Path) of %% This version 6 we still have ver 5 - %% true -> - %% ssh_xfer:xf_send_status(State#state.xf, ReqId, - %% ?SSH_FX_FILE_IS_A_DIRECTORY); - %% false -> - {Status, FS1} = FileMod:delete(Path, FS0), - State1 = State0#state{file_state = FS1}, - send_status(Status, ReqId, State1); - %%end; + {IsDir, _FS1} = FileMod:is_dir(Path, FS0), + case IsDir of %% This version 6 we still have ver 5 + true when Vsn > 5 -> + ssh_xfer:xf_send_status(State0#state.xf, ReqId, + ?SSH_FX_FILE_IS_A_DIRECTORY, "File is a directory"); + true -> + ssh_xfer:xf_send_status(State0#state.xf, ReqId, + ?SSH_FX_FAILURE, "File is a directory"); + false -> + {Status, FS1} = FileMod:delete(Path, FS0), + State1 = State0#state{file_state = FS1}, + send_status(Status, ReqId, State1) + end; handle_op(?SSH_FXP_RMDIR, ReqId, <<?UINT32(PLen), BPath:PLen/binary>>, State0 = #state{file_handler = FileMod, file_state = FS0}) -> Path = relate_file_name(BPath, State0), @@ -637,31 +631,34 @@ open(Vsn, ReqId, Data, State) when Vsn >= 4 -> do_open(ReqId, State, Path, Flags). do_open(ReqId, State0, Path, Flags) -> - #state{file_handler = FileMod, file_state = FS0, root = Root} = State0, + #state{file_handler = FileMod, file_state = FS0, root = Root, xf = #ssh_xfer{vsn = Vsn}} = State0, XF = State0#state.xf, F = [binary | Flags], - %% case FileMod:is_dir(Path) of %% This is version 6 we still have 5 - %% true -> - %% ssh_xfer:xf_send_status(State#state.xf, ReqId, - %% ?SSH_FX_FILE_IS_A_DIRECTORY); - %% false -> - - AbsPath = case Root of - "" -> - Path; - _ -> - relate_file_name(Path, State0) - end, - - {Res, FS1} = FileMod:open(AbsPath, F, FS0), - State1 = State0#state{file_state = FS1}, - case Res of - {ok, IoDevice} -> - add_handle(State1, XF, ReqId, file, {Path,IoDevice}); - {error, Error} -> - ssh_xfer:xf_send_status(State1#state.xf, ReqId, - ssh_xfer:encode_erlang_status(Error)), - State1 + {IsDir, _FS1} = FileMod:is_dir(Path, FS0), + case IsDir of + true when Vsn > 5 -> + ssh_xfer:xf_send_status(State0#state.xf, ReqId, + ?SSH_FX_FILE_IS_A_DIRECTORY, "File is a directory"); + true -> + ssh_xfer:xf_send_status(State0#state.xf, ReqId, + ?SSH_FX_FAILURE, "File is a directory"); + false -> + AbsPath = case Root of + "" -> + Path; + _ -> + relate_file_name(Path, State0) + end, + {Res, FS1} = FileMod:open(AbsPath, F, FS0), + State1 = State0#state{file_state = FS1}, + case Res of + {ok, IoDevice} -> + add_handle(State1, XF, ReqId, file, {Path,IoDevice}); + {error, Error} -> + ssh_xfer:xf_send_status(State1#state.xf, ReqId, + ssh_xfer:encode_erlang_status(Error)), + State1 + end end. %% resolve all symlinks in a path diff --git a/lib/ssh/src/ssh_subsystem.erl b/lib/ssh/src/ssh_subsystem.erl new file mode 100644 index 0000000000..5a9fa32668 --- /dev/null +++ b/lib/ssh/src/ssh_subsystem.erl @@ -0,0 +1,47 @@ +-module(ssh_subsystem). + +%% API to special server side channel that can be pluged into the erlang ssh daemeon +-callback init(Args :: term()) -> + {ok, State :: term()} | {ok, State :: term(), timeout() | hibernate} | + {stop, Reason :: term()} | ignore. + +-callback terminate(Reason :: (normal | shutdown | {shutdown, term()} | + term()), + State :: term()) -> + term(). + +-callback handle_msg(Msg ::term(), State :: term()) -> + {noreply, NewState :: term()} | + {noreply, NewState :: term(), timeout() | hibernate} | + {stop, Reason :: term(), NewState :: term()}. + +-callback handle_ssh_msg({ssh_cm, ConnectionRef::term(), SshMsg::term()}, + State::term()) -> {ok, State::term()} | + {stop, ChannelId::integer(), + State::term()}. + +%%% API +-export([start/4, start/5, start_link/4, start_link/5, enter_loop/1]). + +%% gen_server callbacks +-export([init/1, terminate/2]). + +start(ConnectionManager, ChannelId, CallBack, CbInitArgs) -> + ssh_channel:start(ConnectionManager, ChannelId, CallBack, CbInitArgs, undefined). + +start(ConnectionManager, ChannelId, CallBack, CbInitArgs, Exec) -> + ssh_channel:start(ConnectionManager, ChannelId, CallBack, CbInitArgs, Exec). + +start_link(ConnectionManager, ChannelId, CallBack, CbInitArgs) -> + ssh_channel:start_link(ConnectionManager, ChannelId, CallBack, CbInitArgs, undefined). + +start_link(ConnectionManager, ChannelId, CallBack, CbInitArgs, Exec) -> + ssh_channel:start_link(ConnectionManager, ChannelId, CallBack, CbInitArgs, Exec). + +enter_loop(State) -> + ssh_channel:enter_loop(State). + +init(Args) -> + ssh_channel:init(Args). +terminate(Reason, State) -> + ssh_channel:terminate(Reason, State). diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 7f6e7d9946..1abb69921d 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -449,7 +449,7 @@ verify_host_key_rsa(SSH, K_S, H, H_SIG) -> false -> {error, bad_signature}; true -> - known_host_key(SSH, Public, "ssh-rsa") + known_host_key(SSH, Public, 'ssh-rsa') end; _ -> {error, bad_format} @@ -464,7 +464,7 @@ verify_host_key_dss(SSH, K_S, H, H_SIG) -> false -> {error, bad_signature}; true -> - known_host_key(SSH, Public, "ssh-dss") + known_host_key(SSH, Public, 'ssh-dss') end; _ -> {error, bad_host_key_format} diff --git a/lib/ssh/src/ssh_xfer.erl b/lib/ssh/src/ssh_xfer.erl index d5b6dd03d1..4dfd9ed8b0 100644 --- a/lib/ssh/src/ssh_xfer.erl +++ b/lib/ssh/src/ssh_xfer.erl @@ -383,6 +383,8 @@ decode_status(Status) -> ?SSH_FX_UNKNOWN_PRINCIPLE -> unknown_principle; ?SSH_FX_LOCK_CONFlICT -> lock_conflict; ?SSH_FX_NOT_A_DIRECTORY -> not_a_directory; + ?SSH_FX_FILE_IS_A_DIRECTORY -> file_is_a_directory; + ?SSH_FX_CANNOT_DELETE -> cannot_delete; _ -> {error,Status} end. @@ -392,6 +394,9 @@ encode_erlang_status(Status) -> eof -> ?SSH_FX_EOF; enoent -> ?SSH_FX_NO_SUCH_FILE; eacces -> ?SSH_FX_PERMISSION_DENIED; + eisdir -> ?SSH_FX_FILE_IS_A_DIRECTORY; + eperm -> ?SSH_FX_CANNOT_DELETE; + eexist -> ?SSH_FX_FILE_ALREADY_EXISTS; _ -> ?SSH_FX_FAILURE end. diff --git a/lib/ssh/src/ssh_xfer.hrl b/lib/ssh/src/ssh_xfer.hrl index c13950eb6e..0d85cf2094 100644 --- a/lib/ssh/src/ssh_xfer.hrl +++ b/lib/ssh/src/ssh_xfer.hrl @@ -58,7 +58,6 @@ %%% # SSH_FX_xxx %%% Description: Response packet types for file transfer protocol. %%%---------------------------------------------------------------------- - -define(SSH_FX_OK, 0). -define(SSH_FX_EOF, 1). -define(SSH_FX_NO_SUCH_FILE, 2). @@ -79,7 +78,18 @@ -define(SSH_FX_LOCK_CONFlICT, 17). -define(SSH_FX_DIR_NOT_EMPTY, 18). -define(SSH_FX_NOT_A_DIRECTORY, 19). +-define(SSH_FX_INVALID_FILENAME, 20). +-define(SSH_FX_LINK_LOOP, 21). +-define(SSH_FX_CANNOT_DELETE, 22). +-define(SSH_FX_INVALID_PARAMETER, 23). -define(SSH_FX_FILE_IS_A_DIRECTORY, 24). +-define(SSH_FX_BYTE_RANGE_LOCK_CONFLICT,25). +-define(SSH_FX_BYTE_RANGE_LOCK_REFUSED, 26). +-define(SSH_FX_DELETE_PENDING, 27). +-define(SSH_FX_FILE_CORRUPT, 28). +-define(SSH_FX_OWNER_INVALID, 29). +-define(SSH_FX_GROUP_INVALID, 30). +-define(SSH_FX_NO_MATCHING_BYTE_RANGE_LOCK,31). %%%---------------------------------------------------------------------- %%% # SSH_FILEXFER_xxx |