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