aboutsummaryrefslogtreecommitdiffstats
path: root/lib/ssh
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ssh')
-rw-r--r--lib/ssh/doc/src/book.xml4
-rw-r--r--lib/ssh/doc/src/fascicules.xml2
-rw-r--r--lib/ssh/doc/src/introduction.xml2
-rw-r--r--lib/ssh/doc/src/notes.xml72
-rw-r--r--lib/ssh/doc/src/part_notes.xml4
-rw-r--r--lib/ssh/doc/src/ref_man.xml4
-rw-r--r--lib/ssh/doc/src/ssh.xml4
-rw-r--r--lib/ssh/doc/src/ssh_app.xml4
-rw-r--r--lib/ssh/doc/src/ssh_channel.xml2
-rw-r--r--lib/ssh/doc/src/ssh_client_key_api.xml2
-rw-r--r--lib/ssh/doc/src/ssh_connection.xml2
-rw-r--r--lib/ssh/doc/src/ssh_protocol.xml4
-rw-r--r--lib/ssh/doc/src/ssh_server_key_api.xml2
-rw-r--r--lib/ssh/doc/src/ssh_sftp.xml4
-rw-r--r--lib/ssh/doc/src/ssh_sftpd.xml2
-rw-r--r--lib/ssh/doc/src/usersguide.xml4
-rw-r--r--lib/ssh/doc/src/using_ssh.xml2
-rw-r--r--lib/ssh/src/ssh.hrl1
-rw-r--r--lib/ssh/src/ssh_acceptor_sup.erl6
-rw-r--r--lib/ssh/src/ssh_auth.erl7
-rw-r--r--lib/ssh/src/ssh_bits.erl4
-rw-r--r--lib/ssh/src/ssh_cli.erl17
-rw-r--r--lib/ssh/src/ssh_connection.erl29
-rw-r--r--lib/ssh/src/ssh_connection_handler.erl27
-rw-r--r--lib/ssh/src/ssh_io.erl5
-rw-r--r--lib/ssh/src/ssh_message.erl4
-rw-r--r--lib/ssh/src/ssh_no_io.erl10
-rw-r--r--lib/ssh/src/ssh_sftp.erl7
-rw-r--r--lib/ssh/src/ssh_sftpd.erl18
-rw-r--r--lib/ssh/src/ssh_system_sup.erl15
-rw-r--r--lib/ssh/src/ssh_xfer.erl57
-rw-r--r--lib/ssh/src/sshd_sup.erl11
-rw-r--r--lib/ssh/test/ssh_basic_SUITE.erl38
-rw-r--r--lib/ssh/test/ssh_test_lib.erl16
-rw-r--r--lib/ssh/test/ssh_unicode_SUITE.erl590
-rw-r--r--lib/ssh/test/ssh_unicode_SUITE_data/sftp.txt1
-rw-r--r--lib/ssh/test/ssh_unicode_SUITE_data/sftp瑞点.txt1
-rw-r--r--lib/ssh/test/ssh_unicode_SUITE_data/ssh_host_dsa_key13
-rw-r--r--lib/ssh/test/ssh_unicode_SUITE_data/ssh_host_dsa_key.pub11
-rw-r--r--lib/ssh/vsn.mk2
40 files changed, 892 insertions, 118 deletions
diff --git a/lib/ssh/doc/src/book.xml b/lib/ssh/doc/src/book.xml
index 3c2375f96d..c031d872d7 100644
--- a/lib/ssh/doc/src/book.xml
+++ b/lib/ssh/doc/src/book.xml
@@ -1,10 +1,10 @@
-<?xml version="1.0" encoding="iso-8859-1" ?>
+<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE book SYSTEM "book.dtd">
<book xmlns:xi="http://www.w3.org/2001/XInclude">
<header titlestyle="normal">
<copyright>
- <year>2005</year><year>2012</year>
+ <year>2005</year><year>2013</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
diff --git a/lib/ssh/doc/src/fascicules.xml b/lib/ssh/doc/src/fascicules.xml
index 069d9002e0..7e99398c16 100644
--- a/lib/ssh/doc/src/fascicules.xml
+++ b/lib/ssh/doc/src/fascicules.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="iso-8859-1" ?>
+<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE fascicules SYSTEM "fascicules.dtd">
<fascicules>
diff --git a/lib/ssh/doc/src/introduction.xml b/lib/ssh/doc/src/introduction.xml
index e76aa20d64..b42910cb34 100644
--- a/lib/ssh/doc/src/introduction.xml
+++ b/lib/ssh/doc/src/introduction.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="iso-8859-1" ?>
+<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE chapter SYSTEM "chapter.dtd">
<chapter>
diff --git a/lib/ssh/doc/src/notes.xml b/lib/ssh/doc/src/notes.xml
index 041f5e54af..0d88cbda7a 100644
--- a/lib/ssh/doc/src/notes.xml
+++ b/lib/ssh/doc/src/notes.xml
@@ -29,6 +29,78 @@
<file>notes.xml</file>
</header>
+<section><title>Ssh 3.0</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ The ssh cli is now faster at close and before new prompt.</p>
+ <p>
+ Own Id: OTP-11339 Aux Id: seq12423 </p>
+ </item>
+ <item>
+ <p>
+ Ssh process structure was redesigned to better map to
+ what is truly parallel this has solved a lot of strange
+ timing issues that sometimes would occur, for instance a
+ process leak could happen when a lot of connections where
+ taken up and down in parallel in a short period of time.
+ Also backwards compatible clauses to "original" but never
+ supported features has been removed.</p>
+ <p>
+ Impact: Increases flow efficiency</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-11363</p>
+ </item>
+ <item>
+ <p>
+ Fix various typos in erts, kernel and ssh. Thanks to
+ Martin Hässler.</p>
+ <p>
+ Own Id: OTP-11414</p>
+ </item>
+ <item>
+ <p>
+ Correct private_key type documentation in
+ ssh_server_key_api. Thanks to Tristan Sloughter.</p>
+ <p>
+ Own Id: OTP-11449</p>
+ </item>
+ <item>
+ <p>
+ The functions in ssh_no_io.erl did not mimic the
+ functions in ssh_io.erl correctly, the arity was
+ incorrect for some functions which caused ssh to fail in
+ the wrong way.</p>
+ <p>
+ Own Id: OTP-11490</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Add option to disallow CLI</p>
+ <p>
+ Own Id: OTP-10976</p>
+ </item>
+ <item>
+ <p>
+ Add sockname and user to ssh:connection_info/2</p>
+ <p>
+ Own Id: OTP-11296</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Ssh 2.1.8</title>
<section><title>Improvements and New Features</title>
diff --git a/lib/ssh/doc/src/part_notes.xml b/lib/ssh/doc/src/part_notes.xml
index 1b47a12021..c5cc163717 100644
--- a/lib/ssh/doc/src/part_notes.xml
+++ b/lib/ssh/doc/src/part_notes.xml
@@ -1,10 +1,10 @@
-<?xml version="1.0" encoding="latin1" ?>
+<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE part SYSTEM "part.dtd">
<part xmlns:xi="http://www.w3.org/2001/XInclude">
<header>
<copyright>
- <year>2004</year><year>2010</year>
+ <year>2004</year><year>2013</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
diff --git a/lib/ssh/doc/src/ref_man.xml b/lib/ssh/doc/src/ref_man.xml
index 88203b5034..55339298e8 100644
--- a/lib/ssh/doc/src/ref_man.xml
+++ b/lib/ssh/doc/src/ref_man.xml
@@ -1,10 +1,10 @@
-<?xml version="1.0" encoding="iso-8859-1" ?>
+<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE application SYSTEM "application.dtd">
<application xmlns:xi="http://www.w3.org/2001/XInclude">
<header>
<copyright>
- <year>2004</year><year>2012</year>
+ <year>2004</year><year>2013</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
diff --git a/lib/ssh/doc/src/ssh.xml b/lib/ssh/doc/src/ssh.xml
index c1a51d57fc..89d8be850e 100644
--- a/lib/ssh/doc/src/ssh.xml
+++ b/lib/ssh/doc/src/ssh.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="iso-8859-1" ?>
+<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE erlref SYSTEM "erlref.dtd">
<erlref>
@@ -38,6 +38,8 @@
<item>Supported SSH version is 2.0 </item>
<item>Supported MAC algorithms: hmac-sha1</item>
<item>Supported encryption algorithms: aes128-cb and 3des-cbc</item>
+ <item>Supports unicode filenames if the emulator and the underlaying OS supports it. See the DESCRIPTION section in <seealso marker="file">file</seealso> for information about this subject</item>
+ <item>Supports unicode in shell and cli</item>
</list>
</section>
diff --git a/lib/ssh/doc/src/ssh_app.xml b/lib/ssh/doc/src/ssh_app.xml
index c01f44936a..a1d2402790 100644
--- a/lib/ssh/doc/src/ssh_app.xml
+++ b/lib/ssh/doc/src/ssh_app.xml
@@ -1,10 +1,10 @@
-<?xml version="1.0" encoding="iso-8859-1" ?>
+<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE appref SYSTEM "appref.dtd">
<appref>
<header>
<copyright>
- <year>2012</year>
+ <year>2012</year><year>2013</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
diff --git a/lib/ssh/doc/src/ssh_channel.xml b/lib/ssh/doc/src/ssh_channel.xml
index 66b3b8b656..a52a6a115e 100644
--- a/lib/ssh/doc/src/ssh_channel.xml
+++ b/lib/ssh/doc/src/ssh_channel.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="iso-8859-1" ?>
+<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE erlref SYSTEM "erlref.dtd">
<erlref>
diff --git a/lib/ssh/doc/src/ssh_client_key_api.xml b/lib/ssh/doc/src/ssh_client_key_api.xml
index 2fa06f8bf1..f3d05a8980 100644
--- a/lib/ssh/doc/src/ssh_client_key_api.xml
+++ b/lib/ssh/doc/src/ssh_client_key_api.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="iso-8859-1" ?>
+<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE erlref SYSTEM "erlref.dtd">
<erlref>
diff --git a/lib/ssh/doc/src/ssh_connection.xml b/lib/ssh/doc/src/ssh_connection.xml
index efd4865a6f..72e7252536 100644
--- a/lib/ssh/doc/src/ssh_connection.xml
+++ b/lib/ssh/doc/src/ssh_connection.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="iso-8859-1" ?>
+<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE erlref SYSTEM "erlref.dtd">
<erlref>
diff --git a/lib/ssh/doc/src/ssh_protocol.xml b/lib/ssh/doc/src/ssh_protocol.xml
index 28f42f5707..7288266cf7 100644
--- a/lib/ssh/doc/src/ssh_protocol.xml
+++ b/lib/ssh/doc/src/ssh_protocol.xml
@@ -1,10 +1,10 @@
-<?xml version="1.0" encoding="iso-8859-1" ?>
+<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE chapter SYSTEM "chapter.dtd">
<!-- %EricssonCopyright% -->
<chapter>
<header>
<copyright>
- <year>2013</year>
+ <year>2013</year><year>2013</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
diff --git a/lib/ssh/doc/src/ssh_server_key_api.xml b/lib/ssh/doc/src/ssh_server_key_api.xml
index ee537f2f60..f7133e4ba5 100644
--- a/lib/ssh/doc/src/ssh_server_key_api.xml
+++ b/lib/ssh/doc/src/ssh_server_key_api.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="iso-8859-1" ?>
+<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE erlref SYSTEM "erlref.dtd">
<erlref>
diff --git a/lib/ssh/doc/src/ssh_sftp.xml b/lib/ssh/doc/src/ssh_sftp.xml
index 0d61e57edb..e55d092fe2 100644
--- a/lib/ssh/doc/src/ssh_sftp.xml
+++ b/lib/ssh/doc/src/ssh_sftp.xml
@@ -1,10 +1,10 @@
-<?xml version="1.0" encoding="iso-8859-1" ?>
+<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE erlref SYSTEM "erlref.dtd">
<erlref>
<header>
<copyright>
- <year>2005</year><year>2012</year>
+ <year>2005</year><year>2013</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
diff --git a/lib/ssh/doc/src/ssh_sftpd.xml b/lib/ssh/doc/src/ssh_sftpd.xml
index a73d6e52d4..81c2acc575 100644
--- a/lib/ssh/doc/src/ssh_sftpd.xml
+++ b/lib/ssh/doc/src/ssh_sftpd.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="iso-8859-1" ?>
+<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE erlref SYSTEM "erlref.dtd">
<erlref>
diff --git a/lib/ssh/doc/src/usersguide.xml b/lib/ssh/doc/src/usersguide.xml
index c818003090..8ab14c2945 100644
--- a/lib/ssh/doc/src/usersguide.xml
+++ b/lib/ssh/doc/src/usersguide.xml
@@ -1,10 +1,10 @@
-<?xml version="1.0" encoding="iso-8859-1" ?>
+<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE part SYSTEM "part.dtd">
<part xmlns:xi="http://www.w3.org/2001/XInclude">
<header>
<copyright>
- <year>2012</year>
+ <year>2012</year><year>2013</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
diff --git a/lib/ssh/doc/src/using_ssh.xml b/lib/ssh/doc/src/using_ssh.xml
index c994c1c56c..4d73366f5e 100644
--- a/lib/ssh/doc/src/using_ssh.xml
+++ b/lib/ssh/doc/src/using_ssh.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="iso-8859-1" ?>
+<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE chapter SYSTEM "chapter.dtd">
<chapter>
diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl
index 94ced9da6f..0c4d34f89c 100644
--- a/lib/ssh/src/ssh.hrl
+++ b/lib/ssh/src/ssh.hrl
@@ -54,6 +54,7 @@
-define(uint32(X), << ?UINT32(X) >> ).
-define(uint64(X), << ?UINT64(X) >> ).
-define(string(X), << ?STRING(list_to_binary(X)) >> ).
+-define(string_utf8(X), << ?STRING(unicode:characters_to_binary(X)) >> ).
-define(binary(X), << ?STRING(X) >>).
-define(SSH_CIPHER_NONE, 0).
diff --git a/lib/ssh/src/ssh_acceptor_sup.erl b/lib/ssh/src/ssh_acceptor_sup.erl
index f37e1fe4ff..2be729d305 100644
--- a/lib/ssh/src/ssh_acceptor_sup.erl
+++ b/lib/ssh/src/ssh_acceptor_sup.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2010. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2013. 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
@@ -84,8 +84,8 @@ child_spec(ServerOpts) ->
[{active, false},
{reuseaddr, true}] ++ SocketOpts,
ServerOpts, Timeout]},
- Restart = permanent,
- Shutdown = 3600,
+ Restart = transient,
+ Shutdown = brutal_kill,
Modules = [ssh_acceptor],
Type = worker,
{Name, StartFunc, Restart, Shutdown, Type, Modules}.
diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl
index 1fa3df847f..409a1db6d5 100644
--- a/lib/ssh/src/ssh_auth.erl
+++ b/lib/ssh/src/ssh_auth.erl
@@ -83,7 +83,7 @@ password_msg([#ssh{opts = Opts, io_cb = IoCb,
method = "password",
data =
<<?BOOLEAN(?FALSE),
- ?STRING(list_to_binary(Password))>>},
+ ?STRING(unicode:characters_to_binary(Password))>>},
Ssh)
end.
@@ -190,8 +190,7 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User,
data = Data}, _,
#ssh{opts = Opts} = Ssh) ->
<<_:8, ?UINT32(Sz), BinPwd:Sz/binary>> = Data,
- Password = binary_to_list(BinPwd),
-
+ Password = unicode:characters_to_list(BinPwd),
case check_password(User, Password, Opts) of
true ->
{authorized, User,
@@ -352,7 +351,7 @@ verify_sig(SessionId, User, Service, Alg, KeyBlob, SigWLen, Opts) ->
build_sig_data(SessionId, User, Service, KeyBlob, Alg) ->
Sig = [?binary(SessionId),
?SSH_MSG_USERAUTH_REQUEST,
- ?string(User),
+ ?string_utf8(User),
?string(Service),
?binary(<<"publickey">>),
?TRUE,
diff --git a/lib/ssh/src/ssh_bits.erl b/lib/ssh/src/ssh_bits.erl
index 2b0241cb83..8aaff93b9f 100644
--- a/lib/ssh/src/ssh_bits.erl
+++ b/lib/ssh/src/ssh_bits.erl
@@ -116,6 +116,10 @@ enc(Xs, [string|Ts], Offset) ->
X0 = hd(Xs),
Y = ?string(X0),
[Y | enc(tl(Xs),Ts,Offset+size(Y))];
+enc(Xs, [string_utf8|Ts], Offset) ->
+ X0 = hd(Xs),
+ Y = ?string_utf8(X0),
+ [Y | enc(tl(Xs),Ts,Offset+size(Y))];
enc(Xs, [binary|Ts], Offset) ->
X0 = hd(Xs),
Y = ?binary(X0),
diff --git a/lib/ssh/src/ssh_cli.erl b/lib/ssh/src/ssh_cli.erl
index 5cb1e133d3..41febf9707 100644
--- a/lib/ssh/src/ssh_cli.erl
+++ b/lib/ssh/src/ssh_cli.erl
@@ -161,6 +161,21 @@ handle_msg({ssh_channel_up, ChannelId, ConnectionHandler},
cm = ConnectionHandler} = State) ->
{ok, State};
+handle_msg({Group, set_unicode_state, _Arg}, State) ->
+ Group ! {self(), set_unicode_state, false},
+ {ok, State};
+
+handle_msg({Group, get_unicode_state}, State) ->
+ Group ! {self(), get_unicode_state, false},
+ {ok, State};
+
+handle_msg({Group, tty_geometry}, #state{group = Group,
+ pty = #ssh_pty{width=Width,
+ height=Height}
+ } = State) ->
+ Group ! {self(),tty_geometry,{Width,Height}},
+ {ok,State};
+
handle_msg({Group, Req}, #state{group = Group, buf = Buf, pty = Pty,
cm = ConnectionHandler,
channel = ChannelId} = State) ->
@@ -334,7 +349,7 @@ delete_chars(N, {Buf, BufTail, Col}, Tty) when N > 0 ->
{Buf, NewBufTail, Col}};
delete_chars(N, {Buf, BufTail, Col}, Tty) -> % N < 0
NewBuf = nthtail(-N, Buf),
- NewCol = Col + N,
+ NewCol = case Col + N of V when V >= 0 -> V; _ -> 0 end,
M1 = move_cursor(Col, NewCol, Tty),
M2 = move_cursor(NewCol + length(BufTail) - N, NewCol, Tty),
{[M1, BufTail, lists:duplicate(-N, $ ) | M2],
diff --git a/lib/ssh/src/ssh_connection.erl b/lib/ssh/src/ssh_connection.erl
index 7016f349e8..b377614949 100644
--- a/lib/ssh/src/ssh_connection.erl
+++ b/lib/ssh/src/ssh_connection.erl
@@ -271,10 +271,36 @@ cancel_tcpip_forward(ConnectionHandler, BindIP, Port) ->
%%--------------------------------------------------------------------
%%% Internal API
%%--------------------------------------------------------------------
+l2b(L) when is_integer(hd(L)) ->
+ try list_to_binary(L)
+ of
+ B -> B
+ catch
+ _:_ ->
+ unicode:characters_to_binary(L)
+ end;
+l2b([H|T]) ->
+ << (l2b(H))/binary, (l2b(T))/binary >>;
+l2b(B) when is_binary(B) ->
+ B;
+l2b([]) ->
+ <<>>.
+
+
+
channel_data(ChannelId, DataType, Data, Connection, From)
when is_list(Data)->
channel_data(ChannelId, DataType,
- list_to_binary(Data), Connection, From);
+%% list_to_binary(Data), Connection, From);
+ l2b(Data), Connection, From);
+ %% try list_to_binary(Data)
+ %% of
+ %% B -> B
+ %% catch
+ %% _:_ -> io:format('BAD BINARY: ~p~n',[Data]),
+ %% unicode:characters_to_binary(Data)
+ %% end,
+ %% Connection, From);
channel_data(ChannelId, DataType, Data,
#connection{channel_cache = Cache} = Connection,
@@ -730,7 +756,6 @@ handle_msg(#ssh_msg_request_success{data = Data},
{{replies, [{channel_requst_reply, From, {success, Data}}]},
Connection#connection{requests = Rest}};
-%%% This transport message will also be handled at the connection level
handle_msg(#ssh_msg_disconnect{code = Code,
description = Description,
language = _Lang },
diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl
index 7ba2179a76..3462b98172 100644
--- a/lib/ssh/src/ssh_connection_handler.erl
+++ b/lib/ssh/src/ssh_connection_handler.erl
@@ -519,7 +519,8 @@ connected({#ssh_msg_kexinit{}, _Payload} = Event, State) ->
#state{}) -> gen_fsm_state_return().
%%--------------------------------------------------------------------
-handle_event(#ssh_msg_disconnect{description = Desc}, _StateName, #state{} = State) ->
+handle_event(#ssh_msg_disconnect{description = Desc} = DisconnectMsg, _StateName, #state{} = State) ->
+ handle_disconnect(DisconnectMsg, State),
{stop, {shutdown, Desc}, State};
handle_event(#ssh_msg_ignore{}, StateName, State) ->
@@ -850,7 +851,11 @@ handle_info({Protocol, Socket, Data}, Statename,
handle_info({CloseTag, _Socket}, _StateName,
#state{transport_close_tag = CloseTag,
ssh_params = #ssh{role = _Role, opts = _Opts}} = State) ->
- {stop, {shutdown, "Connection Lost"}, State};
+ DisconnectMsg =
+ #ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION,
+ description = "Connection closed",
+ language = "en"},
+ handle_disconnect(DisconnectMsg, State);
handle_info({timeout, {_, From} = Request}, Statename,
#state{connection_state = #connection{requests = Requests} = Connection} = State) ->
@@ -1377,10 +1382,16 @@ handle_ssh_packet(Length, StateName, #state{decoded_data_buffer = DecData0,
handle_disconnect(DisconnectMsg, State0)
end.
-handle_disconnect(#ssh_msg_disconnect{description = Desc}, State) ->
- {stop, {shutdown, Desc}, State}.
-handle_disconnect(#ssh_msg_disconnect{description = Desc}, State, ErrorMsg) ->
- {stop, {shutdown, {Desc, ErrorMsg}}, State}.
+handle_disconnect(#ssh_msg_disconnect{description = Desc} = Msg, #state{connection_state = Connection0,
+ role = Role} = State0) ->
+ {disconnect, _, {{replies, Replies}, Connection}} = ssh_connection:handle_msg(Msg, Connection0, Role),
+ State = send_replies(Replies, State0),
+ {stop, {shutdown, Desc}, State#state{connection_state = Connection}}.
+handle_disconnect(#ssh_msg_disconnect{description = Desc} = Msg, #state{connection_state = Connection0,
+ role = Role} = State0, ErrorMsg) ->
+ {disconnect, _, {{replies, Replies}, Connection}} = ssh_connection:handle_msg(Msg, Connection0, Role),
+ State = send_replies(Replies, State0),
+ {stop, {shutdown, {Desc, ErrorMsg}}, State#state{connection_state = Connection}}.
counterpart_versions(NumVsn, StrVsn, #ssh{role = server} = Ssh) ->
Ssh#ssh{c_vsn = NumVsn , c_version = StrVsn};
@@ -1420,9 +1431,9 @@ retry_fun(User, PeerAddr, Reason, Opts) ->
do_retry_fun(Fun, User, PeerAddr, Reason) ->
case erlang:fun_info(Fun, arity) of
- 2 -> %% Backwards compatible
+ {arity, 2} -> %% Backwards compatible
catch Fun(User, Reason);
- 3 ->
+ {arity, 3} ->
catch Fun(User, PeerAddr, Reason)
end.
diff --git a/lib/ssh/src/ssh_io.erl b/lib/ssh/src/ssh_io.erl
index 01fc713569..832b144db9 100644
--- a/lib/ssh/src/ssh_io.erl
+++ b/lib/ssh/src/ssh_io.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2005-2012. All Rights Reserved.
+%% Copyright Ericsson AB 2005-2013. 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
@@ -24,7 +24,6 @@
-module(ssh_io).
-export([yes_no/2, read_password/2, read_line/2, format/2]).
--import(lists, [reverse/1]).
-include("ssh.hrl").
read_line(Prompt, Ssh) ->
@@ -81,7 +80,7 @@ format(Fmt, Args) ->
trim(Line) when is_list(Line) ->
- reverse(trim1(reverse(trim1(Line))));
+ lists:reverse(trim1(lists:reverse(trim1(Line))));
trim(Other) -> Other.
trim1([$\s|Cs]) -> trim(Cs);
diff --git a/lib/ssh/src/ssh_message.erl b/lib/ssh/src/ssh_message.erl
index 7bd0375521..01a0988718 100644
--- a/lib/ssh/src/ssh_message.erl
+++ b/lib/ssh/src/ssh_message.erl
@@ -120,7 +120,7 @@ encode(#ssh_msg_userauth_request{
data = Data
}) ->
ssh_bits:encode([?SSH_MSG_USERAUTH_REQUEST, User, Service, Method, Data],
- [byte, string, string, string, '...']);
+ [byte, string_utf8, string, string, '...']);
encode(#ssh_msg_userauth_failure{
authentications = Auths,
partial_success = Bool
@@ -135,7 +135,7 @@ encode(#ssh_msg_userauth_banner{
language = Lang
}) ->
ssh_bits:encode([?SSH_MSG_USERAUTH_BANNER, Banner, Lang],
- [byte, string, string]);
+ [byte, string_utf8, string]);
encode(#ssh_msg_userauth_pk_ok{
algorithm_name = Alg,
diff --git a/lib/ssh/src/ssh_no_io.erl b/lib/ssh/src/ssh_no_io.erl
index 9f83506cdd..825a0d4af5 100644
--- a/lib/ssh/src/ssh_no_io.erl
+++ b/lib/ssh/src/ssh_no_io.erl
@@ -24,27 +24,27 @@
-module(ssh_no_io).
-include("ssh_transport.hrl").
--export([yes_no/1, read_password/1, read_line/1, format/2]).
+-export([yes_no/2, read_password/2, read_line/2, format/2]).
-yes_no(_Prompt) ->
+yes_no(_, _) ->
throw({{no_io_allowed, yes_no},
#ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE,
description = "User interaction is not allowed",
language = "en"}}).
-read_password(_Prompt) ->
+read_password(_, _) ->
throw({{no_io_allowed, read_password},
#ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE,
description = "User interaction is not allowed",
language = "en"}}).
-read_line(_Prompt) ->
+read_line(_, _) ->
throw({{no_io_allowed, read_line},
#ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE,
description = "User interaction is not allowed",
language = "en"}} ).
-format(_Fmt, _Args) ->
+format(_, _) ->
throw({{no_io_allowed, format},
#ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE,
description = "User interaction is not allowed",
diff --git a/lib/ssh/src/ssh_sftp.erl b/lib/ssh/src/ssh_sftp.erl
index 10167a9223..0ea2366ac7 100644
--- a/lib/ssh/src/ssh_sftp.erl
+++ b/lib/ssh/src/ssh_sftp.erl
@@ -352,7 +352,7 @@ 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, unicode:characters_to_binary(List), FileOpTimeout);
write_file(Pid, Name, Bin, FileOpTimeout) ->
case open(Pid, Name, [write, binary], FileOpTimeout) of
{ok, Handle} ->
@@ -514,7 +514,7 @@ do_handle_call({pread,Async,Handle,At,Length}, From, State) ->
case get_mode(Handle, State2) of
binary -> {{ok,Data}, State2};
text ->
- {{ok,binary_to_list(Data)}, State2}
+ {{ok,unicode:characters_to_list(Data)}, State2}
end;
(Rep, State2) ->
{Rep, State2}
@@ -535,8 +535,7 @@ do_handle_call({read,Async,Handle,Length}, From, State) ->
fun({ok,Data}, State2) ->
case get_mode(Handle, State2) of
binary -> {{ok,Data}, State2};
- text ->
- {{ok,binary_to_list(Data)}, State2}
+ text -> {{ok,binary_to_list(Data)}, State2}
end;
(Rep, State2) -> {Rep, State2}
end);
diff --git a/lib/ssh/src/ssh_sftpd.erl b/lib/ssh/src/ssh_sftpd.erl
index 174ca0126b..213b5c714d 100644
--- a/lib/ssh/src/ssh_sftpd.erl
+++ b/lib/ssh/src/ssh_sftpd.erl
@@ -214,8 +214,7 @@ handle_op(?SSH_FXP_INIT, Version, B, State) when is_binary(B) ->
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),
+ RelPath = relate_file_name(RPath, State0, _Canonicalize=false),
{Res, State} = resolve_symlinks(RelPath, State0),
case Res of
{ok, AbsPath} ->
@@ -231,7 +230,7 @@ handle_op(?SSH_FXP_OPENDIR, ReqId,
<<?UINT32(RLen), RPath:RLen/binary>>,
State0 = #state{xf = #ssh_xfer{vsn = Vsn},
file_handler = FileMod, file_state = FS0}) ->
- RelPath = binary_to_list(RPath),
+ RelPath = unicode:characters_to_list(RPath),
AbsPath = relate_file_name(RelPath, State0),
XF = State0#state.xf,
@@ -312,9 +311,8 @@ handle_op(?SSH_FXP_WRITE, ReqId,
?SSH_FX_INVALID_HANDLE),
State
end;
-handle_op(?SSH_FXP_READLINK, ReqId, <<?UINT32(PLen), BPath:PLen/binary>>,
+handle_op(?SSH_FXP_READLINK, ReqId, <<?UINT32(PLen), RelPath: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
@@ -524,10 +522,10 @@ close_our_file({_,Fd}, FileMod, FS0) ->
%%% 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(ReqId, unicode:characters_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).
+ stat(ReqId, unicode:characters_to_list(BPath), State, F).
fstat(Vsn, ReqId, Data, State) when Vsn =< 3->
<<?UINT32(HLen), Handle:HLen/binary>> = Data,
@@ -609,13 +607,13 @@ 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),
+ Path = unicode:characters_to_list(BPath),
Flags = ssh_xfer:decode_open_flags(Vsn, PFlags),
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),
+ Path = unicode:characters_to_list(BPath),
FlagBits = ssh_xfer:decode_open_flags(Vsn, PFlags),
AcessBits = ssh_xfer:decode_ace_mask(Access),
%% TODO: This is to make sure the Access flags are not ignored
@@ -712,7 +710,7 @@ 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(unicode:characters_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) ->
diff --git a/lib/ssh/src/ssh_system_sup.erl b/lib/ssh/src/ssh_system_sup.erl
index bf3c12a988..848133f838 100644
--- a/lib/ssh/src/ssh_system_sup.erl
+++ b/lib/ssh/src/ssh_system_sup.erl
@@ -54,13 +54,16 @@ stop_listener(SysSup) ->
stop_listener(Address, Port) ->
Name = make_name(Address, Port),
stop_acceptor(whereis(Name)).
-
-stop_system(SysSup) when is_pid(SysSup)->
- exit(SysSup, shutdown).
+
+stop_system(SysSup) ->
+ Name = sshd_sup:system_name(SysSup),
+ spawn(fun() -> sshd_sup:stop_child(Name) end),
+ ok.
stop_system(Address, Port) ->
- stop_system(system_supervisor(Address, Port)).
-
+ spawn(fun() -> sshd_sup:stop_child(Address, Port) end),
+ ok.
+
system_supervisor(Address, Port) ->
Name = make_name(Address, Port),
whereis(Name).
@@ -136,7 +139,7 @@ ssh_acceptor_child_spec(ServerOpts) ->
Port = proplists:get_value(port, ServerOpts),
Name = id(ssh_acceptor_sup, Address, Port),
StartFunc = {ssh_acceptor_sup, start_link, [ServerOpts]},
- Restart = permanent,
+ Restart = transient,
Shutdown = infinity,
Modules = [ssh_acceptor_sup],
Type = supervisor,
diff --git a/lib/ssh/src/ssh_xfer.erl b/lib/ssh/src/ssh_xfer.erl
index e18e18a9a9..63d01fd9de 100644
--- a/lib/ssh/src/ssh_xfer.erl
+++ b/lib/ssh/src/ssh_xfer.erl
@@ -72,7 +72,6 @@ protocol_version_request(XF) ->
open(XF, ReqID, FileName, Access, Flags, Attrs) ->
Vsn = XF#ssh_xfer.vsn,
- FileName1 = unicode:characters_to_binary(FileName),
MBits = if Vsn >= 5 ->
M = encode_ace_mask(Access),
?uint32(M);
@@ -82,7 +81,7 @@ open(XF, ReqID, FileName, Access, Flags, Attrs) ->
F = encode_open_flags(Flags),
xf_request(XF,?SSH_FXP_OPEN,
[?uint32(ReqID),
- ?binary(FileName1),
+ ?string_utf8(FileName),
MBits,
?uint32(F),
encode_ATTR(Vsn,Attrs)]).
@@ -90,7 +89,7 @@ open(XF, ReqID, FileName, Access, Flags, Attrs) ->
opendir(XF, ReqID, DirName) ->
xf_request(XF, ?SSH_FXP_OPENDIR,
[?uint32(ReqID),
- ?string(DirName)]).
+ ?string_utf8(DirName)]).
close(XF, ReqID, Handle) ->
@@ -127,13 +126,11 @@ write(XF,ReqID, Handle, Offset, Data) ->
remove(XF, ReqID, File) ->
xf_request(XF, ?SSH_FXP_REMOVE,
[?uint32(ReqID),
- ?string(File)]).
+ ?string_utf8(File)]).
%% Rename a file/directory
-rename(XF, ReqID, Old, New, Flags) ->
+rename(XF, ReqID, OldPath, NewPath, Flags) ->
Vsn = XF#ssh_xfer.vsn,
- OldPath = unicode:characters_to_binary(Old),
- NewPath = unicode:characters_to_binary(New),
FlagBits
= if Vsn >= 5 ->
F0 = encode_rename_flags(Flags),
@@ -143,30 +140,27 @@ rename(XF, ReqID, Old, New, Flags) ->
end,
xf_request(XF, ?SSH_FXP_RENAME,
[?uint32(ReqID),
- ?binary(OldPath),
- ?binary(NewPath),
+ ?string_utf8(OldPath),
+ ?string_utf8(NewPath),
FlagBits]).
%% Create directory
mkdir(XF, ReqID, Path, Attrs) ->
- Path1 = unicode:characters_to_binary(Path),
xf_request(XF, ?SSH_FXP_MKDIR,
[?uint32(ReqID),
- ?binary(Path1),
+ ?string_utf8(Path),
encode_ATTR(XF#ssh_xfer.vsn, Attrs)]).
%% Remove a directory
rmdir(XF, ReqID, Dir) ->
- Dir1 = unicode:characters_to_binary(Dir),
xf_request(XF, ?SSH_FXP_RMDIR,
[?uint32(ReqID),
- ?binary(Dir1)]).
+ ?string_utf8(Dir)]).
%% Stat file
stat(XF, ReqID, Path, Flags) ->
- Path1 = unicode:characters_to_binary(Path),
Vsn = XF#ssh_xfer.vsn,
AttrFlags = if Vsn >= 5 ->
F = encode_attr_flags(Vsn, Flags),
@@ -176,13 +170,12 @@ stat(XF, ReqID, Path, Flags) ->
end,
xf_request(XF, ?SSH_FXP_STAT,
[?uint32(ReqID),
- ?binary(Path1),
+ ?string_utf8(Path),
AttrFlags]).
%% Stat file - follow symbolic links
lstat(XF, ReqID, Path, Flags) ->
- Path1 = unicode:characters_to_binary(Path),
Vsn = XF#ssh_xfer.vsn,
AttrFlags = if Vsn >= 5 ->
F = encode_attr_flags(Vsn, Flags),
@@ -192,7 +185,7 @@ lstat(XF, ReqID, Path, Flags) ->
end,
xf_request(XF, ?SSH_FXP_LSTAT,
[?uint32(ReqID),
- ?binary(Path1),
+ ?string_utf8(Path),
AttrFlags]).
%% Stat open file
@@ -211,10 +204,9 @@ fstat(XF, ReqID, Handle, Flags) ->
%% Modify file attributes
setstat(XF, ReqID, Path, Attrs) ->
- Path1 = unicode:characters_to_binary(Path),
xf_request(XF, ?SSH_FXP_SETSTAT,
[?uint32(ReqID),
- ?binary(Path1),
+ ?string_utf8(Path),
encode_ATTR(XF#ssh_xfer.vsn, Attrs)]).
@@ -227,10 +219,9 @@ fsetstat(XF, ReqID, Handle, Attrs) ->
%% Read a symbolic link
readlink(XF, ReqID, Path) ->
- Path1 = unicode:characters_to_binary(Path),
xf_request(XF, ?SSH_FXP_READLINK,
[?uint32(ReqID),
- ?binary(Path1)]).
+ ?string_utf8(Path)]).
%% Create a symbolic link
@@ -244,10 +235,9 @@ symlink(XF, ReqID, LinkPath, TargetPath) ->
%% Convert a path into a 'canonical' form
realpath(XF, ReqID, Path) ->
- Path1 = unicode:characters_to_binary(Path),
xf_request(XF, ?SSH_FXP_REALPATH,
[?uint32(ReqID),
- ?binary(Path1)]).
+ ?string_utf8(Path)]).
extended(XF, ReqID, Request, Data) ->
xf_request(XF, ?SSH_FXP_EXTENDED,
@@ -296,7 +286,10 @@ xf_send_names(#ssh_xfer{cm = CM, channel = Channel, vsn = Vsn},
Count = length(NamesAndAttrs),
{Data, Len} = encode_names(Vsn, NamesAndAttrs),
Size = 1 + 4 + 4 + Len,
- ToSend = [<<?UINT32(Size), ?SSH_FXP_NAME, ?UINT32(ReqId), ?UINT32(Count)>>,
+ ToSend = [<<?UINT32(Size),
+ ?SSH_FXP_NAME,
+ ?UINT32(ReqId),
+ ?UINT32(Count)>>,
Data],
ssh_connection:send(CM, Channel, ToSend).
@@ -818,25 +811,27 @@ 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),
+ Name = unicode:characters_to_list(FileName),
{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),
+ Name = unicode:characters_to_list(FileName),
{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 ->
+encode_name(Vsn, {NameUC,Attr}, Len) when Vsn =< 3 ->
+ Name = binary_to_list(unicode:characters_to_binary(NameUC)),
NLen = length(Name),
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 ->
+encode_name(Vsn, {NameUC,Attr}, Len) when Vsn >= 4 ->
+ Name = binary_to_list(unicode:characters_to_binary(NameUC)),
NLen = length(Name),
EncAttr = encode_ATTR(Vsn, Attr),
ALen = size(EncAttr),
@@ -851,9 +846,9 @@ 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),
+ Who = ACE#ssh_xfer_ace.who,
[?uint32(Type), ?uint32(Flag), ?uint32(Mask),
- ?binary(Who) | encode_acl_items(As)];
+ ?string_utf8(Who) | encode_acl_items(As)];
encode_acl_items([]) ->
[].
@@ -872,7 +867,7 @@ decode_acl_items(I, <<?UINT32(Type),
[#ssh_xfer_ace { type = decode_ace_type(Type),
flag = decode_ace_flag(Flag),
mask = decode_ace_mask(Mask),
- who = binary_to_list(BWho)} | Acc]).
+ who = unicode:characters_to_list(BWho)} | Acc]).
encode_extensions(Exts) ->
Count = length(Exts),
diff --git a/lib/ssh/src/sshd_sup.erl b/lib/ssh/src/sshd_sup.erl
index 747906b2cf..60222f5172 100644
--- a/lib/ssh/src/sshd_sup.erl
+++ b/lib/ssh/src/sshd_sup.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2008-2010. All Rights Reserved.
+%% Copyright Ericsson AB 2008-2013. 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
@@ -58,12 +58,7 @@ start_child(ServerOpts) ->
end.
stop_child(Name) ->
- case supervisor:terminate_child(?MODULE, Name) of
- ok ->
- supervisor:delete_child(?MODULE, Name);
- Error ->
- Error
- end.
+ supervisor:terminate_child(?MODULE, Name).
stop_child(Address, Port) ->
Name = id(Address, Port),
@@ -94,7 +89,7 @@ init([Servers]) ->
child_spec(Address, Port, ServerOpts) ->
Name = id(Address, Port),
StartFunc = {ssh_system_sup, start_link, [ServerOpts]},
- Restart = transient,
+ Restart = temporary,
Shutdown = infinity,
Modules = [ssh_system_sup],
Type = supervisor,
diff --git a/lib/ssh/test/ssh_basic_SUITE.erl b/lib/ssh/test/ssh_basic_SUITE.erl
index b3281e433e..b4e3871efd 100644
--- a/lib/ssh/test/ssh_basic_SUITE.erl
+++ b/lib/ssh/test/ssh_basic_SUITE.erl
@@ -46,7 +46,7 @@ all() ->
daemon_already_started,
server_password_option,
server_userpassword_option,
- close].
+ double_close].
groups() ->
[{dsa_key, [], basic_tests()},
@@ -57,7 +57,7 @@ groups() ->
].
basic_tests() ->
- [send, peername_sockname,
+ [send, close, peername_sockname,
exec, exec_compressed, shell, cli, known_hosts,
idle_time, rekey, openssh_zlib_basic_test].
@@ -487,7 +487,7 @@ internal_error(Config) when is_list(Config) ->
{Pid, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
{user_dir, UserDir},
{failfun, fun ssh_test_lib:failfun/2}]),
- {error,Error} =
+ {error, Error} =
ssh:connect(Host, Port, [{silently_accept_hosts, true},
{user_dir, UserDir},
{user_interaction, false}]),
@@ -566,9 +566,35 @@ ips(Name) when is_list(Name) ->
ordsets:from_list(IPs4++IPs6).
%%--------------------------------------------------------------------
+
close() ->
- [{doc, "Simulate that we try to close an already closed connection"}].
+ [{doc, "Client receives close when server closes"}].
close(Config) when is_list(Config) ->
+ process_flag(trap_exit, true),
+ SystemDir = filename:join(?config(priv_dir, Config), system),
+ UserDir = ?config(priv_dir, Config),
+
+ {Server, Host, Port} = ssh_test_lib:daemon([{system_dir, SystemDir},
+ {user_dir, UserDir},
+ {failfun, fun ssh_test_lib:failfun/2}]),
+ Client =
+ ssh_test_lib:connect(Host, Port, [{silently_accept_hosts, true},
+ {user_dir, UserDir},
+ {user_interaction, false}]),
+ {ok, ChannelId} = ssh_connection:session_channel(Client, infinity),
+
+ ssh:stop_daemon(Server),
+ receive
+ {ssh_cm, Client,{closed, ChannelId}} ->
+ ok
+ after 5000 ->
+ ct:fail(timeout)
+ end.
+
+%%--------------------------------------------------------------------
+double_close() ->
+ [{doc, "Simulate that we try to close an already closed connection"}].
+double_close(Config) when is_list(Config) ->
SystemDir = ?config(data_dir, Config),
PrivDir = ?config(priv_dir, Config),
UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth
@@ -587,6 +613,8 @@ close(Config) when is_list(Config) ->
exit(CM, {shutdown, normal}),
ok = ssh:close(CM).
+%%--------------------------------------------------------------------
+
openssh_zlib_basic_test() ->
[{doc, "Test basic connection with openssh_zlib"}].
openssh_zlib_basic_test(Config) ->
@@ -612,7 +640,7 @@ openssh_zlib_basic_test(Config) ->
%% the "tcp-application" before the socket closed message is recived
check_error("Internal error") ->
ok;
-check_error("Connection Lost") ->
+check_error("Connection closed") ->
ok;
check_error(Error) ->
ct:fail(Error).
diff --git a/lib/ssh/test/ssh_test_lib.erl b/lib/ssh/test/ssh_test_lib.erl
index 6ed3dfa68c..00c25bf394 100644
--- a/lib/ssh/test/ssh_test_lib.erl
+++ b/lib/ssh/test/ssh_test_lib.erl
@@ -63,8 +63,13 @@ daemon(Host, Port, Options) ->
Error
end.
+
+
start_shell(Port, IOServer, UserDir) ->
- spawn_link(?MODULE, init_shell, [Port, IOServer, [{user_dir, UserDir}]]).
+ start_shell(Port, IOServer, UserDir, []).
+
+start_shell(Port, IOServer, UserDir, Options) ->
+ spawn_link(?MODULE, init_shell, [Port, IOServer, [{user_dir, UserDir}|Options]]).
start_shell(Port, IOServer) ->
spawn_link(?MODULE, init_shell, [Port, IOServer, []]).
@@ -91,18 +96,23 @@ loop_io_server(TestCase, Buff0) ->
{input, TestCase, Line} ->
loop_io_server(TestCase, Buff0 ++ [Line]);
{io_request, From, ReplyAs, Request} ->
+%%ct:pal("~p",[{io_request, From, ReplyAs, Request}]),
{ok, Reply, Buff} = io_request(Request, TestCase, From,
ReplyAs, Buff0),
+%%ct:pal("io_request(~p)-->~p",[Request,{ok, Reply, Buff}]),
io_reply(From, ReplyAs, Reply),
loop_io_server(TestCase, Buff);
{'EXIT',_, _} ->
- erlang:display('EXIT'),
+ erlang:display('ssh_test_lib:loop_io_server/2 EXIT'),
ok
end.
io_request({put_chars, Chars}, TestCase, _, _, Buff) ->
reply(TestCase, Chars),
{ok, ok, Buff};
+io_request({put_chars, unicode, Chars}, TestCase, _, _, Buff) when is_binary(Chars) ->
+ reply(TestCase, Chars),
+ {ok, ok, Buff};
io_request({put_chars, Enc, Chars}, TestCase, _, _, Buff) ->
reply(TestCase, unicode:characters_to_binary(Chars,Enc,latin1)),
{ok, ok, Buff};
@@ -120,11 +130,13 @@ io_request({get_line, _Enc,_}, _, _, _, [Line | Buff]) ->
io_reply(_, _, []) ->
ok;
io_reply(From, ReplyAs, Reply) ->
+%%ct:pal("io_reply ~p sending ~p ! ~p",[self(),From, {io_reply, ReplyAs, Reply}]),
From ! {io_reply, ReplyAs, Reply}.
reply(_, []) ->
ok;
reply(TestCase, Result) ->
+%%ct:pal("reply ~p sending ~p ! ~p",[self(), TestCase, Result]),
TestCase ! Result.
receive_exec_result(Msg) ->
diff --git a/lib/ssh/test/ssh_unicode_SUITE.erl b/lib/ssh/test/ssh_unicode_SUITE.erl
new file mode 100644
index 0000000000..a896a425b9
--- /dev/null
+++ b/lib/ssh/test/ssh_unicode_SUITE.erl
@@ -0,0 +1,590 @@
+%% Next line needed to enable utf8-strings in Erlang:
+%% -*- coding: utf-8 -*-
+
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2005-2013. 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%
+%%
+
+%% gerl +fnu
+%% ct:run_test([{suite,"ssh_unicode_SUITE"}, {logdir,"LOG"}]).
+
+-module(ssh_unicode_SUITE).
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+
+-include_lib("common_test/include/ct.hrl").
+-include_lib("kernel/include/file.hrl").
+
+% Default timetrap timeout
+-define(default_timeout, ?t:minutes(1)).
+
+-define(USER, "åke高兴").
+-define(PASSWD, "ärlig日本じん").
+-define('sftp.txt', "sftp瑞点.txt").
+-define('test.txt', "testハンス.txt").
+-define('link_test.txt', "link_test語.txt").
+
+-define(bindata, unicode:characters_to_binary("foobar å 一二三四いちにさんち") ).
+
+-define(NEWLINE, <<"\r\n">>).
+
+%%--------------------------------------------------------------------
+%% Common Test interface functions -----------------------------------
+%%--------------------------------------------------------------------
+
+%% suite() ->
+%% [{ct_hooks,[ts_install_cth]}].
+
+all() ->
+ [{group, sftp},
+ {group, shell}
+ ].
+
+
+init_per_suite(Config) ->
+ case {file:native_name_encoding(), (catch crypto:start())} of
+ {utf8, ok} ->
+ ssh:start(),
+ Config;
+ {utf8, _} ->
+ {skip,"Could not start crypto!"};
+ _ ->
+ {skip,"Not unicode filename enabled emulator"}
+ end.
+
+end_per_suite(Config) ->
+ ssh:stop(),
+ crypto:stop(),
+ Config.
+
+%%--------------------------------------------------------------------
+groups() ->
+ [{shell, [], [shell_no_unicode, shell_unicode_string]},
+ {sftp, [], [open_close_file, open_close_dir, read_file, read_dir,
+ write_file, rename_file, mk_rm_dir, remove_file, links,
+ retrieve_attributes, set_attributes, async_read, async_read_bin,
+ async_write
+ %% , position, pos_read, pos_write
+ ]}].
+
+init_per_group(Group, Config) when Group==sftp
+ ; Group==shell ->
+ PrivDir = ?config(priv_dir, Config),
+ SysDir = ?config(data_dir, Config),
+ Sftpd =
+ ssh_test_lib:daemon([{system_dir, SysDir},
+ {user_dir, PrivDir},
+ {user_passwords, [{?USER, ?PASSWD}]}]),
+ [{group,Group}, {sftpd, Sftpd} | Config];
+
+init_per_group(Group, Config) ->
+ [{group,Group} | Config].
+
+
+end_per_group(erlang_server, Config) ->
+ Config;
+end_per_group(_, Config) ->
+ Config.
+
+%%--------------------------------------------------------------------
+init_per_testcase(_Case, Config) ->
+ prep(Config),
+ TmpConfig0 = lists:keydelete(watchdog, 1, Config),
+ TmpConfig = lists:keydelete(sftp, 1, TmpConfig0),
+ Dog = ct:timetrap(?default_timeout),
+
+ case ?config(group, Config) of
+ sftp ->
+ {_Pid, Host, Port} = ?config(sftpd, Config),
+ {ok, ChannelPid, Connection} =
+ ssh_sftp:start_channel(Host, Port,
+ [{user, ?USER},
+ {password, ?PASSWD},
+ {user_interaction, false},
+ {silently_accept_hosts, true}]),
+ Sftp = {ChannelPid, Connection},
+ [{sftp, Sftp}, {watchdog, Dog} | TmpConfig];
+ shell ->
+ UserDir = ?config(priv_dir, Config),
+ process_flag(trap_exit, true),
+ {_Pid, _Host, Port} = ?config(sftpd, Config),
+ ct:sleep(500),
+ IO = ssh_test_lib:start_io_server(),
+ Shell = ssh_test_lib:start_shell(Port, IO, UserDir,
+ [{silently_accept_hosts, true},
+ {user,?USER},{password,?PASSWD}]),
+%%ct:pal("IO=~p, Shell=~p, self()=~p",[IO,Shell,self()]),
+ wait_for_erlang_first_line([{io,IO}, {shell,Shell} | Config])
+ end.
+
+
+wait_for_erlang_first_line(Config) ->
+ receive
+ {'EXIT', _, _} ->
+ {fail,no_ssh_connection};
+ <<"Eshell ",_/binary>> = ErlShellStart ->
+%% ct:pal("Erlang shell start: ~p~n", [ErlShellStart]),
+ Config;
+ Other ->
+ ct:pal("Unexpected answer from ssh server: ~p",[Other]),
+ {fail,unexpected_answer}
+ after 10000 ->
+ ct:pal("No answer from ssh-server"),
+ {fail,timeout}
+ end.
+
+
+
+end_per_testcase(rename_file, Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ NewFileName = filename:join(PrivDir, ?'test.txt'),
+ file:delete(NewFileName),
+ end_per_testcase(Config);
+end_per_testcase(_TC, Config) ->
+ end_per_testcase(Config).
+
+end_per_testcase(Config) ->
+ catch exit(?config(shell,Config), kill),
+ case ?config(sftp, Config) of
+ {Sftp, Connection} ->
+ ssh_sftp:stop_channel(Sftp),
+ ssh:close(Connection);
+ _ ->
+ ok
+ end.
+
+%%--------------------------------------------------------------------
+%% Test Cases --------------------------------------------------------
+
+-define(chk_expected(Received,Expected),
+ (fun(R_,E_) when R_==E_ -> ok;
+ (R_,E_) -> ct:pal("Expected: ~p~nReceived: ~p~n", [E_,R_]),
+ E_ = R_
+ end)(Received,Expected)).
+
+-define(receive_chk(Ref,Expected),
+ (fun(E__) ->
+ receive
+ {async_reply, Ref, Received} when Received==E__ ->
+ ?chk_expected(Received, E__);
+ {async_reply, Ref, Received} when Received=/=E__ ->
+ ct:pal("Expected: ~p~nReceived: ~p~n", [E__,Received]),
+ E__ = Received;
+ Msg ->
+ ct:pal("Expected (Ref=~p): ~p", [Ref,E__]),
+ ct:fail(Msg)
+ end
+ end)(Expected)).
+
+%%--------------------------------------------------------------------
+
+
+open_close_file() ->
+ [{doc, "Test API functions open/3 and close/2"}].
+open_close_file(Config) when is_list(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ FileName = filename:join(PrivDir, ?'sftp.txt'),
+ {Sftp, _} = ?config(sftp, Config),
+
+ lists:foreach(
+ fun(Mode) ->
+ ct:log("Mode: ~p",[Mode]),
+ %% list_dir(PrivDir),
+ ok = open_close_file(Sftp, FileName, Mode)
+ end,
+ [
+ [read],
+ [write],
+ [write, creat],
+ [write, trunc],
+ [append],
+ [read, binary]
+ ]).
+
+open_close_file(Server, File, Mode) ->
+ {ok, Handle} = ssh_sftp:open(Server, File, Mode),
+ ok = ssh_sftp:close(Server, Handle).
+
+%%--------------------------------------------------------------------
+open_close_dir() ->
+ [{doc, "Test API functions opendir/2 and close/2"}].
+open_close_dir(Config) when is_list(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ {Sftp, _} = ?config(sftp, Config),
+ FileName = filename:join(PrivDir, ?'sftp.txt'),
+
+ {ok, Handle} = ssh_sftp:opendir(Sftp, PrivDir),
+ ok = ssh_sftp:close(Sftp, Handle),
+ {error, _} = ssh_sftp:opendir(Sftp, FileName).
+
+%%--------------------------------------------------------------------
+read_file() ->
+ [{doc, "Test API funtion read_file/2"}].
+read_file(Config) when is_list(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ FileName = filename:join(PrivDir, ?'sftp.txt'),
+ {Sftp, _} = ?config(sftp, Config),
+ ?chk_expected(ssh_sftp:read_file(Sftp,FileName), file:read_file(FileName)).
+
+%%--------------------------------------------------------------------
+read_dir() ->
+ [{doc,"Test API function list_dir/2"}].
+read_dir(Config) when is_list(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ {Sftp, _} = ?config(sftp, Config),
+ {ok, Files} = ssh_sftp:list_dir(Sftp, PrivDir),
+ ct:pal("sftp list dir: ~ts~n", [Files]).
+
+%%--------------------------------------------------------------------
+write_file() ->
+ [{doc, "Test API function write_file/2"}].
+write_file(Config) when is_list(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ FileName = filename:join(PrivDir, ?'sftp.txt'),
+ {Sftp, _} = ?config(sftp, Config),
+ ok = ssh_sftp:write_file(Sftp, FileName, [?bindata]),
+ ?chk_expected(file:read_file(FileName), {ok,?bindata}).
+
+%%--------------------------------------------------------------------
+remove_file() ->
+ [{doc,"Test API function delete/2"}].
+remove_file(Config) when is_list(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ FileName = filename:join(PrivDir, ?'sftp.txt'),
+ {Sftp, _} = ?config(sftp, Config),
+
+ {ok, Files} = ssh_sftp:list_dir(Sftp, PrivDir),
+ true = lists:member(filename:basename(FileName), Files),
+ ok = ssh_sftp:delete(Sftp, FileName),
+ {ok, NewFiles} = ssh_sftp:list_dir(Sftp, PrivDir),
+ false = lists:member(filename:basename(FileName), NewFiles),
+ {error, _} = ssh_sftp:delete(Sftp, FileName).
+%%--------------------------------------------------------------------
+rename_file() ->
+ [{doc, "Test API function rename_file/2"}].
+rename_file(Config) when is_list(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ FileName = filename:join(PrivDir, ?'sftp.txt'),
+ NewFileName = filename:join(PrivDir, ?'test.txt'),
+
+ {Sftp, _} = ?config(sftp, Config),
+ {ok, Files} = ssh_sftp:list_dir(Sftp, PrivDir),
+ ct:pal("FileName: ~ts~nFiles: ~ts~n", [FileName, [[$\n,$ ,F]||F<-Files] ]),
+ true = lists:member(filename:basename(FileName), Files),
+ false = lists:member(filename:basename(NewFileName), Files),
+ ok = ssh_sftp:rename(Sftp, FileName, NewFileName),
+ {ok, NewFiles} = ssh_sftp:list_dir(Sftp, PrivDir),
+ ct:pal("FileName: ~ts, Files: ~ts~n", [FileName, [[$\n,F]||F<-NewFiles] ]),
+
+ false = lists:member(filename:basename(FileName), NewFiles),
+ true = lists:member(filename:basename(NewFileName), NewFiles).
+
+%%--------------------------------------------------------------------
+mk_rm_dir() ->
+ [{doc,"Test API functions make_dir/2, del_dir/2"}].
+mk_rm_dir(Config) when is_list(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ {Sftp, _} = ?config(sftp, Config),
+
+ DirName = filename:join(PrivDir, "test"),
+ ok = ssh_sftp:make_dir(Sftp, DirName),
+ ok = ssh_sftp:del_dir(Sftp, DirName),
+ NewDirName = filename:join(PrivDir, "foo/bar"),
+ {error, _} = ssh_sftp:make_dir(Sftp, NewDirName),
+ {error, _} = ssh_sftp:del_dir(Sftp, PrivDir).
+
+%%--------------------------------------------------------------------
+links() ->
+ [{doc,"Tests API function make_symlink/3"}].
+links(Config) when is_list(Config) ->
+ case os:type() of
+ {win32, _} ->
+ {skip, "Links are not fully supported by windows"};
+ _ ->
+ {Sftp, _} = ?config(sftp, Config),
+ PrivDir = ?config(priv_dir, Config),
+ FileName = filename:join(PrivDir, ?'sftp.txt'),
+ LinkFileName = filename:join(PrivDir, ?'link_test.txt'),
+
+ ok = ssh_sftp:make_symlink(Sftp, LinkFileName, FileName),
+ {ok, FileName} = ssh_sftp:read_link(Sftp, LinkFileName)
+ end.
+
+%%--------------------------------------------------------------------
+retrieve_attributes() ->
+ [{doc, "Test API function read_file_info/3"}].
+retrieve_attributes(Config) when is_list(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ FileName = filename:join(PrivDir, ?'sftp.txt'),
+
+ {Sftp, _} = ?config(sftp, Config),
+ {ok, FileInfo} = ssh_sftp:read_file_info(Sftp, FileName),
+ {ok, NewFileInfo} = file:read_file_info(FileName),
+
+ %% TODO comparison. There are some differences now is that ok?
+ ct:pal("SFTP: ~p~nFILE: ~p~n", [FileInfo, NewFileInfo]).
+
+%%--------------------------------------------------------------------
+set_attributes() ->
+ [{doc,"Test API function write_file_info/3"}].
+set_attributes(Config) when is_list(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ FileName = filename:join(PrivDir, ?'test.txt'),
+
+ {Sftp, _} = ?config(sftp, Config),
+ {ok,Fd} = file:open(FileName, write),
+ io:put_chars(Fd,"foo"),
+ ok = ssh_sftp:write_file_info(Sftp, FileName, #file_info{mode=8#400}),
+ {error, eacces} = file:write_file(FileName, "hello again"),
+ ssh_sftp:write_file_info(Sftp, FileName, #file_info{mode=8#600}),
+ ok = file:write_file(FileName, "hello again").
+
+%%--------------------------------------------------------------------
+
+async_read() ->
+ [{doc,"Test API aread/3"}].
+async_read(Config) when is_list(Config) ->
+ do_async_read(Config, false).
+
+async_read_bin() ->
+ [{doc,"Test API aread/3"}].
+async_read_bin(Config) when is_list(Config) ->
+ do_async_read(Config, true).
+
+do_async_read(Config, BinaryFlag) ->
+ {Sftp, _} = ?config(sftp, Config),
+ PrivDir = ?config(priv_dir, Config),
+ FileName = filename:join(PrivDir, ?'sftp.txt'),
+ {ok,ExpDataBin} = file:read_file(FileName),
+ ExpData = case BinaryFlag of
+ true -> ExpDataBin;
+ false -> binary_to_list(ExpDataBin)
+ end,
+ {ok, Handle} = ssh_sftp:open(Sftp, FileName, [read|case BinaryFlag of
+ true -> [binary];
+ false -> []
+ end]),
+ {async, Ref} = ssh_sftp:aread(Sftp, Handle, 20),
+ ?receive_chk(Ref, {ok,ExpData}).
+
+%%--------------------------------------------------------------------
+async_write() ->
+ [{doc,"Test API awrite/3"}].
+async_write(Config) when is_list(Config) ->
+ {Sftp, _} = ?config(sftp, Config),
+ PrivDir = ?config(priv_dir, Config),
+ FileName = filename:join(PrivDir, ?'test.txt'),
+ {ok, Handle} = ssh_sftp:open(Sftp, FileName, [write]),
+ Expected = ?bindata,
+ {async, Ref} = ssh_sftp:awrite(Sftp, Handle, Expected),
+
+ receive
+ {async_reply, Ref, ok} ->
+ {ok, Data} = file:read_file(FileName),
+ ?chk_expected(Data, Expected);
+ Msg ->
+ ct:fail(Msg)
+ end.
+
+%%--------------------------------------------------------------------
+
+position() ->
+ [{doc, "Test API functions position/3"}].
+position(Config) when is_list(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ FileName = filename:join(PrivDir, ?'test.txt'),
+ {Sftp, _} = ?config(sftp, Config),
+
+ Data = list_to_binary("1234567890"),
+ ssh_sftp:write_file(Sftp, FileName, [Data]),
+ {ok, Handle} = ssh_sftp:open(Sftp, FileName, [read]),
+
+ {ok, 3} = ssh_sftp:position(Sftp, Handle, {bof, 3}),
+ {ok, "4"} = ssh_sftp:read(Sftp, Handle, 1),
+
+ {ok, 10} = ssh_sftp:position(Sftp, Handle, eof),
+ eof = ssh_sftp:read(Sftp, Handle, 1),
+
+ {ok, 6} = ssh_sftp:position(Sftp, Handle, {bof, 6}),
+ {ok, "7"} = ssh_sftp:read(Sftp, Handle, 1),
+
+ {ok, 9} = ssh_sftp:position(Sftp, Handle, {cur, 2}),
+ {ok, "0"} = ssh_sftp:read(Sftp, Handle, 1),
+
+ {ok, 0} = ssh_sftp:position(Sftp, Handle, bof),
+ {ok, "1"} = ssh_sftp:read(Sftp, Handle, 1),
+
+ {ok, 1} = ssh_sftp:position(Sftp, Handle, cur),
+ {ok, "2"} = ssh_sftp:read(Sftp, Handle, 1).
+
+%%--------------------------------------------------------------------
+pos_read() ->
+ [{doc,"Test API functions pread/3 and apread/3"}].
+pos_read(Config) when is_list(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ FileName = filename:join(PrivDir, ?'test.txt'),
+ {Sftp, _} = ?config(sftp, Config),
+ Data = ?bindata,
+ ssh_sftp:write_file(Sftp, FileName, [Data]),
+
+ {ok, Handle} = ssh_sftp:open(Sftp, FileName, [read]),
+ {async, Ref} = ssh_sftp:apread(Sftp, Handle, {bof,5}, 4),
+
+ ?receive_chk(Ref, {ok,binary_part(Data,5,4)}),
+ ?chk_expected(ssh_sftp:pread(Sftp,Handle,{bof,4},4), {ok,binary_part(Data,4,4)}).
+
+
+%%--------------------------------------------------------------------
+pos_write() ->
+ [{doc,"Test API functions pwrite/4 and apwrite/4"}].
+pos_write(Config) when is_list(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ FileName = filename:join(PrivDir, ?'test.txt'),
+ {Sftp, _} = ?config(sftp, Config),
+
+ {ok, Handle} = ssh_sftp:open(Sftp, FileName, [write]),
+
+ Data = unicode:characters_to_list("再见"),
+ ssh_sftp:write_file(Sftp, FileName, [Data]),
+
+ NewData = unicode:characters_to_list(" さようなら"),
+ {async, Ref} = ssh_sftp:apwrite(Sftp, Handle, {bof, 2}, NewData),
+ ?receive_chk(Ref, ok),
+
+ ok = ssh_sftp:pwrite(Sftp, Handle, eof, unicode:characters_to_list(" adjö ")),
+
+ ?chk_expected(ssh_sftp:read_file(Sftp,FileName),
+ {ok,unicode:characters_to_binary("再见 さようなら adjö ")}).
+
+%%--------------------------------------------------------------------
+sftp_nonexistent_subsystem() ->
+ [{doc, "Try to execute sftp subsystem on a server that does not support it"}].
+sftp_nonexistent_subsystem(Config) when is_list(Config) ->
+ {_,Host, Port} = ?config(sftpd, Config),
+ {error,"server failed to start sftp subsystem"} =
+ ssh_sftp:start_channel(Host, Port,
+ [{user_interaction, false},
+ {user, ?USER},
+ {password, ?PASSWD},
+ {silently_accept_hosts, true}]).
+
+%%--------------------------------------------------------------------
+shell_no_unicode(Config) ->
+ do_shell(?config(io,Config),
+ [new_prompt,
+ {type,"io:format(\"hej ~p~n\",[42])."},
+ {expect,"hej 42"}
+ ]).
+
+%%--------------------------------------------------------------------
+shell_unicode_string(Config) ->
+ do_shell(?config(io,Config),
+ [new_prompt,
+ {type,"io:format(\"こにちわ~ts~n\",[\"四二\"])."},
+ {expect,"こにちわ四二"},
+ {expect,"ok"}
+ ]).
+
+%%--------------------------------------------------------------------
+%% Internal functions ------------------------------------------------
+%%--------------------------------------------------------------------
+prep(Config) ->
+ PrivDir = ?config(priv_dir, Config),
+ TestFile = filename:join(PrivDir, ?'sftp.txt'),
+ TestFile1 = filename:join(PrivDir, ?'test.txt'),
+ TestLink = filename:join(PrivDir, ?'link_test.txt'),
+
+ file:delete(TestFile),
+ file:delete(TestFile1),
+ file:delete(TestLink),
+
+ %% Initial config
+ DataDir = ?config(data_dir, Config),
+ FileName = filename:join(DataDir, ?'sftp.txt'),
+ {ok,_BytesCopied} = file:copy(FileName, TestFile),
+ Mode = 8#00400 bor 8#00200 bor 8#00040, % read & write owner, read group
+ {ok, FileInfo} = file:read_file_info(TestFile),
+ ok = file:write_file_info(TestFile,
+ FileInfo#file_info{mode = Mode}).
+
+
+%% list_dir(Dir) ->
+%% ct:pal("prep/1: ls(~p):~n~p~n~ts",[Dir, file:list_dir(Dir),
+%% begin
+%% {ok,DL} = file:list_dir(Dir),
+%% [[$\n|FN] || FN <- DL]
+%% end]).
+
+
+%%--------------------------------------------------------------------
+do_shell(IO, List) -> do_shell(IO, 0, List).
+
+do_shell(IO, N, [new_prompt|More]) ->
+ do_shell(IO, N+1, More);
+
+do_shell(IO, N, Ops=[{Order,Arg}|More]) ->
+ receive
+ X = <<"\r\n">> ->
+%% ct:pal("Skip newline ~p",[X]),
+ do_shell(IO, N, Ops);
+
+ <<P1,"> ">> when (P1-$0)==N ->
+ do_shell_prompt(IO, N, Order, Arg, More);
+
+ <<P1,P2,"> ">> when (P1-$0)*10 + (P2-$0) == N ->
+ do_shell_prompt(IO, N, Order, Arg, More);
+
+ Err when element(1,Err)==error ->
+ ct:fail("do_shell error: ~p~n",[Err]);
+
+ RecBin when Order==expect ; Order==expect_echo ->
+%% ct:pal("received ~p",[RecBin]),
+ RecStr = string:strip(unicode:characters_to_list(RecBin)),
+ ExpStr = string:strip(Arg),
+ case lists:prefix(ExpStr, RecStr) of
+ true when Order==expect ->
+ ct:pal("Matched ~ts",[RecStr]),
+ do_shell(IO, N, More);
+ true when Order==expect_echo ->
+ ct:pal("Matched echo ~ts",[RecStr]),
+ do_shell(IO, N, More);
+ false ->
+ ct:fail("*** Expected ~p, but got ~p",[string:strip(ExpStr),RecStr])
+ end
+ after 10000 ->
+ case Order of
+ expect -> ct:fail("timeout, expected ~p",[string:strip(Arg)]);
+ type -> ct:fail("timeout, no prompt")
+ end
+ end;
+
+do_shell(_, _, []) ->
+ ok.
+
+
+do_shell_prompt(IO, N, type, Str, More) ->
+%% ct:pal("Matched prompt ~p to trigger sending of next line to server",[N]),
+ IO ! {input, self(), Str++"\r\n"},
+ ct:pal("Promt '~p> ', Sent ~ts",[N,Str++"\r\n"]),
+ do_shell(IO, N, [{expect_echo,Str}|More]); % expect echo of the sent line
+do_shell_prompt(IO, N, Op, Str, More) ->
+%% ct:pal("Matched prompt ~p",[N]),
+ do_shell(IO, N, [{Op,Str}|More]).
+
+%%--------------------------------------------------------------------
diff --git a/lib/ssh/test/ssh_unicode_SUITE_data/sftp.txt b/lib/ssh/test/ssh_unicode_SUITE_data/sftp.txt
new file mode 100644
index 0000000000..3eaaddca21
--- /dev/null
+++ b/lib/ssh/test/ssh_unicode_SUITE_data/sftp.txt
@@ -0,0 +1 @@
+åäöÅÄÖ瑞語
diff --git a/lib/ssh/test/ssh_unicode_SUITE_data/sftp瑞点.txt b/lib/ssh/test/ssh_unicode_SUITE_data/sftp瑞点.txt
new file mode 100644
index 0000000000..3eaaddca21
--- /dev/null
+++ b/lib/ssh/test/ssh_unicode_SUITE_data/sftp瑞点.txt
@@ -0,0 +1 @@
+åäöÅÄÖ瑞語
diff --git a/lib/ssh/test/ssh_unicode_SUITE_data/ssh_host_dsa_key b/lib/ssh/test/ssh_unicode_SUITE_data/ssh_host_dsa_key
new file mode 100644
index 0000000000..51ab6fbd88
--- /dev/null
+++ b/lib/ssh/test/ssh_unicode_SUITE_data/ssh_host_dsa_key
@@ -0,0 +1,13 @@
+-----BEGIN DSA PRIVATE KEY-----
+MIIBuwIBAAKBgQCClaHzE2ul0gKSUxah5W0W8UiJLy4hXngKEqpaUq9SSdVdY2LK
+wVfKH1gt5iuaf1FfzOhsIC9G/GLnjYttXZc92cv/Gfe3gR+s0ni2++MX+T++mE/Q
+diltXv/Hp27PybS67SmiFW7I+RWnT2OKlMPtw2oUuKeztCe5UWjaj/y5FQIVAPLA
+l9RpiU30Z87NRAHY3NTRaqtrAoGANMRxw8UfdtNVR0CrQj3AgPaXOGE4d+G4Gp4X
+skvnCHycSVAjtYxebUkzUzt5Q6f/IabuLUdge3gXrc8BetvrcKbp+XZgM0/Vj2CF
+Ymmy3in6kzGZq7Fw1sZaku6AOU8vLa5woBT2vAcHLLT1bLAzj7viL048T6MfjrOP
+ef8nHvACgYBhDWFQJ1mf99sg92LalVq1dHLmVXb3PTJDfCO/Gz5NFmj9EZbAtdah
+/XcF3DeRF+eEoz48wQF/ExVxSMIhLdL+o+ElpVhlM7Yii+T7dPhkQfEul6zZXu+U
+ykSTXYUbtsfTNRFQGBW2/GfnEc0mnIxfn9v10NEWMzlq5z9wT9P0CgIVAN4wtL5W
+Lv62jKcdskxNyz2NQoBx
+-----END DSA PRIVATE KEY-----
+
diff --git a/lib/ssh/test/ssh_unicode_SUITE_data/ssh_host_dsa_key.pub b/lib/ssh/test/ssh_unicode_SUITE_data/ssh_host_dsa_key.pub
new file mode 100644
index 0000000000..4dbb1305b0
--- /dev/null
+++ b/lib/ssh/test/ssh_unicode_SUITE_data/ssh_host_dsa_key.pub
@@ -0,0 +1,11 @@
+---- BEGIN SSH2 PUBLIC KEY ----
+AAAAB3NzaC1kc3MAAACBAIKVofMTa6XSApJTFqHlbRbxSIkvLiFeeAoSqlpSr1JJ1V1j
+YsrBV8ofWC3mK5p/UV/M6GwgL0b8YueNi21dlz3Zy/8Z97eBH6zSeLb74xf5P76YT9B2
+KW1e/8enbs/JtLrtKaIVbsj5FadPY4qUw+3DahS4p7O0J7lRaNqP/LkVAAAAFQDywJfU
+aYlN9GfOzUQB2NzU0WqrawAAAIA0xHHDxR9201VHQKtCPcCA9pc4YTh34bganheyS+cI
+fJxJUCO1jF5tSTNTO3lDp/8hpu4tR2B7eBetzwF62+twpun5dmAzT9WPYIViabLeKfqT
+MZmrsXDWxlqS7oA5Ty8trnCgFPa8BwcstPVssDOPu+IvTjxPox+Os495/yce8AAAAIBh
+DWFQJ1mf99sg92LalVq1dHLmVXb3PTJDfCO/Gz5NFmj9EZbAtdah/XcF3DeRF+eEoz48
+wQF/ExVxSMIhLdL+o+ElpVhlM7Yii+T7dPhkQfEul6zZXu+UykSTXYUbtsfTNRFQGBW2
+/GfnEc0mnIxfn9v10NEWMzlq5z9wT9P0Cg==
+---- END SSH2 PUBLIC KEY ----
diff --git a/lib/ssh/vsn.mk b/lib/ssh/vsn.mk
index 90f09471c9..9ffc59dbaf 100644
--- a/lib/ssh/vsn.mk
+++ b/lib/ssh/vsn.mk
@@ -1,5 +1,5 @@
#-*-makefile-*- ; force emacs to enter makefile-mode
-SSH_VSN = 2.1.8
+SSH_VSN = 3.0.1
APP_VSN = "ssh-$(SSH_VSN)"