1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2001-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
22-module(mod_htaccess).
23
24-export([do/1, load/2, store/2]).
25
26-include("httpd.hrl").
27-include("httpd_internal.hrl").
28
29%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
30%% Public methods that interface the eswapi                         %%
31%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
32
33%----------------------------------------------------------------------
34% Public method called by the webbserver to insert the data about
35% Names on accessfiles
36%----------------------------------------------------------------------
37load("AccessFileName" ++ FileNames, _Context)->
38    CleanFileNames=string:strip(FileNames),
39    {ok,[],{access_files,string:tokens(CleanFileNames," ")}}.
40
41store({access_files, Files} = Conf, _) when is_list(Files)->
42    {ok, Conf};
43store({access_files, Value}, _) ->
44    {error, {wrong_type, {access_files, Value}}}.
45
46%----------------------------------------------------------------------
47% Public method that the webbserver calls to control the page
48%----------------------------------------------------------------------
49do(Info)->
50    case proplists:get_value(status, Info#mod.data) of
51	{_Status_code, _PhraseArgs, _Reason}->
52	    {proceed,Info#mod.data};
53	undefined ->
54	    control_path(Info)
55    end.
56
57
58%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
59%%                                                                  %%
60%% The functions that start the control if there is a accessfile    %%
61%% and if so controls if the dir is allowed or not                  %%
62%%                                                                  %%
63%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
64%----------------------------------------------------------------------
65%Info = record mod as specified in httpd.hrl
66%returns either {proceed,Info#mod.data}
67%{proceed,[{status,403....}|Info#mod.data]}
68%{proceed,[{status,401....}|Info#mod.data]}
69%{proceed,[{status,500....}|Info#mod.data]}
70%----------------------------------------------------------------------
71control_path(Info) ->
72    Path = mod_alias:path(Info#mod.data,
73			  Info#mod.config_db,
74			  Info#mod.request_uri),
75    case isErlScriptOrNotAccessibleFile(Path,Info) of
76	true->
77	    {proceed,Info#mod.data};
78	false->
79	    case getHtAccessData(Path,Info)of
80		{ok,public}->
81		    %%There was no restrictions on the page continue
82		    {proceed,Info#mod.data};
83		{error, _Reason} ->
84		    %%Something got wrong continue or quit??????????????????/
85                   {proceed,Info#mod.data};
86		{accessData,AccessData}->
87		    controlAllowedMethod(Info,AccessData)
88	    end
89    end.
90
91
92%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
93%%                                                                  %%
94%% These methods controls that the method the client used in the    %%
95%% request is one of the limited                                    %%
96%%                                                                  %%
97%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
98%----------------------------------------------------------------------
99%Control that if the accessmethod used is in the list of modes to challenge
100%
101%Info is the mod record as specified in httpd.hrl
102%AccessData is an ets table whit the data in the .htaccessfiles
103%----------------------------------------------------------------------
104controlAllowedMethod(Info,AccessData)->
105    case allowedRequestMethod(Info,AccessData) of
106	allow->
107	    %%The request didnt use one of the limited methods
108	    ets:delete(AccessData),
109	    {proceed,Info#mod.data};
110	challenge->
111	    authenticateUser(Info,AccessData)
112    end.
113
114%----------------------------------------------------------------------
115%Check the specified access method in the .htaccessfile
116%----------------------------------------------------------------------
117allowedRequestMethod(Info,AccessData)->
118    case ets:lookup(AccessData,limit) of
119	[{limit,all}]->
120	    challenge;
121	[{limit,Methods}]->
122	    isLimitedRequestMethod(Info,Methods)
123    end.
124
125
126%----------------------------------------------------------------------
127%Check the specified accessmethods in the .htaccesfile against the users
128%accessmethod
129%
130%Info is the record from the do call
131%Methods is a list of the methods specified in the .htaccessfile
132%----------------------------------------------------------------------
133isLimitedRequestMethod(Info,Methods)->
134    case lists:member(Info#mod.method,Methods) of
135	true->
136	    challenge;
137	false ->
138	    allow
139    end.
140
141
142%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
143%%                                                                  %%
144%% These methods controls that the user comes from an allowwed net  %%
145%% and if so wheather its a valid user or a challenge shall be      %%
146%% generated                                                        %%
147%%                                                                  %%
148%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
149%----------------------------------------------------------------------
150%The first thing to control is that the user is from a network
151%that has access to the page
152%----------------------------------------------------------------------
153authenticateUser(Info,AccessData)->
154    case controlNet(Info,AccessData) of
155	allow->
156	    %the network is ok control that it is an allowed user
157	    authenticateUser2(Info,AccessData);
158	deny->
159	    %The user isnt allowed to access the pages from that network
160	    ets:delete(AccessData),
161	    {proceed,[{status,{403,Info#mod.request_uri,
162	    "Restricted area not allowed from your network"}}|Info#mod.data]}
163    end.
164
165
166%----------------------------------------------------------------------
167%The network the user comes from is allowed to view the resources
168%control whether the user needsto supply a password or not
169%----------------------------------------------------------------------
170authenticateUser2(Info,AccessData)->
171    case ets:lookup(AccessData,require) of
172	[{require,AllowedUsers}]->
173	    case ets:lookup(AccessData,auth_name) of
174		[{auth_name,Realm}]->
175		    authenticateUser2(Info,AccessData,Realm,AllowedUsers);
176		_NoAuthName->
177		    ets:delete(AccessData),
178		    {break,[{status,{500,none,
179				     ?NICE("mod_htaccess:AuthName directive "
180					   "not specified")}}]}
181	    end;
182	[] ->
183	    %%No special user is required the network is ok so let
184	    %%the user in
185	    ets:delete(AccessData),
186	    {proceed,Info#mod.data}
187    end.
188
189
190%----------------------------------------------------------------------
191%The user must send a userId and a password to get the resource
192%Control if its already in the http-request
193%if the file with users is bad send an 500 response
194%----------------------------------------------------------------------
195authenticateUser2(Info,AccessData,Realm,AllowedUsers)->
196    case authenticateUser(Info,AccessData,AllowedUsers) of
197	allow ->
198	    ets:delete(AccessData),
199	    {user,Name, _Pwd} = getAuthenticatingDataFromHeader(Info),
200	    {proceed, [{remote_user_name,Name}|Info#mod.data]};
201	challenge->
202	    ets:delete(AccessData),
203	    ReasonPhrase = httpd_util:reason_phrase(401),
204	    Message = httpd_util:message(401,none,Info#mod.config_db),
205	    {proceed,
206	     [{response,
207	       {401,
208		["WWW-Authenticate: Basic realm=\"",Realm,
209		 "\"\r\n\r\n","<HTML>\n<HEAD>\n<TITLE>",
210		 ReasonPhrase,"</TITLE>\n",
211		 "</HEAD>\n<BODY>\n<H1>",ReasonPhrase,
212		 "</H1>\n",Message,"\n</BODY>\n</HTML>\n"]}}|
213	      Info#mod.data]};
214	deny->
215	    ets:delete(AccessData),
216	    {break,[{status,{500,none,
217			     ?NICE("mod_htaccess:Bad path to user "
218				   "or group file")}}]}
219    end.
220
221
222%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
223%%                                                                  %%
224%% Methods that validate the netwqork the user comes from           %%
225%% according to the allowed networks                                %%
226%%                                                                  %%
227%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
228%---------------------------------------------------------------------
229%Controls the users networkaddress agains the specifed networks to
230%allow or deny
231%
232%returns either allow or deny
233%----------------------------------------------------------------------
234controlNet(Info,AccessData)->
235    UserNetwork=getUserNetworkAddress(Info),
236    case getAllowDenyOrder(AccessData) of
237	{_deny,[],_allow,[]}->
238	    allow;
239	{deny,[],allow,AllowedNetworks}->
240	    controlIfAllowed(AllowedNetworks,UserNetwork,allow,deny);
241	{allow,AllowedNetworks,deny,[]}->
242	    controlIfAllowed(AllowedNetworks,UserNetwork,allow,deny);
243
244	{deny,DeniedNetworks,allow,[]}->
245	    controlIfAllowed(DeniedNetworks,UserNetwork,allow,deny);
246	{allow,[],deny,DeniedNetworks}->
247	    controlIfAllowed(DeniedNetworks,UserNetwork,allow,deny);
248
249	{deny,DeniedNetworks,allow,AllowedNetworks}->
250	    controlDenyAllow(DeniedNetworks,AllowedNetworks,UserNetwork);
251	{allow,AllowedNetworks,deny,DeniedNetworks}->
252	    controlAllowDeny(AllowedNetworks,DeniedNetworks,UserNetwork)
253    end.
254
255
256%----------------------------------------------------------------------
257%Returns the users IP-Number
258%----------------------------------------------------------------------
259getUserNetworkAddress(Info)->
260    {_Socket,Address}=(Info#mod.init_data)#init_data.peername,
261    Address.
262
263
264%----------------------------------------------------------------------
265%Control the users Ip-number against the ip-numbers in the .htaccessfile
266%----------------------------------------------------------------------
267controlIfAllowed(AllowedNetworks,UserNetwork,IfAllowed,IfDenied)->
268    case AllowedNetworks of
269	[{allow,all}]->
270	   IfAllowed;
271	[{deny,all}]->
272	    IfDenied;
273        [{deny,Networks}]->
274	    memberNetwork(Networks,UserNetwork,IfDenied,IfAllowed);
275	[{allow,Networks}]->
276	    memberNetwork(Networks,UserNetwork,IfAllowed,IfDenied);
277	_Error->
278	    IfDenied
279    end.
280
281
282%---------------------------------------------------------------------%
283%The Denycontrol isn't neccessary to preform since the allow control  %
284%override the deny control                                            %
285%---------------------------------------------------------------------%
286controlDenyAllow(_DeniedNetworks, AllowedNetworks, UserNetwork)->
287    case AllowedNetworks of
288	[{allow, all}]->
289	    allow;
290	[{allow, Networks}]->
291	  case memberNetwork(Networks, UserNetwork) of
292	      true->
293		  allow;
294	      false->
295		  deny
296	  end
297    end.
298
299
300%----------------------------------------------------------------------%
301%Control that the user is in the allowed list if so control that the   %
302%network is in the denied list
303%----------------------------------------------------------------------%
304controlAllowDeny(AllowedNetworks,DeniedNetworks,UserNetwork)->
305    case controlIfAllowed(AllowedNetworks,UserNetwork,allow,deny) of
306	allow->
307	    controlIfAllowed(DeniedNetworks,UserNetwork,deny,allow);
308	deny ->
309	    deny
310    end.
311
312%----------------------------------------------------------------------
313%Controls if the users Ipnumber is in the list of either denied or
314%allowed networks
315%----------------------------------------------------------------------
316memberNetwork(Networks,UserNetwork,IfTrue,IfFalse)->
317    case memberNetwork(Networks,UserNetwork) of
318	true->
319	    IfTrue;
320	false->
321	    IfFalse
322    end.
323
324
325%----------------------------------------------------------------------
326%regexp match the users ip-address against the networks in the list of
327%ipadresses or subnet addresses.
328memberNetwork(Networks,UserNetwork)->
329    case lists:filter(fun(Net)->
330			      case re:run(UserNetwork,
331					  formatRegexp(Net), [{capture, first}]) of
332				  {match,[{0,_}]}->
333				      true;
334				  _NotSubNet ->
335				      false
336			      end
337		      end,Networks) of
338	[]->
339	    false;
340	_MemberNetWork ->
341	    true
342    end.
343
344
345%----------------------------------------------------------------------
346%Creates a regexp from an ip-number i.e "127.0.0-> "^127[.]0[.]0.*"
347%"127.0.0.-> "^127[.]0[.]0[.].*"
348%----------------------------------------------------------------------
349formatRegexp(Net)->
350    [SubNet1|SubNets]=string:tokens(Net,"."),
351    NetRegexp=lists:foldl(fun(SubNet,Newnet)->
352				  Newnet ++ "[.]" ++SubNet
353			  end,"^"++SubNet1,SubNets),
354    case string:len(Net)-string:rchr(Net,$.) of
355	0->
356	    NetRegexp++"[.].*";
357	_->
358	    NetRegexp++".*"
359    end.
360
361%----------------------------------------------------------------------
362%If the user has specified if the allow or deny check shall be preformed
363%first get that order if no order is specified take
364%allow - deny since its harder that deny - allow
365%----------------------------------------------------------------------
366getAllowDenyOrder(AccessData)->
367    case ets:lookup(AccessData,order) of
368	[{order,{deny,allow}}]->
369	    {deny,ets:lookup(AccessData,deny),
370	     allow,ets:lookup(AccessData,allow)};
371	_DefaultOrder->
372	    {allow,ets:lookup(AccessData,allow),
373	     deny,ets:lookup(AccessData,deny)}
374    end.
375
376
377%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
378%%                                                                  %%
379%% The methods that validates the user                              %%
380%%                                                                  %%
381%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
382
383%----------------------------------------------------------------------
384%Control if there is anyu autheticating data in threquest header
385%if so it controls it against the users in the list Allowed Users
386%----------------------------------------------------------------------
387authenticateUser(Info,AccessData,AllowedUsers)->
388    case getAuthenticatingDataFromHeader(Info) of
389	{user,User,PassWord}->
390	    authenticateUser(Info,AccessData,AllowedUsers,
391			     {user,User,PassWord});
392	{error,nouser}->
393	    challenge;
394	{error, _BadData}->
395	    challenge
396    end.
397
398
399%----------------------------------------------------------------------
400%Returns the Autheticating data in the http-request
401%----------------------------------------------------------------------
402getAuthenticatingDataFromHeader(Info)->
403    PrsedHeader=Info#mod.parsed_header,
404    case proplists:get_value("authorization", PrsedHeader) of
405	undefined->
406	    {error,nouser};
407	[$B,$a,$s,$i,$c,$\ |EncodedString] = Credentials ->
408	    case (catch base64:decode_to_string(EncodedString)) of
409		{'EXIT',{function_clause, _}} ->
410		    {error, Credentials};
411		UnCodedString ->
412		    case httpd_util:split(UnCodedString,":",2) of
413			{ok,[User,PassWord]}->
414			    {user,User,PassWord};
415			Other ->
416			    {error, Other}
417		    end
418	    end;
419	BadCredentials ->
420	    {error,BadCredentials}
421    end.
422
423%----------------------------------------------------------------------
424%Returns a list of all members of the allowed groups
425%----------------------------------------------------------------------
426getGroupMembers(Groups,AllowedGroups)->
427    Allowed=lists:foldl(fun({group,Name,Members},AllowedMembers)->
428				case lists:member(Name,AllowedGroups) of
429				    true->
430					AllowedMembers++Members;
431				    false ->
432					AllowedMembers
433				end
434	       end,[],Groups),
435    {ok,Allowed}.
436
437authenticateUser(Info,AccessData,{{users,[]},{groups,Groups}},User)->
438    authenticateUser(Info,AccessData,{groups,Groups},User);
439authenticateUser(Info,AccessData,{{users,Users},{groups,[]}},User)->
440    authenticateUser(Info,AccessData,{users,Users},User);
441
442authenticateUser(Info,AccessData,{{users,Users},{groups,Groups}},User)->
443    AllowUser=authenticateUser(Info,AccessData,{users,Users},User),
444    AllowGroup=authenticateUser(Info,AccessData,{groups,Groups},User),
445    case {AllowGroup,AllowUser} of
446	{_,allow}->
447	    allow;
448	{allow,_}->
449	    allow;
450	{challenge,_}->
451	    challenge;
452	{_,challenge}->
453	    challenge;
454	{_deny,_deny}->
455	    deny
456    end;
457
458
459%----------------------------------------------------------------------
460%Controls that the user is a member in one of the allowed group
461%----------------------------------------------------------------------
462authenticateUser(Info,AccessData,{groups,AllowedGroups},{user,User,PassWord})->
463    case getUsers(AccessData,group_file) of
464	{group_data,Groups}->
465	    {ok, Members } = getGroupMembers(Groups,AllowedGroups),
466	    authenticateUser(Info,AccessData,{users,Members},
467			     {user,User,PassWord});
468	{error, _BadData}->
469	    deny
470    end;
471
472
473%----------------------------------------------------------------------
474%Control that the user is one of the allowed users and that the passwd is ok
475%----------------------------------------------------------------------
476authenticateUser(_Info,AccessData,{users,AllowedUsers},{user,User,PassWord})->
477    case lists:member(User,AllowedUsers) of
478       true->
479	    %Get the usernames and passwords from the file
480	    case getUsers(AccessData,user_file) of
481		{error, _BadData}->
482		    deny;
483		{user_data,Users}->
484		    %Users is a list of the users in
485		    %the userfile [{user,User,Passwd}]
486		    checkPassWord(Users,{user,User,PassWord})
487	    end;
488	false ->
489	    challenge
490    end.
491
492
493%----------------------------------------------------------------------
494%Control that the user User={user,"UserName","PassWd"} is
495%member of the list of Users
496%----------------------------------------------------------------------
497checkPassWord(Users,User)->
498    case lists:member(User,Users) of
499	true->
500	    allow;
501	false->
502	    challenge
503    end.
504
505
506%----------------------------------------------------------------------
507%Get the users in the specified file
508%UserOrGroup is an atom that specify if its a group file or a user file
509%i.e. group_file or user_file
510%----------------------------------------------------------------------
511getUsers({file,FileName},UserOrGroup)->
512    case file:open(FileName,[read]) of
513        {ok,AccessFileHandle} ->
514	    getUsers({stream,AccessFileHandle},[],UserOrGroup);
515        {error,Reason} ->
516	    {error,{Reason,FileName}}
517    end;
518
519
520%----------------------------------------------------------------------
521%The method that starts the lokkong for user files
522%----------------------------------------------------------------------
523
524getUsers(AccessData,UserOrGroup)->
525    case ets:lookup(AccessData,UserOrGroup) of
526	[{UserOrGroup,File}]->
527	    getUsers({file,File},UserOrGroup);
528	_ ->
529	    {error,noUsers}
530    end.
531
532
533%----------------------------------------------------------------------
534%Reads data from the filehandle File to the list FileData and when its
535%reach the end it returns the list in a tuple {user_file|group_file,FileData}
536%----------------------------------------------------------------------
537getUsers({stream,File},FileData,UserOrGroup)->
538    case io:get_line(File,[]) of
539        eof when UserOrGroup =:= user_file ->
540	    {user_data,FileData};
541	eof when UserOrGroup =:= group_file ->
542	   {group_data,FileData};
543        Line ->
544	    getUsers({stream,File},
545		     formatUser(Line,FileData,UserOrGroup),UserOrGroup)
546    end.
547
548
549%----------------------------------------------------------------------
550%If the line is a comment remove it
551%----------------------------------------------------------------------
552formatUser([$#|_UserDataComment],FileData,_UserOrgroup)->
553    FileData;
554
555
556%----------------------------------------------------------------------
557%The user name in the file is Username:Passwd\n
558%Remove the newline sign and split the user name in
559%UserName and Password
560%----------------------------------------------------------------------
561formatUser(UserData,FileData,UserOrGroup)->
562    case string:tokens(UserData," \r\n")of
563	[User| _Whitespace] when UserOrGroup =:= user_file ->
564	    case string:tokens(User,":") of
565		[Name,PassWord]->
566		    [{user,Name,PassWord}|FileData];
567		_Error->
568		    FileData
569	    end;
570	GroupData when UserOrGroup =:= group_file ->
571	    parseGroupData(GroupData,FileData);
572	_Error ->
573	    FileData
574    end.
575
576
577%----------------------------------------------------------------------
578%if everything is right GroupData is on the form
579% ["groupName:", "Member1", "Member2", "Member2"
580%----------------------------------------------------------------------
581parseGroupData([GroupName|GroupData],FileData)->
582    [{group,formatGroupName(GroupName),GroupData}|FileData].
583
584
585%----------------------------------------------------------------------
586%the line in the file is GroupName: Member1 Member2 .....MemberN
587%Remove the : from the group name
588%----------------------------------------------------------------------
589formatGroupName(GroupName)->
590    string:strip(GroupName,right,$:).
591
592
593%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
594%%                                                                  %%
595%%  Functions that parses the accessfiles                           %%
596%%                                                                  %%
597%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
598%----------------------------------------------------------------------
599%Control that the asset is a real file and not a request for an virtual
600%asset
601%----------------------------------------------------------------------
602isErlScriptOrNotAccessibleFile(Path, _Info)->
603    case file:read_file_info(Path) of
604	{ok,_fileInfo}->
605	    false;
606	{error,_Reason} ->
607	    true
608    end.
609
610
611%----------------------------------------------------------------------
612%Path=PathToTheRequestedFile=String
613%Innfo=record#mod
614%----------------------------------------------------------------------
615getHtAccessData(Path,Info)->
616    HtAccessFileNames=getHtAccessFileNames(Info),
617    case getData(Path,Info,HtAccessFileNames) of
618	{ok,public}->
619	    {ok,public};
620	{accessData,AccessData}->
621	    {accessData,AccessData};
622	{error,Reason} ->
623	    {error,Reason}
624    end.
625
626
627%----------------------------------------------------------------------
628%returns the names of the accessfiles
629%----------------------------------------------------------------------
630getHtAccessFileNames(Info)->
631    case httpd_util:lookup(Info#mod.config_db,access_files) of
632	undefined->
633	    [".htaccess"];
634	Files->
635	    Files
636    end.
637%----------------------------------------------------------------------
638%HtAccessFileNames=["accessfileName1",..."AccessFileName2"]
639%----------------------------------------------------------------------
640getData(Path,Info,HtAccessFileNames)->
641    SplittedPath = re:split(Path, "/", [{return, list}]),
642    getData2(HtAccessFileNames,SplittedPath,Info).
643
644%----------------------------------------------------------------------
645%Add to together the data in the Splittedpath up to the path
646%that is the alias or the document root
647%Since we do not need to control after any accessfiles before here
648%----------------------------------------------------------------------
649getData2(HtAccessFileNames,SplittedPath,Info)->
650    case getRootPath(SplittedPath,Info) of
651	{error,Path}->
652	    {error,Path};
653	{ok,StartPath,RestOfSplittedPath} ->
654	    getData2(HtAccessFileNames,StartPath,RestOfSplittedPath,Info)
655    end.
656
657
658%----------------------------------------------------------------------
659%HtAccessFilenames is a list the names the accesssfiles can have
660%Path is the shortest match agains all alias and documentroot
661%rest of splitted path is a list of the parts of the path
662%Info is the mod recod from the server
663%----------------------------------------------------------------------
664getData2(HtAccessFileNames, StartPath, RestOfSplittedPath, _Info)->
665    case getHtAccessFiles(HtAccessFileNames,StartPath,RestOfSplittedPath) of
666	[]->
667	    %No accessfile qiut its a public directory
668	    {ok,public};
669	Files ->
670	    loadAccessFilesData(Files)
671    end.
672
673
674%----------------------------------------------------------------------
675%Loads the data in the accessFiles specifiied by
676% AccessFiles=["/hoem/public/html/accefile",
677%               "/home/public/html/priv/accessfile"]
678%----------------------------------------------------------------------
679loadAccessFilesData(AccessFiles)->
680    loadAccessFilesData(AccessFiles,ets:new(accessData,[])).
681
682
683%----------------------------------------------------------------------
684%Returns the found data
685%----------------------------------------------------------------------
686contextToValues(AccessData)->
687    case ets:lookup(AccessData,context) of
688	[{context,Values}]->
689	    ets:delete(AccessData,context),
690	    insertContext(AccessData,Values),
691	    {accessData,AccessData};
692	_Error->
693	    {error,errorInAccessFile}
694    end.
695
696
697insertContext(_AccessData, [])->
698    ok;
699
700insertContext(AccessData,[{allow,From}|Values])->
701    insertDenyAllowContext(AccessData,{allow,From}),
702    insertContext(AccessData,Values);
703
704insertContext(AccessData,[{deny,From}|Values])->
705    insertDenyAllowContext(AccessData,{deny,From}),
706    insertContext(AccessData,Values);
707
708insertContext(AccessData,[{require,{GrpOrUsr,Members}}|Values])->
709    case ets:lookup(AccessData,require) of
710	[] when GrpOrUsr =:= users ->
711	    ets:insert(AccessData,{require,{{users,Members},{groups,[]}}});
712
713	[{require,{{users,Users},{groups,Groups}}}] when GrpOrUsr =:= users ->
714	    ets:insert(AccessData,{require,{{users,Users++Members},
715					   {groups,Groups}}});
716	[] when GrpOrUsr =:= groups ->
717	    ets:insert(AccessData,{require,{{users,[]},{groups,Members}}});
718
719	[{require,{{users,Users},{groups,Groups}}}] when GrpOrUsr =:= groups ->
720	    ets:insert(AccessData,{require,{{users,Users},
721					   {groups,Groups++Members}}})
722    end,
723    insertContext(AccessData,Values);
724
725
726
727%%limit and order directive need no transforming they areis just to insert
728insertContext(AccessData,[Elem|Values])->
729    ets:insert(AccessData,Elem),
730    insertContext(AccessData,Values).
731
732
733insertDenyAllowContext(AccessData,{AllowDeny,From})->
734    case From of
735	all ->
736	    ets:insert(AccessData,{AllowDeny,all});
737	_AllowedSubnets ->
738	    case ets:lookup(AccessData,AllowDeny) of
739		[]->
740		    ets:insert(AccessData,{AllowDeny,From});
741		[{AllowDeny,all}]->
742		    ok;
743		[{AllowDeny,Networks}]->
744		    ets:insert(AccessData,{allow,Networks++From})
745	    end
746    end.
747
748loadAccessFilesData([],AccessData)->
749    %preform context to limits
750    contextToValues(AccessData),
751    {accessData,AccessData};
752
753%----------------------------------------------------------------------
754%Takes each file in the list and load the data to the ets table
755%AccessData
756%----------------------------------------------------------------------
757loadAccessFilesData([FileName|FileNames],AccessData)->
758    case loadAccessFileData({file,FileName},AccessData) of
759	overRide->
760	    loadAccessFilesData(FileNames,AccessData);
761	noOverRide ->
762	    {accessData,AccessData};
763	error->
764	    ets:delete(AccessData),
765	    {error,errorInAccessFile}
766    end.
767
768%----------------------------------------------------------------------
769%opens the filehandle to the specified file
770%----------------------------------------------------------------------
771loadAccessFileData({file,FileName},AccessData)->
772    case file:open(FileName,[read]) of
773        {ok,AccessFileHandle}->
774	    loadAccessFileData({stream,AccessFileHandle},AccessData,[]);
775        {error, _Reason} ->
776	    overRide
777    end.
778
779%----------------------------------------------------------------------
780%%look att each line in the file and add them to the database
781%%When end of file is reached control i overrride is allowed
782%% if so return
783%----------------------------------------------------------------------
784loadAccessFileData({stream,File},AccessData,FileData)->
785    case io:get_line(File,[]) of
786        eof->
787	    insertData(AccessData,FileData),
788	    case ets:match_object(AccessData,{'_',error}) of
789		[]->
790		    %Case we got no error control that we can override a
791		    %at least some of the values
792		    case ets:match_object(AccessData,
793					  {allow_over_ride,none}) of
794			[]->
795			    overRide;
796			_NoOverride->
797			    noOverRide
798		    end;
799		_ ->
800		    error
801	    end;
802	Line ->
803	    loadAccessFileData({stream,File},AccessData,
804			       insertLine(string:strip(Line,left),FileData))
805    end.
806
807%----------------------------------------------------------------------
808%AccessData is a ets table where the previous found data is inserted
809%FileData is a list of the directives in the last parsed file
810%before insertion a control is done that the directive is allowed to
811%override
812%----------------------------------------------------------------------
813insertData(AccessData,{{context,Values},FileData})->
814    insertData(AccessData,[{context,Values}|FileData]);
815
816insertData(AccessData,FileData)->
817    case ets:lookup(AccessData,allow_over_ride) of
818	[{allow_over_ride,all}]->
819	    lists:foreach(fun(Elem)->
820				  ets:insert(AccessData,Elem)
821			  end,FileData);
822	[]->
823	    lists:foreach(fun(Elem)->
824				  ets:insert(AccessData,Elem)
825			  end,FileData);
826	[{allow_over_ride,Directives}] when is_list(Directives)->
827	    lists:foreach(fun({Key,Value}) ->
828				  case lists:member(Key,Directives) of
829				      true->
830					  ok;
831				      false ->
832					  ets:insert(AccessData,{Key,Value})
833				  end
834			  end,FileData);
835	[{allow_over_ride,_}]->
836	    %Will never appear if the user
837	    %aint doing very strang econfig files
838	    ok
839    end.
840%----------------------------------------------------------------------
841%Take a line in the accessfile and transform it into a tuple that
842%later can be inserted in to the ets:table
843%----------------------------------------------------------------------
844%%%Here is the alternatives that resides inside the limit context
845
846insertLine("order"++ Order, {{context, Values}, FileData})->
847    {{context,[{order,getOrder(Order)}|Values]},FileData};
848%%Let the user place a tab in the beginning
849insertLine([$\t,$o,$r,$d,$e,$r|Order],{{context,Values},FileData})->
850     {{context,[{order,getOrder(Order)}|Values]},FileData};
851
852insertLine("allow" ++ Allow, {{context, Values}, FileData})->
853    {{context,[{allow,getAllowDenyData(Allow)}|Values]},FileData};
854insertLine([$\t,$a,$l,$l,$o,$w|Allow],{{context,Values},FileData})->
855    {{context,[{allow,getAllowDenyData(Allow)}|Values]},FileData};
856
857insertLine("deny" ++ Deny, {{context,Values}, FileData})->
858    {{context,[{deny,getAllowDenyData(Deny)}|Values]},FileData};
859insertLine([$\t, $d,$e,$n,$y|Deny],{{context,Values},FileData})->
860    {{context,[{deny,getAllowDenyData(Deny)}|Values]},FileData};
861
862insertLine("require" ++ Require, {{context, Values}, FileData})->
863    {{context,[{require,getRequireData(Require)}|Values]},FileData};
864insertLine([$\t,$r,$e,$q,$u,$i,$r,$e|Require],{{context,Values},FileData})->
865    {{context,[{require,getRequireData(Require)}|Values]},FileData};
866
867insertLine("</Limit" ++ _EndLimit, {Context,FileData})->
868    [Context | FileData];
869insertLine("<Limit" ++ Limit, FileData)->
870    {{context,[{limit,getLimits(Limit)}]}, FileData};
871
872insertLine([$A,$u,$t,$h,$U,$s,$e,$r,$F,$i,$l,$e,$\ |AuthUserFile],FileData)->
873    [{user_file,string:strip(AuthUserFile,right,$\n)}|FileData];
874
875insertLine([$A,$u,$t,$h,$G,$r,$o,$u,$p,$F,$i,$l,$e,$\ |AuthGroupFile],
876           FileData)->
877    [{group_file,string:strip(AuthGroupFile,right,$\n)}|FileData];
878
879insertLine("AllowOverRide" ++ AllowOverRide, FileData)->
880    [{allow_over_ride,getAllowOverRideData(AllowOverRide)}
881     | FileData];
882
883insertLine([$A,$u,$t,$h,$N,$a,$m,$e,$\ |AuthName],FileData)->
884    [{auth_name,string:strip(AuthName,right,$\n)}|FileData];
885
886insertLine("AuthType" ++ AuthType,FileData)->
887    [{auth_type,getAuthorizationType(AuthType)}|FileData];
888
889insertLine(_BadDirectiveOrComment,FileData)->
890    FileData.
891
892%----------------------------------------------------------------------
893%transform the Data specified about override to a form that is ieasier
894%handled later
895%Override data="all"|"md5"|"Directive1 .... DirectioveN"
896%----------------------------------------------------------------------
897
898getAllowOverRideData(OverRideData)->
899   case string:tokens(OverRideData," \r\n") of
900       ["all" ++ _] ->
901	   all;
902       ["none" ++ _]->
903	   none;
904       Directives ->
905	   getOverRideDirectives(Directives)
906   end.
907
908getOverRideDirectives(Directives)->
909    lists:map(fun(Directive)->
910		      transformDirective(Directive)
911	      end,Directives).
912transformDirective("AuthUserFile" ++  _)->
913    user_file;
914transformDirective("AuthGroupFile" ++ _) ->
915    group_file;
916transformDirective("AuthName" ++ _)->
917    auth_name;
918transformDirective("AuthType" ++ _)->
919    auth_type;
920transformDirective(_UnAllowedOverRideDirective) ->
921    unallowed.
922%----------------------------------------------------------------------
923%Replace the string that specify which method to use for authentication
924%and replace it with the atom for easier mathing
925%----------------------------------------------------------------------
926getAuthorizationType(AuthType)->
927    [Arg | _Crap] = string:tokens(AuthType,"\n\r\ "),
928    case Arg of
929	"Basic"->
930	    basic;
931	"MD5" ->
932	    md5;
933	_What ->
934	    error
935    end.
936%----------------------------------------------------------------------
937%Returns a list of the specified methods to limit or the atom all
938%----------------------------------------------------------------------
939getLimits(Limits)->
940    case re:split(Limits,">", [{return, list}])of
941	[_NoEndOnLimit]->
942	    error;
943	[Methods | _Crap]->
944	    case re:split(Methods," ",  [{return, list}]) of
945		[[]]->
946		    all;
947		SplittedMethods ->
948		    SplittedMethods
949	    end
950    end.
951
952
953%----------------------------------------------------------------------
954% Transform the order to prefrom deny allow control to a tuple of atoms
955%----------------------------------------------------------------------
956getOrder(Order)->
957    [First | _Rest]=lists:map(fun(Part)->
958		      list_to_atom(Part)
959	      end,string:tokens(Order," \n\r")),
960    case First of
961	deny->
962	    {deny,allow};
963	allow->
964	    {allow,deny};
965	_Error->
966	    error
967    end.
968
969%----------------------------------------------------------------------
970% The string AllowDeny is "from all" or "from Subnet1 Subnet2...SubnetN"
971%----------------------------------------------------------------------
972getAllowDenyData(AllowDeny)->
973    case string:tokens(AllowDeny," \n\r") of
974	[_From|AllowDenyData] when length(AllowDenyData)>=1 ->
975	    case lists:nth(1,AllowDenyData) of
976		"all" ->
977		    all;
978		_Hosts->
979		    AllowDenyData
980	    end;
981	_ ->
982	    error
983    end.
984%----------------------------------------------------------------------
985% Fix the string that describes who is allowed to se the page
986%----------------------------------------------------------------------
987getRequireData(Require)->
988    [UserOrGroup|UserData]=string:tokens(Require," \n\r"),
989    case UserOrGroup of
990	"user"->
991	    {users,UserData};
992	"group" ->
993	    {groups,UserData};
994	_Whatever ->
995	    error
996    end.
997
998
999%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1000%%                                                                  %%
1001%% Methods that collects the searchways to the accessfiles          %%
1002%%                                                                  %%
1003%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1004
1005%----------------------------------------------------------------------
1006% Get the whole path to the different accessfiles
1007%----------------------------------------------------------------------
1008getHtAccessFiles(HtAccessFileNames,Path,RestOfSplittedPath)->
1009    getHtAccessFiles(HtAccessFileNames,Path,RestOfSplittedPath,[]).
1010
1011getHtAccessFiles(HtAccessFileNames,Path,[[]],HtAccessFiles)->
1012    HtAccessFiles ++ accessFilesOfPath(HtAccessFileNames,Path++"/");
1013
1014getHtAccessFiles(_HtAccessFileNames, _Path, [], HtAccessFiles)->
1015    HtAccessFiles;
1016getHtAccessFiles(HtAccessFileNames,Path,[NextDir|RestOfSplittedPath],
1017		 AccessFiles)->
1018    getHtAccessFiles(HtAccessFileNames,Path++"/"++NextDir,RestOfSplittedPath,
1019		     AccessFiles ++
1020		     accessFilesOfPath(HtAccessFileNames,Path++"/")).
1021
1022
1023%----------------------------------------------------------------------
1024%Control if therer are any accessfies in the path
1025%----------------------------------------------------------------------
1026accessFilesOfPath(HtAccessFileNames,Path)->
1027    lists:foldl(fun(HtAccessFileName,Files)->
1028			case file:read_file_info(Path++HtAccessFileName) of
1029			    {ok, _}->
1030				[Path++HtAccessFileName|Files];
1031			    {error,_Error} ->
1032				Files
1033			end
1034		end,[],HtAccessFileNames).
1035
1036
1037%----------------------------------------------------------------------
1038%Sake the splitted path and joins it up to the documentroot or the alias
1039%that match first
1040%----------------------------------------------------------------------
1041
1042getRootPath(SplittedPath, Info)->
1043    DocRoot=httpd_util:lookup(Info#mod.config_db,document_root,"/"),
1044    PresumtiveRootPath=
1045	[DocRoot|lists:map(fun({_Alias,RealPath})->
1046				   RealPath
1047			   end,
1048		 httpd_util:multi_lookup(Info#mod.config_db,alias))],
1049    getRootPath(PresumtiveRootPath,SplittedPath,Info).
1050
1051
1052getRootPath(PresumtiveRootPath,[[],Splittedpath],Info)->
1053    getRootPath(PresumtiveRootPath,["/",Splittedpath],Info);
1054
1055
1056getRootPath(PresumtiveRootPath,[Part,NextPart|SplittedPath],Info)->
1057    case lists:member(Part,PresumtiveRootPath)of
1058	true->
1059	    {ok,Part,[NextPart|SplittedPath]};
1060	false ->
1061	    getRootPath(PresumtiveRootPath,
1062			[Part++"/"++NextPart|SplittedPath],Info)
1063    end;
1064
1065getRootPath(PresumtiveRootPath, [Part], _Info)->
1066    case lists:member(Part,PresumtiveRootPath)of
1067	true->
1068	    {ok,Part,[]};
1069	false ->
1070	    {error,Part}
1071    end.
1072