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