diff options
Diffstat (limited to 'lib/inets/test/httpd_SUITE.erl')
-rw-r--r-- | lib/inets/test/httpd_SUITE.erl | 226 |
1 files changed, 206 insertions, 20 deletions
diff --git a/lib/inets/test/httpd_SUITE.erl b/lib/inets/test/httpd_SUITE.erl index 4be20d3a69..342004f19b 100644 --- a/lib/inets/test/httpd_SUITE.erl +++ b/lib/inets/test/httpd_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2014. All Rights Reserved. +%% Copyright Ericsson AB 2013-2015. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in @@ -39,6 +39,7 @@ -define(FAIL_EXPIRE_TIME,1). %% Seconds before successful auths timeout. -define(AUTH_TIMEOUT,5). +-define(URL_START, "http://"). %%-------------------------------------------------------------------- %% Common Test interface functions ----------------------------------- @@ -63,7 +64,9 @@ all() -> {group, http_htaccess}, {group, https_htaccess}, {group, http_security}, - {group, https_security} + {group, https_security}, + {group, http_reload}, + {group, https_reload} ]. groups() -> @@ -84,7 +87,18 @@ groups() -> {https_htaccess, [], [{group, htaccess}]}, {http_security, [], [{group, security}]}, {https_security, [], [{group, security}]}, + {http_reload, [], [{group, reload}]}, + {https_reload, [], [{group, reload}]}, {limit, [], [max_clients_1_1, max_clients_1_0, max_clients_0_9]}, + {reload, [], [non_disturbing_reconfiger_dies, + disturbing_reconfiger_dies, + non_disturbing_1_1, + non_disturbing_1_0, + non_disturbing_0_9, + disturbing_1_1, + disturbing_1_0, + disturbing_0_9 + ]}, {basic_auth, [], [basic_auth_1_1, basic_auth_1_0, basic_auth_0_9]}, {auth_api, [], [auth_api_1_1, auth_api_1_0, auth_api_0_9 ]}, @@ -118,6 +132,7 @@ http_get() -> bad_hex, missing_CR, max_header, + max_content_length, ipv6 ]. @@ -134,8 +149,24 @@ init_per_suite(Config) -> inets_test_lib:del_dirs(ServerRoot), DocRoot = filename:join(ServerRoot, "htdocs"), setup_server_dirs(ServerRoot, DocRoot, DataDir), + {ok, Hostname0} = inet:gethostname(), + Inet = + case (catch ct:get_config(ipv6_hosts)) of + undefined -> + inet; + Hosts when is_list(Hosts) -> + case lists:member(list_to_atom(Hostname0), Hosts) of + true -> + inet6; + false -> + inet + end; + _ -> + inet + end, [{server_root, ServerRoot}, {doc_root, DocRoot}, + {ipfamily, Inet}, {node, node()}, {host, inets_test_lib:hostname()}, {address, getaddr()} | Config]. @@ -150,7 +181,8 @@ init_per_group(Group, Config0) when Group == https_basic; Group == https_auth_api; Group == https_auth_api_dets; Group == https_auth_api_mnesia; - Group == https_security + Group == https_security; + Group == https_reload -> init_ssl(Group, Config0); init_per_group(Group, Config0) when Group == http_basic; @@ -159,7 +191,8 @@ init_per_group(Group, Config0) when Group == http_basic; Group == http_auth_api; Group == http_auth_api_dets; Group == http_auth_api_mnesia; - Group == http_security + Group == http_security; + Group == http_reload -> ok = start_apps(Group), init_httpd(Group, [{type, ip_comm} | Config0]); @@ -202,17 +235,19 @@ end_per_group(Group, _Config) when Group == http_basic; Group == http_auth_api_dets; Group == http_auth_api_mnesia; Group == http_htaccess; - Group == http_security + Group == http_security; + Group == http_reload -> inets:stop(); end_per_group(Group, _Config) when Group == https_basic; Group == https_limit; Group == https_basic_auth; Group == https_auth_api; - Group == http_auth_api_dets; - Group == http_auth_api_mnesia; + Group == https_auth_api_dets; + Group == https_auth_api_mnesia; Group == https_htaccess; - Group == http_security + Group == https_security; + Group == https_reload -> ssl:stop(), inets:stop(); @@ -506,7 +541,7 @@ ipv6(Config) when is_list(Config) -> true -> Version = ?config(http_version, Config), Host = ?config(host, Config), - URI = http_request("GET /", Version, Host), + URI = http_request("GET / ", Version, Host), httpd_test_lib:verify_request(?config(type, Config), Host, ?config(port, Config), [inet6], ?config(code, Config), @@ -945,13 +980,22 @@ max_header(Config) when is_list(Config) -> Host = ?config(host, Config), case Version of "HTTP/0.9" -> - {skip, no_implemented}; + {skip, not_implemented}; _ -> dos_hostname(?config(type, Config), ?config(port, Config), Host, ?config(node, Config), Version, ?MAX_HEADER_SIZE) end. %%------------------------------------------------------------------------- +max_content_length() -> + ["Denial Of Service (DOS) attack, prevented by max_content_length"]. +max_content_length(Config) when is_list(Config) -> + Version = ?config(http_version, Config), + Host = ?config(host, Config), + garbage_content_length(?config(type, Config), ?config(port, Config), Host, + ?config(node, Config), Version). + +%%------------------------------------------------------------------------- security_1_1(Config) when is_list(Config) -> security([{http_version, "HTTP/1.1"} | Config]). @@ -1088,12 +1132,114 @@ security(Config) -> [{statuscode, 401}]), true = unblock_user(Node, "two", Port, OpenDir). + +%%------------------------------------------------------------------------- +non_disturbing_reconfiger_dies(Config) when is_list(Config) -> + do_reconfiger_dies([{http_version, "HTTP/1.1"} | Config], non_disturbing). +disturbing_reconfiger_dies(Config) when is_list(Config) -> + do_reconfiger_dies([{http_version, "HTTP/1.1"} | Config], disturbing). + +do_reconfiger_dies(Config, DisturbingType) -> + Server = ?config(server_pid, Config), + Version = ?config(http_version, Config), + Host = ?config(host, Config), + Port = ?config(port, Config), + Type = ?config(type, Config), + + HttpdConfig = httpd:info(Server), + BlockRequest = http_request("GET /eval?httpd_example:delay(2000) ", Version, Host), + {ok, Socket} = inets_test_lib:connect_bin(Type, Host, Port, transport_opts(Type, Config)), + inets_test_lib:send(Type, Socket, BlockRequest), + ct:sleep(100), %% Avoid possible timing issues + Pid = spawn(fun() -> httpd:reload_config([{server_name, "httpd_kill_" ++ Version}, + {port, Port}| + proplists:delete(server_name, HttpdConfig)], DisturbingType) + end), + monitor(process, Pid), + exit(Pid, kill), + receive + {'DOWN', _, _, _, _} -> + ok + end, + inets_test_lib:close(Type, Socket), + [{server_name, "httpd_test"}] = httpd:info(Server, [server_name]). +%%------------------------------------------------------------------------- +disturbing_1_1(Config) when is_list(Config) -> + disturbing([{http_version, "HTTP/1.1"} | Config]). + +disturbing_1_0(Config) when is_list(Config) -> + disturbing([{http_version, "HTTP/1.0"} | Config]). + +disturbing_0_9(Config) when is_list(Config) -> + disturbing([{http_version, "HTTP/0.9"} | Config]). + +disturbing(Config) when is_list(Config)-> + Server = ?config(server_pid, Config), + Version = ?config(http_version, Config), + Host = ?config(host, Config), + Port = ?config(port, Config), + Type = ?config(type, Config), + HttpdConfig = httpd:info(Server), + BlockRequest = http_request("GET /eval?httpd_example:delay(2000) ", Version, Host), + {ok, Socket} = inets_test_lib:connect_bin(Type, Host, Port, transport_opts(Type, Config)), + inets_test_lib:send(Type, Socket, BlockRequest), + ct:sleep(100), %% Avoid possible timing issues + ok = httpd:reload_config([{server_name, "httpd_disturbing_" ++ Version}, {port, Port}| + proplists:delete(server_name, HttpdConfig)], disturbing), + Close = list_to_atom((typestr(Type)) ++ "_closed"), + receive + {Close, Socket} -> + ok; + Msg -> + ct:fail({{expected, {Close, Socket}}, {got, Msg}}) + end, + inets_test_lib:close(Type, Socket), + [{server_name, "httpd_disturbing_" ++ Version}] = httpd:info(Server, [server_name]). +%%------------------------------------------------------------------------- +non_disturbing_1_1(Config) when is_list(Config) -> + non_disturbing([{http_version, "HTTP/1.1"} | Config]). + +non_disturbing_1_0(Config) when is_list(Config) -> + non_disturbing([{http_version, "HTTP/1.0"} | Config]). + +non_disturbing_0_9(Config) when is_list(Config) -> + non_disturbing([{http_version, "HTTP/0.9"} | Config]). + +non_disturbing(Config) when is_list(Config)-> + Server = ?config(server_pid, Config), + Version = ?config(http_version, Config), + Host = ?config(host, Config), + Port = ?config(port, Config), + Type = ?config(type, Config), + + HttpdConfig = httpd:info(Server), + BlockRequest = http_request("GET /eval?httpd_example:delay(2000) ", Version, Host), + {ok, Socket} = inets_test_lib:connect_bin(Type, Host, Port, transport_opts(Type, Config)), + inets_test_lib:send(Type, Socket, BlockRequest), + ct:sleep(100), %% Avoid possible timing issues + ok = httpd:reload_config([{server_name, "httpd_non_disturbing_" ++ Version}, {port, Port}| + proplists:delete(server_name, HttpdConfig)], non_disturbing), + Transport = type(Type), + receive + {Transport, Socket, Msg} -> + ct:pal("Received message ~p~n", [Msg]), + ok + after 2000 -> + ct:fail(timeout) + end, + inets_test_lib:close(Type, Socket), + [{server_name, "httpd_non_disturbing_" ++ Version}] = httpd:info(Server, [server_name]). %%-------------------------------------------------------------------- %% Internal functions ----------------------------------- %%-------------------------------------------------------------------- +url(http, End, Config) -> + Port = ?config(port, Config), + {ok,Host} = inet:gethostname(), + ?URL_START ++ Host ++ ":" ++ integer_to_list(Port) ++ End. + do_max_clients(Config) -> Version = ?config(http_version, Config), Host = ?config(host, Config), @@ -1171,7 +1317,9 @@ start_apps(Group) when Group == https_basic; Group == https_auth_api_dets; Group == https_auth_api_mnesia; Group == http_htaccess; - Group == http_security -> + Group == http_security; + Group == http_reload + -> inets_test_lib:start_apps([inets, asn1, crypto, public_key, ssl]); start_apps(Group) when Group == http_basic; Group == http_limit; @@ -1180,7 +1328,8 @@ start_apps(Group) when Group == http_basic; Group == http_auth_api_dets; Group == http_auth_api_mnesia; Group == https_htaccess; - Group == https_security -> + Group == https_security; + Group == https_reload-> inets_test_lib:start_apps([inets]). server_start(_, HttpdConfig) -> @@ -1224,8 +1373,14 @@ server_config(http_basic, Config) -> basic_conf() ++ server_config(http, Config); server_config(https_basic, Config) -> basic_conf() ++ server_config(https, Config); +server_config(http_reload, Config) -> + [{keep_alive_timeout, 2}] ++ server_config(http, Config); +server_config(https_reload, Config) -> + [{keep_alive_timeout, 2}] ++ server_config(https, Config); server_config(http_limit, Config) -> - [{max_clients, 1}] ++ server_config(http, Config); + [{max_clients, 1}, + %% Make sure option checking code is run + {max_content_length, 100000002}] ++ server_config(http, Config); server_config(https_limit, Config) -> [{max_clients, 1}] ++ server_config(https, Config); server_config(http_basic_auth, Config) -> @@ -1270,7 +1425,7 @@ server_config(http, Config) -> {server_root, ServerRoot}, {document_root, ?config(doc_root, Config)}, {bind_address, any}, - {ipfamily, inet}, + {ipfamily, ?config(ipfamily, Config)}, {max_header_size, 256}, {max_header_action, close}, {directory_index, ["index.html", "welcome.html"]}, @@ -1539,9 +1694,10 @@ cleanup_mnesia() -> transport_opts(ssl, Config) -> PrivDir = ?config(priv_dir, Config), - [{cacertfile, filename:join(PrivDir, "public_key_cacert.pem")}]; -transport_opts(_, _) -> - []. + [?config(ipfamily, Config), + {cacertfile, filename:join(PrivDir, "public_key_cacert.pem")}]; +transport_opts(_, Config) -> + [?config(ipfamily, Config)]. %%% mod_range @@ -1670,7 +1826,7 @@ dos_hostname(Type, Port, Host, Node, Version, Max) -> ok = httpd_test_lib:verify_request(Type, Host, Port, Node, dos_hostname_request(TooLongHeader, Version), - [{statuscode, dos_code(Version)}, + [{statuscode, request_entity_too_large_code(Version)}, {version, Version}]). dos_hostname_request(Host, Version) -> dos_http_request("GET / ", Version, Host). @@ -1680,11 +1836,32 @@ dos_http_request(Request, "HTTP/1.1" = Version, Host) -> dos_http_request(Request, Version, Host) -> Request ++ Version ++ "\r\nhost:" ++ Host ++ "\r\n\r\n". -dos_code("HTTP/1.0") -> +request_entity_too_large_code("HTTP/1.0") -> 403; %% 413 not defined in HTTP/1.0 -dos_code(_) -> +request_entity_too_large_code(_) -> 413. +length_required_code("HTTP/1.0") -> + 403; %% 411 not defined in HTTP/1.0 +length_required_code(_) -> + 411. + +garbage_content_length(Type, Port, Host, Node, Version) -> + ok = httpd_test_lib:verify_request(Type, Host, Port, Node, + garbage_content_length_request("GET / ", Version, Host, "aaaa"), + [{statuscode, length_required_code(Version)}, + {version, Version}]), + ok = httpd_test_lib:verify_request(Type, Host, Port, Node, + garbage_content_length_request("GET / ", Version, Host, + lists:duplicate($a, 100)), + [{statuscode, request_entity_too_large_code(Version)}, + {version, Version}]). + +garbage_content_length_request(Request, Version, Host, Garbage) -> + http_request(Request, Version, Host, + {"content-length:" ++ Garbage, "Body with garbage content length indicator"}). + + update_password(Node, ServerRoot, _Address, Port, AuthPrefix, Dir, Old, New)-> Directory = filename:join([ServerRoot, "htdocs", AuthPrefix ++ Dir]), rpc:call(Node, mod_auth, update_password, @@ -1792,3 +1969,12 @@ event(What, Port, Dir, Data) -> global:send(mod_security_test, Msg) end. +type(ip_comm) -> + tcp; +type(_) -> + ssl. + +typestr(ip_comm) -> + "tcp"; +typestr(_) -> + "ssl". |