ssh_userauth.erl

来自「OTP是开放电信平台的简称」· ERL 代码 · 共 469 行

ERL
469
字号
%% ``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 user authenication-module(ssh_userauth).-export([auth/3, auth_remote/3, disconnect/2,	 reg_user_auth_server/0, get_auth_users/0,	 get_user_from_cm/1]).-include("ssh.hrl").-include("ssh_userauth.hrl").-include("PKCS-1.hrl").-define(PREFERRED_PK_ALG, ssh_rsa).other_alg(ssh_rsa) -> ssh_dsa;other_alg(ssh_dsa) -> ssh_rsa.auth(SSH, Service, Opts) ->    case user_name(Opts) of	{ok, User} ->	    _Failure = none(SSH, Service, User),	    AlgM = proplists:get_value(public_key_alg,				       Opts, ?PREFERRED_PK_ALG),	    case public_key(SSH, Service, User, Opts, AlgM) of		ok -> ok;		{failure, _F1} ->		    case public_key(SSH, Service, User, Opts,				    other_alg(AlgM)) of			ok ->			    ok;			{failure, _F2} ->			    passwd(SSH, Service, User, Opts);			Error -> Error		    end;		Error -> Error	    end;	Error -> Error    end.%% Find user nameuser_name(Opts) ->    Env = case os:type() of	      {win32, _} -> "USERNAME";	      {unix, _} -> "LOGNAME"	  end,    case proplists:get_value(user, Opts, os:getenv(Env)) of	false ->	    case os:getenv("USER") of		false -> {error, no_user};		User1 -> {ok, User1}	    end;	User2 -> {ok, User2}    end.%% Public-key authenticationpublic_key(SSH, Service, User, Opts, AlgM) ->    SSH ! {ssh_install, userauth_pk_messages()},    Alg = AlgM:alg_name(),    case ssh_file:private_identity_key(Alg, Opts) of	{ok, PrivKey} ->	    PubKeyBlob = ssh_file:encode_public_key(PrivKey),	    SigData = build_sig_data(SSH, User, Service, Alg, PubKeyBlob),	    Sig = AlgM:sign(PrivKey, SigData),	    SigBlob = list_to_binary([?string(Alg), ?binary(Sig)]),	    SSH ! {ssh_msg, self(),		   #ssh_msg_userauth_request{				  user = User,				  service = Service,				  method = "publickey",				  data = [?TRUE,					  ?string(Alg),					  ?binary(PubKeyBlob),					  ?binary(SigBlob)]}},	    public_key_reply(SSH);	{error, enoent} ->	    {failure, enoent};	Error ->	    Error    end.%% Send none to find out supported authentication methodsnone(SSH, Service, User) ->    SSH ! {ssh_install, 	   userauth_messages() ++ userauth_passwd_messages()},    SSH ! {ssh_msg, self(), 	   #ssh_msg_userauth_request { user = User,				       service = Service,				       method = "none",				       data = <<>> }},    passwd_reply(SSH).%% Password authentication for ssh-connectionpasswd(SSH, Service, User, Opts) ->     SSH ! {ssh_install, 	   userauth_messages() ++ userauth_passwd_messages()},    do_password(SSH, Service, User, Opts).check_password(User, Password, Opts) ->    case proplists:get_value(pwdfun, Opts) of	undefined ->	    Static = get_password_option(Opts, User),	    if		Password == Static ->		    true;		true ->		    {false, "Bad password"}	    end;	PW ->	    PW(User, Password)    end.disconnect(Handle, Opts) ->     case proplists:get_value(disconnectfun, Opts) of	undefined ->	     ok;	 DF ->	     DF(Handle)     end.connected_fun(User, PeerAddr, Opts, Method) ->    reg_user_auth(User),    case proplists:get_value(connectfun, Opts) of	undefined ->	    ok;	CF ->	    CF(User, PeerAddr, Method)    end.infofun(User, Opts, Reason) ->    case proplists:get_value(infofun, Opts) of	undefined ->	    error_logger:info_msg("~p failed to login: ~p~n", [User, Reason]);	FF ->	    FF(User, Reason)    end.%% Reason for failfun() is a structured tuple, the following values are %% possible% {passwd, Str}   - if the password was bad % {authmethod, none}  - if the client attempted to use none as method% {authmethod, OtherStr} - if the client tried to use an unknown authmeth% {error, key_unacceptable} - if a the key sent to us wasn't verifiable% {error, inconsistent_key} - we got a broken DSA key% {error, invalid_signature} - again, broken key% {error, {{openerr, PosixAtom}, {file, Filename}}} - if we failed to open the %                                                     file containing the user keys% {error, nouserdir}   - No dir configured for user which contains keysfailfun(User, Opts, Reason) ->    case proplists:get_value(failfun, Opts) of	undefined ->	    error_logger:format("~p failed to login: ~p~n", [User, Reason]);	FF ->	    FF(User, Reason)    end.get_password_option(Opts, User) ->    Passwords = proplists:get_value(user_passwords, Opts, []),    case lists:keysearch(User, 1, Passwords) of	{value, {User, Pw}} -> Pw;	false -> proplists:get_value(password, Opts, false)    end.do_password(SSH, Service, User, Opts) ->    Password = case proplists:get_value(password, Opts) of		   undefined -> ssh_transport:read_password(SSH, "ssh password: ");		   PW -> PW	       end,    ?dbg(true, "do_password: User=~p Password=~p\n", [User, Password]),    SSH ! {ssh_msg, self(), 	   #ssh_msg_userauth_request { user = User,				       service = Service,				       method = "password",				       data =				       <<?BOOLEAN(?FALSE),					?STRING(list_to_binary(Password))>> }},    passwd_reply(SSH).public_key_reply(SSH) ->    receive	{ssh_msg, SSH, R} when record(R, ssh_msg_userauth_success) ->	    ok;	{ssh_msg, SSH, R} when record(R, ssh_msg_userauth_failure) ->	    {failure,	     string:tokens(R#ssh_msg_userauth_failure.authentications, ",")};	{ssh_msg, SSH, R} when record(R, ssh_msg_userauth_banner) ->	    io:format("~w", [R#ssh_msg_userauth_banner.message]),	    public_key_reply(SSH);	{ssh_msg, SSH, R} when record(R, ssh_msg_disconnect) ->	    {error, disconnected};	_Other ->	    ?dbg(true, "public_key_reply: Other=~w\n", [_Other]),	    {error, unknown_msg}    end.passwd_reply(SSH) ->    receive	{ssh_msg, SSH, R} when record(R, ssh_msg_userauth_success) ->	    ok;	{ssh_msg, SSH, R} when record(R, ssh_msg_userauth_failure) ->	    {failure,	     string:tokens(R#ssh_msg_userauth_failure.authentications, ",")};	{ssh_msg, SSH, R} when record(R, ssh_msg_userauth_banner) ->	    io:format("~w", [R#ssh_msg_userauth_banner.message]),	    passwd_reply(SSH);	{ssh_msg, SSH, R} when record(R, ssh_msg_userauth_passwd_changereq) ->	    {error, R};	{ssh_msg, SSH, R} when record(R, ssh_msg_disconnect) ->	    {error, disconnected};	Other ->	    ?dbg(true, "passwd_reply: Other=~w\n", [Other]),	    self() ! Other,	    {error, unknown_msg}    end.auth_remote(SSH, Service, Opts) ->    SSH ! {ssh_install, 	   userauth_messages() ++ userauth_passwd_messages()},    SSH ! {self(), ok},    ssh_transport:service_accept(SSH, "ssh-userauth"),    do_auth_remote(SSH, Service, Opts).%% validate_password(User, Password, Opts) ->%%     OurPwd = get_password_option(Opts, User),%%     Password == OurPwd.do_auth_remote(SSH, Service, Opts) ->    receive	{ssh_msg, SSH, #ssh_msg_userauth_request { user = User,						   service = Service,						   method = "password",						   data = Data}} ->	    <<_:8, ?UINT32(Sz), BinPwd:Sz/binary>> = Data,	    Password = binary_to_list(BinPwd), 	    %% If we need to support PAM session mgmt this 	    %% function needs to return an opaque handle back to 	    %% to ssh_cm so that the session can be ended properly 	    %% at logout 	    Ret =  check_password(User, Password, Opts), 	    case Ret of 		true -> 		    {ok, Peer} = ssh_transport:peername(SSH), 		    connected_fun(User, Peer, Opts, "password"), 		    SSH ! {ssh_msg, self(), #ssh_msg_userauth_success{}}, 		    {ok, {undefined, User}}; 		{true, Handle} -> 		    {ok, Peer} = ssh_transport:peername(SSH), 		    connected_fun(User, Peer, Opts, "password"), 		    SSH ! {ssh_msg, self(), #ssh_msg_userauth_success{}}, 		    {ok, Handle}; 		{false, Str} -> 		    infofun(User, Opts, {passwd, Str}),		    SSH ! {ssh_msg, self(), #ssh_msg_userauth_failure {					  authentications = "",					  partial_success = false}},		    do_auth_remote(SSH, Service, Opts)	    end;	{ssh_msg, SSH, #ssh_msg_userauth_request { user = _User,						   service = Service,						   method = "none",						   data = _Data}} ->	    SSH ! {ssh_msg, self(), #ssh_msg_userauth_failure {				  authentications = "publickey,password",				  partial_success = false}},	    do_auth_remote(SSH, Service, Opts);	{ssh_msg, SSH, #ssh_msg_userauth_request  { user = User,						    service = Service,						    method = "publickey",						    data = Data}} ->	    <<?BOOLEAN(HaveSig), ?UINT32(ALen), BAlg:ALen/binary, 	     ?UINT32(KLen), KeyBlob:KLen/binary, SigWLen/binary>> = Data,	    Alg = binary_to_list(BAlg),	    %% is ssh_file the proper module?	    %% shouldn't this fun be in a ssh_key.erl? 	    case HaveSig of 		?TRUE ->		    case verify_sig(SSH, User, Service, Alg,				    KeyBlob, SigWLen, Opts) of			ok -> 			    {ok, Peer} = ssh_transport:peername(SSH),			    SSH ! {ssh_msg, self(),				   #ssh_msg_userauth_success{}}, 			    connected_fun(User, Peer, Opts, "publickey"), 			    {ok, {undefined, User}}; 			{error, ERR} -> 			    %% we have a file, but failed to open/read it 			    failfun(User, Opts, ERR),			    SSH ! {ssh_msg, self(),				   #ssh_msg_userauth_failure{						  authentications="publickey,password",						  partial_success = false}},			    do_auth_remote(SSH, Service, Opts)		    end;		?FALSE ->		    SSH ! {ssh_install, userauth_pk_messages()},		    SSH ! {ssh_msg, self(),			   #ssh_msg_userauth_pk_ok{					  algorithm_name = Alg,					  key_blob = KeyBlob}},		    do_auth_remote(SSH, Service, Opts)	    end;	{ssh_msg, SSH, #ssh_msg_userauth_request  { user = User,						    service = Service,						    method = Other,						    data = _Data}} ->	    infofun(User, Opts, {authmethod, Other}),	    SSH ! {ssh_msg, self(),		   #ssh_msg_userauth_failure {				  authentications = "publickey,password",				  partial_success = false}},	    do_auth_remote(SSH, Service, Opts);	Other ->	    ?dbg(true, "Other ~p~n", [Other]),	    {error, Other}    end.alg_to_module("ssh-dss") ->    ssh_dsa;alg_to_module("ssh-rsa") ->    ssh_rsa.%% make a signature for PGP user authbuild_sig_data(SSH, User, Service, Alg, KeyBlob) ->    %% {P1,P2} = Key#ssh_key.public,    %% EncKey = ssh_bits:encode([Alg,P1,P2], [string, mpint, mpint]),    SessionID = ssh_transport:get_session_id(SSH),    Sig = [?binary(SessionID),	   ?SSH_MSG_USERAUTH_REQUEST,	   ?string(User),	   ?string(Service),	   ?binary(<<"publickey">>),	   ?TRUE,	   ?string(Alg),	   ?binary(KeyBlob)],    list_to_binary(Sig).%% verify signature for PGP user authverify_sig(SSH, User, Service, Alg, KeyBlob, SigWLen, Opts) ->    {ok, Key} = ssh_file:decode_public_key_v2(KeyBlob, Alg),    case ssh_file:lookup_user_key(User, Alg, Opts) of	{ok, OurKey} ->	    case OurKey of		Key ->		    NewSig = build_sig_data(SSH, User, Service, Alg, KeyBlob),		    <<?UINT32(AlgSigLen), AlgSig:AlgSigLen/binary>> = SigWLen,		    <<?UINT32(AlgLen), _Alg:AlgLen/binary,		     ?UINT32(SigLen), Sig:SigLen/binary>> = AlgSig,		    M = alg_to_module(Alg),		    M:verify(OurKey, NewSig, Sig);		_ ->		    {error, key_unacceptable}	    end;	Error -> Error    end.%% the messages for userauthuserauth_messages() ->    [ {ssh_msg_userauth_request, ?SSH_MSG_USERAUTH_REQUEST,       [string, 	string, 	string, 	'...']},      {ssh_msg_userauth_failure, ?SSH_MSG_USERAUTH_FAILURE,       [string, 	boolean]},      {ssh_msg_userauth_success, ?SSH_MSG_USERAUTH_SUCCESS,       []},      {ssh_msg_userauth_banner, ?SSH_MSG_USERAUTH_BANNER,       [string, 	string]}].userauth_passwd_messages() ->    [       {ssh_msg_userauth_passwd_changereq, ?SSH_MSG_USERAUTH_PASSWD_CHANGEREQ,       [string, 	string]}     ].userauth_pk_messages() ->    [ {ssh_msg_userauth_pk_ok, ?SSH_MSG_USERAUTH_PK_OK,       [string, % algorithm name	string]} % key blob     ].%% user registry (which is a really simple server)reg_user_auth(User) ->    case whereis(?MODULE) of	undefined ->	    ok;	Pid ->	    Pid ! {reg, User, self()},	    receive {Pid, ok} -> ok end    end.get_auth_users() ->    Self = self(),    case whereis(?MODULE) of	undefined ->	    {error, no_user_auth_reg};	Pid ->	    Pid ! {get, Self},	    receive		{Pid, R} -> R	    end    end.get_user_from_cm(CM) ->    case get_auth_users() of	{ok, UsersCMs} ->	    case lists:keysearch(CM, 2, UsersCMs) of		{value, {User, CM}} ->		    User;		_ ->		    error	    end;	_ ->	    error    end.reg_user_auth_server() ->    case whereis(?MODULE) of	undefined -> ok;	_ -> unregister(?MODULE)    end,    Pid = spawn(fun() -> reg_user_auth_server_loop([]) end),    register(?MODULE, Pid).reg_user_auth_server_loop(Users) ->    receive	{get, From} ->	    NewUsers = [{U, P} || {U, P} <- Users,				  erlang:is_process_alive(P)],	    From ! {self(), {ok, NewUsers}},	    reg_user_auth_server_loop(NewUsers);	{reg, User, From} ->	    NewUsers = [{User, From} | Users],	    From ! {self(), ok},	    reg_user_auth_server_loop(NewUsers);	_ ->	    ok    end.

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?