1%% ``Licensed under the Apache License, Version 2.0 (the "License"); 2%% you may not use this file except in compliance with the License. 3%% You may obtain a copy of the License at 4%% 5%% http://www.apache.org/licenses/LICENSE-2.0 6%% 7%% Unless required by applicable law or agreed to in writing, software 8%% distributed under the License is distributed on an "AS IS" BASIS, 9%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10%% See the License for the specific language governing permissions and 11%% limitations under the License. 12%% 13%% The Initial Developer of the Original Code is Ericsson Utvecklings AB. 14%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings 15%% AB. All Rights Reserved.'' 16%% 17%% $Id: mod_security_server.erl,v 1.1 2008/12/17 09:53:36 mikpe Exp $ 18%% 19%% Security Audit Functionality 20 21%% 22%% The gen_server code. 23%% 24%% A gen_server is needed in this module to take care of shared access to the 25%% data file used to store failed and successful authentications aswell as 26%% user blocks. 27%% 28%% The storage model is a write-through model with both an ets and a dets 29%% table. Writes are done to both the ets and then the dets table, but reads 30%% are only done from the ets table. 31%% 32%% This approach also enables parallelism when using dets by returning the 33%% same dets table identifier when opening several files with the same 34%% physical location. 35%% 36%% NOTE: This could be implemented using a single dets table, as it is 37%% possible to open a dets file with the ram_file flag, but this 38%% would require periodical sync's to disk, and it would be hard 39%% to decide when such an operation should occur. 40%% 41 42-module(mod_security_server). 43 44-include("httpd.hrl"). 45-include("httpd_verbosity.hrl"). 46 47 48-behaviour(gen_server). 49 50 51%% User API exports (called via mod_security) 52-export([list_blocked_users/2, list_blocked_users/3, 53 block_user/5, 54 unblock_user/3, unblock_user/4, 55 list_auth_users/2, list_auth_users/3]). 56 57%% Internal exports (for mod_security only) 58-export([start/2, stop/1, stop/2, 59 new_table/3, delete_tables/2, 60 store_failed_auth/5, store_successful_auth/4, 61 check_blocked_user/5]). 62 63%% gen_server exports 64-export([start_link/3, 65 init/1, 66 handle_info/2, handle_call/3, handle_cast/2, 67 terminate/2, 68 code_change/3]). 69 70-export([verbosity/3]). 71 72 73%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 74%% %% 75%% External API %% 76%% %% 77%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 78 79%% start_link/3 80%% 81%% NOTE: This is called by httpd_misc_sup when the process is started 82%% 83 84start_link(Addr, Port, Verbosity) -> 85 ?vtrace("start_link -> entry with" 86 "~n Addr: ~p" 87 "~n Port: ~p", [Addr, Port]), 88 Name = make_name(Addr, Port), 89 gen_server:start_link({local, Name}, ?MODULE, [Verbosity], 90 [{timeout, infinity}]). 91 92 93%% start/2 94%% Called by the mod_security module. 95 96start(Addr, Port) -> 97 Name = make_name(Addr, Port), 98 case whereis(Name) of 99 undefined -> 100 Verbosity = get(security_verbosity), 101 case httpd_misc_sup:start_sec_server(Addr, Port, Verbosity) of 102 {ok, Pid} -> 103 put(security_server, Pid), 104 ok; 105 Error -> 106 exit({failed_start_security_server, Error}) 107 end; 108 _ -> %% Already started... 109 ok 110 end. 111 112 113%% stop 114 115stop(Port) -> 116 stop(undefined, Port). 117stop(Addr, Port) -> 118 Name = make_name(Addr, Port), 119 case whereis(Name) of 120 undefined -> 121 ok; 122 _ -> 123 httpd_misc_sup:stop_sec_server(Addr, Port) 124 end. 125 126 127%% verbosity 128 129verbosity(Addr, Port, Verbosity) -> 130 Name = make_name(Addr, Port), 131 Req = {verbosity, Verbosity}, 132 call(Name, Req). 133 134 135%% list_blocked_users 136 137list_blocked_users(Addr, Port) -> 138 Name = make_name(Addr,Port), 139 Req = {list_blocked_users, Addr, Port, '_'}, 140 call(Name, Req). 141 142list_blocked_users(Addr, Port, Dir) -> 143 Name = make_name(Addr, Port), 144 Req = {list_blocked_users, Addr, Port, Dir}, 145 call(Name, Req). 146 147 148%% block_user 149 150block_user(User, Addr, Port, Dir, Time) -> 151 Name = make_name(Addr, Port), 152 Req = {block_user, User, Addr, Port, Dir, Time}, 153 call(Name, Req). 154 155 156%% unblock_user 157 158unblock_user(User, Addr, Port) -> 159 Name = make_name(Addr, Port), 160 Req = {unblock_user, User, Addr, Port, '_'}, 161 call(Name, Req). 162 163unblock_user(User, Addr, Port, Dir) -> 164 Name = make_name(Addr, Port), 165 Req = {unblock_user, User, Addr, Port, Dir}, 166 call(Name, Req). 167 168 169%% list_auth_users 170 171list_auth_users(Addr, Port) -> 172 Name = make_name(Addr, Port), 173 Req = {list_auth_users, Addr, Port, '_'}, 174 call(Name, Req). 175 176list_auth_users(Addr, Port, Dir) -> 177 Name = make_name(Addr,Port), 178 Req = {list_auth_users, Addr, Port, Dir}, 179 call(Name, Req). 180 181 182%% new_table 183 184new_table(Addr, Port, TabName) -> 185 Name = make_name(Addr,Port), 186 Req = {new_table, Addr, Port, TabName}, 187 call(Name, Req). 188 189 190%% delete_tables 191 192delete_tables(Addr, Port) -> 193 Name = make_name(Addr, Port), 194 case whereis(Name) of 195 undefined -> 196 ok; 197 _ -> 198 call(Name, delete_tables) 199 end. 200 201 202%% store_failed_auth 203 204store_failed_auth(Info, Addr, Port, DecodedString, SDirData) -> 205 Name = make_name(Addr,Port), 206 Msg = {store_failed_auth,[Info,DecodedString,SDirData]}, 207 cast(Name, Msg). 208 209 210%% store_successful_auth 211 212store_successful_auth(Addr, Port, User, SDirData) -> 213 Name = make_name(Addr,Port), 214 Msg = {store_successful_auth, [User,Addr,Port,SDirData]}, 215 cast(Name, Msg). 216 217 218%% check_blocked_user 219 220check_blocked_user(Info, User, SDirData, Addr, Port) -> 221 Name = make_name(Addr, Port), 222 Req = {check_blocked_user, [Info, User, SDirData]}, 223 call(Name, Req). 224 225 226%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 227%% %% 228%% Server call-back functions %% 229%% %% 230%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 231 232%% init 233 234init([undefined]) -> 235 init([?default_verbosity]); 236init([Verbosity]) -> 237 ?DEBUG("init -> entry with Verbosity: ~p",[Verbosity]), 238 process_flag(trap_exit, true), 239 put(sname, sec), 240 put(verbosity, Verbosity), 241 ?vlog("starting",[]), 242 {ok, []}. 243 244 245%% handle_call 246 247handle_call(stop, _From, Tables) -> 248 ?vlog("stop",[]), 249 {stop, normal, ok, []}; 250 251 252handle_call({verbosity,Verbosity}, _From, Tables) -> 253 ?vlog("set verbosity to ~p",[Verbosity]), 254 OldVerbosity = get(verbosity), 255 put(verbosity,Verbosity), 256 ?vdebug("old verbosity: ~p",[OldVerbosity]), 257 {reply,OldVerbosity,Tables}; 258 259 260handle_call({block_user, User, Addr, Port, Dir, Time}, _From, Tables) -> 261 ?vlog("block user '~p' for ~p",[User,Dir]), 262 Ret = block_user_int({User, Addr, Port, Dir, Time}), 263 ?vdebug("block user result: ~p",[Ret]), 264 {reply, Ret, Tables}; 265 266 267handle_call({list_blocked_users, Addr, Port, Dir}, _From, Tables) -> 268 ?vlog("list blocked users for ~p",[Dir]), 269 Blocked = list_blocked(Tables, Addr, Port, Dir, []), 270 ?vdebug("list blocked users: ~p",[Blocked]), 271 {reply, Blocked, Tables}; 272 273 274handle_call({unblock_user, User, Addr, Port, Dir}, _From, Tables) -> 275 ?vlog("unblock user '~p' for ~p",[User,Dir]), 276 Ret = unblock_user_int({User, Addr, Port, Dir}), 277 ?vdebug("unblock user result: ~p",[Ret]), 278 {reply, Ret, Tables}; 279 280 281handle_call({list_auth_users, Addr, Port, Dir}, _From, Tables) -> 282 ?vlog("list auth users for ~p",[Dir]), 283 Auth = list_auth(Tables, Addr, Port, Dir, []), 284 ?vdebug("list auth users result: ~p",[Auth]), 285 {reply, Auth, Tables}; 286 287 288handle_call({new_table, Addr, Port, Name}, _From, Tables) -> 289 case lists:keysearch(Name, 1, Tables) of 290 {value, {Name, {Ets, Dets}}} -> 291 ?DEBUG("handle_call(new_table) -> we already have this table: ~p", 292 [Name]), 293 ?vdebug("new table; we already have this one: ~p",[Name]), 294 {reply, {ok, {Ets, Dets}}, Tables}; 295 false -> 296 ?LOG("handle_call(new_table) -> new_table: Name = ~p",[Name]), 297 ?vlog("new table: ~p",[Name]), 298 TName = make_name(Addr,Port,length(Tables)), 299 ?DEBUG("handle_call(new_table) -> TName: ~p",[TName]), 300 ?vdebug("new table: ~p",[TName]), 301 case dets:open_file(TName, [{type, bag}, {file, Name}, 302 {repair, true}, 303 {access, read_write}]) of 304 {ok, DFile} -> 305 ETS = ets:new(TName, [bag, private]), 306 sync_dets_to_ets(DFile, ETS), 307 NewTables = [{Name, {ETS, DFile}}|Tables], 308 ?DEBUG("handle_call(new_table) -> ~n" 309 " NewTables: ~p",[NewTables]), 310 ?vtrace("new tables: ~p",[NewTables]), 311 {reply, {ok, {ETS, DFile}}, NewTables}; 312 {error, Err} -> 313 ?LOG("handle_call -> Err: ~p",[Err]), 314 ?vinfo("failed open dets file: ~p",[Err]), 315 {reply, {error, {create_dets, Err}}, Tables} 316 end 317 end; 318 319handle_call(delete_tables, _From, Tables) -> 320 ?vlog("delete tables",[]), 321 lists:foreach(fun({Name, {ETS, DETS}}) -> 322 dets:close(DETS), 323 ets:delete(ETS) 324 end, Tables), 325 {reply, ok, []}; 326 327handle_call({check_blocked_user, [Info, User, SDirData]}, _From, Tables) -> 328 ?vlog("check blocked user '~p'",[User]), 329 {ETS, DETS} = httpd_util:key1search(SDirData, data_file), 330 Dir = httpd_util:key1search(SDirData, path), 331 Addr = httpd_util:key1search(SDirData, bind_address), 332 Port = httpd_util:key1search(SDirData, port), 333 CBModule = httpd_util:key1search(SDirData, callback_module, no_module_at_all), 334 ?vdebug("call back module: ~p",[CBModule]), 335 Ret = check_blocked_user(Info, User, Dir, Addr, Port, ETS, DETS, CBModule), 336 ?vdebug("check result: ~p",[Ret]), 337 {reply, Ret, Tables}; 338handle_call(Request,From,Tables) -> 339 ?vinfo("~n unknown call '~p' from ~p",[Request,From]), 340 {reply,ok,Tables}. 341 342 343%% handle_cast 344 345handle_cast({store_failed_auth, [Info, DecodedString, SDirData]}, Tables) -> 346 ?vlog("store failed auth",[]), 347 {ETS, DETS} = httpd_util:key1search(SDirData, data_file), 348 Dir = httpd_util:key1search(SDirData, path), 349 Addr = httpd_util:key1search(SDirData, bind_address), 350 Port = httpd_util:key1search(SDirData, port), 351 {ok, [User,Password]} = httpd_util:split(DecodedString,":",2), 352 ?vdebug("user '~p' and password '~p'",[User,Password]), 353 Seconds = universal_time(), 354 Key = {User, Dir, Addr, Port}, 355 356 %% Event 357 CBModule = httpd_util:key1search(SDirData, callback_module, no_module_at_all), 358 ?vtrace("call back module: ~p",[CBModule]), 359 auth_fail_event(CBModule,Addr,Port,Dir,User,Password), 360 361 %% Find out if any of this user's other failed logins are too old to keep.. 362 ?vtrace("remove old login failures",[]), 363 case ets:match_object(ETS, {failed, {Key, '_', '_'}}) of 364 [] -> 365 ?vtrace("no old login failures",[]), 366 no; 367 List when list(List) -> 368 ?vtrace("~p old login failures",[length(List)]), 369 ExpireTime = httpd_util:key1search(SDirData, fail_expire_time, 30)*60, 370 ?vtrace("expire time ~p",[ExpireTime]), 371 lists:map(fun({failed, {TheKey, LS, Gen}}) -> 372 Diff = Seconds-LS, 373 if 374 Diff > ExpireTime -> 375 ?vtrace("~n '~p' is to old to keep: ~p", 376 [TheKey,Gen]), 377 ets:match_delete(ETS, {failed, {TheKey, LS, Gen}}), 378 dets:match_delete(DETS, {failed, {TheKey, LS, Gen}}); 379 true -> 380 ?vtrace("~n '~p' is not old enough: ~p", 381 [TheKey,Gen]), 382 ok 383 end 384 end, 385 List); 386 O -> 387 ?vlog("~n unknown login failure search resuylt: ~p",[O]), 388 no 389 end, 390 391 %% Insert the new failure.. 392 Generation = length(ets:match_object(ETS, {failed, {Key, '_', '_'}})), 393 ?vtrace("insert ('~p') new login failure: ~p",[Key,Generation]), 394 ets:insert(ETS, {failed, {Key, Seconds, Generation}}), 395 dets:insert(DETS, {failed, {Key, Seconds, Generation}}), 396 397 %% See if we should block this user.. 398 MaxRetries = httpd_util:key1search(SDirData, max_retries, 3), 399 BlockTime = httpd_util:key1search(SDirData, block_time, 60), 400 ?vtrace("~n Max retries ~p, block time ~p",[MaxRetries,BlockTime]), 401 case ets:match_object(ETS, {failed, {Key, '_', '_'}}) of 402 List1 -> 403 ?vtrace("~n ~p tries so far",[length(List1)]), 404 if 405 length(List1) >= MaxRetries -> 406 %% Block this user until Future 407 ?vtrace("block user '~p'",[User]), 408 Future = Seconds+BlockTime*60, 409 ?vtrace("future: ~p",[Future]), 410 Reason = io_lib:format("Blocking user ~s from dir ~s " 411 "for ~p minutes", 412 [User, Dir, BlockTime]), 413 mod_log:security_log(Info, lists:flatten(Reason)), 414 415 %% Event 416 user_block_event(CBModule,Addr,Port,Dir,User), 417 418 ets:match_delete(ETS,{blocked_user, 419 {User, Addr, Port, Dir, '$1'}}), 420 dets:match_delete(DETS, {blocked_user, 421 {User, Addr, Port, Dir, '$1'}}), 422 BlockRecord = {blocked_user, 423 {User, Addr, Port, Dir, Future}}, 424 ets:insert(ETS, BlockRecord), 425 dets:insert(DETS, BlockRecord), 426 %% Remove previous failed requests. 427 ets:match_delete(ETS, {failed, {Key, '_', '_'}}), 428 dets:match_delete(DETS, {failed, {Key, '_', '_'}}); 429 true -> 430 ?vtrace("still some tries to go",[]), 431 no 432 end; 433 Other -> 434 no 435 end, 436 {noreply, Tables}; 437 438handle_cast({store_successful_auth, [User, Addr, Port, SDirData]}, Tables) -> 439 ?vlog("store successfull auth",[]), 440 {ETS, DETS} = httpd_util:key1search(SDirData, data_file), 441 AuthTimeOut = httpd_util:key1search(SDirData, auth_timeout, 30), 442 Dir = httpd_util:key1search(SDirData, path), 443 Key = {User, Dir, Addr, Port}, 444 445 %% Remove failed entries for this Key 446 dets:match_delete(DETS, {failed, {Key, '_', '_'}}), 447 ets:match_delete(ETS, {failed, {Key, '_', '_'}}), 448 449 %% Keep track of when the last successful login took place. 450 Seconds = universal_time()+AuthTimeOut, 451 ets:match_delete(ETS, {success, {Key, '_'}}), 452 dets:match_delete(DETS, {success, {Key, '_'}}), 453 ets:insert(ETS, {success, {Key, Seconds}}), 454 dets:insert(DETS, {success, {Key, Seconds}}), 455 {noreply, Tables}; 456 457handle_cast(Req, Tables) -> 458 ?vinfo("~n unknown cast '~p'",[Req]), 459 error_msg("security server got unknown cast: ~p",[Req]), 460 {noreply, Tables}. 461 462 463%% handle_info 464 465handle_info(Info, State) -> 466 ?vinfo("~n unknown info '~p'",[Info]), 467 {noreply, State}. 468 469 470%% terminate 471 472terminate(Reason, _Tables) -> 473 ?vlog("~n Terminating for reason: ~p",[Reason]), 474 ok. 475 476 477%% code_change({down, ToVsn}, State, Extra) 478%% 479code_change({down, _}, State, _Extra) -> 480 ?vlog("downgrade", []), 481 {ok, State}; 482 483 484%% code_change(FromVsn, State, Extra) 485%% 486code_change(_, State, Extra) -> 487 ?vlog("upgrade", []), 488 {ok, State}. 489 490 491 492 493%% block_user_int/2 494block_user_int({User, Addr, Port, Dir, Time}) -> 495 Dirs = httpd_manager:config_match(Addr, Port, {security_directory, '_'}), 496 ?vtrace("block '~p' for ~p during ~p",[User,Dir,Time]), 497 case find_dirdata(Dirs, Dir) of 498 {ok, DirData, {ETS, DETS}} -> 499 Time1 = 500 case Time of 501 infinity -> 502 99999999999999999999999999999; 503 _ -> 504 Time 505 end, 506 Future = universal_time()+Time1, 507 ets:match_delete(ETS, {blocked_user, {User,Addr,Port,Dir,'_'}}), 508 dets:match_delete(DETS, {blocked_user, {User,Addr,Port,Dir,'_'}}), 509 ets:insert(ETS, {blocked_user, {User,Addr,Port,Dir,Future}}), 510 dets:insert(DETS, {blocked_user, {User,Addr,Port,Dir,Future}}), 511 CBModule = httpd_util:key1search(DirData, callback_module, 512 no_module_at_all), 513 ?vtrace("call back module ~p",[CBModule]), 514 user_block_event(CBModule,Addr,Port,Dir,User), 515 true; 516 _ -> 517 {error, no_such_directory} 518 end. 519 520 521find_dirdata([], _Dir) -> 522 false; 523find_dirdata([{security_directory, DirData}|SDirs], Dir) -> 524 case lists:keysearch(path, 1, DirData) of 525 {value, {path, Dir}} -> 526 {value, {data_file, {ETS, DETS}}} = 527 lists:keysearch(data_file, 1, DirData), 528 {ok, DirData, {ETS, DETS}}; 529 _ -> 530 find_dirdata(SDirs, Dir) 531 end. 532 533%% unblock_user_int/2 534 535unblock_user_int({User, Addr, Port, Dir}) -> 536 ?vtrace("unblock user '~p' for ~p",[User,Dir]), 537 Dirs = httpd_manager:config_match(Addr, Port, {security_directory, '_'}), 538 ?vtrace("~n dirs: ~p",[Dirs]), 539 case find_dirdata(Dirs, Dir) of 540 {ok, DirData, {ETS, DETS}} -> 541 case ets:match_object(ETS,{blocked_user,{User,Addr,Port,Dir,'_'}}) of 542 [] -> 543 ?vtrace("not blocked",[]), 544 {error, not_blocked}; 545 Objects -> 546 ets:match_delete(ETS, {blocked_user, 547 {User, Addr, Port, Dir, '_'}}), 548 dets:match_delete(DETS, {blocked_user, 549 {User, Addr, Port, Dir, '_'}}), 550 CBModule = httpd_util:key1search(DirData, callback_module, 551 no_module_at_all), 552 user_unblock_event(CBModule,Addr,Port,Dir,User), 553 true 554 end; 555 _ -> 556 ?vlog("~n cannot unblock: no such directory '~p'",[Dir]), 557 {error, no_such_directory} 558 end. 559 560 561 562%% list_auth/2 563 564list_auth([], _Addr, _Port, Dir, Acc) -> 565 Acc; 566list_auth([{Name, {ETS, DETS}}|Tables], Addr, Port, Dir, Acc) -> 567 case ets:match_object(ETS, {success, {{'_', Dir, Addr, Port}, '_'}}) of 568 [] -> 569 list_auth(Tables, Addr, Port, Dir, Acc); 570 List when list(List) -> 571 TN = universal_time(), 572 NewAcc = lists:foldr(fun({success,{{U,Ad,P,D},T}},Ac) -> 573 if 574 T-TN > 0 -> 575 [U|Ac]; 576 true -> 577 Rec = {success,{{U,Ad,P,D},T}}, 578 ets:match_delete(ETS,Rec), 579 dets:match_delete(DETS,Rec), 580 Ac 581 end 582 end, 583 Acc, List), 584 list_auth(Tables, Addr, Port, Dir, NewAcc); 585 _ -> 586 list_auth(Tables, Addr, Port, Dir, Acc) 587 end. 588 589 590%% list_blocked/2 591 592list_blocked([], Addr, Port, Dir, Acc) -> 593 TN = universal_time(), 594 lists:foldl(fun({U,Ad,P,D,T}, Ac) -> 595 if 596 T-TN > 0 -> 597 [{U,Ad,P,D,local_time(T)}|Ac]; 598 true -> 599 Ac 600 end 601 end, 602 [], Acc); 603list_blocked([{Name, {ETS, DETS}}|Tables], Addr, Port, Dir, Acc) -> 604 NewBlocked = 605 case ets:match_object(ETS, {blocked_user, {'_',Addr,Port,Dir,'_'}}) of 606 List when list(List) -> 607 lists:foldl(fun({blocked_user, X}, A) -> [X|A] end, Acc, List); 608 _ -> 609 Acc 610 end, 611 list_blocked(Tables, Addr, Port, Dir, NewBlocked). 612 613 614%% 615%% sync_dets_to_ets/2 616%% 617%% Reads dets-table DETS and syncronizes it with the ets-table ETS. 618%% 619sync_dets_to_ets(DETS, ETS) -> 620 dets:traverse(DETS, fun(X) -> 621 ets:insert(ETS, X), 622 continue 623 end). 624 625%% 626%% check_blocked_user/7 -> true | false 627%% 628%% Check if a specific user is blocked from access. 629%% 630%% The sideeffect of this routine is that it unblocks also other users 631%% whos blocking time has expired. This to keep the tables as small 632%% as possible. 633%% 634check_blocked_user(Info, User, Dir, Addr, Port, ETS, DETS, CBModule) -> 635 TN = universal_time(), 636 case ets:match_object(ETS, {blocked_user, {User, '_', '_', '_', '_'}}) of 637 List when list(List) -> 638 Blocked = lists:foldl(fun({blocked_user, X}, A) -> 639 [X|A] end, [], List), 640 check_blocked_user(Info,User,Dir,Addr,Port,ETS,DETS,TN,Blocked,CBModule); 641 _ -> 642 false 643 end. 644check_blocked_user(Info, User, Dir, Addr, Port, ETS, DETS, TN, [], CBModule) -> 645 false; 646check_blocked_user(Info, User, Dir, Addr, Port, ETS, DETS, TN, 647 [{User,Addr,Port,Dir,T}|Ls], CBModule) -> 648 TD = T-TN, 649 if 650 TD =< 0 -> 651 %% Blocking has expired, remove and grant access. 652 unblock_user(Info, User, Dir, Addr, Port, ETS, DETS, CBModule), 653 false; 654 true -> 655 true 656 end; 657check_blocked_user(Info, User, Dir, Addr, Port, ETS, DETS, TN, 658 [{OUser,ODir,OAddr,OPort,T}|Ls], CBModule) -> 659 TD = T-TN, 660 if 661 TD =< 0 -> 662 %% Blocking has expired, remove. 663 unblock_user(Info, OUser, ODir, OAddr, OPort, ETS, DETS, CBModule); 664 true -> 665 true 666 end, 667 check_blocked_user(Info, User, Dir, Addr, Port, ETS, DETS, TN, Ls, CBModule). 668 669unblock_user(Info, User, Dir, Addr, Port, ETS, DETS, CBModule) -> 670 Reason=io_lib:format("User ~s was removed from the block list for dir ~s", 671 [User, Dir]), 672 mod_log:security_log(Info, lists:flatten(Reason)), 673 user_unblock_event(CBModule,Addr,Port,Dir,User), 674 dets:match_delete(DETS, {blocked_user, {User, Addr, Port, Dir, '_'}}), 675 ets:match_delete(ETS, {blocked_user, {User, Addr, Port, Dir, '_'}}). 676 677 678make_name(Addr,Port) -> 679 httpd_util:make_name("httpd_security",Addr,Port). 680 681make_name(Addr,Port,Num) -> 682 httpd_util:make_name("httpd_security",Addr,Port, 683 "__" ++ integer_to_list(Num)). 684 685 686auth_fail_event(Mod,Addr,Port,Dir,User,Passwd) -> 687 event(auth_fail,Mod,Addr,Port,Dir,[{user,User},{password,Passwd}]). 688 689user_block_event(Mod,Addr,Port,Dir,User) -> 690 event(user_block,Mod,Addr,Port,Dir,[{user,User}]). 691 692user_unblock_event(Mod,Addr,Port,Dir,User) -> 693 event(user_unblock,Mod,Addr,Port,Dir,[{user,User}]). 694 695event(Event,Mod,undefined,Port,Dir,Info) -> 696 (catch Mod:event(Event,Port,Dir,Info)); 697event(Event,Mod,Addr,Port,Dir,Info) -> 698 (catch Mod:event(Event,Addr,Port,Dir,Info)). 699 700universal_time() -> 701 calendar:datetime_to_gregorian_seconds(calendar:universal_time()). 702 703local_time(T) -> 704 calendar:universal_time_to_local_time( 705 calendar:gregorian_seconds_to_datetime(T)). 706 707 708error_msg(F, A) -> 709 error_logger:error_msg(F, A). 710 711 712call(Name, Req) -> 713 case (catch gen_server:call(Name, Req)) of 714 {'EXIT', Reason} -> 715 {error, Reason}; 716 Reply -> 717 Reply 718 end. 719 720 721cast(Name, Msg) -> 722 case (catch gen_server:cast(Name, Msg)) of 723 {'EXIT', Reason} -> 724 {error, Reason}; 725 Result -> 726 Result 727 end. 728