%%
%% %CopyrightBegin%
%% 
%% Copyright Ericsson AB 2005-2022. All Rights Reserved.
%% 
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%%     http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%
%%

-module(httpd_mod).

-include_lib("common_test/include/ct.hrl").

%% General testcases bodies called from httpd_SUITE
-export([alias/4, actions/4, security/5, auth/4, auth_api/6,
	 auth_mnesia_api/4, htaccess/4, 
	 cgi/4, esi/4, get/4, head/4, all/4]).

%% Help functions 
-export([event/4, ssl_password_cb/0]).

%% Seconds before successful auths timeout.
-define(AUTH_TIMEOUT,5).


%%-------------------------------------------------------------------------
%% Test cases starts here.
%%-------------------------------------------------------------------------
alias(Type, Port, Host, Node) ->
    %% This is very crude, but...
    Opts = [], 
    ok = httpd_test_lib:verify_request(Type, Host, Port, Opts, Node, 
 				       "GET /pics/icon.sheet.gif "
 				       "HTTP/1.0\r\n\r\n",
 				       [{statuscode, 200},
 					{header, "Content-Type","image/gif"},
 					{header, "Server"},
 					{header, "Date"},
 				        {version, "HTTP/1.0"}]),
    
    ok = httpd_test_lib:verify_request(Type, Host, Port, Opts, Node, 
 				       "GET / HTTP/1.0\r\n\r\n",
 				       [{statuscode, 200},
 					{header, "Content-Type","text/html"},
 					{header, "Server"},
 					{header, "Date"},
 				        {version, "HTTP/1.0"}]),

    ok = httpd_test_lib:verify_request(Type, Host, Port, Opts, Node, 
 				       "GET /misc/ HTTP/1.0\r\n\r\n",
 				       [{statuscode, 200},
 					{header, "Content-Type","text/html"},
 					{header, "Server"},
 					{header, "Date"},
 				        {version, "HTTP/1.0"}]),

    %% Check redirection if trailing slash is missing.
    ok = httpd_test_lib:verify_request(Type, Host, Port, Opts, Node, 
 				       "GET /misc HTTP/1.0\r\n\r\n",
 				       [{statuscode, 301},
 					{header, "Location"},
 					{header, "Content-Type","text/html"},
				        {version, "HTTP/1.0"}]).

%%-------------------------------------------------------------------------
actions(Type, Port, Host, Node) ->
    ok = httpd_test_lib:verify_request(Type, Host, Port, Node, 
				       "HEAD / HTTP/1.0\r\n\r\n", 
				       [{statuscode, 200},
				        {version, "HTTP/1.0"}]).


%%-------------------------------------------------------------------------
security(ServerRoot, Type, Port, Host, Node) ->
   
    global:register_name(mod_security_test, self()),   % Receive events
   
    ct:sleep(5000),

    OpenDir = filename:join([ServerRoot, "htdocs", "open"]),

    %% Test blocking / unblocking of users.

    %% /open, require user one Aladdin
 
    remove_users(Node, ServerRoot, Host, Port, "open"),

    auth_request(Type, Host, Port, Node, "/open/", "one", "onePassword", 
		 [{statuscode, 401}]),

    receive_security_event({event, auth_fail, Port, OpenDir,
			    [{user, "one"}, {password, "onePassword"}]},
			   Node, Port),
    
    auth_request(Type,Host,Port,Node,"/open/", "two", "twoPassword",
		 [{statuscode, 401}]),
    
    receive_security_event({event, auth_fail, Port, OpenDir,
			    [{user, "two"}, {password, "twoPassword"}]},
			   Node, Port),
    auth_request(Type, Host, Port, Node,"/open/", "Aladdin", 
		 "AladdinPassword", [{statuscode, 401}]),

    receive_security_event({event, auth_fail, Port, OpenDir,
			    [{user, "Aladdin"},
			     {password, "AladdinPassword"}]},
			   Node, Port),
    add_user(Node, ServerRoot, Port, "open", "one", "onePassword", []),

    add_user(Node, ServerRoot, Port, "open", "two", "twoPassword", []),

    auth_request(Type, Host, Port, Node,"/open/", "one", "WrongPassword", 
		 [{statuscode, 401}]),
    receive_security_event({event, auth_fail, Port, OpenDir,
			    [{user, "one"}, {password, "WrongPassword"}]},
			   Node, Port),
    auth_request(Type, Host, Port, Node,"/open/", "one", "WrongPassword", 
		 [{statuscode, 401}]),

    receive_security_event({event, auth_fail, Port, OpenDir,
			    [{user, "one"}, {password, "WrongPassword"}]},
			   Node, Port),
        receive_security_event({event, user_block, Port, OpenDir,
			    [{user, "one"}]}, Node, Port),
    
    global:unregister_name(mod_security_test),   % No more events.

    auth_request(Type, Host, Port, Node,"/open/", "one", "WrongPassword", 
		 [{statuscode, 401}]),
    auth_request(Type, Host, Port, Node,"/open/", "one", "onePassword",
		 [{statuscode, 403}]),

    %% User "one" should be blocked now..
    case list_blocked_users(Node, Port) of
	[{"one",_, Port, OpenDir,_}] ->
	    ok;
	Blocked ->
	    exit({unexpected_blocked, Blocked})
    end,

    [{"one",_, Port, OpenDir,_}] = list_blocked_users(Node, Port, OpenDir),

    true = unblock_user(Node, "one", Port, OpenDir),
    %% User "one" should not be blocked any more.

    [] = list_blocked_users(Node, Port),

    auth_request(Type, Host, Port, Node,"/open/", "one", "onePassword", 
		 [{statuscode, 200}]),



    %% Test list_auth_users & auth_timeout
    ["one"] = list_auth_users(Node, Port),

    auth_request(Type, Host, Port, Node,"/open/", "two", "onePassword", 
		 [{statuscode, 401}]),
    ["one"] = list_auth_users(Node, Port),

    ["one"] = list_auth_users(Node, Port, OpenDir),

    auth_request(Type, Host, Port, Node,"/open/", "two", "twoPassword", 
		 [{statuscode, 401}]),

    ["one"] = list_auth_users(Node, Port),

    ["one"] = list_auth_users(Node, Port, OpenDir),

    %% Wait for successful auth to timeout.
    ct:sleep(?AUTH_TIMEOUT*1001),  

    [] = list_auth_users(Node, Port),


    [] = list_auth_users(Node, Port, OpenDir),

    %% "two" is blocked.

    true = unblock_user(Node, "two", Port, OpenDir),
    %% Test explicit blocking. Block user 'two'.

    [] = list_blocked_users(Node,Port,OpenDir),

    true = block_user(Node, "two", Port, OpenDir, 10),
    auth_request(Type, Host, Port, Node,"/open/", "two", "twoPassword", 
		 [{statuscode, 401}]).

