1%% ``Licensed under the Apache License, Version 2.0 (the "License");
2%% you may not use this file except in compliance with the License.
3%% You may obtain a copy of the License at
4%%
5%%     http://www.apache.org/licenses/LICENSE-2.0
6%%
7%% Unless required by applicable law or agreed to in writing, software
8%% distributed under the License is distributed on an "AS IS" BASIS,
9%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10%% See the License for the specific language governing permissions and
11%% limitations under the License.
12%%
13%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
14%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
15%% AB. All Rights Reserved.''
16%%
17%%     $Id: mod_auth_plain.erl,v 1.1 2008/12/17 09:53:35 mikpe Exp $
18-module(mod_auth_plain).
19
20-include("httpd.hrl").
21-include("mod_auth.hrl").
22
23-define(VMODULE,"AUTH_PLAIN").
24-include("httpd_verbosity.hrl").
25
26
27%% Internal API
28-export([store_directory_data/2]).
29
30
31-export([get_user/2,
32	 list_group_members/2,
33	 add_user/2,
34	 add_group_member/3,
35	 list_users/1,
36	 delete_user/2,
37	 list_groups/1,
38	 delete_group_member/3,
39	 delete_group/2,
40	 remove/1]).
41
42%%
43%% API
44%%
45
46%%
47%% Storage format of users in the ets table:
48%% {UserName, Password, UserData}
49%%
50
51add_user(DirData, #httpd_user{username = User} = UStruct) ->
52    ?vtrace("add_user -> entry with:"
53	"~n   User: ~p",[User]),
54    PWDB = httpd_util:key1search(DirData, auth_user_file),
55    Record = {User,
56	      UStruct#httpd_user.password,
57	      UStruct#httpd_user.user_data},
58    case ets:lookup(PWDB, User) of
59	[{User, _SomePassword, _SomeData}] ->
60	    {error, user_already_in_db};
61	_ ->
62	    ets:insert(PWDB, Record),
63	    true
64    end.
65
66get_user(DirData, User) ->
67    ?vtrace("get_user -> entry with:"
68	"~n   User: ~p",[User]),
69    PWDB = httpd_util:key1search(DirData, auth_user_file),
70    case ets:lookup(PWDB, User) of
71	[{User, PassWd, Data}] ->
72	    {ok, #httpd_user{username=User, password=PassWd, user_data=Data}};
73	_ ->
74	    {error, no_such_user}
75    end.
76
77list_users(DirData) ->
78    PWDB = httpd_util:key1search(DirData, auth_user_file),
79    case ets:match(PWDB, '$1') of
80	Records when list(Records) ->
81	    {ok, lists:foldr(fun({User,PassWd,Data}, A) -> [User|A] end,
82			     [], lists:flatten(Records))};
83	O ->
84	    {ok, []}
85    end.
86
87delete_user(DirData, UserName) ->
88    ?vtrace("delete_user -> entry with:"
89	"~n   UserName: ~p",[UserName]),
90    PWDB = httpd_util:key1search(DirData, auth_user_file),
91    case ets:lookup(PWDB, UserName) of
92	[{UserName, SomePassword, SomeData}] ->
93	    ets:delete(PWDB, UserName),
94	    case list_groups(DirData) of
95		{ok,Groups}->
96		    lists:foreach(fun(Group) ->
97					  delete_group_member(DirData, Group, UserName)
98				  end,Groups),
99		    true;
100		_->
101		    true
102	    end;
103	_ ->
104	    {error, no_such_user}
105    end.
106
107%%
108%% Storage of groups in the ets table:
109%% {Group, UserList} where UserList is a list of strings.
110%%
111
112add_group_member(DirData, Group, UserName) ->
113    ?DEBUG("add_group_members -> ~n"
114	   "    Group:    ~p~n"
115	   "    UserName: ~p",[Group,UserName]),
116    GDB = httpd_util:key1search(DirData, auth_group_file),
117    case ets:lookup(GDB, Group) of
118	[{Group, Users}] ->
119	    case lists:member(UserName, Users) of
120		true ->
121		    ?DEBUG("add_group_members -> already member in group",[]),
122		    true;
123		false ->
124		    ?DEBUG("add_group_members -> add",[]),
125		    ets:insert(GDB, {Group, [UserName|Users]}),
126		    true
127	    end;
128	[] ->
129	    ?DEBUG("add_group_members -> create grouo",[]),
130	    ets:insert(GDB, {Group, [UserName]}),
131	    true;
132	Other ->
133	    ?ERROR("add_group_members -> Other: ~p",[Other]),
134	    {error, Other}
135    end.
136
137list_group_members(DirData, Group) ->
138    ?DEBUG("list_group_members -> Group: ~p",[Group]),
139    GDB = httpd_util:key1search(DirData, auth_group_file),
140    case ets:lookup(GDB, Group) of
141	[{Group, Users}] ->
142	    ?DEBUG("list_group_members -> Users: ~p",[Users]),
143	    {ok, Users};
144	_ ->
145	    {error, no_such_group}
146    end.
147
148list_groups(DirData) ->
149    ?DEBUG("list_groups -> entry",[]),
150    GDB = httpd_util:key1search(DirData, auth_group_file),
151    case ets:match(GDB, '$1') of
152	[] ->
153	    ?DEBUG("list_groups -> []",[]),
154	    {ok, []};
155	Groups0 when list(Groups0) ->
156	    ?DEBUG("list_groups -> Groups0: ~p",[Groups0]),
157	    {ok, httpd_util:uniq(lists:foldr(fun({G, U}, A) -> [G|A] end,
158					     [], lists:flatten(Groups0)))};
159	_ ->
160	    {ok, []}
161    end.
162
163delete_group_member(DirData, Group, User) ->
164    ?DEBUG("list_group_members -> ~n"
165	   "     Group: ~p~n"
166	   "     User:  ~p",[Group,User]),
167    GDB = httpd_util:key1search(DirData, auth_group_file),
168    UDB = httpd_util:key1search(DirData, auth_user_file),
169    case ets:lookup(GDB, Group) of
170	[{Group, Users}] when list(Users) ->
171	    case lists:member(User, Users) of
172		true ->
173		    ?DEBUG("list_group_members -> deleted from group",[]),
174		    ets:delete(GDB, Group),
175		    ets:insert(GDB, {Group, lists:delete(User, Users)}),
176		    true;
177		false ->
178		    ?DEBUG("list_group_members -> not member",[]),
179		    {error, no_such_group_member}
180	    end;
181	_ ->
182	    ?ERROR("list_group_members -> no such group",[]),
183	    {error, no_such_group}
184    end.
185
186delete_group(DirData, Group) ->
187    ?DEBUG("list_group_members -> Group: ~p",[Group]),
188    GDB = httpd_util:key1search(DirData, auth_group_file),
189    case ets:lookup(GDB, Group) of
190	[{Group, Users}] ->
191	    ?DEBUG("list_group_members -> delete",[]),
192	    ets:delete(GDB, Group),
193	    true;
194	_ ->
195	    ?ERROR("delete_group -> no such group",[]),
196	    {error, no_such_group}
197    end.
198
199
200store_directory_data(Directory, DirData) ->
201    PWFile = httpd_util:key1search(DirData, auth_user_file),
202    GroupFile = httpd_util:key1search(DirData, auth_group_file),
203    case load_passwd(PWFile) of
204	{ok, PWDB} ->
205	    case load_group(GroupFile) of
206		{ok, GRDB} ->
207		    %% Address and port is included in the file names...
208		    Addr = httpd_util:key1search(DirData, bind_address),
209		    Port = httpd_util:key1search(DirData, port),
210		    {ok, PasswdDB} = store_passwd(Addr,Port,PWDB),
211		    {ok, GroupDB}  = store_group(Addr,Port,GRDB),
212		    NDD1 = lists:keyreplace(auth_user_file, 1, DirData,
213					    {auth_user_file, PasswdDB}),
214		    NDD2 = lists:keyreplace(auth_group_file, 1, NDD1,
215					    {auth_group_file, GroupDB}),
216		    {ok, NDD2};
217		Err ->
218		    ?ERROR("failed storing directory data: "
219			   "load group error: ~p",[Err]),
220		    {error, Err}
221	    end;
222	Err2 ->
223	    ?ERROR("failed storing directory data: "
224		   "load passwd error: ~p",[Err2]),
225	    {error, Err2}
226    end.
227
228
229
230%% load_passwd
231
232load_passwd(AuthUserFile) ->
233    case file:open(AuthUserFile, [read]) of
234	{ok,Stream} ->
235	    parse_passwd(Stream, []);
236	{error, _} ->
237	    {error, ?NICE("Can't open "++AuthUserFile)}
238    end.
239
240parse_passwd(Stream,PasswdList) ->
241    Line =
242	case io:get_line(Stream, '') of
243	    eof ->
244		eof;
245	    String ->
246		httpd_conf:clean(String)
247	end,
248    parse_passwd(Stream, PasswdList, Line).
249
250parse_passwd(Stream, PasswdList, eof) ->
251    file:close(Stream),
252    {ok, PasswdList};
253parse_passwd(Stream, PasswdList, "") ->
254    parse_passwd(Stream, PasswdList);
255parse_passwd(Stream, PasswdList, [$#|_]) ->
256    parse_passwd(Stream, PasswdList);
257parse_passwd(Stream, PasswdList, Line) ->
258    case regexp:split(Line,":") of
259	{ok, [User,Password]} ->
260	    parse_passwd(Stream, [{User,Password, []}|PasswdList]);
261	{ok,_} ->
262	    {error, ?NICE(Line)}
263    end.
264
265%% load_group
266
267load_group(AuthGroupFile) ->
268    case file:open(AuthGroupFile, [read]) of
269	{ok, Stream} ->
270	    parse_group(Stream,[]);
271	{error, _} ->
272	    {error, ?NICE("Can't open "++AuthGroupFile)}
273    end.
274
275parse_group(Stream, GroupList) ->
276    Line=
277	case io:get_line(Stream,'') of
278	    eof ->
279		eof;
280	    String ->
281		httpd_conf:clean(String)
282	end,
283    parse_group(Stream, GroupList, Line).
284
285parse_group(Stream, GroupList, eof) ->
286    file:close(Stream),
287    {ok, GroupList};
288parse_group(Stream, GroupList, "") ->
289    parse_group(Stream, GroupList);
290parse_group(Stream, GroupList, [$#|_]) ->
291    parse_group(Stream, GroupList);
292parse_group(Stream, GroupList, Line) ->
293    case regexp:split(Line, ":") of
294	{ok, [Group,Users]} ->
295	    {ok, UserList} = regexp:split(Users," "),
296	    parse_group(Stream, [{Group,UserList}|GroupList]);
297	{ok, _} ->
298	    {error, ?NICE(Line)}
299    end.
300
301
302%% store_passwd
303
304store_passwd(Addr,Port,PasswdList) ->
305    Name = httpd_util:make_name("httpd_passwd",Addr,Port),
306    PasswdDB = ets:new(Name, [set, public]),
307    store_passwd(PasswdDB, PasswdList).
308
309store_passwd(PasswdDB, []) ->
310    {ok, PasswdDB};
311store_passwd(PasswdDB, [User|Rest]) ->
312    ets:insert(PasswdDB, User),
313    store_passwd(PasswdDB, Rest).
314
315%% store_group
316
317store_group(Addr,Port,GroupList) ->
318    Name = httpd_util:make_name("httpd_group",Addr,Port),
319    GroupDB = ets:new(Name, [set, public]),
320    store_group(GroupDB, GroupList).
321
322
323store_group(GroupDB,[]) ->
324    {ok, GroupDB};
325store_group(GroupDB,[User|Rest]) ->
326    ets:insert(GroupDB, User),
327    store_group(GroupDB, Rest).
328
329
330%% remove/1
331%%
332%% Deletes ets tables used by this auth mod.
333%%
334remove(DirData) ->
335    PWDB = httpd_util:key1search(DirData, auth_user_file),
336    GDB = httpd_util:key1search(DirData, auth_group_file),
337    ets:delete(PWDB),
338    ets:delete(GDB).
339