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 + -
显示快捷键?