%%-------------------------------------------------------------------------
auth(Type, Port, Host, Node) ->
 
    %% Authentication required!
    ok = httpd_test_lib:verify_request(Type,Host,Port,Node, 
				       "GET /open/ HTTP/1.0\r\n\r\n",
				       [{statuscode, 401},
					{version, "HTTP/1.0"},
					{header, "WWW-Authenticate"}]),
    ok = httpd_test_lib:verify_request(Type,Host,Port,Node, 
				       "GET /secret/ HTTP/1.0\r\n\r\n",
				       [{statuscode, 401},
					{version, "HTTP/1.0"}, 
					{header, "WWW-Authenticate"}]),
    ok = httpd_test_lib:verify_request(Type,Host,Port,Node, 
				       "GET /secret/top_secret/"
				       " HTTP/1.0\r\n\r\n",
				       [{statuscode, 401},
					{version, "HTTP/1.0"}, 
					{header, "WWW-Authenticate"}]),
 
    %% Authentication OK! ["one:OnePassword" user first in user list]
    auth_request(Type, Host, Port, Node, "/open/dummy.html", "one", 
		 "onePassword", [{statuscode, 200}]),
    %% Authentication OK and a directory listing is supplied!
    %% ["Aladdin:open sesame" user second in user list]
    auth_request(Type, Host, Port, Node, "/open/","Aladdin", 
		 "AladdinPassword", [{statuscode, 200}]),
    
    %% User correct but wrong password! ["one:one" user first in user list]
    auth_request(Type, Host, Port, Node, "/open/", "one", "one", 
		 [{statuscode, 401},{header, "WWW-Authenticate"}]),
    %% Make sure Authenticate header is received even the second time
    %% we try a incorrect password! Otherwise a browser client will hang!
    auth_request(Type, Host, Port, Node, "/open/", "one", "one", 
		 [{statuscode, 401},{header, "WWW-Authenticate"}]),

    %% Neither user or password correct! ["dummy:dummy"]
    auth_request(Type, Host, Port, Node, "/open/", "dummy", "dummy", 
		 [{statuscode, 401}]),
    
    %% Authentication OK! ["two:TwoPassword" user in first group]
    auth_request(Type, Host, Port, Node, "/secret/dummy.html", "two", 
		 "twoPassword", [{statuscode, 200}]),
    %% Authentication OK and a directory listing is supplied!
    %% ["three:ThreePassword" user in second group]
    auth_request(Type, Host, Port, Node,"/secret/", "three", 
		 "threePassword", [{statuscode, 200}]),
 
    %% User correct but wrong password! ["two:two" user in first group]
    auth_request(Type, Host, Port, Node, "/secret/", "two", "two", 
		 [{statuscode, 401}]),
    %% Neither user or password correct! ["dummy:dummy"]
    auth_request(Type, Host, Port, Node,"/secret/", "dummy", "dummy", 
		 [{statuscode, 401}]),

    %% Nested secret/top_secret OK! ["Aladdin:open sesame"]
    auth_request(Type, Host, Port, Node, "/secret/top_secret/", "Aladdin", 
		 "AladdinPassword", [{statuscode, 200}]),
    %% Authentication still required!
    ok =  httpd_test_lib:verify_request(Type, Host, Port, Node, "GET /open/ "
					"HTTP/1.0\r\n\r\n",
					[{statuscode, 401},
					 {version, "HTTP/1.0"}, 
					 {header, "WWW-Authenticate"}]),
    ok = httpd_test_lib:verify_request(Type, Host, Port, Node, "GET /secret/ "
				       "HTTP/1.0\r\n\r\n",
				       [{statuscode, 401},
					{version, "HTTP/1.0"}, 
					{header, "WWW-Authenticate"}]),
    ok = httpd_test_lib:verify_request(Type, Host, Port, Node, 
				       "GET /secret/top_secret/ "
				       "HTTP/1.0\r\n\r\n",
				       [{statuscode, 401},
					{version, "HTTP/1.0"}, 
					{header, "WWW-Authenticate"}]).


