aboutsummaryrefslogtreecommitdiffstats
path: root/lib/kernel/src/auth.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/kernel/src/auth.erl')
-rw-r--r--lib/kernel/src/auth.erl391
1 files changed, 391 insertions, 0 deletions
diff --git a/lib/kernel/src/auth.erl b/lib/kernel/src/auth.erl
new file mode 100644
index 0000000000..62c0bef0cc
--- /dev/null
+++ b/lib/kernel/src/auth.erl
@@ -0,0 +1,391 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1996-2009. 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%
+%%
+-module(auth).
+-behaviour(gen_server).
+
+-export([start_link/0]).
+
+%% Old documented interface - deprecated
+-export([is_auth/1, cookie/0, cookie/1, node_cookie/1, node_cookie/2]).
+-deprecated([{is_auth,1}, {cookie,'_'}, {node_cookie, '_'}]).
+
+%% New interface - meant for internal use within kernel only
+-export([get_cookie/0, get_cookie/1,
+ set_cookie/1, set_cookie/2,
+ sync_cookie/0,
+ print/3]).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3]).
+
+-define(COOKIE_ETS_PROTECTION, protected).
+
+-record(state, {
+ our_cookie, %% Our own cookie
+ other_cookies %% The send-cookies of other nodes
+ }).
+
+-include("../include/file.hrl").
+
+%%----------------------------------------------------------------------
+%% Exported functions
+%%----------------------------------------------------------------------
+
+start_link() ->
+ gen_server:start_link({local, auth}, auth, [], []).
+
+%%--Deprecated interface------------------------------------------------
+
+-spec is_auth(Node :: node()) -> 'yes' | 'no'.
+
+is_auth(Node) ->
+ case net_adm:ping(Node) of
+ pong -> yes;
+ pang -> no
+ end.
+
+-spec cookie() -> atom().
+
+cookie() ->
+ get_cookie().
+
+-spec cookie(Cookies :: [atom(),...] | atom()) -> 'true'.
+
+cookie([Cookie]) ->
+ set_cookie(Cookie);
+cookie(Cookie) ->
+ set_cookie(Cookie).
+
+-spec node_cookie(Cookies :: [atom(),...]) -> 'yes' | 'no'.
+
+node_cookie([Node, Cookie]) ->
+ node_cookie(Node, Cookie).
+
+-spec node_cookie(Node :: node(), Cookie :: atom()) -> 'yes' | 'no'.
+
+node_cookie(Node, Cookie) ->
+ set_cookie(Node, Cookie),
+ is_auth(Node).
+
+%%--"New" interface-----------------------------------------------------
+
+-spec get_cookie() -> atom().
+
+get_cookie() ->
+ get_cookie(node()).
+
+-spec get_cookie(Node :: node()) -> atom().
+
+get_cookie(_Node) when node() =:= nonode@nohost ->
+ nocookie;
+get_cookie(Node) ->
+ gen_server:call(auth, {get_cookie, Node}).
+
+-spec set_cookie(Cookie :: atom()) -> 'true'.
+
+set_cookie(Cookie) ->
+ set_cookie(node(), Cookie).
+
+-spec set_cookie(Node :: node(), Cookie :: atom()) -> 'true'.
+
+set_cookie(_Node, _Cookie) when node() =:= nonode@nohost ->
+ erlang:error(distribution_not_started);
+set_cookie(Node, Cookie) ->
+ gen_server:call(auth, {set_cookie, Node, Cookie}).
+
+-spec sync_cookie() -> any().
+
+sync_cookie() ->
+ gen_server:call(auth, sync_cookie).
+
+-spec print(Node :: node(), Format :: string(), Args :: [_]) -> 'ok'.
+
+print(Node,Format,Args) ->
+ (catch gen_server:cast({auth,Node},{print,Format,Args})).
+
+%%--gen_server callbacks------------------------------------------------
+
+init([]) ->
+ process_flag(trap_exit, true),
+ {ok, init_cookie()}.
+
+%% Opened is a list of servers we have opened up
+%% The net kernel will let all message to the auth server
+%% through as is
+
+handle_call({get_cookie, Node}, {_From,_Tag}, State) when Node =:= node() ->
+ {reply, State#state.our_cookie, State};
+handle_call({get_cookie, Node}, {_From,_Tag}, State) ->
+ case ets:lookup(State#state.other_cookies, Node) of
+ [{Node, Cookie}] ->
+ {reply, Cookie, State};
+ [] ->
+ {reply, State#state.our_cookie, State}
+ end;
+handle_call({set_cookie, Node, Cookie}, {_From,_Tag}, State)
+ when Node =:= node() ->
+ {reply, true, State#state{our_cookie = Cookie}};
+
+%%
+%% Happens when the distribution is brought up and
+%% Someone wight have set up the cookie for our new nodename.
+%%
+
+handle_call({set_cookie, Node, Cookie}, {_From,_Tag}, State) ->
+ ets:insert(State#state.other_cookies, {Node, Cookie}),
+ {reply, true, State};
+
+handle_call(sync_cookie, _From, State) ->
+ case ets:lookup(State#state.other_cookies,node()) of
+ [{_N,C}] ->
+ ets:delete(State#state.other_cookies,node()),
+ {reply, true, State#state{our_cookie = C}};
+ [] ->
+ {reply, true, State}
+ end;
+
+handle_call(echo, _From, O) ->
+ {reply, hello, O}.
+
+handle_cast({print,What,Args}, O) ->
+ %% always allow print outs
+ error_logger:error_msg(What,Args),
+ {noreply, O}.
+
+%% A series of bad messages that may come (from older distribution versions).
+
+handle_info({From,badcookie,net_kernel,{From,spawn,_M,_F,_A,_Gleader}}, O) ->
+ auth:print(node(From) ,"~n** Unauthorized spawn attempt to ~w **~n",
+ [node()]),
+ erlang:disconnect_node(node(From)),
+ {noreply, O};
+handle_info({From,badcookie,net_kernel,{From,spawn_link,_M,_F,_A,_Gleader}}, O) ->
+ auth:print(node(From),
+ "~n** Unauthorized spawn_link attempt to ~w **~n",
+ [node()]),
+ erlang:disconnect_node(node(From)),
+ {noreply, O};
+handle_info({_From,badcookie,ddd_server,_Mess}, O) ->
+ %% Ignore bad messages to the ddd server, they will be resent
+ %% If the authentication is succesful
+ {noreply, O};
+handle_info({From,badcookie,rex,_Msg}, O) ->
+ auth:print(getnode(From),
+ "~n** Unauthorized rpc attempt to ~w **~n",[node()]),
+ disconnect_node(node(From)),
+ {noreply, O};
+%% These two messages has to do with the old auth:is_auth() call (net_adm:ping)
+handle_info({From,badcookie,net_kernel,{'$gen_call',{From,Tag},{is_auth,_Node}}}, O) -> %% ho ho
+ From ! {Tag, no},
+ {noreply, O};
+handle_info({_From,badcookie,To,{{auth_reply,N},R}}, O) ->%% Let auth replys through
+ catch To ! {{auth_reply,N},R},
+ {noreply, O};
+handle_info({From,badcookie,Name,Mess}, Opened) ->
+ %% This may be registered send as well as pid send.
+ case lists:member(Name, Opened) of
+ true ->
+ catch Name ! Mess;
+ false ->
+ case catch lists:member(element(1, Mess), Opened) of
+ true ->
+ catch Name ! Mess; %% Might be a pid as well
+ _ ->
+ auth:print(getnode(From),
+ "~n** Unauthorized send attempt ~w to ~w **~n",
+ [Mess,node()]),
+ erlang:disconnect_node(getnode(From))
+ end
+ end,
+ {noreply, Opened};
+handle_info(_, O)-> % Ignore anything else especially EXIT signals
+ {noreply, O}.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+terminate(_Reason, _State) ->
+ ok.
+
+getnode(P) when is_pid(P) -> node(P);
+getnode(P) -> P.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%
+%%% Cookie functions
+%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Read cookie from $HOME/.erlang.cookie and set it.
+init_cookie() ->
+ case init:get_argument(nocookie) of
+ error ->
+ case init:get_argument(setcookie) of
+ {ok, [[C0]]} ->
+ C = list_to_atom(C0),
+ #state{our_cookie = C,
+ other_cookies = ets:new(cookies,
+ [?COOKIE_ETS_PROTECTION])};
+ _ ->
+ %% Here is the default
+ case read_cookie() of
+ {error, Error} ->
+ error_logger:error_msg(Error, []),
+ %% Is this really this serious?
+ erlang:error(Error);
+ {ok, Co} ->
+ #state{our_cookie = list_to_atom(Co),
+ other_cookies = ets:new(
+ cookies,
+ [?COOKIE_ETS_PROTECTION])}
+ end
+ end;
+ _Other ->
+ #state{our_cookie = nocookie,
+ other_cookies = ets:new(cookies,[?COOKIE_ETS_PROTECTION])}
+ end.
+
+read_cookie() ->
+ case init:get_argument(home) of
+ {ok, [[Home]]} ->
+ read_cookie(filename:join(Home, ".erlang.cookie"));
+ _ ->
+ {error, "No home for cookie file"}
+ end.
+
+read_cookie(Name) ->
+ case file:raw_read_file_info(Name) of
+ {ok, #file_info {type=Type, mode=Mode, size=Size}} ->
+ case check_attributes(Name, Type, Mode, os:type()) of
+ ok -> read_cookie(Name, Size);
+ Error -> Error
+ end;
+ {error, enoent} ->
+ case create_cookie(Name) of
+ ok -> read_cookie(Name);
+ Error -> Error
+ end;
+ {error, Reason} ->
+ {error, make_error(Name, Reason)}
+ end.
+
+read_cookie(Name, Size) ->
+ case file:open(Name, [raw, read]) of
+ {ok, File} ->
+ case file:read(File, Size) of
+ {ok, List} ->
+ file:close(File),
+ check_cookie(List, []);
+ {error, Reason} ->
+ make_error(Name, Reason)
+ end;
+ {error, Reason} ->
+ make_error(Name, Reason)
+ end.
+
+make_error(Name, Reason) ->
+ {error, "Error when reading " ++ Name ++ ": " ++ atom_to_list(Reason)}.
+
+%% Verifies that only the owner can access the cookie file.
+
+check_attributes(Name, Type, _Mode, _Os) when Type =/= regular ->
+ {error, "Cookie file " ++ Name ++ " is of type " ++ Type};
+check_attributes(Name, _Type, Mode, {unix, _}) when (Mode band 8#077) =/= 0 ->
+ {error, "Cookie file " ++ Name ++ " must be accessible by owner only"};
+check_attributes(_Name, _Type, _Mode, _Os) ->
+ ok.
+
+%% Checks that the cookie has the correct format.
+
+check_cookie([Letter|Rest], Result) when $\s =< Letter, Letter =< $~ ->
+ check_cookie(Rest, [Letter|Result]);
+check_cookie([X|Rest], Result) ->
+ check_cookie1([X|Rest], Result);
+check_cookie([], Result) ->
+ check_cookie1([], Result).
+
+check_cookie1([$\n|Rest], Result) ->
+ check_cookie1(Rest, Result);
+check_cookie1([$\r|Rest], Result) ->
+ check_cookie1(Rest, Result);
+check_cookie1([$\s|Rest], Result) ->
+ check_cookie1(Rest, Result);
+check_cookie1([_|_], _Result) ->
+ {error, "Bad characters in cookie"};
+check_cookie1([], []) ->
+ {error, "Too short cookie string"};
+check_cookie1([], Result) ->
+ {ok, lists:reverse(Result)}.
+
+%% Creates a new, random cookie.
+
+create_cookie(Name) ->
+ {_, S1, S2} = now(),
+ Seed = S2*10000+S1,
+ Cookie = random_cookie(20, Seed, []),
+ case file:open(Name, [write, raw]) of
+ {ok, File} ->
+ R1 = file:write(File, Cookie),
+ file:close(File),
+ R2 = file:raw_write_file_info(Name, make_info(Name)),
+ case {R1, R2} of
+ {ok, ok} ->
+ ok;
+ {{error,_Reason}, _} ->
+ {error, "Failed to create cookie file"};
+ {ok, {error, Reason}} ->
+ {error, "Failed to change mode: " ++ atom_to_list(Reason)}
+ end;
+ {error,_Reason} ->
+ {error, "Failed to create cookie file"}
+ end.
+
+random_cookie(0, _, Result) ->
+ Result;
+random_cookie(Count, X0, Result) ->
+ X = next_random(X0),
+ Letter = X*($Z-$A+1) div 16#1000000000 + $A,
+ random_cookie(Count-1, X, [Letter|Result]).
+
+%% Returns suitable information for a new cookie.
+%%
+%% Note: Since the generated cookie depends on the time the file was
+%% created, and the time can be seen plainly in the file, we will
+%% round down the file creation times to the nearest midnight to
+%% give crackers some more work.
+
+make_info(Name) ->
+ Midnight =
+ case file:raw_read_file_info(Name) of
+ {ok, #file_info{atime={Date, _}}} ->
+ {Date, {0, 0, 0}};
+ _ ->
+ {{1990, 1, 1}, {0, 0, 0}}
+ end,
+ #file_info{mode=8#400, atime=Midnight, mtime=Midnight, ctime=Midnight}.
+
+%% This RNG is from line 21 on page 102 in Knuth: The Art of Computer Programming,
+%% Volume II, Seminumerical Algorithms.
+%%
+%% Returns an integer in the range 0..(2^35-1).
+
+next_random(X) ->
+ (X*17059465+1) band 16#fffffffff.