diff options
Diffstat (limited to 'lib/ssh')
34 files changed, 776 insertions, 111 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/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..5d5f2e5b91 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="kernel: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.erl b/lib/ssh/src/ssh.erl index 2685b1553b..9f571adba2 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -59,8 +59,8 @@ stop() -> application:stop(ssh). %%-------------------------------------------------------------------- --spec connect(string(), integer(), proplists:proplists()) -> {ok, pid()} | {error, term()}. --spec connect(string(), integer(), proplists:proplists(), timeout()) -> {ok, pid()} | {error, term()}. +-spec connect(string(), integer(), proplists:proplist()) -> {ok, pid()} | {error, term()}. +-spec connect(string(), integer(), proplists:proplist(), timeout()) -> {ok, pid()} | {error, term()}. %% %% Description: Starts an ssh connection. %%-------------------------------------------------------------------- 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_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 2c8e515a14..77453e8fd7 100644 --- a/lib/ssh/src/ssh_cli.erl +++ b/lib/ssh/src/ssh_cli.erl @@ -170,10 +170,19 @@ handle_msg({Group, get_unicode_state}, State) -> {ok, State}; handle_msg({Group, tty_geometry}, #state{group = Group, - pty = #ssh_pty{width=Width, - height=Height} + pty = Pty } = State) -> - Group ! {self(),tty_geometry,{Width,Height}}, + case Pty of + #ssh_pty{width=Width,height=Height} -> + Group ! {self(),tty_geometry,{Width,Height}}; + _ -> + %% This is a dirty fix of the problem with the otp ssh:shell + %% client. That client will not allocate a tty, but someone + %% asks for the tty_geometry just before every erlang prompt. + %% If that question is not answered, there is a 2 sec timeout + %% Until the prompt is seen by the user at the client side ... + Group ! {self(),tty_geometry,{0,0}} + end, {ok,State}; handle_msg({Group, Req}, #state{group = Group, buf = Buf, pty = Pty, @@ -349,7 +358,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 03dddae3c8..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, diff --git a/lib/ssh/src/ssh_file.erl b/lib/ssh/src/ssh_file.erl index 21cdedc156..5692138a8a 100644 --- a/lib/ssh/src/ssh_file.erl +++ b/lib/ssh/src/ssh_file.erl @@ -65,7 +65,7 @@ is_auth_key(Key, User,Opts) -> %% Used by client is_host_key(Key, PeerName, Algorithm, Opts) -> - case lookup_host_key(PeerName, Algorithm, Opts) of + case lookup_host_key(Key, PeerName, Algorithm, Opts) of {ok, Key} -> true; _ -> @@ -121,9 +121,9 @@ decode_ssh_file(Pem, Password) -> %% return {ok, Key(s)} or {error, not_found} %% -lookup_host_key(Host, Alg, Opts) -> +lookup_host_key(KeyToMatch, Host, Alg, Opts) -> Host1 = replace_localhost(Host), - do_lookup_host_key(Host1, Alg, Opts). + do_lookup_host_key(KeyToMatch, Host1, Alg, Opts). add_host_key(Host, Key, Opts) -> @@ -204,10 +204,10 @@ replace_localhost("localhost") -> replace_localhost(Host) -> Host. -do_lookup_host_key(Host, Alg, Opts) -> +do_lookup_host_key(KeyToMatch, Host, Alg, Opts) -> case file:open(file_name(user, "known_hosts", Opts), [read, binary]) of {ok, Fd} -> - Res = lookup_host_key_fd(Fd, Host, Alg), + Res = lookup_host_key_fd(Fd, KeyToMatch, Host, Alg), file:close(Fd), {ok, Res}; {error, enoent} -> {error, not_found}; @@ -228,16 +228,16 @@ identity_pass_phrase('ssh-rsa') -> identity_pass_phrase("ssh-rsa") -> rsa_pass_phrase. -lookup_host_key_fd(Fd, Host, KeyType) -> +lookup_host_key_fd(Fd, KeyToMatch, Host, KeyType) -> case io:get_line(Fd, '') of eof -> {error, not_found}; Line -> case ssh_decode_line(Line, known_hosts) of [{Key, Attributes}] -> - handle_host(Fd, Host, proplists:get_value(hostnames, Attributes), Key, KeyType); + handle_host(Fd, KeyToMatch, Host, proplists:get_value(hostnames, Attributes), Key, KeyType); [] -> - lookup_host_key_fd(Fd, Host, KeyType) + lookup_host_key_fd(Fd, KeyToMatch, Host, KeyType) end end. @@ -248,13 +248,13 @@ ssh_decode_line(Line, Type) -> [] end. -handle_host(Fd, Host, HostList, Key, KeyType) -> +handle_host(Fd, KeyToMatch, Host, HostList, Key, KeyType) -> Host1 = host_name(Host), - case lists:member(Host1, HostList) and key_match(Key, KeyType) of - true -> + case lists:member(Host1, HostList) andalso key_match(Key, KeyType) of + true when KeyToMatch == Key -> Key; - false -> - lookup_host_key_fd(Fd, Host, KeyType) + _ -> + lookup_host_key_fd(Fd, KeyToMatch, Host, KeyType) end. host_name(Atom) when is_atom(Atom) -> diff --git a/lib/ssh/src/ssh_message.erl b/lib/ssh/src/ssh_message.erl index 7bd0375521..8d6c77c0ed 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, @@ -315,8 +315,8 @@ decode(<<?BYTE(?SSH_MSG_CHANNEL_DATA), ?UINT32(Recipient), ?UINT32(Len), Data:Le recipient_channel = Recipient, data = Data }; -decode(<<?BYTE(?SSH_MSG_CHANNEL_EXTENDED_DATA), ?UINT32(Recipient), - ?UINT32(DataType), Data/binary>>) -> +decode(<<?BYTE(?SSH_MSG_CHANNEL_EXTENDED_DATA), ?UINT32(Recipient), + ?UINT32(DataType), ?UINT32(Len), Data:Len/binary>>) -> #ssh_msg_channel_extended_data{ recipient_channel = Recipient, data_type_code = DataType, @@ -380,27 +380,30 @@ decode(<<?BYTE(?SSH_MSG_USERAUTH_BANNER), language = Lang }; +decode(<<?BYTE(?SSH_MSG_USERAUTH_INFO_REQUEST), ?UINT32(Len0), Name:Len0/binary, + ?UINT32(Len1), Inst:Len1/binary, ?UINT32(Len2), Lang:Len2/binary, + ?UINT32(NumPromtps), Data/binary>>) -> + #ssh_msg_userauth_info_request{ + name = Name, + instruction = Inst, + language_tag = Lang, + num_prompts = NumPromtps, + data = Data}; + +%%% Unhandled message, also masked by same 1:st byte value as ?SSH_MSG_USERAUTH_INFO_REQUEST: decode(<<?BYTE(?SSH_MSG_USERAUTH_PK_OK), ?UINT32(Len), Alg:Len/binary, KeyBlob/binary>>) -> #ssh_msg_userauth_pk_ok{ algorithm_name = Alg, key_blob = KeyBlob }; +%%% Unhandled message, also masked by same 1:st byte value as ?SSH_MSG_USERAUTH_INFO_REQUEST: decode(<<?BYTE(?SSH_MSG_USERAUTH_PASSWD_CHANGEREQ), ?UINT32(Len0), Prompt:Len0/binary, ?UINT32(Len1), Lang:Len1/binary>>) -> #ssh_msg_userauth_passwd_changereq{ prompt = Prompt, languge = Lang }; -decode(<<?BYTE(?SSH_MSG_USERAUTH_INFO_REQUEST), ?UINT32(Len0), Name:Len0/binary, - ?UINT32(Len1), Inst:Len1/binary, ?UINT32(Len2), Lang:Len2/binary, - ?UINT32(NumPromtps), Data/binary>>) -> - #ssh_msg_userauth_info_request{ - name = Name, - instruction = Inst, - language_tag = Lang, - num_prompts = NumPromtps, - data = Data}; decode(<<?BYTE(?SSH_MSG_USERAUTH_INFO_RESPONSE), ?UINT32(Num), Data/binary>>) -> #ssh_msg_userauth_info_response{ @@ -424,8 +427,9 @@ decode(<<?BYTE(?SSH_MSG_KEX_DH_GEX_REQUEST_OLD), ?UINT32(N)>>) -> #ssh_msg_kex_dh_gex_request_old{ n = N }; -decode(<<?BYTE(?SSH_MSG_KEX_DH_GEX_GROUP), ?UINT32(Len0), Prime:Len0/big-signed-integer, - ?UINT32(Len1), Generator:Len1/big-signed-integer>>) -> +decode(<<?BYTE(?SSH_MSG_KEX_DH_GEX_GROUP), + ?UINT32(Len0), Prime:Len0/big-signed-integer-unit:8, + ?UINT32(Len1), Generator:Len1/big-signed-integer-unit:8>>) -> #ssh_msg_kex_dh_gex_group{ p = Prime, g = Generator 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..52665635f0 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 @@ -675,7 +673,7 @@ 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 + Path = case lists:droplast(AccPathComps0) of [] -> ""; AccPathComps -> @@ -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_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/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 8186f39888..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 = 3.0 +SSH_VSN = 3.0.1 APP_VSN = "ssh-$(SSH_VSN)" |