%%-------------------------------------------------------------------------
%% What to test here:
%%
%% /open                      - plain,  require user one Aladdin
%% /secret                    - plain,  require group group1 group2
%% /secret/top_secret         - plain,  require group group3
%% /dets_open                 - dets,   require user one Aladdin
%% /dets_secret               - dets,   require group group1 group2
%% /dets_secret/top_secret    - dets,   require group group3
%% /mnesia_open/              - mnesia, require user one Aladdin
%% /mnesia_secret/            - mnesia, require group group1 group2
%% /mnesia_secret/top_secret/ - mnesia, require group group3
auth_api(ServerRoot, AuthStoreType, Type, Port, Host, Node) ->
    ok = httpd_test_lib:verify_request(Type, Host, Port, Node, 
				       "GET / HTTP/1.0\r\n\r\n",
				       [{statuscode, 200},
				        {version, "HTTP/1.0"}]),
    auth_request(Type, Host, Port, Node, "/", "one", "WrongPassword", 
		 [{statuscode, 200}]),

    %% Make sure Authenticate header is received even the second time
    %% we try a incorrect password! Otherwise a browser client will hang!
    auth_request(Type, Host, Port, Node,"/" ++ AuthStoreType ++ "open/",
		 "dummy", "WrongPassword", [{statuscode, 401},
					    {header, "WWW-Authenticate"}]),
    auth_request(Type, Host, Port, Node,"/" ++ AuthStoreType ++ "open/",
		 "dummy", "WrongPassword", [{statuscode, 401},	
					    {header, "WWW-Authenticate"}]),
    
    %% Change the password to DummyPassword then try to add a user 
    %% Get an error and set it to NoPassword
    ok = update_password(Node, ServerRoot, Host, Port, AuthStoreType ++ 
			 "open", "NoPassword", "DummyPassword"),
    {error,bad_password} = 
	add_user(Node, ServerRoot, Port, AuthStoreType ++ "open", "one", 
		 "onePassword", []),
    ok = update_password(Node, ServerRoot, Host, Port, AuthStoreType ++"open",
			 "DummyPassword", "NoPassword"),
  
    %% Test /*open, require user one Aladdin
    remove_users(Node, ServerRoot, Host, Port, AuthStoreType ++ "open"),

    auth_request(Type, Host, Port, Node,"/" ++ AuthStoreType ++ "open/",
		 "one", "onePassword", [{statuscode, 401}]),
    
    auth_request(Type, Host, Port, Node,"/" ++ AuthStoreType ++ "open/",
		 "two", "twoPassword", [{statuscode, 401}]),
 
    auth_request(Type, Host, Port, Node,"/" ++ AuthStoreType ++ "open/", 
		 "Aladdin", "onePassword", [{statuscode, 401}]),

    add_user(Node, ServerRoot, Port, AuthStoreType ++ "open", "one", 
	     "onePassword", []),
    add_user(Node, ServerRoot, Port, AuthStoreType ++ "open", "two", 
	     "twoPassword", []),
    add_user(Node, ServerRoot, Port, AuthStoreType ++ "open", "Aladdin", 
	     "AladdinPassword", []),
   
    {ok, [_|_]} = list_users(Node, ServerRoot, Host, Port, 
			  AuthStoreType++"open"),
    auth_request(Type, Host, Port, Node, "/" ++ AuthStoreType ++ "open/",
		 "one", "WrongPassword", [{statuscode, 401}]),
    auth_request(Type, Host, Port, Node, "/" ++ AuthStoreType ++ "open/", 
		 "one", "onePassword", [{statuscode, 200}]),
    auth_request(Type, Host, Port, Node,"/" ++ AuthStoreType ++ "open/", 
		 "two", "twoPassword", [{statuscode, 401}]),
    auth_request(Type, Host, Port, Node, "/" ++ AuthStoreType ++ "open/", 
		 "Aladdin", "WrongPassword", [{statuscode, 401}]),
    auth_request(Type, Host, Port, Node,"/" ++ AuthStoreType ++ "open/", 
		 "Aladdin", "AladdinPassword", [{statuscode, 200}]),
   
    remove_users(Node, ServerRoot, Host, Port, AuthStoreType++"open"),
    {ok, []} = list_users(Node, ServerRoot, Host, Port, 
			  AuthStoreType++"open"),

    %% Phase 2
    remove_users(Node, ServerRoot, Host, Port, AuthStoreType++"secret"),
    {ok, []} = list_users(Node, ServerRoot, Host, Port, AuthStoreType ++
			  "secret"),
    auth_request(Type, Host, Port, Node,"/" ++ AuthStoreType ++ "secret/",
		 "one", "onePassword", [{statuscode, 401}]),
    auth_request(Type, Host, Port, Node,"/" ++ AuthStoreType ++ "secret/", 
		 "two", "twoPassword", [{statuscode, 401}]),
    auth_request(Type, Host, Port,  Node, "/" ++ AuthStoreType ++ "secret/", 
		 "three", "threePassword", [{statuscode, 401}]),
    add_user(Node, ServerRoot, Port, AuthStoreType ++ "secret", "one",
	     "onePassword", 
	     []),
    add_user(Node, ServerRoot, Port, AuthStoreType ++ "secret", 
	     "two", "twoPassword", []),
    add_user(Node, ServerRoot, Port, AuthStoreType++"secret", "Aladdin", 
	     "AladdinPassword",[]),
    add_group_member(Node, ServerRoot, Port, AuthStoreType ++ "secret", 
		     "one", "group1"),
    add_group_member(Node, ServerRoot, Port, AuthStoreType ++ "secret", 
		     "two", "group1"),
    add_group_member(Node, ServerRoot, Port, AuthStoreType ++ 
		     "secret", "Aladdin", "group2"),
    auth_request(Type, Host, Port, Node,"/" ++ AuthStoreType ++ "secret/",
		 "one", "onePassword", [{statuscode, 200}]),
    auth_request(Type, Host, Port, Node,"/" ++ AuthStoreType ++ "secret/", 
		 "two", "twoPassword", [{statuscode, 200}]),
    auth_request(Type, Host, Port, Node,"/" ++ AuthStoreType ++ "secret/",
		 "Aladdin", "AladdinPassword", [{statuscode, 200}]),
    auth_request(Type, Host, Port, Node,"/" ++ AuthStoreType ++ "secret/",
		 "three", "threePassword", [{statuscode, 401}]),
    remove_users(Node, ServerRoot, Host, Port, AuthStoreType ++ "secret"),
    {ok, []} = list_users(Node, ServerRoot, Host, Port, 
			  AuthStoreType ++ "secret"),
    remove_groups(Node, ServerRoot, Host, Port, AuthStoreType ++ "secret"),
    Directory = filename:join([ServerRoot, "htdocs", AuthStoreType ++ 
			       "secret"]),
    {ok, []} = list_groups(Node, ServerRoot, Host, Port, Directory),

    %% Phase 3
    remove_users(Node, ServerRoot, Host, Port, AuthStoreType ++ 
		 "secret/top_secret"),
    remove_groups(Node, ServerRoot, Host, Port, AuthStoreType ++ 
		  "secret/top_secret"),
    auth_request(Type, Host, Port, Node,"/" ++ AuthStoreType ++ 
		 "secret/top_secret/",
		 "three", "threePassword", [{statuscode, 401}]),
    auth_request(Type, Host, Port, Node,"/" ++ AuthStoreType ++ 
		 "secret/top_secret/", "two", "twoPassword", 
		 [{statuscode, 401}]),
    add_user(Node, ServerRoot, Port, AuthStoreType ++ 
	     "secret/top_secret","three",
	     "threePassword",[]),
    add_user(Node, ServerRoot, Port, AuthStoreType ++ "secret/top_secret",
	     "two","twoPassword", []),
    add_group_member(Node, ServerRoot, Port, AuthStoreType ++ 
		     "secret/top_secret",
		     "three", "group3"),
    auth_request(Type, Host, Port, Node,"/" ++ AuthStoreType ++ 
		 "secret/top_secret/", "three", "threePassword", 
		 [{statuscode, 200}]),
    auth_request(Type, Host, Port, Node,"/" ++ AuthStoreType ++ 
		 "secret/top_secret/", "two", "twoPassword", 
		 [{statuscode, 401}]),
    add_group_member(Node, ServerRoot, Port, AuthStoreType ++ 
		     "secret/top_secret",
		     "two", "group3"),
    auth_request(Type,Host,Port,Node,"/" ++ AuthStoreType ++ 
		 "secret/top_secret/",
		 "two", "twoPassword", [{statuscode, 200}]),
    remove_users(Node, ServerRoot, Host, Port, AuthStoreType ++ 
		 "secret/top_secret"),
    {ok, []} = list_users(Node, ServerRoot, Host, Port, 
			  AuthStoreType ++ "secret/top_secret"),
    remove_groups(Node, ServerRoot, Host, Port, AuthStoreType ++ 
		  "secret/top_secret"),
    Directory2 = filename:join([ServerRoot, "htdocs", 
				AuthStoreType ++ "secret/top_secret"]),
    {ok, []} = list_groups(Node, ServerRoot, Host, Port, Directory2),
    auth_request(Type, Host, Port, Node, "/" ++ AuthStoreType ++ 
		 "secret/top_secret/", "two", "twoPassword", 
		 [{statuscode, 401}]),
    auth_request(Type, Host, Port, Node, "/" ++ AuthStoreType ++ 
		 "secret/top_secret/","three", "threePassword",
		 [{statuscode, 401}]).

%%--------------------------------------------------------------------------
auth_mnesia_api(_Type, Port, _Host, _Node) ->
    %% Create three groups:
    %% group1 : one Aladdin
    %% group2 : two
    %% group3 : three
    mod_auth_mnesia:store_user("one", "onePassword", Port, 
			       "/mnesia_open", ""),
    mod_auth_mnesia:store_user("Aladdin", "AladdinPassword", Port, 
			       "/mnesia_open", ""),
    mod_auth_mnesia:store_user("two", "twoPassword", Port, 
			       "/mnesia_open", ""),
    mod_auth_mnesia:store_user("three", "threePassword", Port, 
				     "/mnesia_open", ""),
    Users = mod_auth_mnesia:list_users(Port, "/mnesia_open"),

    ok = check_lists_members(Users,["Aladdin","one","two","three"]),
    
    true = mod_auth_mnesia:store_group_member("group1", "one", Port,
					      "/mnesia_open", ""),
    true = mod_auth_mnesia:store_group_member("group1","Aladdin", Port,
					      "/mnesia_open", ""),
    true = mod_auth_mnesia:store_group_member("group2","two", Port,
					      "/mnesia_open", ""),
    true = mod_auth_mnesia:store_group_member("group3","three", Port, 
					      "/mnesia_open", ""),
    %% Check that all three created groups exist.
    Groups = mod_auth_mnesia:list_groups(Port, "/mnesia_open"),
    ok = check_lists_members(Groups, ["group1","group2","group3"]),

    %% Check that the members of all groups are correct.
    Group1 = mod_auth_mnesia:list_group_members("group1", Port, 
						"/mnesia_open"),
    ok = check_lists_members(Group1,["one","Aladdin"]),
    {ok,["two"]}  = mod_auth_mnesia:list_group_members("group2", Port, 
						"/mnesia_open"),
   
    {ok,["three"]} = mod_auth_mnesia:list_group_members("group3", Port, 
						       "/mnesia_open"),
    
    %% Delete user 'one' from group one and check that he was removed
    %% correctly.
    true = mod_auth_mnesia:remove_group_member("group1", "one", Port, 
					       "/mnesia_open", ""),
    {ok,["Aladdin"]}  = mod_auth_mnesia:list_group_members("group1", Port, 
							   "/mnesia_open"),
    
    %% Remove group1 and check that the group was removed correctly.
    true = mod_auth_mnesia:remove_group("group1", Port, "/mnesia_open", ""),
    Groups_1 = mod_auth_mnesia:list_groups(Port, "/mnesia_open"),
    ok = check_lists_members(Groups_1,["group2","group3"]),
    
    %% Check that the other users still exist in their groups.
    Users_1 = mod_auth_mnesia:list_users(Port, "/mnesia_open"),
    ok = check_lists_members(Users_1,["Aladdin","one","two","three"]),
    {ok,["two"]} = mod_auth_mnesia:list_group_members("group2", Port, 
						  "/mnesia_open"),
    {ok,["three"]} = mod_auth_mnesia:list_group_members("group3", Port,
						  "/mnesia_open"),
    
    %% Remove the remaining groups/users and check that all
    %% users/groups are removed.
    true = mod_auth_mnesia:remove_group("group2", Port, "/mnesia_open", ""),
    true = mod_auth_mnesia:remove_group("group3", Port, "/mnesia_open", ""),
    {ok, []} = mod_auth_mnesia:list_groups(Port, "/mnesia_open"),
    true = mod_auth_mnesia:remove_user("one", Port, "/mnesia_open", ""),
    true = mod_auth_mnesia:remove_user("Aladdin", Port, "/mnesia_open", ""),
    true = mod_auth_mnesia:remove_user("two", Port, "/mnesia_open", ""),
    true = mod_auth_mnesia:remove_user("three", Port, "/mnesia_open", ""),
    {ok, []} = mod_auth_mnesia:list_users(Port, "/mnesia_open"),
    ok.
%%--------------------------------------------------------------------------
htaccess(Type, Port, Host, Node) ->
    %% Control that authentication required!
    %% Control that the pages that shall be 
    %% authenticated really need authenticatin
    ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
				       "GET /ht/open/ HTTP/1.0\r\n\r\n",
				       [{statuscode, 401},
					{version, "HTTP/1.0"}, 
					{header, "WWW-Authenticate"}]),
    ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
				       "GET /ht/secret/ HTTP/1.0\r\n\r\n",
				       [{statuscode, 401},
					{version, "HTTP/1.0"}, 
					{header, "WWW-Authenticate"}]),
    ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
				       "GET /ht/secret/top_secret/ "
				       "HTTP/1.0\r\n\r\n",
				       [{statuscode, 401},
					{version, "HTTP/1.0"}, 
					{header, "WWW-Authenticate"}]),

    %% Make sure Authenticate header is received even the second time
    %% we try a incorrect password! Otherwise a browser client will hang!
    auth_request(Type, Host, Port, Node,"/ht/open/",
		 "dummy", "WrongPassword", [{statuscode, 401},
					    {header, "WWW-Authenticate"}]),
    auth_request(Type, Host, Port, Node,"/ht/open/",
		 "dummy", "WrongPassword", [{statuscode, 401},		
					    {header, "WWW-Authenticate"}]),
    
    %% Control that not just the first user in the list is valid
    %% Control the first user
    %% Authennticating ["one:OnePassword" user first in user list]
    auth_request(Type, Host, Port, Node, "/ht/open/dummy.html", "one", 
		 "OnePassword", [{statuscode, 200}]),
    
    %% Control the second user
    %% Authentication OK and a directory listing is supplied! 
    %% ["Aladdin:open sesame" user second in user list]
    auth_request(Type, Host, Port, Node, "/ht/open/","Aladdin", 
		 "AladdinPassword", [{statuscode, 200}]),
    
    %% Control that bad passwords and userids get a good denial
    %% User correct but wrong password! ["one:one" user first in user list]
    auth_request(Type, Host, Port, Node, "/ht/open/", "one", "one", 
		 [{statuscode, 401}]),
    %% Neither user or password correct! ["dummy:dummy"]
    auth_request(Type, Host, Port, Node, "/ht/open/", "dummy", "dummy", 
		 [{statuscode, 401}]),
    
    %% Control that authentication still works, even if its a member in a group
    %% Authentication OK! ["two:TwoPassword" user in first group]
    auth_request(Type, Host, Port, Node, "/ht/secret/dummy.html", "two", 
		 "TwoPassword", [{statuscode, 200}]),
    
    %% Authentication OK and a directory listing is supplied! 
    %% ["three:ThreePassword" user in second group]
    auth_request(Type, Host, Port, Node,"/ht/secret/", "three",
		 "ThreePassword", [{statuscode, 200}]),
    
    %% Deny users with bad passwords even if the user is a group member
    %% User correct but wrong password! ["two:two" user in first group]
    auth_request(Type, Host, Port, Node, "/ht/secret/", "two", "two", 
		 [{statuscode, 401}]),
    %% Neither user or password correct! ["dummy:dummy"]
    auth_request(Type, Host, Port, Node,"/ht/secret/", "dummy", "dummy", 
		 [{statuscode, 401}]),

    %% control that we deny the users that are in subnet above the allowed
    auth_request(Type, Host, Port, Node,"/ht/blocknet/dummy.html", "four",
		 "FourPassword", [{statuscode, 403}]),
    %% Control that we only applies the rules to the right methods
    ok = httpd_test_lib:verify_request(Type, Host, Port, Node, 
				       "HEAD /ht/blocknet/dummy.html"
				       " HTTP/1.0\r\n\r\n",
				       [{statuscode, 200},
					{version, "HTTP/1.0"}]),
    
    %% Control that the rerquire directive can be overrideen
    auth_request(Type, Host, Port, Node, 
		 "/ht/secret/top_secret/", "Aladdin", "AladdinPassword", 
		 [{statuscode, 401}]),
    
    %% Authentication still required!
    ok = httpd_test_lib:verify_request(Type, Host, Port, Node, "GET /ht/open/ "
				       "HTTP/1.0\r\n\r\n",
				       [{statuscode, 401},
					{version, "HTTP/1.0"}, 
					{header, "WWW-Authenticate"}]),
    ok = httpd_test_lib:verify_request(Type, Host, Port, Node, 
				       "GET /ht/secret/ HTTP/1.0\r\n\r\n",
				       [{statuscode, 401},
					{version, "HTTP/1.0"},    
					{header, "WWW-Authenticate"}]),
    ok = httpd_test_lib:verify_request(Type, Host, Port, Node, 
				       "GET /ht/secret/top_secret/ "
				       "HTTP/1.0\r\n\r\n",
				       [{statuscode, 401},
					{version, "HTTP/1.0"}, 
					{header, "WWW-Authenticate"}]).
%%--------------------------------------------------------------------
cgi(Type, Port, Host, Node) ->
    {Script, Script2, Script3} =
	case os:type() of
	    {win32, _} ->
		{"printenv.bat", "printenv.sh", "cgi_echo.exe"};
	    _ ->
		{"printenv.sh", "printenv.bat", "cgi_echo"}
	end,

    %% The length (> 100) is intentional
    ok = httpd_test_lib:
	verify_request(Type, Host, Port, Node, 
		       "POST /cgi-bin/" ++ Script3 ++
		       " HTTP/1.0\r\n"
		       "Content-Length:100 \r\n\r\n "
		       "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
		       "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
		       "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
		       "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
		       "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
		       "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
		       "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
		       "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
		       "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
		       "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
		       "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
		       "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
		       "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
		       "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
		       "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
		       "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
		       "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
		       " \r\n\r\n",
		       [{statuscode, 200},
			{version, "HTTP/1.0"},
			{header, "content-type", "text/plain"}]),
    
    ok = httpd_test_lib:verify_request(Type, Host, Port, Node, 
				       "GET /cgi-bin/"++ Script ++
				       " HTTP/1.0\r\n\r\n", 
				       [{statuscode, 200},
				       {version, "HTTP/1.0"}]),

    ok = httpd_test_lib:verify_request(Type, Host, Port, Node, 
				       "GET /cgi-bin/not_there "
				       "HTTP/1.0\r\n\r\n", 
				       [{statuscode, 404},{statuscode, 500},
				       {version, "HTTP/1.0"}]),

    ok = httpd_test_lib:verify_request(Type, Host, Port, Node, 
				       "GET /cgi-bin/"++ Script ++
				       "?Nisse:kkk?sss/lll HTTP/1.0\r\n\r\n", 
				       [{statuscode, 200}, 
					{version, "HTTP/1.0"}]),

    ok = httpd_test_lib:verify_request(Type, Host, Port, Node, 
				       "POST /cgi-bin/"++ Script ++
				       " HTTP/1.0\r\n\r\n", 
				       [{statuscode, 200},
					{version, "HTTP/1.0"}]),

    ok = httpd_test_lib:verify_request(Type, Host, Port, Node, 
				       "GET /htbin/"++ Script ++
				       " HTTP/1.0\r\n\r\n",
				       [{statuscode, 200},
				       {version, "HTTP/1.0"}]),
    ok = httpd_test_lib:verify_request(Type, Host, Port, Node, 
				       "GET /htbin/not_there "
				       "HTTP/1.0\r\n\r\n", 
				       [{statuscode, 404},{statuscode, 500},
				        {version, "HTTP/1.0"}]),

    ok = httpd_test_lib:verify_request(Type, Host, Port, Node, 
				       "GET /htbin/"++ Script ++
				       "?Nisse:kkk?sss/lll HTTP/1.0\r\n\r\n", 
				       [{statuscode, 200},
					{version, "HTTP/1.0"}]),
    ok = httpd_test_lib:verify_request(Type, Host, Port, Node, 
				       "POST /htbin/"++ Script ++
				       " HTTP/1.0\r\n\r\n",
				       [{statuscode, 200},
				       {version, "HTTP/1.0"}]),
    ok = httpd_test_lib:verify_request(Type, Host, Port, Node, 
				       "POST /htbin/"++ Script ++
				       " HTTP/1.0\r\n\r\n",
				       [{statuscode, 200},
				       {version, "HTTP/1.0"}]),
    
    %% Execute an existing, but bad CGI script..
    ok = httpd_test_lib:verify_request(Type, Host, Port, Node, 
				       "POST /htbin/"++ Script2 ++
				       " HTTP/1.0\r\n\r\n",
				       [{statuscode, 404},
					{version, "HTTP/1.0"}]),
    
    ok = httpd_test_lib:verify_request(Type, Host, Port, Node, 
				       "POST /cgi-bin/"++ Script2 ++
				       " HTTP/1.0\r\n\r\n",
				       [{statuscode, 404},
				       {version, "HTTP/1.0"}]),

    %% Check "ScriptNoCache" directive (default: false)
    ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
				       "GET /cgi-bin/" ++ Script ++
				       " HTTP/1.0\r\n\r\n",
				       [{statuscode, 200},
					{no_header, "cache-control"},
					{version, "HTTP/1.0"}]).

%%--------------------------------------------------------------------
esi(Type, Port, Host, Node) ->
    %% Check "ErlScriptAlias" and "EvalScriptAlias" directives
     ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
 				       "GET /eval?httpd_example:print(\"Hi!\")"
 				       " HTTP/1.0\r\n\r\n", 
 				       [{statuscode, 200},
 				       {version, "HTTP/1.0"}]),
     ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
 				       "GET /eval?not_allowed:print(\"Hi!\")"
 				       " HTTP/1.0\r\n\r\n",
 				       [{statuscode, 403},
 				       {version, "HTTP/1.0"}]),
     ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
 				       "GET /eval?httpd_example:undef(\"Hi!\")"
 				       " HTTP/1.0\r\n\r\n",
 				       [{statuscode, 500},
 					{version, "HTTP/1.0"}]),
     ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
 				       "GET /cgi-bin/erl/httpd_example "
  				       "HTTP/1.0\r\n\r\n",
  				       [{statuscode, 400},
 					{version, "HTTP/1.0"}]),
     ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
 				       "GET /cgi-bin/erl/httpd_example:get "
 				       "HTTP/1.0\r\n\r\n", 
 				       [{statuscode, 200},
 				       {version, "HTTP/1.0"}]),
     ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
 				       "GET /cgi-bin/erl/httpd_example:"
 				       "get?input=4711"
 				       " HTTP/1.0\r\n\r\n", 
 				       [{statuscode, 200},
 				       {version, "HTTP/1.0"}]),
    ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
				       "GET /cgi-bin/erl/httpd_example:"
				       "post HTTP/1.0\r\n\r\n",
				       [{statuscode, 200},
					{version, "HTTP/1.0"}]),
    ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
				       "GET /cgi-bin/erl/not_allowed:post "
				       "HTTP/1.0\r\n\r\n",  
				       [{statuscode, 403},
				       {version, "HTTP/1.0"}]),
    ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
				       "GET /cgi-bin/erl/httpd_example:undef "
				       "HTTP/1.0\r\n\r\n",
				       [{statuscode, 404},
				       {version, "HTTP/1.0"}]),
    ok =  httpd_test_lib:verify_request(Type, Host, Port, Node,
					"GET /cgi-bin/erl/httpd_example/yahoo"
					" HTTP/1.0\r\n\r\n",
					[{statuscode, 302},
					{version, "HTTP/1.0"}]),
    %% Check "ErlScriptNoCache" directive (default: false)
    ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
				       "GET /cgi-bin/erl/httpd_example:get"
				       " HTTP/1.0\r\n\r\n",
				       [{statuscode, 200},
					{no_header, "cache-control"},
					{version, "HTTP/1.0"}]),
    ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
				       "GET /cgi-bin/erl/httpd_example:new_status_and_location"
				       " HTTP/1.1\r\n\r\n",
				       [{statuscode, 201},
                                        {header, "Location"},
					{version, "HTTP/1.1"}]),
    ok.

%%--------------------------------------------------------------------
get(Type, Port, Host, Node) ->
    ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
				       "GET /index.html HTTP/1.0\r\n\r\n",
				       [{statuscode, 200},
					{header, "Content-Type", "text/html"},
					{header, "Date"},
					{header, "Server"},
					{version, "HTTP/1.0"}]),
    ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
				       "GET /fsize.shtml HTTP/1.1\r\nHost:" 
				       ++ Host ++ "\r\n\r\n", 
				       [{statuscode, 200},
					{header, "Content-Type", "text/html"},
					{header, "Date"},
					{header, "Server"}]),
    ok =  httpd_test_lib:verify_request(Type, Host, Port, Node,
					"GET /fsize.shtml HTTP/1.0\r\n\r\n", 
					[{statuscode, 200},
					 {header, "Content-Type"},
					 {header, "Server"},
					 {header, "Date"},
					{version, "HTTP/1.0"}]),
    ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
				       "GET /secret/dummy.html "
				       "HTTP/1.0\r\n\r\n", 
				       [{statuscode, 401},
					{header, "WWW-Authenticate"},
					{version, "HTTP/1.0"}]),
    ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
				       "GET /index.html HTTP/1.0\r\n\r\n", 
				       [{statuscode, 200},
					{header, "Server"},
					{header, "Date"},
					{header, "Content-Type", 
					 "text/html"},
				       {version, "HTTP/1.0"}]),
    ok.

%%--------------------------------------------------------------------
head(Type, Port, Host, Node) ->
    ok = httpd_test_lib:verify_request(Type, Host, Port, Node, 
				       "HEAD /index.html HTTP/1.0\r\n\r\n",
				       [{statuscode, 200},
					{version, "HTTP/1.0"}]),
    ok.
%%--------------------------------------------------------------------
all(Type, Port, Host, Node) ->
    actions(Type, Port, Host, Node),
    alias(Type, Port, Host, Node),
    auth(Type, Port, Host, Node),
    cgi(Type, Port, Host, Node),
    esi(Type, Port, Host, Node),
    get(Type, Port, Host, Node),
    head(Type, Port, Host, Node),
    ok.

%%--------------------------------------------------------------------
%% Internal functions
%%--------------------------------------------------------------------
auth_request(Type, Host, Port, Node, URI, User, Passwd, Expect) ->
    Req = ["GET ", URI, " HTTP/1.0\r\n", 
	   "Authorization: Basic ", 
	   base64:encode_to_string(User++":"++Passwd),
	   "\r\n\r\n"],
    ok = httpd_test_lib:verify_request(Type, Host, Port, Node, 
				       lists:flatten(Req), 
				       [{version, "HTTP/1.0"} | Expect]).

remove_users(Node, ServerRoot, Host, Port, Dir) ->
    %% List users, delete them, and make sure they are gone.
    case list_users(Node, ServerRoot, Host, Port, Dir) of
	{ok, Users} ->
	    lists:foreach(fun(User) -> 
				  delete_user(Node, ServerRoot, Host, 
					      Port, Dir, User)
			  end,
			  Users),
		  {ok, []} = list_users(Node, ServerRoot, Host, Port, Dir);
	_ ->
	    ok
    end.

add_user(Node, Root, Port, Dir, User, Password, UserData) ->
    Addr = undefined, 
    Directory = filename:join([Root, "htdocs", Dir]),
    rpc:call(Node, mod_auth, add_user, 
	     [User, Password, UserData, Addr, Port, Directory]).

delete_user(Node, Root, _Host, Port, Dir, User) ->
    Addr = undefined, 
    Directory = filename:join([Root, "htdocs", Dir]),
    rpc:call(Node, mod_auth, delete_user, [User, Addr, Port, Directory]).

list_users(Node, Root, _Host, Port, Dir) ->
    Addr = undefined, 
    Directory = filename:join([Root, "htdocs", Dir]),
    rpc:call(Node, mod_auth, list_users, [Addr, Port, Directory]).


receive_security_event(Event, Node, Port) ->
    receive 
	Event ->
	    ok;
	{'EXIT', _, _} ->
	    receive_security_event(Event, Node, Port)
    after 5000 ->
	    %% Flush the message queue, to see if we got something...
	    Msgs = inets_test_lib:flush(),
	    ct:fail({expected_event_not_received, Msgs})
				     
    end.

%% receive_security_event(Event, Node, Port) ->
%%     io:format(user, "~w:receive_security_event -> entry with"
%% 	      "~n   Event: ~p"
%% 	      "~n   Node:  ~p"
%% 	      "~n   Port:  ~p"
%% 	      "~n", [?MODULE, Event, Node, Port]),
%%     receive 
%% 	Event ->
%% 	    ok;
%% 	{'EXIT', _, _} ->
%% 	    receive_security_event(Event, Node, Port);	
%% 	Other ->
%% 	    ct:fail({unexpected_event, 
%% 			      {expected, Event}, {received, Other}})
%%     after 5000 ->
%% 	    ct:fail(no_event_recived)
				     
%%     end.

list_blocked_users(Node,Port) ->
    Addr = undefined, % Assumed to be on the same host
    rpc:call(Node, mod_security, list_blocked_users, [Addr,Port]).

list_blocked_users(Node,Port,Dir) ->
    Addr = undefined, % Assumed to be on the same host
    rpc:call(Node, mod_security, list_blocked_users, [Addr,Port,Dir]).

block_user(Node,User,Port,Dir,Sec) ->
    Addr = undefined, % Assumed to be on the same host
    rpc:call(Node, mod_security, block_user, [User, Addr, Port, Dir, Sec]).

unblock_user(Node,User,Port,Dir) ->
    Addr = undefined, % Assumed to be on the same host
    rpc:call(Node, mod_security, unblock_user, [User, Addr, Port, Dir]).

list_auth_users(Node,Port) ->
    Addr = undefined, % Assumed to be on the same host
    rpc:call(Node, mod_security, list_auth_users, [Addr,Port]).

list_auth_users(Node,Port,Dir) ->
    Addr = undefined, % Assumed to be on the same host
    rpc:call(Node, mod_security, list_auth_users, [Addr,Port,Dir]).

update_password(Node, ServerRoot, _Address, Port, Dir, Old, New)->
    Directory = filename:join([ServerRoot, "htdocs", Dir]),
    rpc:call(Node, mod_auth, update_password, 
	     [undefined, Port, Directory, Old, New, New]).

remove_groups(Node, ServerRoot, Host, Port, Dir) ->
    Directory = filename:join([ServerRoot, "htdocs", Dir]),
    {ok, Groups} = list_groups(Node, ServerRoot, Host, Port, Directory),
    lists:foreach(fun(Group) ->
				delete_group(Node, Group, Port, Directory)
			end,
			Groups),
    {ok, []} = list_groups(Node, ServerRoot, Host, Port, Directory),
    ok.

delete_group(Node, Group, Port, Dir) ->
    Addr = undefined, 
    rpc:call(Node, mod_auth, delete_group, [Group, Addr, Port, Dir]).

list_groups(Node, _, _, Port, Dir) ->
    Addr = undefined, 
    rpc:call(Node, mod_auth, list_groups, [Addr, Port, Dir]).

add_group_member(Node, ServerRoot, Port, Dir, User, Group) ->
    Addr = undefined, 
    rpc:call(Node, mod_auth, add_group_member, [Group, User, Addr, Port, 
						filename:join(
						  [ServerRoot,
						   "htdocs",Dir])]).
event(What, Port, Dir, Data) ->
    Msg = {event, What, Port, Dir, Data},
    case global:whereis_name(mod_security_test) of
	undefined ->
	    ok;
	_Pid ->
	    global:send(mod_security_test, Msg)
    end.

ssl_password_cb() ->
    "dummy-ssl-password".

check_lists_members({ok,L},L) -> 
    ok;
check_lists_members({ok,L1},L2) ->
    check_lists_members1(lists:sort(L1),lists:sort(L2));
check_lists_members(Error,_L) ->
    Error.

check_lists_members1(L,L) ->
    ok;
check_lists_members1(L1,L2) ->
    {error,{lists_not_equal,L1,L2}}.



