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