aboutsummaryrefslogtreecommitdiffstats
path: root/lib/ssh/src
diff options
context:
space:
mode:
authorErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
committerErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
commit84adefa331c4159d432d22840663c38f155cd4c1 (patch)
treebff9a9c66adda4df2106dfd0e5c053ab182a12bd /lib/ssh/src
downloadotp-84adefa331c4159d432d22840663c38f155cd4c1.tar.gz
otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.bz2
otp-84adefa331c4159d432d22840663c38f155cd4c1.zip
The R13B03 release.OTP_R13B03
Diffstat (limited to 'lib/ssh/src')
-rwxr-xr-xlib/ssh/src/DSS.asn120
-rw-r--r--lib/ssh/src/Makefile157
-rwxr-xr-xlib/ssh/src/PKCS-1.asn1116
-rw-r--r--lib/ssh/src/prebuild.skip2
-rw-r--r--lib/ssh/src/ssh.app.src48
-rw-r--r--lib/ssh/src/ssh.appup.src37
-rw-r--r--lib/ssh/src/ssh.erl339
-rw-r--r--lib/ssh/src/ssh.hrl180
-rw-r--r--lib/ssh/src/ssh_acceptor.erl115
-rw-r--r--lib/ssh/src/ssh_acceptor_sup.erl95
-rw-r--r--lib/ssh/src/ssh_app.erl34
-rw-r--r--lib/ssh/src/ssh_auth.erl423
-rw-r--r--lib/ssh/src/ssh_auth.hrl83
-rwxr-xr-xlib/ssh/src/ssh_bits.erl483
-rw-r--r--lib/ssh/src/ssh_channel.erl328
-rw-r--r--lib/ssh/src/ssh_channel_sup.erl54
-rw-r--r--lib/ssh/src/ssh_cli.erl500
-rwxr-xr-xlib/ssh/src/ssh_cm.erl237
-rwxr-xr-xlib/ssh/src/ssh_connect.hrl264
-rw-r--r--lib/ssh/src/ssh_connection.erl1366
-rw-r--r--lib/ssh/src/ssh_connection_controler.erl137
-rw-r--r--lib/ssh/src/ssh_connection_handler.erl879
-rw-r--r--lib/ssh/src/ssh_connection_manager.erl760
-rwxr-xr-xlib/ssh/src/ssh_dsa.erl95
-rwxr-xr-xlib/ssh/src/ssh_file.erl530
-rwxr-xr-xlib/ssh/src/ssh_io.erl79
-rwxr-xr-xlib/ssh/src/ssh_math.erl131
-rw-r--r--lib/ssh/src/ssh_no_io.erl39
-rwxr-xr-xlib/ssh/src/ssh_rsa.erl299
-rwxr-xr-xlib/ssh/src/ssh_sftp.erl1148
-rw-r--r--lib/ssh/src/ssh_sftpd.erl932
-rw-r--r--lib/ssh/src/ssh_sftpd_file.erl83
-rw-r--r--lib/ssh/src/ssh_sftpd_file_api.erl47
-rw-r--r--lib/ssh/src/ssh_shell.erl178
-rw-r--r--lib/ssh/src/ssh_ssh.erl65
-rw-r--r--lib/ssh/src/ssh_sshd.erl48
-rw-r--r--lib/ssh/src/ssh_subsystem_sup.erl109
-rw-r--r--lib/ssh/src/ssh_sup.erl101
-rw-r--r--lib/ssh/src/ssh_system_sup.erl160
-rw-r--r--lib/ssh/src/ssh_transport.erl1161
-rw-r--r--lib/ssh/src/ssh_transport.hrl235
-rwxr-xr-xlib/ssh/src/ssh_userauth.hrl77
-rw-r--r--lib/ssh/src/ssh_userreg.erl127
-rw-r--r--lib/ssh/src/ssh_xfer.erl925
-rwxr-xr-xlib/ssh/src/ssh_xfer.hrl251
-rw-r--r--lib/ssh/src/sshc_sup.erl65
-rw-r--r--lib/ssh/src/sshd_sup.erl111
47 files changed, 13653 insertions, 0 deletions
diff --git a/lib/ssh/src/DSS.asn1 b/lib/ssh/src/DSS.asn1
new file mode 100755
index 0000000000..77aca3808b
--- /dev/null
+++ b/lib/ssh/src/DSS.asn1
@@ -0,0 +1,20 @@
+DSS DEFINITIONS EXPLICIT TAGS ::=
+
+BEGIN
+
+-- EXPORTS ALL
+-- All types and values defined in this module are exported for use
+-- in other ASN.1 modules.
+
+DSAPrivateKey ::= SEQUENCE {
+ version INTEGER,
+ p INTEGER, -- p
+ q INTEGER, -- q
+ g INTEGER, -- q
+ y INTEGER, -- y
+ x INTEGER -- x
+}
+
+END
+
+
diff --git a/lib/ssh/src/Makefile b/lib/ssh/src/Makefile
new file mode 100644
index 0000000000..7abf06e52b
--- /dev/null
+++ b/lib/ssh/src/Makefile
@@ -0,0 +1,157 @@
+#
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB 2004-2009. 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%
+#
+
+#
+include $(ERL_TOP)/make/target.mk
+include $(ERL_TOP)/make/$(TARGET)/otp.mk
+
+# ----------------------------------------------------
+# Application version
+# ----------------------------------------------------
+include ../vsn.mk
+VSN=$(SSH_VSN)
+
+# ----------------------------------------------------
+# Release directory specification
+# ----------------------------------------------------
+RELSYSDIR = $(RELEASE_PATH)/lib/ssh-$(VSN)
+
+# ----------------------------------------------------
+# Common Macros
+# ----------------------------------------------------
+
+MODULES= \
+ ssh \
+ ssh_sup \
+ sshc_sup \
+ sshd_sup \
+ ssh_channel \
+ ssh_connection \
+ ssh_connection_handler \
+ ssh_connection_manager \
+ ssh_connection_controler \
+ ssh_shell \
+ ssh_system_sup \
+ ssh_subsystem_sup \
+ ssh_channel_sup \
+ ssh_acceptor_sup \
+ ssh_acceptor \
+ ssh_app \
+ ssh_auth\
+ ssh_bits \
+ ssh_cli \
+ ssh_cm \
+ ssh_dsa \
+ ssh_file \
+ ssh_io \
+ ssh_math \
+ ssh_no_io \
+ ssh_rsa \
+ ssh_sftp \
+ ssh_sftpd \
+ ssh_sftpd_file\
+ ssh_sftpd_file_api \
+ ssh_ssh \
+ ssh_sshd \
+ ssh_transport \
+ ssh_userreg \
+ ssh_xfer
+
+PUBLIC_HRL_FILES= ssh.hrl ssh_userauth.hrl ssh_xfer.hrl
+
+ERL_FILES= $(MODULES:%=%.erl) $(ASN_ERLS)
+
+ALL_MODULES= $(MODULES) $(ASN_MODULES)
+
+TARGET_FILES= $(ALL_MODULES:%=$(EBIN)/%.$(EMULATOR)) $(APP_TARGET) $(APPUP_TARGET)
+
+APP_FILE= ssh.app
+APPUP_FILE= ssh.appup
+
+APP_SRC= $(APP_FILE).src
+APP_TARGET= $(EBIN)/$(APP_FILE)
+
+APPUP_SRC= $(APPUP_FILE).src
+APPUP_TARGET= $(EBIN)/$(APPUP_FILE)
+
+ASN_MODULES = PKCS-1 DSS
+ASN_ASNS = $(ASN_MODULES:%=%.asn1)
+ASN_ERLS = $(ASN_MODULES:%=%.erl)
+ASN_HRLS = $(ASN_MODULES:%=%.hrl)
+ASN_DBS = $(ASN_MODULES:%=%.asn1db)
+ASN_TABLES = $(ASN_MODULES:%=%.table)
+
+ASN_FLAGS = -bber_bin +der +compact_bit_string +optimize +noobj +inline
+
+INTERNAL_HRL_FILES = $(ASN_HRLS) ssh_auth.hrl ssh_connect.hrl ssh_transport.hrl
+
+# ----------------------------------------------------
+# FLAGS
+# ----------------------------------------------------
+ERL_COMPILE_FLAGS += -pa$(EBIN)
+
+# ----------------------------------------------------
+# Targets
+# ----------------------------------------------------
+
+debug opt: $(TARGET_FILES)
+
+debug: ERLC_FLAGS += -Ddebug
+
+clean:
+ rm -f $(TARGET_FILES)
+ rm -f errs core *~
+ rm -f $(ASN_ERLS) $(ASN_HRLS) $(ASN_DBS)
+
+$(TARGET_FILES): ssh.hrl
+
+# $(EBIN)/ssh_sftpd_file.$(EMULATOR): ERLC_FLAGS += -pa$(EBIN)
+# $(EBIN)/ssh_sftpd_file.$(EMULATOR): $(EBIN)/ssh_sftpd_file_api.$(EMULATOR)
+
+$(APP_TARGET): $(APP_SRC) ../vsn.mk
+ sed -e 's;%VSN%;$(VSN);' $< > $@
+
+$(APPUP_TARGET): $(APPUP_SRC) ../vsn.mk
+ sed -e 's;%VSN%;$(VSN);' $< > $@
+
+%.hrl: %.asn1
+ erlc $(ASN_FLAGS) $<
+
+DSS.hrl DSS.erl: DSS.asn1
+PKCS-1.hrl PKCS-1.erl: PKCS-1.asn1
+
+$(EBIN)/ssh_file.$(EMULATOR): $(ASN_HRLS)
+
+docs:
+
+# ----------------------------------------------------
+# Release Target
+# ----------------------------------------------------
+include $(ERL_TOP)/make/otp_release_targets.mk
+
+release_spec: opt
+ $(INSTALL_DIR) $(RELSYSDIR)/src
+ $(INSTALL_DATA) $(INTERNAL_HRL_FILES) $(ERL_FILES) $(RELSYSDIR)/src
+ $(INSTALL_DIR) $(RELSYSDIR)/ebin
+ $(INSTALL_DATA) $(TARGET_FILES) $(RELSYSDIR)/ebin
+ $(INSTALL_DIR) $(RELSYSDIR)/include
+ $(INSTALL_DATA) $(PUBLIC_HRL_FILES) $(RELSYSDIR)/include
+
+release_docs_spec:
+
diff --git a/lib/ssh/src/PKCS-1.asn1 b/lib/ssh/src/PKCS-1.asn1
new file mode 100755
index 0000000000..e7d6b18c63
--- /dev/null
+++ b/lib/ssh/src/PKCS-1.asn1
@@ -0,0 +1,116 @@
+PKCS-1 {
+ iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs-1(1)
+ modules(0) pkcs-1(1)
+}
+
+-- $Revision: 1.1 $
+
+DEFINITIONS EXPLICIT TAGS ::=
+
+BEGIN
+
+-- IMPORTS id-sha256, id-sha384, id-sha512
+-- FROM NIST-SHA2 {
+-- joint-iso-itu-t(2) country(16) us(840) organization(1)
+-- gov(101) csor(3) nistalgorithm(4) modules(0) sha2(1)
+-- };
+
+pkcs-1 OBJECT IDENTIFIER ::= {
+ iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) 1
+}
+
+rsaEncryption OBJECT IDENTIFIER ::= { pkcs-1 1 }
+
+id-RSAES-OAEP OBJECT IDENTIFIER ::= { pkcs-1 7 }
+
+id-pSpecified OBJECT IDENTIFIER ::= { pkcs-1 9 }
+
+id-RSASSA-PSS OBJECT IDENTIFIER ::= { pkcs-1 10 }
+
+md2WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 2 }
+md5WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 4 }
+sha1WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 5 }
+sha256WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 11 }
+sha384WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 12 }
+sha512WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 13 }
+
+id-sha1 OBJECT IDENTIFIER ::= {
+ iso(1) identified-organization(3) oiw(14) secsig(3)
+ algorithms(2) 26
+}
+
+id-md2 OBJECT IDENTIFIER ::= {
+ iso(1) member-body(2) us(840) rsadsi(113549) digestAlgorithm(2) 2
+}
+
+id-md5 OBJECT IDENTIFIER ::= {
+ iso(1) member-body(2) us(840) rsadsi(113549) digestAlgorithm(2) 5
+}
+
+id-mgf1 OBJECT IDENTIFIER ::= { pkcs-1 8 }
+
+
+RSAPublicKey ::= SEQUENCE {
+ modulus INTEGER, -- n
+ publicExponent INTEGER -- e
+}
+
+RSAPrivateKey ::= SEQUENCE {
+ version Version,
+ modulus INTEGER, -- n
+ publicExponent INTEGER, -- e
+ privateExponent INTEGER, -- d
+ prime1 INTEGER, -- p
+ prime2 INTEGER, -- q
+ exponent1 INTEGER, -- d mod (p-1)
+ exponent2 INTEGER, -- d mod (q-1)
+ coefficient INTEGER, -- (inverse of q) mod p
+ otherPrimeInfos OtherPrimeInfos OPTIONAL
+}
+
+Version ::= INTEGER { two-prime(0), multi(1) }
+ (CONSTRAINED BY {
+ -- version must be multi if otherPrimeInfos present --
+ })
+
+OtherPrimeInfos ::= SEQUENCE SIZE(1..MAX) OF OtherPrimeInfo
+
+OtherPrimeInfo ::= SEQUENCE {
+ prime INTEGER, -- ri
+ exponent INTEGER, -- di
+ coefficient INTEGER -- ti
+}
+
+Algorithm ::= SEQUENCE {
+ algorithm OBJECT IDENTIFIER,
+ parameters ANY DEFINED BY algorithm OPTIONAL
+}
+
+AlgorithmNull ::= SEQUENCE {
+ algorithm OBJECT IDENTIFIER,
+ parameters NULL
+}
+
+
+RSASSA-PSS-params ::= SEQUENCE {
+ hashAlgorithm [0] Algorithm, -- DEFAULT sha1,
+ maskGenAlgorithm [1] Algorithm, -- DEFAULT mgf1SHA1,
+ saltLength [2] INTEGER DEFAULT 20,
+ trailerField [3] TrailerField DEFAULT trailerFieldBC
+}
+
+TrailerField ::= INTEGER { trailerFieldBC(1) }
+
+DigestInfo ::= SEQUENCE {
+ digestAlgorithm Algorithm,
+ digest OCTET STRING
+}
+
+DigestInfoNull ::= SEQUENCE {
+ digestAlgorithm AlgorithmNull,
+ digest OCTET STRING
+}
+
+
+END -- PKCS1Definitions
+
diff --git a/lib/ssh/src/prebuild.skip b/lib/ssh/src/prebuild.skip
new file mode 100644
index 0000000000..1d7552d98d
--- /dev/null
+++ b/lib/ssh/src/prebuild.skip
@@ -0,0 +1,2 @@
+DSS.asn1db
+PKCS-1.asn1db
diff --git a/lib/ssh/src/ssh.app.src b/lib/ssh/src/ssh.app.src
new file mode 100644
index 0000000000..9319f39591
--- /dev/null
+++ b/lib/ssh/src/ssh.app.src
@@ -0,0 +1,48 @@
+%%% This is an -*- erlang -*- file.
+
+{application, ssh,
+ [{description, "SSH-2 for Erlang/OTP"},
+ {vsn, "%VSN%"},
+ {modules, ['DSS',
+ 'PKCS-1',
+ ssh,
+ ssh_app,
+ ssh_acceptor,
+ ssh_acceptor_sup,
+ ssh_auth,
+ ssh_bits,
+ ssh_cli,
+ ssh_channel,
+ ssh_channel_sup,
+ ssh_cm,
+ ssh_connection,
+ ssh_connection_handler,
+ ssh_connection_manager,
+ ssh_connection_controler,
+ ssh_shell,
+ sshc_sup,
+ sshd_sup,
+ ssh_dsa,
+ ssh_file,
+ ssh_io,
+ ssh_math,
+ ssh_no_io,
+ ssh_rsa,
+ ssh_sftp,
+ ssh_sftpd,
+ ssh_sftpd_file,
+ ssh_sftpd_file_api,
+ ssh_ssh,
+ ssh_sshd,
+ ssh_subsystem_sup,
+ ssh_sup,
+ ssh_system_sup,
+ ssh_transport,
+ ssh_userreg,
+ ssh_xfer]},
+ {registered, []},
+ {applications, [kernel, stdlib, crypto]},
+ {env, []},
+ {mod, {ssh_app, []}}]}.
+
+
diff --git a/lib/ssh/src/ssh.appup.src b/lib/ssh/src/ssh.appup.src
new file mode 100644
index 0000000000..3bf772b42b
--- /dev/null
+++ b/lib/ssh/src/ssh.appup.src
@@ -0,0 +1,37 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2004-2009. 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%
+%%
+
+{"%VSN%",
+ [
+ {"1.1.6", [{restart_application, ssh}]},
+ {"1.1.5", [{restart_application, ssh}]},
+ {"1.1.4", [{restart_application, ssh}]},
+ {"1.1.3", [{restart_application, ssh}]},
+ {"1.1.2", [{restart_application, ssh}]}
+ ],
+ [
+ {"1.1.6", [{restart_application, ssh}]},
+ {"1.1.5", [{restart_application, ssh}]},
+ {"1.1.4", [{restart_application, ssh}]},
+ {"1.1.3", [{restart_application, ssh}]},
+ {"1.1.2", [{restart_application, ssh}]}
+ ]
+}.
+
+
diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl
new file mode 100644
index 0000000000..f9a986a8b6
--- /dev/null
+++ b/lib/ssh/src/ssh.erl
@@ -0,0 +1,339 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2004-2009. 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).
+
+-include("ssh.hrl").
+-include("ssh_connect.hrl").
+
+-export([start/0, start/1, stop/0, connect/3, close/1, connection_info/2,
+ channel_info/3,
+ daemon/1, daemon/2, daemon/3,
+ stop_listener/1, stop_listener/2, stop_daemon/1, stop_daemon/2,
+ shell/1, shell/2, shell/3]).
+
+%%--------------------------------------------------------------------
+%% Function: start([, Type]) -> ok
+%%
+%% Type = permanent | transient | temporary
+%%
+%% Description: Starts the inets application. Default type
+%% is temporary. see application(3)
+%%--------------------------------------------------------------------
+start() ->
+ application:start(ssh).
+
+start(Type) ->
+ application:start(ssh, Type).
+
+%%--------------------------------------------------------------------
+%% Function: stop() -> ok
+%%
+%% Description: Stops the inets application.
+%%--------------------------------------------------------------------
+stop() ->
+ application:stop(ssh).
+
+%%--------------------------------------------------------------------
+%% Function: connect(Host, Port, Options) ->
+%% connect(Host, Port, Options, Timeout -> ConnectionRef | {error, Reason}
+%%
+%% Host - string()
+%% Port - integer()
+%% Options - [{Option, Value}]
+%% Timeout - infinity | integer().
+%%
+%% Description: Starts an ssh connection.
+%%--------------------------------------------------------------------
+connect(Host, Port, Options) ->
+ connect(Host, Port, Options, infinity).
+connect(Host, Port, Options, Timeout) ->
+ {SocketOpts, Opts} = handle_options(Options),
+ DisableIpv6 = proplists:get_value(ip_v6_disabled, Opts, false),
+ Inet = inetopt(DisableIpv6),
+ try sshc_sup:start_child([[{address, Host}, {port, Port},
+ {role, client},
+ {channel_pid, self()},
+ {socket_opts, [Inet | SocketOpts]},
+ {ssh_opts, [{host, Host}| Opts]}]]) of
+ {ok, ConnectionSup} ->
+ {ok, Manager} =
+ ssh_connection_controler:connection_manager(ConnectionSup),
+ MRef = erlang:monitor(process, Manager),
+ receive
+ {Manager, is_connected} ->
+ do_demonitor(MRef, Manager),
+ {ok, Manager};
+ %% When the connection fails
+ %% ssh_connection_sup:connection_manager
+ %% might return undefined as the connection manager
+ %% could allready have terminated, so we will not
+ %% match the Manager in this case
+ {_, not_connected, {error, Reason}} ->
+ do_demonitor(MRef, Manager),
+ {error, Reason};
+ {_, not_connected, Other} ->
+ do_demonitor(MRef, Manager),
+ {error, Other};
+ {'DOWN', MRef, _, Manager, Reason} when is_pid(Manager) ->
+ receive %% Clear EXIT message from queue
+ {'EXIT', Manager, _What} ->
+ {error, Reason}
+ after 0 ->
+ {error, Reason}
+ end
+ after Timeout ->
+ do_demonitor(MRef, Manager),
+ ssh_connection_manager:stop(Manager),
+ {error, timeout}
+ end
+ catch
+ exit:{noproc, _} ->
+ {error, ssh_not_started}
+ end.
+
+do_demonitor(MRef, Manager) ->
+ erlang:demonitor(MRef),
+ receive
+ {'DOWN', MRef, _, Manager, _} ->
+ ok
+ after 0 ->
+ ok
+ end.
+
+
+%%--------------------------------------------------------------------
+%% Function: close(ConnectionRef) -> ok
+%%
+%% Description: Closes an ssh connection.
+%%--------------------------------------------------------------------
+close(ConnectionRef) ->
+ ssh_connection_manager:stop(ConnectionRef).
+
+%%--------------------------------------------------------------------
+%% Function: connection_info(ConnectionRef) -> [{Option, Value}]
+%%
+%% Description: Retrieves information about a connection.
+%%--------------------------------------------------------------------
+connection_info(ConnectionRef, Options) ->
+ ssh_connection_manager:connection_info(ConnectionRef, Options).
+
+%%--------------------------------------------------------------------
+%% Function: channel_info(ConnectionRef) -> [{Option, Value}]
+%%
+%% Description: Retrieves information about a connection.
+%%--------------------------------------------------------------------
+channel_info(ConnectionRef, ChannelId, Options) ->
+ ssh_connection_manager:channel_info(ConnectionRef, ChannelId, Options).
+
+%%--------------------------------------------------------------------
+%% Function: daemon(Port) ->
+%% daemon(Port, Options) ->
+%% daemon(Address, Port, Options) -> SshSystemRef
+%%
+%% Description: Starts a server listening for SSH connections
+%% on the given port.
+%%--------------------------------------------------------------------
+daemon(Port) ->
+ daemon(Port, []).
+
+daemon(Port, Options) ->
+ daemon(any, Port, Options).
+
+daemon(HostAddr, Port, Options0) ->
+ Options1 = case proplists:get_value(shell, Options0) of
+ undefined ->
+ [{shell, {shell, start, []}} | Options0];
+ _ ->
+ Options0
+ end,
+ DisableIpv6 = proplists:get_value(ip_v6_disabled, Options0, false),
+ {Host, Inet, Options} = case HostAddr of
+ any ->
+ {ok, Host0} = inet:gethostname(),
+ {Host0, inetopt(DisableIpv6), Options1};
+ {_,_,_,_} ->
+ {HostAddr, inet,
+ [{ip, HostAddr} | Options1]};
+ {_,_,_,_,_,_,_,_} ->
+ {HostAddr, inet6,
+ [{ip, HostAddr} | Options1]}
+ end,
+ start_daemon(Host, Port, [{role, server} | Options], Inet).
+
+%%--------------------------------------------------------------------
+%% Function: stop_listener(SysRef) -> ok
+%% stop_listener(Address, Port) -> ok
+%%
+%%
+%% Description: Stops the listener, but leaves
+%% existing connections started by the listener up and running.
+%%--------------------------------------------------------------------
+stop_listener(SysSup) ->
+ ssh_system_sup:stop_listener(SysSup).
+stop_listener(Address, Port) ->
+ ssh_system_sup:stop_listener(Address, Port).
+
+%%--------------------------------------------------------------------
+%% Function: stop_daemon(SysRef) -> ok
+%%% stop_daemon(Address, Port) -> ok
+%%
+%%
+%% Description: Stops the listener and all connections started by
+%% the listener.
+%%--------------------------------------------------------------------
+stop_daemon(SysSup) ->
+ ssh_system_sup:stop_system(SysSup).
+stop_daemon(Address, Port) ->
+ ssh_system_sup:stop_system(Address, Port).
+
+%%--------------------------------------------------------------------
+%% Function: shell(Host [,Port,Options]) -> {ok, ConnectionRef} |
+%% {error, Reason}
+%%
+%% Host = string()
+%% Port = integer()
+%% Options = [{Option, Value}]
+%%
+%% Description: Starts an interactive shell to an SSH server on the
+%% given <Host>. The function waits for user input,
+%% and will not return until the remote shell is ended.(e.g. on
+%% exit from the shell)
+%%--------------------------------------------------------------------
+shell(Host) ->
+ shell(Host, ?SSH_DEFAULT_PORT, []).
+shell(Host, Options) ->
+ shell(Host, ?SSH_DEFAULT_PORT, Options).
+shell(Host, Port, Options) ->
+ case connect(Host, Port, Options) of
+ {ok, ConnectionRef} ->
+ case ssh_connection:session_channel(ConnectionRef, infinity) of
+ {ok,ChannelId} ->
+ Args = [{channel_cb, ssh_shell},
+ {init_args,[ConnectionRef, ChannelId]},
+ {cm, ConnectionRef}, {channel_id, ChannelId}],
+ {ok, State} = ssh_channel:init([Args]),
+ ssh_channel:enter_loop(State);
+ Error ->
+ Error
+ end;
+ Error ->
+ Error
+ end.
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+start_daemon(Host, Port, Options, Inet) ->
+ {SocketOpts, Opts} = handle_options(Options),
+ case ssh_system_sup:system_supervisor(Host, Port) of
+ undefined ->
+ %% TODO: 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},
+ {port, Port}, {role, server},
+ {socket_opts, [Inet | SocketOpts]},
+ {ssh_opts, Opts}]) of
+ {ok, SysSup} ->
+ {ok, SysSup};
+ {error, {already_started, _}} ->
+ {error, eaddrinuse}
+ catch
+ exit:{noproc, _} ->
+ {error, ssh_not_started}
+ end;
+ Sup ->
+ case ssh_system_sup:restart_acceptor(Host, Port) of
+ {ok, _} ->
+ {ok, Sup};
+ _ ->
+ {error, eaddrinuse}
+ end
+ end.
+
+handle_options(Opts) ->
+ handle_options(proplists:unfold(Opts), [], []).
+handle_options([], SockOpts, Opts) ->
+ {SockOpts, Opts};
+%% TODO: Could do some type checks here on plain ssh-opts
+handle_options([{system_dir, _} = Opt | Rest], SockOpts, Opts) ->
+ handle_options(Rest, SockOpts, [Opt | Opts]);
+handle_options([{user_dir, _} = Opt | Rest], SockOpts, Opts) ->
+ handle_options(Rest, SockOpts, [Opt | Opts]);
+handle_options([{user_dir_fun, _} = Opt | Rest], SockOpts, Opts) ->
+ handle_options(Rest, SockOpts, [Opt | Opts]);
+handle_options([{silently_accept_hosts, _} = Opt | Rest], SockOpts, Opts) ->
+ handle_options(Rest, SockOpts, [Opt | Opts]);
+handle_options([{user_interaction, _} = Opt | Rest], SockOpts, Opts) ->
+ handle_options(Rest, SockOpts, [Opt | Opts]);
+handle_options([{public_key_alg, _} = Opt | Rest], SockOpts, Opts) ->
+ handle_options(Rest, SockOpts, [Opt | Opts]);
+handle_options([{connect_timeout, _} = Opt | Rest], SockOpts, Opts) ->
+ handle_options(Rest, SockOpts, [Opt | Opts]);
+handle_options([{user, _} = Opt | Rest], SockOpts, Opts) ->
+ handle_options(Rest, SockOpts, [Opt | Opts]);
+handle_options([{password, _} = Opt | Rest], SockOpts, Opts) ->
+ handle_options(Rest, SockOpts, [Opt | Opts]);
+handle_options([{user_passwords, _} = Opt | Rest], SockOpts, Opts) ->
+ handle_options(Rest, SockOpts, [Opt | Opts]);
+handle_options([{pwdfun, _} = Opt | Rest], SockOpts, Opts) ->
+ handle_options(Rest, SockOpts, [Opt | Opts]);
+handle_options([{user_auth, _} = Opt | Rest], SockOpts, Opts) ->
+ handle_options(Rest, SockOpts, [Opt | Opts]);
+handle_options([{key_cb, _} = Opt | Rest], SockOpts, Opts) ->
+ handle_options(Rest, SockOpts, [Opt | Opts]);
+handle_options([{role, _} = Opt | Rest], SockOpts, Opts) ->
+ handle_options(Rest, SockOpts, [Opt | Opts]);
+handle_options([{channel, _} = Opt | Rest], SockOpts, Opts) ->
+ handle_options(Rest, SockOpts, [Opt | Opts]);
+handle_options([{compression, _} = Opt | Rest], SockOpts, Opts) ->
+ handle_options(Rest, SockOpts, [Opt | Opts]);
+handle_options([{allow_user_interaction, _} = Opt | Rest], SockOpts, Opts) ->
+ handle_options(Rest, SockOpts, [Opt | Opts]);
+handle_options([{infofun, _} = Opt | Rest], SockOpts, Opts) ->
+ handle_options(Rest, SockOpts, [Opt | Opts]);
+handle_options([{connectfun, _} = Opt | Rest], SockOpts, Opts) ->
+ handle_options(Rest, SockOpts, [Opt | Opts]);
+handle_options([{disconnectfun , _} = Opt | Rest], SockOpts, Opts) ->
+ handle_options(Rest, SockOpts, [Opt | Opts]);
+handle_options([{failfun, _} = Opt | Rest], SockOpts, Opts) ->
+ handle_options(Rest, SockOpts, [Opt | Opts]);
+handle_options([{ip_v6_disabled, _} = Opt | Rest], SockOpts, Opts) ->
+ handle_options(Rest, SockOpts, [Opt | Opts]);
+handle_options([{ip, _} = Opt | Rest], SockOpts, Opts) ->
+ handle_options(Rest, [Opt |SockOpts], Opts);
+handle_options([{ifaddr, _} = Opt | Rest], SockOpts, Opts) ->
+ handle_options(Rest, [Opt |SockOpts], Opts);
+handle_options([{fd, _} = Opt | Rest], SockOpts, Opts) ->
+ handle_options(Rest, [Opt | SockOpts], Opts);
+handle_options([{nodelay, _} = Opt | Rest], SockOpts, Opts) ->
+ handle_options(Rest, [Opt | SockOpts], Opts);
+handle_options([Opt | Rest], SockOpts, Opts) ->
+ handle_options(Rest, SockOpts, [Opt | Opts]).
+
+inetopt(true) ->
+ inet6;
+
+inetopt(false) ->
+ inet.
+
+
diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl
new file mode 100644
index 0000000000..0e4285295c
--- /dev/null
+++ b/lib/ssh/src/ssh.hrl
@@ -0,0 +1,180 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2004-2009. 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%
+%%
+
+%%
+
+%%
+%% SSH definitions
+%%
+
+-ifndef(SSH_HRL).
+-define(SSH_HRL, 1).
+
+-define(SSH_DEFAULT_PORT, 22).
+-define(SSH_MAX_PACKET_SIZE, (256*1024)).
+-define(SSH_LENGHT_INDICATOR_SIZE, 4).
+
+-define(FALSE, 0).
+-define(TRUE, 1).
+%% basic binary constructors
+-define(BOOLEAN(X), X:8/unsigned-big-integer).
+-define(BYTE(X), X:8/unsigned-big-integer).
+-define(UINT16(X), X:16/unsigned-big-integer).
+-define(UINT32(X), X:32/unsigned-big-integer).
+-define(UINT64(X), X:64/unsigned-big-integer).
+-define(STRING(X), ?UINT32((size(X))), (X)/binary).
+
+%% building macros
+-define(boolean(X),
+ case X of
+ true -> <<?BOOLEAN(1)>>;
+ false -> (<<?BOOLEAN(0)>>)
+ end).
+
+-define(byte(X), << ?BYTE(X) >> ).
+-define(uint16(X), << ?UINT16(X) >> ).
+-define(uint32(X), << ?UINT32(X) >> ).
+-define(uint64(X), << ?UINT64(X) >> ).
+-define(string(X), << ?STRING(list_to_binary(X)) >> ).
+-define(binary(X), << ?STRING(X) >>).
+
+-ifdef(debug).
+-define(dbg(Debug, Fmt, As),
+ case (Debug) of
+ true ->
+ io:format([$# | (Fmt)], (As));
+ _ ->
+ ok
+ end).
+-else.
+-define(dbg(Debug, Fmt, As), ok).
+-endif.
+
+-define(SSH_CIPHER_NONE, 0).
+-define(SSH_CIPHER_3DES, 3).
+-define(SSH_CIPHER_AUTHFILE, ?SSH_CIPHER_3DES).
+
+-record(ssh,
+ {
+ %%state, %% what it's waiting for
+
+ role, %% client | server
+ peer, %% string version of peer address
+
+ c_vsn, %% client version {Major,Minor}
+ s_vsn, %% server version {Major,Minor}
+
+ c_version, %% client version string
+ s_version, %% server version string
+
+ c_keyinit, %% binary payload of kexinit packet
+ s_keyinit, %% binary payload of kexinit packet
+
+ algorithms, %% #alg{}
+
+ kex, %% key exchange algorithm
+ hkey, %% host key algorithm
+ key_cb, %% Private/Public key callback module
+ io_cb, %% Interaction callback module
+
+ send_mac = none, %% send MAC algorithm
+ send_mac_key, %% key used in send MAC algorithm
+ send_mac_size = 0,
+
+ recv_mac = none, %% recv MAC algorithm
+ recv_mac_key, %% key used in recv MAC algorithm
+ recv_mac_size = 0,
+
+ encrypt = none, %% encrypt algorithm
+ encrypt_keys, %% encrypt keys
+ encrypt_block_size = 8,
+ encrypt_ctx,
+
+ decrypt = none, %% decrypt algorithm
+ decrypt_keys, %% decrypt keys
+ decrypt_block_size = 8,
+ decrypt_ctx, %% Decryption context
+
+ compress = none,
+ compress_ctx,
+ decompress = none,
+ decompress_ctx,
+
+ c_lng=none, %% client to server languages
+ s_lng=none, %% server to client languages
+
+ user_ack = true, %% client
+ timeout = infinity,
+
+ shared_secret, %% K from key exchange
+ exchanged_hash, %% H from key exchange
+ session_id, %% same as FIRST exchanged_hash
+
+ opts = [],
+ send_sequence = 0,
+ recv_sequence = 0,
+ keyex_key,
+ keyex_info,
+
+ %% User auth
+ user,
+ service,
+ userauth_quiet_mode, % boolean()
+ userauth_supported_methods , %
+ userauth_methods,
+ userauth_preference
+ }).
+
+-record(alg,
+ {
+ kex,
+ hkey,
+ send_mac,
+ recv_mac,
+ encrypt,
+ decrypt,
+ compress,
+ decompress,
+ c_lng,
+ s_lng
+ }).
+
+-record(ssh_key,
+ {
+ type,
+ public,
+ private,
+ comment = ""
+ }).
+
+-record(ssh_pty, {term = "", % e.g. "xterm"
+ width = 80,
+ height = 25,
+ pixel_width = 1024,
+ pixel_height = 768,
+ modes = <<>>}).
+
+%% assertion macro
+-define(ssh_assert(Expr, Reason),
+ case Expr of
+ true -> ok;
+ _ -> exit(Reason)
+ end).
+
+-endif. % SSH_HRL defined
diff --git a/lib/ssh/src/ssh_acceptor.erl b/lib/ssh/src/ssh_acceptor.erl
new file mode 100644
index 0000000000..d19fee14e1
--- /dev/null
+++ b/lib/ssh/src/ssh_acceptor.erl
@@ -0,0 +1,115 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2009. 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_acceptor).
+
+%% Internal application API
+-export([start_link/5]).
+
+%% spawn export
+%% TODO: system messages
+-export([acceptor_init/6, acceptor_loop/6]).
+
+-define(SLEEP_TIME, 200).
+
+%%====================================================================
+%% Internal application API
+%%====================================================================
+start_link(Port, Address, SockOpts, Opts, AcceptTimeout) ->
+ Args = [self(), Port, Address, SockOpts, Opts, AcceptTimeout],
+ proc_lib:start_link(?MODULE, acceptor_init, Args).
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+acceptor_init(Parent, Port, Address, SockOpts, Opts, AcceptTimeout) ->
+ {_, Callback, _} =
+ proplists:get_value(transport, Opts, {tcp, gen_tcp, tcp_closed}),
+ case (catch do_socket_listen(Callback, Port, SockOpts)) of
+ {ok, ListenSocket} ->
+ proc_lib:init_ack(Parent, {ok, self()}),
+ acceptor_loop(Callback,
+ Port, Address, Opts, ListenSocket, AcceptTimeout);
+ Error ->
+ proc_lib:init_ack(Parent, Error),
+ error
+ end.
+
+do_socket_listen(Callback, Port, Opts) ->
+ case Callback:listen(Port, Opts) of
+ {error, eafnosupport} ->
+ Callback:listen(Port, lists:delete(inet6, Opts));
+ Other ->
+ Other
+ end.
+
+acceptor_loop(Callback, Port, Address, Opts, ListenSocket, AcceptTimeout) ->
+ case (catch Callback:accept(ListenSocket, AcceptTimeout)) of
+ {ok, Socket} ->
+ handle_connection(Callback, Address, Port, Opts, Socket),
+ ?MODULE:acceptor_loop(Callback, Port, Address, Opts,
+ ListenSocket, AcceptTimeout);
+ {error, Reason} ->
+ handle_error(Reason),
+ ?MODULE:acceptor_loop(Callback, Port, Address, Opts,
+ ListenSocket, AcceptTimeout);
+ {'EXIT', Reason} ->
+ handle_error(Reason),
+ ?MODULE:acceptor_loop(Callback, Port, Address, Opts,
+ ListenSocket, AcceptTimeout)
+ end.
+
+handle_connection(Callback, Address, Port, Options, Socket) ->
+ SystemSup = ssh_system_sup:system_supervisor(Address, Port),
+ ssh_system_sup:start_subsystem(SystemSup, Options),
+ ConnectionSup = ssh_system_sup:connection_supervisor(SystemSup),
+ {ok, Pid} =
+ ssh_connection_controler:start_manager_child(ConnectionSup,
+ [server, Socket, Options]),
+ Callback:controlling_process(Socket, Pid),
+ SshOpts = proplists:get_value(ssh_opts, Options),
+ Pid ! {start_connection, server, [Address, Port, Socket, SshOpts]}.
+
+handle_error(timeout) ->
+ ok;
+
+handle_error(enfile) ->
+ %% Out of sockets...
+ timer:sleep(?SLEEP_TIME);
+
+handle_error(emfile) ->
+ %% Too many open files -> Out of sockets...
+ timer:sleep(?SLEEP_TIME);
+
+handle_error(closed) ->
+ error_logger:info_report("The ssh accept socket was closed by "
+ "a third party. "
+ "This will not have an impact on ssh "
+ "that will open a new accept socket and "
+ "go on as nothing happened. It does however "
+ "indicate that some other software is behaving "
+ "badly."),
+ exit(normal);
+
+handle_error(Reason) ->
+ String = lists:flatten(io_lib:format("Accept error: ~p", [Reason])),
+ error_logger:error_report(String),
+ exit({accept_failed, String}).
diff --git a/lib/ssh/src/ssh_acceptor_sup.erl b/lib/ssh/src/ssh_acceptor_sup.erl
new file mode 100644
index 0000000000..707e3d3a5e
--- /dev/null
+++ b/lib/ssh/src/ssh_acceptor_sup.erl
@@ -0,0 +1,95 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2009. 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%
+%%
+
+%%
+%%----------------------------------------------------------------------
+%% Purpose: The acceptor supervisor for ssh servers hangs under
+%% ssh_system_sup.
+%%----------------------------------------------------------------------
+
+-module(ssh_acceptor_sup).
+-behaviour(supervisor).
+
+-export([start_link/1, start_child/2, stop_child/2]).
+
+%% Supervisor callback
+-export([init/1]).
+
+-define(DEFAULT_TIMEOUT, 50000).
+
+%%%=========================================================================
+%%% API
+%%%=========================================================================
+start_link(Servers) ->
+ supervisor:start_link(?MODULE, [Servers]).
+
+start_child(AccSup, ServerOpts) ->
+ Spec = child_spec(ServerOpts),
+ case supervisor:start_child(AccSup, Spec) of
+ {error, already_present} ->
+ Address = proplists:get_value(address, ServerOpts),
+ Port = proplists:get_value(port, ServerOpts),
+ Name = id(Address, Port),
+ supervisor:delete_child(?MODULE, Name),
+ supervisor:start_child(AccSup, Spec);
+ Reply ->
+ Reply
+ end.
+
+stop_child(Address, Port) ->
+ Name = id(Address, Port),
+ case supervisor:terminate_child(?MODULE, Name) of
+ ok ->
+ supervisor:delete_child(?MODULE, Name);
+ Error ->
+ Error
+ end.
+
+%%%=========================================================================
+%%% Supervisor callback
+%%%=========================================================================
+init([ServerOpts]) ->
+ RestartStrategy = one_for_one,
+ MaxR = 10,
+ MaxT = 3600,
+ Children = [child_spec(ServerOpts)],
+ {ok, {{RestartStrategy, MaxR, MaxT}, Children}}.
+
+%%%=========================================================================
+%%% Internal functions
+%%%=========================================================================
+child_spec(ServerOpts) ->
+ Address = proplists:get_value(address, ServerOpts),
+ Port = proplists:get_value(port, ServerOpts),
+ Timeout = proplists:get_value(timeout, ServerOpts, ?DEFAULT_TIMEOUT),
+ Name = id(Address, Port),
+ SocketOpts = proplists:get_value(socket_opts, ServerOpts),
+ StartFunc = {ssh_acceptor, start_link, [Port, Address,
+ [{active, false},
+ {reuseaddr, true}] ++ SocketOpts,
+ ServerOpts, Timeout]},
+ Restart = permanent,
+ Shutdown = 3600,
+ Modules = [ssh_acceptor],
+ Type = worker,
+ {Name, StartFunc, Restart, Shutdown, Type, Modules}.
+
+id(Address, Port) ->
+ {ssh_acceptor_sup, Address, Port}.
+
diff --git a/lib/ssh/src/ssh_app.erl b/lib/ssh/src/ssh_app.erl
new file mode 100644
index 0000000000..5793d3a321
--- /dev/null
+++ b/lib/ssh/src/ssh_app.erl
@@ -0,0 +1,34 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2004-2009. 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%
+%%
+
+%%
+
+%% Purpose : Application master for SSH.
+
+-module(ssh_app).
+
+-behaviour(application).
+
+-export([start/2, stop/1]).
+
+start(_Type, _State) ->
+ supervisor:start_link({local, ssh_sup}, ssh_sup, []).
+
+stop(_State) ->
+ ok.
diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl
new file mode 100644
index 0000000000..aa74528544
--- /dev/null
+++ b/lib/ssh/src/ssh_auth.erl
@@ -0,0 +1,423 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2009. 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_auth).
+
+-include("ssh.hrl").
+
+-include("ssh_auth.hrl").
+-include("ssh_transport.hrl").
+
+-export([publickey_msg/1, password_msg/1, keyboard_interactive_msg/1,
+ service_request_msg/1, init_userauth_request_msg/1,
+ userauth_request_msg/1, handle_userauth_request/3,
+ handle_userauth_info_request/3, handle_userauth_info_response/2,
+ userauth_messages/0
+ ]).
+
+%%--------------------------------------------------------------------
+%%% Internal application API
+%%--------------------------------------------------------------------
+publickey_msg([Cb, #ssh{user = User,
+ session_id = SessionId,
+ service = Service,
+ opts = Opts} = Ssh]) ->
+ ssh_bits:install_messages(userauth_pk_messages()),
+ Alg = Cb:alg_name(),
+ case ssh_file:private_identity_key(Alg, Opts) of
+ {ok, PrivKey} ->
+ PubKeyBlob = ssh_file:encode_public_key(PrivKey),
+ SigData = build_sig_data(SessionId,
+ User, Service, Alg, PubKeyBlob),
+ Sig = Cb:sign(PrivKey, SigData),
+ SigBlob = list_to_binary([?string(Alg), ?binary(Sig)]),
+ ssh_transport:ssh_packet(
+ #ssh_msg_userauth_request{user = User,
+ service = Service,
+ method = "publickey",
+ data = [?TRUE,
+ ?string(Alg),
+ ?binary(PubKeyBlob),
+ ?binary(SigBlob)]},
+ Ssh);
+ _Error ->
+ not_ok
+ end.
+
+password_msg([#ssh{opts = Opts, io_cb = IoCb,
+ user = User, service = Service} = Ssh]) ->
+ ssh_bits:install_messages(userauth_passwd_messages()),
+ Password = case proplists:get_value(password, Opts) of
+ undefined ->
+ IoCb:read_password("ssh password: ");
+ PW ->
+ PW
+ end,
+ ssh_transport:ssh_packet(
+ #ssh_msg_userauth_request{user = User,
+ service = Service,
+ method = "password",
+ data =
+ <<?BOOLEAN(?FALSE),
+ ?STRING(list_to_binary(Password))>>},
+ Ssh).
+
+%% See RFC 4256 for info on keyboard-interactive
+keyboard_interactive_msg([#ssh{user = User,
+ service = Service} = Ssh]) ->
+ ssh_bits:install_messages(userauth_keyboard_interactive_messages()),
+ ssh_transport:ssh_packet(
+ #ssh_msg_userauth_request{user = User,
+ service = Service,
+ method = "keyboard-interactive",
+ data = << ?STRING(<<"">>),
+ ?STRING(<<>>) >> },
+ Ssh).
+
+service_request_msg(Ssh) ->
+ ssh_transport:ssh_packet(#ssh_msg_service_request{name = "ssh-userauth"},
+ Ssh#ssh{service = "ssh-userauth"}).
+
+init_userauth_request_msg(#ssh{opts = Opts} = Ssh) ->
+ case user_name(Opts) of
+ {ok, User} ->
+ Msg = #ssh_msg_userauth_request{user = User,
+ service = "ssh-connection",
+ method = "none",
+ data = <<>>},
+ CbFirst = proplists:get_value(public_key_alg, Opts,
+ ?PREFERRED_PK_ALG),
+ CbSecond = other_cb(CbFirst),
+ AllowUserInt = proplists:get_value(allow_user_interaction, Opts,
+ true),
+ Prefs = method_preference(CbFirst, CbSecond, AllowUserInt),
+ ssh_transport:ssh_packet(Msg, Ssh#ssh{user = User,
+ userauth_preference = Prefs,
+ userauth_methods = none,
+ service = "ssh-connection"});
+ {error, no_user} ->
+ ErrStr = "Could not determine the users name",
+ throw(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_ILLEGAL_USER_NAME,
+ description = ErrStr,
+ language = "en"})
+ end.
+
+userauth_request_msg(#ssh{userauth_preference = []} = Ssh) ->
+ Msg = #ssh_msg_disconnect{code =
+ ?SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE,
+ description = "Unable to connect using the available"
+ " authentication methods",
+ language = "en"},
+ {disconnect, Msg, ssh_transport:ssh_packet(Msg, Ssh)};
+
+userauth_request_msg(#ssh{userauth_methods = Methods,
+ userauth_preference = [{Pref, Module,
+ Function, Args} | Prefs]}
+ = Ssh0) ->
+ Ssh = Ssh0#ssh{userauth_preference = Prefs},
+ case lists:member(Pref, Methods) of
+ true ->
+ case Module:Function(Args ++ [Ssh]) of
+ not_ok ->
+ userauth_request_msg(Ssh);
+ Result ->
+ Result
+ end;
+ false ->
+ userauth_request_msg(Ssh)
+ end.
+
+
+handle_userauth_request(#ssh_msg_service_request{name =
+ Name = "ssh-userauth"},
+ _, Ssh) ->
+ {ok, ssh_transport:ssh_packet(#ssh_msg_service_accept{name = Name},
+ Ssh#ssh{service = "ssh-connection"})};
+
+handle_userauth_request(#ssh_msg_userauth_request{user = User,
+ service = "ssh-connection",
+ method = "password",
+ data = Data}, _,
+ #ssh{opts = Opts} = Ssh) ->
+ <<_:8, ?UINT32(Sz), BinPwd:Sz/binary>> = Data,
+ Password = binary_to_list(BinPwd),
+
+ case check_password(User, Password, Opts) of
+ true ->
+ {authorized, User,
+ ssh_transport:ssh_packet(#ssh_msg_userauth_success{}, Ssh)};
+ false ->
+ {not_authorized, {User, {passwd, Password}},
+ ssh_transport:ssh_packet(#ssh_msg_userauth_failure{
+ authentications = "",
+ partial_success = false}, Ssh)}
+ end;
+
+handle_userauth_request(#ssh_msg_userauth_request{user = User,
+ service = "ssh-connection",
+ method = "none"}, _,
+ #ssh{userauth_supported_methods = Methods} = Ssh) ->
+ {not_authorized, {User, undefined},
+ ssh_transport:ssh_packet(
+ #ssh_msg_userauth_failure{authentications = Methods,
+ partial_success = false}, Ssh)};
+
+handle_userauth_request(#ssh_msg_userauth_request{user = User,
+ service = "ssh-connection",
+ method = "publickey",
+ data = Data},
+ SessionId, #ssh{opts = Opts} = Ssh) ->
+ <<?BYTE(HaveSig), ?UINT32(ALen), BAlg:ALen/binary,
+ ?UINT32(KLen), KeyBlob:KLen/binary, SigWLen/binary>> = Data,
+ Alg = binary_to_list(BAlg),
+ case HaveSig of
+ ?TRUE ->
+ case verify_sig(SessionId, User, "ssh-connection", Alg,
+ KeyBlob, SigWLen, Opts) of
+ ok ->
+ {authorized, User,
+ ssh_transport:ssh_packet(
+ #ssh_msg_userauth_success{}, Ssh)};
+ {error, Reason} ->
+ {not_authorized, {User, {error, Reason}},
+ ssh_transport:ssh_packet(#ssh_msg_userauth_failure{
+ authentications="publickey,password",
+ partial_success = false}, Ssh)}
+ end;
+ ?FALSE ->
+ ssh_bits:install_messages(userauth_pk_messages()),
+ {not_authorized, {User, undefined},
+ ssh_transport:ssh_packet(
+ #ssh_msg_userauth_pk_ok{algorithm_name = Alg,
+ key_blob = KeyBlob}, Ssh)}
+ end;
+
+handle_userauth_request(#ssh_msg_userauth_request{user = User,
+ service = "ssh-connection",
+ method = Other}, _,
+ #ssh{userauth_supported_methods = Methods} = Ssh) ->
+ {not_authorized, {User, {authmethod, Other}},
+ ssh_transport:ssh_packet(
+ #ssh_msg_userauth_failure{authentications = Methods,
+ partial_success = false}, Ssh)}.
+
+handle_userauth_info_request(
+ #ssh_msg_userauth_info_request{name = Name,
+ instruction = Instr,
+ num_prompts = NumPrompts,
+ data = Data}, IoCb,
+ #ssh{opts = Opts} = Ssh) ->
+ PromptInfos = decode_keyboard_interactive_prompts(NumPrompts,Data),
+ Resps = keyboard_interact_get_responses(IoCb, Opts,
+ Name, Instr, PromptInfos),
+ %%?dbg(true, "keyboard_interactive_reply: resps=~n#~p ~n", [Resps]),
+ RespBin = list_to_binary(
+ lists:map(fun(S) -> <<?STRING(list_to_binary(S))>> end,
+ Resps)),
+ {ok,
+ ssh_transport:ssh_packet(
+ #ssh_msg_userauth_info_response{num_responses = NumPrompts,
+ data = RespBin}, Ssh)}.
+
+handle_userauth_info_response(#ssh_msg_userauth_info_response{},
+ _Auth) ->
+ throw(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE,
+ description = "Server does not support"
+ "keyboard-interactive",
+ language = "en"}).
+userauth_messages() ->
+ [ {ssh_msg_userauth_request, ?SSH_MSG_USERAUTH_REQUEST,
+ [string,
+ string,
+ string,
+ '...']},
+
+ {ssh_msg_userauth_failure, ?SSH_MSG_USERAUTH_FAILURE,
+ [string,
+ boolean]},
+
+ {ssh_msg_userauth_success, ?SSH_MSG_USERAUTH_SUCCESS,
+ []},
+
+ {ssh_msg_userauth_banner, ?SSH_MSG_USERAUTH_BANNER,
+ [string,
+ string]}].
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+method_preference(Callback1, Callback2, true) ->
+ [{"publickey", ?MODULE, publickey_msg, [Callback1]},
+ {"publickey", ?MODULE, publickey_msg,[Callback2]},
+ {"password", ?MODULE, password_msg, []},
+ {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []}
+ ];
+method_preference(Callback1, Callback2, false) ->
+ [{"publickey", ?MODULE, publickey_msg, [Callback1]},
+ {"publickey", ?MODULE, publickey_msg,[Callback2]},
+ {"password", ?MODULE, password_msg, []}
+ ].
+
+user_name(Opts) ->
+ Env = case os:type() of
+ {win32, _} ->
+ "USERNAME";
+ {unix, _} ->
+ "LOGNAME"
+ end,
+ case proplists:get_value(user, Opts, os:getenv(Env)) of
+ false ->
+ case os:getenv("USER") of
+ false ->
+ {error, no_user};
+ User ->
+ {ok, User}
+ end;
+ User ->
+ {ok, User}
+ end.
+
+check_password(User, Password, Opts) ->
+ %%?dbg(true, " ~p ~p ~p ~n", [User, Password, Opts]),
+ case proplists:get_value(pwdfun, Opts) of
+ undefined ->
+ Static = get_password_option(Opts, User),
+ Password == Static;
+ Cheker ->
+ Cheker(User, Password)
+ end.
+
+get_password_option(Opts, User) ->
+ Passwords = proplists:get_value(user_passwords, Opts, []),
+ case lists:keysearch(User, 1, Passwords) of
+ {value, {User, Pw}} -> Pw;
+ false -> proplists:get_value(password, Opts, false)
+ end.
+
+verify_sig(SessionId, User, Service, Alg, KeyBlob, SigWLen, Opts) ->
+ case ssh_file:lookup_user_key(User, Alg, Opts) of
+ {ok, OurKey} ->
+ {ok, Key} = ssh_file:decode_public_key_v2(KeyBlob, Alg),
+ case OurKey of
+ Key ->
+ NewSig = build_sig_data(SessionId,
+ User, Service, Alg, KeyBlob),
+ <<?UINT32(AlgSigLen), AlgSig:AlgSigLen/binary>> = SigWLen,
+ <<?UINT32(AlgLen), _Alg:AlgLen/binary,
+ ?UINT32(SigLen), Sig:SigLen/binary>> = AlgSig,
+ M = alg_to_module(Alg),
+ M:verify(OurKey, NewSig, Sig);
+ _ ->
+ {error, key_unacceptable}
+ end;
+ Error -> Error
+ end.
+
+build_sig_data(SessionId, User, Service, Alg, KeyBlob) ->
+ Sig = [?binary(SessionId),
+ ?SSH_MSG_USERAUTH_REQUEST,
+ ?string(User),
+ ?string(Service),
+ ?binary(<<"publickey">>),
+ ?TRUE,
+ ?string(Alg),
+ ?binary(KeyBlob)],
+ list_to_binary(Sig).
+
+decode_keyboard_interactive_prompts(NumPrompts, Data) ->
+ Types = lists:append(lists:duplicate(NumPrompts, [string, boolean])),
+ pairwise_tuplify(ssh_bits:decode(Data, Types)).
+
+pairwise_tuplify([E1, E2 | Rest]) -> [{E1, E2} | pairwise_tuplify(Rest)];
+pairwise_tuplify([]) -> [].
+
+
+keyboard_interact_get_responses(IoCb, Opts, Name, Instr, PromptInfos) ->
+ NumPrompts = length(PromptInfos),
+ case proplists:get_value(keyboard_interact_fun, Opts) of
+ undefined when NumPrompts == 1 ->
+ %% Special case/fallback for just one prompt
+ %% (assumed to be the password prompt)
+ case proplists:get_value(password, Opts) of
+ undefined -> keyboard_interact(IoCb, Name, Instr, PromptInfos);
+ PW -> [PW]
+ end;
+ undefined ->
+ keyboard_interact(IoCb, Name, Instr, PromptInfos);
+ KbdInteractFun ->
+ Prompts = lists:map(fun({Prompt, _Echo}) -> Prompt end,
+ PromptInfos),
+ case KbdInteractFun(Name, Instr, Prompts) of
+ Rs when length(Rs) == NumPrompts ->
+ Rs;
+ Rs ->
+ erlang:error({mismatching_number_of_responses,
+ {got,Rs},
+ {expected,NumPrompts}})
+ end
+ end.
+
+keyboard_interact(IoCb, Name, Instr, Prompts) ->
+ if Name /= "" -> IoCb:format("~s", [Name]);
+ true -> ok
+ end,
+ if Instr /= "" -> IoCb:format("~s", [Instr]);
+ true -> ok
+ end,
+ lists:map(fun({Prompt, true}) -> IoCb:read_line(Prompt);
+ ({Prompt, false}) -> IoCb:read_password(Prompt)
+ end,
+ Prompts).
+
+userauth_passwd_messages() ->
+ [
+ {ssh_msg_userauth_passwd_changereq, ?SSH_MSG_USERAUTH_PASSWD_CHANGEREQ,
+ [string,
+ string]}
+ ].
+
+userauth_keyboard_interactive_messages() ->
+ [ {ssh_msg_userauth_info_request, ?SSH_MSG_USERAUTH_INFO_REQUEST,
+ [string,
+ string,
+ string,
+ uint32,
+ '...']},
+
+ {ssh_msg_userauth_info_response, ?SSH_MSG_USERAUTH_INFO_RESPONSE,
+ [uint32,
+ '...']}
+ ].
+
+userauth_pk_messages() ->
+ [ {ssh_msg_userauth_pk_ok, ?SSH_MSG_USERAUTH_PK_OK,
+ [string, % algorithm name
+ binary]} % key blob
+ ].
+
+alg_to_module("ssh-dss") ->
+ ssh_dsa;
+alg_to_module("ssh-rsa") ->
+ ssh_rsa.
+
+other_cb(ssh_rsa) ->
+ ssh_dsa;
+other_cb(ssh_dsa) ->
+ ssh_rsa.
diff --git a/lib/ssh/src/ssh_auth.hrl b/lib/ssh/src/ssh_auth.hrl
new file mode 100644
index 0000000000..80c5a6819b
--- /dev/null
+++ b/lib/ssh/src/ssh_auth.hrl
@@ -0,0 +1,83 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2009. 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%
+%%
+
+%%
+
+%%% Description: Ssh User Authentication Protocol
+
+-define(SUPPORTED_AUTH_METHODS, "publickey,keyboard_interactive,password").
+
+-define(PREFERRED_PK_ALG, ssh_rsa).
+
+-define(SSH_MSG_USERAUTH_REQUEST, 50).
+-define(SSH_MSG_USERAUTH_FAILURE, 51).
+-define(SSH_MSG_USERAUTH_SUCCESS, 52).
+-define(SSH_MSG_USERAUTH_BANNER, 53).
+-define(SSH_MSG_USERAUTH_PK_OK, 60).
+-define(SSH_MSG_USERAUTH_PASSWD_CHANGEREQ, 60).
+-define(SSH_MSG_USERAUTH_INFO_REQUEST, 60).
+-define(SSH_MSG_USERAUTH_INFO_RESPONSE, 61).
+
+-record(ssh_msg_userauth_request,
+ {
+ user, %% string
+ service, %% string
+ method, %% string "publickey", "password"
+ data %% opaque
+ }).
+
+-record(ssh_msg_userauth_failure,
+ {
+ authentications, %% string
+ partial_success %% boolean
+ }).
+
+-record(ssh_msg_userauth_success,
+ {
+ }).
+
+-record(ssh_msg_userauth_banner,
+ {
+ message, %% string
+ language %% string
+ }).
+
+-record(ssh_msg_userauth_passwd_changereq,
+ {
+ prompt, %% string
+ languge %% string
+ }).
+
+-record(ssh_msg_userauth_pk_ok,
+ {
+ algorithm_name, % string
+ key_blob % string
+ }).
+
+-record(ssh_msg_userauth_info_request,
+ {name,
+ instruction,
+ language_tag,
+ num_prompts,
+ data}).
+
+-record(ssh_msg_userauth_info_response,
+ {num_responses,
+ data}).
+
diff --git a/lib/ssh/src/ssh_bits.erl b/lib/ssh/src/ssh_bits.erl
new file mode 100755
index 0000000000..21ddc5e8fe
--- /dev/null
+++ b/lib/ssh/src/ssh_bits.erl
@@ -0,0 +1,483 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2009. 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%
+%%
+
+%%
+
+%%% Description : SSH 1/2 pdu elements encode/decode
+
+-module(ssh_bits).
+
+-include("ssh.hrl").
+
+-export([encode/1, encode/2]).
+-export([decode/1, decode/2, decode/3]).
+-export([mpint/1, bignum/1, string/1, name_list/1]).
+-export([b64_encode/1, b64_decode/1]).
+-export([install_messages/1, uninstall_messages/1]).
+
+%% integer utils
+-export([isize/1]).
+-export([irandom/1, irandom/3]).
+-export([random/1, random/3]).
+-export([xor_bits/2, fill_bits/2]).
+-export([i2bin/2, bin2i/1]).
+
+-import(lists, [foreach/2, reverse/1]).
+
+-define(name_list(X),
+ (fun(B) -> ?binary(B) end)(list_to_binary(name_concat(X)))).
+
+
+name_concat([Name]) when is_atom(Name) -> atom_to_list(Name);
+name_concat([Name]) when is_list(Name) -> Name;
+name_concat([Name|Ns]) ->
+ if is_atom(Name) ->
+ [atom_to_list(Name),"," | name_concat(Ns)];
+ is_list(Name) ->
+ [Name,"," | name_concat(Ns)]
+ end;
+name_concat([]) -> [].
+
+
+name_list(Ns) ->
+ ?name_list(Ns).
+
+
+string(Str) ->
+ ?string(Str).
+
+
+%% MP representaion (SSH2)
+mpint(X) when X < 0 ->
+ if X == -1 ->
+ <<0,0,0,1,16#ff>>;
+ true ->
+ mpint_neg(X,0,[])
+ end;
+mpint(X) ->
+ if X == 0 ->
+ <<0,0,0,0>>;
+ true ->
+ mpint_pos(X,0,[])
+ end.
+
+mpint_neg(-1,I,Ds=[MSB|_]) ->
+ if MSB band 16#80 =/= 16#80 ->
+ <<?UINT32((I+1)), (list_to_binary([255|Ds]))/binary>>;
+ true ->
+ (<<?UINT32(I), (list_to_binary(Ds))/binary>>)
+ end;
+mpint_neg(X,I,Ds) ->
+ mpint_neg(X bsr 8,I+1,[(X band 255)|Ds]).
+
+mpint_pos(0,I,Ds=[MSB|_]) ->
+ if MSB band 16#80 == 16#80 ->
+ <<?UINT32((I+1)), (list_to_binary([0|Ds]))/binary>>;
+ true ->
+ (<<?UINT32(I), (list_to_binary(Ds))/binary>>)
+ end;
+mpint_pos(X,I,Ds) ->
+ mpint_pos(X bsr 8,I+1,[(X band 255)|Ds]).
+
+
+%% BIGNUM representation SSH1
+bignum(X) ->
+ XSz = isize(X),
+ Pad = (8 - (XSz rem 8)) rem 8,
+ <<?UINT16(XSz),0:Pad/unsigned-integer,X:XSz/big-unsigned-integer>>.
+
+
+install_messages(Codes) ->
+ foreach(fun({Name, Code, Ts}) ->
+ %% ?dbg(true, "install msg: ~s = ~w ~w~n",
+%% [Name,Code,Ts]),
+ put({msg_name,Code}, {Name,Ts}),
+ put({msg_code,Name}, {Code,Ts})
+ end, Codes).
+
+uninstall_messages(Codes) ->
+ foreach(fun({Name, Code, _Ts}) ->
+ %% ?dbg(true, "uninstall msg: ~s = ~w ~w~n",
+%% [Name,Code,_Ts]),
+ erase({msg_name,Code}),
+ erase({msg_code,Name})
+ end, Codes).
+
+%%
+%% Encode a record, the type spec is expected to be
+%% in process dictionary under the key {msg_code, RecodeName}
+%%
+encode(Record) ->
+ case get({msg_code, element(1, Record)}) of
+ undefined ->
+ {error, unimplemented};
+ {Code, Ts} ->
+ Data = enc(tl(tuple_to_list(Record)), Ts),
+ list_to_binary([Code, Data])
+ end.
+
+encode(List, Types) ->
+ list_to_binary(enc(List, Types)).
+
+%%
+%% Encode record element
+%%
+enc(Xs, Ts) ->
+ enc(Xs, Ts, 0).
+
+enc(Xs, [Type|Ts], Offset) ->
+ case Type of
+ boolean ->
+ X=hd(Xs),
+ [?boolean(X) | enc(tl(Xs), Ts, Offset+1)];
+ byte ->
+ X=hd(Xs),
+ [?byte(X) | enc(tl(Xs), Ts,Offset+1)];
+ uint16 ->
+ X=hd(Xs),
+ [?uint16(X) | enc(tl(Xs), Ts,Offset+2)];
+ uint32 ->
+ X=hd(Xs),
+ [?uint32(X) | enc(tl(Xs), Ts,Offset+4)];
+ uint64 ->
+ X=hd(Xs),
+ [?uint64(X) | enc(tl(Xs), Ts,Offset+8)];
+ mpint ->
+ Y=mpint(hd(Xs)),
+ [Y | enc(tl(Xs), Ts,Offset+size(Y))];
+ bignum ->
+ Y=bignum(hd(Xs)),
+ [Y | enc(tl(Xs),Ts,Offset+size(Y))];
+ string ->
+ X0=hd(Xs),
+ Y=?string(X0),
+ [Y | enc(tl(Xs),Ts,Offset+size(Y))];
+ binary ->
+ X0=hd(Xs),
+ Y=?binary(X0),
+ [Y | enc(tl(Xs), Ts,Offset+size(Y))];
+ name_list ->
+ X0=hd(Xs),
+ Y=?name_list(X0),
+ [Y | enc(tl(Xs), Ts, Offset+size(Y))];
+ cookie ->
+ [random(16) | enc(tl(Xs), Ts, Offset+16)];
+ {pad,N} ->
+ K = (N - (Offset rem N)) rem N,
+ [fill_bits(K,0) | enc(Xs, Ts, Offset+K)];
+ '...' when Ts==[] ->
+ X=hd(Xs),
+ if is_binary(X) ->
+ [X];
+ is_list(X) ->
+ [list_to_binary(X)];
+ X==undefined ->
+ []
+ end
+ end;
+enc([], [],_) ->
+ [].
+
+
+
+%%
+%% Decode a SSH record the type is encoded as the first byte
+%% and the type spec MUST be installed in {msg_name, ID}
+%%
+
+decode(Binary = <<?BYTE(ID), _/binary>>) ->
+ case get({msg_name, ID}) of
+ undefined ->
+ {unknown, Binary};
+ {Name, Ts} ->
+ {_, Elems} = decode(Binary,1,Ts),
+ list_to_tuple([Name | Elems])
+ end.
+
+%%
+%% Decode a binary form offset 0
+%%
+
+decode(Binary, Types) when is_binary(Binary) andalso is_list(Types) ->
+ {_,Elems} = decode(Binary, 0, Types),
+ Elems.
+
+
+%%
+%% Decode a binary from byte offset Offset
+%% return {UpdatedOffset, DecodedElements}
+%%
+decode(Binary, Offset, Types) ->
+ decode(Binary, Offset, Types, []).
+
+decode(Binary, Offset, [Type|Ts], Acc) ->
+ case Type of
+ boolean ->
+ <<_:Offset/binary, ?BOOLEAN(X0), _/binary>> = Binary,
+ X = if X0 == 0 -> false; true -> true end,
+ decode(Binary, Offset+1, Ts, [X | Acc]);
+
+ byte ->
+ <<_:Offset/binary, ?BYTE(X), _/binary>> = Binary,
+ decode(Binary, Offset+1, Ts, [X | Acc]);
+
+ uint16 ->
+ <<_:Offset/binary, ?UINT16(X), _/binary>> = Binary,
+ decode(Binary, Offset+2, Ts, [X | Acc]);
+
+ uint32 ->
+ <<_:Offset/binary, ?UINT32(X), _/binary>> = Binary,
+ decode(Binary, Offset+4, Ts, [X | Acc]);
+
+ uint64 ->
+ <<_:Offset/binary, ?UINT64(X), _/binary>> = Binary,
+ decode(Binary, Offset+8, Ts, [X | Acc]);
+
+ mpint ->
+ <<_:Offset/binary, ?UINT32(L), X0:L/binary,_/binary>> = Binary,
+ Sz = L*8,
+ <<X:Sz/big-signed-integer>> = X0,
+ decode(Binary, Offset+4+L, Ts, [X | Acc]);
+
+ bignum ->
+ <<_:Offset/binary, ?UINT16(Bits),_/binary>> = Binary,
+ L = (Bits+7) div 8,
+ Pad = (8 - (Bits rem 8)) rem 8,
+ <<_:Offset/binary, _:16, _:Pad, X:Bits/big-unsigned-integer,
+ _/binary>> = Binary,
+ decode(Binary, Offset+2+L, Ts, [X | Acc]);
+
+ string ->
+ Size = size(Binary),
+ if Size < Offset + 4 ->
+ %% empty string at end
+ {Size, reverse(["" | Acc])};
+ true ->
+ <<_:Offset/binary,?UINT32(L), X:L/binary,_/binary>> =
+ Binary,
+ decode(Binary, Offset+4+L, Ts, [binary_to_list(X) |
+ Acc])
+ end;
+
+ binary ->
+ <<_:Offset/binary,?UINT32(L), X:L/binary,_/binary>> = Binary,
+ decode(Binary, Offset+4+L, Ts, [X | Acc]);
+
+ name_list ->
+ <<_:Offset/binary,?UINT32(L), X:L/binary,_/binary>> = Binary,
+ List = string:tokens(binary_to_list(X), ","),
+ decode(Binary, Offset+4+L, Ts, [List | Acc]);
+
+ cookie ->
+ <<_:Offset/binary, X:16/binary, _/binary>> = Binary,
+ decode(Binary, Offset+16, Ts, [X | Acc]);
+
+ {pad,N} -> %% pad offset to a multiple of N
+ K = (N - (Offset rem N)) rem N,
+ decode(Binary, Offset+K, Ts, Acc);
+
+
+ '...' when Ts==[] ->
+ <<_:Offset/binary, X/binary>> = Binary,
+ {Offset+size(X), reverse([X | Acc])}
+ end;
+decode(_Binary, Offset, [], Acc) ->
+ {Offset, reverse(Acc)}.
+
+
+
+%% HACK WARNING :-)
+-define(VERSION_MAGIC, 131).
+-define(SMALL_INTEGER_EXT, $a).
+-define(INTEGER_EXT, $b).
+-define(SMALL_BIG_EXT, $n).
+-define(LARGE_BIG_EXT, $o).
+
+isize(N) when N > 0 ->
+ case term_to_binary(N) of
+ <<?VERSION_MAGIC, ?SMALL_INTEGER_EXT, X>> ->
+ isize_byte(X);
+ <<?VERSION_MAGIC, ?INTEGER_EXT, X3,X2,X1,X0>> ->
+ isize_bytes([X3,X2,X1,X0]);
+ <<?VERSION_MAGIC, ?SMALL_BIG_EXT, S:8/big-unsigned-integer, 0,
+ Ds:S/binary>> ->
+ K = S - 1,
+ <<_:K/binary, Top>> = Ds,
+ isize_byte(Top)+K*8;
+ <<?VERSION_MAGIC, ?LARGE_BIG_EXT, S:32/big-unsigned-integer, 0,
+ Ds:S/binary>> ->
+ K = S - 1,
+ <<_:K/binary, Top>> = Ds,
+ isize_byte(Top)+K*8
+ end;
+isize(0) -> 0.
+
+%% big endian byte list
+isize_bytes([0|L]) ->
+ isize_bytes(L);
+isize_bytes([Top|L]) ->
+ isize_byte(Top) + length(L)*8.
+
+%% Well could be improved
+isize_byte(X) ->
+ if X >= 2#10000000 -> 8;
+ X >= 2#1000000 -> 7;
+ X >= 2#100000 -> 6;
+ X >= 2#10000 -> 5;
+ X >= 2#1000 -> 4;
+ X >= 2#100 -> 3;
+ X >= 2#10 -> 2;
+ X >= 2#1 -> 1;
+ true -> 0
+ end.
+
+%% Convert integer into binary
+%% When XLen is the wanted size in octets of the output
+i2bin(X, XLen) ->
+ XSz = isize(X),
+ Sz = XLen*8,
+ if Sz < XSz ->
+ exit(integer_to_large);
+ true ->
+ (<<X:Sz/big-unsigned-integer>>)
+ end.
+
+%% Convert a binary into an integer
+%%
+bin2i(X) ->
+ Sz = size(X)*8,
+ <<Y:Sz/big-unsigned-integer>> = X,
+ Y.
+
+%%
+%% Create a binary with constant bytes
+%%
+fill_bits(N,C) ->
+ list_to_binary(fill(N,C)).
+
+fill(0,_C) -> [];
+fill(1,C) -> [C];
+fill(N,C) ->
+ Cs = fill(N div 2, C),
+ Cs1 = [Cs,Cs],
+ if N band 1 == 0 ->
+ Cs1;
+ true ->
+ [C,Cs,Cs]
+ end.
+
+%% xor 2 binaries
+xor_bits(XBits, YBits) ->
+ XSz = size(XBits)*8,
+ YSz = size(YBits)*8,
+ Sz = if XSz < YSz -> XSz; true -> YSz end, %% min
+ <<X:Sz, _/binary>> = XBits,
+ <<Y:Sz, _/binary>> = YBits,
+ <<(X bxor Y):Sz>>.
+
+%%
+%% irandom(N)
+%%
+%% Generate a N bits size random number
+%% note that the top most bit is always set
+%% to guarantee that the number is N bits
+%%
+irandom(Bits) ->
+ irandom(Bits, 1, 0).
+
+%% irandom_odd(Bits) ->
+%% irandom(Bits, 1, 1).
+
+%%
+%% irandom(N, Top, Bottom)
+%%
+%% Generate a N bits size random number
+%% Where Top = 0 - do not set top bit
+%% = 1 - set the most significant bit
+%% = 2 - set two most significant bits
+%% Bot = 0 - do not set the least signifcant bit
+%% Bot = 1 - set the least signifcant bit (i.e always odd)
+%%
+irandom(0, _Top, _Bottom) ->
+ 0;
+irandom(Bits, Top, Bottom) ->
+ Bytes = (Bits+7) div 8,
+ Skip = (8-(Bits rem 8)) rem 8,
+ TMask = case Top of
+ 0 -> 0;
+ 1 -> 16#80;
+ 2 -> 16#c0
+ end,
+ BMask = case Bottom of
+ 0 -> 0;
+ 1 -> (1 bsl Skip)
+ end,
+ <<X:Bits/big-unsigned-integer, _:Skip>> = random(Bytes, TMask, BMask),
+ X.
+
+%%
+%% random/1
+%% Generate N random bytes
+%%
+random(N) ->
+ random(N, 0, 0).
+
+random(N, TMask, BMask) ->
+ list_to_binary(rnd(N, TMask, BMask)).
+
+%% random/3
+%% random(Bytes, TopMask, BotMask)
+%% where
+%% Bytes is the number of bytes to generate
+%% TopMask is bitwised or'ed to the first byte
+%% BotMask is bitwised or'ed to the last byte
+%%
+rnd(0, _TMask, _BMask) ->
+ [];
+rnd(1, TMask, BMask) ->
+ [(rand8() bor TMask) bor BMask];
+rnd(N, TMask, BMask) ->
+ [(rand8() bor TMask) | rnd_n(N-1, BMask)].
+
+rnd_n(1, BMask) ->
+ [rand8() bor BMask];
+rnd_n(I, BMask) ->
+ [rand8() | rnd_n(I-1, BMask)].
+
+rand8() ->
+ (rand32() bsr 8) band 16#ff.
+
+rand32() ->
+ random:uniform(16#100000000) -1.
+
+%%
+%% Base 64 encode/decode
+%%
+
+b64_encode(Bs) when is_list(Bs) ->
+ base64:encode(Bs);
+b64_encode(Bin) when is_binary(Bin) ->
+ base64:encode(Bin).
+
+b64_decode(Bin) when is_binary(Bin) ->
+ base64:mime_decode(Bin);
+b64_decode(Cs) when is_list(Cs) ->
+ base64:mime_decode(Cs).
+
+
diff --git a/lib/ssh/src/ssh_channel.erl b/lib/ssh/src/ssh_channel.erl
new file mode 100644
index 0000000000..3d67065ee1
--- /dev/null
+++ b/lib/ssh/src/ssh_channel.erl
@@ -0,0 +1,328 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2009. 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_channel).
+
+-include("ssh_connect.hrl").
+
+-behaviour(gen_server).
+
+%%% API
+-export([behaviour_info/1, start/4, start/5, start_link/4, start_link/5, call/2, call/3,
+ cast/2, reply/2, enter_loop/1]).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3]).
+
+%% Internal application API
+-export([cache_create/0, cache_lookup/2, cache_update/2,
+ cache_delete/1, cache_delete/2, cache_foldl/3,
+ cache_find/2]).
+
+-record(state, {
+ cm,
+ channel_cb,
+ channel_state,
+ channel_id,
+ close_sent = false
+ }).
+
+%%====================================================================
+%% API
+%%====================================================================
+
+%%% Optionel callbacks handle_call/3, handle_cast/2, handle_msg/2,
+%%% code_change/3
+behaviour_info(callbacks) ->
+ [
+ {init, 1},
+ {terminate, 2},
+ {handle_ssh_msg, 2},
+ {handle_msg, 2}
+ ].
+
+
+call(ChannelPid, Msg) ->
+ call(ChannelPid, Msg, infinity).
+
+call(ChannelPid, Msg, TimeOute) ->
+ try gen_server:call(ChannelPid, Msg, TimeOute) of
+ Result ->
+ Result
+ catch
+ exit:{noproc, _} ->
+ {error, closed};
+ exit:{timeout, _} ->
+ {error, timeout}
+ end.
+
+
+cast(ChannelPid, Msg) ->
+ gen_server:cast(ChannelPid, Msg).
+
+
+reply(From, Msg) ->
+ gen_server:reply(From, Msg).
+
+%%====================================================================
+%% Internal application API
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
+%% Description: Starts the server
+%%--------------------------------------------------------------------
+start(ConnectionManager, ChannelId, CallBack, CbInitArgs) ->
+ start(ConnectionManager, ChannelId, CallBack, CbInitArgs, undefined).
+
+start(ConnectionManager, ChannelId, CallBack, CbInitArgs, Exec) ->
+ Options = [{channel_cb, CallBack},
+ {channel_id, ChannelId},
+ {init_args, CbInitArgs},
+ {cm, ConnectionManager},
+ {exec, Exec}],
+ gen_server:start(?MODULE, [Options], []).
+
+start_link(ConnectionManager, ChannelId, CallBack, CbInitArgs) ->
+ start_link(ConnectionManager, ChannelId, CallBack, CbInitArgs, undefined).
+
+start_link(ConnectionManager, ChannelId, CallBack, CbInitArgs, Exec) ->
+ Options = [{channel_cb, CallBack},
+ {channel_id, ChannelId},
+ {init_args, CbInitArgs},
+ {cm, ConnectionManager},
+ {exec, Exec}],
+ gen_server:start_link(?MODULE, [Options], []).
+
+enter_loop(State) ->
+ gen_server:enter_loop(?MODULE, [], State).
+
+%%====================================================================
+%% gen_server callbacks
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% Function: init(Args) -> {ok, State} |
+%% {ok, State, Timeout} |
+%% ignore |
+%% {stop, Reason}
+%% Description: Initiates the server
+%%--------------------------------------------------------------------
+init([Options]) ->
+ Cb = proplists:get_value(channel_cb, Options),
+ ConnectionManager = proplists:get_value(cm, Options),
+ ChannelId = proplists:get_value(channel_id, Options),
+ process_flag(trap_exit, true),
+ InitArgs =
+ case proplists:get_value(exec, Options) of
+ undefined ->
+ proplists:get_value(init_args, Options);
+ Exec ->
+ proplists:get_value(init_args, Options) ++ [Exec]
+ end,
+ try Cb:init(InitArgs) of
+ {ok, ChannelState} ->
+ State = #state{cm = ConnectionManager,
+ channel_cb = Cb,
+ channel_id = ChannelId,
+ channel_state = ChannelState},
+ self() ! {ssh_channel_up, ChannelId, ConnectionManager},
+ {ok, State};
+ {ok, ChannelState, Timeout} ->
+ State = #state{cm = ConnectionManager,
+ channel_cb = Cb,
+ channel_id = ChannelId,
+ channel_state = ChannelState},
+ self() ! {ssh_channel_up, ChannelId, ConnectionManager},
+ {ok, State, Timeout};
+ {stop, Why} ->
+ {stop, Why}
+ catch
+ _:Reason ->
+ {stop, Reason}
+ end.
+
+%%--------------------------------------------------------------------
+%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
+%% {reply, Reply, State, Timeout} |
+%% {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, Reply, State} |
+%% {stop, Reason, State}
+%% Description: Handling call messages
+%%--------------------------------------------------------------------
+handle_call(Request, From, #state{channel_cb = Module,
+ channel_state = ChannelState} = State) ->
+ try Module:handle_call(Request, From, ChannelState) of
+ Result ->
+ handle_cb_result(Result, State)
+ catch
+ error:{undef, _} ->
+ {noreply, State}
+ end.
+
+
+%%--------------------------------------------------------------------
+%% Function: handle_cast(Msg, State) -> {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State}
+%% Description: Handling cast messages
+%%--------------------------------------------------------------------
+handle_cast(Msg, #state{channel_cb = Module,
+ channel_state = ChannelState} = State) ->
+
+ try Module:handle_cast(Msg, ChannelState) of
+ Result ->
+ handle_cb_result(Result, State)
+ catch
+ error:{undef, _} ->
+ {noreply, State}
+ end.
+
+%%--------------------------------------------------------------------
+%% Function: handle_info(Info, State) -> {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State}
+%% Description: Handling all non call/cast messages
+%%--------------------------------------------------------------------
+handle_info({ssh_cm, ConnectionManager, {closed, _ChannelId}},
+ #state{cm = ConnectionManager,
+ close_sent = true} = State) ->
+ {stop, normal, State};
+handle_info({ssh_cm, ConnectionManager, {closed, ChannelId}},
+ #state{cm = ConnectionManager,
+ close_sent = false} = State) ->
+ %% To be on the safe side, i.e. the manager has already been terminated.
+ (catch ssh_connection:close(ConnectionManager, ChannelId)),
+ {stop, normal, State};
+
+handle_info({ssh_cm, _, _} = Msg, #state{cm = ConnectionManager,
+ channel_cb = Module,
+ channel_state = ChannelState0} = State) ->
+ case Module:handle_ssh_msg(Msg, ChannelState0) of
+ {ok, ChannelState} ->
+ adjust_window(Msg),
+ {noreply, State#state{channel_state = ChannelState}};
+ {ok, ChannelState, Timeout} ->
+ adjust_window(Msg),
+ {noreply, State#state{channel_state = ChannelState}, Timeout};
+ {stop, ChannelId, ChannelState} ->
+ ssh_connection:close(ConnectionManager, ChannelId),
+ {stop, normal, State#state{close_sent = true,
+ channel_state = ChannelState}}
+ end;
+
+handle_info(Msg, #state{cm = ConnectionManager, channel_cb = Module,
+ channel_state = ChannelState0} = State) ->
+ case Module:handle_msg(Msg, ChannelState0) of
+ {ok, ChannelState} ->
+ {noreply, State#state{channel_state = ChannelState}};
+ {ok, ChannelState, Timeout} ->
+ {noreply, State#state{channel_state = ChannelState}, Timeout};
+ {stop, ChannelId, ChannelState} ->
+ ssh_connection:close(ConnectionManager, ChannelId),
+ {stop, normal, State#state{close_sent = true,
+ channel_state = ChannelState}}
+ end.
+
+%%--------------------------------------------------------------------
+%% Function: terminate(Reason, State) -> void()
+%% Description: This function is called by a gen_server when it is about to
+%% terminate. It should be the opposite of Module:init/1 and do any necessary
+%% cleaning up. When it returns, the gen_server terminates with Reason.
+%% The return value is ignored.
+%%--------------------------------------------------------------------
+terminate(Reason, #state{cm = ConnectionManager,
+ channel_id = ChannelId,
+ close_sent = false} = State) ->
+ ssh_connection:close(ConnectionManager, ChannelId),
+ terminate(Reason, State#state{close_sent = true});
+terminate(_, #state{channel_cb = Cb, channel_state = ChannelState}) ->
+ catch Cb:terminate(Cb, ChannelState),
+ ok.
+
+%%--------------------------------------------------------------------
+%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
+%% Description: Convert process state when code is changed
+%%--------------------------------------------------------------------
+code_change(OldVsn, #state{channel_cb = Module,
+ channel_state = ChannelState0} = State, Extra) ->
+ {ok, ChannelState} = Module:code_change(OldVsn, ChannelState0, Extra),
+ {ok, State#state{channel_state = ChannelState}}.
+
+%%====================================================================
+%% Internal application API
+%%====================================================================
+cache_create() ->
+ ets:new(cm_tab, [set,{keypos, #channel.local_id}]).
+
+cache_lookup(Cache, Key) ->
+ case ets:lookup(Cache, Key) of
+ [Channel] ->
+ Channel;
+ [] ->
+ undefined
+ end.
+
+cache_update(Cache, #channel{local_id = Id} = Entry) when Id =/= undefined ->
+ ets:insert(Cache, Entry).
+
+cache_delete(Cache, Key) ->
+ ets:delete(Cache, Key).
+
+cache_delete(Cache) ->
+ ets:delete(Cache).
+
+cache_foldl(Fun, Acc, Cache) ->
+ ets:foldl(Fun, Acc, Cache).
+
+cache_find(ChannelPid, Cache) ->
+ case ets:match_object(Cache, #channel{user = ChannelPid}) of
+ [] ->
+ undefined;
+ [Channel] ->
+ Channel
+ end.
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+handle_cb_result({reply, Reply, ChannelState}, State) ->
+ {reply, Reply, State#state{channel_state = ChannelState}};
+handle_cb_result({reply, Reply, ChannelState, Timeout}, State) ->
+ {reply, Reply,State#state{channel_state = ChannelState}, Timeout};
+handle_cb_result({noreply, ChannelState}, State) ->
+ {noreply, State#state{channel_state = ChannelState}};
+handle_cb_result({noreply, ChannelState, Timeout}, State) ->
+ {noreply, State#state{channel_state = ChannelState}, Timeout};
+handle_cb_result({stop, Reason, Reply, ChannelState}, State) ->
+ {stop, Reason, Reply, State#state{channel_state = ChannelState}};
+handle_cb_result({stop, Reason, ChannelState}, State) ->
+ {stop, Reason, State#state{channel_state = ChannelState}}.
+
+adjust_window({ssh_cm, ConnectionManager,
+ {data, ChannelId, _, Data}}) ->
+ ssh_connection:adjust_window(ConnectionManager, ChannelId, size(Data));
+adjust_window(_) ->
+ ok.
+
+
diff --git a/lib/ssh/src/ssh_channel_sup.erl b/lib/ssh/src/ssh_channel_sup.erl
new file mode 100644
index 0000000000..c184fed627
--- /dev/null
+++ b/lib/ssh/src/ssh_channel_sup.erl
@@ -0,0 +1,54 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2009. 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%
+%%
+
+%%
+%%----------------------------------------------------------------------
+%% Purpose: Ssh channel supervisor.
+%%----------------------------------------------------------------------
+-module(ssh_channel_sup).
+
+-behaviour(supervisor).
+
+-export([start_link/1, start_child/2]).
+
+%% Supervisor callback
+-export([init/1]).
+
+%%%=========================================================================
+%%% API
+%%%=========================================================================
+start_link(Args) ->
+ supervisor:start_link(?MODULE, [Args]).
+
+start_child(Sup, ChildSpec) ->
+ supervisor:start_child(Sup, ChildSpec).
+
+%%%=========================================================================
+%%% Supervisor callback
+%%%=========================================================================
+init(_Args) ->
+ RestartStrategy = one_for_one,
+ MaxR = 10,
+ MaxT = 3600,
+ Children = [],
+ {ok, {{RestartStrategy, MaxR, MaxT}, Children}}.
+
+%%%=========================================================================
+%%% Internal functions
+%%%=========================================================================
diff --git a/lib/ssh/src/ssh_cli.erl b/lib/ssh/src/ssh_cli.erl
new file mode 100644
index 0000000000..964f35121a
--- /dev/null
+++ b/lib/ssh/src/ssh_cli.erl
@@ -0,0 +1,500 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2009. 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%
+%%
+
+%%
+%% Description: a gen_server implementing a simple
+%% terminal (using the group module) for a CLI
+%% over SSH
+
+-module(ssh_cli).
+
+-behaviour(ssh_channel).
+
+-include("ssh.hrl").
+-include("ssh_connect.hrl").
+
+%% ssh_channel callbacks
+-export([init/1, handle_ssh_msg/2, handle_msg/2, terminate/2]).
+
+%% backwards compatibility
+-export([listen/1, listen/2, listen/3, listen/4, stop/1]).
+
+%% state
+-record(state, {
+ cm,
+ channel,
+ pty,
+ group,
+ buf,
+ shell,
+ exec
+ }).
+
+%%====================================================================
+%% ssh_channel callbacks
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% Function: init(Args) -> {ok, State}
+%%
+%% Description: Initiates the CLI
+%%--------------------------------------------------------------------
+init([Shell, Exec]) ->
+ {ok, #state{shell = Shell, exec = Exec}};
+init([Shell]) ->
+ {ok, #state{shell = Shell}}.
+
+%%--------------------------------------------------------------------
+%% Function: handle_ssh_msg(Args) -> {ok, State} | {stop, ChannelId, State}
+%%
+%% Description: Handles channel messages received on the ssh-connection.
+%%--------------------------------------------------------------------
+handle_ssh_msg({ssh_cm, _ConnectionManager,
+ {data, _ChannelId, _Type, Data}},
+ #state{group = Group} = State) ->
+ Group ! {self(), {data, binary_to_list(Data)}},
+ {ok, State};
+
+handle_ssh_msg({ssh_cm, ConnectionManager,
+ {pty, ChannelId, WantReply,
+ {TermName, Width, Height, PixWidth, PixHeight, Modes}}},
+ State0) ->
+ State = State0#state{pty =
+ #ssh_pty{term = TermName,
+ width = not_zero(Width, 80),
+ height = not_zero(Height, 24),
+ pixel_width = PixWidth,
+ pixel_height = PixHeight,
+ modes = Modes}},
+ set_echo(State),
+ ssh_connection:reply_request(ConnectionManager, WantReply,
+ success, ChannelId),
+ {ok, State};
+
+handle_ssh_msg({ssh_cm, ConnectionManager,
+ {env, ChannelId, WantReply, _Var, _Value}}, State) ->
+ ssh_connection:reply_request(ConnectionManager,
+ WantReply, failure, ChannelId),
+ {ok, State};
+
+handle_ssh_msg({ssh_cm, ConnectionManager,
+ {window_change, ChannelId, Width, Height, PixWidth, PixHeight}},
+ #state{buf = Buf, pty = Pty0} = State) ->
+ Pty = Pty0#ssh_pty{width = Width, height = Height,
+ pixel_width = PixWidth,
+ pixel_height = PixHeight},
+ {Chars, NewBuf} = io_request({window_change, Pty0}, Buf, Pty),
+ write_chars(ConnectionManager, ChannelId, Chars),
+ {ok, State#state{pty = Pty, buf = NewBuf}};
+
+handle_ssh_msg({ssh_cm, ConnectionManager,
+ {shell, ChannelId, WantReply}}, State) ->
+ NewState = start_shell(ConnectionManager, State),
+ ssh_connection:reply_request(ConnectionManager, WantReply,
+ success, ChannelId),
+ {ok, NewState#state{channel = ChannelId,
+ cm = ConnectionManager}};
+
+handle_ssh_msg({ssh_cm, ConnectionManager,
+ {exec, ChannelId, WantReply, Cmd}}, #state{exec=undefined} = State) ->
+ {Reply, Status} = exec(Cmd),
+ write_chars(ConnectionManager,
+ ChannelId, io_lib:format("~p\n", [Reply])),
+ ssh_connection:reply_request(ConnectionManager, WantReply,
+ success, ChannelId),
+ ssh_connection:exit_status(ConnectionManager, ChannelId, Status),
+ ssh_connection:send_eof(ConnectionManager, ChannelId),
+ {stop, ChannelId, State#state{channel = ChannelId, cm = ConnectionManager}};
+handle_ssh_msg({ssh_cm, ConnectionManager,
+ {exec, ChannelId, WantReply, Cmd}}, State) ->
+ NewState = start_shell(ConnectionManager, Cmd, State),
+ ssh_connection:reply_request(ConnectionManager, WantReply,
+ success, ChannelId),
+ {ok, NewState#state{channel = ChannelId,
+ cm = ConnectionManager}};
+
+handle_ssh_msg({ssh_cm, _ConnectionManager, {eof, _ChannelId}}, State) ->
+ {ok, State};
+
+handle_ssh_msg({ssh_cm, _, {signal, _, _}}, State) ->
+ %% Ignore signals according to RFC 4254 section 6.9.
+ {ok, State};
+
+handle_ssh_msg({ssh_cm, _, {exit_signal, ChannelId, _, Error, _}}, State) ->
+ Report = io_lib:format("Connection closed by peer ~n Error ~p~n",
+ [Error]),
+ error_logger:error_report(Report),
+ {stop, ChannelId, State};
+
+handle_ssh_msg({ssh_cm, _, {exit_status, ChannelId, 0}}, State) ->
+ {stop, ChannelId, State};
+
+handle_ssh_msg({ssh_cm, _, {exit_status, ChannelId, Status}}, State) ->
+
+ Report = io_lib:format("Connection closed by peer ~n Status ~p~n",
+ [Status]),
+ error_logger:error_report(Report),
+ {stop, ChannelId, State}.
+
+%%--------------------------------------------------------------------
+%% Function: handle_msg(Args) -> {ok, State} | {stop, ChannelId, State}
+%%
+%% Description: Handles other channel messages.
+%%--------------------------------------------------------------------
+handle_msg({ssh_channel_up, ChannelId, ConnectionManager},
+ #state{channel = ChannelId,
+ cm = ConnectionManager} = State) ->
+ {ok, State};
+
+handle_msg({Group, Req}, #state{group = Group, buf = Buf, pty = Pty,
+ cm = ConnectionManager,
+ channel = ChannelId} = State) ->
+ {Chars, NewBuf} = io_request(Req, Buf, Pty),
+ write_chars(ConnectionManager, ChannelId, Chars),
+ {ok, State#state{buf = NewBuf}};
+
+handle_msg({'EXIT', Group, _Reason}, #state{group = Group,
+ channel = ChannelId} = State) ->
+ {stop, ChannelId, State};
+
+handle_msg(_, State) ->
+ {ok, State}.
+
+%%--------------------------------------------------------------------
+%% Function: terminate(Reason, State) -> void()
+%% Description: Called when the channel process is trminated
+%%--------------------------------------------------------------------
+terminate(_Reason, _State) ->
+ ok.
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+
+exec(Cmd) ->
+ eval(parse(scan(Cmd))).
+
+scan(Cmd) ->
+ erl_scan:string(Cmd).
+
+parse({ok, Tokens, _}) ->
+ erl_parse:parse_exprs(Tokens);
+parse(Error) ->
+ Error.
+
+eval({ok, Expr_list}) ->
+ case (catch erl_eval:exprs(Expr_list,
+ erl_eval:new_bindings())) of
+ {value, Value, _NewBindings} ->
+ {Value, 0};
+ {'EXIT', {Error, _}} ->
+ {Error, -1};
+ Error ->
+ {Error, -1}
+ end;
+eval(Error) ->
+ {Error, -1}.
+
+%%% io_request, handle io requests from the user process,
+%%% Note, this is not the real I/O-protocol, but the mockup version
+%%% used between edlin and a user_driver. The protocol tags are
+%%% similar, but the message set is different.
+%%% The protocol only exists internally between edlin and a character
+%%% displaying device...
+%%% We are *not* really unicode aware yet, we just filter away characters
+%%% beyond the latin1 range. We however handle the unicode binaries...
+io_request({window_change, OldTty}, Buf, Tty) ->
+ window_change(Tty, OldTty, Buf);
+io_request({put_chars, Cs}, Buf, Tty) ->
+ put_chars(bin_to_list(Cs), Buf, Tty);
+io_request({put_chars, unicode, Cs}, Buf, Tty) ->
+ put_chars([Ch || Ch <- unicode:characters_to_list(Cs,unicode), Ch =< 255], Buf, Tty);
+io_request({insert_chars, Cs}, Buf, Tty) ->
+ insert_chars(bin_to_list(Cs), Buf, Tty);
+io_request({insert_chars, unicode, Cs}, Buf, Tty) ->
+ insert_chars([Ch || Ch <- unicode:characters_to_list(Cs,unicode), Ch =< 255], Buf, Tty);
+io_request({move_rel, N}, Buf, Tty) ->
+ move_rel(N, Buf, Tty);
+io_request({delete_chars,N}, Buf, Tty) ->
+ delete_chars(N, Buf, Tty);
+io_request(beep, Buf, _Tty) ->
+ {[7], Buf};
+
+%% New in R12
+io_request({get_geometry,columns},Buf,Tty) ->
+ {ok, Tty#ssh_pty.width, Buf};
+io_request({get_geometry,rows},Buf,Tty) ->
+ {ok, Tty#ssh_pty.height, Buf};
+io_request({requests,Rs}, Buf, Tty) ->
+ io_requests(Rs, Buf, Tty, []);
+io_request(tty_geometry, Buf, Tty) ->
+ io_requests([{move_rel, 0}, {put_chars, unicode, [10]}], Buf, Tty, []);
+ %{[], Buf};
+io_request(_R, Buf, _Tty) ->
+ {[], Buf}.
+
+io_requests([R|Rs], Buf, Tty, Acc) ->
+ {Chars, NewBuf} = io_request(R, Buf, Tty),
+ io_requests(Rs, NewBuf, Tty, [Acc|Chars]);
+io_requests([], Buf, _Tty, Acc) ->
+ {Acc, Buf}.
+
+%%% return commands for cursor navigation, assume everything is ansi
+%%% (vt100), add clauses for other terminal types if needed
+ansi_tty(N, L) ->
+ ["\e[", integer_to_list(N), L].
+
+get_tty_command(up, N, _TerminalType) ->
+ ansi_tty(N, $A);
+get_tty_command(down, N, _TerminalType) ->
+ ansi_tty(N, $B);
+get_tty_command(right, N, _TerminalType) ->
+ ansi_tty(N, $C);
+get_tty_command(left, N, _TerminalType) ->
+ ansi_tty(N, $D).
+
+
+-define(PAD, 10).
+-define(TABWIDTH, 8).
+
+%% convert input characters to buffer and to writeout
+%% Note that the buf is reversed but the buftail is not
+%% (this is handy; the head is always next to the cursor)
+conv_buf([], AccBuf, AccBufTail, AccWrite, Col) ->
+ {AccBuf, AccBufTail, lists:reverse(AccWrite), Col};
+conv_buf([13, 10 | Rest], _AccBuf, AccBufTail, AccWrite, _Col) ->
+ conv_buf(Rest, [], tl2(AccBufTail), [10, 13 | AccWrite], 0);
+conv_buf([13 | Rest], _AccBuf, AccBufTail, AccWrite, _Col) ->
+ conv_buf(Rest, [], tl1(AccBufTail), [13 | AccWrite], 0);
+conv_buf([10 | Rest], _AccBuf, AccBufTail, AccWrite, _Col) ->
+ conv_buf(Rest, [], tl1(AccBufTail), [10, 13 | AccWrite], 0);
+conv_buf([C | Rest], AccBuf, AccBufTail, AccWrite, Col) ->
+ conv_buf(Rest, [C | AccBuf], tl1(AccBufTail), [C | AccWrite], Col + 1).
+
+
+%%% put characters at current position (possibly overwriting
+%%% characters after current position in buffer)
+put_chars(Chars, {Buf, BufTail, Col}, _Tty) ->
+ {NewBuf, NewBufTail, WriteBuf, NewCol} =
+ conv_buf(Chars, Buf, BufTail, [], Col),
+ {WriteBuf, {NewBuf, NewBufTail, NewCol}}.
+
+%%% insert character at current position
+insert_chars([], {Buf, BufTail, Col}, _Tty) ->
+ {[], {Buf, BufTail, Col}};
+insert_chars(Chars, {Buf, BufTail, Col}, Tty) ->
+ {NewBuf, _NewBufTail, WriteBuf, NewCol} =
+ conv_buf(Chars, Buf, [], [], Col),
+ M = move_cursor(NewCol + length(BufTail), NewCol, Tty),
+ {[WriteBuf, BufTail | M], {NewBuf, BufTail, NewCol}}.
+
+%%% delete characters at current position, (backwards if negative argument)
+delete_chars(0, {Buf, BufTail, Col}, _Tty) ->
+ {[], {Buf, BufTail, Col}};
+delete_chars(N, {Buf, BufTail, Col}, Tty) when N > 0 ->
+ NewBufTail = nthtail(N, BufTail),
+ M = move_cursor(Col + length(NewBufTail) + N, Col, Tty),
+ {[NewBufTail, lists:duplicate(N, $ ) | M],
+ {Buf, NewBufTail, Col}};
+delete_chars(N, {Buf, BufTail, Col}, Tty) -> % N < 0
+ NewBuf = nthtail(-N, Buf),
+ NewCol = Col + N,
+ M1 = move_cursor(Col, NewCol, Tty),
+ M2 = move_cursor(NewCol + length(BufTail) - N, NewCol, Tty),
+ {[M1, BufTail, lists:duplicate(-N, $ ) | M2],
+ {NewBuf, BufTail, NewCol}}.
+
+%%% Window change, redraw the current line (and clear out after it
+%%% if current window is wider than previous)
+window_change(Tty, OldTty, Buf)
+ when OldTty#ssh_pty.width == Tty#ssh_pty.width ->
+ {[], Buf};
+window_change(Tty, OldTty, {Buf, BufTail, Col}) ->
+ M1 = move_cursor(Col, 0, OldTty),
+ N = max(Tty#ssh_pty.width - OldTty#ssh_pty.width, 0) * 2,
+ S = lists:reverse(Buf, [BufTail | lists:duplicate(N, $ )]),
+ M2 = move_cursor(length(Buf) + length(BufTail) + N, Col, Tty),
+ {[M1, S | M2], {Buf, BufTail, Col}}.
+
+%% move around in buffer, respecting pad characters
+step_over(0, Buf, [?PAD | BufTail], Col) ->
+ {[?PAD | Buf], BufTail, Col+1};
+step_over(0, Buf, BufTail, Col) ->
+ {Buf, BufTail, Col};
+step_over(N, [C | Buf], BufTail, Col) when N < 0 ->
+ N1 = ifelse(C == ?PAD, N, N+1),
+ step_over(N1, Buf, [C | BufTail], Col-1);
+step_over(N, Buf, [C | BufTail], Col) when N > 0 ->
+ N1 = ifelse(C == ?PAD, N, N-1),
+ step_over(N1, [C | Buf], BufTail, Col+1).
+
+%%% an empty line buffer
+empty_buf() -> {[], [], 0}.
+
+%%% col and row from position with given width
+col(N, W) -> N rem W.
+row(N, W) -> N div W.
+
+%%% move relative N characters
+move_rel(N, {Buf, BufTail, Col}, Tty) ->
+ {NewBuf, NewBufTail, NewCol} = step_over(N, Buf, BufTail, Col),
+ M = move_cursor(Col, NewCol, Tty),
+ {M, {NewBuf, NewBufTail, NewCol}}.
+
+%%% give move command for tty
+move_cursor(A, A, _Tty) ->
+ [];
+move_cursor(From, To, #ssh_pty{width=Width, term=Type}) ->
+ Tcol = case col(To, Width) - col(From, Width) of
+ 0 -> "";
+ I when I < 0 -> get_tty_command(left, -I, Type);
+ I -> get_tty_command(right, I, Type)
+ end,
+ Trow = case row(To, Width) - row(From, Width) of
+ 0 -> "";
+ J when J < 0 -> get_tty_command(up, -J, Type);
+ J -> get_tty_command(down, J, Type)
+ end,
+ [Tcol | Trow].
+
+%% %%% write out characters
+%% %%% make sure that there is data to send
+%% %%% before calling ssh_connection:send
+write_chars(ConnectionManager, ChannelId, Chars) ->
+ case erlang:iolist_size(Chars) of
+ 0 ->
+ ok;
+ _ ->
+ ssh_connection:send(ConnectionManager, ChannelId,
+ ?SSH_EXTENDED_DATA_DEFAULT, Chars)
+ end.
+
+%%% tail, works with empty lists
+tl1([_|A]) -> A;
+tl1(_) -> [].
+
+%%% second tail
+tl2([_,_|A]) -> A;
+tl2(_) -> [].
+
+%%% nthtail as in lists, but no badarg if n > the length of list
+nthtail(0, A) -> A;
+nthtail(N, [_ | A]) when N > 0 -> nthtail(N-1, A);
+nthtail(_, _) -> [].
+
+%%% utils
+max(A, B) when A > B -> A;
+max(_A, B) -> B.
+
+ifelse(Cond, A, B) ->
+ case Cond of
+ true -> A;
+ _ -> B
+ end.
+
+bin_to_list(B) when is_binary(B) ->
+ binary_to_list(B);
+bin_to_list(L) when is_list(L) ->
+ lists:flatten([bin_to_list(A) || A <- L]);
+bin_to_list(I) when is_integer(I) ->
+ I.
+
+start_shell(ConnectionManager, State) ->
+ Shell = State#state.shell,
+ ShellFun = case is_function(Shell) of
+ true ->
+ case erlang:fun_info(Shell, arity) of
+ {arity, 1} ->
+ {ok, User} =
+ ssh_userreg:lookup_user(ConnectionManager),
+ fun() -> Shell(User) end;
+ {arity, 2} ->
+ {ok, User} =
+ ssh_userreg:lookup_user(ConnectionManager),
+ {ok, PeerAddr} =
+ ssh_cm:get_peer_addr(ConnectionManager),
+ fun() -> Shell(User, PeerAddr) end;
+ _ ->
+ Shell
+ end;
+ _ ->
+ Shell
+ end,
+ Echo = get_echo(State#state.pty),
+ Group = group:start(self(), ShellFun, [{echo, Echo}]),
+ State#state{group = Group, buf = empty_buf()}.
+
+start_shell(_ConnectionManager, Cmd, #state{exec={M, F, A}} = State) ->
+ Group = group:start(self(), {M, F, A++[Cmd]}, [{echo,false}]),
+ State#state{group = Group, buf = empty_buf()}.
+
+
+% Pty can be undefined if the client never sets any pty options before
+% starting the shell.
+get_echo(undefined) ->
+ true;
+get_echo(#ssh_pty{modes = Modes}) ->
+ case proplists:get_value(echo, Modes, 1) of
+ 0 ->
+ false;
+ _ ->
+ true
+ end.
+
+% Group is undefined if the pty options are sent between open and
+% shell messages.
+set_echo(#state{group = undefined}) ->
+ ok;
+set_echo(#state{group = Group, pty = Pty}) ->
+ Echo = get_echo(Pty),
+ Group ! {self(), echo, Echo}.
+
+not_zero(0, B) ->
+ B;
+not_zero(A, _) ->
+ A.
+
+%%% Backwards compatibility
+
+%%--------------------------------------------------------------------
+%% Function: listen(...) -> {ok,Pid} | ignore | {error,Error}
+%% Description: Starts a listening server
+%% Note that the pid returned is NOT the pid of this gen_server;
+%% this server is started when an SSH connection is made on the
+%% listening port
+%%--------------------------------------------------------------------
+listen(Shell) ->
+ listen(Shell, 22).
+
+listen(Shell, Port) ->
+ listen(Shell, Port, []).
+
+listen(Shell, Port, Opts) ->
+ listen(Shell, any, Port, Opts).
+
+listen(Shell, HostAddr, Port, Opts) ->
+ ssh:daemon(HostAddr, Port, [{shell, Shell} | Opts]).
+
+
+%%--------------------------------------------------------------------
+%% Function: stop(Pid) -> ok
+%% Description: Stops the listener
+%%--------------------------------------------------------------------
+stop(Pid) ->
+ ssh:stop_listener(Pid).
diff --git a/lib/ssh/src/ssh_cm.erl b/lib/ssh/src/ssh_cm.erl
new file mode 100755
index 0000000000..c4d535df9a
--- /dev/null
+++ b/lib/ssh/src/ssh_cm.erl
@@ -0,0 +1,237 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2009. 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%
+%%
+
+%%
+
+%%% Description : Backwards compatibility wrapper
+
+-module(ssh_cm).
+
+-include("ssh.hrl").
+-include("ssh_connect.hrl").
+
+%% -define(DEFAULT_PACKET_SIZE, 32768).
+%% -define(DEFAULT_WINDOW_SIZE, 2*?DEFAULT_PACKET_SIZE).
+%%-define(DEFAULT_TIMEOUT, 5000).
+
+-export([connect/1, connect/2, connect/3]).
+-export([listen/2, listen/3, listen/4, stop_listener/1]).
+-export([stop/1]).
+
+-deprecated({connect, 1, next_major_release}).
+-deprecated({connect, 2, next_major_release}).
+-deprecated({connect, 3, next_major_release}).
+-deprecated({listen, 2, next_major_release}).
+-deprecated({listen, 3, next_major_release}).
+-deprecated({listen, 4, next_major_release}).
+-deprecated({stop_listener, 1, next_major_release}).
+-deprecated({stop, 1, next_major_release}).
+
+-export([adjust_window/3, attach/2, attach/3, detach/2,
+ tcpip_forward/3, cancel_tcpip_forward/3, direct_tcpip/6,
+ direct_tcpip/8, close/2, shell/2, exec/4,
+ send/3, send/4,
+ send_ack/3, send_ack/4, send_ack/5, send_eof/2,
+ session_open/2, session_open/4, subsystem/4,
+ open_pty/3, open_pty/7, open_pty/9,
+ set_user_ack/4,
+ setenv/5, signal/3, winch/4]).
+
+-deprecated({adjust_window, 3, next_major_release}).
+-deprecated({attach, 2, next_major_release}).
+-deprecated({attach, 3, next_major_release}).
+-deprecated({detach, 2, next_major_release}).
+-deprecated({tcpip_forward, 3, next_major_release}).
+-deprecated({cancel_tcpip_forward, 3, next_major_release}).
+-deprecated({direct_tcpip, 6, next_major_release}).
+-deprecated({direct_tcpip, 8, next_major_release}).
+-deprecated({close, 2, next_major_release}).
+-deprecated({shell, 2, next_major_release}).
+-deprecated({exec, 4, next_major_release}).
+-deprecated({send, 3, next_major_release}).
+-deprecated({send, 4, next_major_release}).
+-deprecated({send_ack, 3, next_major_release}).
+-deprecated({send_ack, 4, next_major_release}).
+-deprecated({send_ack, 5, next_major_release}).
+-deprecated({send_eof, 2, next_major_release}).
+-deprecated({session_open, 2, next_major_release}).
+-deprecated({session_open, 4, next_major_release}).
+-deprecated({subsystem, 4, next_major_release}).
+-deprecated({open_pty, 3, next_major_release}).
+-deprecated({open_pty, 7, next_major_release}).
+-deprecated({open_pty, 9, next_major_release}).
+-deprecated({set_user_ack, 4, next_major_release}).
+-deprecated({setenv, 5, next_major_release}).
+-deprecated({signal, 3, next_major_release}).
+-deprecated({winch, 4, next_major_release}).
+
+-export([info/1, info/2, recv_window/3,
+ send_window/3, renegotiate/1, renegotiate/2,
+ get_peer_addr/1]).
+
+%%====================================================================
+%% API
+%%====================================================================
+connect(Host) ->
+ connect(Host, []).
+connect(Host, Opts) ->
+ connect(Host, ?SSH_DEFAULT_PORT, Opts).
+connect(Host, Port, Opts) ->
+ ssh:connect(Host, Port, Opts).
+
+listen(ChannelSpec, Port) ->
+ listen(ChannelSpec, Port, []).
+listen(ChannelSpec, Port, Opts) ->
+ listen(ChannelSpec, any, Port, Opts).
+listen(ChannelSpec, "localhost", Port, Opts) ->
+ listen(ChannelSpec, any, Port, Opts);
+listen(_ChannelSpec, Host, Port, Opts) ->
+ ssh:daemon(Host, Port, Opts).
+
+stop_listener(SysSup) ->
+ ssh_system_sup:stop_listener(SysSup).
+stop(Cm) ->
+ ssh:close(Cm).
+
+%% CM Client commands
+session_open(Cm, Timeout) ->
+ session_open(Cm, ?DEFAULT_WINDOW_SIZE, ?DEFAULT_PACKET_SIZE, Timeout).
+
+session_open(Cm, InitialWindowSize, MaxPacketSize, Timeout) ->
+ ssh_connection:session_channel(Cm, InitialWindowSize, MaxPacketSize,
+ Timeout).
+
+
+setenv(Cm, Channel, Var, Value, Timeout) ->
+ ssh_connection:setenv(Cm, Channel, Var, Value, Timeout).
+
+shell(Cm, Channel) ->
+ ssh_connection:shell(Cm, Channel).
+
+exec(Cm, Channel, Command, Timeout) ->
+ ssh_connection:exec(Cm, Channel, Command, Timeout).
+
+subsystem(Cm, Channel, SubSystem, Timeout) ->
+ ssh_connection:subsystem(Cm, Channel, SubSystem, Timeout).
+
+%% Not needed for backwards compatibility for now
+attach(_Cm, _Timeout) ->
+ ok.
+
+attach(_Cm, _ChannelPid, _Timeout) ->
+ ok.
+
+detach(_Cm, _Timeout) ->
+ ok.
+
+%% Not needed, send_ack is now call! Temp backwardcompability
+set_user_ack(_, _, _, _) ->
+ ok.
+
+adjust_window(Cm, Channel, Bytes) ->
+ ssh_connection:adjust_window(Cm, Channel, Bytes).
+
+close(Cm, Channel) ->
+ ssh_connection:close(Cm, Channel).
+
+send_eof(Cm, Channel) ->
+ ssh_connection:send_eof(Cm, Channel).
+
+send(Cm, Channel, Data) ->
+ ssh_connection:send(Cm, Channel, 0, Data).
+
+send(Cm, Channel, Type, Data) ->
+ ssh_connection:send(Cm, Channel, Type, Data).
+
+%% Send ack is not needed
+send_ack(Cm, Channel, Data) ->
+ send_ack(Cm, Channel, 0, Data, infinity).
+
+send_ack(Cm, Channel, Type, Data) ->
+ send_ack(Cm, Channel, Type, Data, infinity).
+
+send_ack(Cm, Channel, Type, Data, Timeout) ->
+ ssh_connection:send(Cm, Channel, Type, Data, Timeout).
+
+%% ----------------------------------------------------------------------
+%% These functions replacers are not officially supported but proably will be
+%% when we had time to test them.
+%% ----------------------------------------------------------------------
+direct_tcpip(Cm, RemoteHost, RemotePort, OrigIP, OrigPort, Timeout) ->
+ direct_tcpip(Cm, RemoteHost, RemotePort, OrigIP, OrigPort,
+ ?DEFAULT_WINDOW_SIZE, ?DEFAULT_PACKET_SIZE, Timeout).
+
+direct_tcpip(Cm, RemoteIP, RemotePort, OrigIP, OrigPort,
+ InitialWindowSize, MaxPacketSize, Timeout) ->
+ ssh_connection:direct_tcpip(Cm, RemoteIP, RemotePort,
+ OrigIP, OrigPort,
+ InitialWindowSize,
+ MaxPacketSize, Timeout).
+
+tcpip_forward(Cm, BindIP, BindPort) ->
+ ssh_connection:tcpip_forward(Cm, BindIP, BindPort).
+
+cancel_tcpip_forward(Cm, BindIP, Port) ->
+ ssh_connection:cancel_tcpip_forward(Cm, BindIP, Port).
+
+open_pty(Cm, Channel, Timeout) ->
+ open_pty(Cm, Channel, os:getenv("TERM"), 80, 24, [], Timeout).
+
+open_pty(Cm, Channel, Term, Width, Height, PtyOpts, Timeout) ->
+ open_pty(Cm, Channel, Term, Width, Height, 0, 0, PtyOpts, Timeout).
+
+open_pty(Cm, Channel, Term, Width, Height, PixWidth, PixHeight,
+ PtyOpts, Timeout) ->
+ ssh_connection:open_pty(Cm, Channel, Term,
+ Width, Height, PixWidth,
+ PixHeight, PtyOpts, Timeout).
+winch(Cm, Channel, Width, Height) ->
+ winch(Cm, Channel, Width, Height, 0, 0).
+winch(Cm, Channel, Width, Height, PixWidth, PixHeight) ->
+ ssh_connection:window_change(Cm, Channel, Width,
+ Height, PixWidth, PixHeight).
+signal(Cm, Channel, Sig) ->
+ ssh_connection:signal(Cm, Channel, Sig).
+
+%% ----------------------------------------------------------------------
+%% These functions replacers are not officially supported and
+%% the format of them will proably change when and
+%% if they get supported.
+%% ----------------------------------------------------------------------
+info(Cm) ->
+ info(Cm, all).
+
+info(Cm, ChannelPid) ->
+ ssh_connection_manager:info(Cm, ChannelPid).
+
+send_window(Cm, Channel, Timeout) ->
+ ssh_connection_manager:send_window(Cm, Channel, Timeout).
+
+recv_window(Cm, Channel, Timeout) ->
+ ssh_connection_manager:recv_window(Cm, Channel, Timeout).
+
+renegotiate(Cm) ->
+ renegotiate(Cm, []).
+renegotiate(Cm, _Opts) ->
+ %%TODO: How should this work, backwards compat?
+ ssh_connection_manager:renegotiate(Cm).
+
+get_peer_addr(Cm) ->
+ ssh_connection_manager:peer_addr(Cm).
+
diff --git a/lib/ssh/src/ssh_connect.hrl b/lib/ssh/src/ssh_connect.hrl
new file mode 100755
index 0000000000..a5a4e42cd8
--- /dev/null
+++ b/lib/ssh/src/ssh_connect.hrl
@@ -0,0 +1,264 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2009. 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%
+%%
+
+%%
+
+%%% Description : SSH connection protocol
+
+-define(DEFAULT_PACKET_SIZE, 32768).
+-define(DEFAULT_WINDOW_SIZE, 2*?DEFAULT_PACKET_SIZE).
+-define(DEFAULT_TIMEOUT, 5000).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%
+%% CONNECT messages
+%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%%----------------------------------------------------------------------
+%%% # SSH_MSG_xxx
+%%% Description: Packet types used by the connection protocol.
+%%%----------------------------------------------------------------------
+-define(SSH_MSG_GLOBAL_REQUEST, 80).
+-define(SSH_MSG_REQUEST_SUCCESS, 81).
+-define(SSH_MSG_REQUEST_FAILURE, 82).
+-define(SSH_MSG_CHANNEL_OPEN, 90).
+-define(SSH_MSG_CHANNEL_OPEN_CONFIRMATION, 91).
+-define(SSH_MSG_CHANNEL_OPEN_FAILURE, 92).
+-define(SSH_MSG_CHANNEL_WINDOW_ADJUST, 93).
+-define(SSH_MSG_CHANNEL_DATA, 94).
+-define(SSH_MSG_CHANNEL_EXTENDED_DATA, 95).
+-define(SSH_MSG_CHANNEL_EOF, 96).
+-define(SSH_MSG_CHANNEL_CLOSE, 97).
+-define(SSH_MSG_CHANNEL_REQUEST, 98).
+-define(SSH_MSG_CHANNEL_SUCCESS, 99).
+-define(SSH_MSG_CHANNEL_FAILURE, 100).
+
+-record(ssh_msg_global_request,
+ {
+ name,
+ want_reply,
+ data %% ...
+ }).
+
+-record(ssh_msg_request_success,
+ {
+ data %% ...
+ }).
+
+-record(ssh_msg_request_failure,
+ {
+ }).
+
+
+-record(ssh_msg_channel_open,
+ {
+ channel_type,
+ sender_channel,
+ initial_window_size,
+ maximum_packet_size,
+ data %% ...
+ }).
+
+-record(ssh_msg_channel_open_confirmation,
+ {
+ recipient_channel,
+ sender_channel,
+ initial_window_size,
+ maximum_packet_size,
+ data %% ...
+ }).
+
+
+%%%----------------------------------------------------------------------
+%%% # SSH_OPEN_xxx
+%%% Description: Reason codes for SSH_MSG_OPEN_FAILURE packages.
+%%%----------------------------------------------------------------------
+
+-define(SSH_OPEN_ADMINISTRATIVELY_PROHIBITED, 1).
+-define(SSH_OPEN_CONNECT_FAILED, 2).
+-define(SSH_OPEN_UNKNOWN_CHANNEL_TYPE, 3).
+-define(SSH_OPEN_RESOURCE_SHORTAGE, 4).
+
+-record(ssh_msg_channel_open_failure,
+ {
+ recipient_channel,
+ reason,
+ description,
+ lang
+ }).
+
+
+-record(ssh_msg_channel_window_adjust,
+ {
+ recipient_channel,
+ bytes_to_add
+ }).
+
+-record(ssh_msg_channel_data,
+ {
+ recipient_channel,
+ data
+ }).
+
+%%%----------------------------------------------------------------------
+%%% # SSH_EXTENDED_DATA_xxx
+%%% Description: Type codes for SSH_MSG_CHANNEL_EXTENDED_DATA packages
+%%%----------------------------------------------------------------------
+-define(SSH_EXTENDED_DATA_DEFAULT, 0).
+-define(SSH_EXTENDED_DATA_STDERR, 1).
+
+-record(ssh_msg_channel_extended_data,
+ {
+ recipient_channel,
+ data_type_code,
+ data
+ }).
+
+-record(ssh_msg_channel_eof,
+ {
+ recipient_channel
+ }).
+
+-record(ssh_msg_channel_close,
+ {
+ recipient_channel
+ }).
+
+
+-record(ssh_msg_channel_request,
+ {
+ recipient_channel,
+ request_type,
+ want_reply,
+ data %% ...
+ }).
+
+
+-record(ssh_msg_channel_success,
+ {
+ recipient_channel
+ }).
+
+
+-record(ssh_msg_channel_failure,
+ {
+ recipient_channel
+ }).
+
+-define(TTY_OP_END,0). %% Indicates end of options.
+-define(VINTR,1). %% Interrupt character; 255 if none. Similarly for the
+ %% other characters. Not all of these characters are
+ %% supported on all systems.
+-define(VQUIT,2). %% The quit character (sends SIGQUIT signal on POSIX
+ %% systems).
+-define(VERASE,3). %% Erase the character to left of the cursor.
+-define(VKILL,4). %% Kill the current input line.
+-define(VEOF,5). %% End-of-file character (sends EOF from the terminal).
+-define(VEOL,6). %% End-of-line character in addition to carriage return
+ %% or,and). linefeed.
+-define(VEOL2,7). %% Additional end-of-line character.
+-define(VSTART,8). %% Continues paused output (normally control-Q).
+-define(VSTOP,9). %% Pauses output (normally control-S).
+-define(VSUSP,10). %% Suspends the current program.
+-define(VDSUSP,11). %% Another suspend character.
+-define(VREPRINT,12). %% Reprints the current input line.
+-define(VWERASE,13). %% Erases a word left of cursor.
+-define(VLNEXT,14). %% Enter the next character typed literally, even if it
+ %% is a special character
+-define(VFLUSH,15). %% Character to flush output.
+-define(VSWTCH,16). %% Switch to a different shell layer.
+-define(VSTATUS,17). %% Prints system status line (load, command, pid etc).
+-define(VDISCARD,18). %% Toggles the flushing of terminal output.
+-define(IGNPAR,30). %% The ignore parity flag. The parameter SHOULD be 0 if
+ %% this flag is FALSE set, and 1 if it is TRUE.
+-define(PARMRK,31). %% Mark parity and framing errors.
+-define(INPCK,32). %% Enable checking of parity errors.
+-define(ISTRIP,33). %% Strip 8th bit off characters.
+-define(INLCR,34). %% Map NL into CR on input.
+-define(IGNCR,35). %% Ignore CR on input.
+-define(ICRNL,36). %% Map CR to NL on input.
+-define(IUCLC,37). %% Translate uppercase characters to lowercase.
+-define(IXON,38). %% Enable output flow control.
+-define(IXANY,39). %% Any char will restart after stop.
+-define(IXOFF,40). %% Enable input flow control.
+-define(IMAXBEL,41). %% Ring bell on input queue full.
+-define(ISIG,50). %% Enable signals INTR, QUIT, [D]SUSP.
+-define(ICANON,51). %% Canonicalize input lines.
+-define(XCASE,52). %% Enable input and output of uppercase characters by
+ %% preceding their lowercase equivalents with `\'.
+-define(ECHO,53). %% Enable echoing.
+-define(ECHOE,54). %% Visually erase chars.
+-define(ECHOK,55). %% Kill character discards current line.
+-define(ECHONL,56). %% Echo NL even if ECHO is off.
+-define(NOFLSH,57). %% Don't flush after interrupt.
+-define(TOSTOP,58). %% Stop background jobs from output.
+-define(IEXTEN,59). %% Enable extensions.
+-define(ECHOCTL,60). %% Echo control characters as ^(Char).
+-define(ECHOKE,61). %% Visual erase for line kill.
+-define(PENDIN,62). %% Retype pending input.
+-define(OPOST,70). %% Enable output processing.
+-define(OLCUC,71). %% Convert lowercase to uppercase.
+-define(ONLCR,72). %% Map NL to CR-NL.
+-define(OCRNL,73). %% Translate carriage return to newline (output).
+-define(ONOCR,74). %% Translate newline to carriage return-newline
+ %% (output).
+-define(ONLRET,75). %% Newline performs a carriage return (output).
+-define(CS7,90). %% 7 bit mode.
+-define(CS8,91). %% 8 bit mode.
+-define(PARENB,92). %% Parity enable.
+-define(PARODD,93). %% Odd parity, else even.
+
+%% Specifies the input baud rate in bits per second.
+-define(TTY_OP_ISPEED,128).
+%% Specifies the output baud rate in bits per second.
+-define(TTY_OP_OSPEED,129).
+
+-record(channel,
+ {
+ type, %% "session", "x11", "forwarded-tcpip", "direct-tcpip"
+ sys, %% "none", "shell", "exec" "subsystem"
+ user, %% "user" process id (default to cm user)
+ flow_control,
+
+ local_id, %% local channel id
+
+ recv_window_size,
+ recv_packet_size,
+ recv_close = false,
+
+ remote_id, %% remote channel id
+ send_window_size,
+ send_packet_size,
+ sent_close = false,
+ send_buf = []
+ }).
+
+-record(connection, {
+ requests = [], %% [{ChannelId, Pid}...] awaiting reply on request,
+ channel_cache,
+ channel_pids = [],
+ port_bindings,
+ channel_id_seed,
+ cli_spec,
+ address,
+ port,
+ options,
+ exec
+ }).
diff --git a/lib/ssh/src/ssh_connection.erl b/lib/ssh/src/ssh_connection.erl
new file mode 100644
index 0000000000..0aaf1c18d2
--- /dev/null
+++ b/lib/ssh/src/ssh_connection.erl
@@ -0,0 +1,1366 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2009. 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%
+%%
+
+%%
+
+%%----------------------------------------------------------------------
+%% Purpose: Details of connection protocol
+%%----------------------------------------------------------------------
+
+-module(ssh_connection).
+
+-include("ssh.hrl").
+-include("ssh_connect.hrl").
+-include("ssh_transport.hrl").
+
+-export([session_channel/2, session_channel/4,
+ exec/4, shell/2, subsystem/4, send/3, send/4, send/5,
+ send_eof/2, adjust_window/3, open_pty/3, open_pty/7,
+ open_pty/9, setenv/5, window_change/4, window_change/6,
+ direct_tcpip/6, direct_tcpip/8, tcpip_forward/3,
+ cancel_tcpip_forward/3, signal/3, exit_status/3, encode_ip/1, close/2,
+ reply_request/4]).
+
+-export([channel_data/6, handle_msg/4, channel_eof_msg/1,
+ channel_close_msg/1, channel_success_msg/1, channel_failure_msg/1,
+ channel_adjust_window_msg/2, channel_data_msg/3,
+ channel_open_msg/5, channel_open_confirmation_msg/4,
+ channel_open_failure_msg/4, channel_request_msg/4,
+ global_request_msg/3, request_failure_msg/0,
+ request_success_msg/1, bind/4, unbind/3, unbind_channel/2,
+ bound_channel/3, messages/0]).
+
+%%--------------------------------------------------------------------
+%%% Internal application API
+%%--------------------------------------------------------------------
+
+%%--------------------------------------------------------------------
+%% Function: session_channel(ConnectionManager
+%% [, InitialWindowSize, MaxPacketSize],
+%% Timeout) -> {ok, }
+%% ConnectionManager = pid()
+%% InitialWindowSize = integer()
+%% MaxPacketSize = integer()
+%%
+%% Description: Opens a channel for a ssh session. A session is a
+%% remote execution of a program. The program may be a shell, an
+%% application, a system command, or some built-in subsystem.
+%% --------------------------------------------------------------------
+session_channel(ConnectionManager, Timeout) ->
+ session_channel(ConnectionManager,
+ ?DEFAULT_WINDOW_SIZE, ?DEFAULT_PACKET_SIZE,
+ Timeout).
+session_channel(ConnectionManager, InitialWindowSize,
+ MaxPacketSize, Timeout) ->
+ ssh_connection_manager:open_channel(ConnectionManager, "session", <<>>,
+ InitialWindowSize,
+ MaxPacketSize, Timeout).
+%%--------------------------------------------------------------------
+%% Function: exec(ConnectionManager, ChannelId, Command, Timeout) ->
+%%
+%% ConnectionManager = pid()
+%% ChannelId = integer()
+%% Cmd = string()
+%% Timeout = integer()
+%%
+%% Description: Will request that the server start the
+%% execution of the given command.
+%%--------------------------------------------------------------------
+exec(ConnectionManager, ChannelId, Command, TimeOut) ->
+ ssh_connection_manager:request(ConnectionManager, self(), ChannelId, "exec",
+ true, [?string(Command)], TimeOut).
+%%--------------------------------------------------------------------
+%% Function: shell(ConnectionManager, ChannelId) ->
+%%
+%% ConnectionManager = pid()
+%% ChannelId = integer()
+%%
+%% Description: Will request that the user's default shell (typically
+%% defined in /etc/passwd in UNIX systems) be started at the other
+%% end.
+%%--------------------------------------------------------------------
+shell(ConnectionManager, ChannelId) ->
+ ssh_connection_manager:request(ConnectionManager, self(), ChannelId,
+ "shell", false, <<>>, 0).
+%%--------------------------------------------------------------------
+%% Function: subsystem(ConnectionManager, ChannelId, SubSystem, TimeOut) ->
+%%
+%% ConnectionManager = pid()
+%% ChannelId = integer()
+%% SubSystem = string()
+%% TimeOut = integer()
+%%
+%%
+%% Description: Executes a predefined subsystem.
+%%--------------------------------------------------------------------
+subsystem(ConnectionManager, ChannelId, SubSystem, TimeOut) ->
+ ssh_connection_manager:request(ConnectionManager, self(),
+ ChannelId, "subsystem",
+ true, [?string(SubSystem)], TimeOut).
+%%--------------------------------------------------------------------
+%% Function: send(ConnectionManager, ChannelId, Type, Data, [TimeOut]) ->
+%%
+%%
+%% Description: Sends channel data.
+%%--------------------------------------------------------------------
+send(ConnectionManager, ChannelId, Data) ->
+ send(ConnectionManager, ChannelId, 0, Data, infinity).
+send(ConnectionManager, ChannelId, Data, TimeOut) when is_integer(TimeOut) ->
+ send(ConnectionManager, ChannelId, 0, Data, TimeOut);
+send(ConnectionManager, ChannelId, Type, Data) ->
+ send(ConnectionManager, ChannelId, Type, Data, infinity).
+send(ConnectionManager, ChannelId, Type, Data, TimeOut) ->
+ ssh_connection_manager:send(ConnectionManager, ChannelId,
+ Type, Data, TimeOut).
+%%--------------------------------------------------------------------
+%% Function: send_eof(ConnectionManager, ChannelId) ->
+%%
+%%
+%% Description: Sends eof on the channel <ChannelId>.
+%%--------------------------------------------------------------------
+send_eof(ConnectionManager, Channel) ->
+ ssh_connection_manager:send_eof(ConnectionManager, Channel).
+
+%%--------------------------------------------------------------------
+%% Function: adjust_window(ConnectionManager, Channel, Bytes) ->
+%%
+%%
+%% Description: Adjusts the ssh flowcontrol window.
+%%--------------------------------------------------------------------
+adjust_window(ConnectionManager, Channel, Bytes) ->
+ ssh_connection_manager:adjust_window(ConnectionManager, Channel, Bytes).
+
+%%--------------------------------------------------------------------
+%% Function: setenv(ConnectionManager, ChannelId, Var, Value, TimeOut) ->
+%%
+%%
+%% Description: Environment variables may be passed to the shell/command to be
+%% started later.
+%%--------------------------------------------------------------------
+setenv(ConnectionManager, ChannelId, Var, Value, TimeOut) ->
+ ssh_connection_manager:request(ConnectionManager, ChannelId,
+ "env", true, [?string(Var), ?string(Value)], TimeOut).
+
+
+%%--------------------------------------------------------------------
+%% Function: close(ConnectionManager, ChannelId) ->
+%%
+%%
+%% Description: Sends a close message on the channel <ChannelId>.
+%%--------------------------------------------------------------------
+close(ConnectionManager, ChannelId) ->
+ ssh_connection_manager:close(ConnectionManager, ChannelId).
+
+
+%%--------------------------------------------------------------------
+%% Function: reply_request(ConnectionManager, WantReply, Status, CannelId) ->_
+%%
+%%
+%% Description: Send status replies to requests that want such replies.
+%%--------------------------------------------------------------------
+reply_request(ConnectionManager, true, Status, ChannelId) ->
+ ConnectionManager ! {ssh_cm, self(), {Status, ChannelId}},
+ ok;
+reply_request(_,false, _, _) ->
+ ok.
+
+
+%%--------------------------------------------------------------------
+%% Function: window_change(ConnectionManager, Channel, Width, Height) ->
+%%
+%%
+%% Description: Not yet officialy supported.
+%%--------------------------------------------------------------------
+window_change(ConnectionManager, Channel, Width, Height) ->
+ window_change(ConnectionManager, Channel, Width, Height, 0, 0).
+window_change(ConnectionManager, Channel, Width, Height,
+ PixWidth, PixHeight) ->
+ ssh_connection_manager:request(ConnectionManager, Channel,
+ "window-change", false,
+ [?uint32(Width), ?uint32(Height),
+ ?uint32(PixWidth), ?uint32(PixHeight)], 0).
+%%--------------------------------------------------------------------
+%% Function: signal(ConnectionManager, Channel, Sig) ->
+%%
+%%
+%% Description: Not yet officialy supported.
+%%--------------------------------------------------------------------
+signal(ConnectionManager, Channel, Sig) ->
+ ssh_connection_manager:request(ConnectionManager, Channel,
+ "signal", false, [?string(Sig)], 0).
+
+%%--------------------------------------------------------------------
+%% Function: signal(ConnectionManager, Channel, Status) ->
+%%
+%%
+%% Description: Not yet officialy supported.
+%%--------------------------------------------------------------------
+exit_status(ConnectionManager, Channel, Status) ->
+ ssh_connection_manager:request(ConnectionManager, Channel,
+ "exit-status", false, [?uint32(Status)], 0).
+
+
+%%--------------------------------------------------------------------
+%% Function: open_pty(ConnectionManager, Channel, TimeOut) ->
+%%
+%%
+%% Description: Not yet officialy supported.
+%%--------------------------------------------------------------------
+open_pty(ConnectionManager, Channel, TimeOut) ->
+ open_pty(ConnectionManager, Channel,
+ os:getenv("TERM"), 80, 24, [], TimeOut).
+
+open_pty(ConnectionManager, Channel, Term, Width, Height, PtyOpts, TimeOut) ->
+ open_pty(ConnectionManager, Channel, Term, Width,
+ Height, 0, 0, PtyOpts, TimeOut).
+
+open_pty(ConnectionManager, Channel, Term, Width, Height,
+ PixWidth, PixHeight, PtyOpts, TimeOut) ->
+ ssh_connection_manager:request(ConnectionManager,
+ Channel, "pty-req", true,
+ [?string(Term),
+ ?uint32(Width), ?uint32(Height),
+ ?uint32(PixWidth),?uint32(PixHeight),
+ encode_pty_opts(PtyOpts)], TimeOut).
+
+
+%%--------------------------------------------------------------------
+%% Function: direct_tcpip(ConnectionManager, RemoteHost,
+%% RemotePort, OrigIP, OrigPort, Timeout) ->
+%%
+%%
+%% Description: Not yet officialy supported.
+%%--------------------------------------------------------------------
+direct_tcpip(ConnectionManager, RemoteHost,
+ RemotePort, OrigIP, OrigPort, Timeout) ->
+ direct_tcpip(ConnectionManager, RemoteHost, RemotePort, OrigIP, OrigPort,
+ ?DEFAULT_WINDOW_SIZE, ?DEFAULT_PACKET_SIZE, Timeout).
+
+direct_tcpip(ConnectionManager, RemoteIP, RemotePort, OrigIP, OrigPort,
+ InitialWindowSize, MaxPacketSize, Timeout) ->
+ case {encode_ip(RemoteIP), encode_ip(OrigIP)} of
+ {false, _} ->
+ {error, einval};
+ {_, false} ->
+ {error, einval};
+ {RIP, OIP} ->
+ ssh_connection_manager:open_channel(ConnectionManager,
+ "direct-tcpip",
+ [?string(RIP),
+ ?uint32(RemotePort),
+ ?string(OIP),
+ ?uint32(OrigPort)],
+ InitialWindowSize,
+ MaxPacketSize,
+ Timeout)
+ end.
+%%--------------------------------------------------------------------
+%% Function: tcpip_forward(ConnectionManager, BindIP, BindPort) ->
+%%
+%%
+%% Description: Not yet officialy supported.
+%%--------------------------------------------------------------------
+tcpip_forward(ConnectionManager, BindIP, BindPort) ->
+ case encode_ip(BindIP) of
+ false ->
+ {error, einval};
+ IPStr ->
+ ssh_connection_manager:global_request(ConnectionManager,
+ "tcpip-forward", true,
+ [?string(IPStr),
+ ?uint32(BindPort)])
+ end.
+%%--------------------------------------------------------------------
+%% Function: cancel_tcpip_forward(ConnectionManager, BindIP, Port) ->
+%%
+%%
+%% Description: Not yet officialy supported.
+%%--------------------------------------------------------------------
+cancel_tcpip_forward(ConnectionManager, BindIP, Port) ->
+ case encode_ip(BindIP) of
+ false ->
+ {error, einval};
+ IPStr ->
+ ssh_connection_manager:global_request(ConnectionManager,
+ "cancel-tcpip-forward", true,
+ [?string(IPStr),
+ ?uint32(Port)])
+ end.
+
+%%--------------------------------------------------------------------
+%%% Internal API
+%%--------------------------------------------------------------------
+channel_data(ChannelId, DataType, Data, Connection, ConnectionPid, From)
+ when is_list(Data)->
+ channel_data(ChannelId, DataType,
+ list_to_binary(Data), Connection, ConnectionPid, From);
+
+channel_data(ChannelId, DataType, Data,
+ #connection{channel_cache = Cache} = Connection, ConnectionPid,
+ From) ->
+
+ case ssh_channel:cache_lookup(Cache, ChannelId) of
+ #channel{remote_id = Id} = Channel0 ->
+ {SendList, Channel} = update_send_window(Channel0, DataType,
+ Data, Connection),
+ Replies =
+ lists:map(fun({SendDataType, SendData}) ->
+ {connection_reply, ConnectionPid,
+ channel_data_msg(Id,
+ SendDataType,
+ SendData)}
+ end, SendList),
+ FlowCtrlMsgs = flow_control(Replies,
+ Channel#channel{flow_control = From},
+ Cache),
+ {{replies, Replies ++ FlowCtrlMsgs}, Connection};
+ undefined ->
+ {noreply, Connection}
+ end.
+
+handle_msg(#ssh_msg_channel_open_confirmation{recipient_channel = ChannelId,
+ sender_channel = RemoteId,
+ initial_window_size = WindowSz,
+ maximum_packet_size = PacketSz},
+ #connection{channel_cache = Cache} = Connection0, _, _) ->
+
+ #channel{remote_id = undefined} = Channel =
+ ssh_channel:cache_lookup(Cache, ChannelId),
+
+ ssh_channel:cache_update(Cache, Channel#channel{
+ remote_id = RemoteId,
+ send_window_size = WindowSz,
+ send_packet_size = PacketSz}),
+ {Reply, Connection} = reply_msg(Channel, Connection0, {open, ChannelId}),
+ {{replies, [Reply]}, Connection};
+
+handle_msg(#ssh_msg_channel_open_failure{recipient_channel = ChannelId,
+ reason = Reason,
+ description = Descr,
+ lang = Lang},
+ #connection{channel_cache = Cache} = Connection0, _, _) ->
+ Channel = ssh_channel:cache_lookup(Cache, ChannelId),
+ ssh_channel:cache_delete(Cache, ChannelId),
+ {Reply, Connection} =
+ reply_msg(Channel, Connection0, {open_error, Reason, Descr, Lang}),
+ {{replies, [Reply]}, Connection};
+
+handle_msg(#ssh_msg_channel_success{recipient_channel = ChannelId},
+ #connection{channel_cache = Cache} = Connection0, _, _) ->
+ Channel = ssh_channel:cache_lookup(Cache, ChannelId),
+ {Reply, Connection} = reply_msg(Channel, Connection0, success),
+ {{replies, [Reply]}, Connection};
+
+handle_msg(#ssh_msg_channel_failure{recipient_channel = ChannelId},
+ #connection{channel_cache = Cache} = Connection0, _, _) ->
+ Channel = ssh_channel:cache_lookup(Cache, ChannelId),
+ {Reply, Connection} = reply_msg(Channel, Connection0, failure),
+ {{replies, [Reply]}, Connection};
+
+handle_msg(#ssh_msg_channel_eof{recipient_channel = ChannelId},
+ #connection{channel_cache = Cache} = Connection0, _, _) ->
+ Channel = ssh_channel:cache_lookup(Cache, ChannelId),
+ {Reply, Connection} = reply_msg(Channel, Connection0, {eof, ChannelId}),
+ {{replies, [Reply]}, Connection};
+
+handle_msg(#ssh_msg_channel_close{recipient_channel = ChannelId},
+ #connection{channel_cache = Cache} = Connection0,
+ ConnectionPid, _) ->
+
+ case ssh_channel:cache_lookup(Cache, ChannelId) of
+ #channel{sent_close = Closed, remote_id = RemoteId} = Channel ->
+ ssh_channel:cache_delete(Cache, ChannelId),
+ {CloseMsg, Connection} =
+ reply_msg(Channel, Connection0, {closed, ChannelId}),
+ case Closed of
+ true ->
+ {{replies, [CloseMsg]}, Connection};
+ false ->
+ RemoteCloseMsg = channel_close_msg(RemoteId),
+ {{replies,
+ [{connection_reply,
+ ConnectionPid, RemoteCloseMsg},
+ CloseMsg]}, Connection}
+ end;
+ undefined ->
+ {{replies, []}, Connection0}
+ end;
+
+handle_msg(#ssh_msg_channel_data{recipient_channel = ChannelId,
+ data = Data},
+ #connection{channel_cache = Cache} = Connection0, _, _) ->
+
+ #channel{recv_window_size = Size} = Channel =
+ ssh_channel:cache_lookup(Cache, ChannelId),
+ WantedSize = Size - size(Data),
+ ssh_channel:cache_update(Cache, Channel#channel{
+ recv_window_size = WantedSize}),
+ {Replies, Connection} =
+ channel_data_reply(Cache, Channel, Connection0, 0, Data),
+ {{replies, Replies}, Connection};
+
+handle_msg(#ssh_msg_channel_extended_data{recipient_channel = ChannelId,
+ data_type_code = DataType,
+ data = Data},
+ #connection{channel_cache = Cache} = Connection0, _, _) ->
+
+ #channel{recv_window_size = Size} = Channel =
+ ssh_channel:cache_lookup(Cache, ChannelId),
+ WantedSize = Size - size(Data),
+ ssh_channel:cache_update(Cache, Channel#channel{
+ recv_window_size = WantedSize}),
+ {Replies, Connection} =
+ channel_data_reply(Cache, Channel, Connection0, DataType, Data),
+ {{replies, Replies}, Connection};
+
+handle_msg(#ssh_msg_channel_window_adjust{recipient_channel = ChannelId,
+ bytes_to_add = Add},
+ #connection{channel_cache = Cache} = Connection,
+ ConnectionPid, _) ->
+
+ #channel{send_window_size = Size} =
+ Channel0 = ssh_channel:cache_lookup(Cache, ChannelId),
+
+ {SendList, Channel} = %% TODO: Datatype 0 ?
+ update_send_window(Channel0#channel{send_window_size = Size + Add},
+ 0, <<>>, Connection),
+
+ Replies = lists:map(fun({Type, Data}) ->
+ {connection_reply, ConnectionPid,
+ channel_data_msg(ChannelId, Type, Data)}
+ end, SendList),
+ FlowCtrlMsgs = flow_control(Channel, Cache),
+ {{replies, Replies ++ FlowCtrlMsgs}, Connection};
+
+handle_msg(#ssh_msg_channel_open{channel_type = "session" = Type,
+ sender_channel = ChannelId,
+ initial_window_size = WindowSz,
+ maximum_packet_size = PacketSz}, Connection0,
+ ConnectionPid, server) ->
+
+ try setup_session(Connection0, ConnectionPid, ChannelId,
+ Type, WindowSz, PacketSz) of
+ Result ->
+ Result
+ catch _:_ ->
+ FailMsg = channel_open_failure_msg(ChannelId,
+ ?SSH_OPEN_CONNECT_FAILED,
+ "Connection refused", "en"),
+ {{replies, [{connection_reply, ConnectionPid, FailMsg}]},
+ Connection0}
+ end;
+
+handle_msg(#ssh_msg_channel_open{channel_type = "session",
+ sender_channel = RemoteId},
+ Connection, ConnectionPid, client) ->
+ %% Client implementations SHOULD reject any session channel open
+ %% requests to make it more difficult for a corrupt server to attack the
+ %% client. See See RFC 4254 6.1.
+ FailMsg = channel_open_failure_msg(RemoteId,
+ ?SSH_OPEN_CONNECT_FAILED,
+ "Connection refused", "en"),
+ {{replies, [{connection_reply, ConnectionPid, FailMsg}]},
+ Connection};
+
+handle_msg(#ssh_msg_channel_open{channel_type = "forwarded-tcpip" = Type,
+ sender_channel = RemoteId,
+ initial_window_size = RWindowSz,
+ maximum_packet_size = RPacketSz,
+ data = Data},
+ #connection{channel_cache = Cache} = Connection0,
+ ConnectionPid, server) ->
+ <<?UINT32(ALen), Address:ALen/binary, ?UINT32(Port),
+ ?UINT32(OLen), Orig:OLen/binary, ?UINT32(OrigPort)>> = Data,
+
+ case bound_channel(Address, Port, Connection0) of
+ undefined ->
+ FailMsg = channel_open_failure_msg(RemoteId,
+ ?SSH_OPEN_CONNECT_FAILED,
+ "Connection refused", "en"),
+ {{replies,
+ [{connection_reply, ConnectionPid, FailMsg}]}, Connection0};
+ ChannelPid ->
+ {ChannelId, Connection1} = new_channel_id(Connection0),
+ LWindowSz = ?DEFAULT_WINDOW_SIZE,
+ LPacketSz = ?DEFAULT_PACKET_SIZE,
+ Channel = #channel{type = Type,
+ sys = "none",
+ user = ChannelPid,
+ local_id = ChannelId,
+ recv_window_size = LWindowSz,
+ recv_packet_size = LPacketSz,
+ send_window_size = RWindowSz,
+ send_packet_size = RPacketSz},
+ ssh_channel:cache_update(Cache, Channel),
+ OpenConfMsg = channel_open_confirmation_msg(RemoteId, ChannelId,
+ LWindowSz, LPacketSz),
+ {OpenMsg, Connection} =
+ reply_msg(Channel, Connection1,
+ {open, Channel, {forwarded_tcpip,
+ decode_ip(Address), Port,
+ decode_ip(Orig), OrigPort}}),
+ {{replies, [{connection_reply, ConnectionPid, OpenConfMsg},
+ OpenMsg]}, Connection}
+ end;
+
+handle_msg(#ssh_msg_channel_open{channel_type = "forwarded-tcpip",
+ sender_channel = RemoteId},
+ Connection, ConnectionPid, client) ->
+ %% Client implementations SHOULD reject direct TCP/IP open requests for
+ %% security reasons. See RFC 4254 7.2.
+ FailMsg = channel_open_failure_msg(RemoteId,
+ ?SSH_OPEN_CONNECT_FAILED,
+ "Connection refused", "en"),
+ {{replies, [{connection_reply, ConnectionPid, FailMsg}]}, Connection};
+
+
+handle_msg(#ssh_msg_channel_open{sender_channel = ChannelId}, Connection,
+ ConnectionPid, _) ->
+ FailMsg = channel_open_failure_msg(ChannelId,
+ ?SSH_OPEN_ADMINISTRATIVELY_PROHIBITED,
+ "Not allowed", "en"),
+ {{replies, [{connection_reply, ConnectionPid, FailMsg}]}, Connection};
+
+handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId,
+ request_type = "exit-status",
+ data = Data},
+ #connection{channel_cache = Cache} = Connection, _, _) ->
+ <<?UINT32(Status)>> = Data,
+ Channel = ssh_channel:cache_lookup(Cache, ChannelId),
+ {Reply, Connection} =
+ reply_msg(Channel, Connection, {exit_status, ChannelId, Status}),
+ {{replies, [Reply]}, Connection};
+
+handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId,
+ request_type = "exit-signal",
+ want_reply = false,
+ data = Data},
+ #connection{channel_cache = Cache} = Connection0,
+ ConnectionPid, _) ->
+ <<?UINT32(SigLen), SigName:SigLen/binary,
+ ?BOOLEAN(_Core),
+ ?UINT32(ErrLen), Err:ErrLen/binary,
+ ?UINT32(LangLen), Lang:LangLen/binary>> = Data,
+ Channel = ssh_channel:cache_lookup(Cache, ChannelId),
+ RemoteId = Channel#channel.remote_id,
+ {Reply, Connection} = reply_msg(Channel, Connection0,
+ {exit_signal, ChannelId,
+ binary_to_list(SigName),
+ binary_to_list(Err),
+ binary_to_list(Lang)}),
+ CloseMsg = channel_close_msg(RemoteId),
+ {{replies, [{connection_reply, ConnectionPid, CloseMsg}, Reply]},
+ Connection};
+
+handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId,
+ request_type = "xon-xoff",
+ want_reply = false,
+ data = Data},
+ #connection{channel_cache = Cache} = Connection, _, _) ->
+ <<?BOOLEAN(CDo)>> = Data,
+ Channel = ssh_channel:cache_lookup(Cache, ChannelId),
+ {Reply, Connection} =
+ reply_msg(Channel, Connection, {xon_xoff, ChannelId, CDo=/= 0}),
+ {{replies, [Reply]}, Connection};
+
+handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId,
+ request_type = "window-change",
+ want_reply = false,
+ data = Data},
+ #connection{channel_cache = Cache} = Connection0, _, _) ->
+ <<?UINT32(Width),?UINT32(Height),
+ ?UINT32(PixWidth), ?UINT32(PixHeight)>> = Data,
+ Channel = ssh_channel:cache_lookup(Cache, ChannelId),
+ {Reply, Connection} =
+ reply_msg(Channel, Connection0, {window_change, ChannelId,
+ Width, Height,
+ PixWidth, PixHeight}),
+ {{replies, [Reply]}, Connection};
+
+handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId,
+ request_type = "signal",
+ data = Data},
+ #connection{channel_cache = Cache} = Connection0, _, _) ->
+ <<?UINT32(SigLen), SigName:SigLen/binary>> = Data,
+
+ Channel = ssh_channel:cache_lookup(Cache, ChannelId),
+ {Reply, Connection} =
+ reply_msg(Channel, Connection0, {signal, ChannelId,
+ binary_to_list(SigName)}),
+ {{replies, [Reply]}, Connection};
+
+handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId,
+ request_type = "subsystem",
+ want_reply = WantReply,
+ data = Data},
+ #connection{channel_cache = Cache} = Connection,
+ ConnectionPid, server) ->
+ <<?UINT32(SsLen), SsName:SsLen/binary>> = Data,
+
+ #channel{remote_id = RemoteId} = Channel0 =
+ ssh_channel:cache_lookup(Cache, ChannelId),
+
+ ReplyMsg = {subsystem, ChannelId, WantReply, binary_to_list(SsName)},
+
+ try start_subsytem(SsName, Connection, Channel0, ReplyMsg) of
+ {ok, Pid} ->
+ erlang:monitor(process, Pid),
+ Channel = Channel0#channel{user = Pid},
+ ssh_channel:cache_update(Cache, Channel),
+ Reply = {connection_reply, ConnectionPid,
+ channel_success_msg(RemoteId)},
+ {{replies, [Reply]}, Connection}
+ catch _:_ ->
+ Reply = {connection_reply, ConnectionPid,
+ channel_failure_msg(RemoteId)},
+ {{replies, [Reply]}, Connection}
+ end;
+
+handle_msg(#ssh_msg_channel_request{request_type = "subsystem"},
+ Connection, _, client) ->
+ %% The client SHOULD ignore subsystem requests. See RFC 4254 6.5.
+ {{replies, []}, Connection};
+
+handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId,
+ request_type = "pty-req",
+ want_reply = WantReply,
+ data = Data},
+ #connection{channel_cache = Cache} = Connection,
+ ConnectionPid, server) ->
+ <<?UINT32(TermLen), BTermName:TermLen/binary,
+ ?UINT32(Width),?UINT32(Height),
+ ?UINT32(PixWidth), ?UINT32(PixHeight),
+ Modes/binary>> = Data,
+ TermName = binary_to_list(BTermName),
+
+ PtyRequest = {TermName, Width, Height,
+ PixWidth, PixHeight, decode_pty_opts(Modes)},
+
+ Channel = ssh_channel:cache_lookup(Cache, ChannelId),
+
+ handle_cli_msg(Connection, ConnectionPid, Channel,
+ {pty, ChannelId, WantReply, PtyRequest});
+
+handle_msg(#ssh_msg_channel_request{request_type = "pty-req"},
+ Connection, _, client) ->
+ %% The client SHOULD ignore pty requests. See RFC 4254 6.2.
+ {{replies, []}, Connection};
+
+handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId,
+ request_type = "shell",
+ want_reply = WantReply},
+ #connection{channel_cache = Cache} = Connection,
+ ConnectionPid, server) ->
+
+ Channel = ssh_channel:cache_lookup(Cache, ChannelId),
+
+ handle_cli_msg(Connection, ConnectionPid, Channel,
+ {shell, ChannelId, WantReply});
+
+handle_msg(#ssh_msg_channel_request{request_type = "shell"},
+ Connection, _, client) ->
+ %% The client SHOULD ignore shell requests. See RFC 4254 6.5.
+ {{replies, []}, Connection};
+
+handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId,
+ request_type = "exec",
+ want_reply = WantReply,
+ data = Data},
+ #connection{channel_cache = Cache} = Connection,
+ ConnectionPid, server) ->
+ <<?UINT32(Len), Command:Len/binary>> = Data,
+
+ Channel = ssh_channel:cache_lookup(Cache, ChannelId),
+
+ handle_cli_msg(Connection, ConnectionPid, Channel,
+ {exec, ChannelId, WantReply, binary_to_list(Command)});
+
+handle_msg(#ssh_msg_channel_request{request_type = "exec"},
+ Connection, _, client) ->
+ %% The client SHOULD ignore exec requests. See RFC 4254 6.5.
+ {{replies, []}, Connection};
+
+handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId,
+ request_type = "env",
+ want_reply = WantReply,
+ data = Data},
+ #connection{channel_cache = Cache} = Connection,
+ ConnectionPid, server) ->
+
+ <<?UINT32(VarLen),
+ Var:VarLen/binary, ?UINT32(ValueLen), Value:ValueLen/binary>> = Data,
+
+ Channel = ssh_channel:cache_lookup(Cache, ChannelId),
+
+ handle_cli_msg(Connection, ConnectionPid, Channel,
+ {env, ChannelId, WantReply, Var, Value});
+
+handle_msg(#ssh_msg_channel_request{request_type = "env"},
+ Connection, _, client) ->
+ %% The client SHOULD ignore env requests.
+ {{replies, []}, Connection};
+
+handle_msg(#ssh_msg_channel_request{recipient_channel = ChannelId,
+ request_type = _Other,
+ want_reply = WantReply}, Connection,
+ ConnectionPid, _) ->
+ ?dbg(true, "ssh_msg ssh_msg_channel_request: Other=~p\n",
+ [_Other]),
+ if WantReply == true ->
+ FailMsg = channel_failure_msg(ChannelId),
+ {{replies, [{connection_reply, ConnectionPid, FailMsg}]},
+ Connection};
+ true ->
+ {noreply, Connection}
+ end;
+
+handle_msg(#ssh_msg_global_request{name = _Type,
+ want_reply = WantReply,
+ data = _Data}, Connection,
+ ConnectionPid, _) ->
+ if WantReply == true ->
+ FailMsg = request_failure_msg(),
+ {{replies, [{connection_reply, ConnectionPid, FailMsg}]},
+ Connection};
+ true ->
+ {noreply, Connection}
+ end;
+
+%%% This transport message will also be handled at the connection level
+handle_msg(#ssh_msg_disconnect{code = Code,
+ description = Description,
+ language = _Lang },
+ #connection{channel_cache = Cache} = Connection0, _, _) ->
+ {Connection, Replies} =
+ ssh_channel:cache_foldl(fun(Channel, {Connection1, Acc}) ->
+ {Reply, Connection2} =
+ reply_msg(Channel,
+ Connection1, {closed, Channel#channel.local_id}),
+ {Connection2, [Reply | Acc]}
+ end, {Connection0, []}, Cache),
+
+ ssh_channel:cache_delete(Cache),
+ {disconnect, {Code, Description}, {{replies, Replies}, Connection}}.
+
+handle_cli_msg(#connection{channel_cache = Cache} = Connection0,
+ ConnectionPid,
+ #channel{user = undefined,
+ local_id = ChannelId} = Channel0, Reply0) ->
+
+ case (catch start_cli(Connection0, ChannelId)) of
+ {ok, Pid} ->
+ erlang:monitor(process, Pid),
+ Channel = Channel0#channel{user = Pid},
+ ssh_channel:cache_update(Cache, Channel),
+ {Reply, Connection} = reply_msg(Channel, Connection0, Reply0),
+ {{replies, [Reply]}, Connection};
+ _ ->
+ Reply = {connection_reply, ConnectionPid,
+ request_failure_msg()},
+ {{replies, [Reply]}, Connection0}
+ end;
+
+handle_cli_msg(Connection0, _, Channel, Reply0) ->
+ {Reply, Connection} = reply_msg(Channel, Connection0, Reply0),
+ {{replies, [Reply]}, Connection}.
+
+
+channel_eof_msg(ChannelId) ->
+ #ssh_msg_channel_eof{recipient_channel = ChannelId}.
+
+channel_close_msg(ChannelId) ->
+ #ssh_msg_channel_close {recipient_channel = ChannelId}.
+
+channel_success_msg(ChannelId) ->
+ #ssh_msg_channel_success{recipient_channel = ChannelId}.
+
+channel_failure_msg(ChannelId) ->
+ #ssh_msg_channel_failure{recipient_channel = ChannelId}.
+
+channel_adjust_window_msg(ChannelId, Bytes) ->
+ #ssh_msg_channel_window_adjust{recipient_channel = ChannelId,
+ bytes_to_add = Bytes}.
+
+channel_data_msg(ChannelId, 0, Data) ->
+ #ssh_msg_channel_data{recipient_channel = ChannelId,
+ data = Data};
+channel_data_msg(ChannelId, Type, Data) ->
+ #ssh_msg_channel_extended_data{recipient_channel = ChannelId,
+ data_type_code = Type,
+ data = Data}.
+
+channel_open_msg(Type, ChannelId, WindowSize, MaxPacketSize, Data) ->
+ #ssh_msg_channel_open{channel_type = Type,
+ sender_channel = ChannelId,
+ initial_window_size = WindowSize,
+ maximum_packet_size = MaxPacketSize,
+ data = Data
+ }.
+
+channel_open_confirmation_msg(RemoteId, LID, WindowSize, PacketSize) ->
+ #ssh_msg_channel_open_confirmation{recipient_channel = RemoteId,
+ sender_channel = LID,
+ initial_window_size = WindowSize,
+ maximum_packet_size = PacketSize}.
+
+channel_open_failure_msg(RemoteId, Reason, Description, Lang) ->
+ #ssh_msg_channel_open_failure{recipient_channel = RemoteId,
+ reason = Reason,
+ description = Description,
+ lang = Lang}.
+
+channel_request_msg(ChannelId, Type, WantReply, Data) ->
+ #ssh_msg_channel_request{recipient_channel = ChannelId,
+ request_type = Type,
+ want_reply = WantReply,
+ data = Data}.
+
+global_request_msg(Type, WantReply, Data) ->
+ #ssh_msg_global_request{name = Type,
+ want_reply = WantReply,
+ data = Data}.
+request_failure_msg() ->
+ #ssh_msg_request_failure{}.
+
+request_success_msg(Data) ->
+ #ssh_msg_request_success{data = Data}.
+
+bind(IP, Port, ChannelPid, Connection) ->
+ Binds = [{{IP, Port}, ChannelPid}
+ | lists:keydelete({IP, Port}, 1,
+ Connection#connection.port_bindings)],
+ Connection#connection{port_bindings = Binds}.
+
+unbind(IP, Port, Connection) ->
+ Connection#connection{
+ port_bindings =
+ lists:keydelete({IP, Port}, 1,
+ Connection#connection.port_bindings)}.
+unbind_channel(ChannelPid, Connection) ->
+ Binds = [{Bind, ChannelP} || {Bind, ChannelP}
+ <- Connection#connection.port_bindings,
+ ChannelP =/= ChannelPid],
+ Connection#connection{port_bindings = Binds}.
+
+bound_channel(IP, Port, Connection) ->
+ case lists:keysearch({IP, Port}, 1, Connection#connection.port_bindings) of
+ {value, {{IP, Port}, ChannelPid}} -> ChannelPid;
+ _ -> undefined
+ end.
+
+messages() ->
+ [ {ssh_msg_global_request, ?SSH_MSG_GLOBAL_REQUEST,
+ [string,
+ boolean,
+ '...']},
+
+ {ssh_msg_request_success, ?SSH_MSG_REQUEST_SUCCESS,
+ ['...']},
+
+ {ssh_msg_request_failure, ?SSH_MSG_REQUEST_FAILURE,
+ []},
+
+ {ssh_msg_channel_open, ?SSH_MSG_CHANNEL_OPEN,
+ [string,
+ uint32,
+ uint32,
+ uint32,
+ '...']},
+
+ {ssh_msg_channel_open_confirmation, ?SSH_MSG_CHANNEL_OPEN_CONFIRMATION,
+ [uint32,
+ uint32,
+ uint32,
+ uint32,
+ '...']},
+
+ {ssh_msg_channel_open_failure, ?SSH_MSG_CHANNEL_OPEN_FAILURE,
+ [uint32,
+ uint32,
+ string,
+ string]},
+
+ {ssh_msg_channel_window_adjust, ?SSH_MSG_CHANNEL_WINDOW_ADJUST,
+ [uint32,
+ uint32]},
+
+ {ssh_msg_channel_data, ?SSH_MSG_CHANNEL_DATA,
+ [uint32,
+ binary]},
+
+ {ssh_msg_channel_extended_data, ?SSH_MSG_CHANNEL_EXTENDED_DATA,
+ [uint32,
+ uint32,
+ binary]},
+
+ {ssh_msg_channel_eof, ?SSH_MSG_CHANNEL_EOF,
+ [uint32]},
+
+ {ssh_msg_channel_close, ?SSH_MSG_CHANNEL_CLOSE,
+ [uint32]},
+
+ {ssh_msg_channel_request, ?SSH_MSG_CHANNEL_REQUEST,
+ [uint32,
+ string,
+ boolean,
+ '...']},
+
+ {ssh_msg_channel_success, ?SSH_MSG_CHANNEL_SUCCESS,
+ [uint32]},
+
+ {ssh_msg_channel_failure, ?SSH_MSG_CHANNEL_FAILURE,
+ [uint32]}
+ ].
+
+encode_ip(Addr) when is_tuple(Addr) ->
+ case catch inet_parse:ntoa(Addr) of
+ {'EXIT',_} -> false;
+ A -> A
+ end;
+encode_ip(Addr) when is_list(Addr) ->
+ case inet_parse:address(Addr) of
+ {ok, _} -> Addr;
+ Error ->
+ case inet:getaddr(Addr, inet) of
+ {ok, A} ->
+ inet_parse:ntoa(A);
+ Error -> false
+ end
+ end.
+
+start_channel(Address, Port, Cb, Id, Args) ->
+ start_channel(Address, Port, Cb, Id, Args, undefined).
+
+start_channel(Address, Port, Cb, Id, Args, Exec) ->
+ ChildSpec = child_spec(Cb, Id, Args, Exec),
+ SystemSup = ssh_system_sup:system_supervisor(Address, Port),
+ ChannelSup = ssh_system_sup:channel_supervisor(SystemSup),
+ ssh_channel_sup:start_child(ChannelSup, ChildSpec).
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+setup_session(#connection{channel_cache = Cache} = Connection0,
+ ConnectionPid, RemoteId,
+ Type, WindowSize, PacketSize) ->
+ {ChannelId, Connection} = new_channel_id(Connection0),
+
+ Channel = #channel{type = Type,
+ sys = "ssh",
+ local_id = ChannelId,
+ recv_window_size = ?DEFAULT_WINDOW_SIZE,
+ recv_packet_size = ?DEFAULT_PACKET_SIZE,
+ send_window_size = WindowSize,
+ send_packet_size = PacketSize,
+ remote_id = RemoteId
+ },
+ ssh_channel:cache_update(Cache, Channel),
+ OpenConfMsg = channel_open_confirmation_msg(RemoteId, ChannelId,
+ ?DEFAULT_WINDOW_SIZE,
+ ?DEFAULT_PACKET_SIZE),
+
+ {{replies, [{connection_reply, ConnectionPid, OpenConfMsg}]}, Connection}.
+
+
+check_subsystem("sftp"= SsName, Options) ->
+ case proplists:get_value(subsystems, Options, no_subsys) of
+ no_subsys ->
+ {SsName, {Cb, Opts}} = ssh_sftpd:subsystem_spec([]),
+ {Cb, Opts};
+ SubSystems ->
+ proplists:get_value(SsName, SubSystems, {none, []})
+ end;
+
+check_subsystem(SsName, Options) ->
+ Subsystems = proplists:get_value(subsystems, Options, []),
+ case proplists:get_value(SsName, Subsystems, {none, []}) of
+ Fun when is_function(Fun) ->
+ {Fun, []};
+ {_, _} = Value ->
+ Value
+ end.
+
+child_spec(Callback, Id, Args, Exec) ->
+ Name = make_ref(),
+ StartFunc = {ssh_channel, start_link, [self(), Id, Callback, Args, Exec]},
+ Restart = temporary,
+ Shutdown = 3600,
+ Type = worker,
+ {Name, StartFunc, Restart, Shutdown, Type, [ssh_channel]}.
+
+%% Backwards compatibility
+start_cli(#connection{address = Address, port = Port, cli_spec = {Fun, [Shell]},
+ options = Options},
+ _ChannelId) when is_function(Fun) ->
+ case Fun(Shell, Address, Port, Options) of
+ NewFun when is_function(NewFun) ->
+ {ok, NewFun()};
+ Pid when is_pid(Pid) ->
+ {ok, Pid}
+ end;
+
+start_cli(#connection{address = Address, port = Port,
+ cli_spec = {CbModule, Args}, exec = Exec}, ChannelId) ->
+ start_channel(Address, Port, CbModule, ChannelId, Args, Exec).
+
+start_subsytem(BinName, #connection{address = Address, port = Port,
+ options = Options},
+ #channel{local_id = ChannelId, remote_id = RemoteChannelId},
+ ReplyMsg) ->
+ Name = binary_to_list(BinName),
+ case check_subsystem(Name, Options) of
+ {Callback, Opts} when is_atom(Callback), Callback =/= none ->
+ start_channel(Address, Port, Callback, ChannelId, Opts);
+ {Other, _} when Other =/= none ->
+ handle_backwards_compatibility(Other, self(),
+ ChannelId, RemoteChannelId,
+ Options, Address, Port,
+ {ssh_cm, self(), ReplyMsg})
+ end.
+
+channel_data_reply(_, #channel{local_id = ChannelId} = Channel,
+ Connection0, DataType, Data) ->
+ {Reply, Connection} =
+ reply_msg(Channel, Connection0, {data, ChannelId, DataType, Data}),
+ {[Reply], Connection}.
+
+new_channel_id(Connection) ->
+ ID = Connection#connection.channel_id_seed,
+ {ID, Connection#connection{channel_id_seed = ID + 1}}.
+
+reply_msg(Channel, Connection, {open, _} = Reply) ->
+ request_reply_or_data(Channel, Connection, Reply);
+reply_msg(Channel, Connection, {open_error, _, _, _} = Reply) ->
+ request_reply_or_data(Channel, Connection, Reply);
+reply_msg(Channel, Connection, success = Reply) ->
+ request_reply_or_data(Channel, Connection, Reply);
+reply_msg(Channel, Connection, failure = Reply) ->
+ request_reply_or_data(Channel, Connection, Reply);
+reply_msg(Channel, Connection, {closed, _} = Reply) ->
+ request_reply_or_data(Channel, Connection, Reply);
+reply_msg(#channel{user = ChannelPid}, Connection, Reply) ->
+ {{channel_data, ChannelPid, Reply}, Connection}.
+
+request_reply_or_data(#channel{local_id = ChannelId, user = ChannelPid},
+ #connection{requests = Requests} =
+ Connection, Reply) ->
+ case lists:keysearch(ChannelId, 1, Requests) of
+ {value, {ChannelId, From}} ->
+ {{channel_requst_reply, From, Reply},
+ Connection#connection{requests =
+ lists:keydelete(ChannelId, 1, Requests)}};
+ false ->
+ {{channel_data, ChannelPid, Reply}, Connection}
+ end.
+
+update_send_window(Channel0, DataType, Data,
+ #connection{channel_cache = Cache}) ->
+ Buf0 = if Data == <<>> ->
+ Channel0#channel.send_buf;
+ true ->
+ Channel0#channel.send_buf ++ [{DataType, Data}]
+ end,
+ {Buf1, NewSz, Buf2} = get_window(Buf0,
+ Channel0#channel.send_packet_size,
+ Channel0#channel.send_window_size),
+
+ Channel = Channel0#channel{send_window_size = NewSz, send_buf = Buf2},
+ ssh_channel:cache_update(Cache, Channel),
+ {Buf1, Channel}.
+
+get_window(Bs, PSz, WSz) ->
+ get_window(Bs, PSz, WSz, []).
+
+get_window(Bs, _PSz, 0, Acc) ->
+ {lists:reverse(Acc), 0, Bs};
+get_window([B0 = {DataType, Bin} | Bs], PSz, WSz, Acc) ->
+ BSz = size(Bin),
+ if BSz =< WSz -> %% will fit into window
+ if BSz =< PSz -> %% will fit into a packet
+ get_window(Bs, PSz, WSz-BSz, [B0|Acc]);
+ true -> %% split into packet size
+ <<Bin1:PSz/binary, Bin2/binary>> = Bin,
+ get_window([setelement(2, B0, Bin2) | Bs],
+ PSz, WSz-PSz,
+ [{DataType, Bin1}|Acc])
+ end;
+ WSz =< PSz -> %% use rest of window
+ <<Bin1:WSz/binary, Bin2/binary>> = Bin,
+ get_window([setelement(2, B0, Bin2) | Bs],
+ PSz, WSz-WSz,
+ [{DataType, Bin1}|Acc]);
+ true -> %% use packet size
+ <<Bin1:PSz/binary, Bin2/binary>> = Bin,
+ get_window([setelement(2, B0, Bin2) | Bs],
+ PSz, WSz-PSz,
+ [{DataType, Bin1}|Acc])
+ end;
+get_window([], _PSz, WSz, Acc) ->
+ {lists:reverse(Acc), WSz, []}.
+
+flow_control(Channel, Cache) ->
+ flow_control([window_adjusted], Channel, Cache).
+
+flow_control([], Channel, Cache) ->
+ ssh_channel:cache_update(Cache, Channel),
+ [];
+flow_control([_|_], #channel{flow_control = From} = Channel, Cache) ->
+ case From of
+ undefined ->
+ [];
+ _ ->
+ [{flow_control, Cache, Channel, From, ok}]
+ end.
+
+encode_pty_opts(Opts) ->
+ Bin = list_to_binary(encode_pty_opts2(Opts)),
+ Len = size(Bin),
+ <<?UINT32(Len), Bin/binary>>.
+
+encode_pty_opts2([]) ->
+ [?TTY_OP_END];
+encode_pty_opts2([{vintr,Value} | Opts]) ->
+ [?VINTR, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{vquit,Value} | Opts]) ->
+ [?VQUIT, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{verase,Value} | Opts]) ->
+ [?VERASE, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{vkill,Value} | Opts]) ->
+ [?VKILL, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{veof,Value} | Opts]) ->
+ [?VEOF, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{veol,Value} | Opts]) ->
+ [?VEOL, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{veol2,Value} | Opts]) ->
+ [?VEOL2, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{vstart,Value} | Opts]) ->
+ [?VSTART, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{vstop,Value} | Opts]) ->
+ [?VSTOP, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{vsusp,Value} | Opts]) ->
+ [?VSUSP, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{vdsusp,Value} | Opts]) ->
+ [?VDSUSP, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{vreprint,Value} | Opts]) ->
+ [?VREPRINT, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{vwerase,Value} | Opts]) ->
+ [ ?VWERASE, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{vlnext,Value} | Opts]) ->
+ [?VLNEXT, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{vflush,Value} | Opts]) ->
+ [?VFLUSH, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{vswtch,Value} | Opts]) ->
+ [?VSWTCH, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{vstatus,Value} | Opts]) ->
+ [?VSTATUS, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{vdiscard,Value} | Opts]) ->
+ [?VDISCARD, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{ignpar,Value} | Opts]) ->
+ [?IGNPAR, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{parmrk,Value} | Opts]) ->
+ [?PARMRK, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{inpck,Value} | Opts]) ->
+ [?INPCK, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{istrip,Value} | Opts]) ->
+ [?ISTRIP, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{inlcr,Value} | Opts]) ->
+ [?INLCR, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{igncr,Value} | Opts]) ->
+ [?IGNCR, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{icrnl,Value} | Opts]) ->
+ [?ICRNL, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{iuclc,Value} | Opts]) ->
+ [?IUCLC, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{ixon,Value} | Opts]) ->
+ [?IXON, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{ixany,Value} | Opts]) ->
+ [?IXANY, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{ixoff,Value} | Opts]) ->
+ [?IXOFF, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{imaxbel,Value} | Opts]) ->
+ [?IMAXBEL, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{isig,Value} | Opts]) ->
+ [?ISIG, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{icanon,Value} | Opts]) ->
+ [?ICANON, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{xcase,Value} | Opts]) ->
+ [?XCASE, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{echo,Value} | Opts]) ->
+ [?ECHO, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{echoe,Value} | Opts]) ->
+ [?ECHOE, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{echok,Value} | Opts]) ->
+ [?ECHOK, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{echonl,Value} | Opts]) ->
+ [?ECHONL, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{noflsh,Value} | Opts]) ->
+ [?NOFLSH, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{tostop,Value} | Opts]) ->
+ [?TOSTOP, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{iexten,Value} | Opts]) ->
+ [?IEXTEN, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{echoctl,Value} | Opts]) ->
+ [?ECHOCTL, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{echoke,Value} | Opts]) ->
+ [?ECHOKE, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{pendin,Value} | Opts]) ->
+ [?PENDIN, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{opost,Value} | Opts]) ->
+ [?OPOST, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{olcuc,Value} | Opts]) ->
+ [?OLCUC, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{onlcr,Value} | Opts]) ->
+ [?ONLCR, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{ocrnl,Value} | Opts]) ->
+ [?OCRNL, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{onocr,Value} | Opts]) ->
+ [?ONOCR, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{onlret,Value} | Opts]) ->
+ [?ONLRET, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{cs7,Value} | Opts]) ->
+ [?CS7, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{cs8,Value} | Opts]) ->
+ [?CS8, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{parenb,Value} | Opts]) ->
+ [?PARENB, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{parodd,Value} | Opts]) ->
+ [?PARODD, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{tty_op_ispeed,Value} | Opts]) ->
+ [?TTY_OP_ISPEED, ?uint32(Value) | encode_pty_opts2(Opts)];
+encode_pty_opts2([{tty_op_ospeed,Value} | Opts]) ->
+ [?TTY_OP_OSPEED, ?uint32(Value) | encode_pty_opts2(Opts)].
+
+decode_pty_opts(<<>>) ->
+ [];
+decode_pty_opts(<<0, 0, 0, 0>>) ->
+ [];
+decode_pty_opts(<<?UINT32(Len), Modes:Len/binary>>) ->
+ decode_pty_opts2(Modes);
+decode_pty_opts(Binary) ->
+ decode_pty_opts2(Binary).
+
+decode_pty_opts2(<<?TTY_OP_END>>) ->
+ [];
+decode_pty_opts2(<<Code, ?UINT32(Value), Tail/binary>>) ->
+ Op = case Code of
+ ?VINTR -> vintr;
+ ?VQUIT -> vquit;
+ ?VERASE -> verase;
+ ?VKILL -> vkill;
+ ?VEOF -> veof;
+ ?VEOL -> veol;
+ ?VEOL2 -> veol2;
+ ?VSTART -> vstart;
+ ?VSTOP -> vstop;
+ ?VSUSP -> vsusp;
+ ?VDSUSP -> vdsusp;
+ ?VREPRINT -> vreprint;
+ ?VWERASE -> vwerase;
+ ?VLNEXT -> vlnext;
+ ?VFLUSH -> vflush;
+ ?VSWTCH -> vswtch;
+ ?VSTATUS -> vstatus;
+ ?VDISCARD -> vdiscard;
+ ?IGNPAR -> ignpar;
+ ?PARMRK -> parmrk;
+ ?INPCK -> inpck;
+ ?ISTRIP -> istrip;
+ ?INLCR -> inlcr;
+ ?IGNCR -> igncr;
+ ?ICRNL -> icrnl;
+ ?IUCLC -> iuclc;
+ ?IXON -> ixon;
+ ?IXANY -> ixany;
+ ?IXOFF -> ixoff;
+ ?IMAXBEL -> imaxbel;
+ ?ISIG -> isig;
+ ?ICANON -> icanon;
+ ?XCASE -> xcase;
+ ?ECHO -> echo;
+ ?ECHOE -> echoe;
+ ?ECHOK -> echok;
+ ?ECHONL -> echonl;
+ ?NOFLSH -> noflsh;
+ ?TOSTOP -> tostop;
+ ?IEXTEN -> iexten;
+ ?ECHOCTL -> echoctl;
+ ?ECHOKE -> echoke;
+ ?PENDIN -> pendin;
+ ?OPOST -> opost;
+ ?OLCUC -> olcuc;
+ ?ONLCR -> onlcr;
+ ?OCRNL -> ocrnl;
+ ?ONOCR -> onocr;
+ ?ONLRET -> onlret;
+ ?CS7 -> cs7;
+ ?CS8 -> cs8;
+ ?PARENB -> parenb;
+ ?PARODD -> parodd;
+ ?TTY_OP_ISPEED -> tty_op_ispeed;
+ ?TTY_OP_OSPEED -> tty_op_ospeed;
+ _ -> Code
+ end,
+ [{Op, Value} | decode_pty_opts2(Tail)].
+
+decode_ip(Addr) when is_binary(Addr) ->
+ case inet_parse:address(binary_to_list(Addr)) of
+ {error,_} -> Addr;
+ {ok,A} -> A
+ end.
+
+%% This is really awful and that is why it is beeing phased out.
+handle_backwards_compatibility({_,_,_,_,_,_} = ChildSpec, _, _, _, _,
+ Address, Port, _) ->
+ SystemSup = ssh_system_sup:system_supervisor(Address, Port),
+ ChannelSup = ssh_system_sup:channel_supervisor(SystemSup),
+ ssh_channel_sup:start_child(ChannelSup, ChildSpec);
+
+handle_backwards_compatibility(Module, ConnectionManager, ChannelId,
+ RemoteChannelId, Opts,
+ _, _, Msg) when is_atom(Module) ->
+ {ok, SubSystemPid} = gen_server:start_link(Module, [Opts], []),
+ SubSystemPid !
+ {ssh_cm, ConnectionManager,
+ {open, ChannelId, RemoteChannelId, {session}}},
+ SubSystemPid ! Msg,
+ {ok, SubSystemPid};
+
+handle_backwards_compatibility(Fun, ConnectionManager, ChannelId,
+ RemoteChannelId,
+ _, _, _, Msg) when is_function(Fun) ->
+ SubSystemPid = Fun(),
+ SubSystemPid !
+ {ssh_cm, ConnectionManager,
+ {open, ChannelId, RemoteChannelId, {session}}},
+ SubSystemPid ! Msg,
+ {ok, SubSystemPid};
+
+handle_backwards_compatibility(ChildSpec,
+ ConnectionManager,
+ ChannelId, RemoteChannelId, _,
+ Address, Port, Msg) ->
+ SystemSup = ssh_system_sup:system_supervisor(Address, Port),
+ ChannelSup = ssh_system_sup:channel_supervisor(SystemSup),
+ {ok, SubSystemPid}
+ = ssh_channel_sup:start_child(ChannelSup, ChildSpec),
+ SubSystemPid !
+ {ssh_cm, ConnectionManager,
+ {open, ChannelId, RemoteChannelId, {session}}},
+ SubSystemPid ! Msg,
+ {ok, SubSystemPid}.
diff --git a/lib/ssh/src/ssh_connection_controler.erl b/lib/ssh/src/ssh_connection_controler.erl
new file mode 100644
index 0000000000..7960eb11c6
--- /dev/null
+++ b/lib/ssh/src/ssh_connection_controler.erl
@@ -0,0 +1,137 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2009. 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%
+%%
+%%--------------------------------------------------------------------
+%% File : ssh_connection_controler.erl
+%% Description :
+%%
+%%--------------------------------------------------------------------
+
+-module(ssh_connection_controler).
+
+-behaviour(gen_server).
+
+%%-----------------------------------------------------------------
+%% External exports
+%%-----------------------------------------------------------------
+-export([start_link/1, start_handler_child/2, start_manager_child/2,
+ connection_manager/1]).
+
+%%-----------------------------------------------------------------
+%% Internal exports
+%%-----------------------------------------------------------------
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ code_change/3, terminate/2, stop/1]).
+
+-record(state, {role, manager, handler, timeout}).
+
+%%-----------------------------------------------------------------
+%% External interface functions
+%%-----------------------------------------------------------------
+%%-----------------------------------------------------------------
+%% Func: start/0
+%%-----------------------------------------------------------------
+start_link(Args) ->
+ gen_server:start_link(?MODULE, [Args], []).
+
+%% Will be called from the manager child process
+start_handler_child(ServerRef, Args) ->
+ gen_server:call(ServerRef, {handler, self(), Args}, infinity).
+
+%% Will be called from the acceptor process
+start_manager_child(ServerRef, Args) ->
+ gen_server:call(ServerRef, {manager, Args}, infinity).
+
+connection_manager(ServerRef) ->
+ {ok, gen_server:call(ServerRef, manager, infinity)}.
+
+%%-----------------------------------------------------------------
+%% Internal interface functions
+%%-----------------------------------------------------------------
+%%-----------------------------------------------------------------
+%% Func: stop/1
+%%-----------------------------------------------------------------
+stop(Pid) ->
+ gen_server:cast(Pid, stop).
+
+%%-----------------------------------------------------------------
+%% Server functions
+%%-----------------------------------------------------------------
+%%-----------------------------------------------------------------
+%% Func: init/1
+%%-----------------------------------------------------------------
+init([Opts]) ->
+ process_flag(trap_exit, true),
+ case proplists:get_value(role, Opts) of
+ client ->
+ {ok, Manager} = ssh_connection_manager:start_link([client, Opts]),
+ {ok, #state{role = client, manager = Manager}};
+ _server ->
+ %% Children started by acceptor process
+ {ok, #state{role = server}}
+ end.
+
+
+%%-----------------------------------------------------------------
+%% Func: terminate/2
+%%-----------------------------------------------------------------
+terminate(_Reason, #state{}) ->
+ ok.
+
+%%-----------------------------------------------------------------
+%% Func: handle_call/3
+%%-----------------------------------------------------------------
+handle_call({handler, Pid, [Role, Socket, Opts]}, _From, State) ->
+ {ok, Handler} = ssh_connection_handler:start_link(Role, Pid, Socket, Opts),
+ {reply, {ok, Handler}, State#state{handler = Handler}};
+handle_call({manager, [server = Role, Socket, Opts]}, _From, State) ->
+ {ok, Manager} = ssh_connection_manager:start_link([Role, Socket, Opts]),
+ {reply, {ok, Manager}, State#state{manager = Manager}};
+handle_call({manager, [client = Role | Opts]}, _From, State) ->
+ {ok, Manager} = ssh_connection_manager:start_link([Role, Opts]),
+ {reply, {ok, Manager}, State#state{manager = Manager}};
+handle_call(manager, _From, State) ->
+ {reply, State#state.manager, State};
+handle_call(stop, _From, State) ->
+ {stop, normal, ok, State};
+handle_call(_, _, State) ->
+ {noreply, State, State#state.timeout}.
+
+%%-----------------------------------------------------------------
+%% Func: handle_cast/2
+%%-----------------------------------------------------------------
+handle_cast(stop, State) ->
+ {stop, normal, State};
+handle_cast(_, State) ->
+ {noreply, State, State#state.timeout}.
+
+%%-----------------------------------------------------------------
+%% Func: handle_info/2
+%%-----------------------------------------------------------------
+%% handle_info(ssh_connected, State) ->
+%% {stop, normal, State};
+%% Servant termination.
+handle_info({'EXIT', _Pid, normal}, State) ->
+ {stop, normal, State}.
+
+%%-----------------------------------------------------------------
+%% Func: code_change/3
+%%-----------------------------------------------------------------
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl
new file mode 100644
index 0000000000..5240b4b4c5
--- /dev/null
+++ b/lib/ssh/src/ssh_connection_handler.erl
@@ -0,0 +1,879 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2009. 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%
+%%
+%%
+%%----------------------------------------------------------------------
+%% Purpose: Handles the setup of an ssh connection, e.i. both the
+%% setup SSH Transport Layer Protocol (RFC 4253) and Authentication
+%% Protocol (RFC 4252). Details of the different protocols are
+%% implemented in ssh_transport.erl, ssh_auth.erl
+%% ----------------------------------------------------------------------
+
+-module(ssh_connection_handler).
+
+-behaviour(gen_fsm).
+
+-include("ssh.hrl").
+-include("ssh_transport.hrl").
+-include("ssh_auth.hrl").
+-include("ssh_connect.hrl").
+
+-export([start_link/4, send/2, renegotiate/1, send_event/2,
+ connection_info/3,
+ peer_address/1]).
+
+%% gen_fsm callbacks
+-export([hello/2, kexinit/2, key_exchange/2, new_keys/2,
+ userauth/2, connected/2]).
+
+-export([init/1, state_name/3, handle_event/3,
+ handle_sync_event/4, handle_info/3, terminate/3, code_change/4]).
+
+%% spawn export
+-export([ssh_info_handler/3]).
+
+-record(state, {
+ transport_protocol, % ex: tcp
+ transport_cb,
+ transport_close_tag,
+ ssh_params, % #ssh{} - from ssh.hrl
+ socket, % socket()
+ decoded_data_buffer, % binary()
+ encoded_data_buffer, % binary()
+ undecoded_packet_length, % integer()
+ key_exchange_init_msg, % #ssh_msg_kexinit{}
+ renegotiate = false, % boolean()
+ manager, % pid()
+ connection_queue,
+ address,
+ port,
+ opts
+ }).
+
+-define(DBG_MESSAGE, true).
+
+%%====================================================================
+%% Internal application API
+%%====================================================================
+%%--------------------------------------------------------------------
+%% Function: start_link() -> ok,Pid} | ignore | {error,Error}
+%% Description:Creates a gen_fsm process which calls Module:init/1 to
+%% initialize. To ensure a synchronized start-up procedure, this function
+%% does not return until Module:init/1 has returned.
+%%--------------------------------------------------------------------
+start_link(Role, Manager, Socket, Options) ->
+ gen_fsm:start_link(?MODULE, [Role, Manager, Socket, Options], []).
+
+send(ConnectionHandler, Data) ->
+ send_all_state_event(ConnectionHandler, {send, Data}).
+
+renegotiate(ConnectionHandler) ->
+ send_all_state_event(ConnectionHandler, renegotiate).
+
+connection_info(ConnectionHandler, From, Options) ->
+ send_all_state_event(ConnectionHandler, {info, From, Options}).
+
+%% Replaced with option to connection_info/3. For now keep
+%% for backwards compatibility
+peer_address(ConnectionHandler) ->
+ sync_send_all_state_event(ConnectionHandler, peer_address).
+
+%%====================================================================
+%% gen_fsm callbacks
+%%====================================================================
+%%--------------------------------------------------------------------
+%% Function: init(Args) -> {ok, StateName, State} |
+%% {ok, StateName, State, Timeout} |
+%% ignore |
+%% {stop, StopReason}
+%% Description:Whenever a gen_fsm is started using gen_fsm:start/[3,4] or
+%% gen_fsm:start_link/3,4, this function is called by the new process to
+%% initialize.
+%%--------------------------------------------------------------------
+init([Role, Manager, Socket, SshOpts]) ->
+ {A,B,C} = erlang:now(),
+ random:seed(A, B, C),
+ {NumVsn, StrVsn} = ssh_transport:versions(Role, SshOpts),
+ ssh_bits:install_messages(ssh_transport:transport_messages(NumVsn)),
+ {Protocol, Callback, CloseTag} =
+ proplists:get_value(transport, SshOpts, {tcp, gen_tcp, tcp_closed}),
+ Ssh = init_ssh(Role, NumVsn, StrVsn, SshOpts, Socket),
+ {ok, hello, #state{ssh_params =
+ Ssh#ssh{send_sequence = 0, recv_sequence = 0},
+ socket = Socket,
+ decoded_data_buffer = <<>>,
+ encoded_data_buffer = <<>>,
+ transport_protocol = Protocol,
+ transport_cb = Callback,
+ transport_close_tag = CloseTag,
+ manager = Manager,
+ opts = SshOpts
+ }}.
+%%--------------------------------------------------------------------
+%% Function:
+%% state_name(Event, State) -> {next_state, NextStateName, NextState}|
+%% {next_state, NextStateName,
+%% NextState, Timeout} |
+%% {stop, Reason, NewState}
+%% Description:There should be one instance of this function for each possible
+%% state name. Whenever a gen_fsm receives an event sent using
+%% gen_fsm:send_event/2, the instance of this function with the same name as
+%% the current state name StateName is called to handle the event. It is also
+%% called if a timeout occurs.
+%%--------------------------------------------------------------------
+hello(socket_control, #state{socket = Socket, ssh_params = Ssh} = State) ->
+ VsnMsg = ssh_transport:hello_version_msg(string_version(Ssh)),
+ send_msg(VsnMsg, State),
+ inet:setopts(Socket, [{packet, line}]),
+ {next_state, hello, next_packet(State)};
+
+hello({info_line, _Line}, State) ->
+ {next_state, hello, next_packet(State)};
+
+hello({version_exchange, Version}, #state{ssh_params = Ssh0,
+ socket = Socket} = State) ->
+ {NumVsn, StrVsn} = ssh_transport:handle_hello_version(Version),
+ case handle_version(NumVsn, StrVsn, Ssh0) of
+ {ok, Ssh1} ->
+ inet:setopts(Socket, [{packet,0}, {mode,binary}]),
+ {KeyInitMsg, SshPacket, Ssh} = ssh_transport:key_exchange_init_msg(Ssh1),
+ send_msg(SshPacket, State),
+ {next_state, kexinit, next_packet(State#state{ssh_params = Ssh,
+ key_exchange_init_msg =
+ KeyInitMsg})};
+ not_supported ->
+ DisconnectMsg =
+ #ssh_msg_disconnect{code =
+ ?SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED,
+ description = "Protocol version " ++ StrVsn
+ ++ " not supported",
+ language = "en"},
+ handle_disconnect(DisconnectMsg, State)
+ end.
+
+kexinit({#ssh_msg_kexinit{} = Kex, Payload},
+ #state{ssh_params = #ssh{role = Role} = Ssh0,
+ key_exchange_init_msg = OwnKex} =
+ State) ->
+ Ssh1 = ssh_transport:key_init(opposite_role(Role), Ssh0, Payload),
+ try ssh_transport:handle_kexinit_msg(Kex, OwnKex, Ssh1) of
+ {ok, NextKexMsg, Ssh} when Role == client ->
+ send_msg(NextKexMsg, State),
+ {next_state, key_exchange,
+ next_packet(State#state{ssh_params = Ssh})};
+ {ok, Ssh} when Role == server ->
+ {next_state, key_exchange,
+ next_packet(State#state{ssh_params = Ssh})}
+ catch
+ #ssh_msg_disconnect{} = DisconnectMsg ->
+ handle_disconnect(DisconnectMsg, State)
+ end.
+
+key_exchange(#ssh_msg_kexdh_init{} = Msg,
+ #state{ssh_params = #ssh{role = server} =Ssh0} = State) ->
+ try ssh_transport:handle_kexdh_init(Msg, Ssh0) of
+ {ok, KexdhReply, Ssh1} ->
+ send_msg(KexdhReply, State),
+ {ok, NewKeys, Ssh} = ssh_transport:new_keys_message(Ssh1),
+ send_msg(NewKeys, State),
+ {next_state, new_keys, next_packet(State#state{ssh_params = Ssh})}
+ catch
+ #ssh_msg_disconnect{} = DisconnectMsg ->
+ handle_disconnect(DisconnectMsg, State)
+ end;
+
+key_exchange(#ssh_msg_kexdh_reply{} = Msg,
+ #state{ssh_params = #ssh{role = client} = Ssh0} = State) ->
+ try ssh_transport:handle_kexdh_reply(Msg, Ssh0) of
+ {ok, NewKeys, Ssh} ->
+ send_msg(NewKeys, State),
+ {next_state, new_keys, next_packet(State#state{ssh_params = Ssh})}
+ catch
+ #ssh_msg_disconnect{} = DisconnectMsg ->
+ handle_disconnect(DisconnectMsg, State)
+ end;
+
+key_exchange(#ssh_msg_kex_dh_gex_group{} = Msg,
+ #state{ssh_params = #ssh{role = server} = Ssh0} = State) ->
+ try ssh_transport:handle_kex_dh_gex_group(Msg, Ssh0) of
+ {ok, NextKexMsg, Ssh1} ->
+ send_msg(NextKexMsg, State),
+ {ok, NewKeys, Ssh} = ssh_transport:new_keys_message(Ssh1),
+ send_msg(NewKeys, State),
+ {next_state, new_keys, next_packet(State#state{ssh_params = Ssh})}
+ catch
+ #ssh_msg_disconnect{} = DisconnectMsg ->
+ handle_disconnect(DisconnectMsg, State)
+ end;
+
+key_exchange(#ssh_msg_kex_dh_gex_request{} = Msg,
+ #state{ssh_params = #ssh{role = client} = Ssh0} = State) ->
+ try ssh_transport:handle_kex_dh_gex_request(Msg, Ssh0) of
+ {ok, NextKexMsg, Ssh} ->
+ send_msg(NextKexMsg, State),
+ {next_state, new_keys, next_packet(State#state{ssh_params = Ssh})}
+ catch
+ #ssh_msg_disconnect{} = DisconnectMsg ->
+ handle_disconnect(DisconnectMsg, State)
+ end;
+key_exchange(#ssh_msg_kex_dh_gex_reply{} = Msg,
+ #state{ssh_params = #ssh{role = client} = Ssh0} = State) ->
+ try ssh_transport:handle_kex_dh_gex_reply(Msg, Ssh0) of
+ {ok, NewKeys, Ssh} ->
+ send_msg(NewKeys, State),
+ {next_state, new_keys, next_packet(State#state{ssh_params = Ssh})}
+ catch
+ #ssh_msg_disconnect{} = DisconnectMsg ->
+ handle_disconnect(DisconnectMsg, State)
+ end.
+
+new_keys(#ssh_msg_newkeys{} = Msg, #state{ssh_params = Ssh0} = State0) ->
+ try ssh_transport:handle_new_keys(Msg, Ssh0) of
+ {ok, Ssh} ->
+ {NextStateName, State} =
+ after_new_keys(State0#state{ssh_params = Ssh}),
+ {next_state, NextStateName, next_packet(State)}
+ catch
+ #ssh_msg_disconnect{} = DisconnectMsg ->
+ handle_disconnect(DisconnectMsg, State0),
+ {stop, normal, State0}
+ end.
+
+userauth(#ssh_msg_service_request{name = "ssh-userauth"} = Msg,
+ #state{ssh_params = #ssh{role = server,
+ session_id = SessionId} = Ssh0} = State) ->
+ ssh_bits:install_messages(ssh_auth:userauth_messages()),
+ try ssh_auth:handle_userauth_request(Msg, SessionId, Ssh0) of
+ {ok, {Reply, Ssh}} ->
+ send_msg(Reply, State),
+ {next_state, userauth, next_packet(State#state{ssh_params = Ssh})}
+ catch
+ #ssh_msg_disconnect{} = DisconnectMsg ->
+ handle_disconnect(DisconnectMsg, State)
+ end;
+
+userauth(#ssh_msg_service_accept{name = "ssh-userauth"},
+ #state{ssh_params = #ssh{role = client,
+ service = "ssh-userauth"} = Ssh0} =
+ State) ->
+ {Msg, Ssh} = ssh_auth:init_userauth_request_msg(Ssh0),
+ send_msg(Msg, State),
+ {next_state, userauth, next_packet(State#state{ssh_params = Ssh})};
+
+userauth(#ssh_msg_userauth_request{service = "ssh-connection",
+ method = "none"} = Msg,
+ #state{ssh_params = #ssh{session_id = SessionId, role = server,
+ service = "ssh-connection"} = Ssh0
+ } = State) ->
+ try ssh_auth:handle_userauth_request(Msg, SessionId, Ssh0) of
+ {not_authorized, {_User, _Reason}, {Reply, Ssh}} ->
+ send_msg(Reply, State),
+ {next_state, userauth, next_packet(State#state{ssh_params = Ssh})}
+ catch
+ #ssh_msg_disconnect{} = DisconnectMsg ->
+ handle_disconnect(DisconnectMsg, State)
+ end;
+
+userauth(#ssh_msg_userauth_request{service = "ssh-connection",
+ method = Method} = Msg,
+ #state{ssh_params = #ssh{session_id = SessionId, role = server,
+ service = "ssh-connection",
+ peer = {_, Address}} = Ssh0,
+ opts = Opts, manager = Pid} = State) ->
+ try ssh_auth:handle_userauth_request(Msg, SessionId, Ssh0) of
+ {authorized, User, {Reply, Ssh}} ->
+ send_msg(Reply, State),
+ ssh_userreg:register_user(User, Pid),
+ Pid ! ssh_connected,
+ connected_fun(User, Address, Method, Opts),
+ {next_state, connected,
+ next_packet(State#state{ssh_params = Ssh})};
+ {not_authorized, {User, Reason}, {Reply, Ssh}} ->
+ retry_fun(User, Reason, Opts),
+ send_msg(Reply, State),
+ {next_state, userauth, next_packet(State#state{ssh_params = Ssh})}
+ catch
+ #ssh_msg_disconnect{} = DisconnectMsg ->
+ handle_disconnect(DisconnectMsg, State)
+ end;
+
+userauth(#ssh_msg_userauth_info_request{} = Msg,
+ #state{ssh_params = #ssh{role = client,
+ io_cb = IoCb} = Ssh0} = State) ->
+ try ssh_auth:handle_userauth_info_request(Msg, IoCb, Ssh0) of
+ {ok, {Reply, Ssh}} ->
+ send_msg(Reply, State),
+ {next_state, userauth, next_packet(State#state{ssh_params = Ssh})}
+ catch
+ #ssh_msg_disconnect{} = DisconnectMsg ->
+ handle_disconnect(DisconnectMsg, State)
+ end;
+
+userauth(#ssh_msg_userauth_info_response{} = Msg,
+ #state{ssh_params = #ssh{role = server} = Ssh0} = State) ->
+ try ssh_auth:handle_userauth_info_response(Msg, Ssh0) of
+ {ok, {Reply, Ssh}} ->
+ send_msg(Reply, State),
+ {next_state, userauth, next_packet(State#state{ssh_params = Ssh})}
+ catch
+ #ssh_msg_disconnect{} = DisconnectMsg ->
+ handle_disconnect(DisconnectMsg, State)
+ end;
+
+userauth(#ssh_msg_userauth_success{}, #state{ssh_params = #ssh{role = client},
+ manager = Pid} = State) ->
+ Pid ! ssh_connected,
+ {next_state, connected, next_packet(State)};
+
+userauth(#ssh_msg_userauth_failure{},
+ #state{ssh_params = #ssh{role = client,
+ userauth_methods = []}}
+ = State) ->
+ Msg = #ssh_msg_disconnect{code =
+ ?SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE,
+ description = "Unable to connect using the available"
+ " authentication methods",
+ language = "en"},
+ handle_disconnect(Msg, State);
+
+%% Server tells us which authentication methods that are allowed
+userauth(#ssh_msg_userauth_failure{authentications = Methodes},
+ #state{ssh_params = #ssh{role = client,
+ userauth_methods = none} = Ssh0} = State) ->
+ AuthMethods = string:tokens(Methodes, ","),
+ {Msg, Ssh} = ssh_auth:userauth_request_msg(
+ Ssh0#ssh{userauth_methods = AuthMethods}),
+ send_msg(Msg, State),
+ {next_state, userauth, next_packet(State#state{ssh_params = Ssh})};
+
+%% The prefered authentication method failed try next method
+userauth(#ssh_msg_userauth_failure{},
+ #state{ssh_params = #ssh{role = client} = Ssh0,
+ manager = Pid} = State) ->
+ case ssh_auth:userauth_request_msg(Ssh0) of
+ {disconnect, Event, {Msg, _}} ->
+ try
+ send_msg(Msg, State),
+ ssh_connection_manager:event(Pid, Event)
+ catch
+ exit:{noproc, _Reason} ->
+ Report = io_lib:format("Connection Manager terminated: ~p~n",
+ [Pid]),
+ error_logger:info_report(Report);
+ exit:Exit ->
+ Report = io_lib:format("Connection Manager returned:~n~p~n~p~n",
+ [Msg, Exit]),
+ error_logger:info_report(Report)
+ end,
+ {stop, normal, State};
+ {Msg, Ssh} ->
+ send_msg(Msg, State),
+ {next_state, userauth, next_packet(State#state{ssh_params = Ssh})}
+ end;
+
+userauth(#ssh_msg_userauth_banner{},
+ #state{ssh_params = #ssh{userauth_quiet_mode = true,
+ role = client}} = State) ->
+ {next_state, userauth, next_packet(State)};
+userauth(#ssh_msg_userauth_banner{message = Msg},
+ #state{ssh_params =
+ #ssh{userauth_quiet_mode = false, role = client}} = State) ->
+ io:format("~s", [Msg]),
+ {next_state, userauth, next_packet(State)}.
+
+connected({#ssh_msg_kexinit{}, _Payload} = Event, State) ->
+ kexinit(Event, State#state{renegotiate = true}).
+
+%%--------------------------------------------------------------------
+%% Function:
+%% state_name(Event, From, State) -> {next_state, NextStateName, NextState} |
+%% {next_state, NextStateName,
+%% NextState, Timeout} |
+%% {reply, Reply, NextStateName, NextState}|
+%% {reply, Reply, NextStateName,
+%% NextState, Timeout} |
+%% {stop, Reason, NewState}|
+%% {stop, Reason, Reply, NewState}
+%% Description: There should be one instance of this function for each
+%% possible state name. Whenever a gen_fsm receives an event sent using
+%% gen_fsm:sync_send_event/2,3, the instance of this function with the same
+%% name as the current state name StateName is called to handle the event.
+%%--------------------------------------------------------------------
+state_name(_Event, _From, State) ->
+ Reply = ok,
+ {reply, Reply, state_name, State}.
+
+%%--------------------------------------------------------------------
+%% Function:
+%% handle_event(Event, StateName, State) -> {next_state, NextStateName,
+%% NextState} |
+%% {next_state, NextStateName,
+%% NextState, Timeout} |
+%% {stop, Reason, NewState}
+%% Description: Whenever a gen_fsm receives an event sent using
+%% gen_fsm:send_all_state_event/2, this function is called to handle
+%% the event.
+%%--------------------------------------------------------------------
+handle_event({send, Data}, StateName, #state{ssh_params = Ssh0} = State) ->
+ {Packet, Ssh} = ssh_transport:pack(Data, Ssh0),
+ send_msg(Packet, State),
+ {next_state, StateName, next_packet(State#state{ssh_params = Ssh})};
+
+handle_event(#ssh_msg_disconnect{} = Msg, _StateName,
+ #state{manager = Pid} = State) ->
+ (catch ssh_connection_manager:event(Pid, Msg)),
+ {stop, normal, State};
+
+handle_event(#ssh_msg_ignore{}, StateName, State) ->
+ {next_state, StateName, next_packet(State)};
+
+handle_event(#ssh_msg_debug{always_display = true, message = DbgMsg},
+ StateName, State) ->
+ io:format("DEBUG: ~p\n", [DbgMsg]),
+ {next_state, StateName, next_packet(State)};
+
+handle_event(#ssh_msg_debug{}, StateName, State) ->
+ {next_state, StateName, next_packet(State)};
+
+handle_event(#ssh_msg_unimplemented{}, StateName, State) ->
+ {next_state, StateName, next_packet(State)};
+
+handle_event(renegotiate, connected, #state{ssh_params = Ssh0}
+ = State) ->
+ {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})};
+
+handle_event(renegotiate, StateName, State) ->
+ %% Allready in keyexcahange so ignore
+ {next_state, 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({unknown, Data}, StateName, State) ->
+ Msg = #ssh_msg_unimplemented{sequence = Data},
+ send_msg(Msg, State),
+ {next_state, StateName, next_packet(State)}.
+%%--------------------------------------------------------------------
+%% Function:
+%% handle_sync_event(Event, From, StateName,
+%% State) -> {next_state, NextStateName, NextState} |
+%% {next_state, NextStateName, NextState,
+%% Timeout} |
+%% {reply, Reply, NextStateName, NextState}|
+%% {reply, Reply, NextStateName, NextState,
+%% Timeout} |
+%% {stop, Reason, NewState} |
+%% {stop, Reason, Reply, NewState}
+%% Description: Whenever a gen_fsm receives an event sent using
+%% gen_fsm:sync_send_all_state_event/2,3, this function is called to handle
+%% the event.
+%%--------------------------------------------------------------------
+
+%% Replaced with option to connection_info/3. For now keep
+%% for backwards compatibility
+handle_sync_event(peer_address, _From, StateName,
+ #state{ssh_params = #ssh{peer = {_, Address}}} = State) ->
+ {reply, {ok, Address}, StateName, State}.
+
+%%--------------------------------------------------------------------
+%% Function:
+%% handle_info(Info,StateName,State)-> {next_state, NextStateName, NextState}|
+%% {next_state, NextStateName, NextState,
+%% Timeout} |
+%% {stop, Reason, NewState}
+%% Description: This function is called by a gen_fsm when it receives any
+%% other message than a synchronous or asynchronous event
+%% (or a system message).
+%%--------------------------------------------------------------------
+handle_info({Protocol, Socket, "SSH-" ++ _ = Version}, hello,
+ #state{socket = Socket,
+ transport_protocol = Protocol} = State ) ->
+ event({version_exchange, Version}, hello, State);
+
+handle_info({Protocol, Socket, Info}, hello,
+ #state{socket = Socket,
+ transport_protocol = Protocol} = State) ->
+ event({info_line, Info}, hello, State);
+
+handle_info({Protocol, Socket, Data}, Statename,
+ #state{socket = Socket,
+ transport_protocol = Protocol,
+ ssh_params = #ssh{decrypt_block_size = BlockSize,
+ recv_mac_size = MacSize} = Ssh0,
+ decoded_data_buffer = <<>>,
+ encoded_data_buffer = EncData0} = State0) ->
+
+ %% Implementations SHOULD decrypt the length after receiving the
+ %% first 8 (or cipher block size, whichever is larger) bytes of a
+ %% packet. (RFC 4253: Section 6 - Binary Packet Protocol)
+ case size(EncData0) + size(Data) >= max(8, BlockSize) of
+ true ->
+ {Ssh, SshPacketLen, DecData, EncData} =
+ ssh_transport:decrypt_first_block(<<EncData0/binary,
+ Data/binary>>, Ssh0),
+ case SshPacketLen > ?SSH_MAX_PACKET_SIZE of
+ true ->
+ DisconnectMsg =
+ #ssh_msg_disconnect{code =
+ ?SSH_DISCONNECT_PROTOCOL_ERROR,
+ description = "Bad packet length "
+ ++ integer_to_list(SshPacketLen),
+ language = "en"},
+ handle_disconnect(DisconnectMsg, State0);
+ false ->
+ RemainingSshPacketLen =
+ (SshPacketLen + ?SSH_LENGHT_INDICATOR_SIZE) -
+ BlockSize + MacSize,
+ State = State0#state{ssh_params = Ssh},
+ handle_ssh_packet_data(RemainingSshPacketLen,
+ DecData, EncData, Statename,
+ State)
+ end;
+ false ->
+ {next_state, Statename,
+ next_packet(State0#state{encoded_data_buffer =
+ <<EncData0/binary, Data/binary>>})}
+ end;
+
+handle_info({Protocol, Socket, Data}, Statename,
+ #state{socket = Socket,
+ transport_protocol = Protocol,
+ decoded_data_buffer = DecData,
+ encoded_data_buffer = EncData,
+ undecoded_packet_length = Len} =
+ State) when is_integer(Len) ->
+ handle_ssh_packet_data(Len, DecData, <<EncData/binary, Data/binary>>,
+ Statename, State);
+
+handle_info({CloseTag, _Socket}, _StateName,
+ #state{transport_close_tag = CloseTag, %%manager = Pid,
+ ssh_params = #ssh{role = _Role, opts = _Opts}} = State) ->
+ %%ok = ssh_connection_manager:delivered(Pid),
+ {stop, normal, State}.
+%%--------------------------------------------------------------------
+%% Function: terminate(Reason, StateName, State) -> void()
+%% Description:This function is called by a gen_fsm when it is about
+%% to terminate. It should be the opposite of Module:init/1 and do any
+%% necessary cleaning up. When it returns, the gen_fsm terminates with
+%% Reason. The return value is ignored.
+%%--------------------------------------------------------------------
+terminate(normal, _, #state{transport_cb = Transport,
+ socket = Socket}) ->
+ (catch Transport:close(Socket)),
+ ok;
+
+terminate(shutdown, _, State) ->
+ DisconnectMsg =
+ #ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION,
+ description = "Application disconnect",
+ language = "en"},
+ handle_disconnect(DisconnectMsg, State);
+
+terminate(Reason, _, State) ->
+ Desc = io_lib:format("Erlang ssh connection handler failed with reason: "
+ "~p , please report this to [email protected] \n",
+ [Reason]),
+ DisconnectMsg =
+ #ssh_msg_disconnect{code = ?SSH_DISCONNECT_CONNECTION_LOST,
+ description = Desc,
+ language = "en"},
+ handle_disconnect(DisconnectMsg, State).
+
+%%--------------------------------------------------------------------
+%% Function:
+%% code_change(OldVsn, StateName, State, Extra) -> {ok, StateName, NewState}
+%% Description: Convert process state when code is changed
+%%--------------------------------------------------------------------
+code_change(_OldVsn, StateName, State, _Extra) ->
+ {ok, StateName, State}.
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+init_ssh(client = Role, Vsn, Version, Options, Socket) ->
+ IOCb = case proplists:get_value(user_interaction, Options, true) of
+ true ->
+ ssh_io;
+ false ->
+ ssh_no_io
+ end,
+
+ AuthMethods = proplists:get_value(auth_methods, Options,
+ ?SUPPORTED_AUTH_METHODS),
+ {ok, PeerAddr} = inet:peername(Socket),
+
+ PeerName = proplists:get_value(host, Options),
+
+ #ssh{role = Role,
+ c_vsn = Vsn,
+ c_version = Version,
+ key_cb = proplists:get_value(key_cb, Options, ssh_file),
+ io_cb = IOCb,
+ userauth_quiet_mode = proplists:get_value(quiet_mode, Options, false),
+ opts = Options,
+ userauth_supported_methods = AuthMethods,
+ peer = {PeerName, PeerAddr}
+ };
+
+init_ssh(server = Role, Vsn, Version, Options, Socket) ->
+
+ AuthMethods = proplists:get_value(auth_methods, Options,
+ ?SUPPORTED_AUTH_METHODS),
+ {ok, PeerAddr} = inet:peername(Socket),
+
+ #ssh{role = Role,
+ s_vsn = Vsn,
+ s_version = Version,
+ key_cb = proplists:get_value(key_cb, Options, ssh_file),
+ io_cb = proplists:get_value(io_cb, Options, ssh_io),
+ opts = Options,
+ userauth_supported_methods = AuthMethods,
+ peer = {undefined, PeerAddr}
+ }.
+
+send_msg(Msg, #state{socket = Socket, transport_cb = Transport}) ->
+ Transport:send(Socket, Msg).
+
+handle_version({2, 0} = NumVsn, StrVsn, Ssh0) ->
+ Ssh = counterpart_versions(NumVsn, StrVsn, Ssh0),
+ {ok, Ssh};
+handle_version(_,_,_) ->
+ not_supported.
+
+string_version(#ssh{role = client, c_version = Vsn}) ->
+ Vsn;
+string_version(#ssh{role = server, s_version = Vsn}) ->
+ Vsn.
+
+send_event(FsmPid, Event) ->
+ gen_fsm:send_event(FsmPid, Event).
+
+send_all_state_event(FsmPid, Event) ->
+ gen_fsm:send_all_state_event(FsmPid, Event).
+
+sync_send_all_state_event(FsmPid, Event) ->
+ gen_fsm:sync_send_all_state_event(FsmPid, Event).
+
+%% simulate send_all_state_event(self(), Event)
+event(#ssh_msg_disconnect{} = Event, StateName, State) ->
+ handle_event(Event, StateName, State);
+event(#ssh_msg_ignore{} = Event, StateName, State) ->
+ handle_event(Event, StateName, State);
+event(#ssh_msg_debug{} = Event, StateName, State) ->
+ handle_event(Event, StateName, State);
+event(#ssh_msg_unimplemented{} = Event, StateName, State) ->
+ handle_event(Event, StateName, State);
+%% simulate send_event(self(), Event)
+event(Event, StateName, State) ->
+ ?MODULE:StateName(Event, State).
+
+generate_event(<<?BYTE(Byte), _/binary>> = Msg, StateName,
+ #state{manager = Pid} = State0, EncData)
+ when Byte == ?SSH_MSG_GLOBAL_REQUEST;
+ Byte == ?SSH_MSG_REQUEST_SUCCESS;
+ Byte == ?SSH_MSG_REQUEST_FAILURE;
+ Byte == ?SSH_MSG_CHANNEL_OPEN;
+ Byte == ?SSH_MSG_CHANNEL_OPEN_CONFIRMATION;
+ Byte == ?SSH_MSG_CHANNEL_OPEN_FAILURE;
+ Byte == ?SSH_MSG_CHANNEL_WINDOW_ADJUST;
+ Byte == ?SSH_MSG_CHANNEL_DATA;
+ Byte == ?SSH_MSG_CHANNEL_EXTENDED_DATA;
+ Byte == ?SSH_MSG_CHANNEL_EOF;
+ Byte == ?SSH_MSG_CHANNEL_CLOSE;
+ Byte == ?SSH_MSG_CHANNEL_REQUEST;
+ Byte == ?SSH_MSG_CHANNEL_SUCCESS;
+ Byte == ?SSH_MSG_CHANNEL_FAILURE ->
+ ssh_connection_manager:event(Pid, Msg),
+ State = generate_event_new_state(State0, EncData),
+ next_packet(State),
+ {next_state, StateName, State};
+
+generate_event(Msg, StateName, State0, EncData) ->
+ Event = ssh_bits:decode(Msg),
+ State = generate_event_new_state(State0, EncData),
+ case Event of
+ #ssh_msg_kexinit{} ->
+ %% We need payload for verification later.
+ event({Event, Msg}, StateName, State);
+ _ ->
+ event(Event, StateName, State)
+ end.
+
+generate_event_new_state(#state{ssh_params =
+ #ssh{recv_sequence = SeqNum0}
+ = Ssh} = State, EncData) ->
+ SeqNum = ssh_transport:next_seqnum(SeqNum0),
+ State#state{ssh_params = Ssh#ssh{recv_sequence = SeqNum},
+ decoded_data_buffer = <<>>,
+ encoded_data_buffer = EncData,
+ undecoded_packet_length = undefined}.
+
+
+next_packet(#state{decoded_data_buffer = <<>>,
+ encoded_data_buffer = Buff,
+ socket = Socket,
+ transport_protocol = Protocol} =
+ State) when Buff =/= <<>> andalso size(Buff) >= 8 ->
+ %% More data from the next packet has been received
+ %% Fake a socket-recive message so that the data will be processed
+ self() ! {Protocol, Socket, <<>>} ,
+ State;
+
+next_packet(#state{socket = Socket} = State) ->
+ inet:setopts(Socket, [{active, once}]),
+ State.
+
+after_new_keys(#state{renegotiate = true} = State) ->
+ {connected, State#state{renegotiate = false}};
+after_new_keys(#state{renegotiate = false,
+ ssh_params = #ssh{role = client} = Ssh0} = State) ->
+ ssh_bits:install_messages(ssh_auth:userauth_messages()),
+ {Msg, Ssh} = ssh_auth:service_request_msg(Ssh0),
+ send_msg(Msg, State),
+ {userauth, State#state{ssh_params = Ssh}};
+after_new_keys(#state{renegotiate = false,
+ ssh_params = #ssh{role = server}} = State) ->
+ {userauth, State}.
+
+max(N, M) when N > M ->
+ N;
+max(_, M) ->
+ M.
+
+handle_ssh_packet_data(RemainingSshPacketLen, DecData, EncData, StateName,
+ State) ->
+ EncSize = size(EncData),
+ case RemainingSshPacketLen > EncSize of
+ true ->
+ {next_state, StateName,
+ next_packet(State#state{decoded_data_buffer = DecData,
+ encoded_data_buffer = EncData,
+ undecoded_packet_length =
+ RemainingSshPacketLen})};
+ false ->
+ handle_ssh_packet(RemainingSshPacketLen, StateName,
+ State#state{decoded_data_buffer = DecData,
+ encoded_data_buffer = EncData})
+
+ end.
+
+handle_ssh_packet(Length, StateName, #state{decoded_data_buffer = DecData0,
+ encoded_data_buffer = EncData0,
+ ssh_params = Ssh0,
+ transport_protocol = _Protocol,
+ socket = _Socket} = State0) ->
+ {Ssh1, DecData, EncData, Mac} =
+ ssh_transport:unpack(EncData0, Length, Ssh0),
+ SshPacket = <<DecData0/binary, DecData/binary>>,
+ case ssh_transport:is_valid_mac(Mac, SshPacket, Ssh1) of
+ true ->
+ PacketData = ssh_transport:msg_data(SshPacket),
+ {Ssh1, Msg} = ssh_transport:decompress(Ssh1, PacketData),
+ generate_event(Msg, StateName,
+ State0#state{ssh_params = Ssh1,
+ %% Important to be set for
+ %% next_packet
+ decoded_data_buffer = <<>>}, EncData);
+ false ->
+ DisconnectMsg =
+ #ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR,
+ description = "Bad mac",
+ language = "en"},
+ handle_disconnect(DisconnectMsg, State0)
+ end.
+
+handle_disconnect(#ssh_msg_disconnect{} = Msg,
+ #state{ssh_params = Ssh0, manager = Pid} = State) ->
+ {SshPacket, Ssh} = ssh_transport:ssh_packet(Msg, Ssh0),
+ try
+ send_msg(SshPacket, State),
+ ssh_connection_manager:event(Pid, Msg)
+ catch
+ exit:{noproc, _Reason} ->
+ Report = io_lib:format("~p Connection Manager terminated: ~p~n",
+ [self(), Pid]),
+ error_logger:info_report(Report);
+ exit:Exit ->
+ Report = io_lib:format("Connection Manager returned:~n~p~n~p~n",
+ [Msg, Exit]),
+ error_logger:info_report(Report)
+ end,
+ {stop, normal, State#state{ssh_params = Ssh}}.
+
+counterpart_versions(NumVsn, StrVsn, #ssh{role = server} = Ssh) ->
+ Ssh#ssh{c_vsn = NumVsn , c_version = StrVsn};
+counterpart_versions(NumVsn, StrVsn, #ssh{role = client} = Ssh) ->
+ Ssh#ssh{s_vsn = NumVsn , s_version = StrVsn}.
+
+opposite_role(client) ->
+ server;
+opposite_role(server) ->
+ client.
+connected_fun(User, PeerAddr, Method, Opts) ->
+ case proplists:get_value(connectfun, Opts) of
+ undefined ->
+ ok;
+ Fun ->
+ catch Fun(User, PeerAddr, Method)
+ end.
+
+retry_fun(_, undefined, _) ->
+ ok;
+
+retry_fun(User, {error, Reason}, Opts) ->
+ case proplists:get_value(failfun, Opts) of
+ undefined ->
+ ok;
+ Fun ->
+ catch Fun(User, Reason)
+ end;
+
+retry_fun(User, Reason, Opts) ->
+ case proplists:get_value(infofun, Opts) of
+ undefined ->
+ ok;
+ Fun ->
+ catch Fun(User, Reason)
+ end.
+
+ssh_info_handler(Options, Ssh, From) ->
+ Info = ssh_info(Options, Ssh, []),
+ ssh_connection_manager:send_msg({channel_requst_reply, From, Info}).
+
+ssh_info([], _, Acc) ->
+ Acc;
+
+ssh_info([client_version | Rest], #ssh{c_vsn = IntVsn,
+ c_version = StringVsn} = SshParams, Acc) ->
+ ssh_info(Rest, SshParams, [{client_version, {IntVsn, StringVsn}} | Acc]);
+
+ssh_info([server_version | Rest], #ssh{s_vsn = IntVsn,
+ s_version = StringVsn} = SshParams, Acc) ->
+ ssh_info(Rest, SshParams, [{server_version, {IntVsn, StringVsn}} | Acc]);
+
+ssh_info([peer | Rest], #ssh{peer = Peer} = SshParams, Acc) ->
+ ssh_info(Rest, SshParams, [{peer, Peer} | Acc]);
+
+ssh_info([ _ | Rest], SshParams, Acc) ->
+ ssh_info(Rest, SshParams, Acc).
diff --git a/lib/ssh/src/ssh_connection_manager.erl b/lib/ssh/src/ssh_connection_manager.erl
new file mode 100644
index 0000000000..3863005e74
--- /dev/null
+++ b/lib/ssh/src/ssh_connection_manager.erl
@@ -0,0 +1,760 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2009. 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%
+%%
+%%
+%%----------------------------------------------------------------------
+%% Purpose: Handles multiplexing to ssh channels and global connection
+%% requests e.i. the SSH Connection Protocol (RFC 4254), that provides
+%% interactive login sessions, remote execution of commands, forwarded
+%% TCP/IP connections, and forwarded X11 connections. Details of the
+%% protocol is implemented in ssh_connection.erl
+%% ----------------------------------------------------------------------
+-module(ssh_connection_manager).
+
+-behaviour(gen_server).
+
+-include("ssh.hrl").
+-include("ssh_connect.hrl").
+-include("ssh_transport.hrl").
+
+-export([start_link/1]).
+
+-export([info/1, info/2,
+ renegotiate/1, connection_info/2, channel_info/3,
+ peer_addr/1, send_window/3, recv_window/3, adjust_window/3,
+ close/2, stop/1, send/5,
+ send_eof/2]).
+
+-export([open_channel/6, request/6, request/7, global_request/4, event/2,
+ cast/2]).
+
+%% Internal application API and spawn
+-export([send_msg/1, ssh_channel_info_handler/3]).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3]).
+
+-define(DBG_MESSAGE, true).
+
+-record(state,
+ {
+ role,
+ client,
+ starter,
+ connection, % pid()
+ connection_state, % #connection{}
+ latest_channel_id = 0,
+ opts,
+ channel_args,
+ connected
+ }).
+
+%%====================================================================
+%% Internal application API
+%%====================================================================
+
+start_link(Opts) ->
+ gen_server:start_link(?MODULE, Opts, []).
+
+open_channel(ConnectionManager, ChannelType, ChannelSpecificData,
+ InitialWindowSize, MaxPacketSize, Timeout) ->
+ case (catch call(ConnectionManager, {open, self(), ChannelType,
+ InitialWindowSize,
+ MaxPacketSize, ChannelSpecificData},
+ Timeout)) of
+ {open, Channel} ->
+ {ok, Channel};
+ Error ->
+ %% TODO: Best way?
+ Error
+ end.
+
+request(ConnectionManager, ChannelPid, ChannelId, Type, true, Data, Timeout) ->
+ call(ConnectionManager, {request, ChannelPid, ChannelId, Type, Data}, Timeout);
+request(ConnectionManager, ChannelPid, ChannelId, Type, false, Data, _) ->
+ cast(ConnectionManager, {request, ChannelPid, ChannelId, Type, Data}).
+
+request(ConnectionManager, ChannelId, Type, true, Data, Timeout) ->
+ call(ConnectionManager, {request, ChannelId, Type, Data}, Timeout);
+request(ConnectionManager, ChannelId, Type, false, Data, _) ->
+ cast(ConnectionManager, {request, ChannelId, Type, Data}).
+
+global_request(ConnectionManager, Type, true = Reply, Data) ->
+ case call(ConnectionManager,
+ {global_request, self(), Type, Reply, Data}) of
+ {ssh_cm, ConnectionManager, {success, _}} ->
+ ok;
+ {ssh_cm, ConnectionManager, {failure, _}} ->
+ error
+ end;
+
+global_request(ConnectionManager, Type, false = Reply, Data) ->
+ cast(ConnectionManager, {global_request, self(), Type, Reply, Data}).
+
+event(ConnectionManager, BinMsg) ->
+ call(ConnectionManager, {ssh_msg, self(), BinMsg}).
+
+info(ConnectionManager) ->
+ info(ConnectionManager, {info, all}).
+
+info(ConnectionManager, ChannelProcess) ->
+ call(ConnectionManager, {info, ChannelProcess}).
+
+%% TODO: Do we really want this function? Should not
+%% renegotiation be triggered by configurable timer
+%% or amount of data sent counter!
+renegotiate(ConnectionManager) ->
+ cast(ConnectionManager, renegotiate).
+
+connection_info(ConnectionManager, Options) ->
+ call(ConnectionManager, {connection_info, Options}).
+
+channel_info(ConnectionManager, ChannelId, Options) ->
+ call(ConnectionManager, {channel_info, ChannelId, Options}).
+
+%% Replaced by option peer to connection_info/2 keep for now
+%% for Backwards compatibility!
+peer_addr(ConnectionManager) ->
+ call(ConnectionManager, {peer_addr, self()}).
+
+%% Backwards compatibility!
+send_window(ConnectionManager, Channel, TimeOut) ->
+ call(ConnectionManager, {send_window, Channel}, TimeOut).
+%% Backwards compatibility!
+recv_window(ConnectionManager, Channel, TimeOut) ->
+ call(ConnectionManager, {recv_window, Channel}, TimeOut).
+
+adjust_window(ConnectionManager, Channel, Bytes) ->
+ cast(ConnectionManager, {adjust_window, Channel, Bytes}).
+
+close(ConnectionManager, ChannelId) ->
+ try call(ConnectionManager, {close, ChannelId}) of
+ ok ->
+ ok
+ catch
+ exit:{noproc, _} ->
+ ok
+ end.
+
+stop(ConnectionManager) ->
+ try call(ConnectionManager, stop) of
+ ok ->
+ ok
+ catch
+ exit:{noproc, _} ->
+ ok
+ end.
+
+send(ConnectionManager, ChannelId, Type, Data, Timeout) ->
+ call(ConnectionManager, {data, ChannelId, Type, Data}, Timeout).
+
+send_eof(ConnectionManager, ChannelId) ->
+ cast(ConnectionManager, {eof, ChannelId}).
+
+%%====================================================================
+%% gen_server callbacks
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% Function: init(Args) -> {ok, State} |
+%% {ok, State, Timeout} |
+%% ignore |
+%% {stop, Reason}
+%% Description: Initiates the server
+%%--------------------------------------------------------------------
+init([server, _Socket, Opts]) ->
+ process_flag(trap_exit, true),
+ ssh_bits:install_messages(ssh_connection:messages()),
+ Cache = ssh_channel:cache_create(),
+ {ok, #state{role = server,
+ connection_state = #connection{channel_cache = Cache,
+ channel_id_seed = 0,
+ port_bindings = [],
+ requests = [],
+ channel_pids = []},
+ opts = Opts,
+ connected = false}};
+
+init([client, Opts]) ->
+ process_flag(trap_exit, true),
+ {links, [Parent]} = process_info(self(), links),
+ ssh_bits:install_messages(ssh_connection:messages()),
+ Cache = ssh_channel:cache_create(),
+ Address = proplists:get_value(address, Opts),
+ Port = proplists:get_value(port, Opts),
+ SocketOpts = proplists:get_value(socket_opts, Opts),
+ Options = proplists:get_value(ssh_opts, Opts),
+ ChannelPid = proplists:get_value(channel_pid, Opts),
+ self() !
+ {start_connection, client, [Parent, Address, Port,
+ ChannelPid, SocketOpts, Options]},
+ {ok, #state{role = client,
+ client = ChannelPid,
+ connection_state = #connection{channel_cache = Cache,
+ channel_id_seed = 0,
+ port_bindings = [],
+ requests = [],
+ channel_pids = []},
+ opts = Opts,
+ connected = false}}.
+
+%%--------------------------------------------------------------------
+%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
+%% {reply, Reply, State, Timeout} |
+%% {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, Reply, State} |
+%% {stop, Reason, State}
+%% Description: Handling call messages
+%%--------------------------------------------------------------------
+handle_call({request, ChannelPid, ChannelId, Type, Data}, From, State0) ->
+ {{replies, Replies}, State} = handle_request(ChannelPid,
+ ChannelId, Type, Data,
+ true, From, State0),
+ %% Sends message to the connection handler process, reply to
+ %% channel is sent later when reply arrives from the connection
+ %% handler.
+ lists:foreach(fun send_msg/1, Replies),
+ {noreply, State};
+
+handle_call({request, ChannelId, Type, Data}, From, State0) ->
+ {{replies, Replies}, State} = handle_request(ChannelId, Type, Data,
+ true, From, State0),
+ %% Sends message to the connection handler process, reply to
+ %% channel is sent later when reply arrives from the connection
+ %% handler.
+ lists:foreach(fun send_msg/1, Replies),
+ {noreply, State};
+
+%% Message from ssh_connection_handler
+handle_call({ssh_msg, Pid, Msg}, From,
+ #state{connection_state = Connection0,
+ role = Role, opts = Opts, connected = IsConnected,
+ client = ClientPid}
+ = State) ->
+
+ %% To avoid that not all data sent by the other side is processes before
+ %% possible crash in ssh_connection_handler takes down the connection.
+ gen_server:reply(From, ok),
+
+ ConnectionMsg = decode_ssh_msg(Msg),
+ try ssh_connection:handle_msg(ConnectionMsg, Connection0, Pid, Role) of
+ {{replies, Replies}, Connection} ->
+ lists:foreach(fun send_msg/1, Replies),
+ {noreply, State#state{connection_state = Connection}};
+ {noreply, Connection} ->
+ {noreply, State#state{connection_state = Connection}};
+ {disconnect, {_, Reason}, {{replies, Replies}, Connection}}
+ when Role == client andalso (not IsConnected) ->
+ lists:foreach(fun send_msg/1, Replies),
+ ClientPid ! {self(), not_connected, Reason},
+ {stop, normal, State#state{connection = Connection}};
+ {disconnect, Reason, {{replies, Replies}, Connection}} ->
+ lists:foreach(fun send_msg/1, Replies),
+ SSHOpts = proplists:get_value(ssh_opts, Opts),
+ disconnect_fun(Reason, SSHOpts),
+ {stop, normal, State#state{connection_state = Connection}}
+ catch
+ exit:{noproc, Reason} ->
+ Report = io_lib:format("Connection probably terminated:~n~p~n~p~n",
+ [ConnectionMsg, Reason]),
+ error_logger:info_report(Report),
+ {noreply, State};
+ error:Error ->
+ Report = io_lib:format("Connection message returned:~n~p~n~p~n",
+ [ConnectionMsg, Error]),
+ error_logger:info_report(Report),
+ {noreply, State};
+ exit:Exit ->
+ Report = io_lib:format("Connection message returned:~n~p~n~p~n",
+ [ConnectionMsg, Exit]),
+ error_logger:info_report(Report),
+ {noreply, State}
+ end;
+
+handle_call({global_request, Pid, _, _, _} = Request, From,
+ #state{connection_state =
+ #connection{channel_cache = Cache}} = State0) ->
+ State1 = handle_global_request(Request, State0),
+ Channel = ssh_channel:cache_find(Pid, Cache),
+ State = add_request(true, Channel#channel.local_id, From, State1),
+ {noreply, State};
+
+handle_call({data, ChannelId, Type, Data}, From,
+ #state{connection_state = #connection{channel_cache = _Cache}
+ = Connection0,
+ connection = ConnectionPid} = State) ->
+ channel_data(ChannelId, Type, Data, Connection0, ConnectionPid, From,
+ State);
+
+handle_call({connection_info, Options}, From,
+ #state{connection = Connection} = State) ->
+ ssh_connection_handler:connection_info(Connection, From, Options),
+ %% Reply will be sent by the connection handler by calling
+ %% ssh_connection_handler:send_msg/1.
+ {noreply, State};
+
+handle_call({channel_info, ChannelId, Options}, From,
+ #state{connection_state = #connection{channel_cache = Cache}} = State) ->
+
+ case ssh_channel:cache_lookup(Cache, ChannelId) of
+ #channel{} = Channel ->
+ spawn(?MODULE, ssh_channel_info_handler, [Options, Channel, From]),
+ {noreply, State};
+ undefined ->
+ {reply, []}
+ end;
+
+handle_call({info, ChannelPid}, _From,
+ #state{connection_state =
+ #connection{channel_cache = Cache}} = State) ->
+ Result = ssh_channel:cache_foldl(
+ fun(Channel, Acc) when ChannelPid == all;
+ Channel#channel.user == ChannelPid ->
+ [Channel | Acc];
+ (_, Acc) ->
+ Acc
+ end, [], Cache),
+ {reply, {ok, Result}, State};
+
+handle_call({open, ChannelPid, Type, InitialWindowSize, MaxPacketSize, Data},
+ From, #state{connection = Pid,
+ connection_state =
+ #connection{channel_cache = Cache}} = State0) ->
+ {ChannelId, State1} = new_channel_id(State0),
+ Msg = ssh_connection:channel_open_msg(Type, ChannelId,
+ InitialWindowSize,
+ MaxPacketSize, Data),
+ send_msg({connection_reply, Pid, Msg}),
+ Channel = #channel{type = Type,
+ sys = "none",
+ user = ChannelPid,
+ local_id = ChannelId,
+ recv_window_size = InitialWindowSize,
+ recv_packet_size = MaxPacketSize},
+ ssh_channel:cache_update(Cache, Channel),
+ State = add_request(true, ChannelId, From, State1),
+ {noreply, State};
+
+handle_call({send_window, ChannelId}, _From,
+ #state{connection_state =
+ #connection{channel_cache = Cache}} = State) ->
+ Reply = case ssh_channel:cache_lookup(Cache, ChannelId) of
+ #channel{send_window_size = WinSize,
+ send_packet_size = Packsize} ->
+ {ok, {WinSize, Packsize}};
+ undefined ->
+ {error, einval}
+ end,
+ {reply, Reply, State};
+
+handle_call({recv_window, ChannelId}, _From,
+ #state{connection_state = #connection{channel_cache = Cache}}
+ = State) ->
+
+ Reply = case ssh_channel:cache_lookup(Cache, ChannelId) of
+ #channel{recv_window_size = WinSize,
+ recv_packet_size = Packsize} ->
+ {ok, {WinSize, Packsize}};
+ undefined ->
+ {error, einval}
+ end,
+ {reply, Reply, State};
+
+%% Replaced by option peer to connection_info/2 keep for now
+%% for Backwards compatibility!
+handle_call({peer_addr, _ChannelId}, _From,
+ #state{connection = Pid} = State) ->
+ Reply = ssh_connection_handler:peer_address(Pid),
+ {reply, Reply, State};
+
+handle_call(opts, _, #state{opts = Opts} = State) ->
+ {reply, Opts, State};
+
+handle_call({close, ChannelId}, _,
+ #state{connection = Pid, connection_state =
+ #connection{channel_cache = Cache}} = State) ->
+ case ssh_channel:cache_lookup(Cache, ChannelId) of
+ #channel{remote_id = Id} ->
+ send_msg({connection_reply, Pid,
+ ssh_connection:channel_close_msg(Id)}),
+ {reply, ok, State};
+ undefined ->
+ {reply, ok, State}
+ end;
+
+handle_call(stop, _, #state{role = _client,
+ client = ChannelPid,
+ connection = Pid} = State) ->
+ DisconnectMsg =
+ #ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION,
+ description = "Application disconnect",
+ language = "en"},
+ (catch gen_fsm:send_all_state_event(Pid, DisconnectMsg)),
+% ssh_connection_handler:send(Pid, DisconnectMsg),
+ {stop, normal, ok, State};
+handle_call(stop, _, State) ->
+ {stop, normal, ok, State};
+
+%% API violation make it the violaters problem
+%% by ignoring it. The violating process will get
+%% a timeout or hang.
+handle_call(_, _, State) ->
+ {noreply, State}.
+
+%%--------------------------------------------------------------------
+%% Function: handle_cast(Msg, State) -> {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State}
+%% Description: Handling cast messages
+%%--------------------------------------------------------------------
+handle_cast({request, ChannelPid, ChannelId, Type, Data}, State0) ->
+ {{replies, Replies}, State} = handle_request(ChannelPid, ChannelId,
+ Type, Data,
+ false, none, State0),
+ lists:foreach(fun send_msg/1, Replies),
+ {noreply, State};
+
+handle_cast({request, ChannelId, Type, Data}, State0) ->
+ {{replies, Replies}, State} = handle_request(ChannelId, Type, Data,
+ false, none, State0),
+ lists:foreach(fun send_msg/1, Replies),
+ {noreply, State};
+
+handle_cast({global_request, _, _, _, _} = Request, State0) ->
+ State = handle_global_request(Request, State0),
+ {noreply, State};
+
+handle_cast(renegotiate, #state{connection = Pid} = State) ->
+ ssh_connection_handler:renegotiate(Pid),
+ {noreply, State};
+
+handle_cast({adjust_window, ChannelId, Bytes},
+ #state{connection = Pid, connection_state =
+ #connection{channel_cache = Cache}} = State) ->
+ case ssh_channel:cache_lookup(Cache, ChannelId) of
+ #channel{recv_window_size = WinSize, remote_id = Id} = Channel ->
+ ssh_channel:cache_update(Cache, Channel#channel{recv_window_size =
+ WinSize + Bytes}),
+ Msg = ssh_connection:channel_adjust_window_msg(Id, Bytes),
+ send_msg({connection_reply, Pid, Msg});
+ undefined ->
+ ignore
+ end,
+ {noreply, State};
+
+handle_cast({eof, ChannelId},
+ #state{connection = Pid, connection_state =
+ #connection{channel_cache = Cache}} = State) ->
+ case ssh_channel:cache_lookup(Cache, ChannelId) of
+ #channel{remote_id = Id} ->
+ send_msg({connection_reply, Pid,
+ ssh_connection:channel_eof_msg(Id)}),
+ {noreply, State};
+ undefined ->
+ {noreply, State}
+ end;
+
+handle_cast({success, ChannelId}, #state{connection = Pid} = State) ->
+ Msg = ssh_connection:channel_success_msg(ChannelId),
+ send_msg({connection_reply, Pid, Msg}),
+ {noreply, State};
+
+handle_cast({failure, ChannelId}, #state{connection = Pid} = State) ->
+ Msg = ssh_connection:channel_failure_msg(ChannelId),
+ send_msg({connection_reply, Pid, Msg}),
+ {noreply, State}.
+
+%%--------------------------------------------------------------------
+%% Function: handle_info(Info, State) -> {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State}
+%% Description: Handling all non call/cast messages
+%%--------------------------------------------------------------------
+handle_info({start_connection, server,
+ [Address, Port, Socket, Options]},
+ #state{connection_state = CState} = State) ->
+ {ok, Connection} = ssh_transport:accept(Address, Port, Socket, Options),
+ Shell = proplists:get_value(shell, Options),
+ Exec = proplists:get_value(exec, Options),
+ CliSpec = proplists:get_value(ssh_cli, Options, {ssh_cli, [Shell]}),
+ {noreply, State#state{connection = Connection,
+ connection_state =
+ CState#connection{address = Address,
+ port = Port,
+ cli_spec = CliSpec,
+ options = Options,
+ exec = Exec}}};
+
+handle_info({start_connection, client,
+ [Parent, Address, Port, ChannelPid, SocketOpts, Options]},
+ State) ->
+ case (catch ssh_transport:connect(Parent, Address,
+ Port, SocketOpts, Options)) of
+ {ok, Connection} ->
+ erlang:monitor(process, ChannelPid),
+ {noreply, State#state{connection = Connection}};
+ Reason ->
+ ChannelPid ! {self(), not_connected, Reason},
+ {stop, normal, State}
+ end;
+
+handle_info({ssh_cm, _Sender, Msg}, State0) ->
+ %% Backwards compatibility!
+ State = cm_message(Msg, State0),
+ {noreply, State};
+
+%% Nop backwards compatibility
+handle_info({same_user, _}, State) ->
+ {noreply, State};
+
+handle_info(ssh_connected, #state{role = client, client = Pid}
+ = State) ->
+ Pid ! {self(), is_connected},
+ {noreply, State#state{connected = true}};
+
+handle_info(ssh_connected, #state{role = server} = State) ->
+ {noreply, State#state{connected = true}};
+
+handle_info({'DOWN', _Ref, process, ChannelPid, normal}, State0) ->
+ handle_down(handle_channel_down(ChannelPid, State0));
+
+handle_info({'DOWN', _Ref, process, ChannelPid, shutdown}, State0) ->
+ handle_down(handle_channel_down(ChannelPid, State0));
+
+handle_info({'DOWN', _Ref, process, ChannelPid, Reason}, State0) ->
+ Report = io_lib:format("Pid ~p DOWN ~p\n", [ChannelPid, Reason]),
+ error_logger:error_report(Report),
+ handle_down(handle_channel_down(ChannelPid, State0));
+
+handle_info({'EXIT', _, _}, State) ->
+ %% Handled in 'DOWN'
+ {noreply, State}.
+
+%%--------------------------------------------------------------------
+%% Function: terminate(Reason, State) -> void()
+%% Description: This function is called by a gen_server when it is about to
+%% terminate. It should be the opposite of Module:init/1 and do any necessary
+%% cleaning up. When it returns, the gen_server terminates with Reason.
+%% The return value is ignored.
+%%--------------------------------------------------------------------
+terminate(Reason, #state{connection_state =
+ #connection{requests = Requests},
+ opts = Opts}) ->
+ SSHOpts = proplists:get_value(ssh_opts, Opts),
+ disconnect_fun(Reason, SSHOpts),
+ (catch lists:foreach(fun({_, From}) ->
+ gen_server:reply(From, {error, connection_closed})
+ end, Requests)),
+ ok.
+
+%%--------------------------------------------------------------------
+%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
+%% Description: Convert process state when code is changed
+%%--------------------------------------------------------------------
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+channel_data(Id, Type, Data, Connection0, ConnectionPid, From, State) ->
+ case ssh_connection:channel_data(Id, Type, Data, Connection0,
+ ConnectionPid, From) of
+ {{replies, Replies}, Connection} ->
+ lists:foreach(fun send_msg/1, Replies),
+ {noreply, State#state{connection_state = Connection}};
+ {noreply, Connection} ->
+ {noreply, State#state{connection_state = Connection}}
+ end.
+
+call(Pid, Msg) ->
+ call(Pid, Msg, infinity).
+call(Pid, Msg, Timeout) ->
+ try gen_server:call(Pid, Msg, Timeout) of
+ Result ->
+ Result
+ catch
+ exit:{timeout, _} ->
+ {error, timeout}
+ end.
+
+cast(Pid, Msg) ->
+ gen_server:cast(Pid, Msg).
+
+decode_ssh_msg(BinMsg) when is_binary(BinMsg)->
+ ssh_bits:decode(BinMsg);
+decode_ssh_msg(Msg) ->
+ Msg.
+
+
+send_msg(Msg) ->
+ case catch do_send_msg(Msg) of
+ {'EXIT', Reason}->
+ Report = io_lib:format("Connection Manager fail to send:~n~p~n"
+ "Reason why it failed was:~n~p~n",
+ [Msg, Reason]),
+ error_logger:info_report(Report);
+ _ ->
+ ok
+ end.
+
+do_send_msg({channel_data, Pid, Data}) ->
+ Pid ! {ssh_cm, self(), Data};
+do_send_msg({channel_requst_reply, From, Data}) ->
+ gen_server:reply(From, Data);
+do_send_msg({connection_reply, Pid, Data}) ->
+ Msg = ssh_bits:encode(Data),
+ ssh_connection_handler:send(Pid, Msg);
+do_send_msg({flow_control, Cache, Channel, From, Msg}) ->
+ ssh_channel:cache_update(Cache, Channel#channel{flow_control = undefined}),
+ gen_server:reply(From, Msg).
+
+handle_request(ChannelPid, ChannelId, Type, Data, WantReply, From,
+ #state{connection = Pid,
+ connection_state =
+ #connection{channel_cache = Cache}} = State0) ->
+ case ssh_channel:cache_lookup(Cache, ChannelId) of
+ #channel{remote_id = Id} = Channel ->
+ update_sys(Cache, Channel, Type, ChannelPid),
+ Msg = ssh_connection:channel_request_msg(Id, Type,
+ WantReply, Data),
+ Replies = [{connection_reply, Pid, Msg}],
+ State = add_request(WantReply, ChannelId, From, State0),
+ {{replies, Replies}, State};
+ undefined ->
+ {{replies, []}, State0}
+ end.
+
+handle_request(ChannelId, Type, Data, WantReply, From,
+ #state{connection = Pid,
+ connection_state =
+ #connection{channel_cache = Cache}} = State0) ->
+ case ssh_channel:cache_lookup(Cache, ChannelId) of
+ #channel{remote_id = Id} ->
+ Msg = ssh_connection:channel_request_msg(Id, Type,
+ WantReply, Data),
+ Replies = [{connection_reply, Pid, Msg}],
+ State = add_request(WantReply, ChannelId, From, State0),
+ {{replies, Replies}, State};
+ undefined ->
+ {{replies, []}, State0}
+ end.
+
+handle_down({{replies, Replies}, State}) ->
+ lists:foreach(fun send_msg/1, Replies),
+ {noreply, State}.
+
+handle_channel_down(ChannelPid, #state{connection_state =
+ #connection{channel_cache = Cache}} =
+ State) ->
+ ssh_channel:cache_foldl(
+ fun(Channel, Acc) when Channel#channel.user == ChannelPid ->
+ ssh_channel:cache_delete(Cache,
+ Channel#channel.local_id),
+ Acc;
+ (_,Acc) ->
+ Acc
+ end, [], Cache),
+ {{replies, []}, State}.
+
+update_sys(Cache, Channel, Type, ChannelPid) ->
+ ssh_channel:cache_update(Cache,
+ Channel#channel{sys = Type, user = ChannelPid}).
+
+add_request(false, _ChannelId, _From, State) ->
+ State;
+add_request(true, ChannelId, From, #state{connection_state =
+ #connection{requests = Requests0} =
+ Connection} = State) ->
+ Requests = [{ChannelId, From} | Requests0],
+ State#state{connection_state = Connection#connection{requests = Requests}}.
+
+new_channel_id(#state{connection_state = #connection{channel_id_seed = Id} =
+ Connection}
+ = State) ->
+ {Id, State#state{connection_state =
+ Connection#connection{channel_id_seed = Id + 1}}}.
+
+handle_global_request({global_request, ChannelPid,
+ "tcpip-forward" = Type, WantReply,
+ <<?UINT32(IPLen),
+ IP:IPLen/binary, ?UINT32(Port)>> = Data},
+ #state{connection = ConnectionPid,
+ connection_state =
+ #connection{channel_cache = Cache}
+ = Connection0} = State) ->
+ ssh_channel:cache_update(Cache, #channel{user = ChannelPid,
+ type = "forwarded-tcpip",
+ sys = none}),
+ Connection = ssh_connection:bind(IP, Port, ChannelPid, Connection0),
+ Msg = ssh_connection:global_request_msg(Type, WantReply, Data),
+ send_msg({connection_reply, ConnectionPid, Msg}),
+ State#state{connection_state = Connection};
+
+handle_global_request({global_request, _Pid, "cancel-tcpip-forward" = Type,
+ WantReply, <<?UINT32(IPLen),
+ IP:IPLen/binary, ?UINT32(Port)>> = Data},
+ #state{connection = Pid,
+ connection_state = Connection0} = State) ->
+ Connection = ssh_connection:unbind(IP, Port, Connection0),
+ Msg = ssh_connection:global_request_msg(Type, WantReply, Data),
+ send_msg({connection_reply, Pid, Msg}),
+ State#state{connection_state = Connection};
+
+handle_global_request({global_request, _Pid, "cancel-tcpip-forward" = Type,
+ WantReply, Data}, #state{connection = Pid} = State) ->
+ Msg = ssh_connection:global_request_msg(Type, WantReply, Data),
+ send_msg({connection_reply, Pid, Msg}),
+ State.
+
+cm_message(Msg, State) ->
+ {noreply, NewState} = handle_cast(Msg, State),
+ NewState.
+
+disconnect_fun(Reason, Opts) ->
+ case proplists:get_value(disconnectfun, Opts) of
+ undefined ->
+ ok;
+ Fun ->
+ catch Fun(Reason)
+ end.
+
+ssh_channel_info_handler(Options, Channel, From) ->
+ Info = ssh_channel_info(Options, Channel, []),
+ send_msg({channel_requst_reply, From, Info}).
+
+ssh_channel_info([], _, Acc) ->
+ Acc;
+
+ssh_channel_info([recv_window | Rest], #channel{recv_window_size = WinSize,
+ recv_packet_size = Packsize
+ } = Channel, Acc) ->
+ ssh_channel_info(Rest, Channel, [{recv_window, {{win_size, WinSize},
+ {packet_size, Packsize}}} | Acc]);
+ssh_channel_info([send_window | Rest], #channel{send_window_size = WinSize,
+ send_packet_size = Packsize
+ } = Channel, Acc) ->
+ ssh_channel_info(Rest, Channel, [{send_window, {{win_size, WinSize},
+ {packet_size, Packsize}}} | Acc]);
+ssh_channel_info([ _ | Rest], Channel, Acc) ->
+ ssh_channel_info(Rest, Channel, Acc).
+
+
+
diff --git a/lib/ssh/src/ssh_dsa.erl b/lib/ssh/src/ssh_dsa.erl
new file mode 100755
index 0000000000..ec24fbcd01
--- /dev/null
+++ b/lib/ssh/src/ssh_dsa.erl
@@ -0,0 +1,95 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2009. 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%
+%%
+
+%%
+
+%%% Description: dsa public-key sign and verify
+
+-module(ssh_dsa).
+
+-export([verify/3]).
+-export([sign/2]).
+-export([alg_name/0]).
+
+-include("ssh.hrl").
+
+%% start() ->
+%% crypto:start().
+
+%% sign_file(File, Opts) ->
+%% start(),
+%% {ok,Bin} = file:read_file(File),
+%% {ok,Key} = ssh_file:private_host_dsa_key(user, Opts),
+%% sign(Key, Bin).
+
+%% verify_file(File, Sig) ->
+%% start(),
+%% {ok,Bin} = file:read_file(File),
+%% {ok,Key} = ssh_file:public_host_key(user, dsa),
+%% verify(Key, Bin, Sig).
+
+sign(_Private=#ssh_key { private={P,Q,G,X} },Mb) ->
+ K = ssh_bits:irandom(160) rem Q,
+ R = ssh_math:ipow(G, K, P) rem Q,
+ Ki = ssh_math:invert(K, Q),
+ <<M:160/big-unsigned-integer>> = crypto:sha(Mb),
+ S = (Ki * (M + X*R)) rem Q,
+ <<R:160/big-unsigned-integer, S:160/big-unsigned-integer>>.
+
+
+%% the paramiko client sends a bad sig sometimes,
+%% instead of crashing, we nicely return error, the
+%% typcally manifests itself as Sb being 39 bytes
+%% instead of 40.
+
+verify(Public, Mb, Sb) ->
+ case catch xverify(Public, Mb, Sb) of
+ {'EXIT', _Reason} ->
+ %store({Public, Mb, Sb, _Reason}),
+ {error, inconsistent_key};
+ ok ->
+ %store({Public, Mb, Sb, ok})
+ ok
+ end.
+
+%% store(Term) ->
+%% {ok, Fd} = file:open("/tmp/dsa", [append]),
+%% io:format(Fd, "~p~n~n~n", [Term]),
+%% file:close(Fd).
+
+
+xverify(_Public=#ssh_key { public={P,Q,G,Y} },Mb,Sb) ->
+ <<R0:160/big-unsigned-integer, S0:160/big-unsigned-integer>> = Sb,
+ ?ssh_assert(R0 >= 0 andalso R0 < Q andalso
+ S0 >= 0 andalso S0 < Q, out_of_range),
+ W = ssh_math:invert(S0,Q),
+ <<M0:160/big-unsigned-integer>> = crypto:sha(Mb),
+ U1 = (M0*W) rem Q,
+ U2 = (R0*W) rem Q,
+ T1 = ssh_math:ipow(G,U1,P),
+ T2 = ssh_math:ipow(Y,U2,P),
+ V = ((T1*T2) rem P) rem Q,
+ if V == R0 ->
+ ok;
+ true ->
+ {error, inconsistent_key}
+ end.
+
+alg_name() ->
+ "ssh-dss".
diff --git a/lib/ssh/src/ssh_file.erl b/lib/ssh/src/ssh_file.erl
new file mode 100755
index 0000000000..8a3c903e51
--- /dev/null
+++ b/lib/ssh/src/ssh_file.erl
@@ -0,0 +1,530 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2009. 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%
+%%
+
+%%
+
+%%% Description: SSH file handling
+
+-module(ssh_file).
+
+-include("ssh.hrl").
+-include("PKCS-1.hrl").
+-include("DSS.hrl").
+
+-export([public_host_dsa_key/2,private_host_dsa_key/2,
+ public_host_rsa_key/2,private_host_rsa_key/2,
+ public_host_key/2,private_host_key/2,
+ lookup_host_key/3, add_host_key/3, % del_host_key/2,
+ lookup_user_key/3, ssh_dir/2, file_name/3]).
+
+-export([private_identity_key/2]).
+%% , public_identity_key/2,
+%% identity_keys/2]).
+
+-export([encode_public_key/1, decode_public_key_v2/2]).
+
+-import(lists, [reverse/1, append/1]).
+
+-define(DBG_PATHS, true).
+
+%% API
+public_host_dsa_key(Type, Opts) ->
+ File = file_name(Type, "ssh_host_dsa_key.pub", Opts),
+ read_public_key_v2(File, "ssh-dss").
+
+private_host_dsa_key(Type, Opts) ->
+ File = file_name(Type, "ssh_host_dsa_key", Opts),
+ read_private_key_v2(File, "ssh-dss").
+
+public_host_rsa_key(Type, Opts) ->
+ File = file_name(Type, "ssh_host_rsa_key.pub", Opts),
+ read_public_key_v2(File, "ssh-rsa").
+
+private_host_rsa_key(Type, Opts) ->
+ File = file_name(Type, "ssh_host_rsa_key", Opts),
+ read_private_key_v2(File, "ssh-rsa").
+
+public_host_key(Type, Opts) ->
+ File = file_name(Type, "ssh_host_key", Opts),
+ case read_private_key_v1(File,public) of
+ {error, enoent} ->
+ read_public_key_v1(File++".pub");
+ Result ->
+ Result
+ end.
+
+
+private_host_key(Type, Opts) ->
+ File = file_name(Type, "ssh_host_key", Opts),
+ read_private_key_v1(File,private).
+
+
+
+%% in: "host" out: "host,1.2.3.4.
+add_ip(Host) ->
+ case inet:getaddr(Host, inet) of
+ {ok, Addr} ->
+ case ssh_connection:encode_ip(Addr) of
+ false -> Host;
+ IPString -> Host ++ "," ++ IPString
+ end;
+ _ -> Host
+ end.
+
+replace_localhost("localhost") ->
+ {ok, Hostname} = inet:gethostname(),
+ Hostname;
+replace_localhost(Host) ->
+ Host.
+
+%% lookup_host_key
+%% return {ok, Key(s)} or {error, not_found}
+%%
+
+lookup_host_key(Host, Alg, Opts) ->
+ Host1 = replace_localhost(Host),
+ do_lookup_host_key(Host1, Alg, Opts).
+
+do_lookup_host_key(Host, Alg, Opts) ->
+ case file:open(file_name(user, "known_hosts", Opts), [read]) of
+ {ok, Fd} ->
+ Res = lookup_host_key_fd(Fd, Host, Alg),
+ file:close(Fd),
+ Res;
+ {error, enoent} -> {error, not_found};
+ Error -> Error
+ end.
+
+add_host_key(Host, Key, Opts) ->
+ Host1 = add_ip(replace_localhost(Host)),
+ case file:open(file_name(user, "known_hosts", Opts),[write,append]) of
+ {ok, Fd} ->
+ Res = add_key_fd(Fd, Host1, Key),
+ file:close(Fd),
+ Res;
+ Error ->
+ Error
+ end.
+
+%% del_host_key(Host, Opts) ->
+%% Host1 = replace_localhost(Host),
+%% case file:open(file_name(user, "known_hosts", Opts),[write,read]) of
+%% {ok, Fd} ->
+%% Res = del_key_fd(Fd, Host1),
+%% file:close(Fd),
+%% Res;
+%% Error ->
+%% Error
+%% end.
+
+identity_key_filename("ssh-dss") -> "id_dsa";
+identity_key_filename("ssh-rsa") -> "id_rsa".
+
+private_identity_key(Alg, Opts) ->
+ Path = file_name(user, identity_key_filename(Alg), Opts),
+ read_private_key_v2(Path, Alg).
+
+read_public_key_v2(File, Type) ->
+ case file:read_file(File) of
+ {ok,Bin} ->
+ List = binary_to_list(Bin),
+ case lists:prefix(Type, List) of
+ true ->
+ List1 = lists:nthtail(length(Type), List),
+ K_S = ssh_bits:b64_decode(List1),
+ decode_public_key_v2(K_S, Type);
+ false ->
+ {error, bad_format}
+ end;
+ Error ->
+ Error
+ end.
+
+decode_public_key_v2(K_S, "ssh-rsa") ->
+ case ssh_bits:decode(K_S,[string,mpint,mpint]) of
+ ["ssh-rsa", E, N] ->
+ {ok, #ssh_key { type = rsa,
+ public = {N,E},
+ comment=""}};
+ _ ->
+ {error, bad_format}
+ end;
+decode_public_key_v2(K_S, "ssh-dss") ->
+ case ssh_bits:decode(K_S,[string,mpint,mpint,mpint,mpint]) of
+ ["ssh-dss",P,Q,G,Y] ->
+ {ok,#ssh_key { type = dsa,
+ public = {P,Q,G,Y}
+ }};
+ _A ->
+ {error, bad_format}
+ end;
+decode_public_key_v2(_, _) ->
+ {error, bad_format}.
+
+
+read_public_key_v1(File) ->
+ case file:read_file(File) of
+ {ok,Bin} ->
+ List = binary_to_list(Bin),
+ case io_lib:fread("~d ~d ~d ~s", List) of
+ {ok,[_Sz,E,N,Comment],_} ->
+ {ok,#ssh_key { type = rsa,
+ public ={N,E},
+ comment = Comment }};
+ _Error ->
+ {error, bad_format}
+ end;
+ Error ->
+ Error
+ end.
+
+%% pem_type("ssh-dss") -> "DSA";
+%% pem_type("ssh-rsa") -> "RSA".
+
+read_private_key_v2(File, Type) ->
+ case catch (public_key:pem_to_der(File)) of
+ {ok, [{_, Bin, not_encrypted}]} ->
+ decode_private_key_v2(Bin, Type);
+ Error -> %% Note we do not handle password encrypted keys at the moment
+ {error, Error}
+ end.
+%% case file:read_file(File) of
+%% {ok,Bin} ->
+%% case read_pem(binary_to_list(Bin), pem_type(Type)) of
+%% {ok,Bin1} ->
+%% decode_private_key_v2(Bin1, Type);
+%% Error ->
+%% Error
+%% end;
+%% Error ->
+%% Error
+%% end.
+
+decode_private_key_v2(Private,"ssh-rsa") ->
+ case 'PKCS-1':decode( 'RSAPrivateKey', Private) of
+ {ok,RSA} -> %% FIXME Check for two-prime version
+ {ok, #ssh_key { type = rsa,
+ public = {RSA#'RSAPrivateKey'.modulus,
+ RSA#'RSAPrivateKey'.publicExponent},
+ private = {RSA#'RSAPrivateKey'.modulus,
+ RSA#'RSAPrivateKey'.privateExponent}
+ }};
+ Error ->
+ Error
+ end;
+decode_private_key_v2(Private, "ssh-dss") ->
+ case 'DSS':decode('DSAPrivateKey', Private) of
+ {ok,DSA} -> %% FIXME Check for two-prime version
+ {ok, #ssh_key { type = dsa,
+ public = {DSA#'DSAPrivateKey'.p,
+ DSA#'DSAPrivateKey'.q,
+ DSA#'DSAPrivateKey'.g,
+ DSA#'DSAPrivateKey'.y},
+ private= {DSA#'DSAPrivateKey'.p,
+ DSA#'DSAPrivateKey'.q,
+ DSA#'DSAPrivateKey'.g,
+ DSA#'DSAPrivateKey'.x}
+ }};
+ _ ->
+ {error,bad_format}
+ end.
+
+%% SSH1 private key format
+%% <<"SSH PRIVATE KEY FILE FORMATE 1.1\n" 0:8
+%% CipherNum:8, Reserved:32,
+%% NSz/uint32, N/bignum, E/bignum, Comment/string,
+%%
+%% [ R0:8 R1:8 R0:8 R1:8, D/bignum, IQMP/bignum, Q/bignum, P/bignum, Pad(8)]>>
+%%
+%% where [ ] is encrypted using des3 (ssh1 version) and
+%% a posssibly empty pass phrase using md5(passphase) as key
+%%
+
+read_private_key_v1(File, Type) ->
+ case file:read_file(File) of
+ {ok,<<"SSH PRIVATE KEY FILE FORMAT 1.1\n",0,
+ CipherNum,_Resereved:32,Bin/binary>>} ->
+ decode_private_key_v1(Bin, CipherNum,Type);
+ {ok,_} ->
+ {error, bad_format};
+ Error ->
+ Error
+ end.
+
+decode_private_key_v1(Bin, CipherNum, Type) ->
+ case ssh_bits:decode(Bin,0,[uint32, bignum, bignum, string]) of
+ {Offset,[_NSz,N,E,Comment]} ->
+ if Type == public ->
+ {ok,#ssh_key { type=rsa,
+ public={N,E},
+ comment=Comment}};
+ Type == private ->
+ <<_:Offset/binary, Encrypted/binary>> = Bin,
+ case ssh_bits:decode(decrypt1(Encrypted, CipherNum),0,
+ [uint32, bignum, bignum,
+ bignum, bignum,{pad,8}]) of
+ {_,[_,D,IQMP,Q,P]} ->
+ {ok,#ssh_key { type=rsa,
+ public={N,E},
+ private={D,IQMP,Q,P},
+ comment=Comment}};
+ _ ->
+ {error,bad_format}
+ end
+ end;
+ _ ->
+ {error,bad_format}
+ end.
+
+
+decrypt1(Bin, CipherNum) ->
+ decrypt1(Bin, CipherNum,"").
+
+decrypt1(Bin, CipherNum, Phrase) ->
+ if CipherNum == ?SSH_CIPHER_NONE; Phrase == "" ->
+ Bin;
+ CipherNum == ?SSH_CIPHER_3DES ->
+ <<K1:8/binary, K2:8/binary>> = erlang:md5(Phrase),
+ K3 = K1,
+ IV = <<0,0,0,0,0,0,0,0>>,
+ Bin1 = crypto:des_cbc_decrypt(K3,IV,Bin),
+ Bin2 = crypto:des_cbc_encrypt(K2,IV,Bin1),
+ crypto:des_cbc_decrypt(K1,IV,Bin2)
+ end.
+
+%% encrypt1(Bin, CipherNum) ->
+%% encrypt1(Bin, CipherNum,"").
+
+%% encrypt1(Bin, CipherNum, Phrase) ->
+%% if CipherNum == ?SSH_CIPHER_NONE; Phrase == "" ->
+%% Bin;
+%% CipherNum == ?SSH_CIPHER_3DES ->
+%% <<K1:8/binary, K2:8/binary>> = erlang:md5(Phrase),
+%% K3 = K1,
+%% IV = <<0,0,0,0,0,0,0,0>>,
+%% Bin1 = crypto:des_cbc_encrypt(K1,IV,Bin),
+%% Bin2 = crypto:des_cbc_decrypt(K2,IV,Bin1),
+%% crypto:des_cbc_encrypt(K3,IV,Bin2)
+%% end.
+
+lookup_host_key_fd(Fd, Host, Alg) ->
+ case io:get_line(Fd, '') of
+ eof ->
+ {error, not_found};
+ Line ->
+ case string:tokens(Line, " ") of
+ [HostList, Alg, KeyData] ->
+%% io:format(" ~p lookup_host_key_fd: HostList ~p Alg ~p KeyData ~p\n",
+%% [Host, HostList, Alg, KeyData]),
+ case lists:member(Host, string:tokens(HostList, ",")) of
+ true ->
+ decode_public_key_v2(ssh_bits:b64_decode(KeyData), Alg);
+ false ->
+ lookup_host_key_fd(Fd, Host, Alg)
+ end;
+ _ ->
+ lookup_host_key_fd(Fd, Host, Alg)
+ end
+ end.
+
+
+
+%% del_key_fd(Fd, Host) ->
+%% del_key_fd(Fd, Host, 0, 0).
+
+%% del_key_fd(Fd, Host, ReadPos0, WritePos0) ->
+%% case io:get_line(Fd, '') of
+%% eof ->
+%% if ReadPos0 == WritePos0 ->
+%% ok;
+%% true ->
+%% file:truncate(Fd)
+%% end;
+%% Line ->
+%% {ok,ReadPos1} = file:position(Fd, cur),
+%% case string:tokens(Line, " ") of
+%% [HostList, _Type, _KeyData] ->
+%% case lists:member(Host, string:tokens(HostList, ",")) of
+%% true ->
+%% del_key_fd(Fd, Host, ReadPos1, WritePos0);
+%% false ->
+%% if ReadPos0 == WritePos0 ->
+%% del_key_fd(Fd, Host, ReadPos1, ReadPos1);
+%% true ->
+%% file:position(Fd, WritePos0),
+%% file:write(Fd, Line),
+%% {ok,WritePos1} = file:position(Fd,cur),
+%% del_key_fd(Fd, Host, ReadPos1, WritePos1)
+%% end
+%% end;
+%% _ ->
+%% if ReadPos0 == WritePos0 ->
+%% del_key_fd(Fd, Host, ReadPos1, ReadPos1);
+%% true ->
+%% file:position(Fd, WritePos0),
+%% file:write(Fd, Line),
+%% {ok,WritePos1} = file:position(Fd,cur),
+%% del_key_fd(Fd, Host, ReadPos1, WritePos1)
+%% end
+%% end
+%% end.
+
+
+add_key_fd(Fd, Host, Key) ->
+ case Key#ssh_key.type of
+ rsa ->
+ {N,E} = Key#ssh_key.public,
+ DK = ssh_bits:b64_encode(
+ ssh_bits:encode(["ssh-rsa",E,N],
+ [string,mpint,mpint])),
+ file:write(Fd, [Host, " ssh-rsa ", DK, "\n"]);
+ dsa ->
+ {P,Q,G,Y} = Key#ssh_key.public,
+ DK = ssh_bits:b64_encode(
+ ssh_bits:encode(["ssh-dss",P,Q,G,Y],
+ [string,mpint,mpint,mpint,mpint])),
+ file:write(Fd, [Host, " ssh-dss ", DK, "\n"])
+ end.
+
+
+%% read_pem(Cs, Type) ->
+%% case read_line(Cs) of
+%% {"-----BEGIN "++Rest,Cs1} ->
+%% case string:tokens(Rest, " ") of
+%% [Type, "PRIVATE", "KEY-----"] ->
+%% read_pem64(Cs1, [], Type);
+%% _ ->
+%% {error, bad_format}
+%% end;
+%% {"",Cs1} when Cs1 =/= "" ->
+%% read_pem(Cs1,Type);
+%% {_,""} ->
+%% {error, bad_format}
+%% end.
+
+%% read_pem64(Cs, Acc, Type) ->
+%% case read_line(Cs) of
+%% {"-----END "++Rest,_Cs1} ->
+%% case string:tokens(Rest, " ") of
+%% [Type, "PRIVATE", "KEY-----"] ->
+%% {ok,ssh_bits:b64_decode(append(reverse(Acc)))};
+%% Toks ->
+%% error_logger:format("ssh: TOKENS=~p\n", [Toks]),
+%% {error, bad_format}
+%% end;
+%% {B64, Cs1} when Cs1 =/= "" ->
+%% read_pem64(Cs1, [B64|Acc], Type);
+%% _What ->
+%% {error, bad_format}
+%% end.
+
+
+%% read_line(Cs) -> read_line(Cs,[]).
+%% read_line([$\r,$\n|T], Acc) -> {reverse(Acc), T};
+%% read_line([$\n|T], Acc) -> {reverse(Acc), T};
+%% read_line([C|T], Acc) -> read_line(T,[C|Acc]);
+%% read_line([], Acc) -> {reverse(Acc),[]}.
+
+lookup_user_key(User, Alg, Opts) ->
+ SshDir = ssh_dir({remoteuser,User}, Opts),
+ case lookup_user_key_f(User, SshDir, Alg, "authorized_keys", Opts) of
+ {ok, Key} ->
+ {ok, Key};
+ _ ->
+ lookup_user_key_f(User, SshDir, Alg, "authorized_keys2", Opts)
+ end.
+
+lookup_user_key_f(_User, [], _Alg, _F, _Opts) ->
+ {error, nouserdir};
+lookup_user_key_f(_User, nouserdir, _Alg, _F, _Opts) ->
+ {error, nouserdir};
+lookup_user_key_f(_User, Dir, Alg, F, _Opts) ->
+ FileName = filename:join(Dir, F),
+ case file:open(FileName, [read]) of
+ {ok, Fd} ->
+ Res = lookup_user_key_fd(Fd, Alg),
+ file:close(Fd),
+ Res;
+ {error, Reason} ->
+ {error, {{openerr, Reason}, {file, FileName}}}
+ end.
+
+lookup_user_key_fd(Fd, Alg) ->
+ case io:get_line(Fd, '') of
+ eof ->
+ {error, not_found};
+ Line ->
+ case string:tokens(Line, " ") of
+ [Alg, KeyData, _] ->
+ %% io:format("lookup_user_key_fd: HostList ~p Alg ~p KeyData ~p\n",
+ %% [HostList, Alg, KeyData]),
+ decode_public_key_v2(ssh_bits:b64_decode(KeyData), Alg);
+ _Other ->
+ %%?dbg(false, "key_fd Other: ~w ~w\n", [Alg, _Other]),
+ lookup_user_key_fd(Fd, Alg)
+ end
+ end.
+
+
+encode_public_key(#ssh_key{type = rsa, public = {N, E}}) ->
+ ssh_bits:encode(["ssh-rsa",E,N],
+ [string,mpint,mpint]);
+encode_public_key(#ssh_key{type = dsa, public = {P,Q,G,Y}}) ->
+ ssh_bits:encode(["ssh-dss",P,Q,G,Y],
+ [string,mpint,mpint,mpint,mpint]).
+
+%%
+%% Utils
+%%
+
+%% server use this to find individual keys for
+%% an individual user when user tries to login
+%% with publickey
+ssh_dir({remoteuser, User}, Opts) ->
+ case proplists:get_value(user_dir_fun, Opts) of
+ undefined ->
+ case proplists:get_value(user_dir, Opts) of
+ undefined ->
+ default_user_dir();
+ Dir ->
+ Dir
+ end;
+ FUN ->
+ FUN(User)
+ end;
+
+%% client use this to find client ssh keys
+ssh_dir(user, Opts) ->
+ case proplists:get_value(user_dir, Opts, false) of
+ false -> default_user_dir();
+ D -> D
+ end;
+
+%% server use this to find server host keys
+ssh_dir(system, Opts) ->
+ proplists:get_value(system_dir, Opts, "/etc/ssh").
+
+file_name(Type, Name, Opts) ->
+ FN = filename:join(ssh_dir(Type, Opts), Name),
+ %%?dbg(?DBG_PATHS, "file_name: ~p\n", [FN]),
+ FN.
+
+default_user_dir()->
+ {ok,[[Home|_]]} = init:get_argument(home),
+ filename:join(Home, ".ssh").
diff --git a/lib/ssh/src/ssh_io.erl b/lib/ssh/src/ssh_io.erl
new file mode 100755
index 0000000000..0e343c20b4
--- /dev/null
+++ b/lib/ssh/src/ssh_io.erl
@@ -0,0 +1,79 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2009. 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%
+%%
+
+%%
+
+%%% Description: user interaction for SSH
+
+-module(ssh_io).
+
+-export([yes_no/1, read_password/1, read_line/1, format/2]).
+-import(lists, [reverse/1]).
+
+
+read_line(Prompt) when is_list(Prompt) ->
+ io:get_line(list_to_atom(Prompt));
+read_line(Prompt) when is_atom(Prompt) ->
+ io:get_line(Prompt).
+
+read_ln(Prompt) ->
+ trim(read_line(Prompt)).
+
+yes_no(Prompt) ->
+ io:format("~s [y/n]?", [Prompt]),
+ case read_ln('') of
+ "y" -> yes;
+ "n" -> no;
+ "Y" -> yes;
+ "N" -> no;
+ _ ->
+ io:format("please answer y or n\n"),
+ yes_no(Prompt)
+ end.
+
+
+read_password(Prompt) ->
+ format("~s", [listify(Prompt)]),
+ case io:get_password() of
+ "" ->
+ read_password(Prompt);
+ Pass -> Pass
+ end.
+
+listify(A) when is_atom(A) ->
+ atom_to_list(A);
+listify(L) when is_list(L) ->
+ L.
+
+format(Fmt, Args) ->
+ io:format(Fmt, Args).
+
+
+trim(Line) when is_list(Line) ->
+ reverse(trim1(reverse(trim1(Line))));
+trim(Other) -> Other.
+
+trim1([$\s|Cs]) -> trim(Cs);
+trim1([$\r|Cs]) -> trim(Cs);
+trim1([$\n|Cs]) -> trim(Cs);
+trim1([$\t|Cs]) -> trim(Cs);
+trim1(Cs) -> Cs.
+
+
+
diff --git a/lib/ssh/src/ssh_math.erl b/lib/ssh/src/ssh_math.erl
new file mode 100755
index 0000000000..efe7f56979
--- /dev/null
+++ b/lib/ssh/src/ssh_math.erl
@@ -0,0 +1,131 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2009. 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%
+%%
+
+%%
+
+%%% Description: SSH math utilities
+
+-module(ssh_math).
+
+-export([ilog2/1, ipow/3, invert/2, ipow2/3]).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%
+%% INTEGER utils
+%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% number of bits (used) in a integer = isize(N) = |log2(N)|+1
+ilog2(N) ->
+ ssh_bits:isize(N) - 1.
+
+
+%% calculate A^B mod M
+ipow(A, B, M) when M > 0, B >= 0 ->
+ crypto:mod_exp(A, B, M).
+
+ipow2(A, B, M) when M > 0, B >= 0 ->
+ if A == 1 ->
+ 1;
+ true ->
+ ipow2(A, B, M, 1)
+ end.
+
+ipow2(A, 1, M, Prod) ->
+ (A*Prod) rem M;
+ipow2(_A, 0, _M, Prod) ->
+ Prod;
+ipow2(A, B, M, Prod) ->
+ B1 = B bsr 1,
+ A1 = (A*A) rem M,
+ if B - B1 == B1 ->
+ ipow2(A1, B1, M, Prod);
+ true ->
+ ipow2(A1, B1, M, (A*Prod) rem M)
+ end.
+
+%% %%
+%% %% Normal gcd
+%% %%
+%% gcd(R, Q) when abs(Q) < abs(R) -> gcd1(Q,R);
+%% gcd(R, Q) -> gcd1(R,Q).
+
+%% gcd1(0, Q) -> Q;
+%% gcd1(R, Q) ->
+%% gcd1(Q rem R, R).
+
+
+%% %%
+%% %% Least common multiple of (R,Q)
+%% %%
+%% lcm(0, _Q) -> 0;
+%% lcm(_R, 0) -> 0;
+%% lcm(R, Q) ->
+%% (Q div gcd(R, Q)) * R.
+
+%% %%
+%% %% Extended gcd gcd(R,Q) -> {G, {A,B}} such that G == R*A + Q*B
+%% %%
+%% %% Here we could have use for a bif divrem(Q, R) -> {Quote, Remainder}
+%% %%
+%% egcd(R,Q) when abs(Q) < abs(R) -> egcd1(Q,R,1,0,0,1);
+%% egcd(R,Q) -> egcd1(R,Q,0,1,1,0).
+
+%% egcd1(0,Q,_,_,Q1,Q2) -> {Q, {Q2,Q1}};
+%% egcd1(R,Q,R1,R2,Q1,Q2) ->
+%% D = Q div R,
+%% egcd1(Q rem R, R, Q1-D*R1, Q2-D*R2, R1, R2).
+
+%%
+%% Invert an element X mod P
+%% Calculated as {1, {A,B}} = egcd(X,P),
+%% 1 == P*A + X*B == X*B (mod P) i.e B is the inverse element
+%%
+%% X > 0, P > 0, X < P (P should be prime)
+%%
+invert(X,P) when X > 0, P > 0, X < P ->
+ I = inv(X,P,1,0),
+ if
+ I < 0 -> P + I;
+ true -> I
+ end.
+
+inv(0,_,_,Q) -> Q;
+inv(X,P,R1,Q1) ->
+ D = P div X,
+ inv(P rem X, X, Q1 - D*R1, R1).
+
+
+%% %%
+%% %% Integer square root
+%% %%
+
+%% isqrt(0) -> 0;
+%% isqrt(1) -> 1;
+%% isqrt(X) when X >= 0 ->
+%% R = X div 2,
+%% isqrt(X div R, R, X).
+
+%% isqrt(Q,R,X) when Q < R ->
+%% R1 = (R+Q) div 2,
+%% isqrt(X div R1, R1, X);
+%% isqrt(_, R, _) -> R.
+
+
diff --git a/lib/ssh/src/ssh_no_io.erl b/lib/ssh/src/ssh_no_io.erl
new file mode 100644
index 0000000000..5f363ae6c2
--- /dev/null
+++ b/lib/ssh/src/ssh_no_io.erl
@@ -0,0 +1,39 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2009. 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%
+%%
+
+%%
+
+%%% Description: ssh_io replacement that throws on everything
+
+-module(ssh_no_io).
+
+-export([yes_no/1, read_password/1, read_line/1, format/2]).
+
+yes_no(_Prompt) ->
+ throw({no_io_allowed, yes_no}).
+
+read_password(_Prompt) ->
+ throw({no_io_allowed, read_password}).
+
+read_line(_Prompt) ->
+ throw({no_io_allowed, read_line}).
+
+format(_Fmt, _Args) ->
+ throw({no_io_allowed, format}).
+
diff --git a/lib/ssh/src/ssh_rsa.erl b/lib/ssh/src/ssh_rsa.erl
new file mode 100755
index 0000000000..7c2bf9a2bf
--- /dev/null
+++ b/lib/ssh/src/ssh_rsa.erl
@@ -0,0 +1,299 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2009. 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%
+%%
+
+%%
+
+%%% Description: rsa public-key sign and verify
+
+-module(ssh_rsa).
+
+-export([verify/3, sign/2]).
+-export([alg_name/0]).
+
+-include("ssh.hrl").
+-include("PKCS-1.hrl").
+
+
+-define(MGF(Seed,Len), mgf1((Seed),(Len))).
+-define(HASH(X), crypto:sha((X))).
+-define(HLen, 20).
+
+%% start() ->
+%% crypto:start().
+
+%% sign_file(File) ->
+%% start(),
+%% {ok,Bin} = file:read_file(File),
+%% {ok,Key} = ssh_file:private_host_rsa_key(user),
+%% sign(Key, Bin).
+
+%% verify_file(File, Sig) ->
+%% start(),
+%% {ok,Bin} = file:read_file(File),
+%% {ok,Key} = ssh_file:public_host_rsa_key(user),
+%% verify(Key, Bin, Sig).
+
+sign(Private,Mb) ->
+ rsassa_pkcs1_v1_5_sign(Private,Mb).
+
+verify(Public,Mb,Sb) ->
+ rsassa_pkcs1_v1_5_verify(Public,Mb,Sb).
+
+
+
+%% Integer to octet string
+i2osp(X, XLen) ->
+ ssh_bits:i2bin(X, XLen).
+
+%% Octet string to Integer
+os2ip(X) ->
+ ssh_bits:bin2i(X).
+
+%% decrypt1, M = message representative
+%% rsaep(#ssh_key { public={N,E}}, M) ->
+%% ?ssh_assert(M >= 0 andalso M =< N-1, out_of_range),
+%% ssh_math:ipow(M, E, N).
+
+%% encrypt1, C = cipher representative
+%% rsadp(#ssh_key { public={N,_}, private={_,D}}, C) ->
+%% ?ssh_assert(C >= 0 andalso C =< N-1, out_of_range),
+%% ssh_math:ipow(C, D, N).
+
+%% sign1, M = message representative
+rsasp1(#ssh_key { public={N,_}, private={_,D}}, M) ->
+ ?ssh_assert((M >= 0 andalso M =< N-1), out_of_range),
+ ssh_math:ipow(M, D, N).
+
+%% verify1, S =signature representative
+rsavp1(#ssh_key { public={N,E}}, S) ->
+ ?ssh_assert(S >= 0 andalso S =< N-1, out_of_range),
+ ssh_math:ipow(S, E, N).
+
+
+%% M messaage
+%% rsaes_oaep_encrypt(Public, M) ->
+%% rsaes_oaep_encrypt(Public, M, <<>>).
+
+%% rsaes_oaep_encrypt(Public=#ssh_key { public={N,_E}}, M, L) ->
+%% ?ssh_assert(size(L) =< 16#ffffffffffffffff, label_to_long),
+%% K = (ssh_bits:isize(N)+7) div 8,
+%% MLen = size(M),
+%% %% LLen = size(L),
+%% ?ssh_assert(MLen =< K - 2*?HLen - 2, message_to_long),
+%% LHash = ?HASH(L),
+%% PS = ssh_bits:fill_bits(K - MLen - 2*?HLen - 2, 0),
+%% DB = <<LHash/binary, PS/binary, 16#01, M/binary>>,
+%% Seed = ssh_bits:random(?HLen),
+%% DbMask = ?MGF(Seed, K - ?HLen - 1),
+%% MaskedDB = ssh_bits:xor_bits(DB, DbMask),
+%% SeedMask = ?MGF(MaskedDB, ?HLen),
+%% MaskedSeed = ssh_bits:xor_bits(Seed, SeedMask),
+%% EM = <<16#00, MaskedSeed/binary, MaskedDB/binary>>,
+%% Mc = os2ip(EM),
+%% C = rsaep(Public, Mc),
+%% i2osp(C, K).
+
+%% rsaes_oaep_decrypt(Key, C) ->
+%% rsaes_oaep_decrypt(Key, C, <<>>).
+
+%% rsaes_oaep_decrypt(Private=#ssh_key { public={N,_},private={_,_}},Cb,L) ->
+%% ?ssh_assert(size(L) =< 16#ffffffffffffffff, label_to_long),
+%% K = (ssh_bits:isize(N)+7) div 8,
+%% ?ssh_assert(K == 2*?HLen + 2, decryption_error),
+%% C = os2ip(Cb),
+%% M = rsadp(Private, C),
+%% EM = i2osp(M, K),
+%% LHash = ?HASH(L),
+%% MLen = K - ?HLen -1,
+%% case EM of
+%% <<16#00, MaskedSeed:?HLen/binary, MaskedDB:MLen>> ->
+%% SeedMask = ?MGF(MaskedDB, ?HLen),
+%% Seed = ssh_bits:xor_bits(MaskedSeed, SeedMask),
+%% DbMask = ?MGF(Seed, K - ?HLen - 1),
+%% DB = ssh_bits:xor_bits(MaskedDB, DbMask),
+%% PSLen = K - MLen - 2*?HLen - 2,
+%% case DB of
+%% <<LHash:?HLen, _PS:PSLen/binary, 16#01, M/binary>> ->
+%% M;
+%% _ ->
+%% exit(decryption_error)
+%% end;
+%% _ ->
+%% exit(decryption_error)
+%% end.
+
+
+%% rsaes_pkcs1_v1_5_encrypt(Public=#ssh_key { public={N,_}}, M) ->
+%% K = (ssh_bits:isize(N)+7) div 8,
+%% MLen = size(M),
+%% ?ssh_assert(MLen =< K - 11, message_to_long),
+%% PS = ssh_bits:random(K - MLen - 3),
+%% EM = <<16#00,16#02,PS/binary,16#00,M/binary>>,
+%% Mc = os2ip(EM),
+%% C = rsaep(Public, Mc),
+%% i2osp(C, K).
+
+
+%% rsaes_pkcs1_v1_5_decrypt(Private=#ssh_key { public={N,_},private={_,_}},
+%% Cb) ->
+%% K = (ssh_bits:isize(N)+7) div 8,
+%% CLen = size(Cb),
+%% ?ssh_assert(CLen == K andalso K >= 11, decryption_error),
+%% C = os2ip(Cb),
+%% M = rsadp(Private, C),
+%% EM = i2osp(M, K),
+%% PSLen = K - CLen - 3,
+%% case EM of
+%% <<16#00, 16#02, _PS:PSLen/binary, 16#00, M>> ->
+%% M;
+%% _ ->
+%% exit(decryption_error)
+%% end.
+
+%% rsassa_pss_sign(Private=#ssh_key { public={N,_},private={_,_}},Mb) ->
+%% ModBits = ssh_bits:isize(N),
+%% K = (ModBits+7) div 8,
+%% EM = emsa_pss_encode(Mb, ModBits - 1),
+%% M = os2ip(EM),
+%% S = rsasp1(Private, M),
+%% i2osp(S, K).
+
+%% rsassa_pss_verify(Public=#ssh_key { public={N,_E}},Mb,Sb) ->
+%% ModBits = ssh_bits:isize(N),
+%% K = (ModBits+7) div 8,
+%% ?ssh_assert(size(Sb) == K, invalid_signature),
+%% S = os2ip(Sb),
+%% M = rsavp1(Public,S),
+%% EMLen = (ModBits-1+7) div 8,
+%% EM = i2osp(M, EMLen),
+%% emsa_pss_verify(Mb, EM, ModBits-1).
+
+
+rsassa_pkcs1_v1_5_sign(Private=#ssh_key { public={N,_},private={_,_D}},Mb) ->
+ K = (ssh_bits:isize(N)+7) div 8,
+ EM = emsa_pkcs1_v1_5_encode(Mb, K),
+ M = os2ip(EM),
+ S = rsasp1(Private, M),
+ i2osp(S, K).
+
+rsassa_pkcs1_v1_5_verify(Public=#ssh_key { public={N,_E}}, Mb, Sb) ->
+ K = (ssh_bits:isize(N)+7) div 8,
+ ?ssh_assert(size(Sb) == K, invalid_signature),
+ S = os2ip(Sb),
+ M = rsavp1(Public, S),
+ EM = i2osp(M, K),
+ %?dbg(true, "verify K=~p S=~w ~n#M=~w~n#EM=~w~n", [K, S, M, EM]),
+ case emsa_pkcs1_v1_5_encode(Mb, K) of
+ EM -> ok;
+ _S ->
+ io:format("S: ~p~n", [_S]),
+ {error, invalid_signature} % exit(invalid_signature)
+ end.
+
+
+emsa_pkcs1_v1_5_encode(M, EMLen) ->
+ H = ?HASH(M),
+ %% Must use speical xxNull types here!
+ Alg = #'AlgorithmNull' { algorithm = ?'id-sha1',
+ parameters = <<>> },
+ {ok,TCode} = 'PKCS-1':encode('DigestInfoNull',
+ #'DigestInfoNull'{ digestAlgorithm = Alg,
+ digest = H }),
+ T = list_to_binary(TCode),
+ TLen = size(T),
+ ?ssh_assert(EMLen >= TLen + 11, message_to_short),
+ PS = ssh_bits:fill_bits(EMLen - TLen - 3, 16#ff),
+ <<16#00, 16#01, PS/binary, 16#00, T/binary>>.
+
+
+%% emsa_pss_encode(M, EMBits) ->
+%% emsa_pss_encode(M, EMBits, 0).
+
+%% emsa_pss_encode(M, EMBits, SLen) ->
+%% ?ssh_assert(size(M) =< 16#ffffffffffffffff, message_to_long),
+%% EMLen = (EMBits + 7) div 8,
+%% MHash = ?HASH(M),
+%% ?ssh_assert(EMLen >= ?HLen + SLen + 2, encoding_error),
+%% Salt = ssh_bits:random(SLen),
+%% M1 = [16#00,16#00,16#00,16#00,16#00,16#00,16#00,16#00,
+%% MHash, Salt],
+%% H = ?HASH(M1),
+%% PS = ssh_bits:fill_bits(EMLen-SLen-?HLen-2, 0),
+%% DB = <<PS/binary, 16#01, Salt/binary>>,
+%% DbMask = ?MGF(H, EMLen - ?HLen -1),
+%% MaskedDB = ssh_bits:xor_bits(DB, DbMask),
+%% ZLen = 8*EMLen - EMBits,
+%% NZLen = (8 - (ZLen rem 8)) rem 8,
+%% <<_:ZLen, NZ:NZLen, MaskedDB1/binary>> = MaskedDB,
+%% MaskedDB2 = <<0:ZLen, NZ:NZLen, MaskedDB1/binary>>,
+%% <<MaskedDB2/binary, H/binary, 16#BC>>.
+
+
+%% emsa_pss_verify(M, EM, EMBits) ->
+%% emsa_pss_verify(M, EM, EMBits, 0).
+
+%% emsa_pss_verify(M, EM, EMBits, SLen) ->
+%% ?ssh_assert(size(M) =< 16#ffffffffffffffff, message_to_long),
+%% EMLen = (EMBits + 7) div 8,
+%% MHash = ?HASH(M),
+%% ?ssh_assert(EMLen >= ?HLen + SLen + 2, inconsistent),
+%% MaskLen = (EMLen - ?HLen - 1)-1,
+%% ZLen = 8*EMLen - EMBits,
+%% NZLen = (8 - (ZLen rem 8)) rem 8,
+%% case EM of
+%% <<0:ZLen,Nz:NZLen,MaskedDB1:MaskLen/binary, H:?HLen/binary, 16#BC>> ->
+%% MaskedDB = <<0:ZLen,Nz:NZLen,MaskedDB1/binary>>,
+%% DbMask = ?MGF(H, EMLen - ?HLen - 1),
+%% DB = ssh_bits:xor_bits(MaskedDB, DbMask),
+%% PSLen1 = (EMLen - SLen - ?HLen - 2) - 1,
+%% PS = ssh_bits:fill_bits(PSLen1, 0),
+%% case DB of
+%% <<_:ZLen,0:NZLen,PS:PSLen1/binary,16#01,Salt:SLen/binary>> ->
+%% M1 = [16#00,16#00,16#00,16#00,16#00,16#00,16#00,16#00,
+%% MHash, Salt],
+%% case ?HASH(M1) of
+%% H -> ok;
+%% _ -> exit(inconsistent)
+%% end;
+%% _ ->
+%% exit(inconsistent)
+%% end;
+%% _ ->
+%% exit(inconsistent)
+%% end.
+
+
+
+%% Mask generating function MGF1
+%% mgf1(MGFSeed, MaskLen) ->
+%% T = mgf1_loop(0, ((MaskLen + ?HLen -1) div ?HLen) - 1, MGFSeed, ""),
+%% <<R:MaskLen/binary, _/binary>> = T,
+%% R.
+
+%% mgf1_loop(Counter, N, _, T) when Counter > N ->
+%% list_to_binary(T);
+%% mgf1_loop(Counter, N, MGFSeed, T) ->
+%% C = i2osp(Counter, 4),
+%% mgf1_loop(Counter+1, N, MGFSeed, [T, ?HASH([MGFSeed, C])]).
+
+
+
+
+alg_name() ->
+ "ssh-rsa".
diff --git a/lib/ssh/src/ssh_sftp.erl b/lib/ssh/src/ssh_sftp.erl
new file mode 100755
index 0000000000..cbfa208f6f
--- /dev/null
+++ b/lib/ssh/src/ssh_sftp.erl
@@ -0,0 +1,1148 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2009. 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%
+%%
+
+%%
+
+%%% Description: SFTP protocol front-end
+
+-module(ssh_sftp).
+
+-behaviour(ssh_channel).
+
+-include_lib("kernel/include/file.hrl").
+-include("ssh.hrl").
+-include("ssh_xfer.hrl").
+
+%% API
+
+-export([start_channel/1, start_channel/2, start_channel/3, stop_channel/1]).
+
+-export([open/3, opendir/2, close/2, readdir/2, pread/4, read/3,
+ open/4, opendir/3, close/3, readdir/3, pread/5, read/4,
+ apread/4, aread/3, pwrite/4, write/3, apwrite/4, awrite/3,
+ pwrite/5, write/4,
+ position/3, real_path/2, read_file_info/2, get_file_info/2,
+ position/4, real_path/3, read_file_info/3, get_file_info/3,
+ write_file_info/3, read_link_info/2, read_link/2, make_symlink/3,
+ write_file_info/4, read_link_info/3, read_link/3, make_symlink/4,
+ rename/3, delete/2, make_dir/2, del_dir/2, send_window/1,
+ rename/4, delete/3, make_dir/3, del_dir/3, send_window/2,
+ recv_window/1, list_dir/2, read_file/2, write_file/3,
+ recv_window/2, list_dir/3, read_file/3, write_file/4]).
+
+%% Deprecated
+-export([connect/1, connect/2, connect/3, stop/1]).
+
+-deprecated({connect, 1, next_major_release}).
+-deprecated({connect, 2, next_major_release}).
+-deprecated({connect, 3, next_major_release}).
+-deprecated({stop, 1, next_major_release}).
+
+%% ssh_channel callbacks
+-export([init/1, handle_call/3, handle_msg/2, handle_ssh_msg/2, terminate/2]).
+%% TODO: Should be placed elsewhere ssh_sftpd should not call functions in ssh_sftp!
+-export([info_to_attr/1, attr_to_info/1]).
+
+-record(state,
+ {
+ xf,
+ rep_buf = <<>>,
+ req_id,
+ req_list = [], %% {ReqId, Fun}
+ inf %% list of fileinf
+ }).
+
+-record(fileinf,
+ {
+ handle,
+ offset,
+ size,
+ mode
+ }).
+
+-define(FILEOP_TIMEOUT, infinity).
+
+-define(NEXT_REQID(S),
+ S#state { req_id = (S#state.req_id + 1) band 16#ffffffff}).
+
+-define(XF(S), S#state.xf).
+-define(REQID(S), S#state.req_id).
+
+%%====================================================================
+%% API
+%%====================================================================
+start_channel(Cm) when is_pid(Cm) ->
+ start_channel(Cm, []);
+start_channel(Host) when is_list(Host) ->
+ start_channel(Host, []).
+start_channel(Cm, Opts) when is_pid(Cm) ->
+ Timeout = proplists:get_value(timeout, Opts, infinity),
+ case ssh_xfer:attach(Cm, []) of
+ {ok, ChannelId, Cm} ->
+ case ssh_channel:start(Cm, ChannelId,
+ ?MODULE, [Cm, ChannelId, Timeout]) of
+ {ok, Pid} ->
+ case wait_for_version_negotiation(Pid, Timeout) of
+ ok ->
+ {ok, Pid};
+ TimeOut ->
+ TimeOut
+ end;
+ {error, Reason} ->
+ {error, Reason};
+ ignore ->
+ {error, ignore}
+ end;
+ Error ->
+ Error
+ end;
+
+start_channel(Host, Opts) ->
+ start_channel(Host, 22, Opts).
+start_channel(Host, Port, Opts) ->
+ Timeout = proplists:get_value(timeout, Opts, infinity),
+ case ssh_xfer:connect(Host, Port, proplists:delete(timeout, Opts)) of
+ {ok, ChannelId, Cm} ->
+ case ssh_channel:start(Cm, ChannelId, ?MODULE, [Cm,
+ ChannelId, Timeout]) of
+ {ok, Pid} ->
+ case wait_for_version_negotiation(Pid, Timeout) of
+ ok ->
+ {ok, Pid, Cm};
+ TimeOut ->
+ TimeOut
+ end;
+ {error, Reason} ->
+ {error, Reason};
+ ignore ->
+ {error, ignore}
+ end;
+ Error ->
+ Error
+ end.
+
+stop_channel(Pid) ->
+ case process_info(Pid, [trap_exit]) of
+ [{trap_exit, Bool}] ->
+ process_flag(trap_exit, true),
+ link(Pid),
+ exit(Pid, ssh_sftp_stop_channel),
+ receive
+ {'EXIT', Pid, normal} ->
+ ok
+ after 5000 ->
+ exit(Pid, kill),
+ receive
+ {'EXIT', Pid, killed} ->
+ ok
+ end
+ end,
+ process_flag(trap_exit, Bool),
+ ok;
+ undefined ->
+ ok
+ end.
+
+wait_for_version_negotiation(Pid, Timeout) ->
+ call(Pid, wait_for_version_negotiation, Timeout).
+
+open(Pid, File, Mode) ->
+ open(Pid, File, Mode, ?FILEOP_TIMEOUT).
+open(Pid, File, Mode, FileOpTimeout) ->
+ call(Pid, {open, false, File, Mode}, FileOpTimeout).
+
+opendir(Pid, Path) ->
+ opendir(Pid, Path, ?FILEOP_TIMEOUT).
+opendir(Pid, Path, FileOpTimeout) ->
+ call(Pid, {opendir, false, Path}, FileOpTimeout).
+
+close(Pid, Handle) ->
+ close(Pid, Handle, ?FILEOP_TIMEOUT).
+close(Pid, Handle, FileOpTimeout) ->
+ call(Pid, {close,false,Handle}, FileOpTimeout).
+
+readdir(Pid,Handle) ->
+ readdir(Pid,Handle, ?FILEOP_TIMEOUT).
+readdir(Pid,Handle, FileOpTimeout) ->
+ call(Pid, {readdir,false,Handle}, FileOpTimeout).
+
+pread(Pid, Handle, Offset, Len) ->
+ pread(Pid, Handle, Offset, Len, ?FILEOP_TIMEOUT).
+pread(Pid, Handle, Offset, Len, FileOpTimeout) ->
+ call(Pid, {pread,false,Handle, Offset, Len}, FileOpTimeout).
+
+read(Pid, Handle, Len) ->
+ read(Pid, Handle, Len, ?FILEOP_TIMEOUT).
+read(Pid, Handle, Len, FileOpTimeout) ->
+ call(Pid, {read,false,Handle, Len}, FileOpTimeout).
+
+%% TODO this ought to be a cast! Is so in all practial meaning
+%% even if it is obscure!
+apread(Pid, Handle, Offset, Len) ->
+ call(Pid, {pread,true,Handle, Offset, Len}, infinity).
+
+%% TODO this ought to be a cast!
+aread(Pid, Handle, Len) ->
+ call(Pid, {read,true,Handle, Len}, infinity).
+
+pwrite(Pid, Handle, Offset, Data) ->
+ pwrite(Pid, Handle, Offset, Data, ?FILEOP_TIMEOUT).
+pwrite(Pid, Handle, Offset, Data, FileOpTimeout) ->
+ call(Pid, {pwrite,false,Handle,Offset,Data}, FileOpTimeout).
+
+write(Pid, Handle, Data) ->
+ write(Pid, Handle, Data, ?FILEOP_TIMEOUT).
+write(Pid, Handle, Data, FileOpTimeout) ->
+ call(Pid, {write,false,Handle,Data}, FileOpTimeout).
+
+%% TODO this ought to be a cast! Is so in all practial meaning
+%% even if it is obscure!
+apwrite(Pid, Handle, Offset, Data) ->
+ call(Pid, {pwrite,true,Handle,Offset,Data}, infinity).
+
+%% TODO this ought to be a cast! Is so in all practial meaning
+%% even if it is obscure!
+awrite(Pid, Handle, Data) ->
+ call(Pid, {write,true,Handle,Data}, infinity).
+
+position(Pid, Handle, Pos) ->
+ position(Pid, Handle, Pos, ?FILEOP_TIMEOUT).
+position(Pid, Handle, Pos, FileOpTimeout) ->
+ call(Pid, {position, Handle, Pos}, FileOpTimeout).
+
+real_path(Pid, Path) ->
+ real_path(Pid, Path, ?FILEOP_TIMEOUT).
+real_path(Pid, Path, FileOpTimeout) ->
+ call(Pid, {real_path, false, Path}, FileOpTimeout).
+
+read_file_info(Pid, Name) ->
+ read_file_info(Pid, Name, ?FILEOP_TIMEOUT).
+read_file_info(Pid, Name, FileOpTimeout) ->
+ call(Pid, {read_file_info,false,Name}, FileOpTimeout).
+
+get_file_info(Pid, Handle) ->
+ get_file_info(Pid, Handle, ?FILEOP_TIMEOUT).
+get_file_info(Pid, Handle, FileOpTimeout) ->
+ call(Pid, {get_file_info,false,Handle}, FileOpTimeout).
+
+write_file_info(Pid, Name, Info) ->
+ write_file_info(Pid, Name, Info, ?FILEOP_TIMEOUT).
+write_file_info(Pid, Name, Info, FileOpTimeout) ->
+ call(Pid, {write_file_info,false,Name, Info}, FileOpTimeout).
+
+read_link_info(Pid, Name) ->
+ read_link_info(Pid, Name, ?FILEOP_TIMEOUT).
+read_link_info(Pid, Name, FileOpTimeout) ->
+ call(Pid, {read_link_info,false,Name}, FileOpTimeout).
+
+read_link(Pid, LinkName) ->
+ read_link(Pid, LinkName, ?FILEOP_TIMEOUT).
+read_link(Pid, LinkName, FileOpTimeout) ->
+ case call(Pid, {read_link,false,LinkName}, FileOpTimeout) of
+ {ok, [{Name, _Attrs}]} ->
+ {ok, Name};
+ ErrMsg ->
+ ErrMsg
+ end.
+
+make_symlink(Pid, Name, Target) ->
+ make_symlink(Pid, Name, Target, ?FILEOP_TIMEOUT).
+make_symlink(Pid, Name, Target, FileOpTimeout) ->
+ call(Pid, {make_symlink,false, Name, Target}, FileOpTimeout).
+
+rename(Pid, FromFile, ToFile) ->
+ rename(Pid, FromFile, ToFile, ?FILEOP_TIMEOUT).
+rename(Pid, FromFile, ToFile, FileOpTimeout) ->
+ call(Pid, {rename,false,FromFile, ToFile}, FileOpTimeout).
+
+delete(Pid, Name) ->
+ delete(Pid, Name, ?FILEOP_TIMEOUT).
+delete(Pid, Name, FileOpTimeout) ->
+ call(Pid, {delete,false,Name}, FileOpTimeout).
+
+make_dir(Pid, Name) ->
+ make_dir(Pid, Name, ?FILEOP_TIMEOUT).
+make_dir(Pid, Name, FileOpTimeout) ->
+ call(Pid, {make_dir,false,Name}, FileOpTimeout).
+
+del_dir(Pid, Name) ->
+ del_dir(Pid, Name, ?FILEOP_TIMEOUT).
+del_dir(Pid, Name, FileOpTimeout) ->
+ call(Pid, {del_dir,false,Name}, FileOpTimeout).
+
+%% TODO : send_window and recv_window - Really needed? Not documented!
+%% internal use maybe should be handled in other way!
+send_window(Pid) ->
+ send_window(Pid, ?FILEOP_TIMEOUT).
+send_window(Pid, FileOpTimeout) ->
+ call(Pid, send_window, FileOpTimeout).
+
+recv_window(Pid) ->
+ recv_window(Pid, ?FILEOP_TIMEOUT).
+recv_window(Pid, FileOpTimeout) ->
+ call(Pid, recv_window, FileOpTimeout).
+
+
+list_dir(Pid, Name) ->
+ list_dir(Pid, Name, ?FILEOP_TIMEOUT).
+
+list_dir(Pid, Name, FileOpTimeout) ->
+ case opendir(Pid, Name, FileOpTimeout) of
+ {ok,Handle} ->
+ Res = do_list_dir(Pid, Handle, FileOpTimeout, []),
+ close(Pid, Handle, FileOpTimeout),
+ case Res of
+ {ok, List} ->
+ NList = lists:foldl(fun({Nm, _Info},Acc) ->
+ [Nm|Acc] end,
+ [], List),
+ {ok,NList};
+ Error -> Error
+ end;
+ Error ->
+ Error
+ end.
+
+do_list_dir(Pid, Handle, FileOpTimeout, Acc) ->
+ case readdir(Pid, Handle, FileOpTimeout) of
+ {ok, []} ->
+ {ok, Acc};
+ {ok, Names} ->
+ do_list_dir(Pid, Handle, FileOpTimeout, Acc ++ Names);
+ eof ->
+ {ok, Acc};
+ Error ->
+ Error
+ end.
+
+
+read_file(Pid, Name) ->
+ read_file(Pid, Name, ?FILEOP_TIMEOUT).
+
+read_file(Pid, Name, FileOpTimeout) ->
+ case open(Pid, Name, [read, binary], FileOpTimeout) of
+ {ok, Handle} ->
+ {ok,{_WindowSz,PacketSz}} = recv_window(Pid, FileOpTimeout),
+ Res = read_file_loop(Pid, Handle, PacketSz, FileOpTimeout, []),
+ close(Pid, Handle),
+ Res;
+ Error ->
+ Error
+ end.
+
+read_file_loop(Pid, Handle, PacketSz, FileOpTimeout, Acc) ->
+ case read(Pid, Handle, PacketSz, FileOpTimeout) of
+ {ok, Data} ->
+ read_file_loop(Pid, Handle, PacketSz, FileOpTimeout, [Data|Acc]);
+ eof ->
+ {ok, list_to_binary(lists:reverse(Acc))};
+ Error ->
+ Error
+ end.
+
+write_file(Pid, Name, List) ->
+ write_file(Pid, Name, List, ?FILEOP_TIMEOUT).
+
+write_file(Pid, Name, List, FileOpTimeout) when is_list(List) ->
+ write_file(Pid, Name, list_to_binary(List), FileOpTimeout);
+write_file(Pid, Name, Bin, FileOpTimeout) ->
+ case open(Pid, Name, [write, binary], FileOpTimeout) of
+ {ok, Handle} ->
+ {ok,{_Window,Packet}} = send_window(Pid, FileOpTimeout),
+ Res = write_file_loop(Pid, Handle, 0, Bin, size(Bin), Packet,
+ FileOpTimeout),
+ close(Pid, Handle, FileOpTimeout),
+ Res;
+ Error ->
+ Error
+ end.
+
+write_file_loop(_Pid, _Handle, _Pos, _Bin, 0, _PacketSz,_FileOpTimeout) ->
+ ok;
+write_file_loop(Pid, Handle, Pos, Bin, Remain, PacketSz, FileOpTimeout) ->
+ if Remain >= PacketSz ->
+ <<_:Pos/binary, Data:PacketSz/binary, _/binary>> = Bin,
+ case write(Pid, Handle, Data, FileOpTimeout) of
+ ok ->
+ write_file_loop(Pid, Handle,
+ Pos+PacketSz, Bin, Remain-PacketSz,
+ PacketSz, FileOpTimeout);
+ Error ->
+ Error
+ end;
+ true ->
+ <<_:Pos/binary, Data/binary>> = Bin,
+ write(Pid, Handle, Data, FileOpTimeout)
+ end.
+
+%%====================================================================
+%% SSh channel callbacks
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% Function: init(Args) -> {ok, State}
+%%
+%% Description:
+%%--------------------------------------------------------------------
+init([Cm, ChannelId, Timeout]) ->
+ erlang:monitor(process, Cm),
+ case ssh_connection:subsystem(Cm, ChannelId, "sftp", Timeout) of
+ success ->
+ Xf = #ssh_xfer{cm = Cm,
+ channel = ChannelId},
+ {ok, #state{xf = Xf,
+ req_id = 0,
+ rep_buf = <<>>,
+ inf = new_inf()}};
+ failure ->
+ {stop, {error, "server failed to start sftp subsystem"}};
+ Error ->
+ {stop, Error}
+ end.
+
+%%--------------------------------------------------------------------
+%% Function: handle_call/3
+%% Description: Handling call messages
+%% Returns: {reply, Reply, State} |
+%% {reply, Reply, State, Timeout} |
+%% {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, Reply, State} | (terminate/2 is called)
+%% {stop, Reason, State} (terminate/2 is called)
+%%--------------------------------------------------------------------
+handle_call({{timeout, infinity}, wait_for_version_negotiation}, From,
+ #state{xf = #ssh_xfer{vsn = undefined} = Xf} = State) ->
+ {noreply, State#state{xf = Xf#ssh_xfer{vsn = From}}};
+
+handle_call({{timeout, Timeout}, wait_for_version_negotiation}, From,
+ #state{xf = #ssh_xfer{vsn = undefined} = Xf} = State) ->
+ timer:send_after(Timeout, {timeout, undefined, From}),
+ {noreply, State#state{xf = Xf#ssh_xfer{vsn = From}}};
+
+handle_call({_, wait_for_version_negotiation}, _, State) ->
+ {reply, ok, State};
+
+handle_call({{timeout, infinity}, Msg}, From, State) ->
+ do_handle_call(Msg, From, State);
+handle_call({{timeout, Timeout}, Msg}, From, #state{req_id = Id} = State) ->
+ timer:send_after(Timeout, {timeout, Id, From}),
+ do_handle_call(Msg, From, State).
+
+do_handle_call({open, Async,FileName,Mode}, From, #state{xf = XF} = State) ->
+ {Access,Flags,Attrs} = open_mode(XF#ssh_xfer.vsn, Mode),
+ ReqID = State#state.req_id,
+ ssh_xfer:open(XF, ReqID, FileName, Access, Flags, Attrs),
+ case Async of
+ true ->
+ {reply, {async,ReqID},
+ update_request_info(ReqID, State,
+ fun({ok,Handle},State1) ->
+ open2(ReqID,FileName,Handle,Mode,Async,
+ From,State1);
+ (Rep,State1) ->
+ async_reply(ReqID, Rep, From, State1)
+ end)};
+ false ->
+ {noreply,
+ update_request_info(ReqID, State,
+ fun({ok,Handle},State1) ->
+ open2(ReqID,FileName,Handle,Mode,Async,
+ From,State1);
+ (Rep,State1) ->
+ sync_reply(Rep, From, State1)
+ end)}
+ end;
+
+do_handle_call({opendir,Async,Path}, From, State) ->
+ ReqID = State#state.req_id,
+ ssh_xfer:opendir(?XF(State), ReqID, Path),
+ make_reply(ReqID, Async, From, State);
+
+do_handle_call({readdir,Async,Handle}, From, State) ->
+ ReqID = State#state.req_id,
+ ssh_xfer:readdir(?XF(State), ReqID, Handle),
+ make_reply(ReqID, Async, From, State);
+
+do_handle_call({close,_Async,Handle}, From, State) ->
+ %% wait until all operations on handle are done
+ case get_size(Handle, State) of
+ undefined ->
+ ReqID = State#state.req_id,
+ ssh_xfer:close(?XF(State), ReqID, Handle),
+ make_reply_post(ReqID, false, From, State,
+ fun(Rep, State1) ->
+ {Rep, erase_handle(Handle, State1)}
+ end);
+ _ ->
+ case lseek_position(Handle, cur, State) of
+ {ok,_} ->
+ ReqID = State#state.req_id,
+ ssh_xfer:close(?XF(State), ReqID, Handle),
+ make_reply_post(ReqID, false, From, State,
+ fun(Rep, State1) ->
+ {Rep, erase_handle(Handle, State1)}
+ end);
+ Error ->
+ {reply, Error, State}
+ end
+ end;
+
+do_handle_call({pread,Async,Handle,At,Length}, From, State) ->
+ case lseek_position(Handle, At, State) of
+ {ok,Offset} ->
+ ReqID = State#state.req_id,
+ ssh_xfer:read(?XF(State),ReqID,Handle,Offset,Length),
+ %% To get multiple async read to work we must update the offset
+ %% before the operation begins
+ State1 = update_offset(Handle, Offset+Length, State),
+ make_reply_post(ReqID,Async,From,State1,
+ fun({ok,Data}, State2) ->
+ case get_mode(Handle, State2) of
+ binary -> {{ok,Data}, State2};
+ text ->
+ {{ok,binary_to_list(Data)}, State2}
+ end;
+ (Rep, State2) ->
+ {Rep, State2}
+ end);
+ Error ->
+ {reply, Error, State}
+ end;
+
+do_handle_call({read,Async,Handle,Length}, From, State) ->
+ case lseek_position(Handle, cur, State) of
+ {ok,Offset} ->
+ ReqID = State#state.req_id,
+ ssh_xfer:read(?XF(State),ReqID,Handle,Offset,Length),
+ %% To get multiple async read to work we must update the offset
+ %% before the operation begins
+ State1 = update_offset(Handle, Offset+Length, State),
+ make_reply_post(ReqID,Async,From,State1,
+ fun({ok,Data}, State2) ->
+ case get_mode(Handle, State2) of
+ binary -> {{ok,Data}, State2};
+ text ->
+ {{ok,binary_to_list(Data)}, State2}
+ end;
+ (Rep, State2) -> {Rep, State2}
+ end);
+ Error ->
+ {reply, Error, State}
+ end;
+
+do_handle_call({pwrite,Async,Handle,At,Data0}, From, State) ->
+ case lseek_position(Handle, At, State) of
+ {ok,Offset} ->
+ Data = if
+ is_binary(Data0) ->
+ Data0;
+ is_list(Data0) ->
+ list_to_binary(Data0)
+ end,
+ ReqID = State#state.req_id,
+ Size = size(Data),
+ ssh_xfer:write(?XF(State),ReqID,Handle,Offset,Data),
+ State1 = update_size(Handle, Offset+Size, State),
+ make_reply(ReqID, Async, From, State1);
+ Error ->
+ {reply, Error, State}
+ end;
+
+do_handle_call({write,Async,Handle,Data0}, From, State) ->
+ case lseek_position(Handle, cur, State) of
+ {ok,Offset} ->
+ Data = if
+ is_binary(Data0) ->
+ Data0;
+ is_list(Data0) ->
+ list_to_binary(Data0)
+ end,
+ ReqID = State#state.req_id,
+ Size = size(Data),
+ ssh_xfer:write(?XF(State),ReqID,Handle,Offset,Data),
+ State1 = update_offset(Handle, Offset+Size, State),
+ make_reply(ReqID, Async, From, State1);
+ Error ->
+ {reply, Error, State}
+ end;
+
+do_handle_call({position,Handle,At}, _From, State) ->
+ %% We could make this auto sync when all request to Handle is done?
+ case lseek_position(Handle, At, State) of
+ {ok,Offset} ->
+ {reply, {ok, Offset}, update_offset(Handle, Offset, State)};
+ Error ->
+ {reply, Error, State}
+ end;
+
+do_handle_call({rename,Async,FromFile,ToFile}, From, State) ->
+ ReqID = State#state.req_id,
+ ssh_xfer:rename(?XF(State),ReqID,FromFile,ToFile,[overwrite]),
+ make_reply(ReqID, Async, From, State);
+
+do_handle_call({delete,Async,Name}, From, State) ->
+ ReqID = State#state.req_id,
+ ssh_xfer:remove(?XF(State), ReqID, Name),
+ make_reply(ReqID, Async, From, State);
+
+do_handle_call({make_dir,Async,Name}, From, State) ->
+ ReqID = State#state.req_id,
+ ssh_xfer:mkdir(?XF(State), ReqID, Name,
+ #ssh_xfer_attr{ type = directory }),
+ make_reply(ReqID, Async, From, State);
+
+do_handle_call({del_dir,Async,Name}, From, State) ->
+ ReqID = State#state.req_id,
+ ssh_xfer:rmdir(?XF(State), ReqID, Name),
+ make_reply(ReqID, Async, From, State);
+
+do_handle_call({real_path,Async,Name}, From, State) ->
+ ReqID = State#state.req_id,
+ ssh_xfer:realpath(?XF(State), ReqID, Name),
+ make_reply(ReqID, Async, From, State);
+
+do_handle_call({read_file_info,Async,Name}, From, State) ->
+ ReqID = State#state.req_id,
+ ssh_xfer:stat(?XF(State), ReqID, Name, all),
+ make_reply(ReqID, Async, From, State);
+
+do_handle_call({get_file_info,Async,Name}, From, State) ->
+ ReqID = State#state.req_id,
+ ssh_xfer:fstat(?XF(State), ReqID, Name, all),
+ make_reply(ReqID, Async, From, State);
+
+do_handle_call({read_link_info,Async,Name}, From, State) ->
+ ReqID = State#state.req_id,
+ ssh_xfer:lstat(?XF(State), ReqID, Name, all),
+ make_reply(ReqID, Async, From, State);
+
+do_handle_call({read_link,Async,Name}, From, State) ->
+ ReqID = State#state.req_id,
+ ssh_xfer:readlink(?XF(State), ReqID, Name),
+ make_reply(ReqID, Async, From, State);
+
+do_handle_call({make_symlink, Async, Path, TargetPath}, From, State) ->
+ ReqID = State#state.req_id,
+ ssh_xfer:symlink(?XF(State), ReqID, Path, TargetPath),
+ make_reply(ReqID, Async, From, State);
+
+do_handle_call({write_file_info,Async,Name,Info}, From, State) ->
+ ReqID = State#state.req_id,
+ A = info_to_attr(Info),
+ ssh_xfer:setstat(?XF(State), ReqID, Name, A),
+ make_reply(ReqID, Async, From, State);
+
+%% TODO: Do we really want this format? Function send_window
+%% is not documented and seems to be used only inernaly!
+%% It is backwards compatible for now.
+do_handle_call(send_window, _From, State) ->
+ XF = State#state.xf,
+ [{send_window,{{win_size, Size0},{packet_size, Size1}}}] =
+ ssh:channel_info(XF#ssh_xfer.cm, XF#ssh_xfer.channel, [send_window]),
+ {reply, {ok, {Size0, Size1}}, State};
+
+%% TODO: Do we really want this format? Function recv_window
+%% is not documented and seems to be used only inernaly!
+%% It is backwards compatible for now.
+do_handle_call(recv_window, _From, State) ->
+ XF = State#state.xf,
+ [{recv_window,{{win_size, Size0},{packet_size, Size1}}}] =
+ ssh:channel_info(XF#ssh_xfer.cm, XF#ssh_xfer.channel, [recv_window]),
+ {reply, {ok, {Size0, Size1}}, State};
+
+%% Backwards compatible
+do_handle_call(stop, _From, State) ->
+ {stop, shutdown, ok, State};
+
+do_handle_call(Call, _From, State) ->
+ {reply, {error, bad_call, Call, State}, State}.
+
+%%--------------------------------------------------------------------
+%% Function: handle_ssh_msg(Args) -> {ok, State} | {stop, ChannelId, State}
+%%
+%% Description: Handles channel messages
+%%--------------------------------------------------------------------
+handle_ssh_msg({ssh_cm, _ConnectionManager,
+ {data, _ChannelId, 0, Data}}, #state{rep_buf = Data0} =
+ State0) ->
+ State = handle_reply(State0, <<Data0/binary,Data/binary>>),
+ {ok, State};
+
+handle_ssh_msg({ssh_cm, _ConnectionManager,
+ {data, _ChannelId, 1, Data}}, State) ->
+ error_logger:format("ssh: STDERR: ~s\n", [binary_to_list(Data)]),
+ {ok, State};
+
+handle_ssh_msg({ssh_cm, _ConnectionManager, {eof, _ChannelId}}, State) ->
+ {ok, State};
+
+handle_ssh_msg({ssh_cm, _, {signal, _, _}}, State) ->
+ %% Ignore signals according to RFC 4254 section 6.9.
+ {ok, State};
+
+handle_ssh_msg({ssh_cm, _, {exit_signal, ChannelId, _, Error, _}},
+ State0) ->
+ State = reply_all(State0, {error, Error}),
+ {stop, ChannelId, State};
+
+handle_ssh_msg({ssh_cm, _, {exit_status, ChannelId, Status}}, State0) ->
+ State = reply_all(State0, {error, {exit_status, Status}}),
+ {stop, ChannelId, State}.
+
+%%--------------------------------------------------------------------
+%% Function: handle_msg(Args) -> {ok, State} | {stop, ChannelId, State}
+%%
+%% Description: Handles channel messages
+%%--------------------------------------------------------------------
+handle_msg({ssh_channel_up, _, _}, #state{xf = Xf} = State) ->
+ ssh_xfer:protocol_version_request(Xf),
+ {ok, State};
+
+%% Version negotiation timed out
+handle_msg({timeout, undefined, From},
+ #state{xf = #ssh_xfer{channel = ChannelId}} = State) ->
+ ssh_channel:reply(From, {error, timeout}),
+ {stop, ChannelId, State};
+
+handle_msg({timeout, Id, From}, #state{req_list = ReqList0} = State) ->
+ case lists:keysearch(Id, 1, ReqList0) of
+ false ->
+ {ok, State};
+ _ ->
+ ReqList = lists:keydelete(Id, 1, ReqList0),
+ ssh_channel:reply(From, {error, timeout}),
+ {ok, State#state{req_list = ReqList}}
+ end;
+
+%% Connection manager goes down
+handle_msg({'DOWN', _Ref, _Type, _Process, _},
+ #state{xf = #ssh_xfer{channel = ChannelId}} = State) ->
+ {stop, ChannelId, State};
+
+%% Stopped by user
+handle_msg({'EXIT', _, ssh_sftp_stop_channel},
+ #state{xf = #ssh_xfer{channel = ChannelId}} = State) ->
+ {stop, ChannelId, State};
+
+handle_msg(_, State) ->
+ {ok, State}.
+%%--------------------------------------------------------------------
+%% Function: terminate(Reason, State) -> void()
+%% Description: Called when the channel process is terminated
+%%--------------------------------------------------------------------
+%% Backwards compatible
+terminate(shutdown, #state{xf = #ssh_xfer{cm = Cm}} = State) ->
+ reply_all(State, {error, closed}),
+ ssh:close(Cm);
+
+terminate(_Reason, State) ->
+ reply_all(State, {error, closed}).
+
+%%====================================================================
+%% Internal functions
+%%====================================================================
+call(Pid, Msg, TimeOut) ->
+ ssh_channel:call(Pid, {{timeout, TimeOut}, Msg}, infinity).
+
+handle_reply(State, <<?UINT32(Len),Reply:Len/binary,Rest/binary>>) ->
+ do_handle_reply(State, Reply, Rest);
+handle_reply(State, Data) ->
+ State#state{rep_buf = Data}.
+
+do_handle_reply(#state{xf = Xf} = State,
+ <<?SSH_FXP_VERSION, ?UINT32(Version), BinExt/binary>>, Rest) ->
+ Ext = ssh_xfer:decode_ext(BinExt),
+ case Xf#ssh_xfer.vsn of
+ undefined ->
+ ok;
+ From ->
+ ssh_channel:reply(From, ok)
+ end,
+ State#state{xf = Xf#ssh_xfer{vsn = Version, ext = Ext}, rep_buf = Rest};
+
+do_handle_reply(State0, Data, Rest) ->
+ case catch ssh_xfer:xf_reply(?XF(State0), Data) of
+ {'EXIT', _Reason} ->
+ handle_reply(State0, Rest);
+ XfReply ->
+ State = handle_req_reply(State0, XfReply),
+ handle_reply(State, Rest)
+ end.
+
+handle_req_reply(State0, {_, ReqID, _} = XfReply) ->
+ case lists:keysearch(ReqID, 1, State0#state.req_list) of
+ false ->
+ State0;
+ {value,{_,Fun}} ->
+ List = lists:keydelete(ReqID, 1, State0#state.req_list),
+ State1 = State0#state { req_list = List },
+ case catch Fun(xreply(XfReply),State1) of
+ {'EXIT', _} ->
+ State1;
+ State ->
+ State
+ end
+ end.
+
+xreply({handle,_,H}) -> {ok, H};
+xreply({data,_,Data}) -> {ok, Data};
+xreply({name,_,Names}) -> {ok, Names};
+xreply({attrs, _, A}) -> {ok, attr_to_info(A)};
+xreply({extended_reply,_,X}) -> {ok, X};
+xreply({status,_,{ok, _Err, _Lang, _Rep}}) -> ok;
+xreply({status,_,{eof, _Err, _Lang, _Rep}}) -> eof;
+xreply({status,_,{Stat, _Err, _Lang, _Rep}}) -> {error, Stat};
+xreply({Code, _, Reply}) -> {Code, Reply}.
+
+update_request_info(ReqID, State, Fun) ->
+ List = [{ReqID,Fun} | State#state.req_list],
+ ID = (State#state.req_id + 1) band 16#ffffffff,
+ State#state { req_list = List, req_id = ID }.
+
+async_reply(ReqID, Reply, _From={To,_}, State) ->
+ To ! {async_reply, ReqID, Reply},
+ State.
+
+sync_reply(Reply, From, State) ->
+ catch (ssh_channel:reply(From, Reply)),
+ State.
+
+open2(OrigReqID,FileName,Handle,Mode,Async,From,State) ->
+ I0 = State#state.inf,
+ FileMode = case lists:member(binary, Mode) orelse lists:member(raw, Mode) of
+ true -> binary;
+ false -> text
+ end,
+ I1 = add_new_handle(Handle, FileMode, I0),
+ State0 = State#state{inf = I1},
+ ReqID = State0#state.req_id,
+ ssh_xfer:stat(State0#state.xf, ReqID, FileName, [size]),
+ case Async of
+ true ->
+ update_request_info(ReqID, State0,
+ fun({ok,FI},State1) ->
+ Size = FI#file_info.size,
+ State2 = if is_integer(Size) ->
+ put_size(Handle, Size, State1);
+ true ->
+ State1
+ end,
+ async_reply(OrigReqID, {ok,Handle}, From, State2);
+ (_, State1) ->
+ async_reply(OrigReqID, {ok,Handle}, From, State1)
+ end);
+ false ->
+ update_request_info(ReqID, State0,
+ fun({ok,FI},State1) ->
+ Size = FI#file_info.size,
+ State2 = if is_integer(Size) ->
+ put_size(Handle, Size, State1);
+ true ->
+ State1
+ end,
+ sync_reply({ok,Handle}, From, State2);
+ (_, State1) ->
+ sync_reply({ok,Handle}, From, State1)
+ end)
+ end.
+
+reply_all(State, Reply) ->
+ List = State#state.req_list,
+ lists:foreach(fun({_ReqID,Fun}) ->
+ catch Fun(Reply,State)
+ end, List),
+ State#state {req_list = []}.
+
+make_reply(ReqID, true, From, State) ->
+ {reply, {async, ReqID},
+ update_request_info(ReqID, State,
+ fun(Reply,State1) ->
+ async_reply(ReqID,Reply,From,State1)
+ end)};
+
+make_reply(ReqID, false, From, State) ->
+ {noreply,
+ update_request_info(ReqID, State,
+ fun(Reply,State1) ->
+ sync_reply(Reply, From, State1)
+ end)}.
+
+make_reply_post(ReqID, true, From, State, PostFun) ->
+ {reply, {async, ReqID},
+ update_request_info(ReqID, State,
+ fun(Reply,State1) ->
+ case catch PostFun(Reply, State1) of
+ {'EXIT',_} ->
+ async_reply(ReqID,Reply, From, State1);
+ {Reply1, State2} ->
+ async_reply(ReqID,Reply1, From, State2)
+ end
+ end)};
+
+make_reply_post(ReqID, false, From, State, PostFun) ->
+ {noreply,
+ update_request_info(ReqID, State,
+ fun(Reply,State1) ->
+ case catch PostFun(Reply, State1) of
+ {'EXIT',_} ->
+ sync_reply(Reply, From, State1);
+ {Reply1, State2} ->
+ sync_reply(Reply1, From, State2)
+ end
+ end)}.
+
+%% convert: file_info -> ssh_xfer_attr
+info_to_attr(I) when is_record(I, file_info) ->
+ #ssh_xfer_attr { permissions = I#file_info.mode,
+ size = I#file_info.size,
+ type = I#file_info.type,
+ owner = I#file_info.uid,
+ group = I#file_info.gid,
+ atime = datetime_to_unix(I#file_info.atime),
+ mtime = datetime_to_unix(I#file_info.mtime),
+ createtime = datetime_to_unix(I#file_info.ctime)}.
+
+%% convert: ssh_xfer_attr -> file_info
+attr_to_info(A) when is_record(A, ssh_xfer_attr) ->
+ #file_info{
+ size = A#ssh_xfer_attr.size,
+ type = A#ssh_xfer_attr.type,
+ access = read_write, %% FIXME: read/write/read_write/none
+ atime = unix_to_datetime(A#ssh_xfer_attr.atime),
+ mtime = unix_to_datetime(A#ssh_xfer_attr.mtime),
+ ctime = unix_to_datetime(A#ssh_xfer_attr.createtime),
+ mode = A#ssh_xfer_attr.permissions,
+ links = 1,
+ major_device = 0,
+ minor_device = 0,
+ inode = 0,
+ uid = A#ssh_xfer_attr.owner,
+ gid = A#ssh_xfer_attr.group}.
+
+
+%% Added workaround for sftp timestam problem. (Timestamps should be
+%% in UTC but they where not) . The workaround uses a deprecated
+%% function i calandar. This will work as expected most of the time
+%% but has problems for the same reason as
+%% calendar:local_time_to_universal_time/1. We consider it better that
+%% the timestamps work as expected most of the time instead of none of
+%% the time. Hopfully the file-api will be updated so that we can
+%% solve this problem in a better way in the future.
+
+unix_to_datetime(undefined) ->
+ undefined;
+unix_to_datetime(UTCSecs) ->
+ UTCDateTime =
+ calendar:gregorian_seconds_to_datetime(UTCSecs + 62167219200),
+ erlang:universaltime_to_localtime(UTCDateTime).
+
+datetime_to_unix(undefined) ->
+ undefined;
+datetime_to_unix(LocalDateTime) ->
+ UTCDateTime = erlang:localtime_to_universaltime(LocalDateTime),
+ calendar:datetime_to_gregorian_seconds(UTCDateTime) - 62167219200.
+
+
+open_mode(Vsn,Modes) when Vsn >= 5 ->
+ open_mode5(Modes);
+open_mode(_Vsn, Modes) ->
+ open_mode3(Modes).
+
+open_mode5(Modes) ->
+ A = #ssh_xfer_attr{type = regular},
+ {Fl, Ac} = case {lists:member(write, Modes),
+ lists:member(read, Modes),
+ lists:member(append, Modes)} of
+ {_, _, true} ->
+ {[append_data],
+ [read_attributes,
+ append_data, write_attributes]};
+ {true, false, false} ->
+ {[create_truncate],
+ [write_data, write_attributes]};
+ {true, true, _} ->
+ {[open_or_create],
+ [read_data, read_attributes,
+ write_data, write_attributes]};
+ {false, true, _} ->
+ {[open_existing],
+ [read_data, read_attributes]}
+ end,
+ {Ac, Fl, A}.
+
+open_mode3(Modes) ->
+ A = #ssh_xfer_attr{type = regular},
+ Fl = case {lists:member(write, Modes),
+ lists:member(read, Modes),
+ lists:member(append, Modes)} of
+ {_, _, true} ->
+ [append];
+ {true, false, false} ->
+ [write, creat, trunc];
+ {true, true, _} ->
+ [read, write];
+ {false, true, _} ->
+ [read]
+ end,
+ {[], Fl, A}.
+
+%% accessors for inf dict
+new_inf() -> dict:new().
+
+add_new_handle(Handle, FileMode, Inf) ->
+ dict:store(Handle, #fileinf{offset=0, size=0, mode=FileMode}, Inf).
+
+update_size(Handle, NewSize, State) ->
+ OldSize = get_size(Handle, State),
+ if NewSize > OldSize ->
+ put_size(Handle, NewSize, State);
+ true ->
+ State
+ end.
+
+%% set_offset(Handle, NewOffset) ->
+%% put({offset,Handle}, NewOffset).
+
+update_offset(Handle, NewOffset, State0) ->
+ State1 = put_offset(Handle, NewOffset, State0),
+ update_size(Handle, NewOffset, State1).
+
+%% access size and offset for handle
+put_size(Handle, Size, State) ->
+ Inf0 = State#state.inf,
+ case dict:find(Handle, Inf0) of
+ {ok, FI} ->
+ State#state{inf=dict:store(Handle, FI#fileinf{size=Size}, Inf0)};
+ _ ->
+ State#state{inf=dict:store(Handle, #fileinf{size=Size,offset=0},
+ Inf0)}
+ end.
+
+put_offset(Handle, Offset, State) ->
+ Inf0 = State#state.inf,
+ case dict:find(Handle, Inf0) of
+ {ok, FI} ->
+ State#state{inf=dict:store(Handle, FI#fileinf{offset=Offset},
+ Inf0)};
+ _ ->
+ State#state{inf=dict:store(Handle, #fileinf{size=Offset,
+ offset=Offset}, Inf0)}
+ end.
+
+get_size(Handle, State) ->
+ case dict:find(Handle, State#state.inf) of
+ {ok, FI} ->
+ FI#fileinf.size;
+ _ ->
+ undefined
+ end.
+
+%% get_offset(Handle, State) ->
+%% {ok, FI} = dict:find(Handle, State#state.inf),
+%% FI#fileinf.offset.
+
+get_mode(Handle, State) ->
+ case dict:find(Handle, State#state.inf) of
+ {ok, FI} ->
+ FI#fileinf.mode;
+ _ ->
+ undefined
+ end.
+
+erase_handle(Handle, State) ->
+ FI = dict:erase(Handle, State#state.inf),
+ State#state{inf = FI}.
+
+%%
+%% Caluclate a integer offset
+%%
+lseek_position(Handle, Pos, State) ->
+ case dict:find(Handle, State#state.inf) of
+ {ok, #fileinf{offset=O, size=S}} ->
+ lseek_pos(Pos, O, S);
+ _ ->
+ {error, einval}
+ end.
+
+lseek_pos(_Pos, undefined, _) ->
+ {error, einval};
+lseek_pos(Pos, _CurOffset, _CurSize)
+ when is_integer(Pos) andalso 0 =< Pos andalso Pos < ?SSH_FILEXFER_LARGEFILESIZE ->
+ {ok,Pos};
+lseek_pos(bof, _CurOffset, _CurSize) ->
+ {ok,0};
+lseek_pos(cur, CurOffset, _CurSize) ->
+ {ok,CurOffset};
+lseek_pos(eof, _CurOffset, CurSize) ->
+ {ok,CurSize};
+lseek_pos({bof, Offset}, _CurOffset, _CurSize)
+ when is_integer(Offset) andalso 0 =< Offset andalso Offset < ?SSH_FILEXFER_LARGEFILESIZE ->
+ {ok, Offset};
+lseek_pos({cur, Offset}, CurOffset, _CurSize)
+ when is_integer(Offset) andalso -(?SSH_FILEXFER_LARGEFILESIZE) =< Offset andalso
+ Offset < ?SSH_FILEXFER_LARGEFILESIZE ->
+ NewOffset = CurOffset + Offset,
+ if NewOffset < 0 ->
+ {ok, 0};
+ true ->
+ {ok, NewOffset}
+ end;
+lseek_pos({eof, Offset}, _CurOffset, CurSize)
+ when is_integer(Offset) andalso -(?SSH_FILEXFER_LARGEFILESIZE) =< Offset andalso
+ Offset < ?SSH_FILEXFER_LARGEFILESIZE ->
+ NewOffset = CurSize + Offset,
+ if NewOffset < 0 ->
+ {ok, 0};
+ true ->
+ {ok, NewOffset}
+ end;
+lseek_pos(_, _, _) ->
+ {error, einval}.
+
+
+%%%%%% Deprecated %%%%
+connect(Cm) when is_pid(Cm) ->
+ connect(Cm, []);
+connect(Host) when is_list(Host) ->
+ connect(Host, []).
+connect(Cm, Opts) when is_pid(Cm) ->
+ Timeout = proplists:get_value(timeout, Opts, infinity),
+ case ssh_xfer:attach(Cm, []) of
+ {ok, ChannelId, Cm} ->
+ ssh_channel:start(Cm, ChannelId, ?MODULE, [Cm, ChannelId,
+ Timeout]);
+ Error ->
+ Error
+ end;
+connect(Host, Opts) ->
+ connect(Host, 22, Opts).
+connect(Host, Port, Opts) ->
+ Timeout = proplists:get_value(timeout, Opts, infinity),
+ case ssh_xfer:connect(Host, Port, proplists:delete(timeout, Opts)) of
+ {ok, ChannelId, Cm} ->
+ ssh_channel:start(Cm, ChannelId, ?MODULE, [Cm,
+ ChannelId, Timeout]);
+ Error ->
+ Error
+ end.
+
+
+stop(Pid) ->
+ call(Pid, stop, infinity).
+
diff --git a/lib/ssh/src/ssh_sftpd.erl b/lib/ssh/src/ssh_sftpd.erl
new file mode 100644
index 0000000000..10b8ede1e4
--- /dev/null
+++ b/lib/ssh/src/ssh_sftpd.erl
@@ -0,0 +1,932 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2009. 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%
+%%
+
+%%
+
+%%% Description: SFTP server daemon
+
+-module(ssh_sftpd).
+
+%%-behaviour(gen_server).
+-behaviour(ssh_channel).
+
+-include_lib("kernel/include/file.hrl").
+
+-include("ssh.hrl").
+-include("ssh_xfer.hrl").
+
+%%--------------------------------------------------------------------
+%% External exports
+-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]).
+
+-record(state, {
+ xf, % [{channel,ssh_xfer states}...]
+ cwd, % current dir (on first connect)
+ root, % root dir
+ remote_channel, % remote channel
+ pending, % binary()
+ file_handler, % atom() - callback module
+ file_state, % state for the file callback module
+ max_files, % integer >= 0 max no files sent during READDIR
+ handles % list of open handles
+ %% handle is either {<int>, directory, {Path, unread|eof}} or
+ %% {<int>, file, {Path, IoDevice}}
+ }).
+
+%%====================================================================
+%% API
+%%====================================================================
+subsystem_spec(Options) ->
+ {"sftp", {?MODULE, Options}}.
+
+%%% DEPRECATED START %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%--------------------------------------------------------------------
+%% Function: listen() -> Pid | {error,Error}
+%% Description: Starts the server
+%%--------------------------------------------------------------------
+listen(Port) ->
+ listen(any, Port, []).
+listen(Port, Options) ->
+ listen(any, Port, Options).
+listen(Addr, Port, Options) ->
+ SubSystems = [subsystem_spec(Options)],
+ ssh:daemon(Addr, Port, [{subsystems, SubSystems} |Options]).
+
+%%--------------------------------------------------------------------
+%% Function: stop(Pid) -> ok
+%% Description: Stops the listener
+%%--------------------------------------------------------------------
+stop(Pid) ->
+ ssh_cli:stop(Pid).
+
+
+%%% DEPRECATED END %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%====================================================================
+%% subsystem callbacks
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% Function: init(Args) -> {ok, State}
+%% Description: Initiates the server
+%%--------------------------------------------------------------------
+init(Options) ->
+ {FileMod, FS0} = case proplists:get_value(file_handler, Options,
+ {ssh_sftpd_file,[]}) of
+ {F, S} ->
+ {F, S};
+ F ->
+ {F, []}
+ end,
+
+ {{ok, Default}, FS1} = FileMod:get_cwd(FS0),
+ CWD = proplists:get_value(cwd, Options, Default),
+
+ Root0 = proplists:get_value(root, Options, ""),
+
+ %% Get the root of the file system (symlinks must be followed,
+ %% otherwise the realpath call won't work). But since symbolic links
+ %% isn't supported on all plattforms we have to use the root property
+ %% supplied by the user.
+ {Root, State} =
+ case resolve_symlinks(Root0,
+ #state{root = Root0,
+ file_handler = FileMod,
+ file_state = FS1}) of
+ {{ok, Root1}, State0} ->
+ {Root1, State0};
+ {{error, _}, State0} ->
+ {Root0, State0}
+ end,
+ MaxLength = proplists:get_value(max_files, Options, 0),
+
+ Vsn = proplists:get_value(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
+%%--------------------------------------------------------------------
+handle_ssh_msg({ssh_cm, _ConnectionManager,
+ {data, _ChannelId, Type, Data}}, State) ->
+ State1 = handle_data(Type, Data, State),
+ {ok, State1};
+
+handle_ssh_msg({ssh_cm, _, {eof, ChannelId}}, State) ->
+ {stop, ChannelId, State};
+
+handle_ssh_msg({ssh_cm, _, {signal, _, _}}, State) ->
+ %% Ignore signals according to RFC 4254 section 6.9.
+ {ok, State};
+
+handle_ssh_msg({ssh_cm, _, {exit_signal, ChannelId, _, Error, _}}, State) ->
+ Report = io_lib:format("Connection closed by peer ~n Error ~p~n",
+ [Error]),
+ error_logger:error_report(Report),
+ {stop, ChannelId, State};
+
+handle_ssh_msg({ssh_cm, _, {exit_status, ChannelId, 0}}, State) ->
+ {stop, ChannelId, State};
+
+handle_ssh_msg({ssh_cm, _, {exit_status, ChannelId, Status}}, State) ->
+
+ Report = io_lib:format("Connection closed by peer ~n Status ~p~n",
+ [Status]),
+ error_logger:error_report(Report),
+ {stop, ChannelId, State}.
+
+%%--------------------------------------------------------------------
+%% Function: handle_ssh_msg(Args) -> {ok, State} | {stop, ChannelId, State}
+%%
+%% Description: Handles other messages
+%%--------------------------------------------------------------------
+handle_msg({ssh_channel_up, ChannelId, ConnectionManager},
+ #state{xf =Xf} = State) ->
+ {ok, State#state{xf = Xf#ssh_xfer{cm = ConnectionManager,
+ channel = ChannelId}}}.
+
+%%--------------------------------------------------------------------
+%% Function: terminate(Reason, State) -> void()
+%% Description: This function is called by a gen_server when it is about to
+%% terminate. It should be the opposite of Module:init/1 and do any necessary
+%% cleaning up. When it returns, the gen_server terminates with Reason.
+%% The return value is ignored.
+%%--------------------------------------------------------------------
+terminate(_, #state{handles=Handles, file_handler=FileMod, file_state=FS}) ->
+ CloseFun = fun({_, file, {_, Fd}}, FS0) ->
+ {_Res, FS1} = FileMod:close(Fd, FS0),
+ FS1;
+ (_, FS0) ->
+ FS0
+ end,
+ lists:foldl(CloseFun, FS, Handles),
+ ok.
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+handle_data(0, <<?UINT32(Len), Msg:Len/binary, Rest/binary>>,
+ State = #state{pending = <<>>}) ->
+ <<Op, ?UINT32(ReqId), Data/binary>> = Msg,
+ NewState = handle_op(Op, ReqId, Data, State),
+ case Rest of
+ <<>> ->
+ NewState;
+ _ ->
+ handle_data(0, Rest, NewState)
+ end;
+
+handle_data(0, Data, State = #state{pending = <<>>}) ->
+ State#state{pending = Data};
+
+handle_data(Type, Data, State = #state{pending = Pending}) ->
+ handle_data(Type, <<Pending/binary, Data/binary>>,
+ State#state{pending = <<>>}).
+
+handle_op(?SSH_FXP_INIT, Version, B, State) when is_binary(B) ->
+ XF = State#state.xf,
+ Vsn = lists:min([XF#ssh_xfer.vsn, Version]),
+ XF1 = XF#ssh_xfer{vsn = Vsn},
+ ssh_xfer:xf_send_reply(XF1, ?SSH_FXP_VERSION, <<?UINT32(Vsn)>>),
+ State#state{xf = XF1};
+handle_op(?SSH_FXP_REALPATH, ReqId,
+ <<?UINT32(Rlen), RPath:Rlen/binary>>,
+ State0) ->
+ RelPath0 = binary_to_list(RPath),
+ RelPath = relate_file_name(RelPath0, State0, _Canonicalize=false),
+ {Res, State} = resolve_symlinks(RelPath, State0),
+ case Res of
+ {ok, AbsPath} ->
+ NewAbsPath = chroot_filename(AbsPath, State),
+ ?dbg(true, "handle_op ?SSH_FXP_REALPATH: RelPath=~p AbsPath=~p\n",
+ [RelPath, NewAbsPath]),
+ XF = State#state.xf,
+ Attr = #ssh_xfer_attr{type=directory},
+ ssh_xfer:xf_send_name(XF, ReqId, NewAbsPath, Attr),
+ State;
+ {error, _} = Error ->
+ send_status(Error, ReqId, State)
+ end;
+handle_op(?SSH_FXP_OPENDIR, ReqId,
+ <<?UINT32(RLen), RPath:RLen/binary>>,
+ State0 = #state{file_handler = FileMod, file_state = FS0}) ->
+ RelPath = binary_to_list(RPath),
+ AbsPath = relate_file_name(RelPath, State0),
+
+ XF = State0#state.xf,
+ {IsDir, FS1} = FileMod:is_dir(AbsPath, FS0),
+ State1 = State0#state{file_state = FS1},
+ case IsDir of
+ false ->
+ ssh_xfer:xf_send_status(XF, ReqId, ?SSH_FX_NOT_A_DIRECTORY,
+ "Not a directory"),
+ State1;
+ true ->
+ add_handle(State1, XF, ReqId, directory, {RelPath,unread})
+ end;
+handle_op(?SSH_FXP_READDIR, ReqId,
+ <<?UINT32(HLen), BinHandle:HLen/binary>>,
+ State) ->
+ XF = State#state.xf,
+ case get_handle(State#state.handles, BinHandle) of
+ {_Handle, directory, {_RelPath, eof}} ->
+ ssh_xfer:xf_send_status(XF, ReqId, ?SSH_FX_EOF),
+ State;
+ {Handle, directory, {RelPath, Status}} ->
+ read_dir(State, XF, ReqId, Handle, RelPath, Status);
+ _ ->
+ ssh_xfer:xf_send_status(XF, ReqId, ?SSH_FX_INVALID_HANDLE),
+ State
+ end;
+handle_op(?SSH_FXP_CLOSE, ReqId,
+ <<?UINT32(HLen), BinHandle:HLen/binary>>,
+ State = #state{handles = Handles, xf = XF,
+ file_handler = FileMod, file_state = FS0}) ->
+ case get_handle(Handles, BinHandle) of
+ {Handle, Type, T} ->
+ FS1 = case Type of
+ file ->
+ close_our_file(T, FileMod, FS0);
+ _ ->
+ FS0
+ end,
+ ssh_xfer:xf_send_status(XF, ReqId, ?SSH_FX_OK),
+ State#state{handles = lists:keydelete(Handle, 1, Handles),
+ file_state = FS1};
+ _ ->
+ ssh_xfer:xf_send_status(XF, ReqId, ?SSH_FX_INVALID_HANDLE),
+ State
+ end;
+handle_op(?SSH_FXP_LSTAT, ReqId, Data, State) ->
+ stat((State#state.xf)#ssh_xfer.vsn, ReqId, Data, State, read_link_info);
+handle_op(?SSH_FXP_STAT, ReqId, Data, State) ->
+ stat((State#state.xf)#ssh_xfer.vsn, ReqId, Data, State, read_file_info);
+handle_op(?SSH_FXP_FSTAT, ReqId, Data, State) ->
+ fstat((State#state.xf)#ssh_xfer.vsn, ReqId, Data, State);
+handle_op(?SSH_FXP_OPEN, ReqId, Data, State) ->
+ open((State#state.xf)#ssh_xfer.vsn, ReqId, Data, State);
+handle_op(?SSH_FXP_READ, ReqId, <<?UINT32(HLen), BinHandle:HLen/binary,
+ ?UINT64(Offset), ?UINT32(Len)>>,
+ State) ->
+ case get_handle(State#state.handles, BinHandle) of
+ {_Handle, file, {_Path, IoDevice}} ->
+ read_file(ReqId, IoDevice, Offset, Len, State);
+ _ ->
+ ssh_xfer:xf_send_status(State#state.xf, ReqId,
+ ?SSH_FX_INVALID_HANDLE),
+ State
+ end;
+handle_op(?SSH_FXP_WRITE, ReqId,
+ <<?UINT32(HLen), BinHandle:HLen/binary, ?UINT64(Offset),
+ ?UINT32(Len), Data:Len/binary>>, State) ->
+ case get_handle(State#state.handles, BinHandle) of
+ {_Handle, file, {_Path, IoDevice}} ->
+ write_file(ReqId, IoDevice, Offset, Data, State);
+ _ ->
+ ssh_xfer:xf_send_status(State#state.xf, ReqId,
+ ?SSH_FX_INVALID_HANDLE),
+ State
+ end;
+handle_op(?SSH_FXP_READLINK, ReqId, <<?UINT32(PLen), BPath:PLen/binary>>,
+ State = #state{file_handler = FileMod, file_state = FS0}) ->
+ RelPath = binary_to_list(BPath),
+ AbsPath = relate_file_name(RelPath, State),
+ {Res, FS1} = FileMod:read_link(AbsPath, FS0),
+ case Res of
+ {ok, NewPath} ->
+ ssh_xfer:xf_send_name(State#state.xf, ReqId, NewPath,
+ #ssh_xfer_attr{type=regular});
+ {error, Error} ->
+ ssh_xfer:xf_send_status(State#state.xf, ReqId,
+ ssh_xfer:encode_erlang_status(Error))
+ end,
+ State#state{file_state = FS1};
+handle_op(?SSH_FXP_SETSTAT, ReqId, <<?UINT32(PLen), BPath:PLen/binary,
+ Attr/binary>>, State0) ->
+ Path = relate_file_name(BPath, State0),
+ {Status, State1} = set_stat(Attr, Path, State0),
+ send_status(Status, ReqId, State1);
+handle_op(?SSH_FXP_MKDIR, ReqId, <<?UINT32(PLen), BPath:PLen/binary,
+ Attr/binary>>,
+ State0 = #state{file_handler = FileMod, file_state = FS0}) ->
+ Path = relate_file_name(BPath, State0),
+ {Res, FS1} = FileMod:make_dir(Path, FS0),
+ State1 = State0#state{file_state = FS1},
+ case Res of
+ ok ->
+ {_, State2} = set_stat(Attr, Path, State1),
+ send_status(ok, ReqId, State2);
+ {error, Error} ->
+ send_status({error, Error}, ReqId, State1)
+ end;
+handle_op(?SSH_FXP_FSETSTAT, ReqId, <<?UINT32(HLen), BinHandle:HLen/binary,
+ Attr/binary>>,
+ State0 = #state{handles = Handles}) ->
+
+ case get_handle(Handles, BinHandle) of
+ {_Handle, _Type, {Path,_}} ->
+ {Status, State1} = set_stat(Attr, Path, State0),
+ send_status(Status, ReqId, State1);
+ _ ->
+ ssh_xfer:xf_send_status(State0#state.xf, ReqId,
+ ?SSH_FX_INVALID_HANDLE),
+ State0
+ end;
+handle_op(?SSH_FXP_REMOVE, ReqId, <<?UINT32(PLen), BPath:PLen/binary>>,
+ State0 = #state{file_handler = FileMod, file_state = FS0}) ->
+ 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;
+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),
+ {Status, FS1} = FileMod:del_dir(Path, FS0),
+ State1 = State0#state{file_state = FS1},
+ send_status(Status, ReqId, State1);
+
+handle_op(?SSH_FXP_RENAME, ReqId,
+ Bin = <<?UINT32(PLen), _:PLen/binary, ?UINT32(PLen2),
+ _:PLen2/binary>>,
+ State = #state{xf = #ssh_xfer{vsn = Vsn}}) when Vsn==3; Vsn==4 ->
+ handle_op(?SSH_FXP_RENAME, ReqId, <<Bin/binary, 0:32>>, State);
+
+handle_op(?SSH_FXP_RENAME, ReqId,
+ <<?UINT32(PLen), BPath:PLen/binary, ?UINT32(PLen2),
+ BPath2:PLen2/binary, ?UINT32(Flags)>>,
+ State0 = #state{file_handler = FileMod, file_state = FS0}) ->
+ Path = relate_file_name(BPath, State0),
+ Path2 = relate_file_name(BPath2, State0),
+ case Flags band ?SSH_FXP_RENAME_ATOMIC of
+ 0 ->
+ case Flags band ?SSH_FXP_RENAME_OVERWRITE of
+ 0 ->
+ {Res, FS1} = FileMod:read_link_info(Path2, FS0),
+ State1 = State0#state{file_state = FS1},
+ case Res of
+ {ok, _Info} ->
+ ssh_xfer:xf_send_status(
+ State1#state.xf,
+ ReqId,
+ ?SSH_FX_FILE_ALREADY_EXISTS),
+ State1;
+ _ ->
+ rename(Path, Path2, ReqId, State1)
+ end;
+ _ ->
+ rename(Path, Path2, ReqId, State0)
+ end;
+ _ ->
+ ssh_xfer:xf_send_status(State0#state.xf, ReqId,
+ ?SSH_FX_OP_UNSUPPORTED),
+ State0
+ end;
+handle_op(?SSH_FXP_SYMLINK, ReqId,
+ <<?UINT32(PLen), BPath:PLen/binary, ?UINT32(PLen2),
+ BPath2:PLen2/binary>>,
+ State0 = #state{file_handler = FileMod, file_state = FS0}) ->
+ Path = relate_file_name(BPath, State0),
+ Path2 = relate_file_name(BPath2, State0),
+ {Status, FS1} = FileMod:make_symlink(Path2, Path, FS0),
+ State1 = State0#state{file_state = FS1},
+ send_status(Status, ReqId, State1).
+
+new_handle([], H) ->
+ H;
+new_handle([{N, _} | Rest], H) when N > H ->
+ new_handle(Rest, N+1);
+new_handle([_ | Rest], H) ->
+ new_handle(Rest, H).
+
+add_handle(State, XF, ReqId, Type, DirFileTuple) ->
+ Handles = State#state.handles,
+ Handle = new_handle(Handles, 0),
+ ssh_xfer:xf_send_handle(XF, ReqId, integer_to_list(Handle)),
+ State#state{handles = [{Handle, Type, DirFileTuple} | Handles]}.
+
+get_handle(Handles, BinHandle) ->
+ case (catch list_to_integer(binary_to_list(BinHandle))) of
+ I when is_integer(I) ->
+ case lists:keysearch(I, 1, Handles) of
+ {value, T} -> T;
+ false -> error
+ end;
+ _ ->
+ error
+ end.
+
+%%% read_dir/5: read directory, send names, and return new state
+read_dir(State0 = #state{file_handler = FileMod, max_files = MaxLength, file_state = FS0},
+ XF, ReqId, Handle, RelPath, {cache, Files}) ->
+ AbsPath = relate_file_name(RelPath, State0),
+ ?dbg(true, "read_dir: AbsPath=~p\n", [AbsPath]),
+ if
+ length(Files) > MaxLength ->
+ {ToSend, NewCache} = lists:split(MaxLength, Files),
+ {NamesAndAttrs, FS1} = get_attrs(AbsPath, ToSend, FileMod, FS0),
+ ssh_xfer:xf_send_names(XF, ReqId, NamesAndAttrs),
+ Handles = lists:keyreplace(Handle, 1,
+ State0#state.handles,
+ {Handle, directory, {RelPath,{cache, NewCache}}}),
+ State0#state{handles = Handles, file_state = FS1};
+ true ->
+ {NamesAndAttrs, FS1} = get_attrs(AbsPath, Files, FileMod, FS0),
+ ssh_xfer:xf_send_names(XF, ReqId, NamesAndAttrs),
+ Handles = lists:keyreplace(Handle, 1,
+ State0#state.handles,
+ {Handle, directory, {RelPath,eof}}),
+ State0#state{handles = Handles, file_state = FS1}
+ end;
+read_dir(State0 = #state{file_handler = FileMod, max_files = MaxLength, file_state = FS0},
+ XF, ReqId, Handle, RelPath, _Status) ->
+ AbsPath = relate_file_name(RelPath, State0),
+ ?dbg(true, "read_dir: AbsPath=~p\n", [AbsPath]),
+ {Res, FS1} = FileMod:list_dir(AbsPath, FS0),
+ case Res of
+ {ok, Files} when MaxLength == 0 orelse MaxLength > length(Files) ->
+ {NamesAndAttrs, FS2} = get_attrs(AbsPath, Files, FileMod, FS1),
+ ssh_xfer:xf_send_names(XF, ReqId, NamesAndAttrs),
+ Handles = lists:keyreplace(Handle, 1,
+ State0#state.handles,
+ {Handle, directory, {RelPath,eof}}),
+ State0#state{handles = Handles, file_state = FS2};
+ {ok, Files} ->
+ {ToSend, Cache} = lists:split(MaxLength, Files),
+ {NamesAndAttrs, FS2} = get_attrs(AbsPath, ToSend, FileMod, FS1),
+ ssh_xfer:xf_send_names(XF, ReqId, NamesAndAttrs),
+ Handles = lists:keyreplace(Handle, 1,
+ State0#state.handles,
+ {Handle, directory, {RelPath,{cache, Cache}}}),
+ State0#state{handles = Handles, file_state = FS2};
+ {error, Error} ->
+ State1 = State0#state{file_state = FS1},
+ send_status({error, Error}, ReqId, State1)
+ end.
+
+
+%%% get_attrs: get stat of each file and return
+get_attrs(RelPath, Files, FileMod, FS) ->
+ get_attrs(RelPath, Files, FileMod, FS, []).
+
+get_attrs(_RelPath, [], _FileMod, FS, Acc) ->
+ {lists:reverse(Acc), FS};
+get_attrs(RelPath, [F | Rest], FileMod, FS0, Acc) ->
+ Path = filename:absname(F, RelPath),
+ ?dbg(true, "get_attrs fun: F=~p\n", [F]),
+ case FileMod:read_link_info(Path, FS0) of
+ {{ok, Info}, FS1} ->
+ Attrs = ssh_sftp:info_to_attr(Info),
+ get_attrs(RelPath, Rest, FileMod, FS1, [{F, Attrs} | Acc]);
+ {{error, enoent}, FS1} ->
+ get_attrs(RelPath, Rest, FileMod, FS1, Acc);
+ {Error, FS1} ->
+ {Error, FS1}
+ end.
+
+close_our_file({_,Fd}, FileMod, FS0) ->
+ {_Res, FS1} = FileMod:close(Fd, FS0),
+ FS1.
+
+%%% stat: do the stat
+stat(Vsn, ReqId, Data, State, F) when Vsn =< 3->
+ <<?UINT32(BLen), BPath:BLen/binary>> = Data,
+ stat(ReqId, binary_to_list(BPath), State, F);
+stat(Vsn, ReqId, Data, State, F) when Vsn >= 4->
+ <<?UINT32(BLen), BPath:BLen/binary, ?UINT32(_Flags)>> = Data,
+ stat(ReqId, binary_to_list(BPath), State, F).
+
+fstat(Vsn, ReqId, Data, State) when Vsn =< 3->
+ <<?UINT32(HLen), Handle:HLen/binary>> = Data,
+ fstat(ReqId, Handle, State);
+fstat(Vsn, ReqId, Data, State) when Vsn >= 4->
+ <<?UINT32(HLen), Handle:HLen/binary, ?UINT32(_Flags)>> = Data,
+ fstat(ReqId, Handle, State).
+
+fstat(ReqId, BinHandle, State) ->
+ case get_handle(State#state.handles, BinHandle) of
+ {_Handle, _Type, {Path, _}} ->
+ stat(ReqId, Path, State, read_file_info);
+ _ ->
+ ssh_xfer:xf_send_status(State#state.xf, ReqId,
+ ?SSH_FX_INVALID_HANDLE),
+ State
+ end.
+
+stat(ReqId, RelPath, State0=#state{file_handler=FileMod,
+ file_state=FS0}, F) ->
+ AbsPath = relate_file_name(RelPath, State0),
+ XF = State0#state.xf,
+ ?dbg(false, "stat: AbsPath=~p\n", [AbsPath]),
+ {Res, FS1} = FileMod:F(AbsPath, FS0),
+ State1 = State0#state{file_state = FS1},
+ case Res of
+ {ok, FileInfo} ->
+ ssh_xfer:xf_send_attr(XF, ReqId,
+ ssh_sftp:info_to_attr(FileInfo)),
+ State1;
+ {error, E} ->
+ send_status({error, E}, ReqId, State1)
+ end.
+
+decode_4_open_flag(create_new) ->
+ [write];
+decode_4_open_flag(create_truncate) ->
+ [write];
+decode_4_open_flag(truncate_existing) ->
+ [write];
+decode_4_open_flag(open_existing) ->
+ [read].
+
+decode_4_flags([OpenFlag | Flags]) ->
+ decode_4_flags(Flags, decode_4_open_flag(OpenFlag)).
+
+decode_4_flags([], Flags) ->
+ Flags;
+decode_4_flags([append_data|R], _Flags) ->
+ decode_4_flags(R, [append]);
+decode_4_flags([append_data_atomic|R], _Flags) ->
+ decode_4_flags(R, [append]);
+decode_4_flags([_|R], Flags) ->
+ decode_4_flags(R, Flags).
+
+decode_4_access_flag(read_data) ->
+ [read];
+decode_4_access_flag(list_directory) ->
+ [read];
+decode_4_access_flag(write_data) ->
+ [write];
+decode_4_access_flag(add_file) ->
+ [write];
+decode_4_access_flag(add_subdirectory) ->
+ [read];
+decode_4_access_flag(append_data) ->
+ [append];
+decode_4_access_flag(_) ->
+ [read].
+
+decode_4_acess([_ | _] = Flags) ->
+ lists:map(fun(Flag) ->
+ [decode_4_access_flag(Flag)]
+ end, Flags);
+decode_4_acess([]) ->
+ [].
+
+open(Vsn, ReqId, Data, State) when Vsn =< 3 ->
+ <<?UINT32(BLen), BPath:BLen/binary, ?UINT32(PFlags),
+ _Attrs/binary>> = Data,
+ Path = binary_to_list(BPath),
+ Flags = ssh_xfer:decode_open_flags(Vsn, PFlags) -- [creat, excl, trunc],
+ ?dbg(true, "open: Flags=~p\n", [Flags]),
+ do_open(ReqId, State, Path, Flags);
+open(Vsn, ReqId, Data, State) when Vsn >= 4 ->
+ <<?UINT32(BLen), BPath:BLen/binary, ?UINT32(Access),
+ ?UINT32(PFlags), _Attrs/binary>> = Data,
+ Path = binary_to_list(BPath),
+ FlagBits = ssh_xfer:decode_open_flags(Vsn, PFlags),
+ AcessBits = ssh_xfer:decode_ace_mask(Access),
+ ?dbg(true, "open: Fl=~p\n", [FlagBits]),
+ %% TODO: This is to make sure the Access flags are not ignored
+ %% but this should be thought through better. This solution should
+ %% be considered a hack in order to buy some time. At least
+ %% it works better than when the Access flags where totally ignored.
+ %% A better solution may need some code refactoring that we do
+ %% not have time for right now.
+ AcessFlags = decode_4_acess(AcessBits),
+ Flags = lists:append(lists:umerge(
+ [[decode_4_flags(FlagBits)] | AcessFlags])),
+
+ ?dbg(true, "open: Flags=~p\n", [Flags]),
+
+ do_open(ReqId, State, Path, Flags).
+
+do_open(ReqId, State0, Path, Flags) ->
+ #state{file_handler = FileMod, file_state = FS0, root = Root} = State0,
+ XF = State0#state.xf,
+ F = [raw, 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
+ end.
+
+%% resolve all symlinks in a path
+resolve_symlinks(Path, State) ->
+ resolve_symlinks(Path, _LinkCnt=32, State).
+
+resolve_symlinks(Path, LinkCnt, State0) ->
+ resolve_symlinks_2(filename:split(Path), State0, LinkCnt, []).
+
+resolve_symlinks_2(_Path, State, LinkCnt, _AccPath) when LinkCnt =:= 0 ->
+ %% Too many links (there might be a symlink loop)
+ {{error, emlink}, State};
+resolve_symlinks_2(["." | RestPath], State0, LinkCnt, AccPath) ->
+ resolve_symlinks_2(RestPath, State0, LinkCnt, AccPath);
+resolve_symlinks_2([".." | RestPath], State0, LinkCnt, AccPath) ->
+ %% Remove the last path component
+ AccPathComps0 = filename:split(AccPath),
+ Path = case lists:reverse(tl(lists:reverse(AccPathComps0))) of
+ [] ->
+ "";
+ AccPathComps ->
+ filename:join(AccPathComps)
+ end,
+ resolve_symlinks_2(RestPath, State0, LinkCnt, Path);
+resolve_symlinks_2([PathComp | RestPath], State0, LinkCnt, AccPath0) ->
+ #state{file_handler = FileMod, file_state = FS0} = State0,
+ AccPath1 = filename:join(AccPath0, PathComp),
+ {Res, FS1} = FileMod:read_link(AccPath1, FS0),
+ State1 = State0#state{file_state = FS1},
+ case Res of
+ {ok, Target0} -> % path is a symlink
+ %% The target may be a relative or an absolute path and
+ %% may contain symlinks
+ Target1 = filename:absname(Target0, AccPath0),
+ {FollowRes, State2} = resolve_symlinks(Target1, LinkCnt-1, State1),
+ case FollowRes of
+ {ok, Target} ->
+ resolve_symlinks_2(RestPath, State2, LinkCnt-1, Target);
+ {error, _} = Error ->
+ {Error, State2}
+ end;
+ {error, einval} -> % path exists, but is not a symlink
+ resolve_symlinks_2(RestPath, State1, LinkCnt, AccPath1);
+ {error, _} = Error ->
+ {Error, State1}
+ end;
+resolve_symlinks_2([], State, _LinkCnt, AccPath) ->
+ {{ok, AccPath}, State}.
+
+
+relate_file_name(File, State) ->
+ relate_file_name(File, State, _Canonicalize=true).
+
+relate_file_name(File, State, Canonicalize) when is_binary(File) ->
+ relate_file_name(binary_to_list(File), State, Canonicalize);
+relate_file_name(File, #state{cwd = CWD, root = ""}, Canonicalize) ->
+ relate_filename_to_path(File, CWD, Canonicalize);
+relate_file_name(File, #state{root = Root}, Canonicalize) ->
+ case is_within_root(Root, File) of
+ true ->
+ File;
+ false ->
+ RelFile = make_relative_filename(File),
+ NewFile = relate_filename_to_path(RelFile, Root, Canonicalize),
+ case is_within_root(Root, NewFile) of
+ true ->
+ NewFile;
+ false ->
+ Root
+ end
+ end.
+
+is_within_root(Root, File) ->
+ lists:prefix(Root, File).
+
+%% Remove leading slash (/), if any, in order to make the filename
+%% relative (to the root)
+make_relative_filename("/") -> "./"; % Make it relative and preserve /
+make_relative_filename("/"++File) -> File;
+make_relative_filename(File) -> File.
+
+relate_filename_to_path(File0, Path, Canonicalize) ->
+ File1 = filename:absname(File0, Path),
+ File2 = if Canonicalize -> canonicalize_filename(File1);
+ true -> File1
+ end,
+ ensure_trailing_slash_is_preserved(File0, File2).
+
+%% It seems as if the openssh client (observed with the
+%% openssh-4.2p1-18.30 package on SLED 10), and possibly other clients
+%% as well (Maverick?), rely on the fact that a trailing slash (/) is
+%% preserved. If trailing slashes aren't preserved, symlinks which
+%% point at directories won't be properly identified as directories.
+%%
+%% A failing example:
+%%
+%% 1) assume the following directory structure:
+%% $ mkdir /tmp/symlink-target
+%% $ touch /tmp/symlink-target/foo
+%% $ ln -s /tmp/symlink-target /tmp/symlink
+%%
+%% 2) login using the sftp client in openssh
+%% sftp> cd /tmp/
+%% sftp> ls symlink-target
+%% symlink-target/foo
+%% sftp> ls symlink
+%% symlink/ <===== foo should have been visible here
+%% sftp> cd symlink-target
+%% sftp> ls
+%% foo
+%% sftp> cd ..
+%% sftp> cd symlink
+%% sftp> ls
+%% <===== foo should have been visible here
+%%
+%% The symlinks are resolved by file:read_link_info/1 only if the path
+%% has a trailing slash, which seems to something that some of the
+%% sftp clients utilize:
+%%
+%% 1> file:read_link_info(".../symlink").
+%% {ok,{file_info,4,symlink,read_write,
+%% {{2008,10,20},{10,25,26}},
+%% {{2008,10,17},{16,22,33}},
+%% {{2008,10,17},{16,22,33}},
+%% 41471,1,2053,0,570447,20996,9935}}
+%%
+%% 2> file:read_link_info(".../symlink/").
+%% {ok,{file_info,8192,directory,read_write,
+%% {{2008,10,20},{10,36,2}},
+%% {{2008,10,20},{10,44,35}},
+%% {{2008,10,20},{10,44,35}},
+%% 17407,29,2053,0,521224,0,0}}
+ensure_trailing_slash_is_preserved(File0, File1) ->
+ case {lists:suffix("/", File0), lists:suffix("/", File1)} of
+ {true, false} -> File1 ++ "/";
+ _Other -> File1
+ end.
+
+
+
+%%% fix file just a little: a/b/.. -> a and a/. -> a
+canonicalize_filename(File0) ->
+ File = filename:join(canonicalize_filename_2(filename:split(File0), [])),
+ ensure_trailing_slash_is_preserved(File0, File).
+
+canonicalize_filename_2([".." | Rest], ["/"] = Acc) ->
+ canonicalize_filename_2(Rest, Acc);
+canonicalize_filename_2([".." | Rest], [_Dir | Paths]) ->
+ canonicalize_filename_2(Rest, Paths);
+canonicalize_filename_2(["." | Rest], Acc) ->
+ canonicalize_filename_2(Rest, Acc);
+canonicalize_filename_2([A | Rest], Acc) ->
+ canonicalize_filename_2(Rest, [A | Acc]);
+canonicalize_filename_2([], Acc) ->
+ lists:reverse(Acc).
+
+%% return a filename which is relative to the root directory
+%% (any filename that's outside the root directory is forced to the root)
+chroot_filename(Filename, #state{root = Root}) ->
+ FilenameComps0 = filename:split(Filename),
+ RootComps = filename:split(Root),
+ filename:join(chroot_filename_2(FilenameComps0, RootComps)).
+
+chroot_filename_2([PathComp | FilenameRest], [PathComp | RootRest]) ->
+ chroot_filename_2(FilenameRest, RootRest);
+chroot_filename_2(FilenameComps, []) when length(FilenameComps) > 0 ->
+ %% Ensure there's a leading / (filename:join above will take care
+ %% of any duplicates)
+ ["/" | FilenameComps];
+chroot_filename_2(_FilenameComps, _RootComps) ->
+ %% The filename is either outside the root or at the root. In
+ %% both cases we want to force the filename to the root.
+ ["/"].
+
+
+read_file(ReqId, IoDevice, Offset, Len,
+ State0 = #state{file_handler = FileMod, file_state = FS0}) ->
+ {Res1, FS1} = FileMod:position(IoDevice, {bof, Offset}, FS0),
+ case Res1 of
+ {ok, _NewPos} ->
+ {Res2, FS2} = FileMod:read(IoDevice, Len, FS1),
+ State1 = State0#state{file_state = FS2},
+ case Res2 of
+ {ok, Data} ->
+ ssh_xfer:xf_send_data(State1#state.xf, ReqId, Data),
+ State1;
+ {error, Error} ->
+ send_status({error, Error}, ReqId, State1);
+ eof ->
+ send_status(eof, ReqId, State1)
+ end;
+ {error, Error} ->
+ State1 = State0#state{file_state = FS1},
+ send_status({error, Error}, ReqId, State1)
+ end.
+
+write_file(ReqId, IoDevice, Offset, Data,
+ State0 = #state{file_handler = FileMod, file_state = FS0}) ->
+ {Res, FS1} = FileMod:position(IoDevice, {bof, Offset}, FS0),
+ case Res of
+ {ok, _NewPos} ->
+ {Status, FS2} = FileMod:write(IoDevice, Data, FS1),
+ State1 = State0#state{file_state = FS2},
+ send_status(Status, ReqId, State1);
+ {error, Error} ->
+ State1 = State0#state{file_state = FS1},
+ send_status({error, Error}, ReqId, State1)
+ end.
+
+get_status(ok) ->
+ ?SSH_FX_OK;
+get_status(eof) ->
+ ?SSH_FX_EOF;
+get_status({error,Error}) ->
+ ssh_xfer:encode_erlang_status(Error).
+
+send_status(Status, ReqId, State) ->
+ ssh_xfer:xf_send_status(State#state.xf, ReqId, get_status(Status)),
+ State.
+
+set_stat(<<>>, _Path, State) ->
+ {ok, State};
+set_stat(Attr, Path,
+ State0 = #state{file_handler=FileMod, file_state=FS0}) ->
+ {DecodedAttr, _Rest} =
+ ssh_xfer:decode_ATTR((State0#state.xf)#ssh_xfer.vsn, Attr),
+ ?dbg(true, "set_stat DecodedAttr=~p\n", [DecodedAttr]),
+ Info = ssh_sftp:attr_to_info(DecodedAttr),
+ {Res1, FS1} = FileMod:read_link_info(Path, FS0),
+ case Res1 of
+ {ok, OldInfo} ->
+ NewInfo = set_file_info(Info, OldInfo),
+ ?dbg(true, "set_stat Path=~p\nInfo=~p\nOldInfo=~p\nNewInfo=~p\n",
+ [Path, Info, OldInfo, NewInfo]),
+ {Res2, FS2} = FileMod:write_file_info(Path, NewInfo, FS1),
+ State1 = State0#state{file_state = FS2},
+ {Res2, State1};
+ {error, Error} ->
+ State1 = State0#state{file_state = FS1},
+ {{error, Error}, State1}
+ end.
+
+
+set_file_info_sel(undefined, F) ->
+ F;
+set_file_info_sel(F, _) ->
+ F.
+
+set_file_info(#file_info{atime = Dst_atime, mtime = Dst_mtime,
+ ctime = Dst_ctime,
+ mode = Dst_mode, uid = Dst_uid, gid = Dst_gid},
+ #file_info{atime = Src_atime, mtime = Src_mtime,
+ ctime = Src_ctime,
+ mode = Src_mode, uid = Src_uid, gid = Src_gid}) ->
+ #file_info{atime = set_file_info_sel(Dst_atime, Src_atime),
+ mtime = set_file_info_sel(Dst_mtime, Src_mtime),
+ ctime = set_file_info_sel(Dst_ctime, Src_ctime),
+ mode = set_file_info_sel(Dst_mode, Src_mode),
+ uid = set_file_info_sel(Dst_uid, Src_uid),
+ gid = set_file_info_sel(Dst_gid, Src_gid)}.
+
+rename(Path, Path2, ReqId, State0) ->
+ #state{file_handler = FileMod, file_state = FS0} = State0,
+ {Status, FS1} = FileMod:rename(Path, Path2, FS0),
+ State1 = State0#state{file_state = FS1},
+ send_status(Status, ReqId, State1).
diff --git a/lib/ssh/src/ssh_sftpd_file.erl b/lib/ssh/src/ssh_sftpd_file.erl
new file mode 100644
index 0000000000..f0b6bb4de5
--- /dev/null
+++ b/lib/ssh/src/ssh_sftpd_file.erl
@@ -0,0 +1,83 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2006-2009. 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%
+%%
+
+%%
+
+%%% Description: Default Callback module for ssh_sftpd
+
+-module(ssh_sftpd_file).
+
+-behaviour(ssh_sftpd_file_api).
+
+%% API
+-export([close/2, delete/2, del_dir/2, get_cwd/1, is_dir/2, list_dir/2,
+ make_dir/2, make_symlink/3, open/3, position/3, read/3,
+ read_file_info/2, read_link/2, read_link_info/2, rename/3,
+ write/3, write_file_info/3]).
+
+close(IoDevice, State) ->
+ {file:close(IoDevice), State}.
+
+delete(Path, State) ->
+ {file:delete(Path), State}.
+
+del_dir(Path, State) ->
+ {file:del_dir(Path), State}.
+
+get_cwd(State) ->
+ {file:get_cwd(), State}.
+
+is_dir(AbsPath, State) ->
+ {filelib:is_dir(AbsPath), State}.
+
+list_dir(AbsPath, State) ->
+ {file:list_dir(AbsPath), State}.
+
+make_dir(Dir, State) ->
+ {file:make_dir(Dir), State}.
+
+make_symlink(Path2, Path, State) ->
+ {file:make_symlink(Path2, Path), State}.
+
+open(Path, Flags, State) ->
+ {file:open(Path, Flags), State}.
+
+position(IoDevice, Offs, State) ->
+ {file:position(IoDevice, Offs), State}.
+
+read(IoDevice, Len, State) ->
+ {file:read(IoDevice, Len), State}.
+
+read_link(Path, State) ->
+ {file:read_link(Path), State}.
+
+read_link_info(Path, State) ->
+ {file:read_link_info(Path), State}.
+
+read_file_info(Path, State) ->
+ {file:read_file_info(Path), State}.
+
+rename(Path, Path2, State) ->
+ {file:rename(Path, Path2), State}.
+
+write(IoDevice, Data, State) ->
+ {file:write(IoDevice, Data), State}.
+
+write_file_info(Path,Info, State) ->
+ {file:write_file_info(Path, Info), State}.
diff --git a/lib/ssh/src/ssh_sftpd_file_api.erl b/lib/ssh/src/ssh_sftpd_file_api.erl
new file mode 100644
index 0000000000..8decfb38d9
--- /dev/null
+++ b/lib/ssh/src/ssh_sftpd_file_api.erl
@@ -0,0 +1,47 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-2009. 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_sftpd_file_api).
+
+-export([behaviour_info/1]).
+
+behaviour_info(callbacks) ->
+ [
+ {close, 2},
+ {delete, 2},
+ {del_dir, 2},
+ {get_cwd, 1},
+ {is_dir, 2},
+ {list_dir, 2},
+ {make_dir, 2},
+ {make_symlink, 3},
+ {open, 3},
+ {position, 3},
+ {read, 3},
+ {read_file_info, 2},
+ {read_link, 2},
+ {read_link_info, 2},
+ {rename, 3},
+ {write, 3},
+ {write_file_info, 3}
+ ];
+behaviour_info(_) ->
+ undefined.
diff --git a/lib/ssh/src/ssh_shell.erl b/lib/ssh/src/ssh_shell.erl
new file mode 100644
index 0000000000..f81b949119
--- /dev/null
+++ b/lib/ssh/src/ssh_shell.erl
@@ -0,0 +1,178 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2009. 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_shell).
+
+-include("ssh_connect.hrl").
+
+-behaviour(ssh_channel).
+
+%% ssh_channel callbacks
+-export([init/1, handle_msg/2, handle_ssh_msg/2, terminate/2]).
+
+%% Spawn export
+-export([input_loop/2]).
+
+-record(state,
+ {
+ io, %% Io process
+ channel, %% Id of the ssh channel
+ cm %% Ssh connection manager
+ }
+ ).
+
+%%====================================================================
+%% ssh_channel callbacks
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% Function: init(Args) -> {ok, State}
+%%
+%% Description: Initiates the CLI
+%%--------------------------------------------------------------------
+init([ConnectionManager, ChannelId] = Args) ->
+
+ %% Make sure that we are proclib compatible as
+ %% this client should be runnable from the
+ %% erlang shell.
+ case get('$initial_call') of
+ undefined ->
+ Me = get_my_name(),
+ Ancestors = get_ancestors(),
+ put('$ancestors', [Me | Ancestors]),
+ put('$initial_call', {?MODULE, init, Args});
+ _ ->
+ ok
+ end,
+
+ case ssh_connection:shell(ConnectionManager, ChannelId) of
+ ok ->
+ {group_leader, GIO} =
+ process_info(self(), group_leader),
+ IoPid = spawn_link(?MODULE, input_loop,
+ [GIO, self()]),
+ {ok, #state{io = IoPid,
+ channel = ChannelId,
+ cm = ConnectionManager}};
+ Error ->
+ {stop, Error}
+ end.
+
+%%--------------------------------------------------------------------
+%% Function: handle_ssh_msg(Args) -> {ok, State} | {stop, ChannelId, State}
+%%
+%% Description: Handles channel messages received on the ssh-connection.
+%%--------------------------------------------------------------------
+handle_ssh_msg({ssh_cm, _, {data, _ChannelId, 0, Data}}, State) ->
+ %% TODO: When unicode support is ready
+ %% should we call this function or perhaps a new
+ %% function.
+ io:put_chars(Data),
+ {ok, State};
+
+handle_ssh_msg({ssh_cm, _,
+ {data, _ChannelId, ?SSH_EXTENDED_DATA_STDERR, Data}},
+ State) ->
+ %% TODO: When unicode support is ready
+ %% should we call this function or perhaps a new
+ %% function.
+ io:put_chars(Data),
+ {ok, State};
+
+handle_ssh_msg({ssh_cm, _, {eof, _ChannelId}}, State) ->
+ {ok, State};
+
+handle_ssh_msg({ssh_cm, _, {signal, _, _}}, State) ->
+ %% Ignore signals according to RFC 4254 section 6.9.
+ {ok, State};
+
+handle_ssh_msg({ssh_cm, _, {exit_signal, ChannelId, _, Error, _}}, State) ->
+ io:put_chars("Connection closed by peer"),
+ %% TODO: When unicode support is ready
+ %% should we call this function or perhaps a new
+ %% function. The error is encoded as UTF-8!
+ io:put_chars(Error),
+ {stop, ChannelId, State};
+
+handle_ssh_msg({ssh_cm, _, {exit_status, ChannelId, 0}}, State) ->
+ io:put_chars("logout"),
+ io:put_chars("Connection closed"),
+ {stop, ChannelId, State};
+
+handle_ssh_msg({ssh_cm, _, {exit_status, ChannelId, Status}}, State) ->
+ io:put_chars("Connection closed by peer"),
+ io:put_chars("Status: " ++ integer_to_list(Status)),
+ {stop, ChannelId, State}.
+
+%%--------------------------------------------------------------------
+%% Function: handle_ssh_msg(Args) -> {ok, State} | {stop, ChannelId, State}
+%%
+%% Description: Handles other channel messages
+%%--------------------------------------------------------------------
+handle_msg({ssh_channel_up, ChannelId, ConnectionManager},
+ #state{channel = ChannelId,
+ cm = ConnectionManager} = State) ->
+ {ok, State};
+
+handle_msg({input, IoPid, eof}, #state{io = IoPid, channel = ChannelId,
+ cm = ConnectionManager} = State) ->
+ ssh_connection:send_eof(ConnectionManager, ChannelId),
+ {ok, State};
+
+handle_msg({input, IoPid, Line}, #state{io = IoPid,
+ channel = ChannelId,
+ cm = ConnectionManager} = State) ->
+ ssh_connection:send(ConnectionManager, ChannelId, Line),
+ {ok, State}.
+
+%%--------------------------------------------------------------------
+%% Function: terminate(Reasons, State) -> _
+%%
+%% Description: Cleanup when shell channel is terminated
+%%--------------------------------------------------------------------
+terminate(_Reason, #state{io = IoPid}) ->
+ exit(IoPid, kill).
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+
+input_loop(Fd, Pid) ->
+ case io:get_line(Fd, '>') of
+ eof ->
+ Pid ! {input, self(), eof},
+ ok;
+ Line ->
+ Pid ! {input, self(), Line},
+ input_loop (Fd, Pid)
+ end.
+
+get_my_name() ->
+ case process_info(self(),registered_name) of
+ {registered_name,Name} -> Name;
+ _ -> self()
+ end.
+
+get_ancestors() ->
+ case get('$ancestors') of
+ A when is_list(A) -> A;
+ _ -> []
+ end.
diff --git a/lib/ssh/src/ssh_ssh.erl b/lib/ssh/src/ssh_ssh.erl
new file mode 100644
index 0000000000..6be8bf7a5a
--- /dev/null
+++ b/lib/ssh/src/ssh_ssh.erl
@@ -0,0 +1,65 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2009. 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%
+%%
+
+%%
+
+%%% Description: THIS MODULE IS DEPRECATD AND SHOULD BE REMOVED IN R14
+
+-module(ssh_ssh).
+
+-export([connect/1, connect/2, connect/3]).
+-deprecated({connect, 1, next_major_release}).
+-deprecated({connect, 2, next_major_release}).
+-deprecated({connect, 3, next_major_release}).
+
+-include("ssh.hrl").
+-include("ssh_connect.hrl").
+
+-define(default_timeout, 10000).
+
+%%% Backwards compatibility
+connect(A) ->
+ connect(A, []).
+
+connect(Host, Opts) when is_list(Host) ->
+ connect(Host, 22, Opts);
+connect(CM, Opts) ->
+ Timeout = proplists:get_value(connect_timeout, Opts, ?default_timeout),
+ session(CM, Timeout).
+
+connect(Host, Port, Opts) ->
+ case ssh:connect(Host, Port, Opts) of
+ {ok, CM} ->
+ session(CM, proplists:get_value(connect_timeout,
+ Opts, ?default_timeout));
+ Error ->
+ Error
+ end.
+
+session(CM, Timeout) ->
+ case ssh_connection:session_channel(CM, Timeout) of
+ {ok, ChannelId} ->
+ Args = [{channel_cb, ssh_shell},
+ {init_args,[CM, ChannelId]},
+ {cm, CM}, {channel_id, ChannelId}],
+ {ok, State} = ssh_channel:init([Args]),
+ ssh_channel:enter_loop(State);
+ Error ->
+ Error
+ end.
diff --git a/lib/ssh/src/ssh_sshd.erl b/lib/ssh/src/ssh_sshd.erl
new file mode 100644
index 0000000000..4bc0469061
--- /dev/null
+++ b/lib/ssh/src/ssh_sshd.erl
@@ -0,0 +1,48 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2009. 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%
+%%
+
+%%
+%% Description: This module uses the erlang shell and
+%% ssh_cli to make an erlang sshd
+
+-module(ssh_sshd).
+
+%% API
+-export([listen/0, listen/1, listen/2, listen/3, stop/1]).
+
+-deprecated({listen, 0, next_major_release}).
+-deprecated({listen, 1, next_major_release}).
+-deprecated({listen, 2, next_major_release}).
+-deprecated({listen, 3, next_major_release}).
+-deprecated({stop, 1, next_major_release}).
+
+listen() ->
+ listen(22).
+
+listen(Port) ->
+ listen(Port, []).
+
+listen(Port, Opts) ->
+ listen(any, Port, Opts).
+
+listen(Addr, Port, Opts) ->
+ ssh:daemon(Addr, Port, Opts).
+
+stop(Pid) ->
+ ssh:stop_daemon(Pid).
diff --git a/lib/ssh/src/ssh_subsystem_sup.erl b/lib/ssh/src/ssh_subsystem_sup.erl
new file mode 100644
index 0000000000..17d47a91d5
--- /dev/null
+++ b/lib/ssh/src/ssh_subsystem_sup.erl
@@ -0,0 +1,109 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2009. 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%
+%%
+%%
+%%----------------------------------------------------------------------
+%% Purpose: The ssh subsystem supervisor
+%%----------------------------------------------------------------------
+
+-module(ssh_subsystem_sup).
+
+-behaviour(supervisor).
+
+-export([start_link/1, connection_supervisor/1, channel_supervisor/1
+ ]).
+
+%% Supervisor callback
+-export([init/1]).
+
+%%%=========================================================================
+%%% API
+%%%=========================================================================
+start_link(Opts) ->
+ supervisor:start_link(?MODULE, [Opts]).
+
+connection_supervisor(SupPid) ->
+ Children = supervisor:which_children(SupPid),
+ ssh_connection_sup(Children).
+
+channel_supervisor(SupPid) ->
+ Children = supervisor:which_children(SupPid),
+ ssh_channel_sup(Children).
+
+%%%=========================================================================
+%%% Supervisor callback
+%%%=========================================================================
+init([Opts]) ->
+ RestartStrategy = one_for_all,
+ MaxR = 0,
+ MaxT = 3600,
+ Children = child_specs(Opts),
+ {ok, {{RestartStrategy, MaxR, MaxT}, Children}}.
+
+%%%=========================================================================
+%%% Internal functions
+%%%=========================================================================
+child_specs(Opts) ->
+ case proplists:get_value(role, Opts) of
+ client ->
+ [ssh_connectinon_child_spec(Opts)];
+ server ->
+ [ssh_connectinon_child_spec(Opts), ssh_channel_child_spec(Opts)]
+ end.
+
+ssh_connectinon_child_spec(Opts) ->
+ Address = proplists:get_value(address, Opts),
+ Port = proplists:get_value(port, Opts),
+ Role = proplists:get_value(role, Opts),
+ Name = id(Role, ssh_connection_controler, Address, Port),
+ StartFunc = {ssh_connection_controler, start_link, [Opts]},
+ Restart = transient,
+% Restart = permanent,
+ Shutdown = 5000,
+ Modules = [ssh_connection_controler],
+ Type = worker,
+ {Name, StartFunc, Restart, Shutdown, Type, Modules}.
+
+ssh_channel_child_spec(Opts) ->
+ Address = proplists:get_value(address, Opts),
+ Port = proplists:get_value(port, Opts),
+ Role = proplists:get_value(role, Opts),
+ Name = id(Role, ssh_channel_sup, Address, Port),
+ StartFunc = {ssh_channel_sup, start_link, [Opts]},
+ Restart = transient,
+% Restart = permanent,
+ Shutdown = infinity,
+ Modules = [ssh_channel_sup],
+ Type = supervisor,
+ {Name, StartFunc, Restart, Shutdown, Type, Modules}.
+
+id(Role, Sup, Address, Port) ->
+ {Role, Sup, Address, Port}.
+
+ssh_connection_sup([{_, Child, _, [ssh_connection_controler]} | _]) ->
+ Child;
+ssh_connection_sup([_ | Rest]) ->
+ ssh_connection_sup(Rest).
+
+ssh_channel_sup([{_, Child, _, [ssh_channel_sup]} | _]) ->
+ Child;
+ssh_channel_sup([_ | Rest]) ->
+ ssh_channel_sup(Rest).
+
+
+
diff --git a/lib/ssh/src/ssh_sup.erl b/lib/ssh/src/ssh_sup.erl
new file mode 100644
index 0000000000..4c46b1586b
--- /dev/null
+++ b/lib/ssh/src/ssh_sup.erl
@@ -0,0 +1,101 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2009. 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%
+%%
+
+%%
+%%----------------------------------------------------------------------
+%% Purpose: The top supervisor for the ssh application.
+%%----------------------------------------------------------------------
+-module(ssh_sup).
+
+-behaviour(supervisor).
+
+-export([init/1]).
+
+%%%=========================================================================
+%%% Supervisor callback
+%%%=========================================================================
+init([]) ->
+ SupFlags = {one_for_one, 10, 3600},
+ Children = children(),
+ {ok, {SupFlags, Children}}.
+
+%%%=========================================================================
+%%% Internal functions
+%%%=========================================================================
+get_services() ->
+ case (catch application:get_env(ssh, services)) of
+ {ok, Services} ->
+ Services;
+ _ ->
+ []
+ end.
+
+children() ->
+ Services = get_services(),
+ Clients = [Service || Service <- Services, is_client(Service)],
+ Servers = [Service || Service <- Services, is_server(Service)],
+
+ [server_child_spec(Servers), client_child_spec(Clients),
+ ssh_userauth_reg_spec()].
+
+server_child_spec(Servers) ->
+ Name = sshd_sup,
+ StartFunc = {sshd_sup, start_link, [Servers]},
+ Restart = permanent,
+ Shutdown = infinity,
+ Modules = [sshd_sup],
+ Type = supervisor,
+ {Name, StartFunc, Restart, Shutdown, Type, Modules}.
+
+client_child_spec(Clients) ->
+ Name = sshc_sup,
+ StartFunc = {sshc_sup, start_link, [Clients]},
+ Restart = permanent,
+ Shutdown = infinity,
+ Modules = [sshc_sup],
+ Type = supervisor,
+ {Name, StartFunc, Restart, Shutdown, Type, Modules}.
+
+ssh_userauth_reg_spec() ->
+ Name = ssh_userreg,
+ StartFunc = {ssh_userreg, start_link, []},
+ Restart = transient,
+ Shutdown = 5000,
+ Modules = [ssh_userreg],
+ Type = worker,
+ {Name, StartFunc, Restart, Shutdown, Type, Modules}.
+
+
+is_server({sftpd, _}) ->
+ true;
+is_server({shelld, _}) ->
+ true;
+is_server(_) ->
+ false.
+
+is_client({sftpc, _}) ->
+ true;
+is_client({shellc, _}) ->
+ true;
+is_client(_) ->
+ false.
+
+
+
+
diff --git a/lib/ssh/src/ssh_system_sup.erl b/lib/ssh/src/ssh_system_sup.erl
new file mode 100644
index 0000000000..477f60f993
--- /dev/null
+++ b/lib/ssh/src/ssh_system_sup.erl
@@ -0,0 +1,160 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2009. 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%
+%%
+
+%%
+%%----------------------------------------------------------------------
+%% Purpose: The ssh server instance supervisor, an instans of this supervisor
+%% exists for every ip-address and port combination, hangs under
+%% sshd_sup.
+%%----------------------------------------------------------------------
+
+-module(ssh_system_sup).
+
+-behaviour(supervisor).
+
+-export([start_link/1, stop_listener/1,
+ stop_listener/2, stop_system/1,
+ stop_system/2, system_supervisor/2,
+ subsystem_supervisor/1, channel_supervisor/1,
+ connection_supervisor/1,
+ acceptor_supervisor/1, start_subsystem/2, restart_subsystem/2, restart_acceptor/2]).
+
+%% Supervisor callback
+-export([init/1]).
+
+%%%=========================================================================
+%%% API
+%%%=========================================================================
+start_link(ServerOpts) ->
+ Address = proplists:get_value(address, ServerOpts),
+ Port = proplists:get_value(port, ServerOpts),
+ Name = make_name(Address, Port),
+ supervisor:start_link({local, Name}, ?MODULE, [ServerOpts]).
+
+stop_listener(SysSup) ->
+ stop_acceptor(SysSup).
+
+stop_listener(Address, Port) ->
+ Name = make_name(Address, Port),
+ stop_acceptor(whereis(Name)).
+
+stop_system(SysSup) ->
+ Name = sshd_sup:system_name(SysSup),
+ sshd_sup:stop_child(Name).
+
+stop_system(Address, Port) ->
+ sshd_sup:stop_child(Address, Port).
+
+system_supervisor(Address, Port) ->
+ Name = make_name(Address, Port),
+ whereis(Name).
+
+subsystem_supervisor(SystemSup) ->
+ ssh_subsystem_sup(supervisor:which_children(SystemSup)).
+
+channel_supervisor(SystemSup) ->
+ SubSysSup = ssh_subsystem_sup(supervisor:which_children(SystemSup)),
+ ssh_subsystem_sup:channel_supervisor(SubSysSup).
+
+connection_supervisor(SystemSup) ->
+ SubSysSup = ssh_subsystem_sup(supervisor:which_children(SystemSup)),
+ ssh_subsystem_sup:connection_supervisor(SubSysSup).
+
+acceptor_supervisor(SystemSup) ->
+ ssh_acceptor_sup(supervisor:which_children(SystemSup)).
+
+start_subsystem(SystemSup, Options) ->
+ Spec = ssh_subsystem_child_spec(Options),
+ supervisor:start_child(SystemSup, Spec).
+
+restart_subsystem(Address, Port) ->
+ SysSupName = make_name(Address, Port),
+ SubSysName = id(ssh_subsystem_sup, Address, Port),
+ case supervisor:terminate_child(SysSupName, SubSysName) of
+ ok ->
+ supervisor:restart_child(SysSupName, SubSysName);
+ Error ->
+ Error
+ end.
+
+restart_acceptor(Address, Port) ->
+ SysSupName = make_name(Address, Port),
+ AcceptorName = id(ssh_acceptor_sup, Address, Port),
+ supervisor:restart_child(SysSupName, AcceptorName).
+
+%%%=========================================================================
+%%% Supervisor callback
+%%%=========================================================================
+init([ServerOpts]) ->
+ RestartStrategy = one_for_one,
+ MaxR = 10,
+ MaxT = 3600,
+ Children = child_specs(ServerOpts),
+ {ok, {{RestartStrategy, MaxR, MaxT}, Children}}.
+
+%%%=========================================================================
+%%% Internal functions
+%%%=========================================================================
+child_specs(ServerOpts) ->
+ [ssh_acceptor_child_spec(ServerOpts)].
+
+ssh_acceptor_child_spec(ServerOpts) ->
+ Address = proplists:get_value(address, ServerOpts),
+ Port = proplists:get_value(port, ServerOpts),
+ Name = id(ssh_acceptor_sup, Address, Port),
+ StartFunc = {ssh_acceptor_sup, start_link, [ServerOpts]},
+ Restart = permanent,
+ Shutdown = infinity,
+ Modules = [ssh_acceptor_sup],
+ Type = supervisor,
+ {Name, StartFunc, Restart, Shutdown, Type, Modules}.
+
+ssh_subsystem_child_spec(ServerOpts) ->
+ Name = make_ref(),
+ StartFunc = {ssh_subsystem_sup, start_link, [ServerOpts]},
+ Restart = temporary,
+ Shutdown = infinity,
+ Modules = [ssh_subsystem_sup],
+ Type = supervisor,
+ {Name, StartFunc, Restart, Shutdown, Type, Modules}.
+
+
+id(Sup, Address, Port) ->
+ {Sup, Address, Port}.
+
+make_name(Address, Port) ->
+ list_to_atom(lists:flatten(io_lib:format("ssh_system_~p_~p_sup",
+ [Address, Port]))).
+
+ssh_subsystem_sup([{_, Child, _, [ssh_subsystem_sup]} | _]) ->
+ Child;
+ssh_subsystem_sup([_ | Rest]) ->
+ ssh_subsystem_sup(Rest).
+
+ssh_acceptor_sup([{_, Child, _, [ssh_acceptor_sup]} | _]) ->
+ Child;
+ssh_acceptor_sup([_ | Rest]) ->
+ ssh_acceptor_sup(Rest).
+
+stop_acceptor(Sup) ->
+ [Name] =
+ [SupName || {SupName, _, _, [ssh_acceptor_sup]} <-
+ supervisor:which_children(Sup)],
+ supervisor:terminate_child(Sup, Name).
+
diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl
new file mode 100644
index 0000000000..5617231c60
--- /dev/null
+++ b/lib/ssh/src/ssh_transport.erl
@@ -0,0 +1,1161 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2004-2009. 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%
+%%
+
+%%
+
+%%% Description: SSH transport protocol
+
+-module(ssh_transport).
+
+-include("ssh_transport.hrl").
+
+-include("ssh.hrl").
+-include_lib("kernel/include/inet.hrl").
+
+-export([connect/5, accept/4]).
+-export([versions/2, hello_version_msg/1]).
+-export([next_seqnum/1, decrypt_first_block/2, decrypt_blocks/3,
+ is_valid_mac/3, transport_messages/1, kexdh_messages/0,
+ kex_dh_gex_messages/0, handle_hello_version/1,
+ key_exchange_init_msg/1, key_init/3, new_keys_message/1,
+ handle_kexinit_msg/3, handle_kexdh_init/2,
+ handle_kex_dh_gex_group/2, handle_kex_dh_gex_reply/2,
+ handle_new_keys/2, handle_kex_dh_gex_request/2,
+ handle_kexdh_reply/2,
+ unpack/3, decompress/2, ssh_packet/2, pack/2, msg_data/1]).
+
+%% debug flagso
+-define(DBG_ALG, true).
+-define(DBG_KEX, true).
+-define(DBG_CRYPTO, false).
+-define(DBG_PACKET, false).
+-define(DBG_MESSAGE, true).
+-define(DBG_BIN_MESSAGE, true).
+-define(DBG_MAC, false).
+-define(DBG_ZLIB, true).
+
+versions(client, Options)->
+ Vsn = proplists:get_value(vsn, Options, ?DEFAULT_CLIENT_VERSION),
+ Version = format_version(Vsn),
+ {Vsn, Version};
+versions(server, Options) ->
+ Vsn = proplists:get_value(vsn, Options, ?DEFAULT_SERVER_VERSION),
+ Version = format_version(Vsn),
+ {Vsn, Version}.
+
+hello_version_msg(Data) ->
+ [Data,"\r\n"].
+
+next_seqnum(SeqNum) ->
+ (SeqNum + 1) band 16#ffffffff.
+
+decrypt_first_block(Bin, #ssh{decrypt_block_size = BlockSize} = Ssh0) ->
+ <<EncBlock:BlockSize/binary, EncData/binary>> = Bin,
+ {Ssh, <<?UINT32(PacketLen), _/binary>> = DecData} =
+ decrypt(Ssh0, EncBlock),
+ {Ssh, PacketLen, DecData, EncData}.
+
+decrypt_blocks(Bin, Length, Ssh0) ->
+ <<EncBlocks:Length/binary, EncData/binary>> = Bin,
+ {Ssh, DecData} = decrypt(Ssh0, EncBlocks),
+ {Ssh, DecData, EncData}.
+
+is_valid_mac(_, _ , #ssh{recv_mac_size = 0}) ->
+ true;
+is_valid_mac(Mac, Data, #ssh{recv_mac = Algorithm,
+ recv_mac_key = Key, recv_sequence = SeqNum}) ->
+ Mac == mac(Algorithm, Key, SeqNum, Data).
+
+transport_messages(_) ->
+ [{ssh_msg_disconnect, ?SSH_MSG_DISCONNECT,
+ [uint32, string, string]},
+
+ {ssh_msg_ignore, ?SSH_MSG_IGNORE,
+ [string]},
+
+ {ssh_msg_unimplemented, ?SSH_MSG_UNIMPLEMENTED,
+ [uint32]},
+
+ {ssh_msg_debug, ?SSH_MSG_DEBUG,
+ [boolean, string, string]},
+
+ {ssh_msg_service_request, ?SSH_MSG_SERVICE_REQUEST,
+ [string]},
+
+ {ssh_msg_service_accept, ?SSH_MSG_SERVICE_ACCEPT,
+ [string]},
+
+ {ssh_msg_kexinit, ?SSH_MSG_KEXINIT,
+ [cookie,
+ name_list, name_list,
+ name_list, name_list,
+ name_list, name_list,
+ name_list, name_list,
+ name_list, name_list,
+ boolean,
+ uint32]},
+
+ {ssh_msg_newkeys, ?SSH_MSG_NEWKEYS,
+ []}
+ ].
+
+kexdh_messages() ->
+ [{ssh_msg_kexdh_init, ?SSH_MSG_KEXDH_INIT,
+ [mpint]},
+
+ {ssh_msg_kexdh_reply, ?SSH_MSG_KEXDH_REPLY,
+ [binary, mpint, binary]}
+ ].
+
+kex_dh_gex_messages() ->
+ [{ssh_msg_kex_dh_gex_request, ?SSH_MSG_KEX_DH_GEX_REQUEST,
+ [uint32, uint32, uint32]},
+
+ {ssh_msg_kex_dh_gex_request_old, ?SSH_MSG_KEX_DH_GEX_REQUEST_OLD,
+ [uint32]},
+
+ {ssh_msg_kex_dh_gex_group, ?SSH_MSG_KEX_DH_GEX_GROUP,
+ [mpint, mpint]},
+
+ {ssh_msg_kex_dh_gex_init, ?SSH_MSG_KEX_DH_GEX_INIT,
+ [mpint]},
+
+ {ssh_msg_kex_dh_gex_reply, ?SSH_MSG_KEX_DH_GEX_REPLY,
+ [binary, mpint, binary]}
+ ].
+
+yes_no(Ssh, Prompt) ->
+ (Ssh#ssh.io_cb):yes_no(Prompt).
+
+connect(ConnectionSup, Address, Port, SocketOpts, Opts) ->
+ Timeout = proplists:get_value(connect_timeout, Opts, infinity),
+ {_, Callback, _} =
+ proplists:get_value(transport, Opts, {tcp, gen_tcp, tcp_closed}),
+ case do_connect(Callback, Address, Port, SocketOpts, Timeout) of
+ {ok, Socket} ->
+ {ok, Pid} =
+ ssh_connection_controler:start_handler_child(ConnectionSup,
+ [client, Socket,
+ [{address, Address},
+ {port, Port} |
+ Opts]]),
+ Callback:controlling_process(Socket, Pid),
+ ssh_connection_handler:send_event(Pid, socket_control),
+ {ok, Pid};
+ {error, Reason} ->
+ {error, Reason}
+ end.
+
+do_connect(Callback, Address, Port, SocketOpts, Timeout) ->
+ Opts = [{active, false} | SocketOpts],
+ case Callback:connect(Address, Port, Opts, Timeout) of
+ {error, nxdomain} ->
+ Callback:connect(Address, Port, lists:delete(inet6, Opts), Timeout);
+ {error, eafnosupport} ->
+ Callback:connect(Address, Port, lists:delete(inet6, Opts), Timeout);
+ Other ->
+ Other
+ end.
+
+accept(Address, Port, Socket, Options) ->
+ {_, Callback, _} =
+ proplists:get_value(transport, Options, {tcp, gen_tcp, tcp_closed}),
+ ConnectionSup =
+ ssh_system_sup:connection_supervisor(
+ ssh_system_sup:system_supervisor(Address, Port)),
+ {ok, Pid} =
+ ssh_connection_controler:start_handler_child(ConnectionSup,
+ [server, Socket,
+ [{address, Address},
+ {port, Port} | Options]]),
+ Callback:controlling_process(Socket, Pid),
+ ssh_connection_handler:send_event(Pid, socket_control),
+ {ok, Pid}.
+
+format_version({Major,Minor}) ->
+ "SSH-" ++ integer_to_list(Major) ++ "." ++
+ integer_to_list(Minor) ++ "-Erlang".
+
+handle_hello_version(Version) ->
+ StrVersion = trim_tail(Version),
+ case string:tokens(Version, "-") of
+ [_, "2.0" | _] ->
+ {{2,0}, StrVersion};
+ [_, "1.99" | _] ->
+ {{2,0}, StrVersion};
+ [_, "1.3" | _] ->
+ {{1,3}, StrVersion};
+ [_, "1.5" | _] ->
+ {{1,5}, StrVersion}
+ end.
+
+key_exchange_init_msg(Ssh0) ->
+ Msg = kex_init(Ssh0),
+ {SshPacket, Ssh} = ssh_packet(Msg, Ssh0),
+ {Msg, SshPacket, Ssh}.
+
+kex_init(#ssh{role = Role, opts = Opts}) ->
+ Random = ssh_bits:random(16),
+ Compression = case proplists:get_value(compression, Opts, none) of
+ zlib -> ["zlib", "none"];
+ none -> ["none", "zlib"]
+ end,
+ kexinit_messsage(Role, Random, Compression).
+
+key_init(client, Ssh, Value) ->
+ Ssh#ssh{c_keyinit = Value};
+key_init(server, Ssh, Value) ->
+ Ssh#ssh{s_keyinit = Value}.
+
+kexinit_messsage(client, Random, Compression) ->
+ #ssh_msg_kexinit{
+ cookie = Random,
+ kex_algorithms = ["diffie-hellman-group1-sha1"],
+ server_host_key_algorithms = ["ssh-rsa", "ssh-dss"],
+ encryption_algorithms_client_to_server = ["aes128-cbc","3des-cbc"],
+ encryption_algorithms_server_to_client = ["aes128-cbc","3des-cbc"],
+ mac_algorithms_client_to_server = ["hmac-sha1"],
+ mac_algorithms_server_to_client = ["hmac-sha1"],
+ compression_algorithms_client_to_server = Compression,
+ compression_algorithms_server_to_client = Compression,
+ languages_client_to_server = [],
+ languages_server_to_client = []
+ };
+
+kexinit_messsage(server, Random, Compression) ->
+ #ssh_msg_kexinit{
+ cookie = Random,
+ kex_algorithms = ["diffie-hellman-group1-sha1"],
+ server_host_key_algorithms = ["ssh-dss"],
+ encryption_algorithms_client_to_server = ["aes128-cbc","3des-cbc"],
+ encryption_algorithms_server_to_client = ["aes128-cbc","3des-cbc"],
+ mac_algorithms_client_to_server = ["hmac-sha1"],
+ mac_algorithms_server_to_client = ["hmac-sha1"],
+ compression_algorithms_client_to_server = Compression,
+ compression_algorithms_server_to_client = Compression,
+ languages_client_to_server = [],
+ languages_server_to_client = []
+ }.
+
+new_keys_message(Ssh0) ->
+ {SshPacket, Ssh} =
+ ssh_packet(#ssh_msg_newkeys{}, Ssh0),
+ {ok, SshPacket, Ssh}.
+
+handle_kexinit_msg(#ssh_msg_kexinit{} = CounterPart, #ssh_msg_kexinit{} = Own,
+ #ssh{role = client} = Ssh0) ->
+ {ok, Algoritms} = select_algorithm(client, Own, CounterPart),
+ case verify_algorithm(Algoritms) of
+ true ->
+ install_messages(Algoritms#alg.kex),
+ key_exchange_first_msg(Algoritms#alg.kex,
+ Ssh0#ssh{algorithms = Algoritms});
+ _ ->
+ %% TODO: Correct code?
+ throw(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR,
+ description = "Selection of key exchange"
+ " algorithm failed",
+ language = "en"})
+ end;
+
+handle_kexinit_msg(#ssh_msg_kexinit{} = CounterPart, #ssh_msg_kexinit{} = Own,
+ #ssh{role = server} = Ssh) ->
+ {ok, Algoritms} = select_algorithm(server, CounterPart, Own),
+ install_messages(Algoritms#alg.kex),
+ {ok, Ssh#ssh{algorithms = Algoritms}}.
+
+
+%% TODO: diffie-hellman-group14-sha1 should also be supported.
+%% Maybe check more things ...
+verify_algorithm(#alg{kex = 'diffie-hellman-group1-sha1'}) ->
+ true;
+verify_algorithm(#alg{kex = 'diffie-hellman-group-exchange-sha1'}) ->
+ true;
+verify_algorithm(_) ->
+ false.
+
+install_messages('diffie-hellman-group1-sha1') ->
+ ssh_bits:install_messages(kexdh_messages());
+install_messages('diffie-hellman-group-exchange-sha1') ->
+ ssh_bits:install_messages(kex_dh_gex_messages()).
+
+key_exchange_first_msg('diffie-hellman-group1-sha1', Ssh0) ->
+ {G, P} = dh_group1(),
+ {Private, Public} = dh_gen_key(G, P, 1024),
+ %%?dbg(?DBG_KEX, "public: ~p~n", [Public]),
+ {SshPacket, Ssh1} = ssh_packet(#ssh_msg_kexdh_init{e = Public}, Ssh0),
+ {ok, SshPacket,
+ Ssh1#ssh{keyex_key = {{Private, Public}, {G, P}}}};
+
+key_exchange_first_msg('diffie-hellman-group-exchange-sha1', Ssh0) ->
+ Min = ?DEFAULT_DH_GROUP_MIN,
+ NBits = ?DEFAULT_DH_GROUP_NBITS,
+ Max = ?DEFAULT_DH_GROUP_MAX,
+ {SshPacket, Ssh1} =
+ ssh_packet(#ssh_msg_kex_dh_gex_request{min = Min,
+ n = NBits, max = Max},
+ Ssh0),
+ {ok, SshPacket,
+ Ssh1#ssh{keyex_info = {Min, Max, NBits}}}.
+
+
+handle_kexdh_init(#ssh_msg_kexdh_init{e = E}, Ssh0) ->
+ {G, P} = dh_group1(),
+ {Private, Public} = dh_gen_key(G, P, 1024),
+ %%?dbg(?DBG_KEX, "public: ~p~n", [Public]),
+ K = ssh_math:ipow(E, Private, P),
+ {Key, K_S} = get_host_key(Ssh0),
+ H = kex_h(Ssh0, K_S, E, Public, K),
+ H_SIG = sign_host_key(Ssh0, Key, H),
+ {SshPacket, Ssh1} = ssh_packet(#ssh_msg_kexdh_reply{public_host_key = K_S,
+ f = Public,
+ h_sig = H_SIG
+ }, Ssh0),
+ %%?dbg(?DBG_KEX, "shared_secret: ~s ~n", [fmt_binary(K, 16, 4)]),
+ %%?dbg(?DBG_KEX, "hash: ~s ~n", [fmt_binary(H, 16, 4)]),
+ {ok, SshPacket, Ssh1#ssh{keyex_key = {{Private, Public}, {G, P}},
+ shared_secret = K,
+ exchanged_hash = H,
+ session_id = sid(Ssh1, H)}}.
+
+handle_kex_dh_gex_group(#ssh_msg_kex_dh_gex_group{p = P, g = G}, Ssh0) ->
+ {Private, Public} = dh_gen_key(G,P,1024),
+ %%?dbg(?DBG_KEX, "public: ~p ~n", [Public]),
+ {SshPacket, Ssh1} =
+ ssh_packet(#ssh_msg_kex_dh_gex_init{e = Public}, Ssh0),
+ {ok, SshPacket,
+ Ssh1#ssh{keyex_key = {{Private, Public}, {G, P}}}}.
+
+handle_new_keys(#ssh_msg_newkeys{}, Ssh0) ->
+ try install_alg(Ssh0) of
+ #ssh{} = Ssh ->
+ {ok, Ssh}
+ catch
+ error:_Error -> %% TODO: Throw earlier ....
+ throw(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR,
+ description = "Install alg failed",
+ language = "en"})
+ end.
+
+
+%% %% Select algorithms
+handle_kexdh_reply(#ssh_msg_kexdh_reply{public_host_key = HostKey, f = F,
+ h_sig = H_SIG},
+ #ssh{keyex_key = {{Private, Public}, {_G, P}}} = Ssh0) ->
+ K = ssh_math:ipow(F, Private, P),
+ H = kex_h(Ssh0, HostKey, Public, F, K),
+ %%?dbg(?DBG_KEX, "shared_secret: ~s ~n", [fmt_binary(K, 16, 4)]),
+ %%?dbg(?DBG_KEX, "hash: ~s ~n", [fmt_binary(H, 16, 4)]),
+ case verify_host_key(Ssh0, HostKey, H, H_SIG) of
+ ok ->
+ {SshPacket, Ssh} = ssh_packet(#ssh_msg_newkeys{}, Ssh0),
+ {ok, SshPacket, Ssh#ssh{shared_secret = K,
+ exchanged_hash = H,
+ session_id = sid(Ssh, H)}};
+ _Error ->
+ Disconnect = #ssh_msg_disconnect{
+ code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
+ description = "Key exchange failed",
+ language = "en"},
+ throw(Disconnect)
+ end.
+
+handle_kex_dh_gex_request(#ssh_msg_kex_dh_gex_request{min = _Min,
+ n = _NBits,
+ max = _Max}, Ssh0) ->
+ {G,P} = dh_group1(), %% TODO real imp this seems to be a hack?!
+ {Private, Public} = dh_gen_key(G, P, 1024),
+ {SshPacket, Ssh} =
+ ssh_packet(#ssh_msg_kex_dh_gex_group{p = P, g = G}, Ssh0),
+ {ok, SshPacket,
+ Ssh#ssh{keyex_key = {{Private, Public}, {G, P}}}}.
+
+handle_kex_dh_gex_reply(#ssh_msg_kex_dh_gex_reply{public_host_key = HostKey,
+ f = F,
+ h_sig = H_SIG},
+ #ssh{keyex_key = {{Private, Public}, {G, P}},
+ keyex_info = {Min, Max, NBits}} =
+ Ssh0) ->
+ K = ssh_math:ipow(F, Private, P),
+ H = kex_h(Ssh0, HostKey, Min, NBits, Max, P, G, Public, F, K),
+ %%?dbg(?DBG_KEX, "shared_secret: ~s ~n", [fmt_binary(K, 16, 4)]),
+ %%?dbg(?DBG_KEX, "hash: ~s ~n", [fmt_binary(H, 16, 4)]),
+ case verify_host_key(Ssh0, HostKey, H, H_SIG) of
+ ok ->
+ {SshPacket, Ssh} = ssh_packet(#ssh_msg_newkeys{}, Ssh0),
+ {ok, SshPacket, Ssh#ssh{shared_secret = K,
+ exchanged_hash = H,
+ session_id = sid(Ssh, H)}};
+ _Error ->
+ Disconnect = #ssh_msg_disconnect{
+ code = ?SSH_DISCONNECT_KEY_EXCHANGE_FAILED,
+ description = "Key exchange failed",
+ language = "en"},
+ throw(Disconnect)
+ end.
+
+%% select session id
+sid(#ssh{session_id = undefined}, H) ->
+ H;
+sid(#ssh{session_id = Id}, _) ->
+ Id.
+
+%%
+%% The host key should be read from storage
+%%
+get_host_key(SSH) ->
+ #ssh{key_cb = Mod, opts = Opts, algorithms = ALG} = SSH,
+ Scope = proplists:get_value(key_scope, Opts, system),
+ case ALG#alg.hkey of
+ 'ssh-rsa' ->
+ case Mod:private_host_rsa_key(Scope, Opts) of
+ {ok,Key=#ssh_key { public={N,E}} } ->
+ %%?dbg(true, "x~n", []),
+ {Key,
+ ssh_bits:encode(["ssh-rsa",E,N],[string,mpint,mpint])};
+ Error ->
+ %%?dbg(true, "y~n", []),
+ exit(Error)
+ end;
+ 'ssh-dss' ->
+ case Mod:private_host_dsa_key(Scope, Opts) of
+ {ok,Key=#ssh_key { public={P,Q,G,Y}}} ->
+ {Key, ssh_bits:encode(["ssh-dss",P,Q,G,Y],
+ [string,mpint,mpint,mpint,mpint])};
+ Error ->
+ exit(Error)
+ end;
+ _ ->
+ exit({error, bad_key_type})
+ end.
+
+sign_host_key(Ssh, Private, H) ->
+ ALG = Ssh#ssh.algorithms,
+ Module = case ALG#alg.hkey of
+ 'ssh-rsa' ->
+ ssh_rsa;
+ 'ssh-dss' ->
+ ssh_dsa
+ end,
+ case catch Module:sign(Private, H) of
+ {'EXIT', Reason} ->
+ error_logger:format("SIGN FAILED: ~p\n", [Reason]),
+ {error, Reason};
+ SIG ->
+ ssh_bits:encode([Module:alg_name() ,SIG],[string,binary])
+ end.
+
+verify_host_key(SSH, K_S, H, H_SIG) ->
+ ALG = SSH#ssh.algorithms,
+ case ALG#alg.hkey of
+ 'ssh-rsa' ->
+ case ssh_bits:decode(K_S,[string,mpint,mpint]) of
+ ["ssh-rsa", E, N] ->
+ ["ssh-rsa",SIG] = ssh_bits:decode(H_SIG,[string,binary]),
+ Public = #ssh_key { type=rsa, public={N,E} },
+ case catch ssh_rsa:verify(Public, H, SIG) of
+ {'EXIT', Reason} ->
+ error_logger:format("VERIFY FAILED: ~p\n", [Reason]),
+ {error, bad_signature};
+ ok ->
+ known_host_key(SSH, Public, "ssh-rsa")
+ end;
+ _ ->
+ {error, bad_format}
+ end;
+ 'ssh-dss' ->
+ case ssh_bits:decode(K_S,[string,mpint,mpint,mpint,mpint]) of
+ ["ssh-dss",P,Q,G,Y] ->
+ ["ssh-dss",SIG] = ssh_bits:decode(H_SIG,[string,binary]),
+ Public = #ssh_key { type=dsa, public={P,Q,G,Y} },
+ case catch ssh_dsa:verify(Public, H, SIG) of
+ {'EXIT', Reason} ->
+ error_logger:format("VERIFY FAILED: ~p\n", [Reason]),
+ {error, bad_signature};
+ ok ->
+ known_host_key(SSH, Public, "ssh-dss")
+ end;
+ _ ->
+ {error, bad_host_key_format}
+ end;
+ _ ->
+ {error, bad_host_key_algorithm}
+ end.
+
+accepted_host(Ssh, PeerName, Opts) ->
+ case proplists:get_value(silently_accept_hosts, Opts, false) of
+ true ->
+ yes;
+ false ->
+ yes_no(Ssh, "New host " ++ PeerName ++ " accept")
+ end.
+
+known_host_key(#ssh{opts = Opts, key_cb = Mod, peer = Peer} = Ssh,
+ Public, Alg) ->
+ PeerName = peer_name(Peer),
+ case Mod:lookup_host_key(PeerName, Alg, Opts) of
+ {ok, Public} ->
+ ok;
+ {ok, BadPublic} ->
+ error_logger:format("known_host_key: Public ~p BadPublic ~p\n",
+ [Public, BadPublic]),
+ {error, bad_public_key};
+ {error, not_found} ->
+ case accepted_host(Ssh, PeerName, Opts) of
+ yes ->
+ Mod:add_host_key(PeerName, Public, Opts);
+ no ->
+ {error, rejected}
+ end
+ end.
+
+
+%% Each of the algorithm strings MUST be a comma-separated list of
+%% algorithm names (see ''Algorithm Naming'' in [SSH-ARCH]). Each
+%% supported (allowed) algorithm MUST be listed in order of preference.
+%%
+%% The first algorithm in each list MUST be the preferred (guessed)
+%% algorithm. Each string MUST contain at least one algorithm name.
+select_algorithm(Role, Client, Server) ->
+ {Encrypt, Decrypt} = select_encrypt_decrypt(Role, Client, Server),
+ {SendMac, RecvMac} = select_send_recv_mac(Role, Client, Server),
+ {Compression, Decompression} =
+ select_compression_decompression(Role, Client, Server),
+
+ C_Lng = select(Client#ssh_msg_kexinit.languages_client_to_server,
+ Server#ssh_msg_kexinit.languages_client_to_server),
+ S_Lng = select(Client#ssh_msg_kexinit.languages_server_to_client,
+ Server#ssh_msg_kexinit.languages_server_to_client),
+ HKey = select_all(Client#ssh_msg_kexinit.server_host_key_algorithms,
+ Server#ssh_msg_kexinit.server_host_key_algorithms),
+ HK = case HKey of
+ [] -> undefined;
+ [HK0|_] -> HK0
+ end,
+ %% Fixme verify Kex against HKey list and algorithms
+
+ Kex = select(Client#ssh_msg_kexinit.kex_algorithms,
+ Server#ssh_msg_kexinit.kex_algorithms),
+
+ Alg = #alg{kex = Kex,
+ hkey = HK,
+ encrypt = Encrypt,
+ decrypt = Decrypt,
+ send_mac = SendMac,
+ recv_mac = RecvMac,
+ compress = Compression,
+ decompress = Decompression,
+ c_lng = C_Lng,
+ s_lng = S_Lng},
+ {ok, Alg}.
+
+select_encrypt_decrypt(client, Client, Server) ->
+ Encrypt =
+ select(Client#ssh_msg_kexinit.encryption_algorithms_client_to_server,
+ Server#ssh_msg_kexinit.encryption_algorithms_client_to_server),
+ Decrypt =
+ select(Client#ssh_msg_kexinit.encryption_algorithms_server_to_client,
+ Server#ssh_msg_kexinit.encryption_algorithms_server_to_client),
+ {Encrypt, Decrypt};
+select_encrypt_decrypt(server, Client, Server) ->
+ Decrypt =
+ select(Client#ssh_msg_kexinit.encryption_algorithms_client_to_server,
+ Server#ssh_msg_kexinit.encryption_algorithms_client_to_server),
+ Encrypt =
+ select(Client#ssh_msg_kexinit.encryption_algorithms_server_to_client,
+ Server#ssh_msg_kexinit.encryption_algorithms_server_to_client),
+ {Encrypt, Decrypt}.
+
+select_send_recv_mac(client, Client, Server) ->
+ SendMac = select(Client#ssh_msg_kexinit.mac_algorithms_client_to_server,
+ Server#ssh_msg_kexinit.mac_algorithms_client_to_server),
+ RecvMac = select(Client#ssh_msg_kexinit.mac_algorithms_server_to_client,
+ Server#ssh_msg_kexinit.mac_algorithms_server_to_client),
+ {SendMac, RecvMac};
+select_send_recv_mac(server, Client, Server) ->
+ RecvMac = select(Client#ssh_msg_kexinit.mac_algorithms_client_to_server,
+ Server#ssh_msg_kexinit.mac_algorithms_client_to_server),
+ SendMac = select(Client#ssh_msg_kexinit.mac_algorithms_server_to_client,
+ Server#ssh_msg_kexinit.mac_algorithms_server_to_client),
+ {SendMac, RecvMac}.
+
+select_compression_decompression(client, Client, Server) ->
+ Compression =
+ select(Client#ssh_msg_kexinit.compression_algorithms_client_to_server,
+ Server#ssh_msg_kexinit.compression_algorithms_client_to_server),
+ Decomprssion =
+ select(Client#ssh_msg_kexinit.compression_algorithms_server_to_client,
+ Server#ssh_msg_kexinit.compression_algorithms_server_to_client),
+ {Compression, Decomprssion};
+select_compression_decompression(server, Client, Server) ->
+ Decomprssion =
+ select(Client#ssh_msg_kexinit.compression_algorithms_client_to_server,
+ Server#ssh_msg_kexinit.compression_algorithms_client_to_server),
+ Compression =
+ select(Client#ssh_msg_kexinit.compression_algorithms_server_to_client,
+ Server#ssh_msg_kexinit.compression_algorithms_server_to_client),
+ {Compression, Decomprssion}.
+
+install_alg(SSH) ->
+ SSH1 = alg_final(SSH),
+ SSH2 = alg_setup(SSH1),
+ alg_init(SSH2).
+
+alg_setup(SSH) ->
+ ALG = SSH#ssh.algorithms,
+ %%?dbg(?DBG_ALG, "ALG: setup ~p ~n", [ALG]),
+ SSH#ssh{kex = ALG#alg.kex,
+ hkey = ALG#alg.hkey,
+ encrypt = ALG#alg.encrypt,
+ decrypt = ALG#alg.decrypt,
+ send_mac = ALG#alg.send_mac,
+ send_mac_size = mac_digest_size(ALG#alg.send_mac),
+ recv_mac = ALG#alg.recv_mac,
+ recv_mac_size = mac_digest_size(ALG#alg.recv_mac),
+ compress = ALG#alg.compress,
+ decompress = ALG#alg.decompress,
+ c_lng = ALG#alg.c_lng,
+ s_lng = ALG#alg.s_lng,
+ algorithms = undefined
+ }.
+
+alg_init(SSH0) ->
+ %%?dbg(?DBG_ALG, "ALG: init~n", []),
+ {ok,SSH1} = send_mac_init(SSH0),
+ {ok,SSH2} = recv_mac_init(SSH1),
+ {ok,SSH3} = encrypt_init(SSH2),
+ {ok,SSH4} = decrypt_init(SSH3),
+ {ok,SSH5} = compress_init(SSH4),
+ {ok,SSH6} = decompress_init(SSH5),
+ SSH6.
+
+alg_final(SSH0) ->
+ %%?dbg(?DBG_ALG, "ALG: final ~n", []),
+ {ok,SSH1} = send_mac_final(SSH0),
+ {ok,SSH2} = recv_mac_final(SSH1),
+ {ok,SSH3} = encrypt_final(SSH2),
+ {ok,SSH4} = decrypt_final(SSH3),
+ {ok,SSH5} = compress_final(SSH4),
+ {ok,SSH6} = decompress_final(SSH5),
+ SSH6.
+
+select_all(CL, SL) ->
+ A = CL -- SL, %% algortihms only used by client
+ %% algorithms used by client and server (client pref)
+ lists:map(fun(ALG) -> list_to_atom(ALG) end, (CL -- A)).
+
+select([], []) ->
+ none;
+select(CL, SL) ->
+ C = case select_all(CL,SL) of
+ [] -> undefined;
+ [ALG|_] -> ALG
+ end,
+ %%?dbg(?DBG_ALG, "ALG: select: ~p ~p = ~p~n", [CL, SL, C]),
+ C.
+
+ssh_packet(#ssh_msg_kexinit{} = Msg, Ssh0) ->
+ BinMsg = ssh_bits:encode(Msg),
+ Ssh = key_init(Ssh0#ssh.role, Ssh0, BinMsg),
+ %%?dbg(?DBG_MESSAGE, "SEND_MSG: ~p~n", [Msg]),
+ pack(BinMsg, Ssh);
+
+ssh_packet(Msg, Ssh) ->
+ BinMsg = ssh_bits:encode(Msg),
+ %%?dbg(?DBG_MESSAGE, "SEND_MSG: ~p~n", [Msg]),
+ %%?dbg(?DBG_BIN_MESSAGE, "Encoded: ~p~n", [BinMsg]),
+ pack(BinMsg, Ssh).
+
+pack(Data0, #ssh{encrypt_block_size = BlockSize,
+ send_sequence = SeqNum, send_mac = MacAlg,
+ send_mac_key = MacKey}
+ = Ssh0) when is_binary(Data0) ->
+ {Ssh1, Data} = compress(Ssh0, Data0),
+ PL = (BlockSize - ((4 + 1 + size(Data)) rem BlockSize)) rem BlockSize,
+ PaddingLen = if PL < 4 -> PL + BlockSize;
+ true -> PL
+ end,
+ Padding = ssh_bits:random(PaddingLen),
+ PacketLen = 1 + PaddingLen + size(Data),
+ PacketData = <<?UINT32(PacketLen),?BYTE(PaddingLen),
+ Data/binary, Padding/binary>>,
+ {Ssh2, EncPacket} = encrypt(Ssh1, PacketData),
+ MAC = mac(MacAlg, MacKey, SeqNum, PacketData),
+ Packet = [EncPacket, MAC],
+ Ssh = Ssh2#ssh{send_sequence = (SeqNum+1) band 16#ffffffff},
+ {Packet, Ssh}.
+
+unpack(EncodedSoFar, ReminingLenght, #ssh{recv_mac_size = MacSize} = Ssh0) ->
+ SshLength = ReminingLenght - MacSize,
+ {NoMac, Mac, Rest} = case MacSize of
+ 0 ->
+ <<NoMac0:SshLength/binary,
+ Rest0/binary>> = EncodedSoFar,
+ {NoMac0, <<>>, Rest0};
+ _ ->
+ <<NoMac0:SshLength/binary,
+ Mac0:MacSize/binary,
+ Rest0/binary>> = EncodedSoFar,
+ {NoMac0, Mac0, Rest0}
+ end,
+ {Ssh1, DecData, <<>>} =
+ case SshLength of
+ 0 ->
+ {Ssh0, <<>>, <<>>};
+ _ ->
+ decrypt_blocks(NoMac, SshLength, Ssh0)
+ end,
+ {Ssh1, DecData, Rest, Mac}.
+
+msg_data(PacketData) ->
+ <<Len:32, PaddingLen:8, _/binary>> = PacketData,
+ DataLen = Len - PaddingLen - 1,
+ <<_:32, _:8, Data:DataLen/binary,
+ _:PaddingLen/binary>> = PacketData,
+ Data.
+
+
+%% Send a disconnect message
+%% terminate(S, SSH, Code, Message) ->
+%% M = #ssh_msg_disconnect{code=Code,
+%% description = Message,
+%% language = "en"},
+%% send_msg(S, SSH, M),
+%% gen_tcp:close(S),
+%% {error, M}.
+
+
+%% public key algorithms
+%%
+%% ssh-dss REQUIRED sign Raw DSS Key
+%% ssh-rsa RECOMMENDED sign Raw RSA Key
+%% x509v3-sign-rsa OPTIONAL sign X.509 certificates (RSA key)
+%% x509v3-sign-dss OPTIONAL sign X.509 certificates (DSS key)
+%% spki-sign-rsa OPTIONAL sign SPKI certificates (RSA key)
+%% spki-sign-dss OPTIONAL sign SPKI certificates (DSS key)
+%% pgp-sign-rsa OPTIONAL sign OpenPGP certificates (RSA key)
+%% pgp-sign-dss OPTIONAL sign OpenPGP certificates (DSS key)
+%%
+
+%% key exchange
+%%
+%% diffie-hellman-group1-sha1 REQUIRED
+%%
+%%
+
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Encryption
+%%
+%% chiphers
+%%
+%% 3des-cbc REQUIRED
+%% three-key 3DES in CBC mode
+%% blowfish-cbc OPTIONAL Blowfish in CBC mode
+%% twofish256-cbc OPTIONAL Twofish in CBC mode,
+%% with 256-bit key
+%% twofish-cbc OPTIONAL alias for "twofish256-cbc" (this
+%% is being retained for
+%% historical reasons)
+%% twofish192-cbc OPTIONAL Twofish with 192-bit key
+%% twofish128-cbc OPTIONAL Twofish with 128-bit key
+%% aes256-cbc OPTIONAL AES in CBC mode,
+%% with 256-bit key
+%% aes192-cbc OPTIONAL AES with 192-bit key
+%% aes128-cbc RECOMMENDED AES with 128-bit key
+%% serpent256-cbc OPTIONAL Serpent in CBC mode, with
+%% 256-bit key
+%% serpent192-cbc OPTIONAL Serpent with 192-bit key
+%% serpent128-cbc OPTIONAL Serpent with 128-bit key
+%% arcfour OPTIONAL the ARCFOUR stream cipher
+%% idea-cbc OPTIONAL IDEA in CBC mode
+%% cast128-cbc OPTIONAL CAST-128 in CBC mode
+%% none OPTIONAL no encryption; NOT RECOMMENDED
+%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+encrypt_init(#ssh{encrypt = none} = Ssh) ->
+ {ok, Ssh};
+encrypt_init(#ssh{encrypt = '3des-cbc', role = client} = Ssh) ->
+ IV = hash(Ssh, "A", 64),
+ <<K1:8/binary, K2:8/binary, K3:8/binary>> = hash(Ssh, "C", 192),
+ {ok, Ssh#ssh{encrypt_keys = {K1,K2,K3},
+ encrypt_block_size = 8,
+ encrypt_ctx = IV}};
+encrypt_init(#ssh{encrypt = '3des-cbc', role = server} = Ssh) ->
+ IV = hash(Ssh, "B", 64),
+ <<K1:8/binary, K2:8/binary, K3:8/binary>> = hash(Ssh, "D", 192),
+ {ok, Ssh#ssh{encrypt_keys = {K1,K2,K3},
+ encrypt_block_size = 8,
+ encrypt_ctx = IV}};
+encrypt_init(#ssh{encrypt = 'aes128-cbc', role = client} = Ssh) ->
+ IV = hash(Ssh, "A", 128),
+ <<K:16/binary>> = hash(Ssh, "C", 128),
+ {ok, Ssh#ssh{encrypt_keys = K,
+ encrypt_block_size = 16,
+ encrypt_ctx = IV}};
+encrypt_init(#ssh{encrypt = 'aes128-cbc', role = server} = Ssh) ->
+ IV = hash(Ssh, "B", 128),
+ <<K:16/binary>> = hash(Ssh, "D", 128),
+ {ok, Ssh#ssh{encrypt_keys = K,
+ encrypt_block_size = 16,
+ encrypt_ctx = IV}}.
+
+encrypt_final(Ssh) ->
+ {ok, Ssh#ssh{encrypt = none,
+ encrypt_keys = undefined,
+ encrypt_block_size = 8,
+ encrypt_ctx = undefined
+ }}.
+
+encrypt(#ssh{encrypt = none} = Ssh, Data) ->
+ {Ssh, Data};
+encrypt(#ssh{encrypt = '3des-cbc',
+ encrypt_keys = {K1,K2,K3},
+ encrypt_ctx = IV0} = Ssh, Data) ->
+ %%?dbg(?DBG_CRYPTO, "encrypt: IV=~p K1=~p, K2=~p, K3=~p ~n",
+ %% [IV0,K1,K2,K3]),
+ Enc = crypto:des3_cbc_encrypt(K1,K2,K3,IV0,Data),
+ %%?dbg(?DBG_CRYPTO, "encrypt: ~p -> ~p ~n", [Data, Enc]),
+ IV = crypto:des_cbc_ivec(Enc),
+ {Ssh#ssh{encrypt_ctx = IV}, Enc};
+encrypt(#ssh{encrypt = 'aes128-cbc',
+ encrypt_keys = K,
+ encrypt_ctx = IV0} = Ssh, Data) ->
+ %%?dbg(?DBG_CRYPTO, "encrypt: IV=~p K=~p ~n",
+ %% [IV0,K]),
+ Enc = crypto:aes_cbc_128_encrypt(K,IV0,Data),
+ %%?dbg(?DBG_CRYPTO, "encrypt: ~p -> ~p ~n", [Data, Enc]),
+ IV = crypto:aes_cbc_ivec(Enc),
+ {Ssh#ssh{encrypt_ctx = IV}, Enc}.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Decryption
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+decrypt_init(#ssh{decrypt = none} = Ssh) ->
+ {ok, Ssh};
+decrypt_init(#ssh{decrypt = '3des-cbc', role = client} = Ssh) ->
+ {IV, KD} = {hash(Ssh, "B", 64),
+ hash(Ssh, "D", 192)},
+ <<K1:8/binary, K2:8/binary, K3:8/binary>> = KD,
+ {ok, Ssh#ssh{decrypt_keys = {K1,K2,K3}, decrypt_ctx = IV,
+ decrypt_block_size = 8}};
+decrypt_init(#ssh{decrypt = '3des-cbc', role = server} = Ssh) ->
+ {IV, KD} = {hash(Ssh, "A", 64),
+ hash(Ssh, "C", 192)},
+ <<K1:8/binary, K2:8/binary, K3:8/binary>> = KD,
+ {ok, Ssh#ssh{decrypt_keys = {K1, K2, K3}, decrypt_ctx = IV,
+ decrypt_block_size = 8}};
+decrypt_init(#ssh{decrypt = 'aes128-cbc', role = client} = Ssh) ->
+ {IV, KD} = {hash(Ssh, "B", 128),
+ hash(Ssh, "D", 128)},
+ <<K:16/binary>> = KD,
+ {ok, Ssh#ssh{decrypt_keys = K, decrypt_ctx = IV,
+ decrypt_block_size = 16}};
+decrypt_init(#ssh{decrypt = 'aes128-cbc', role = server} = Ssh) ->
+ {IV, KD} = {hash(Ssh, "A", 128),
+ hash(Ssh, "C", 128)},
+ <<K:16/binary>> = KD,
+ {ok, Ssh#ssh{decrypt_keys = K, decrypt_ctx = IV,
+ decrypt_block_size = 16}}.
+
+
+decrypt_final(Ssh) ->
+ {ok, Ssh#ssh {decrypt = none,
+ decrypt_keys = undefined,
+ decrypt_ctx = undefined,
+ decrypt_block_size = 8}}.
+
+decrypt(#ssh{decrypt = none} = Ssh, Data) ->
+ {Ssh, Data};
+decrypt(#ssh{decrypt = '3des-cbc', decrypt_keys = Keys,
+ decrypt_ctx = IV0} = Ssh, Data) ->
+ {K1, K2, K3} = Keys,
+ %%?dbg(?DBG_CRYPTO, "decrypt: IV=~p K1=~p, K2=~p, K3=~p ~n",
+ %%[IV0,K1,K2,K3]),
+ Dec = crypto:des3_cbc_decrypt(K1,K2,K3,IV0,Data),
+ %%?dbg(?DBG_CRYPTO, "decrypt: ~p -> ~p ~n", [Data, Dec]),
+ IV = crypto:des_cbc_ivec(Data),
+ {Ssh#ssh{decrypt_ctx = IV}, Dec};
+decrypt(#ssh{decrypt = 'aes128-cbc', decrypt_keys = Key,
+ decrypt_ctx = IV0} = Ssh, Data) ->
+ %%?dbg(?DBG_CRYPTO, "decrypt: IV=~p Key=~p ~n",
+ %% [IV0,Key]),
+ Dec = crypto:aes_cbc_128_decrypt(Key,IV0,Data),
+ %%?dbg(?DBG_CRYPTO, "decrypt: ~p -> ~p ~n", [Data, Dec]),
+ IV = crypto:aes_cbc_ivec(Data),
+ {Ssh#ssh{decrypt_ctx = IV}, Dec}.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Compression
+%%
+%% none REQUIRED no compression
+%% zlib OPTIONAL ZLIB (LZ77) compression
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+compress_init(SSH) ->
+ compress_init(SSH, 1).
+
+compress_init(#ssh{compress = none} = Ssh, _) ->
+ {ok, Ssh};
+compress_init(#ssh{compress = zlib} = Ssh, Level) ->
+ Zlib = zlib:open(),
+ ok = zlib:deflateInit(Zlib, Level),
+ {ok, Ssh#ssh{compress_ctx = Zlib}}.
+
+
+compress_final(#ssh{compress = none} = Ssh) ->
+ {ok, Ssh};
+compress_final(#ssh{compress = zlib, compress_ctx = Context} = Ssh) ->
+ zlib:close(Context),
+ {ok, Ssh#ssh{compress = none, compress_ctx = undefined}}.
+
+compress(#ssh{compress = none} = Ssh, Data) ->
+ {Ssh, Data};
+compress(#ssh{compress = zlib, compress_ctx = Context} = Ssh, Data) ->
+ Compressed = zlib:deflate(Context, Data, sync),
+ %%?dbg(?DBG_ZLIB, "deflate: ~p -> ~p ~n", [Data, Compressed]),
+ {Ssh, list_to_binary(Compressed)}.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Decompression
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+decompress_init(#ssh{decompress = none} = Ssh) ->
+ {ok, Ssh};
+decompress_init(#ssh{decompress = zlib} = Ssh) ->
+ Zlib = zlib:open(),
+ ok = zlib:inflateInit(Zlib),
+ {ok, Ssh#ssh{decompress_ctx = Zlib}}.
+
+decompress_final(#ssh{decompress = none} = Ssh) ->
+ {ok, Ssh};
+decompress_final(#ssh{decompress = zlib, decompress_ctx = Context} = Ssh) ->
+ zlib:close(Context),
+ {ok, Ssh#ssh{decompress = none, decompress_ctx = undefined}}.
+
+decompress(#ssh{decompress = none} = Ssh, Data) ->
+ {Ssh, Data};
+decompress(#ssh{decompress = zlib, decompress_ctx = Context} = Ssh, Data) ->
+ Decompressed = zlib:inflate(Context, Data),
+ %%?dbg(?DBG_ZLIB, "inflate: ~p -> ~p ~n", [Data, Decompressed]),
+ {Ssh, list_to_binary(Decompressed)}.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% MAC calculation
+%%
+%% hmac-sha1 REQUIRED HMAC-SHA1 (digest length = key
+%% length = 20)
+%% hmac-sha1-96 RECOMMENDED first 96 bits of HMAC-SHA1 (digest
+%% length = 12, key length = 20)
+%% hmac-md5 OPTIONAL HMAC-MD5 (digest length = key
+%% length = 16)
+%% hmac-md5-96 OPTIONAL first 96 bits of HMAC-MD5 (digest
+%% length = 12, key length = 16)
+%% none OPTIONAL no MAC; NOT RECOMMENDED
+%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+send_mac_init(SSH) ->
+ case SSH#ssh.role of
+ client ->
+ KeySize =mac_key_size(SSH#ssh.send_mac),
+ Key = hash(SSH, "E", KeySize),
+ {ok, SSH#ssh { send_mac_key = Key }};
+ server ->
+ KeySize = mac_key_size(SSH#ssh.send_mac),
+ Key = hash(SSH, "F", KeySize),
+ {ok, SSH#ssh { send_mac_key = Key }}
+ end.
+
+send_mac_final(SSH) ->
+ {ok, SSH#ssh { send_mac = none, send_mac_key = undefined }}.
+
+recv_mac_init(SSH) ->
+ case SSH#ssh.role of
+ client ->
+ Key = hash(SSH, "F", mac_key_size(SSH#ssh.recv_mac)),
+ {ok, SSH#ssh { recv_mac_key = Key }};
+ server ->
+ Key = hash(SSH, "E", mac_key_size(SSH#ssh.recv_mac)),
+ {ok, SSH#ssh { recv_mac_key = Key }}
+ end.
+
+recv_mac_final(SSH) ->
+ {ok, SSH#ssh { recv_mac = none, recv_mac_key = undefined }}.
+
+mac(none, _ , _, _) ->
+ <<>>;
+mac('hmac-sha1', Key, SeqNum, Data) ->
+ crypto:sha_mac(Key, [<<?UINT32(SeqNum)>>, Data]);
+mac('hmac-sha1-96', Key, SeqNum, Data) ->
+ crypto:sha_mac_96(Key, [<<?UINT32(SeqNum)>>, Data]);
+mac('hmac-md5', Key, SeqNum, Data) ->
+ crypto:md5_mac(Key, [<<?UINT32(SeqNum)>>, Data]);
+mac('hmac-md5-96', Key, SeqNum, Data) ->
+ crypto:md5_mac_96(Key, [<<?UINT32(SeqNum)>>, Data]).
+
+%% return N hash bytes (HASH)
+hash(SSH, Char, Bits) ->
+ HASH =
+ case SSH#ssh.kex of
+ 'diffie-hellman-group1-sha1' ->
+ fun(Data) -> crypto:sha(Data) end;
+ 'diffie-hellman-group-exchange-sha1' ->
+ fun(Data) -> crypto:sha(Data) end;
+ _ ->
+ exit({bad_algorithm,SSH#ssh.kex})
+ end,
+ hash(SSH, Char, Bits, HASH).
+
+hash(_SSH, _Char, 0, _HASH) ->
+ <<>>;
+hash(SSH, Char, N, HASH) ->
+ K = ssh_bits:mpint(SSH#ssh.shared_secret),
+ H = SSH#ssh.exchanged_hash,
+ SessionID = SSH#ssh.session_id,
+ K1 = HASH([K, H, Char, SessionID]),
+ Sz = N div 8,
+ <<Key:Sz/binary, _/binary>> = hash(K, H, K1, N-128, HASH),
+ %%?dbg(?DBG_KEX, "Key ~s: ~s ~n", [Char, fmt_binary(Key, 16, 4)]),
+ Key.
+
+hash(_K, _H, Ki, N, _HASH) when N =< 0 ->
+ Ki;
+hash(K, H, Ki, N, HASH) ->
+ Kj = HASH([K, H, Ki]),
+ hash(K, H, <<Ki/binary, Kj/binary>>, N-128, HASH).
+
+kex_h(SSH, K_S, E, F, K) ->
+ L = ssh_bits:encode([SSH#ssh.c_version, SSH#ssh.s_version,
+ SSH#ssh.c_keyinit, SSH#ssh.s_keyinit,
+ K_S, E,F,K],
+ [string,string,binary,binary,binary,
+ mpint,mpint,mpint]),
+ crypto:sha(L).
+
+kex_h(SSH, K_S, Min, NBits, Max, Prime, Gen, E, F, K) ->
+ L = if Min==-1; Max==-1 ->
+ Ts = [string,string,binary,binary,binary,
+ uint32,
+ mpint,mpint,mpint,mpint,mpint],
+ ssh_bits:encode([SSH#ssh.c_version,SSH#ssh.s_version,
+ SSH#ssh.c_keyinit,SSH#ssh.s_keyinit,
+ K_S, NBits, Prime, Gen, E,F,K],
+ Ts);
+ true ->
+ Ts = [string,string,binary,binary,binary,
+ uint32,uint32,uint32,
+ mpint,mpint,mpint,mpint,mpint],
+ ssh_bits:encode([SSH#ssh.c_version,SSH#ssh.s_version,
+ SSH#ssh.c_keyinit,SSH#ssh.s_keyinit,
+ K_S, Min, NBits, Max,
+ Prime, Gen, E,F,K], Ts)
+ end,
+ crypto:sha(L).
+
+mac_key_size('hmac-sha1') -> 20*8;
+mac_key_size('hmac-sha1-96') -> 20*8;
+mac_key_size('hmac-md5') -> 16*8;
+mac_key_size('hmac-md5-96') -> 16*8;
+mac_key_size(none) -> 0.
+
+mac_digest_size('hmac-sha1') -> 20;
+mac_digest_size('hmac-sha1-96') -> 12;
+mac_digest_size('hmac-md5') -> 20;
+mac_digest_size('hmac-md5-96') -> 12;
+mac_digest_size(none) -> 0.
+
+peer_name({Host, _}) ->
+ Host.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%
+%% Diffie-Hellman utils
+%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+dh_group1() ->
+ {2, 16#FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF}.
+
+dh_gen_key(G, P, _Bits) ->
+ Private = ssh_bits:irandom(ssh_bits:isize(P)-1, 1, 1),
+ Public = ssh_math:ipow(G, Private, P),
+ {Private,Public}.
+
+%% trim(Str) ->
+%% lists:reverse(trim_head(lists:reverse(trim_head(Str)))).
+
+trim_tail(Str) ->
+ lists:reverse(trim_head(lists:reverse(Str))).
+
+trim_head([$\s|Cs]) -> trim_head(Cs);
+trim_head([$\t|Cs]) -> trim_head(Cs);
+trim_head([$\n|Cs]) -> trim_head(Cs);
+trim_head([$\r|Cs]) -> trim_head(Cs);
+trim_head(Cs) -> Cs.
+
+%% Retrieve session_id from ssh, needed by public-key auth
+%get_session_id(SSH) ->
+% {ok, SessionID} = call(SSH, get_session_id),
+
+%% DEBUG utils
+%% Format integers and binaries as hex blocks
+%%
+%% -ifdef(debug).
+%% fmt_binary(B, BlockSize, GroupSize) ->
+%% fmt_block(fmt_bin(B), BlockSize, GroupSize).
+
+%% fmt_block(Bin, BlockSize, GroupSize) ->
+%% fmt_block(Bin, BlockSize, 0, GroupSize).
+
+
+%% fmt_block(Bin, 0, _I, _G) ->
+%% binary_to_list(Bin);
+%% fmt_block(Bin, Sz, G, G) when G =/= 0 ->
+%% ["~n#" | fmt_block(Bin, Sz, 0, G)];
+%% fmt_block(Bin, Sz, I, G) ->
+%% case Bin of
+%% <<Block:Sz/binary, Tail/binary>> ->
+%% if Tail == <<>> ->
+%% [binary_to_list(Block)];
+%% true ->
+%% [binary_to_list(Block), " " | fmt_block(Tail, Sz, I+1, G)]
+%% end;
+%% <<>> ->
+%% [];
+%% _ ->
+%% [binary_to_list(Bin)]
+%% end.
+
+%% %% Format integer or binary as hex
+%% fmt_bin(X) when integer(X) ->
+%% list_to_binary(io_lib:format("~p", [X]));
+%% fmt_bin(X) when binary(X) ->
+%% Sz = size(X)*8,
+%% <<Y:Sz/unsigned-big>> = X,
+%% %%Fmt = "~"++integer_to_list(size(X)*2)++"~p",
+%% list_to_binary(io_lib:format("~p", [Y])).
+
+%% -endif.
+
diff --git a/lib/ssh/src/ssh_transport.hrl b/lib/ssh/src/ssh_transport.hrl
new file mode 100644
index 0000000000..18a23f0533
--- /dev/null
+++ b/lib/ssh/src/ssh_transport.hrl
@@ -0,0 +1,235 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2009. 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%
+%%
+
+%%
+%%----------------------------------------------------------------------
+%% Purpose: Record and constant defenitions for the SSH-tansport layer
+%% protocol see RFC 4253
+%%----------------------------------------------------------------------
+
+-ifndef(ssh_transport).
+-define(ssh_transport, true).
+
+-define(DEFAULT_CLIENT_VERSION, {2, 0}).
+-define(DEFAULT_SERVER_VERSION, {2, 0}).
+-define(DEFAULT_DH_GROUP_MIN, 512).
+-define(DEFAULT_DH_GROUP_NBITS, 1024).
+-define(DEFAULT_DH_GROUP_MAX, 4096).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%
+%% BASIC transport messages
+%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-define(SSH_MSG_DISCONNECT, 1).
+-define(SSH_MSG_IGNORE, 2).
+-define(SSH_MSG_UNIMPLEMENTED, 3).
+-define(SSH_MSG_DEBUG, 4).
+-define(SSH_MSG_SERVICE_REQUEST, 5).
+-define(SSH_MSG_SERVICE_ACCEPT, 6).
+
+-define(SSH_MSG_KEXINIT, 20).
+-define(SSH_MSG_NEWKEYS, 21).
+
+
+-record(ssh_msg_disconnect,
+ {
+ code, %% uint32
+ description, %% string
+ language %% string
+ }).
+
+-record(ssh_msg_ignore,
+ {
+ data %% string
+ }).
+
+-record(ssh_msg_unimplemented,
+ {
+ sequence %% uint32
+ }).
+
+-record(ssh_msg_debug,
+ {
+ always_display, %% boolean
+ message, %% string
+ language %% string
+ }).
+
+
+-record(ssh_msg_service_request,
+ {
+ name %% string (service name)
+ }).
+
+-record(ssh_msg_service_accept,
+ {
+ name %% string
+ }).
+
+-record(ssh_msg_kexinit,
+ {
+ cookie, %% random(16)
+ kex_algorithms, %% string
+ server_host_key_algorithms, %% string
+ encryption_algorithms_client_to_server, %% string
+ encryption_algorithms_server_to_client, %% string
+ mac_algorithms_client_to_server, %% string
+ mac_algorithms_server_to_client, %% string
+ compression_algorithms_client_to_server, %% string
+ compression_algorithms_server_to_client, %% string
+ languages_client_to_server, %% string
+ languages_server_to_client, %% string
+ first_kex_packet_follows=false, %% boolean
+ %% (reserved for future extension)
+ reserved=0 %% uint32=0
+ }).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%
+%% KEY DH messages
+%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% diffie-hellman-group1-sha1
+-define(SSH_MSG_KEXDH_INIT, 30).
+-define(SSH_MSG_KEXDH_REPLY, 31).
+
+-record(ssh_msg_kexdh_init,
+ {
+ e %% mpint
+ }).
+
+-record(ssh_msg_kexdh_reply,
+ {
+ public_host_key, %% string (K_S)
+ f, %% mpint
+ h_sig %% string, signature of H
+ }).
+
+-record(ssh_msg_newkeys,
+ {}).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%
+%% KEY DH GEX messages
+%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% diffie-hellman-group-exchange-sha1
+-define(SSH_MSG_KEX_DH_GEX_REQUEST_OLD, 30).
+-define(SSH_MSG_KEX_DH_GEX_REQUEST, 34).
+-define(SSH_MSG_KEX_DH_GEX_GROUP, 31).
+-define(SSH_MSG_KEX_DH_GEX_INIT, 32).
+-define(SSH_MSG_KEX_DH_GEX_REPLY, 33).
+
+-record(ssh_msg_kex_dh_gex_request,
+ {
+ min,
+ n,
+ max
+ }).
+
+-record(ssh_msg_kex_dh_gex_request_old,
+ {
+ n
+ }).
+
+-record(ssh_msg_kex_dh_gex_group,
+ {
+ p, %% prime
+ g %% generator
+ }).
+
+-record(ssh_msg_kex_dh_gex_init,
+ {
+ e
+ }).
+
+-record(ssh_msg_kex_dh_gex_reply,
+ {
+ public_host_key, %% string (K_S)
+ f,
+ h_sig
+ }).
+
+%% error codes
+-define(SSH_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT, 1).
+-define(SSH_DISCONNECT_PROTOCOL_ERROR, 2).
+-define(SSH_DISCONNECT_KEY_EXCHANGE_FAILED, 3).
+-define(SSH_DISCONNECT_RESERVED, 4).
+-define(SSH_DISCONNECT_MAC_ERROR, 5).
+-define(SSH_DISCONNECT_COMPRESSION_ERROR, 6).
+-define(SSH_DISCONNECT_SERVICE_NOT_AVAILABLE, 7).
+-define(SSH_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED, 8).
+-define(SSH_DISCONNECT_HOST_KEY_NOT_VERIFIABLE, 9).
+-define(SSH_DISCONNECT_CONNECTION_LOST, 10).
+-define(SSH_DISCONNECT_BY_APPLICATION, 11).
+-define(SSH_DISCONNECT_TOO_MANY_CONNECTIONS, 12).
+-define(SSH_DISCONNECT_AUTH_CANCELLED_BY_USER, 13).
+-define(SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE, 14).
+-define(SSH_DISCONNECT_ILLEGAL_USER_NAME, 15).
+
+
+%%%----------------------------------------------------------------------
+%%% # DH_14_xxx
+%%% Description: Oakley group 14 prime numbers and generator. Used in
+%%% diffie-hellman-group1-sha1 key exchange method.
+%%%----------------------------------------------------------------------
+%%%----------------------------------------------------------------------
+%%% # DH_14_P
+%%% Description: Prime for this group
+%%%----------------------------------------------------------------------
+
+-define(DH_14_P,
+ <<000,000,000,129,000,255,255,255,255,255,255,255,255,201,015,218,
+ 162,033,104,194,052,196,198,098,139,128,220,028,209,041,002,078,
+ 008,138,103,204,116,002,011,190,166,059,019,155,034,081,074,008,
+ 121,142,052,004,221,239,149,025,179,205,058,067,027,048,043,010,
+ 109,242,095,020,055,079,225,053,109,109,081,194,069,228,133,181,
+ 118,098,094,126,198,244,076,066,233,166,055,237,107,011,255,092,
+ 182,244,006,183,237,238,056,107,251,090,137,159,165,174,159,036,
+ 017,124,075,031,230,073,040,102,081,236,230,083,129,255,255,255,
+ 255,255,255,255,255>>).
+
+%%%----------------------------------------------------------------------
+%%% # DH_14_G
+%%% Description: Generator for DH_14_P.
+%%%----------------------------------------------------------------------
+
+-define(DH_14_G, <<0,0,0,1,2>>).
+
+%%%----------------------------------------------------------------------
+%%% # DH_14_Q
+%%% Description: Group order (DH_14_P - 1) / 2.
+%%%----------------------------------------------------------------------
+
+-define(DH_14_Q,
+ <<000,000,000,128,127,255,255,255,255,255,255,255,228,135,237,081,
+ 016,180,097,026,098,099,049,069,192,110,014,104,148,129,039,004,
+ 069,051,230,058,001,005,223,083,029,137,205,145,040,165,004,060,
+ 199,026,002,110,247,202,140,217,230,157,033,141,152,021,133,054,
+ 249,047,138,027,167,240,154,182,182,168,225,034,242,066,218,187,
+ 049,047,063,099,122,038,033,116,211,027,246,181,133,255,174,091,
+ 122,003,091,246,247,028,053,253,173,068,207,210,215,079,146,008,
+ 190,037,143,243,036,148,051,040,246,115,041,192,255,255,255,255,
+ 255,255,255,255>>).
+
+-endif. % -ifdef(ssh_transport).
diff --git a/lib/ssh/src/ssh_userauth.hrl b/lib/ssh/src/ssh_userauth.hrl
new file mode 100755
index 0000000000..39cc032ca5
--- /dev/null
+++ b/lib/ssh/src/ssh_userauth.hrl
@@ -0,0 +1,77 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2009. 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%
+%%
+
+%%
+
+%%% Description: user authentication protocol
+
+-define(SSH_MSG_USERAUTH_REQUEST, 50).
+-define(SSH_MSG_USERAUTH_FAILURE, 51).
+-define(SSH_MSG_USERAUTH_SUCCESS, 52).
+-define(SSH_MSG_USERAUTH_BANNER, 53).
+-define(SSH_MSG_USERAUTH_PK_OK, 60).
+-define(SSH_MSG_USERAUTH_PASSWD_CHANGEREQ, 60).
+-define(SSH_MSG_USERAUTH_INFO_REQUEST, 60).
+-define(SSH_MSG_USERAUTH_INFO_RESPONSE, 61).
+
+-record(ssh_msg_userauth_request,
+ {
+ user, %% string
+ service, %% string
+ method, %% string "publickey", "password"
+ data %% opaque
+ }).
+
+-record(ssh_msg_userauth_failure,
+ {
+ authentications, %% string
+ partial_success %% boolean
+ }).
+
+-record(ssh_msg_userauth_success,
+ {
+ }).
+
+-record(ssh_msg_userauth_banner,
+ {
+ message, %% string
+ language %% string
+ }).
+
+-record(ssh_msg_userauth_passwd_changereq,
+ {
+ prompt, %% string
+ languge %% string
+ }).
+
+-record(ssh_msg_userauth_pk_ok,
+ {
+ algorithm_name, % string
+ key_blob % string
+ }).
+
+-record(ssh_msg_userauth_info_request,
+ {name,
+ instruction,
+ language_tag,
+ num_prompts,
+ data}).
+-record(ssh_msg_userauth_info_response,
+ {num_responses,
+ data}).
diff --git a/lib/ssh/src/ssh_userreg.erl b/lib/ssh/src/ssh_userreg.erl
new file mode 100644
index 0000000000..06f4076b51
--- /dev/null
+++ b/lib/ssh/src/ssh_userreg.erl
@@ -0,0 +1,127 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2009. 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%
+%%
+
+%%
+%% Description: User register for ssh_cli
+
+-module(ssh_userreg).
+
+-behaviour(gen_server).
+
+%% API
+-export([start_link/0, register_user/2, lookup_user/1]).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3]).
+
+-record(state, {user_db = []}).
+
+%%====================================================================
+%% API
+%%====================================================================
+%%--------------------------------------------------------------------
+%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
+%% Description: Starts the server
+%%--------------------------------------------------------------------
+start_link() ->
+ gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
+
+register_user(User, Cm) ->
+ gen_server:cast(?MODULE, {register, {User, Cm}}).
+
+lookup_user(Cm) ->
+ gen_server:call(?MODULE, {get_user, Cm}, infinity).
+
+%%====================================================================
+%% gen_server callbacks
+%%====================================================================
+
+%%--------------------------------------------------------------------
+%% Function: init(Args) -> {ok, State} |
+%% {ok, State, Timeout} |
+%% ignore |
+%% {stop, Reason}
+%% Description: Initiates the server
+%%--------------------------------------------------------------------
+init([]) ->
+ {ok, #state{}}.
+
+%%--------------------------------------------------------------------
+%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
+%% {reply, Reply, State, Timeout} |
+%% {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, Reply, State} |
+%% {stop, Reason, State}
+%% Description: Handling call messages
+%%--------------------------------------------------------------------
+handle_call({get_user, Cm}, _From, #state{user_db = Db} = State) ->
+ User = lookup(Cm, Db),
+ {reply, {ok, User}, State}.
+
+%%--------------------------------------------------------------------
+%% Function: handle_cast(Msg, State) -> {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State}
+%% Description: Handling cast messages
+%%--------------------------------------------------------------------
+handle_cast({register, UserCm}, State0) ->
+ State = insert(UserCm, State0),
+ {noreply, State}.
+
+%%--------------------------------------------------------------------
+%% Function: handle_info(Info, State) -> {noreply, State} |
+%% {noreply, State, Timeout} |
+%% {stop, Reason, State}
+%% Description: Handling all non call/cast messages
+%%--------------------------------------------------------------------
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+%%--------------------------------------------------------------------
+%% Function: terminate(Reason, State) -> void()
+%% Description: This function is called by a gen_server when it is about to
+%% terminate. It should be the opposite of Module:init/1 and do any necessary
+%% cleaning up. When it returns, the gen_server terminates with Reason.
+%% The return value is ignored.
+%%--------------------------------------------------------------------
+terminate(_Reason, _State) ->
+ ok.
+
+%%--------------------------------------------------------------------
+%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
+%% Description: Convert process state when code is changed
+%%--------------------------------------------------------------------
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+insert({User, Cm}, #state{user_db = Db} = State) ->
+ State#state{user_db = [{User, Cm} | Db]}.
+
+lookup(_, []) ->
+ undefined;
+lookup(Cm, [{User, Cm} | _Rest]) ->
+ User;
+lookup(Cm, [_ | Rest]) ->
+ lookup(Cm, Rest).
+
diff --git a/lib/ssh/src/ssh_xfer.erl b/lib/ssh/src/ssh_xfer.erl
new file mode 100644
index 0000000000..a347a9c095
--- /dev/null
+++ b/lib/ssh/src/ssh_xfer.erl
@@ -0,0 +1,925 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2009. 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%
+%%
+
+%%
+
+%%% Description: SFTP functions
+
+-module(ssh_xfer).
+
+-export([attach/2, connect/3]).
+-export([open/6, opendir/3, readdir/3, close/3, read/5, write/5,
+ rename/5, remove/3, mkdir/4, rmdir/3, realpath/3, extended/4,
+ stat/4, fstat/4, lstat/4, setstat/4,
+ readlink/3, fsetstat/4, symlink/4,
+ protocol_version_request/1,
+ xf_reply/2,
+ xf_send_reply/3, xf_send_names/3, xf_send_name/4,
+ xf_send_status/3, xf_send_status/4, xf_send_status/5,
+ xf_send_handle/3, xf_send_attr/3, xf_send_data/3,
+ encode_erlang_status/1,
+ decode_open_flags/2, encode_open_flags/1,
+ decode_ace_mask/1, decode_ext/1,
+ decode_ATTR/2, encode_ATTR/2]).
+
+-include("ssh.hrl").
+-include("ssh_xfer.hrl").
+
+-import(lists, [foldl/3, reverse/1]).
+
+-define(is_set(F, Bits),
+ ((F) band (Bits)) == (F)).
+
+-define(XFER_PACKET_SIZE, 32768).
+-define(XFER_WINDOW_SIZE, 4*?XFER_PACKET_SIZE).
+
+attach(CM, Opts) ->
+ open_xfer(CM, Opts).
+
+connect(Host, Port, Opts) ->
+ case ssh:connect(Host, Port, Opts) of
+ {ok, CM} -> open_xfer(CM, Opts);
+ Error -> Error
+ end.
+
+open_xfer(CM, Opts) ->
+ TMO = proplists:get_value(timeout, Opts, infinity),
+ case ssh_connection:session_channel(CM, ?XFER_WINDOW_SIZE, ?XFER_PACKET_SIZE, TMO) of
+ {ok, ChannelId} ->
+ {ok, ChannelId, CM};
+ Error ->
+ Error
+ end.
+
+protocol_version_request(XF) ->
+ xf_request(XF, ?SSH_FXP_INIT, <<?UINT32(?SSH_SFTP_PROTOCOL_VERSION)>>).
+
+open(XF, ReqID, FileName, Access, Flags, Attrs) ->
+ Vsn = XF#ssh_xfer.vsn,
+ FileName1 = list_to_binary(FileName),
+ MBits = if Vsn >= 5 ->
+ M = encode_ace_mask(Access),
+ ?uint32(M);
+ true ->
+ (<<>>)
+ end,
+ F = encode_open_flags(Flags),
+ xf_request(XF,?SSH_FXP_OPEN,
+ [?uint32(ReqID),
+ ?binary(FileName1),
+ MBits,
+ ?uint32(F),
+ encode_ATTR(Vsn,Attrs)]).
+
+opendir(XF, ReqID, DirName) ->
+ xf_request(XF, ?SSH_FXP_OPENDIR,
+ [?uint32(ReqID),
+ ?string(DirName)]).
+
+
+close(XF, ReqID, Handle) ->
+ xf_request(XF, ?SSH_FXP_CLOSE,
+ [?uint32(ReqID),
+ ?binary(Handle)]).
+
+read(XF, ReqID, Handle, Offset, Length) ->
+ xf_request(XF, ?SSH_FXP_READ,
+ [?uint32(ReqID),
+ ?binary(Handle),
+ ?uint64(Offset),
+ ?uint32(Length)]).
+
+readdir(XF, ReqID, Handle) ->
+ xf_request(XF, ?SSH_FXP_READDIR,
+ [?uint32(ReqID),
+ ?binary(Handle)]).
+
+write(XF,ReqID, Handle, Offset, Data) ->
+ Data1 = if
+ is_binary(Data) ->
+ Data;
+ is_list(Data) ->
+ list_to_binary(Data)
+ end,
+ xf_request(XF,?SSH_FXP_WRITE,
+ [?uint32(ReqID),
+ ?binary(Handle),
+ ?uint64(Offset),
+ ?binary(Data1)]).
+
+%% Remove a file
+remove(XF, ReqID, File) ->
+ xf_request(XF, ?SSH_FXP_REMOVE,
+ [?uint32(ReqID),
+ ?string(File)]).
+
+%% Rename a file/directory
+rename(XF, ReqID, Old, New, Flags) ->
+ Vsn = XF#ssh_xfer.vsn,
+ OldPath = list_to_binary(Old),
+ NewPath = list_to_binary(New),
+ FlagBits
+ = if Vsn >= 5 ->
+ F0 = encode_rename_flags(Flags),
+ ?uint32(F0);
+ true ->
+ (<<>>)
+ end,
+ xf_request(XF, ?SSH_FXP_RENAME,
+ [?uint32(ReqID),
+ ?binary(OldPath),
+ ?binary(NewPath),
+ FlagBits]).
+
+
+
+%% Create directory
+mkdir(XF, ReqID, Path, Attrs) ->
+ Path1 = list_to_binary(Path),
+ xf_request(XF, ?SSH_FXP_MKDIR,
+ [?uint32(ReqID),
+ ?binary(Path1),
+ encode_ATTR(XF#ssh_xfer.vsn, Attrs)]).
+
+%% Remove a directory
+rmdir(XF, ReqID, Dir) ->
+ Dir1 = list_to_binary(Dir),
+ xf_request(XF, ?SSH_FXP_RMDIR,
+ [?uint32(ReqID),
+ ?binary(Dir1)]).
+
+%% Stat file
+stat(XF, ReqID, Path, Flags) ->
+ Path1 = list_to_binary(Path),
+ Vsn = XF#ssh_xfer.vsn,
+ AttrFlags = if Vsn >= 5 ->
+ F = encode_attr_flags(Vsn, Flags),
+ ?uint32(F);
+ true ->
+ []
+ end,
+ xf_request(XF, ?SSH_FXP_STAT,
+ [?uint32(ReqID),
+ ?binary(Path1),
+ AttrFlags]).
+
+
+%% Stat file - follow symbolic links
+lstat(XF, ReqID, Path, Flags) ->
+ Path1 = list_to_binary(Path),
+ Vsn = XF#ssh_xfer.vsn,
+ AttrFlags = if Vsn >= 5 ->
+ F = encode_attr_flags(Vsn, Flags),
+ ?uint32(F);
+ true ->
+ []
+ end,
+ xf_request(XF, ?SSH_FXP_LSTAT,
+ [?uint32(ReqID),
+ ?binary(Path1),
+ AttrFlags]).
+
+%% Stat open file
+fstat(XF, ReqID, Handle, Flags) ->
+ Vsn = XF#ssh_xfer.vsn,
+ AttrFlags = if Vsn >= 5 ->
+ F = encode_attr_flags(Vsn, Flags),
+ ?uint32(F);
+ true ->
+ []
+ end,
+ xf_request(XF, ?SSH_FXP_FSTAT,
+ [?uint32(ReqID),
+ ?binary(Handle),
+ AttrFlags]).
+
+%% Modify file attributes
+setstat(XF, ReqID, Path, Attrs) ->
+ Path1 = list_to_binary(Path),
+ xf_request(XF, ?SSH_FXP_SETSTAT,
+ [?uint32(ReqID),
+ ?binary(Path1),
+ encode_ATTR(XF#ssh_xfer.vsn, Attrs)]).
+
+
+%% Modify file attributes
+fsetstat(XF, ReqID, Handle, Attrs) ->
+ xf_request(XF, ?SSH_FXP_FSETSTAT,
+ [?uint32(ReqID),
+ ?binary(Handle),
+ encode_ATTR(XF#ssh_xfer.vsn, Attrs)]).
+
+%% Read a symbolic link
+readlink(XF, ReqID, Path) ->
+ Path1 = list_to_binary(Path),
+ xf_request(XF, ?SSH_FXP_READLINK,
+ [?uint32(ReqID),
+ ?binary(Path1)]).
+
+
+%% Create a symbolic link
+symlink(XF, ReqID, LinkPath, TargetPath) ->
+ LinkPath1 = list_to_binary(LinkPath),
+ TargetPath1 = list_to_binary(TargetPath),
+ xf_request(XF, ?SSH_FXP_SYMLINK,
+ [?uint32(ReqID),
+ ?binary(LinkPath1),
+ ?binary(TargetPath1)]).
+
+%% Convert a path into a 'canonical' form
+realpath(XF, ReqID, Path) ->
+ Path1 = list_to_binary(Path),
+ xf_request(XF, ?SSH_FXP_REALPATH,
+ [?uint32(ReqID),
+ ?binary(Path1)]).
+
+extended(XF, ReqID, Request, Data) ->
+ xf_request(XF, ?SSH_FXP_EXTENDED,
+ [?uint32(ReqID),
+ ?string(Request),
+ ?binary(Data)]).
+
+
+%% Send xfer request to connection manager
+xf_request(XF, Op, Arg) ->
+ CM = XF#ssh_xfer.cm,
+ Channel = XF#ssh_xfer.channel,
+ Data = if
+ is_binary(Arg) ->
+ Arg;
+ is_list(Arg) ->
+ list_to_binary(Arg)
+ end,
+ Size = 1+size(Data),
+ ssh_connection:send(CM, Channel, <<?UINT32(Size), Op, Data/binary>>).
+
+xf_send_reply(#ssh_xfer{cm = CM, channel = Channel}, Op, Arg) ->
+ Data = if
+ is_binary(Arg) ->
+ Arg;
+ is_list(Arg) ->
+ list_to_binary(Arg)
+ end,
+ Size = 1 + size(Data),
+ ssh_connection:send(CM, Channel, <<?UINT32(Size), Op, Data/binary>>).
+
+xf_send_name(XF, ReqId, Name, Attr) ->
+ xf_send_names(XF, ReqId, [{Name, Attr}]).
+
+
+xf_send_handle(#ssh_xfer{cm = CM, channel = Channel},
+ ReqId, Handle) ->
+ HLen = length(Handle),
+ Size = 1 + 4 + 4+HLen,
+ ToSend = [<<?UINT32(Size), ?SSH_FXP_HANDLE, ?UINT32(ReqId), ?UINT32(HLen)>>,
+ Handle],
+ ssh_connection:send(CM, Channel, ToSend).
+
+xf_send_names(#ssh_xfer{cm = CM, channel = Channel, vsn = Vsn},
+ ReqId, NamesAndAttrs) ->
+ Count = length(NamesAndAttrs),
+ {Data, Len} = encode_names(Vsn, NamesAndAttrs),
+ Size = 1 + 4 + 4 + Len,
+ ToSend = [<<?UINT32(Size), ?SSH_FXP_NAME, ?UINT32(ReqId), ?UINT32(Count)>>,
+ Data],
+ %%?dbg(true, "xf_send_names: Size=~p size(ToSend)=~p\n",
+ %% [Size, size(list_to_binary(ToSend))]),
+ ssh_connection:send(CM, Channel, ToSend).
+
+xf_send_status(XF, ReqId, ErrorCode) ->
+ xf_send_status(XF, ReqId, ErrorCode, "").
+
+xf_send_status(XF, ReqId, ErrorCode, ErrorMsg) ->
+ xf_send_status(XF, ReqId, ErrorCode, ErrorMsg, <<>>).
+
+xf_send_status(#ssh_xfer{cm = CM, channel = Channel},
+ ReqId, ErrorCode, ErrorMsg, Data) ->
+ LangTag = "en",
+ ELen = length(ErrorMsg),
+ TLen = 2, %% length(LangTag),
+ Size = 1 + 4 + 4 + 4+ELen + 4+TLen + size(Data),
+ ToSend = [<<?UINT32(Size), ?SSH_FXP_STATUS, ?UINT32(ReqId),
+ ?UINT32(ErrorCode)>>,
+ <<?UINT32(ELen)>>, ErrorMsg,
+ <<?UINT32(TLen)>>, LangTag,
+ Data],
+ ssh_connection:send(CM, Channel, ToSend).
+
+xf_send_attr(#ssh_xfer{cm = CM, channel = Channel, vsn = Vsn}, ReqId, Attr) ->
+ EncAttr = encode_ATTR(Vsn, Attr),
+ ALen = size(EncAttr),
+ Size = 1 + 4 + ALen,
+ ToSend = [<<?UINT32(Size), ?SSH_FXP_ATTRS, ?UINT32(ReqId)>>, EncAttr],
+ ssh_connection:send(CM, Channel, ToSend).
+
+xf_send_data(#ssh_xfer{cm = CM, channel = Channel}, ReqId, Data) ->
+ DLen = size(Data),
+ Size = 1 + 4 + 4+DLen,
+ ToSend = [<<?UINT32(Size), ?SSH_FXP_DATA, ?UINT32(ReqId), ?UINT32(DLen)>>,
+ Data],
+ ssh_connection:send(CM, Channel, ToSend).
+
+xf_reply(_XF, << ?SSH_FXP_STATUS, ?UINT32(ReqID), ?UINT32(Status),
+ ?UINT32(ELen), Err:ELen/binary,
+ ?UINT32(LLen), Lang:LLen/binary,
+ Reply/binary >> ) ->
+ Stat = decode_status(Status),
+ {status, ReqID, {Stat,binary_to_list(Err),binary_to_list(Lang),
+ Reply}};
+xf_reply(_XF, << ?SSH_FXP_STATUS, ?UINT32(ReqID), ?UINT32(Status)>> ) ->
+ Stat = decode_status(Status),
+ {status, ReqID, {Stat,"","",<<>>}};
+xf_reply(_XF, <<?SSH_FXP_HANDLE, ?UINT32(ReqID),
+ ?UINT32(HLen), Handle:HLen/binary>>) ->
+ {handle, ReqID, Handle};
+xf_reply(_XF, <<?SSH_FXP_DATA, ?UINT32(ReqID),
+ ?UINT32(DLen), Data:DLen/binary>>) ->
+ {data, ReqID, Data};
+xf_reply(XF, <<?SSH_FXP_NAME, ?UINT32(ReqID),
+ ?UINT32(Count), AData/binary>>) ->
+ %%?dbg(true, "xf_reply ?SSH_FXP_NAME: AData=~p\n", [AData]),
+ {name, ReqID, decode_names(XF#ssh_xfer.vsn, Count, AData)};
+xf_reply(XF, <<?SSH_FXP_ATTRS, ?UINT32(ReqID),
+ AData/binary>>) ->
+ {A, _} = decode_ATTR(XF#ssh_xfer.vsn, AData),
+ {attrs, ReqID, A};
+xf_reply(_XF, <<?SSH_FXP_EXTENDED_REPLY, ?UINT32(ReqID),
+ RData>>) ->
+ {extended_reply, ReqID, RData}.
+
+
+
+decode_status(Status) ->
+ case Status of
+ ?SSH_FX_OK -> ok;
+ ?SSH_FX_EOF -> eof;
+ ?SSH_FX_NO_SUCH_FILE -> no_such_file;
+ ?SSH_FX_PERMISSION_DENIED -> permission_denied;
+ ?SSH_FX_FAILURE -> failure;
+ ?SSH_FX_BAD_MESSAGE -> bad_message;
+ ?SSH_FX_NO_CONNECTION -> no_connection;
+ ?SSH_FX_CONNECTION_LOST -> connection_lost;
+ ?SSH_FX_OP_UNSUPPORTED -> op_unsupported;
+ ?SSH_FX_INVALID_HANDLE -> invalid_handle;
+ ?SSH_FX_NO_SUCH_PATH -> no_such_path;
+ ?SSH_FX_FILE_ALREADY_EXISTS -> file_already_exists;
+ ?SSH_FX_WRITE_PROTECT -> write_protect;
+ ?SSH_FX_NO_MEDIA -> no_media;
+ ?SSH_FX_NO_SPACE_ON_FILESYSTEM -> no_space_on_filesystem;
+ ?SSH_FX_QUOTA_EXCEEDED -> quota_exceeded;
+ ?SSH_FX_UNKNOWN_PRINCIPLE -> unknown_principle;
+ ?SSH_FX_LOCK_CONFlICT -> lock_conflict;
+ ?SSH_FX_NOT_A_DIRECTORY -> not_a_directory;
+ _ -> {error,Status}
+ end.
+
+encode_erlang_status(Status) ->
+ case Status of
+ ok -> ?SSH_FX_OK;
+ eof -> ?SSH_FX_EOF;
+ enoent -> ?SSH_FX_NO_SUCH_FILE;
+ eacces -> ?SSH_FX_PERMISSION_DENIED;
+ _ -> ?SSH_FX_FAILURE
+ end.
+
+decode_ext(<<?UINT32(NameLen), Name:NameLen/binary,
+ ?UINT32(DataLen), Data:DataLen/binary,
+ Tail/binary>>) ->
+ [{binary_to_list(Name), binary_to_list(Data)}
+ | decode_ext(Tail)];
+decode_ext(<<>>) ->
+ [].
+
+%%
+%% Encode rename flags
+%%
+encode_rename_flags(Flags) ->
+ encode_bits(
+ fun(overwrite) -> ?SSH_FXP_RENAME_OVERWRITE;
+ (atomic) -> ?SSH_FXP_RENAME_ATOMIC;
+ (native) -> ?SSH_FXP_RENAME_NATIVE
+ end, Flags).
+
+%% decode_rename_flags(F) ->
+%% decode_bits(F,
+%% [{?SSH_FXP_RENAME_OVERWRITE, overwrite},
+%% {?SSH_FXP_RENAME_ATOMIC, atomic},
+%% {?SSH_FXP_RENAME_NATIVE, native}]).
+
+
+encode_open_flags(Flags) ->
+ encode_bits(
+ fun (read) -> ?SSH_FXF_READ;
+ (write) -> ?SSH_FXF_WRITE;
+ (append) -> ?SSH_FXF_APPEND;
+ (creat) -> ?SSH_FXF_CREAT;
+ (trunc) -> ?SSH_FXF_TRUNC;
+ (excl) -> ?SSH_FXF_EXCL;
+ (create_new) -> ?SSH_FXF_CREATE_NEW;
+ (create_truncate) -> ?SSH_FXF_CREATE_TRUNCATE;
+ (open_existing) -> ?SSH_FXF_OPEN_EXISTING;
+ (open_or_create) -> ?SSH_FXF_OPEN_OR_CREATE;
+ (truncate_existing) -> ?SSH_FXF_TRUNCATE_EXISTING;
+ (append_data) -> ?SSH_FXF_ACCESS_APPEND_DATA;
+ (append_data_atomic) -> ?SSH_FXF_ACCESS_APPEND_DATA_ATOMIC;
+ (text_mode) -> ?SSH_FXF_ACCESS_TEXT_MODE;
+ (read_lock) -> ?SSH_FXF_ACCESS_READ_LOCK;
+ (write_lock) -> ?SSH_FXF_ACCESS_WRITE_LOCK;
+ (delete_lock) -> ?SSH_FXF_ACCESS_DELETE_LOCK
+ end, Flags).
+
+encode_ace_mask(Access) ->
+ encode_bits(
+ fun(read_data) -> ?ACE4_READ_DATA;
+ (list_directory) -> ?ACE4_LIST_DIRECTORY;
+ (write_data) -> ?ACE4_WRITE_DATA;
+ (add_file) -> ?ACE4_ADD_FILE;
+ (append_data) -> ?ACE4_APPEND_DATA;
+ (add_subdirectory) -> ?ACE4_ADD_SUBDIRECTORY;
+ (read_named_attrs) -> ?ACE4_READ_NAMED_ATTRS;
+ (write_named_attrs) -> ?ACE4_WRITE_NAMED_ATTRS;
+ (execute) -> ?ACE4_EXECUTE;
+ (delete_child) -> ?ACE4_DELETE_CHILD;
+ (read_attributes) -> ?ACE4_READ_ATTRIBUTES;
+ (write_attributes) -> ?ACE4_WRITE_ATTRIBUTES;
+ (delete) -> ?ACE4_DELETE;
+ (read_acl) -> ?ACE4_READ_ACL;
+ (write_acl) -> ?ACE4_WRITE_ACL;
+ (write_owner) -> ?ACE4_WRITE_OWNER;
+ (synchronize) -> ?ACE4_SYNCHRONIZE
+ end, Access).
+
+decode_ace_mask(F) ->
+ decode_bits(F,
+ [
+ {?ACE4_READ_DATA, read_data},
+ {?ACE4_LIST_DIRECTORY, list_directory},
+ {?ACE4_WRITE_DATA, write_data},
+ {?ACE4_ADD_FILE, add_file},
+ {?ACE4_APPEND_DATA, append_data},
+ {?ACE4_ADD_SUBDIRECTORY, add_subdirectory},
+ {?ACE4_READ_NAMED_ATTRS, read_named_attrs},
+ {?ACE4_WRITE_NAMED_ATTRS, write_named_attrs},
+ {?ACE4_EXECUTE, execute},
+ {?ACE4_DELETE_CHILD, delete_child},
+ {?ACE4_READ_ATTRIBUTES, read_attributes},
+ {?ACE4_WRITE_ATTRIBUTES, write_attributes},
+ {?ACE4_DELETE, delete},
+ {?ACE4_READ_ACL, read_acl},
+ {?ACE4_WRITE_ACL, write_acl},
+ {?ACE4_WRITE_OWNER, write_owner},
+ {?ACE4_SYNCHRONIZE, synchronize}
+ ]).
+
+decode_open_flags(Vsn, F) when Vsn =< 3 ->
+ decode_bits(F,
+ [
+ {?SSH_FXF_READ, read},
+ {?SSH_FXF_WRITE, write},
+ {?SSH_FXF_APPEND, append},
+ {?SSH_FXF_CREAT, creat},
+ {?SSH_FXF_TRUNC, trunc},
+ {?SSH_FXF_EXCL, excl}
+ ]);
+decode_open_flags(Vsn, F) when Vsn >= 4 ->
+ R = decode_bits(F,
+ [
+ {?SSH_FXF_ACCESS_APPEND_DATA, append_data},
+ {?SSH_FXF_ACCESS_APPEND_DATA_ATOMIC, append_data_atomic},
+ {?SSH_FXF_ACCESS_TEXT_MODE, text_mode},
+ {?SSH_FXF_ACCESS_READ_LOCK, read_lock},
+ {?SSH_FXF_ACCESS_WRITE_LOCK, write_lock},
+ {?SSH_FXF_ACCESS_DELETE_LOCK, delete_lock}
+ ]),
+ AD = case F band ?SSH_FXF_ACCESS_DISPOSITION of
+ ?SSH_FXF_CREATE_NEW -> create_new;
+ ?SSH_FXF_CREATE_TRUNCATE -> create_truncate;
+ ?SSH_FXF_OPEN_EXISTING -> open_existing;
+ ?SSH_FXF_OPEN_OR_CREATE -> open_or_create;
+ ?SSH_FXF_TRUNCATE_EXISTING -> truncate_existing
+ end,
+ [AD | R].
+
+encode_ace_type(Type) ->
+ case Type of
+ access_allowed -> ?ACE4_ACCESS_ALLOWED_ACE_TYPE;
+ access_denied -> ?ACE4_ACCESS_DENIED_ACE_TYPE;
+ system_audit -> ?ACE4_SYSTEM_AUDIT_ACE_TYPE;
+ system_alarm -> ?ACE4_SYSTEM_ALARM_ACE_TYPE
+ end.
+
+decode_ace_type(F) ->
+ case F of
+ ?ACE4_ACCESS_ALLOWED_ACE_TYPE -> access_allowed;
+ ?ACE4_ACCESS_DENIED_ACE_TYPE -> access_denied;
+ ?ACE4_SYSTEM_AUDIT_ACE_TYPE -> system_audit;
+ ?ACE4_SYSTEM_ALARM_ACE_TYPE -> system_alarm
+ end.
+
+encode_ace_flag(Flag) ->
+ encode_bits(
+ fun(file_inherit) -> ?ACE4_FILE_INHERIT_ACE;
+ (directory_inherit) -> ?ACE4_DIRECTORY_INHERIT_ACE;
+ (no_propagte_inherit) -> ?ACE4_NO_PROPAGATE_INHERIT_ACE;
+ (inherit_only) -> ?ACE4_INHERIT_ONLY_ACE;
+ (successful_access) -> ?ACE4_SUCCESSFUL_ACCESS_ACE_FLAG;
+ (failed_access) -> ?ACE4_FAILED_ACCESS_ACE_FLAG;
+ (identifier_group) -> ?ACE4_IDENTIFIER_GROUP
+ end, Flag).
+
+decode_ace_flag(F) ->
+ decode_bits(F,
+ [
+ {?ACE4_FILE_INHERIT_ACE, file_inherit},
+ {?ACE4_DIRECTORY_INHERIT_ACE, directory_inherit},
+ {?ACE4_NO_PROPAGATE_INHERIT_ACE, no_propagte_inherit},
+ {?ACE4_INHERIT_ONLY_ACE, inherit_only},
+ {?ACE4_SUCCESSFUL_ACCESS_ACE_FLAG, successful_access},
+ {?ACE4_FAILED_ACCESS_ACE_FLAG, failed_access},
+ {?ACE4_IDENTIFIER_GROUP, identifier_group}
+ ]).
+
+encode_attr_flags(Vsn, all) ->
+ encode_attr_flags(Vsn,
+ [size, uidgid, permissions,
+ acmodtime, accesstime, createtime,
+ modifytime, acl, ownergroup, subsecond_times,
+ bits, extended]);
+encode_attr_flags(Vsn, Flags) ->
+ encode_bits(
+ fun(size) -> ?SSH_FILEXFER_ATTR_SIZE;
+ (uidgid) when Vsn =<3 -> ?SSH_FILEXFER_ATTR_UIDGID;
+ (permissions) -> ?SSH_FILEXFER_ATTR_PERMISSIONS;
+ (acmodtime) when Vsn =< 3 -> ?SSH_FILEXFER_ATTR_ACMODTIME;
+ (accesstime) when Vsn >= 5 -> ?SSH_FILEXFER_ATTR_ACCESSTIME;
+ (createtime) when Vsn >= 5 -> ?SSH_FILEXFER_ATTR_CREATETIME;
+ (modifytime) when Vsn >= 5 -> ?SSH_FILEXFER_ATTR_MODIFYTIME;
+ (acl) when Vsn >= 5 -> ?SSH_FILEXFER_ATTR_ACL;
+ (ownergroup) when Vsn >= 5 -> ?SSH_FILEXFER_ATTR_OWNERGROUP;
+ (subsecond_times) when Vsn >= 5 -> ?SSH_FILEXFER_ATTR_SUBSECOND_TIMES;
+ (bits) when Vsn >= 5 -> ?SSH_FILEXFER_ATTR_BITS;
+ (extended) when Vsn >= 5 -> ?SSH_FILEXFER_ATTR_EXTENDED;
+ (_) -> 0
+ end, Flags).
+
+encode_file_type(Type) ->
+ %%?dbg(true, "encode_file_type(~p)\n", [Type]),
+ case Type of
+ regular -> ?SSH_FILEXFER_TYPE_REGULAR;
+ directory -> ?SSH_FILEXFER_TYPE_DIRECTORY;
+ symlink -> ?SSH_FILEXFER_TYPE_SYMLINK;
+ special -> ?SSH_FILEXFER_TYPE_SPECIAL;
+ unknown -> ?SSH_FILEXFER_TYPE_UNKNOWN;
+ other -> ?SSH_FILEXFER_TYPE_UNKNOWN;
+ socket -> ?SSH_FILEXFER_TYPE_SOCKET;
+ char_device -> ?SSH_FILEXFER_TYPE_CHAR_DEVICE;
+ block_device -> ?SSH_FILEXFER_TYPE_BLOCK_DEVICE;
+ fifo -> ?SSH_FILEXFER_TYPE_FIFO;
+ undefined -> ?SSH_FILEXFER_TYPE_UNKNOWN
+ end.
+
+decode_file_type(Type) ->
+ case Type of
+ ?SSH_FILEXFER_TYPE_REGULAR -> regular;
+ ?SSH_FILEXFER_TYPE_DIRECTORY -> directory;
+ ?SSH_FILEXFER_TYPE_SYMLINK -> symlink;
+ ?SSH_FILEXFER_TYPE_SPECIAL -> special;
+ ?SSH_FILEXFER_TYPE_UNKNOWN -> other; % unknown
+ ?SSH_FILEXFER_TYPE_SOCKET -> socket;
+ ?SSH_FILEXFER_TYPE_CHAR_DEVICE -> char_device;
+ ?SSH_FILEXFER_TYPE_BLOCK_DEVICE -> block_device;
+ ?SSH_FILEXFER_TYPE_FIFO -> fifo
+ end.
+
+encode_attrib_bits(Bits) ->
+ encode_bits(
+ fun(readonly) -> ?SSH_FILEXFER_ATTR_FLAGS_READONLY;
+ (system) -> ?SSH_FILEXFER_ATTR_FLAGS_SYSTEM;
+ (hidden) -> ?SSH_FILEXFER_ATTR_FLAGS_HIDDEN;
+ (case_insensitive) -> ?SSH_FILEXFER_ATTR_FLAGS_CASE_INSENSITIVE;
+ (arcive) -> ?SSH_FILEXFER_ATTR_FLAGS_ARCHIVE;
+ (encrypted) -> ?SSH_FILEXFER_ATTR_FLAGS_ENCRYPTED;
+ (compressed) -> ?SSH_FILEXFER_ATTR_FLAGS_COMPRESSED;
+ (sparse) -> ?SSH_FILEXFER_ATTR_FLAGS_SPARSE;
+ (append_only) -> ?SSH_FILEXFER_ATTR_FLAGS_APPEND_ONLY;
+ (immutable) -> ?SSH_FILEXFER_ATTR_FLAGS_IMMUTABLE;
+ (sync) -> ?SSH_FILEXFER_ATTR_FLAGS_SYNC
+ end, Bits).
+
+decode_attrib_bits(F) ->
+ decode_bits(F,
+ [{?SSH_FILEXFER_ATTR_FLAGS_READONLY, readonly},
+ {?SSH_FILEXFER_ATTR_FLAGS_SYSTEM, system},
+ {?SSH_FILEXFER_ATTR_FLAGS_HIDDEN, hidden},
+ {?SSH_FILEXFER_ATTR_FLAGS_CASE_INSENSITIVE, case_insensitive},
+ {?SSH_FILEXFER_ATTR_FLAGS_ARCHIVE, arcive},
+ {?SSH_FILEXFER_ATTR_FLAGS_ENCRYPTED, encrypted},
+ {?SSH_FILEXFER_ATTR_FLAGS_COMPRESSED, compressed},
+ {?SSH_FILEXFER_ATTR_FLAGS_SPARSE, sparse},
+ {?SSH_FILEXFER_ATTR_FLAGS_APPEND_ONLY, append_only},
+ {?SSH_FILEXFER_ATTR_FLAGS_IMMUTABLE, immutable},
+ {?SSH_FILEXFER_ATTR_FLAGS_SYNC, sync}]).
+
+
+%%
+%% Encode file attributes
+%%
+encode_ATTR(Vsn, A) ->
+ {Flags,As} =
+ encode_As(Vsn,
+ [{size, A#ssh_xfer_attr.size},
+ {ownergroup, A#ssh_xfer_attr.owner},
+ {ownergroup, A#ssh_xfer_attr.group},
+ {permissions, A#ssh_xfer_attr.permissions},
+ {acmodtime, A#ssh_xfer_attr.atime},
+ {acmodtime, A#ssh_xfer_attr.mtime},
+ {accesstime, A#ssh_xfer_attr.atime},
+ {subsecond_times, A#ssh_xfer_attr.atime_nseconds},
+ {createtime, A#ssh_xfer_attr.createtime},
+ {subsecond_times, A#ssh_xfer_attr.createtime_nseconds},
+ {modifytime, A#ssh_xfer_attr.mtime},
+ {subsecond_times, A#ssh_xfer_attr.mtime_nseconds},
+ {acl, A#ssh_xfer_attr.acl},
+ {bits, A#ssh_xfer_attr.attrib_bits},
+ {extended, A#ssh_xfer_attr.extensions}],
+ 0, []),
+ Type = encode_file_type(A#ssh_xfer_attr.type),
+ %%?dbg(true, "encode_ATTR: Vsn=~p A=~p As=~p Flags=~p Type=~p",
+ %% [Vsn, A, As, Flags, Type]),
+ Result = list_to_binary([?uint32(Flags),
+ if Vsn >= 5 ->
+ ?byte(Type);
+ true ->
+ (<<>>)
+ end, As]),
+ %% ?dbg(true, " Result=~p\n", [Result]),
+ Result.
+
+
+encode_As(Vsn, [{_AName, undefined}|As], Flags, Acc) ->
+ encode_As(Vsn, As, Flags, Acc);
+encode_As(Vsn, [{AName, X}|As], Flags, Acc) ->
+ case AName of
+ size ->
+ encode_As(Vsn, As,Flags bor ?SSH_FILEXFER_ATTR_SIZE,
+ [?uint64(X) | Acc]);
+ ownergroup when Vsn=<4 ->
+ encode_As(Vsn, As,Flags bor ?SSH_FILEXFER_ATTR_UIDGID,
+ [?uint32(X) | Acc]);
+ ownergroup when Vsn>=5 ->
+ X1 = list_to_binary(integer_to_list(X)), % TODO: check owner and group
+ encode_As(Vsn, As,Flags bor ?SSH_FILEXFER_ATTR_OWNERGROUP,
+ [?binary(X1) | Acc]);
+ permissions ->
+ encode_As(Vsn, As,Flags bor ?SSH_FILEXFER_ATTR_PERMISSIONS,
+ [?uint32(X) | Acc]);
+ acmodtime when Vsn=<3 ->
+ encode_As(Vsn, As,Flags bor ?SSH_FILEXFER_ATTR_ACMODTIME,
+ [?uint32(X) | Acc]);
+ accesstime when Vsn>=5 ->
+ encode_As(Vsn, As, Flags bor ?SSH_FILEXFER_ATTR_ACCESSTIME,
+ [?uint64(X) | Acc]);
+ createtime when Vsn>=5->
+ encode_As(Vsn, As, Flags bor ?SSH_FILEXFER_ATTR_CREATETIME,
+ [?uint64(X) | Acc]);
+ modifytime when Vsn>=5 ->
+ encode_As(Vsn, As, Flags bor ?SSH_FILEXFER_ATTR_MODIFYTIME,
+ [?uint64(X) | Acc]);
+ subsecond_times when Vsn>=5 ->
+ encode_As(Vsn, As, Flags bor ?SSH_FILEXFER_ATTR_SUBSECOND_TIMES,
+ [?uint64(X) | Acc]);
+ acl when Vsn >=5 ->
+ encode_As(Vsn, As, Flags bor ?SSH_FILEXFER_ATTR_ACL,
+ [encode_acl(X) | Acc]);
+ bits when Vsn>=5 ->
+ F = encode_attrib_bits(X),
+ encode_As(Vsn, As, Flags bor ?SSH_FILEXFER_ATTR_BITS,
+ [?uint32(F) | Acc]);
+ extended ->
+ encode_As(Vsn, As, Flags bor ?SSH_FILEXFER_ATTR_EXTENDED,
+ [encode_extensions(X) | Acc]);
+ _ ->
+ encode_As(Vsn, As, Flags, Acc)
+ end;
+encode_As(_Vsn, [], Flags, Acc) ->
+ {Flags, reverse(Acc)}.
+
+
+decode_ATTR(Vsn, <<?UINT32(Flags), Tail/binary>>) ->
+ %%?dbg(true, "decode_ATTR: Vsn=~p Flags=~p Tail=~p\n", [Vsn, Flags, Tail]),
+ {Type,Tail2} =
+ if Vsn =< 3 ->
+ {?SSH_FILEXFER_TYPE_UNKNOWN, Tail};
+ Vsn >= 5 ->
+ <<?BYTE(T), TL/binary>> = Tail,
+ {T, TL}
+ end,
+ decode_As(Vsn,
+ [{size, #ssh_xfer_attr.size},
+ {ownergroup, #ssh_xfer_attr.owner},
+ {ownergroup, #ssh_xfer_attr.group},
+ {permissions, #ssh_xfer_attr.permissions},
+ {acmodtime, #ssh_xfer_attr.atime},
+ {acmodtime, #ssh_xfer_attr.mtime},
+ {accesstime, #ssh_xfer_attr.atime},
+ {subsecond_times, #ssh_xfer_attr.atime_nseconds},
+ {createtime, #ssh_xfer_attr.createtime},
+ {subsecond_times, #ssh_xfer_attr.createtime_nseconds},
+ {modifytime, #ssh_xfer_attr.mtime},
+ {subsecond_times, #ssh_xfer_attr.mtime_nseconds},
+ {acl, #ssh_xfer_attr.acl},
+ {bits, #ssh_xfer_attr.attrib_bits},
+ {extended, #ssh_xfer_attr.extensions}],
+ #ssh_xfer_attr { type = decode_file_type(Type) },
+ Flags,
+ Tail2).
+
+decode_As(Vsn, [{AName, AField}|As], R, Flags, Tail) ->
+ %%?dbg(false, "decode_As: Vsn=~p AName=~p AField=~p Flags=~p Tail=~p\n", [Vsn, AName, AField, Flags, Tail]),
+ case AName of
+ size when ?is_set(?SSH_FILEXFER_ATTR_SIZE, Flags) ->
+ <<?UINT64(X), Tail2/binary>> = Tail,
+ decode_As(Vsn, As, setelement(AField, R, X), Flags, Tail2);
+ ownergroup when ?is_set(?SSH_FILEXFER_ATTR_UIDGID, Flags),Vsn=<3 ->
+ <<?UINT32(X), Tail2/binary>> = Tail,
+ decode_As(Vsn, As, setelement(AField, R, X), Flags, Tail2);
+ ownergroup when ?is_set(?SSH_FILEXFER_ATTR_OWNERGROUP, Flags),Vsn>=5 ->
+ <<?UINT32(Len), Bin:Len/binary, Tail2/binary>> = Tail,
+ X = binary_to_list(Bin),
+ %%?dbg(true, "ownergroup X=~p\n", [X]),
+ decode_As(Vsn, As, setelement(AField, R, X), Flags, Tail2);
+
+ permissions when ?is_set(?SSH_FILEXFER_ATTR_PERMISSIONS,Flags),Vsn>=5->
+ <<?UINT32(X), Tail2/binary>> = Tail,
+ decode_As(Vsn, As, setelement(AField, R, X), Flags, Tail2);
+
+ permissions when ?is_set(?SSH_FILEXFER_ATTR_PERMISSIONS,Flags),Vsn=<3->
+ <<?UINT32(X), Tail2/binary>> = Tail,
+ R1 = setelement(AField, R, X),
+ Type = case X band ?S_IFMT of
+ ?S_IFDIR -> directory;
+ ?S_IFCHR -> char_device;
+ ?S_IFBLK -> block_device;
+ ?S_IFIFO -> fifi;
+ ?S_IFREG -> regular;
+ ?S_IFSOCK -> socket;
+ ?S_IFLNK -> symlink;
+ _ -> unknown
+ end,
+ decode_As(Vsn, As, R1#ssh_xfer_attr { type=Type}, Flags, Tail2);
+
+ acmodtime when ?is_set(?SSH_FILEXFER_ATTR_ACMODTIME,Flags),Vsn=<3 ->
+ <<?UINT32(X), Tail2/binary>> = Tail,
+ decode_As(Vsn, As, setelement(AField, R, X), Flags, Tail2);
+ accesstime when ?is_set(?SSH_FILEXFER_ATTR_ACCESSTIME,Flags),Vsn>=5 ->
+ <<?UINT64(X), Tail2/binary>> = Tail,
+ decode_As(Vsn, As, setelement(AField, R, X), Flags, Tail2);
+ modifytime when ?is_set(?SSH_FILEXFER_ATTR_MODIFYTIME,Flags),Vsn>=5 ->
+ <<?UINT64(X), Tail2/binary>> = Tail,
+ decode_As(Vsn, As, setelement(AField, R, X), Flags, Tail2);
+ createtime when ?is_set(?SSH_FILEXFER_ATTR_CREATETIME,Flags),Vsn>=5 ->
+ <<?UINT64(X), Tail2/binary>> = Tail,
+ decode_As(Vsn, As, setelement(AField, R, X), Flags, Tail2);
+ subsecond_times when ?is_set(?SSH_FILEXFER_ATTR_SUBSECOND_TIMES,Flags),Vsn>=5 ->
+ <<?UINT32(X), Tail2/binary>> = Tail,
+ decode_As(Vsn, As, setelement(AField, R, X), Flags, Tail2);
+ acl when ?is_set(?SSH_FILEXFER_ATTR_ACL, Flags), Vsn>=5 ->
+ {X,Tail2} = decode_acl(Tail),
+ decode_As(Vsn, As, setelement(AField, R, X), Flags, Tail2);
+ bits when ?is_set(?SSH_FILEXFER_ATTR_BITS, Flags), Vsn >=5 ->
+ <<?UINT32(Y), Tail2/binary>> = Tail,
+ X = decode_attrib_bits(Y),
+ decode_As(Vsn, As, setelement(AField, R, X), Flags, Tail2);
+ extended when ?is_set(?SSH_FILEXFER_ATTR_EXTENDED, Flags) ->
+ {X,Tail2} = decode_extended(Tail),
+ decode_As(Vsn, As, setelement(AField, R, X), Flags, Tail2);
+ _ ->
+ decode_As(Vsn, As, R, Flags, Tail)
+ end;
+decode_As(_Vsn, [], R, _, Tail) ->
+ {R, Tail}.
+
+
+
+
+decode_names(_Vsn, 0, _Data) ->
+ [];
+decode_names(Vsn, I, <<?UINT32(Len), FileName:Len/binary,
+ ?UINT32(LLen), _LongName:LLen/binary,
+ Tail/binary>>) when Vsn =< 3 ->
+ Name = binary_to_list(FileName),
+ %%?dbg(true, "decode_names: ~p\n", [Name]),
+ {A, Tail2} = decode_ATTR(Vsn, Tail),
+ [{Name, A} | decode_names(Vsn, I-1, Tail2)];
+decode_names(Vsn, I, <<?UINT32(Len), FileName:Len/binary,
+ Tail/binary>>) when Vsn >= 4 ->
+ Name = binary_to_list(FileName),
+ %%?dbg(true, "decode_names: ~p\n", [Name]),
+ {A, Tail2} = decode_ATTR(Vsn, Tail),
+ [{Name, A} | decode_names(Vsn, I-1, Tail2)].
+
+encode_names(Vsn, NamesAndAttrs) ->
+ lists:mapfoldl(fun(N, L) -> encode_name(Vsn, N, L) end, 0, NamesAndAttrs).
+
+encode_name(Vsn, {Name,Attr}, Len) when Vsn =< 3 ->
+ NLen = length(Name),
+ %%?dbg(true, "encode_name: Vsn=~p Name=~p Attr=~p\n",
+ %% [Vsn, Name, Attr]),
+ EncAttr = encode_ATTR(Vsn, Attr),
+ ALen = size(EncAttr),
+ NewLen = Len + NLen*2 + 4 + 4 + ALen,
+ {[<<?UINT32(NLen)>>, Name, <<?UINT32(NLen)>>, Name, EncAttr], NewLen};
+encode_name(Vsn, {Name,Attr}, Len) when Vsn >= 4 ->
+ NLen = length(Name),
+ EncAttr = encode_ATTR(Vsn, Attr),
+ ALen = size(EncAttr),
+ {[<<?UINT32(NLen)>>, Name, EncAttr],
+ Len + 4 + NLen + ALen}.
+
+encode_acl(ACLList) ->
+ Count = length(ACLList),
+ [?uint32(Count) | encode_acl_items(ACLList)].
+
+encode_acl_items([ACE|As]) ->
+ Type = encode_ace_type(ACE#ssh_xfer_ace.type),
+ Flag = encode_ace_flag(ACE#ssh_xfer_ace.flag),
+ Mask = encode_ace_mask(ACE#ssh_xfer_ace.mask),
+ Who = list_to_binary(ACE#ssh_xfer_ace.who),
+ [?uint32(Type), ?uint32(Flag), ?uint32(Mask),
+ ?binary(Who) | encode_acl_items(As)];
+encode_acl_items([]) ->
+ [].
+
+
+decode_acl(<<?UINT32(Count), Tail/binary>>) ->
+ decode_acl_items(Count, Tail, []).
+
+decode_acl_items(0, Tail, Acc) ->
+ {reverse(Acc), Tail};
+decode_acl_items(I, <<?UINT32(Type),
+ ?UINT32(Flag),
+ ?UINT32(Mask),
+ ?UINT32(WLen), BWho:WLen/binary,
+ Tail/binary>>, Acc) ->
+ decode_acl_items(I-1, Tail,
+ [#ssh_xfer_ace { type = decode_ace_type(Type),
+ flag = decode_ace_flag(Flag),
+ mask = decode_ace_mask(Mask),
+ who = binary_to_list(BWho)} | Acc]).
+
+encode_extensions(Exts) ->
+ Count = length(Exts),
+ [?uint32(Count) | encode_ext(Exts)].
+
+encode_ext([{Type, Data} | Exts]) ->
+ [?string(Type), ?string(Data) | encode_ext(Exts)];
+encode_ext([]) ->
+ [].
+
+
+decode_extended(<<?UINT32(Count), Tail/binary>>) ->
+ decode_ext(Count, Tail, []).
+
+decode_ext(0, Tail, Acc) ->
+ {reverse(Acc), Tail};
+decode_ext(I, <<?UINT32(TLen), Type:TLen/binary,
+ ?UINT32(DLen), Data:DLen/binary,
+ Tail/binary>>, Acc) ->
+ decode_ext(I-1, Tail, [{binary_to_list(Type), Data}|Acc]).
+
+
+
+%% Encode bit encoded flags
+encode_bits(Fun, BitNames) ->
+ encode_bits(Fun, 0, BitNames).
+
+encode_bits(Fun, F, [Bit|BitNames]) ->
+ encode_bits(Fun, Fun(Bit) bor F, BitNames);
+encode_bits(_Fun, F, []) ->
+ F.
+
+%% Decode bit encoded flags
+decode_bits(F, [{Bit,BitName}|Bits]) ->
+ if F band Bit == Bit ->
+ [BitName | decode_bits(F, Bits)];
+ true ->
+ decode_bits(F, Bits)
+ end;
+decode_bits(_F, []) ->
+ [].
diff --git a/lib/ssh/src/ssh_xfer.hrl b/lib/ssh/src/ssh_xfer.hrl
new file mode 100755
index 0000000000..f32ec5f774
--- /dev/null
+++ b/lib/ssh/src/ssh_xfer.hrl
@@ -0,0 +1,251 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2009. 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%
+%%
+
+%%
+
+%%% Description: SFTP defines
+-define(SSH_SFTP_PROTOCOL_VERSION, 6).
+%%%----------------------------------------------------------------------
+%%% # SSH_FXP_xxx
+%%% Description: Request and initialization packet types for file transfer
+%%% protocol.
+%%%----------------------------------------------------------------------
+-define(SSH_FXP_INIT, 1).
+-define(SSH_FXP_VERSION, 2).
+-define(SSH_FXP_OPEN, 3).
+-define(SSH_FXP_CLOSE, 4).
+-define(SSH_FXP_READ, 5).
+-define(SSH_FXP_WRITE, 6).
+-define(SSH_FXP_LSTAT, 7).
+-define(SSH_FXP_FSTAT, 8).
+-define(SSH_FXP_SETSTAT, 9).
+-define(SSH_FXP_FSETSTAT, 10).
+-define(SSH_FXP_OPENDIR, 11).
+-define(SSH_FXP_READDIR, 12).
+-define(SSH_FXP_REMOVE, 13).
+-define(SSH_FXP_MKDIR, 14).
+-define(SSH_FXP_RMDIR, 15).
+-define(SSH_FXP_REALPATH, 16).
+-define(SSH_FXP_STAT, 17).
+-define(SSH_FXP_RENAME, 18).
+-define(SSH_FXP_READLINK, 19).
+-define(SSH_FXP_SYMLINK, 20).
+-define(SSH_FXP_STATUS, 101).
+-define(SSH_FXP_HANDLE, 102).
+-define(SSH_FXP_DATA, 103).
+-define(SSH_FXP_NAME, 104).
+-define(SSH_FXP_ATTRS, 105).
+-define(SSH_FXP_EXTENDED, 200).
+-define(SSH_FXP_EXTENDED_REPLY, 201).
+
+%%%----------------------------------------------------------------------
+%%% # 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).
+-define(SSH_FX_PERMISSION_DENIED, 3).
+-define(SSH_FX_FAILURE, 4).
+-define(SSH_FX_BAD_MESSAGE, 5).
+-define(SSH_FX_NO_CONNECTION, 6).
+-define(SSH_FX_CONNECTION_LOST, 7).
+-define(SSH_FX_OP_UNSUPPORTED, 8).
+-define(SSH_FX_INVALID_HANDLE, 9).
+-define(SSH_FX_NO_SUCH_PATH, 10).
+-define(SSH_FX_FILE_ALREADY_EXISTS, 11).
+-define(SSH_FX_WRITE_PROTECT, 12).
+-define(SSH_FX_NO_MEDIA, 13).
+-define(SSH_FX_NO_SPACE_ON_FILESYSTEM, 14).
+-define(SSH_FX_QUOTA_EXCEEDED, 15).
+-define(SSH_FX_UNKNOWN_PRINCIPLE, 16).
+-define(SSH_FX_LOCK_CONFlICT, 17).
+-define(SSH_FX_DIR_NOT_EMPTY, 18).
+-define(SSH_FX_NOT_A_DIRECTORY, 19).
+-define(SSH_FX_FILE_IS_A_DIRECTORY, 24).
+
+%%%----------------------------------------------------------------------
+%%% # SSH_FILEXFER_xxx
+%%% Description: Bits for file attributes bit mask
+%%%----------------------------------------------------------------------
+-define(SSH_FILEXFER_ATTR_SIZE, 16#00000001). %% vsn 3,5
+-define(SSH_FILEXFER_ATTR_UIDGID, 16#00000002). %% vsn 3
+-define(SSH_FILEXFER_ATTR_PERMISSIONS, 16#00000004). %% vsn 3,5
+-define(SSH_FILEXFER_ATTR_ACCESSTIME, 16#00000008). %% vsn 5
+-define(SSH_FILEXFER_ATTR_ACMODTIME, 16#00000008). %% vsn 3
+-define(SSH_FILEXFER_ATTR_CREATETIME, 16#00000010). %% vsn 5
+-define(SSH_FILEXFER_ATTR_MODIFYTIME, 16#00000020) .%% vsn 5
+-define(SSH_FILEXFER_ATTR_ACL, 16#00000040). %% vsn 5
+-define(SSH_FILEXFER_ATTR_OWNERGROUP, 16#00000080). %% vsn 5
+-define(SSH_FILEXFER_ATTR_SUBSECOND_TIMES, 16#00000100). %% vsn 5
+-define(SSH_FILEXFER_ATTR_BITS, 16#00000200). %% vsn 5
+-define(SSH_FILEXFER_ATTR_EXTENDED, 16#80000000). %% vsn 3,5
+
+%% File types
+-define(SSH_FILEXFER_TYPE_REGULAR, 1).
+-define(SSH_FILEXFER_TYPE_DIRECTORY, 2).
+-define(SSH_FILEXFER_TYPE_SYMLINK, 3).
+-define(SSH_FILEXFER_TYPE_SPECIAL, 4).
+-define(SSH_FILEXFER_TYPE_UNKNOWN, 5).
+-define(SSH_FILEXFER_TYPE_SOCKET, 6).
+-define(SSH_FILEXFER_TYPE_CHAR_DEVICE, 7).
+-define(SSH_FILEXFER_TYPE_BLOCK_DEVICE, 8).
+-define(SSH_FILEXFER_TYPE_FIFO, 9).
+
+%% Permissions
+-define(S_IRUSR, 8#0000400).
+-define(S_IWUSR, 8#0000200).
+-define(S_IXUSR, 8#0000100).
+-define(S_IRGRP, 8#0000040).
+-define(S_IWGRP, 8#0000020).
+-define(S_IXGRP, 8#0000010).
+-define(S_IROTH, 8#0000004).
+-define(S_IWOTH, 8#0000002).
+-define(S_IXOTH, 8#0000001).
+-define(S_ISUID, 8#0004000).
+-define(S_ISGID, 8#0002000).
+-define(S_ISVTX, 8#0001000).
+%% type bits (version 3 only?)
+-define(S_IFMT, 8#0170000). %% file type mask
+-define(S_IFDIR, 8#0040000).
+-define(S_IFCHR, 8#0020000).
+-define(S_IFBLK, 8#0060000).
+-define(S_IFIFO, 8#0010000).
+-define(S_IFREG, 8#0100000).
+-define(S_IFLNK, 8#0120000).
+-define(S_IFSOCK, 8#0140000).
+
+%% ACE-Type
+-define(ACE4_ACCESS_ALLOWED_ACE_TYPE, 16#00000000).
+-define(ACE4_ACCESS_DENIED_ACE_TYPE, 16#00000001).
+-define(ACE4_SYSTEM_AUDIT_ACE_TYPE, 16#00000002).
+-define(ACE4_SYSTEM_ALARM_ACE_TYPE, 16#00000003).
+
+%% ACE-Flag
+-define(ACE4_FILE_INHERIT_ACE, 16#00000001).
+-define(ACE4_DIRECTORY_INHERIT_ACE, 16#00000002).
+-define(ACE4_NO_PROPAGATE_INHERIT_ACE, 16#00000004).
+-define(ACE4_INHERIT_ONLY_ACE, 16#00000008).
+-define(ACE4_SUCCESSFUL_ACCESS_ACE_FLAG, 16#00000010).
+-define(ACE4_FAILED_ACCESS_ACE_FLAG, 16#00000020).
+-define(ACE4_IDENTIFIER_GROUP, 16#00000040).
+
+%% ACE-Mask
+-define(ACE4_READ_DATA, 16#00000001).
+-define(ACE4_LIST_DIRECTORY, 16#00000001).
+-define(ACE4_WRITE_DATA, 16#00000002).
+-define(ACE4_ADD_FILE, 16#00000002).
+-define(ACE4_APPEND_DATA, 16#00000004).
+-define(ACE4_ADD_SUBDIRECTORY, 16#00000004).
+-define(ACE4_READ_NAMED_ATTRS, 16#00000008).
+-define(ACE4_WRITE_NAMED_ATTRS, 16#00000010).
+-define(ACE4_EXECUTE, 16#00000020).
+-define(ACE4_DELETE_CHILD, 16#00000040).
+-define(ACE4_READ_ATTRIBUTES, 16#00000080).
+-define(ACE4_WRITE_ATTRIBUTES, 16#00000100).
+-define(ACE4_DELETE, 16#00010000).
+-define(ACE4_READ_ACL, 16#00020000).
+-define(ACE4_WRITE_ACL, 16#00040000).
+-define(ACE4_WRITE_OWNER, 16#00080000).
+-define(ACE4_SYNCHRONIZE, 16#00100000).
+
+%% Attrib-bits
+-define(SSH_FILEXFER_ATTR_FLAGS_READONLY, 16#00000001).
+-define(SSH_FILEXFER_ATTR_FLAGS_SYSTEM, 16#00000002).
+-define(SSH_FILEXFER_ATTR_FLAGS_HIDDEN, 16#00000004).
+-define(SSH_FILEXFER_ATTR_FLAGS_CASE_INSENSITIVE, 16#00000008).
+-define(SSH_FILEXFER_ATTR_FLAGS_ARCHIVE, 16#00000010).
+-define(SSH_FILEXFER_ATTR_FLAGS_ENCRYPTED, 16#00000020).
+-define(SSH_FILEXFER_ATTR_FLAGS_COMPRESSED, 16#00000040).
+-define(SSH_FILEXFER_ATTR_FLAGS_SPARSE, 16#00000080).
+-define(SSH_FILEXFER_ATTR_FLAGS_APPEND_ONLY, 16#00000100).
+-define(SSH_FILEXFER_ATTR_FLAGS_IMMUTABLE, 16#00000200).
+-define(SSH_FILEXFER_ATTR_FLAGS_SYNC, 16#00000400).
+
+%% Open flags (version 3)
+-define(SSH_FXF_READ, 16#00000001).
+-define(SSH_FXF_WRITE, 16#00000002).
+-define(SSH_FXF_APPEND, 16#00000004).
+-define(SSH_FXF_CREAT, 16#00000008).
+-define(SSH_FXF_TRUNC, 16#00000010).
+-define(SSH_FXF_EXCL, 16#00000020).
+
+%% Open flags (version 5)
+-define(SSH_FXF_ACCESS_DISPOSITION, 16#00000007).
+-define(SSH_FXF_CREATE_NEW, 16#00000000).
+-define(SSH_FXF_CREATE_TRUNCATE, 16#00000001).
+-define(SSH_FXF_OPEN_EXISTING, 16#00000002).
+-define(SSH_FXF_OPEN_OR_CREATE, 16#00000003).
+-define(SSH_FXF_TRUNCATE_EXISTING, 16#00000004).
+-define(SSH_FXF_ACCESS_APPEND_DATA, 16#00000008).
+-define(SSH_FXF_ACCESS_APPEND_DATA_ATOMIC, 16#00000010).
+-define(SSH_FXF_ACCESS_TEXT_MODE, 16#00000020).
+-define(SSH_FXF_ACCESS_READ_LOCK, 16#00000040).
+-define(SSH_FXF_ACCESS_WRITE_LOCK, 16#00000080).
+-define(SSH_FXF_ACCESS_DELETE_LOCK, 16#00000100).
+
+%% Rename flags
+-define(SSH_FXP_RENAME_OVERWRITE, 16#00000001).
+-define(SSH_FXP_RENAME_ATOMIC, 16#00000002).
+-define(SSH_FXP_RENAME_NATIVE, 16#00000004).
+
+
+-define(SSH_FILEXFER_LARGEFILESIZE, (1 bsl 63)).
+
+-record(ssh_xfer_attr,
+ {
+ type, %% regular, dirctory, symlink, ...
+ size,
+ owner,
+ group,
+ permissions,
+ atime,
+ atime_nseconds,
+ createtime,
+ createtime_nseconds,
+ mtime,
+ mtime_nseconds,
+ acl,
+ attrib_bits,
+ extensions %% list of [{type,data}]
+ }).
+
+-record(ssh_xfer_ace,
+ {
+ type,
+ flag,
+ mask,
+ who
+ }).
+
+%% connection endpoint and server/client info
+-record(ssh_xfer,
+ {
+ vsn, %% server version
+ ext, %% server extensions
+ cm, %% connection mgr
+ channel %% SFTP channel
+ }).
+
+
+
+
+
+
diff --git a/lib/ssh/src/sshc_sup.erl b/lib/ssh/src/sshc_sup.erl
new file mode 100644
index 0000000000..265d1a1cd6
--- /dev/null
+++ b/lib/ssh/src/sshc_sup.erl
@@ -0,0 +1,65 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2009. 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%
+%%
+
+%%
+%%----------------------------------------------------------------------
+%% Purpose: The ssh client subsystem supervisor
+%%----------------------------------------------------------------------
+
+-module(sshc_sup).
+
+-behaviour(supervisor).
+
+-export([start_link/1, start_child/1]).
+
+%% Supervisor callback
+-export([init/1]).
+
+%%%=========================================================================
+%%% API
+%%%=========================================================================
+start_link(Args) ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, [Args]).
+
+start_child(Args) ->
+ supervisor:start_child(?MODULE, Args).
+
+%%%=========================================================================
+%%% Supervisor callback
+%%%=========================================================================
+init(Args) ->
+ RestartStrategy = simple_one_for_one,
+ MaxR = 10,
+ MaxT = 3600,
+ {ok, {{RestartStrategy, MaxR, MaxT}, [child_spec(Args)]}}.
+
+%%%=========================================================================
+%%% Internal functions
+%%%=========================================================================
+child_spec(_) ->
+ Name = undefined, % As simple_one_for_one is used.
+ StartFunc = {ssh_connection_controler, start_link, []},
+ Restart = temporary,
+% Shutdown = infinity,
+ Shutdown = 5000,
+ Modules = [ssh_connection_controler],
+% Type = supervisor,
+ Type = worker,
+ {Name, StartFunc, Restart, Shutdown, Type, Modules}.
+
diff --git a/lib/ssh/src/sshd_sup.erl b/lib/ssh/src/sshd_sup.erl
new file mode 100644
index 0000000000..9c9ba5958c
--- /dev/null
+++ b/lib/ssh/src/sshd_sup.erl
@@ -0,0 +1,111 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2008-2009. 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%
+%%
+%%
+%%----------------------------------------------------------------------
+%% Purpose: The top supervisor for ssh servers hangs under
+%% ssh_sup.
+%%----------------------------------------------------------------------
+
+-module(sshd_sup).
+
+-behaviour(supervisor).
+
+-export([start_link/1, start_child/1, stop_child/1,
+ stop_child/2, system_name/1]).
+
+%% Supervisor callback
+-export([init/1]).
+
+%%%=========================================================================
+%%% API
+%%%=========================================================================
+start_link(Servers) ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, [Servers]).
+
+start_child(ServerOpts) ->
+ Address = proplists:get_value(address, ServerOpts),
+ Port = proplists:get_value(port, ServerOpts),
+ case ssh_system_sup:system_supervisor(Address, Port) of
+ undefined ->
+ Spec = child_spec(Address, Port, ServerOpts),
+ case supervisor:start_child(?MODULE, Spec) of
+ {error, already_present} ->
+ Name = id(Address, Port),
+ supervisor:delete_child(?MODULE, Name),
+ supervisor:start_child(?MODULE, Spec);
+ Reply ->
+ Reply
+ end;
+ Pid ->
+ AccPid = ssh_system_sup:acceptor_supervisor(Pid),
+ ssh_acceptor_sup:start_child(AccPid, ServerOpts)
+ end.
+
+stop_child(Name) ->
+ case supervisor:terminate_child(?MODULE, Name) of
+ ok ->
+ supervisor:delete_child(?MODULE, Name);
+ Error ->
+ Error
+ end.
+
+stop_child(Address, Port) ->
+ Name = id(Address, Port),
+ stop_child(Name).
+
+system_name(SysSup) ->
+ Children = supervisor:which_children(sshd_sup),
+ system_name(SysSup, Children).
+
+%%%=========================================================================
+%%% Supervisor callback
+%%%=========================================================================
+init([Servers]) ->
+ RestartStrategy = one_for_one,
+ MaxR = 10,
+ MaxT = 3600,
+ Fun = fun(ServerOpts) ->
+ Address = proplists:get_value(address, ServerOpts),
+ Port = proplists:get_value(port, ServerOpts),
+ child_spec(Address, Port, ServerOpts)
+ end,
+ Children = lists:map(Fun, Servers),
+ {ok, {{RestartStrategy, MaxR, MaxT}, Children}}.
+
+%%%=========================================================================
+%%% Internal functions
+%%%=========================================================================
+child_spec(Address, Port, ServerOpts) ->
+ Name = id(Address, Port),
+ StartFunc = {ssh_system_sup, start_link, [ServerOpts]},
+ Restart = transient,
+ Shutdown = infinity,
+ Modules = [ssh_system_sup],
+ Type = supervisor,
+ {Name, StartFunc, Restart, Shutdown, Type, Modules}.
+
+id(Address, Port) ->
+ {server, ssh_system_sup, Address, Port}.
+
+system_name([], _ ) ->
+ undefined;
+system_name(SysSup, [{Name, SysSup, _, _} | _]) ->
+ Name;
+system_name(SysSup, [_ | Rest]) ->
+ system_name(SysSup, Rest).