%%
%% %CopyrightBegin%
%% 
%% Copyright Ericsson AB 1998-2010. 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(mod_auth_plain).

-include("httpd.hrl").
-include("mod_auth.hrl").
-include("httpd_internal.hrl").
-include("inets_internal.hrl").


-define(VMODULE,"AUTH_PLAIN").

%% Internal API
-export([store_directory_data/3]).


-export([get_user/2, 
	 list_group_members/2, 
	 add_user/2, 
	 add_group_member/3, 
	 list_users/1, 
	 delete_user/2, 
	 list_groups/1, 
	 delete_group_member/3, 
	 delete_group/2, 
	 remove/1]).

%%
%% API
%%

%%
%% Storage format of users in the ets table:
%% {UserName, Password, UserData}
%%

add_user(DirData, #httpd_user{username = User} = UStruct) ->
    ?hdrt("add user", [{user, UStruct}]),
    PWDB = proplists:get_value(auth_user_file, DirData),
    Record = {User,
	      UStruct#httpd_user.password, 
	      UStruct#httpd_user.user_data}, 
    case ets:lookup(PWDB, User) of
	[{User, _SomePassword, _SomeData}] ->
	    {error, user_already_in_db};
	_ ->
	    ets:insert(PWDB, Record),
	    true
    end.

get_user(DirData, User) ->
    ?hdrt("get user", [{dir_data, DirData}, {user, User}]),
    PWDB = proplists:get_value(auth_user_file, DirData),
    case ets:lookup(PWDB, User) of
	[{User, PassWd, Data}] ->
	    {ok, #httpd_user{username  = User, 
			     password  = PassWd, 
			     user_data = Data}};
	_Other ->
	    {error, no_such_user}
    end.

list_users(DirData) ->
    PWDB = proplists:get_value(auth_user_file, DirData),
    Records = ets:match(PWDB, '$1'), 
    {ok, lists:foldr(fun({User, _PassWd, _Data}, A) -> [User | A] end, 
		     [], lists:flatten(Records))}.

delete_user(DirData, UserName) ->
    ?hdrt("delete user", [{dir_data, DirData}, {user, UserName}]),
    PWDB = proplists:get_value(auth_user_file, DirData),
    case ets:lookup(PWDB, UserName) of
	[{UserName, _SomePassword, _SomeData}] ->
	    ets:delete(PWDB, UserName),
	    {ok, Groups}  = list_groups(DirData),
	    lists:foreach(fun(Group) -> 
				  delete_group_member(DirData, 
						      Group, UserName) 
			  end, Groups);
	_ ->
	    {error, no_such_user}
    end.

%%
%% Storage of groups in the ets table:
%% {Group, UserList} where UserList is a list of strings.
%%
  
add_group_member(DirData, Group, UserName) ->
    GDB = proplists:get_value(auth_group_file, DirData),
    case ets:lookup(GDB, Group) of
	[{Group, Users}] ->
	    case lists:member(UserName, Users) of
		true ->
		    true;
		false ->
		    ets:insert(GDB, {Group, [UserName|Users]}),
		    true
	    end;
	[] ->
	    ets:insert(GDB, {Group, [UserName]}),
	    true;
	Other ->
	    {error, Other}
    end.

list_group_members(DirData, Group) ->
    GDB = proplists:get_value(auth_group_file, DirData),
    case ets:lookup(GDB, Group) of
	[{Group, Users}] ->
	    {ok, Users};
	_ ->
	    {error, no_such_group}
    end.

list_groups(DirData) ->
    GDB = proplists:get_value(auth_group_file, DirData),
    Groups = ets:match(GDB, '$1'), 
    {ok, httpd_util:uniq(lists:foldr(fun({G, _}, A) -> [G|A] end,
				     [], lists:flatten(Groups)))}.

delete_group_member(DirData, Group, User) ->
    GDB = proplists:get_value(auth_group_file, DirData),
    case ets:lookup(GDB, Group) of
	[{Group, Users}] when is_list(Users) ->
	    case lists:member(User, Users) of
		true ->
		    ets:delete(GDB, Group),
		    ets:insert(GDB, {Group, lists:delete(User, Users)}),
		    true;
		false ->
		    {error, no_such_group_member}
	    end;
	_ ->
	    {error, no_such_group}
    end.

delete_group(DirData, Group) ->
    GDB = proplists:get_value(auth_group_file, DirData),
    case ets:lookup(GDB, Group) of
	[{Group, _Users}] ->
	    ets:delete(GDB, Group),
	    true;
	_ ->
	    {error, no_such_group}
    end.

store_directory_data(_Directory, DirData, Server_root) ->
    ?hdrt("store directory data", 
	  [{dir_data, DirData}, {server_root, Server_root}]),
    PWFile = absolute_file_name(auth_user_file, DirData, Server_root),
    GroupFile = absolute_file_name(auth_group_file, DirData, Server_root),
    case load_passwd(PWFile) of
	{ok, PWDB} ->
	    ?hdrt("password file loaded", [{file, PWFile}, {pwdb, PWDB}]),
	    case load_group(GroupFile) of
		{ok, GRDB} ->
		    ?hdrt("group file loaded", 
			  [{file, GroupFile}, {grdb, GRDB}]),
		    %% Address and port is included in the file names...
		    Addr = proplists:get_value(bind_address, DirData),
		    Port = proplists:get_value(port, DirData),
		    {ok, PasswdDB} = store_passwd(Addr,Port,PWDB),
		    {ok, GroupDB}  = store_group(Addr,Port,GRDB),
		    NDD1 = lists:keyreplace(auth_user_file, 1, DirData, 
					    {auth_user_file, PasswdDB}),
		    NDD2 = lists:keyreplace(auth_group_file, 1, NDD1, 
					    {auth_group_file, GroupDB}),
		    {ok, NDD2};
		Err ->
		    {error, Err}
	    end;
	Err2 ->
	    {error, Err2}
    end.



%% load_passwd

load_passwd(AuthUserFile) ->
    case file:open(AuthUserFile, [read]) of
	{ok,Stream} ->
	    parse_passwd(Stream, []);
	{error, _} ->
	    {error, ?NICE("Can't open " ++ AuthUserFile)}
    end.

parse_passwd(Stream, PasswdList) ->
    Line =
	case io:get_line(Stream, '') of
	    eof ->
		eof;
	    String ->
		httpd_conf:clean(String)
	end,
    parse_passwd(Stream, PasswdList, Line).

parse_passwd(Stream, PasswdList, eof) ->
    file:close(Stream),
    {ok, PasswdList};
parse_passwd(Stream, PasswdList, "") ->
    parse_passwd(Stream, PasswdList);
parse_passwd(Stream, PasswdList, [$#|_]) ->
    parse_passwd(Stream, PasswdList);
parse_passwd(Stream, PasswdList, Line) ->      
    case inets_regexp:split(Line,":") of
	{ok, [User,Password]} ->
	    parse_passwd(Stream, [{User,Password, []}|PasswdList]);
	{ok,_} ->
	    {error, ?NICE(Line)}
    end.

%% load_group

load_group(AuthGroupFile) ->
    case file:open(AuthGroupFile, [read]) of
	{ok, Stream} ->
	    parse_group(Stream,[]);
	{error, _} ->
	    {error, ?NICE("Can't open " ++ AuthGroupFile)}
    end.

parse_group(Stream, GroupList) ->
    Line =
	case io:get_line(Stream,'') of
	    eof ->
		eof;
	    String ->
		httpd_conf:clean(String)
	end,
    parse_group(Stream, GroupList, Line).

parse_group(Stream, GroupList, eof) ->
    file:close(Stream),
    {ok, GroupList};
parse_group(Stream, GroupList, "") ->
    parse_group(Stream, GroupList);
parse_group(Stream, GroupList, [$#|_]) ->
    parse_group(Stream, GroupList);
parse_group(Stream, GroupList, Line) ->      
    case inets_regexp:split(Line, ":") of
	{ok, [Group,Users]} ->
	    {ok, UserList} = inets_regexp:split(Users," "),
	    parse_group(Stream, [{Group,UserList}|GroupList]);
	{ok, _} ->
	    {error, ?NICE(Line)}
    end.


%% store_passwd

store_passwd(Addr,Port,PasswdList) ->
    Name = httpd_util:make_name("httpd_passwd",Addr,Port),
    PasswdDB = ets:new(Name, [set, public]),
    store_passwd(PasswdDB, PasswdList).

store_passwd(PasswdDB, []) ->
    {ok, PasswdDB};
store_passwd(PasswdDB, [User|Rest]) ->
    ets:insert(PasswdDB, User),
    store_passwd(PasswdDB, Rest).

%% store_group

store_group(Addr,Port,GroupList) ->
    Name = httpd_util:make_name("httpd_group",Addr,Port),
    GroupDB = ets:new(Name, [set, public]),
    store_group(GroupDB, GroupList).


store_group(GroupDB,[]) ->
    {ok, GroupDB};
store_group(GroupDB, [User|Rest]) ->
    ets:insert(GroupDB, User),
    store_group(GroupDB, Rest).


%% remove/1
%%
%% Deletes ets tables used by this auth mod.
%%
remove(DirData) ->
    PWDB = proplists:get_value(auth_user_file, DirData),
    GDB = proplists:get_value(auth_group_file, DirData),
    ets:delete(PWDB),
    ets:delete(GDB).



%% absolute_file_name/2
%%
%% Return the absolute path name of File_type. 
absolute_file_name(File_type, DirData, Server_root) ->
    Path = proplists:get_value(File_type, DirData),
    case filename:pathtype(Path) of
	relative ->
	    case Server_root of
		undefined ->
		    {error,
		     ?NICE(Path++
			   " is an invalid file name because "
			   "ServerRoot is not defined")};
		_ ->
		    filename:join(Server_root,Path)
	    end;
	_ ->
	    Path
    end.

