ssh_transport.erl
来自「OTP是开放电信平台的简称」· ERL 代码 · 共 1,781 行 · 第 1/4 页
ERL
1,781 行
%% ``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 via the world wide web 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.%% %% The Initial Developer of the Original Code is Ericsson Utvecklings AB.%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings%% AB. All Rights Reserved.''%% %% $Id$%%%%% Description: SSH transport protocol-module(ssh_transport).-import(lists, [reverse/1, map/2, foreach/2, foldl/3]).-include("ssh.hrl").-include_lib("kernel/include/inet.hrl").-export([connect/1, connect/2, connect/3, close/1]).-export([listen/2, listen/3, listen/4, stop_listener/1]).-export([debug/2, debug/3, debug/4]).-export([ignore/2]).-export([disconnect/2, disconnect/3, disconnect/4]).-export([client_init/4, server_init/5]).-export([ssh_init/3]). % , server_hello/4]).-export([service_request/2, service_accept/2]).-export([get_session_id/1]).-export([peername/1]).%% io wrappers-export([yes_no/2, read_password/2]).-define(DEFAULT_TIMEOUT, 5000).%% debug flags-define(DBG_ALG, true).-define(DBG_KEX, false).-define(DBG_CRYPTO, false).-define(DBG_PACKET, false).-define(DBG_MESSAGE, true).-define(DBG_BIN_MESSAGE, false).-define(DBG_MAC, false).-define(DBG_ZLIB, true).-record(alg, { kex, hkey, send_mac, recv_mac, encrypt, decrypt, compress, decompress, c_lng, s_lng }). -record(ssh, { state, %% what it's waiting for role, %% client | server peer, %% string version of peer address c_vsn, %% client version {Major,Minor} s_vsn, %% server version {Major,Minor} c_version, %% client version string s_version, %% server version string c_keyinit, %% binary payload of kexinit packet s_keyinit, %% binary payload of kexinit packet algorithms, %% new algorithms (SSH_MSG_KEXINIT) kex, %% key exchange algorithm hkey, %% host key algorithm key_cb, %% Private/Public key callback module io_cb, %% Interaction callback module send_mac=none, %% send MAC algorithm send_mac_key, %% key used in send MAC algorithm send_mac_size = 0, recv_mac=none, %% recv MAC algorithm recv_mac_key, %% key used in recv MAC algorithm recv_mac_size = 0, encrypt = none, %% encrypt algorithm encrypt_keys, %% encrypt keys encrypt_block_size = 8, decrypt = none, %% decrypt algorithm decrypt_keys, %% decrypt keys decrypt_block_size = 8, compress = none, decompress = none, c_lng=none, %% client to server languages s_lng=none, %% server to client languages user_ack = true, %% client timeout = infinity, shared_secret, %% K from key exchange exchanged_hash, %% H from key exchange session_id, %% same as FIRST exchanged_hash opts = [] }).transport_messages() -> [ {ssh_msg_disconnect, ?SSH_MSG_DISCONNECT, [uint32,string,string]}, {ssh_msg_ignore, ?SSH_MSG_IGNORE, [string]}, {ssh_msg_unimplemented, ?SSH_MSG_UNIMPLEMENTED, [uint32]}, {ssh_msg_debug, ?SSH_MSG_DEBUG, [boolean, string, string]}, {ssh_msg_service_request, ?SSH_MSG_SERVICE_REQUEST, [string]}, {ssh_msg_service_accept, ?SSH_MSG_SERVICE_ACCEPT, [string]}, {ssh_msg_kexinit, ?SSH_MSG_KEXINIT, [cookie, name_list, name_list, name_list, name_list, name_list, name_list, name_list, name_list, name_list, name_list, boolean, uint32]}, {ssh_msg_newkeys, ?SSH_MSG_NEWKEYS, []} ].kexdh_messages() -> [ {ssh_msg_kexdh_init, ?SSH_MSG_KEXDH_INIT, [mpint]}, {ssh_msg_kexdh_reply, ?SSH_MSG_KEXDH_REPLY, [binary, mpint, binary]} ].kex_dh_gex_messages() -> [ {ssh_msg_kex_dh_gex_request, ?SSH_MSG_KEX_DH_GEX_REQUEST, [uint32, uint32, uint32]}, {ssh_msg_kex_dh_gex_request_old, ?SSH_MSG_KEX_DH_GEX_REQUEST_OLD, [uint32]}, {ssh_msg_kex_dh_gex_group, ?SSH_MSG_KEX_DH_GEX_GROUP, [mpint, mpint]}, {ssh_msg_kex_dh_gex_init, ?SSH_MSG_KEX_DH_GEX_INIT, [mpint]}, {ssh_msg_kex_dh_gex_reply, ?SSH_MSG_KEX_DH_GEX_REPLY, [binary, mpint, binary]} ].yes_no(SSH, Prompt) when pid(SSH) -> {ok, CB} = call(SSH, {get_cb, io}), CB:yes_no(Prompt);yes_no(SSH, Prompt) when record(SSH,ssh) -> (SSH#ssh.io_cb):yes_no(Prompt).read_password(SSH, Prompt) when pid(SSH) -> {ok, CB} = call(SSH, {get_cb, io}), CB:read_password(Prompt);read_password(SSH, Prompt) when record(SSH,ssh) -> (SSH#ssh.io_cb):read_password(Prompt).%% read_line(SSH, Prompt) when pid(SSH) ->%% {ok, CB} = call(SSH, {get_cb, io}),%% CB:read_line(Prompt);%% read_line(SSH, Prompt) when record(SSH,ssh) ->%% (SSH#ssh.io_cb):read_line(Prompt).peername(SSH) -> call(SSH, peername).call(SSH, Req) -> Ref = make_ref(), SSH ! {ssh_call, [self()|Ref], Req}, receive {Ref, Reply} -> Reply end.connect(Host) -> connect(Host, []).connect(Host, Opts) -> connect(Host, 22, Opts).connect(Host,Port,Opts) -> Pid = spawn_link(?MODULE, client_init, [self(), Host, Port, Opts]), receive {Pid, Reply} -> Reply end.listen(UserFun, Port) -> listen(UserFun, Port, []).listen(UserFun, Port, Opts) -> listen(UserFun, any, Port, Opts).listen(UserFun, Addr, Port, Opts) -> Pid = spawn_link(?MODULE, server_init, [UserFun, Addr, Port, Opts, self()]), receive ok -> {ok, Pid} end.stop_listener(Pid) when is_pid(Pid) -> Ref = erlang:monitor(process, Pid), Pid ! {Pid, stop}, receive {'DOWN', Ref, process, Pid, normal} -> ok; {'DOWN', Ref, process, Pid, Error} -> {error, Error} after 2000 -> {error, timeout} end. debug(SSH, Message) -> debug(SSH, true, Message, "en").debug(SSH, Message, Lang) -> debug(SSH, true, Message, Lang).debug(SSH, Display, Message, Lang) -> SSH ! {ssh_msg, self(), #ssh_msg_debug { always_display = Display, message = Message, language = Lang }}.ignore(SSH, Data) -> SSH ! {ssh_msg, self(), #ssh_msg_ignore { data = Data }}.disconnect(SSH, Code) -> disconnect(SSH, Code, "", "").disconnect(SSH, Code, _Msg) -> disconnect(SSH, Code, "", "").disconnect(SSH, Code, Msg, Lang) -> SSH ! {ssh_msg, self(), #ssh_msg_disconnect { code = Code, description = Msg, language = Lang }}.close(SSH) -> call(SSH, close).service_accept(SSH, Name) -> SSH ! {ssh_msg, self(), #ssh_msg_service_accept { name = Name }}.service_request(SSH, Name) -> SSH ! {ssh_msg, self(), #ssh_msg_service_request { name = Name}}, receive {ssh_msg, SSH, R} when record(R, ssh_msg_service_accept) -> ok; {ssh_msg, SSH, R} when record(R, ssh_msg_disconnect) -> {error, R}; Other -> {error, Other} end. client_init(User, Host, Port, Opts) -> IfAddr = proplists:get_value(ifaddr, Opts, any), Tmo = proplists:get_value(connect_timeout, Opts, ?DEFAULT_TIMEOUT), NoDelay= proplists:get_value(tcp_nodelay, Opts, false), case gen_tcp:connect(Host, Port, [{packet,line}, {active,once}, {nodelay, NoDelay}, {ifaddr,IfAddr}], Tmo) of {ok, S} -> SSH = ssh_init(S, client, Opts), Peer = if tuple(Host) -> inet_parse:ntoa(Host); atom(Host) -> atom_to_list(Host); list(Host) -> Host end, case client_hello(S, User, SSH#ssh { peer = Peer }, Tmo) of {error, E} -> User ! {self(), {error, E}}; _ -> ok end; Error -> User ! {self(), Error} end.server_init(UserFun, Addr, Port, Opts, From) -> Serv = fun(S) -> SSH = ssh_init(S, server, Opts), Self = self(), User = UserFun(Self), server_hello(S, User, SSH, proplists:get_value(timeout, Opts, ?DEFAULT_TIMEOUT)) end, NoDelay = proplists:get_value(tcp_nodelay, Opts, false), ssh_tcp_wrap:server(Port, [{packet,line}, {active,once}, {ifaddr,Addr}, {reuseaddr,true}, {nodelay, NoDelay}], Serv, From).%%%% Initialize basic ssh system%%ssh_init(S, Role, Opts) -> ssh_bits:install_messages(transport_messages()), {A,B,C} = erlang:now(), random:seed(A, B, C), put(send_sequence, 0), put(recv_sequence, 0), case Role of client -> Vsn = proplists:get_value(vsn, Opts, {2,0}), Version = format_version(Vsn), send_version(S, Version), #ssh { role = Role, c_vsn = Vsn, c_version=Version, key_cb = proplists:get_value(key_cb, Opts, ssh_file), io_cb = case proplists:get_value(user_interaction, Opts, true) of true -> ssh_io; false -> ssh_no_io end, opts = Opts }; server -> Vsn = proplists:get_value(vsn, Opts, {1,99}), Version = format_version(Vsn), send_version(S, Version), #ssh { role = Role, s_vsn = Vsn, s_version=Version, key_cb = proplists:get_value(key_cb, Opts, ssh_file), io_cb = proplists:get_value(io_cb, Opts, ssh_io), opts = Opts } end.ssh_setopts(NewOpts, SSH) -> Opts = SSH#ssh.opts, SSH#ssh { opts = NewOpts ++ Opts }.format_version({Major,Minor}) -> "SSH-"++integer_to_list(Major)++"."++integer_to_list(Minor)++"-Erlang". %% choose algorithmskex_init(SSH) -> Random = ssh_bits:random(16), Comp = case proplists:get_value(compression, SSH#ssh.opts, none) of zlib -> ["zlib", "none"]; none -> ["none", "zlib"] end, case SSH#ssh.role of client -> #ssh_msg_kexinit { cookie = Random, kex_algorithms = ["diffie-hellman-group1-sha1"], server_host_key_algorithms = ["ssh-rsa", "ssh-dss"], encryption_algorithms_client_to_server = ["3des-cbc"], encryption_algorithms_server_to_client = ["3des-cbc"], mac_algorithms_client_to_server = ["hmac-sha1"], mac_algorithms_server_to_client = ["hmac-sha1"], compression_algorithms_client_to_server = Comp, compression_algorithms_server_to_client = Comp, languages_client_to_server = [], languages_server_to_client = [] }; server -> #ssh_msg_kexinit { cookie = Random, kex_algorithms = ["diffie-hellman-group1-sha1"], server_host_key_algorithms = ["ssh-dss"], encryption_algorithms_client_to_server = ["3des-cbc"], encryption_algorithms_server_to_client = ["3des-cbc"], mac_algorithms_client_to_server = ["hmac-sha1"], mac_algorithms_server_to_client = ["hmac-sha1"], compression_algorithms_client_to_server = Comp, compression_algorithms_server_to_client = Comp, languages_client_to_server = [], languages_server_to_client = [] } end.server_hello(S, User, SSH, Timeout) -> receive {tcp, S, V = "SSH-"++_} -> Version = trim_tail(V), ?dbg(true, "client version: ~p\n",[Version]), case string:tokens(Version, "-") of [_, "2.0" | _] -> negotiate(S, User, SSH#ssh { c_vsn = {2,0}, c_version = Version}, false); [_, "1.99" | _] -> negotiate(S, User, SSH#ssh { c_vsn = {2,0}, c_version = Version}, false); [_, "1.3" | _] -> negotiate(S, User, SSH#ssh { c_vsn = {1,3}, c_version = Version}, false); [_, "1.5" | _] -> negotiate(S, User, SSH#ssh { c_vsn = {1,5}, c_version = Version}, false); _ -> exit(unknown_version) end; {tcp, S, _Line} -> ?dbg(true, "info: ~p\n", [_Line]), inet:setopts(S, [{active, once}]),
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?