1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 1997-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). 22 23%% The functions that the webbserver call on startup stop 24%% and when the server traverse the modules. 25-export([do/1, store/2, remove/1]). 26 27%% User entries to the gen-server. 28-export([add_user/2, add_user/5, add_user/6, 29 add_group_member/3, add_group_member/4, add_group_member/5, 30 list_users/1, list_users/2, list_users/3, 31 delete_user/2, delete_user/3, delete_user/4, 32 delete_group_member/3, delete_group_member/4, delete_group_member/5, 33 list_groups/1, list_groups/2, list_groups/3, 34 delete_group/2, delete_group/3, delete_group/4, 35 get_user/2, get_user/3, get_user/4, 36 list_group_members/2, list_group_members/3, list_group_members/4, 37 update_password/6, update_password/5]). 38 39-include("httpd.hrl"). 40-include("mod_auth.hrl"). 41-include("httpd_internal.hrl"). 42 43-define(VMODULE,"AUTH"). 44 45-define(NOPASSWORD,"NoPassword"). 46 47%%==================================================================== 48%% Internal application API 49%%==================================================================== 50 51do(Info) -> 52 case proplists:get_value(status,Info#mod.data) of 53 %% A status code has been generated! 54 {_StatusCode, _PhraseArgs, _Reason} -> 55 {proceed, Info#mod.data}; 56 %% No status code has been generated! 57 undefined -> 58 case proplists:get_value(response, Info#mod.data) of 59 %% No response has been generated! 60 undefined -> 61 Path = mod_alias:path(Info#mod.data,Info#mod.config_db, 62 Info#mod.request_uri), 63 %% Is it a secret area? 64 case secretp(Path,Info#mod.config_db) of 65 {yes, {Directory, DirectoryData}} -> 66 case allow((Info#mod.init_data)#init_data.peername, 67 Info#mod.socket_type,Info#mod.socket, 68 DirectoryData) of 69 allowed -> 70 case deny((Info#mod.init_data)#init_data.peername, 71 Info#mod.socket_type, 72 Info#mod.socket, 73 DirectoryData) of 74 not_denied -> 75 case proplists:get_value(auth_type, 76 DirectoryData) of 77 undefined -> 78 {proceed, Info#mod.data}; 79 none -> 80 {proceed, Info#mod.data}; 81 AuthType -> 82 do_auth(Info, 83 Directory, 84 DirectoryData, 85 AuthType) 86 end; 87 {denied, Reason} -> 88 {proceed, 89 [{status, {403, 90 Info#mod.request_uri, 91 Reason}}| 92 Info#mod.data]} 93 end; 94 {not_allowed, Reason} -> 95 {proceed,[{status,{403, 96 Info#mod.request_uri, 97 Reason}} | 98 Info#mod.data]} 99 end; 100 no -> 101 {proceed, Info#mod.data} 102 end; 103 %% A response has been generated or sent! 104 _Response -> 105 {proceed, Info#mod.data} 106 end 107 end. 108 109 110%% mod_auth recognizes the following Configuration Directives: 111%% <Directory /path/to/directory> 112%% AuthDBType 113%% AuthName 114%% AuthUserFile 115%% AuthGroupFile 116%% AuthAccessPassword 117%% require 118%% allow 119%% </Directory> 120 121%% When a <Directory> directive is found, a new context is set to 122%% [{directory, Directory, DirData}|OtherContext] 123%% DirData in this case is a key-value list of data belonging to the 124%% directory in question. 125%% 126%% When the </Directory> statement is found, the Context created earlier 127%% will be returned as a ConfigList and the context will return to the 128%% state it was previously. 129 130store({directory, {Directory, DirData}}, ConfigList) 131 when is_list(Directory) andalso is_list(DirData) -> 132 try directory_config_check(Directory, DirData) of 133 ok -> 134 store_directory(Directory, DirData, ConfigList) 135 catch 136 throw:Error -> 137 {error, Error, {directory, Directory, DirData}} 138 end; 139store({directory, {Directory, DirData}}, _) -> 140 {error, {wrong_type, {directory, {Directory, DirData}}}}. 141 142remove(ConfigDB) -> 143 lists:foreach(fun({directory, {_Dir, DirData}}) -> 144 AuthMod = auth_mod_name(DirData), 145 (catch apply(AuthMod, remove, [DirData])) 146 end, 147 ets:match_object(ConfigDB,{directory,{'_','_'}})), 148 149 Addr = httpd_util:lookup(ConfigDB, bind_address, undefined), 150 Port = httpd_util:lookup(ConfigDB, port), 151 Profile = httpd_util:lookup(ConfigDB, profile, ?DEFAULT_PROFILE), 152 mod_auth_server:stop(Addr, Port, Profile), 153 ok. 154 155add_user(UserName, Opt) -> 156 case get_options(Opt, mandatory) of 157 {Addr, Port, Dir, AuthPwd}-> 158 case get_options(Opt, userData) of 159 {error, Reason}-> 160 {error, Reason}; 161 {UserData, Password}-> 162 User = [#httpd_user{username = UserName, 163 password = Password, 164 user_data = UserData}], 165 mod_auth_server:add_user(Addr, Port, Dir, User, AuthPwd) 166 end 167 end. 168 169 170add_user(UserName, Password, UserData, Port, Dir) -> 171 add_user(UserName, Password, UserData, undefined, Port, Dir). 172add_user(UserName, Password, UserData, Addr, Port, Dir) -> 173 User = [#httpd_user{username = UserName, 174 password = Password, 175 user_data = UserData}], 176 mod_auth_server:add_user(Addr, Port, Dir, User, ?NOPASSWORD). 177 178get_user(UserName, Opt) -> 179 case get_options(Opt, mandatory) of 180 {Addr, Port, Dir, AuthPwd} -> 181 mod_auth_server:get_user(Addr, Port, Dir, UserName, AuthPwd); 182 {error, Reason} -> 183 {error, Reason} 184 end. 185 186get_user(UserName, Port, Dir) -> 187 get_user(UserName, undefined, Port, Dir). 188get_user(UserName, Addr, Port, Dir) -> 189 mod_auth_server:get_user(Addr, Port, Dir, UserName, ?NOPASSWORD). 190 191add_group_member(GroupName, UserName, Opt)-> 192 case get_options(Opt, mandatory) of 193 {Addr, Port, Dir, AuthPwd}-> 194 mod_auth_server:add_group_member(Addr, Port, Dir, 195 GroupName, UserName, AuthPwd); 196 {error, Reason} -> 197 {error, Reason} 198 end. 199 200add_group_member(GroupName, UserName, Port, Dir) -> 201 add_group_member(GroupName, UserName, undefined, Port, Dir). 202 203add_group_member(GroupName, UserName, Addr, Port, Dir) -> 204 mod_auth_server:add_group_member(Addr, Port, Dir, 205 GroupName, UserName, ?NOPASSWORD). 206 207delete_group_member(GroupName, UserName, Opt) -> 208 case get_options(Opt, mandatory) of 209 {Addr, Port, Dir, AuthPwd} -> 210 mod_auth_server:delete_group_member(Addr, Port, Dir, 211 GroupName, UserName, AuthPwd); 212 {error, Reason} -> 213 {error, Reason} 214 end. 215 216delete_group_member(GroupName, UserName, Port, Dir) -> 217 delete_group_member(GroupName, UserName, undefined, Port, Dir). 218delete_group_member(GroupName, UserName, Addr, Port, Dir) -> 219 mod_auth_server:delete_group_member(Addr, Port, Dir, 220 GroupName, UserName, ?NOPASSWORD). 221 222list_users(Opt) -> 223 case get_options(Opt, mandatory) of 224 {Addr, Port, Dir, AuthPwd} -> 225 mod_auth_server:list_users(Addr, Port, Dir, AuthPwd); 226 {error, Reason} -> 227 {error, Reason} 228 end. 229 230list_users(Port, Dir) -> 231 list_users(undefined, Port, Dir). 232list_users(Addr, Port, Dir) -> 233 mod_auth_server:list_users(Addr, Port, Dir, ?NOPASSWORD). 234 235delete_user(UserName, Opt) -> 236 case get_options(Opt, mandatory) of 237 {Addr, Port, Dir, AuthPwd} -> 238 mod_auth_server:delete_user(Addr, Port, Dir, UserName, AuthPwd); 239 {error, Reason} -> 240 {error, Reason} 241 end. 242 243delete_user(UserName, Port, Dir) -> 244 delete_user(UserName, undefined, Port, Dir). 245delete_user(UserName, Addr, Port, Dir) -> 246 mod_auth_server:delete_user(Addr, Port, Dir, UserName, ?NOPASSWORD). 247 248delete_group(GroupName, Opt) -> 249 case get_options(Opt, mandatory) of 250 {Addr, Port, Dir, AuthPwd} -> 251 mod_auth_server:delete_group(Addr, Port, Dir, GroupName, AuthPwd); 252 {error, Reason} -> 253 {error, Reason} 254 end. 255 256delete_group(GroupName, Port, Dir) -> 257 delete_group(GroupName, undefined, Port, Dir). 258delete_group(GroupName, Addr, Port, Dir) -> 259 mod_auth_server:delete_group(Addr, Port, Dir, GroupName, ?NOPASSWORD). 260 261list_groups(Opt) -> 262 case get_options(Opt, mandatory) of 263 {Addr, Port, Dir, AuthPwd} -> 264 mod_auth_server:list_groups(Addr, Port, Dir, AuthPwd); 265 {error, Reason} -> 266 {error, Reason} 267 end. 268 269list_groups(Port, Dir) -> 270 list_groups(undefined, Port, Dir). 271list_groups(Addr, Port, Dir) -> 272 mod_auth_server:list_groups(Addr, Port, Dir, ?NOPASSWORD). 273 274list_group_members(GroupName, Opt) -> 275 case get_options(Opt, mandatory) of 276 {Addr, Port, Dir, AuthPwd} -> 277 mod_auth_server:list_group_members(Addr, Port, Dir, GroupName, 278 AuthPwd); 279 {error, Reason} -> 280 {error, Reason} 281 end. 282 283list_group_members(GroupName, Port, Dir) -> 284 list_group_members(GroupName, undefined, Port, Dir). 285list_group_members(GroupName, Addr, Port, Dir) -> 286 mod_auth_server:list_group_members(Addr, Port, Dir, 287 GroupName, ?NOPASSWORD). 288 289update_password(Port, Dir, Old, New, New)-> 290 update_password(undefined, Port, Dir, Old, New, New). 291 292update_password(Addr, Port, Dir, Old, New, New) when is_list(New) -> 293 mod_auth_server:update_password(Addr, Port, Dir, Old, New); 294 295update_password(_Addr, _Port, _Dir, _Old, New, New) -> 296 {error, badtype}; 297update_password(_Addr, _Port, _Dir, _Old, _New, _New1) -> 298 {error, notqeual}. 299 300%%-------------------------------------------------------------------- 301%%% Internal functions 302%%-------------------------------------------------------------------- 303 304do_auth(Info, Directory, DirectoryData, _AuthType) -> 305 %% Authenticate (require) 306 case require(Info, Directory, DirectoryData) of 307 authorized -> 308 {proceed,Info#mod.data}; 309 {authorized, User} -> 310 {proceed, [{remote_user,User}|Info#mod.data]}; 311 {authorization_required, Realm} -> 312 ReasonPhrase = httpd_util:reason_phrase(401), 313 Message = httpd_util:message(401,none,Info#mod.config_db), 314 {proceed, 315 [{response, 316 {401, 317 ["WWW-Authenticate: Basic realm=\"",Realm, 318 "\"\r\n\r\n","<HTML>\n<HEAD>\n<TITLE>", 319 ReasonPhrase,"</TITLE>\n", 320 "</HEAD>\n<BODY>\n<H1>",ReasonPhrase, 321 "</H1>\n",Message,"\n</BODY>\n</HTML>\n"]}}| 322 Info#mod.data]}; 323 {status, {StatusCode,PhraseArgs,Reason}} -> 324 {proceed, [{status,{StatusCode,PhraseArgs,Reason}}| 325 Info#mod.data]} 326 end. 327 328require(Info, Directory, DirectoryData) -> 329 ParsedHeader = Info#mod.parsed_header, 330 ValidUsers = proplists:get_value(require_user, DirectoryData), 331 ValidGroups = proplists:get_value(require_group, DirectoryData), 332 %% Any user or group restrictions? 333 case ValidGroups of 334 undefined when ValidUsers =:= undefined -> 335 authorized; 336 _ -> 337 case proplists:get_value("authorization", ParsedHeader) of 338 undefined -> 339 authorization_required(DirectoryData); 340 %% Check credentials! 341 "Basic" ++ EncodedString = Credentials -> 342 case (catch base64:decode_to_string(EncodedString)) of 343 {'EXIT',{function_clause, _}} -> 344 {status, {401, none, ?NICE("Bad credentials "++ 345 Credentials)}}; 346 DecodedString -> 347 validate_user(Info, Directory, DirectoryData, 348 ValidUsers, ValidGroups, 349 DecodedString) 350 end; 351 %% Bad credentials! 352 BadCredentials -> 353 {status, {401, none, ?NICE("Bad credentials "++ 354 BadCredentials)}} 355 end 356 end. 357 358authorization_required(DirectoryData) -> 359 case proplists:get_value(auth_name, DirectoryData) of 360 undefined -> 361 {status,{500, none,?NICE("AuthName directive not specified")}}; 362 Realm -> 363 {authorization_required, Realm} 364 end. 365 366 367validate_user(Info, Directory, DirectoryData, ValidUsers, 368 ValidGroups, DecodedString) -> 369 case a_valid_user(Info, DecodedString, 370 ValidUsers, ValidGroups, 371 Directory, DirectoryData) of 372 {yes, User} -> 373 {authorized, User}; 374 {no, _Reason} -> 375 authorization_required(DirectoryData); 376 {status, {StatusCode,PhraseArgs,Reason}} -> 377 {status,{StatusCode,PhraseArgs,Reason}} 378 end. 379 380a_valid_user(Info,DecodedString,ValidUsers,ValidGroups,Dir,DirData) -> 381 case httpd_util:split(DecodedString,":",2) of 382 {ok, [SupposedUser, Password]} -> 383 case user_accepted(SupposedUser, ValidUsers) of 384 true -> 385 check_password(SupposedUser, Password, Dir, DirData); 386 false -> 387 case group_accepted(Info,SupposedUser, 388 ValidGroups,Dir,DirData) of 389 true -> 390 check_password(SupposedUser,Password,Dir,DirData); 391 false -> 392 {no,?NICE("No such user exists")} 393 end 394 end; 395 {ok, BadCredentials} -> 396 {status,{401,none,?NICE("Bad credentials "++BadCredentials)}} 397 end. 398 399user_accepted(_SupposedUser, undefined) -> 400 false; 401user_accepted(SupposedUser, ValidUsers) -> 402 lists:member(SupposedUser, ValidUsers). 403 404 405group_accepted(_Info, _User, undefined, _Dir, _DirData) -> 406 false; 407group_accepted(_Info, _User, [], _Dir, _DirData) -> 408 false; 409group_accepted(Info, User, [Group|Rest], Dir, DirData) -> 410 Ret = int_list_group_members(Group, Dir, DirData), 411 case Ret of 412 {ok, UserList} -> 413 case lists:member(User, UserList) of 414 true -> 415 true; 416 false -> 417 group_accepted(Info, User, Rest, Dir, DirData) 418 end; 419 _ -> 420 false 421 end. 422 423check_password(User, Password, _Dir, DirData) -> 424 case int_get_user(DirData, User) of 425 {ok, UStruct} -> 426 case UStruct#httpd_user.password of 427 Password -> 428 %% FIXME 429 {yes, UStruct#httpd_user.username}; 430 _ -> 431 {no, "No such user"} % Don't say 'Bad Password' !!! 432 end; 433 _Other -> 434 {no, "No such user"} 435 end. 436 437 438%% Middle API. Theese functions call the appropriate authentication module. 439int_get_user(DirData, User) -> 440 AuthMod = auth_mod_name(DirData), 441 apply(AuthMod, get_user, [DirData, User]). 442 443int_list_group_members(Group, _Dir, DirData) -> 444 AuthMod = auth_mod_name(DirData), 445 apply(AuthMod, list_group_members, [DirData, Group]). 446 447auth_mod_name(DirData) -> 448 case proplists:get_value(auth_type, DirData, plain) of 449 plain -> mod_auth_plain; 450 mnesia -> mod_auth_mnesia; 451 dets -> mod_auth_dets 452 end. 453 454secretp(Path,ConfigDB) -> 455 Directories = ets:match(ConfigDB,{directory, {'$1','_'}}), 456 case secret_path(Path, Directories) of 457 {yes,Directory} -> 458 {yes, {Directory, 459 lists:flatten( 460 ets:match(ConfigDB,{directory, {Directory,'$1'}}))}}; 461 no -> 462 no 463 end. 464 465secret_path(Path, Directories) -> 466 secret_path(Path, httpd_util:uniq(lists:sort(Directories)),to_be_found). 467 468secret_path(_Path, [], to_be_found) -> 469 no; 470secret_path(_Path, [], Directory) -> 471 {yes, Directory}; 472secret_path(Path, [[NewDirectory] | Rest], Directory) -> 473 case re:run(Path, NewDirectory, [{capture, first}]) of 474 {match, _} when Directory =:= to_be_found -> 475 secret_path(Path, Rest, NewDirectory); 476 {match, [{_, Length}]} when Length > length(Directory)-> 477 secret_path(Path, Rest,NewDirectory); 478 {match, _} -> 479 secret_path(Path, Rest, Directory); 480 nomatch -> 481 secret_path(Path, Rest, Directory) 482 end. 483 484allow({_,RemoteAddr}, _SocketType, _Socket, DirectoryData) -> 485 Hosts = proplists:get_value(allow_from, DirectoryData, all), 486 case validate_addr(RemoteAddr, Hosts) of 487 true -> 488 allowed; 489 false -> 490 {not_allowed, ?NICE("Connection from your host is not allowed")} 491 end. 492 493validate_addr(_RemoteAddr, all) -> % When called from 'allow' 494 true; 495validate_addr(_RemoteAddr, none) -> % When called from 'deny' 496 false; 497validate_addr(_RemoteAddr, []) -> 498 false; 499validate_addr(RemoteAddr, [HostRegExp | Rest]) -> 500 case re:run(RemoteAddr, HostRegExp, [{capture, none}]) of 501 match -> 502 true; 503 nomatch -> 504 validate_addr(RemoteAddr,Rest) 505 end. 506 507deny({_,RemoteAddr}, _SocketType, _Socket,DirectoryData) -> 508 Hosts = proplists:get_value(deny_from, DirectoryData, none), 509 case validate_addr(RemoteAddr,Hosts) of 510 true -> 511 {denied, ?NICE("Connection from your host is not allowed")}; 512 false -> 513 not_denied 514 end. 515 516 517directory_config_check(Directory, DirData) -> 518 case proplists:get_value(auth_type, DirData) of 519 plain -> 520 check_filename_present(Directory,auth_user_file,DirData), 521 check_filename_present(Directory,auth_group_file,DirData); 522 _ -> 523 ok 524 end. 525check_filename_present(Dir,AuthFile,DirData) -> 526 case proplists:get_value(AuthFile,DirData) of 527 Name when is_list(Name) -> 528 ok; 529 _ -> 530 throw({missing_auth_file, AuthFile, {directory, {Dir, DirData}}}) 531 end. 532 533store_directory(Directory0, DirData0, ConfigList) -> 534 Port = proplists:get_value(port, ConfigList), 535 DirData = case proplists:get_value(bind_address, ConfigList) of 536 undefined -> 537 [{port, Port}|DirData0]; 538 Addr -> 539 [{port, Port},{bind_address,Addr}|DirData0] 540 end, 541 Directory = 542 case filename:pathtype(Directory0) of 543 relative -> 544 SR = proplists:get_value(server_root, ConfigList), 545 filename:join(SR, Directory0); 546 _ -> 547 Directory0 548 end, 549 AuthMod = 550 case proplists:get_value(auth_type, DirData0) of 551 mnesia -> mod_auth_mnesia; 552 dets -> mod_auth_dets; 553 plain -> mod_auth_plain; 554 _ -> no_module_at_all 555 end, 556 case AuthMod of 557 no_module_at_all -> 558 {ok, {directory, {Directory, DirData}}}; 559 _ -> 560 %% Check that there are a password or add a standard password: 561 %% "NoPassword" 562 %% In this way a user must select to use a noPassword 563 Passwd = 564 case proplists:get_value(auth_access_password, DirData) of 565 undefined -> 566 ?NOPASSWORD; 567 PassW -> 568 PassW 569 end, 570 DirDataLast = lists:keydelete(auth_access_password,1,DirData), 571 Server_root = proplists:get_value(server_root, ConfigList), 572 case catch AuthMod:store_directory_data(Directory, 573 DirDataLast, 574 Server_root) of 575 ok -> 576 add_auth_password(Directory, Passwd, ConfigList), 577 {ok, {directory, {Directory, DirDataLast}}}; 578 {ok, NewDirData} -> 579 add_auth_password(Directory, Passwd, ConfigList), 580 {ok, {directory, {Directory, NewDirData}}}; 581 {error, Reason} -> 582 {error, Reason}; 583 Other -> 584 {error, Other} 585 end 586 end. 587 588add_auth_password(Dir, Pwd0, ConfigList) -> 589 Addr = proplists:get_value(bind_address, ConfigList), 590 Port = proplists:get_value(port, ConfigList), 591 Profile = proplists:get_value(profile, ConfigList, ?DEFAULT_PROFILE), 592 mod_auth_server:start(Addr, Port, Profile), 593 mod_auth_server:add_password(Addr, Port, Dir, Pwd0). 594 595%% Opt = [{port, Port}, 596%% {addr, Addr}, 597%% {dir, Dir}, 598%% {authPassword, AuthPassword} | FunctionSpecificData] 599get_options(Opt, mandatory)-> 600 case proplists:get_value(port, Opt, undefined) of 601 Port when is_integer(Port) -> 602 case proplists:get_value(dir, Opt, undefined) of 603 Dir when is_list(Dir) -> 604 Addr = proplists:get_value(addr, Opt, 605 undefined), 606 AuthPwd = proplists:get_value(authPassword, Opt, 607 ?NOPASSWORD), 608 {Addr, Port, Dir, AuthPwd}; 609 _-> 610 {error, bad_dir} 611 end; 612 _ -> 613 {error, bad_dir} 614 end; 615 616%% FunctionSpecificData = {userData, UserData} | {password, Password} 617get_options(Opt, userData)-> 618 case proplists:get_value(userData, Opt, undefined) of 619 undefined -> 620 {error, no_userdata}; 621 UserData -> 622 case proplists:get_value(password, Opt, undefined) of 623 undefined-> 624 {error, no_password}; 625 Pwd -> 626 {UserData, Pwd} 627 end 628 end. 629