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.erl,v 1.1 2008/12/17 09:53:35 mikpe Exp $
18%%
19-module(mod_security).
20
21%% Security Audit Functionality
22
23%% User API exports
24-export([list_blocked_users/1, list_blocked_users/2, list_blocked_users/3,
25	 block_user/4, block_user/5,
26	 unblock_user/2, unblock_user/3, unblock_user/4,
27	 list_auth_users/1, list_auth_users/2, list_auth_users/3]).
28
29%% module API exports
30-export([do/1, load/2, store/2, remove/1]).
31
32-include("httpd.hrl").
33
34-define(VMODULE,"SEC").
35-include("httpd_verbosity.hrl").
36
37
38%% do/1
39do(Info) ->
40    ?vdebug("~n   do with ~n   Info: ~p",[Info]),
41    %% Check and see if any user has been authorized.
42    case httpd_util:key1search(Info#mod.data,remote_user,not_defined_user) of
43	not_defined_user ->
44	    %% No user has been authorized.
45	    case httpd_util:key1search(Info#mod.data, status) of
46		%% A status code has been generated!
47		{401, PhraseArgs, Reason} ->
48		    case httpd_util:key1search(Info#mod.parsed_header,
49					       "authorization") of
50			undefined ->
51			    %% Not an authorization attempt (server just replied to
52			    %% challenge for authentication)
53			    {proceed, Info#mod.data};
54			[$B,$a,$s,$i,$c,$ |EncodedString] ->
55			    %% Someone tried to authenticate, and obviously failed!
56			    ?vlog("~n   Authentication failed: ~s",
57				  [EncodedString]),
58			    report_failed(Info, EncodedString,"Failed authentication"),
59			    take_failed_action(Info, EncodedString),
60			    {proceed, Info#mod.data}
61		    end;
62		_ ->
63		    {proceed, Info#mod.data}
64	    end;
65	User ->
66	    %% A user has been authenticated, now is he blocked ?
67	    ?vtrace("user '~p' authentication",[User]),
68	    Path = mod_alias:path(Info#mod.data,
69				  Info#mod.config_db,
70				  Info#mod.request_uri),
71	    {Dir, SDirData} = secretp(Path, Info#mod.config_db),
72	    Addr = httpd_util:lookup(Info#mod.config_db, bind_address),
73	    Port = httpd_util:lookup(Info#mod.config_db, port),
74	    DF   = httpd_util:key1search(SDirData, data_file),
75	    case mod_security_server:check_blocked_user(Info, User,
76							SDirData,
77							Addr, Port) of
78		true ->
79		    ?vtrace("user blocked",[]),
80		    report_failed(Info,httpd_util:decode_base64(User) ,"User Blocked"),
81		    {proceed, [{status, {403, Info#mod.request_uri, ""}}|Info#mod.data]};
82		false ->
83		    ?vtrace("user not blocked",[]),
84		    EncodedUser=httpd_util:decode_base64(User),
85		    report_failed(Info, EncodedUser,"Authentication Succedded"),
86		    mod_security_server:store_successful_auth(Addr, Port,
87							      User, SDirData),
88		    {proceed, Info#mod.data}
89	    end
90    end.
91
92
93
94report_failed(Info, EncodedString,Event) ->
95    Request = Info#mod.request_line,
96    Decoded = httpd_util:decode_base64(EncodedString),
97    {PortNumber,RemoteHost}=(Info#mod.init_data)#init_data.peername,
98    String = RemoteHost++" : " ++ Event ++ " : "++Request++" : "++Decoded,
99    mod_disk_log:security_log(Info,String),
100    mod_log:security_log(Info, String).
101
102take_failed_action(Info, EncodedString) ->
103    Path = mod_alias:path(Info#mod.data,Info#mod.config_db, Info#mod.request_uri),
104    {Dir, SDirData} = secretp(Path, Info#mod.config_db),
105    Addr = httpd_util:lookup(Info#mod.config_db, bind_address),
106    Port = httpd_util:lookup(Info#mod.config_db, port),
107    DecodedString = httpd_util:decode_base64(EncodedString),
108    mod_security_server:store_failed_auth(Info, Addr, Port,
109					  DecodedString, SDirData).
110
111secretp(Path, ConfigDB) ->
112    Directories = ets:match(ConfigDB,{directory,'$1','_'}),
113    case secret_path(Path, Directories) of
114	{yes, Directory} ->
115	    SDirs0 = httpd_util:multi_lookup(ConfigDB, security_directory),
116	    SDir = lists:filter(fun(X) ->
117					lists:member({path, Directory}, X)
118				end, SDirs0),
119	    {Directory, lists:flatten(SDir)};
120	no ->
121	    error_report({internal_error_secretp, ?MODULE}),
122	    {[], []}
123    end.
124
125secret_path(Path,Directories) ->
126    secret_path(Path, httpd_util:uniq(lists:sort(Directories)), to_be_found).
127
128secret_path(Path, [], to_be_found) ->
129    no;
130secret_path(Path, [], Directory) ->
131    {yes, Directory};
132secret_path(Path, [[NewDirectory]|Rest], Directory) ->
133    case regexp:match(Path, NewDirectory) of
134	{match, _, _} when Directory == to_be_found ->
135	    secret_path(Path, Rest, NewDirectory);
136	{match, _, Length} when Length > length(Directory)->
137	    secret_path(Path, Rest, NewDirectory);
138	{match, _, Length} ->
139	    secret_path(Path, Rest, Directory);
140	nomatch ->
141	    secret_path(Path, Rest, Directory)
142    end.
143
144
145load([$<,$D,$i,$r,$e,$c,$t,$o,$r,$y,$ |Directory],[]) ->
146    Dir = httpd_conf:custom_clean(Directory,"",">"),
147    {ok, [{security_directory, Dir, [{path, Dir}]}]};
148load(eof,[{security_directory,Directory, DirData}|_]) ->
149    {error, ?NICE("Premature end-of-file in "++Directory)};
150load([$S,$e,$c,$u,$r,$i,$t,$y,$D,$a,$t,$a,$F,$i,$l,$e,$ |FileName],
151     [{security_directory, Dir, DirData}]) ->
152    File = httpd_conf:clean(FileName),
153    {ok, [{security_directory, Dir, [{data_file, File}|DirData]}]};
154load([$S,$e,$c,$u,$r,$i,$t,$y,$C,$a,$l,$l,$b,$a,$c,$k,$M,$o,$d,$u,$l,$e,$ |ModuleName],
155     [{security_directory, Dir, DirData}]) ->
156    Mod = list_to_atom(httpd_conf:clean(ModuleName)),
157    {ok, [{security_directory, Dir, [{callback_module, Mod}|DirData]}]};
158load([$S,$e,$c,$u,$r,$i,$t,$y,$M,$a,$x,$R,$e,$t,$r,$i,$e,$s,$ |Retries],
159     [{security_directory, Dir, DirData}]) ->
160    MaxRetries = httpd_conf:clean(Retries),
161    load_return_int_tag("SecurityMaxRetries", max_retries,
162			httpd_conf:clean(Retries), Dir, DirData);
163load([$S,$e,$c,$u,$r,$i,$t,$y,$B,$l,$o,$c,$k,$T,$i,$m,$e,$ |Time],
164     [{security_directory, Dir, DirData}]) ->
165    load_return_int_tag("SecurityBlockTime", block_time,
166			httpd_conf:clean(Time), Dir, DirData);
167load([$S,$e,$c,$u,$r,$i,$t,$y,$F,$a,$i,$l,$E,$x,$p,$i,$r,$e,$T,$i,$m,$e,$ |Time],
168     [{security_directory, Dir, DirData}]) ->
169    load_return_int_tag("SecurityFailExpireTime", fail_expire_time,
170			httpd_conf:clean(Time), Dir, DirData);
171load([$S,$e,$c,$u,$r,$i,$t,$y,$A,$u,$t,$h,$T,$i,$m,$e,$o,$u,$t,$ |Time0],
172     [{security_directory, Dir, DirData}]) ->
173    Time = httpd_conf:clean(Time0),
174    load_return_int_tag("SecurityAuthTimeout", auth_timeout,
175			httpd_conf:clean(Time), Dir, DirData);
176load([$A,$u,$t,$h,$N,$a,$m,$e,$ |Name0],
177     [{security_directory, Dir, DirData}]) ->
178    Name = httpd_conf:clean(Name0),
179    {ok, [{security_directory, Dir, [{auth_name, Name}|DirData]}]};
180load("</Directory>",[{security_directory,Directory, DirData}]) ->
181    {ok, [], {security_directory, Directory, DirData}}.
182
183load_return_int_tag(Name, Atom, Time, Dir, DirData) ->
184    case Time of
185	"infinity" ->
186	    {ok, [{security_directory, Dir, [{Atom, 99999999999999999999999999999}|DirData]}]};
187	Int ->
188	    case catch list_to_integer(Time) of
189		{'EXIT', _} ->
190		    {error, Time++" is an invalid "++Name};
191		Val ->
192		    {ok, [{security_directory, Dir, [{Atom, Val}|DirData]}]}
193	    end
194    end.
195
196store({security_directory, Dir0, DirData}, ConfigList) ->
197    ?CDEBUG("store(security_directory) -> ~n"
198	    "      Dir0:       ~p~n"
199	    "      DirData:    ~p",
200	    [Dir0, DirData]),
201    Addr = httpd_util:key1search(ConfigList, bind_address),
202    Port = httpd_util:key1search(ConfigList, port),
203    mod_security_server:start(Addr, Port),
204    SR = httpd_util:key1search(ConfigList, server_root),
205    Dir =
206	case filename:pathtype(Dir0) of
207	    relative ->
208		filename:join(SR, Dir0);
209	    _ ->
210		Dir0
211	end,
212    case httpd_util:key1search(DirData, data_file, no_data_file) of
213	no_data_file ->
214	    {error, no_security_data_file};
215	DataFile0 ->
216	    DataFile =
217		case filename:pathtype(DataFile0) of
218		    relative ->
219			filename:join(SR, DataFile0);
220		    _ ->
221			DataFile0
222		end,
223	    case mod_security_server:new_table(Addr, Port, DataFile) of
224		{ok, TwoTables} ->
225		    NewDirData0 = lists:keyreplace(data_file, 1, DirData,
226						   {data_file, TwoTables}),
227		    NewDirData1 = case Addr of
228				      undefined ->
229					  [{port,Port}|NewDirData0];
230				      _ ->
231					  [{port,Port},{bind_address,Addr}|
232					   NewDirData0]
233				  end,
234		    {ok, {security_directory,NewDirData1}};
235		{error, Err} ->
236		    {error, {{open_data_file, DataFile}, Err}}
237	    end
238    end.
239
240
241remove(ConfigDB) ->
242    Addr = case ets:lookup(ConfigDB, bind_address) of
243	       [] ->
244		   undefined;
245	       [{bind_address, Address}] ->
246		   Address
247	   end,
248    [{port, Port}] = ets:lookup(ConfigDB, port),
249    mod_security_server:delete_tables(Addr, Port),
250    mod_security_server:stop(Addr, Port).
251
252
253%%
254%% User API
255%%
256
257%% list_blocked_users
258
259list_blocked_users(Port) ->
260    list_blocked_users(undefined, Port).
261
262list_blocked_users(Port, Dir) when integer(Port) ->
263    list_blocked_users(undefined,Port,Dir);
264list_blocked_users(Addr, Port) when integer(Port) ->
265    mod_security_server:list_blocked_users(Addr, Port).
266
267list_blocked_users(Addr, Port, Dir) ->
268    mod_security_server:list_blocked_users(Addr, Port, Dir).
269
270
271%% block_user
272
273block_user(User, Port, Dir, Time) ->
274    block_user(User, undefined, Port, Dir, Time).
275block_user(User, Addr, Port, Dir, Time) ->
276    mod_security_server:block_user(User, Addr, Port, Dir, Time).
277
278
279%% unblock_user
280
281unblock_user(User, Port) ->
282    unblock_user(User, undefined, Port).
283
284unblock_user(User, Port, Dir) when integer(Port) ->
285    unblock_user(User, undefined, Port, Dir);
286unblock_user(User, Addr, Port) when integer(Port) ->
287    mod_security_server:unblock_user(User, Addr, Port).
288
289unblock_user(User, Addr, Port, Dir) ->
290    mod_security_server:unblock_user(User, Addr, Port, Dir).
291
292
293%% list_auth_users
294
295list_auth_users(Port) ->
296    list_auth_users(undefined,Port).
297
298list_auth_users(Port, Dir) when integer(Port) ->
299    list_auth_users(undefined, Port, Dir);
300list_auth_users(Addr, Port) when integer(Port) ->
301    mod_security_server:list_auth_users(Addr, Port).
302
303list_auth_users(Addr, Port, Dir) ->
304    mod_security_server:list_auth_users(Addr, Port, Dir).
305
306
307error_report(M) ->
308    error_logger:error_report(M).
309