1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 1998-2016. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19%%
20%%
21-module(mod_auth_dets).
22
23%% dets authentication storage
24
25-export([get_user/2,
26	 list_group_members/2,
27	 add_user/2,
28	 add_group_member/3,
29	 list_users/1,
30	 delete_user/2,
31	 list_groups/1,
32	 delete_group_member/3,
33	 delete_group/2,
34	 remove/1]).
35
36-export([store_directory_data/3]).
37
38-include("httpd.hrl").
39-include("httpd_internal.hrl").
40-include("mod_auth.hrl").
41
42%%====================================================================
43%% Internal application API
44%%====================================================================
45
46store_directory_data(_Directory, DirData, Server_root) ->
47    {PWFile, Absolute_pwdfile} = absolute_file_name(auth_user_file, DirData,
48						    Server_root),
49    {GroupFile, Absolute_groupfile} = absolute_file_name(auth_group_file,
50							 DirData, Server_root),
51    Addr = proplists:get_value(bind_address, DirData),
52    Port = proplists:get_value(port, DirData),
53    Profile = proplists:get_value(profile, DirData, ?DEFAULT_PROFILE),
54
55    PWName  = httpd_util:make_name("httpd_dets_pwdb", Addr, Port, Profile),
56    case dets:open_file(PWName,[{type,set},{file,Absolute_pwdfile},{repair,true}]) of
57	{ok, PWDB} ->
58	    GDBName = httpd_util:make_name("httpd_dets_groupdb", Addr, Port, Profile),
59	    case dets:open_file(GDBName,[{type,set},{file,Absolute_groupfile},{repair,true}]) of
60		{ok, GDB} ->
61		    NDD1 = lists:keyreplace(auth_user_file, 1, DirData,
62					    {auth_user_file, PWDB}),
63		    NDD2 = lists:keyreplace(auth_group_file, 1, NDD1,
64					    {auth_group_file, GDB}),
65		    {ok, NDD2};
66		{error, Err}->
67		    {error, {{file, GroupFile},Err}}
68	    end;
69	{error, Err2} ->
70	    {error, {{file, PWFile},Err2}}
71    end.
72
73%% Storage format of users in the dets table:
74%% {{UserName, Addr, Port, Dir}, Password, UserData}
75add_user(DirData, UStruct) ->
76    {Addr, Port, Dir} = lookup_common(DirData),
77    PWDB = proplists:get_value(auth_user_file, DirData),
78    Record = {{UStruct#httpd_user.username, Addr, Port, Dir},
79	      UStruct#httpd_user.password, UStruct#httpd_user.user_data},
80    case dets:lookup(PWDB, UStruct#httpd_user.username) of
81	[Record] ->
82	    {error, user_already_in_db};
83	_ ->
84	    dets:insert(PWDB, Record),
85	    true
86    end.
87
88get_user(DirData, UserName) ->
89    {Addr, Port, Dir} = lookup_common(DirData),
90    PWDB = proplists:get_value(auth_user_file, DirData),
91    User = {UserName, Addr, Port, Dir},
92    case dets:lookup(PWDB, User) of
93	[{User, Password, UserData}] ->
94	    {ok, #httpd_user{username=UserName, password=Password, user_data=UserData}};
95	_ ->
96	    {error, no_such_user}
97    end.
98
99list_users(DirData) ->
100    {Addr, Port, Dir} = lookup_common(DirData),
101    PWDB = proplists:get_value(auth_user_file, DirData),
102    case dets:traverse(PWDB, fun(X) -> {continue, X} end) of
103	Records when is_list(Records) ->
104	    {ok, [UserName || {{UserName, AnyAddr, AnyPort, AnyDir},
105			       _Password, _Data} <- Records,
106			      AnyAddr == Addr, AnyPort == Port,
107			      AnyDir == Dir]};
108	_O ->
109	    {ok, []}
110    end.
111
112delete_user(DirData, UserName) ->
113    {Addr, Port, Dir} = lookup_common(DirData),
114    PWDB = proplists:get_value(auth_user_file, DirData),
115    User = {UserName, Addr, Port, Dir},
116    case dets:lookup(PWDB, User) of
117	[{User, _SomePassword, _UserData}] ->
118	    dets:delete(PWDB, User),
119	    {ok, Groups} = list_groups(DirData),
120	    lists:foreach(fun(Group) ->
121				  delete_group_member(DirData,
122						      Group, UserName) end,
123			  Groups),
124	    true;
125	_ ->
126	    {error, no_such_user}
127    end.
128
129%% Storage of groups in the dets table:
130%% {Group, UserList} where UserList is a list of strings.
131add_group_member(DirData, GroupName, UserName) ->
132    {Addr, Port, Dir} = lookup_common(DirData),
133    GDB = proplists:get_value(auth_group_file, DirData),
134    Group = {GroupName, Addr, Port, Dir},
135    case dets:lookup(GDB, Group) of
136	[{Group, Users}] ->
137	    case lists:member(UserName, Users) of
138		true ->
139		    true;
140		false ->
141		    dets:insert(GDB, {Group, [UserName|Users]}),
142		    true
143	    end;
144	[] ->
145	    dets:insert(GDB, {Group, [UserName]}),
146	    true;
147	Other ->
148	    {error, Other}
149    end.
150
151list_group_members(DirData, GroupName) ->
152    {Addr, Port, Dir} = lookup_common(DirData),
153    GDB = proplists:get_value(auth_group_file, DirData),
154    Group = {GroupName, Addr, Port, Dir},
155    case dets:lookup(GDB, Group) of
156	[{Group, Users}] ->
157	    {ok, Users};
158	_ ->
159	    {error, no_such_group}
160    end.
161
162list_groups(DirData) ->
163    {Addr, Port, Dir} = lookup_common(DirData),
164    GDB  = proplists:get_value(auth_group_file, DirData),
165    case dets:match(GDB, {'$1', '_'}) of
166	[] ->
167	    {ok, []};
168	List when is_list(List) ->
169	    Groups = lists:flatten(List),
170	    {ok, [GroupName ||
171		     {GroupName, AnyAddr, AnyPort, AnyDir} <- Groups,
172		     AnyAddr == Addr, AnyPort == Port, AnyDir == Dir]};
173	_ ->
174	    {ok, []}
175    end.
176
177delete_group_member(DirData, GroupName, UserName) ->
178    {Addr, Port, Dir} = lookup_common(DirData),
179    GDB = proplists:get_value(auth_group_file, DirData),
180    Group = {GroupName, Addr, Port, Dir},
181    case dets:lookup(GDB, GroupName) of
182	[{Group, Users}] ->
183	    case lists:member(UserName, Users) of
184		true ->
185		    dets:delete(GDB, Group),
186		    dets:insert(GDB, {Group,
187				      lists:delete(UserName, Users)}),
188		    true;
189		false ->
190		    {error, no_such_group_member}
191	    end;
192	_ ->
193	    {error, no_such_group}
194    end.
195
196delete_group(DirData, GroupName) ->
197    {Addr, Port, Dir} = lookup_common(DirData),
198    GDB = proplists:get_value(auth_group_file, DirData),
199    Group = {GroupName, Addr, Port, Dir},
200    case dets:lookup(GDB, Group) of
201	[{Group, _Users}] ->
202	    dets:delete(GDB, Group),
203	    true;
204	_ ->
205	    {error, no_such_group}
206    end.
207
208%% Closes dets tables used by this auth mod.
209remove(DirData) ->
210    PWDB = proplists:get_value(auth_user_file, DirData),
211    GDB = proplists:get_value(auth_group_file, DirData),
212    dets:close(GDB),
213    dets:close(PWDB),
214    ok.
215
216%%--------------------------------------------------------------------
217%%% Internal functions
218%%--------------------------------------------------------------------
219%% Return the absolute path name of File_type.
220absolute_file_name(File_type, DirData, Server_root) ->
221    Path = proplists:get_value(File_type, DirData),
222    Absolute_path = case filename:pathtype(Path) of
223			relative ->
224			    case Server_root of
225				undefined ->
226				    {error,
227				     ?NICE(Path++
228					   " is an invalid file name because "
229					   "ServerRoot is not defined")};
230				_ ->
231				    filename:join(Server_root,Path)
232			    end;
233			_ ->
234			    Path
235		    end,
236    {Path, Absolute_path}.
237
238lookup_common(DirData) ->
239    Dir  = proplists:get_value(path, DirData),
240    Port = proplists:get_value(port, DirData),
241    Addr = proplists:get_value(bind_address, DirData),
242    {Addr, Port, Dir}.
243