diff options
author | Henrik Nord <[email protected]> | 2015-06-02 09:09:10 +0200 |
---|---|---|
committer | Henrik Nord <[email protected]> | 2015-06-02 09:09:10 +0200 |
commit | b37c4e256dfd827cdfcb92078c1217a13aa2e656 (patch) | |
tree | 7c53916cfa4b16db12fe0b8492755caa5b50c2f7 | |
parent | e0e2a98d63f73121a56ae199cbfeba42b3a67fa6 (diff) | |
parent | 21b8941d83516e381000387c47758bc7f040ae8b (diff) | |
download | otp-b37c4e256dfd827cdfcb92078c1217a13aa2e656.tar.gz otp-b37c4e256dfd827cdfcb92078c1217a13aa2e656.tar.bz2 otp-b37c4e256dfd827cdfcb92078c1217a13aa2e656.zip |
Merge branch 'maint'
Conflicts:
OTP_VERSION
lib/inets/test/httpd_SUITE.erl
lib/inets/vsn.mk
lib/ssh/src/ssh.erl
lib/ssh/vsn.mk
lib/ssl/src/ssl.appup.src
lib/ssl/vsn.mk
-rw-r--r-- | lib/inets/doc/src/Makefile | 3 | ||||
-rw-r--r-- | lib/inets/doc/src/httpd.xml | 10 | ||||
-rw-r--r-- | lib/inets/doc/src/httpd_custom_api.xml | 63 | ||||
-rw-r--r-- | lib/inets/doc/src/notes.xml | 18 | ||||
-rw-r--r-- | lib/inets/doc/src/ref_man.xml | 3 | ||||
-rw-r--r-- | lib/inets/src/http_server/Makefile | 3 | ||||
-rw-r--r-- | lib/inets/src/http_server/httpd_custom.erl | 69 | ||||
-rw-r--r-- | lib/inets/src/http_server/httpd_request.erl | 140 | ||||
-rw-r--r-- | lib/inets/src/http_server/httpd_request_handler.erl | 11 | ||||
-rw-r--r-- | lib/inets/src/http_server/httpd_response.erl | 52 | ||||
-rw-r--r-- | lib/inets/src/inets_app/inets.app.src | 1 | ||||
-rw-r--r-- | lib/inets/test/httpc_SUITE.erl | 23 | ||||
-rw-r--r-- | lib/inets/test/httpd_SUITE.erl | 1256 | ||||
-rw-r--r-- | lib/ssh/doc/src/notes.xml | 27 | ||||
-rw-r--r-- | lib/ssh/src/ssh.erl | 13 | ||||
-rw-r--r-- | lib/ssh/src/ssh_auth.erl | 80 | ||||
-rw-r--r-- | lib/ssh/src/ssh_connection_handler.erl | 51 | ||||
-rw-r--r-- | lib/ssh/src/ssh_transport.erl | 9 | ||||
-rw-r--r-- | lib/ssl/doc/src/notes.xml | 18 | ||||
-rw-r--r-- | lib/ssl/src/ssl.appup.src | 10 | ||||
-rw-r--r-- | lib/ssl/src/ssl_handshake.erl | 32 | ||||
-rw-r--r-- | otp_versions.table | 1 |
22 files changed, 490 insertions, 1403 deletions
diff --git a/lib/inets/doc/src/Makefile b/lib/inets/doc/src/Makefile index 1a8e1c7ca8..961bfa838d 100644 --- a/lib/inets/doc/src/Makefile +++ b/lib/inets/doc/src/Makefile @@ -1,7 +1,7 @@ # # %CopyrightBegin% # -# Copyright Ericsson AB 1997-2012. All Rights Reserved. +# Copyright Ericsson AB 1997-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 @@ -52,6 +52,7 @@ XML_REF3_FILES = \ httpc.xml\ httpd.xml \ httpd_conf.xml \ + httpd_custom_api.xml \ httpd_socket.xml \ httpd_util.xml \ mod_alias.xml \ diff --git a/lib/inets/doc/src/httpd.xml b/lib/inets/doc/src/httpd.xml index e40660ab39..435f99ee23 100644 --- a/lib/inets/doc/src/httpd.xml +++ b/lib/inets/doc/src/httpd.xml @@ -204,7 +204,15 @@ <marker id="props_limit"></marker> <p><em>Limit properties</em> </p> - <taglist> + <taglist> + + <marker id="prop_customize"></marker> + <tag>{customize, atom()}</tag> + <item> + <p>A callback module to customize the inets HTTP servers behaviour + see <seealso marker="http_custom_api"> httpd_custom_api</seealso> </p> + </item> + <marker id="prop_disable_chunked_encoding"></marker> <tag>{disable_chunked_transfer_encoding_send, boolean()}</tag> <item> diff --git a/lib/inets/doc/src/httpd_custom_api.xml b/lib/inets/doc/src/httpd_custom_api.xml new file mode 100644 index 0000000000..faf1d277df --- /dev/null +++ b/lib/inets/doc/src/httpd_custom_api.xml @@ -0,0 +1,63 @@ +<?xml version="1.0" encoding="utf-8" ?> +<!DOCTYPE erlref SYSTEM "erlref.dtd"> + +<erlref> + <header> + <copyright> + <year>2015</year><year>2015</year> + <holder>Ericsson AB. All Rights Reserved.</holder> + </copyright> + <legalnotice> + 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 + compliance with the License. You should have received a copy of the + Erlang Public License along with this software. If not, it can be + retrieved online at http://www.erlang.org/. + + Software distributed under the License is distributed on an "AS IS" + basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + the License for the specific language governing rights and limitations + under the License. + + </legalnotice> + + <title>httpd_custom_api</title> + <file>httpd_custom_api.xml</file> + </header> + <module>httpd_custom_api</module> + <modulesummary>Behaviour with optional callbacks to customize the inets HTTP server.</modulesummary> + <description> + <p> The module implementing this behaviour shall be supplied to to the servers + configuration with the option <seealso marker="httpd:prop_customize"> customize</seealso></p> + + </description> + <funcs> + <func> + <name>response_header({HeaderName, HeaderValue}) -> {true, Header} | false </name> + <fsummary>Filter and possible alter HTTP response headers.</fsummary> + <type> + <v>Header = {HeaderName :: string(), HeaderValue::string()}</v> + <d>The header name will be in lower case and should not be altered.</d> + </type> + <desc> + <p> Filter and possible alter HTTP response headers before they are sent to the client. + </p> + </desc> + </func> + + <func> + <name>request_header({HeaderName, HeaderValue}) -> {true, Header} | false </name> + <fsummary>Filter and possible alter HTTP request headers.</fsummary> + <type> + <v>Header = {HeaderName :: string(), HeaderValue::string()}</v> + <d>The header name will be in lower case and should not be altered.</d> + </type> + <desc> + <p> Filter and possible alter HTTP request headers before they are processed by the server. + </p> + </desc> + </func> + </funcs> +</erlref> + + diff --git a/lib/inets/doc/src/notes.xml b/lib/inets/doc/src/notes.xml index bae8e327a3..f563a8c4b0 100644 --- a/lib/inets/doc/src/notes.xml +++ b/lib/inets/doc/src/notes.xml @@ -32,7 +32,23 @@ <file>notes.xml</file> </header> - <section><title>Inets 5.10.8</title> + <section><title>Inets 5.10.9</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Add behaviour with optional callbacks to customize the + inets HTTP server.</p> + <p> + Own Id: OTP-12776</p> + </item> + </list> + </section> + +</section> + +<section><title>Inets 5.10.8</title> <section><title>Fixed Bugs and Malfunctions</title> <list> diff --git a/lib/inets/doc/src/ref_man.xml b/lib/inets/doc/src/ref_man.xml index aaedf330b4..3afb020431 100644 --- a/lib/inets/doc/src/ref_man.xml +++ b/lib/inets/doc/src/ref_man.xml @@ -4,7 +4,7 @@ <application xmlns:xi="http://www.w3.org/2001/XInclude"> <header> <copyright> - <year>1997</year><year>2013</year> + <year>1997</year><year>2015</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -39,6 +39,7 @@ <xi:include href="httpc.xml"/> <xi:include href="httpd.xml"/> <xi:include href="httpd_conf.xml"/> + <xi:include href="httpd_custom_api.xml"/> <xi:include href="httpd_socket.xml"/> <xi:include href="httpd_util.xml"/> <xi:include href="mod_alias.xml"/> diff --git a/lib/inets/src/http_server/Makefile b/lib/inets/src/http_server/Makefile index 51e3dd9212..00bad51ff9 100644 --- a/lib/inets/src/http_server/Makefile +++ b/lib/inets/src/http_server/Makefile @@ -1,7 +1,7 @@ # # %CopyrightBegin% # -# Copyright Ericsson AB 2005-2013. All Rights Reserved. +# Copyright Ericsson AB 2005-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 @@ -46,6 +46,7 @@ MODULES = \ httpd_connection_sup\ httpd_cgi \ httpd_conf \ + httpd_custom \ httpd_example \ httpd_esi \ httpd_file\ diff --git a/lib/inets/src/http_server/httpd_custom.erl b/lib/inets/src/http_server/httpd_custom.erl new file mode 100644 index 0000000000..342469a579 --- /dev/null +++ b/lib/inets/src/http_server/httpd_custom.erl @@ -0,0 +1,69 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2015-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 +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +%% +-module(httpd_custom). + +-export([response_header/1, request_header/1]). +-export([customize_headers/3]). + +-include_lib("inets/src/inets_app/inets_internal.hrl"). + +response_header(Header) -> + {true, httpify(Header)}. +request_header(Header) -> + {true, Header}. + +customize_headers(?MODULE, Function, Arg) -> + ?MODULE:Function(Arg); +customize_headers(Module, Function, Arg) -> + try Module:Function(Arg) of + {true, Value} -> + ?MODULE:Function(Value); + false -> + false + catch + _:_ -> + ?MODULE:Function(Arg) + end. + +httpify({Key0, Value}) -> + %% make sure first letter is capital (defacto standard) + Words1 = string:tokens(Key0, "-"), + Words2 = upify(Words1, []), + Key = new_key(Words2), + Key ++ ": " ++ Value ++ ?CRLF . + +new_key([]) -> + ""; +new_key([W]) -> + W; +new_key([W1,W2]) -> + W1 ++ "-" ++ W2; +new_key([W|R]) -> + W ++ "-" ++ new_key(R). + +upify([], Acc) -> + lists:reverse(Acc); +upify([Key|Rest], Acc) -> + upify(Rest, [upify2(Key)|Acc]). + +upify2([C|Rest]) when (C >= $a) andalso (C =< $z) -> + [C-($a-$A)|Rest]; +upify2(Str) -> + Str. diff --git a/lib/inets/src/http_server/httpd_request.erl b/lib/inets/src/http_server/httpd_request.erl index 3ff07616f9..782120c284 100644 --- a/lib/inets/src/http_server/httpd_request.erl +++ b/lib/inets/src/http_server/httpd_request.erl @@ -42,28 +42,28 @@ %%%========================================================================= %%% Internal application API %%%========================================================================= -parse([Bin, MaxSizes]) -> - ?hdrt("parse", [{bin, Bin}, {max_sizes, MaxSizes}]), - parse_method(Bin, [], 0, proplists:get_value(max_method, MaxSizes), MaxSizes, []); +parse([Bin, Options]) -> + ?hdrt("parse", [{bin, Bin}, {max_sizes, Options}]), + parse_method(Bin, [], 0, proplists:get_value(max_method, Options), Options, []); parse(Unknown) -> ?hdrt("parse", [{unknown, Unknown}]), exit({bad_args, Unknown}). %% Functions that may be returned during the decoding process %% if the input data is incompleate. -parse_method([Bin, Method, Current, Max, MaxSizes, Result]) -> - parse_method(Bin, Method, Current, Max, MaxSizes, Result). +parse_method([Bin, Method, Current, Max, Options, Result]) -> + parse_method(Bin, Method, Current, Max, Options, Result). -parse_uri([Bin, URI, Current, Max, MaxSizes, Result]) -> - parse_uri(Bin, URI, Current, Max, MaxSizes, Result). +parse_uri([Bin, URI, Current, Max, Options, Result]) -> + parse_uri(Bin, URI, Current, Max, Options, Result). -parse_version([Bin, Rest, Version, Current, Max, MaxSizes, Result]) -> - parse_version(<<Rest/binary, Bin/binary>>, Version, Current, Max, MaxSizes, +parse_version([Bin, Rest, Version, Current, Max, Options, Result]) -> + parse_version(<<Rest/binary, Bin/binary>>, Version, Current, Max, Options, Result). -parse_headers([Bin, Rest, Header, Headers, Current, Max, MaxSizes, Result]) -> +parse_headers([Bin, Rest, Header, Headers, Current, Max, Options, Result]) -> parse_headers(<<Rest/binary, Bin/binary>>, - Header, Headers, Current, Max, MaxSizes, Result). + Header, Headers, Current, Max, Options, Result). whole_body([Bin, Body, Length]) -> whole_body(<<Body/binary, Bin/binary>>, Length). @@ -134,13 +134,13 @@ update_mod_data(ModData, Method, RequestURI, HTTPVersion, Headers)-> %%%======================================================================== %%% Internal functions %%%======================================================================== -parse_method(<<>>, Method, Current, Max, MaxSizes, Result) -> - {?MODULE, parse_method, [Method, Current, Max, MaxSizes, Result]}; -parse_method(<<?SP, Rest/binary>>, Method, _Current, _Max, MaxSizes, Result) -> - parse_uri(Rest, [], 0, proplists:get_value(max_uri, MaxSizes), MaxSizes, +parse_method(<<>>, Method, Current, Max, Options, Result) -> + {?MODULE, parse_method, [Method, Current, Max, Options, Result]}; +parse_method(<<?SP, Rest/binary>>, Method, _Current, _Max, Options, Result) -> + parse_uri(Rest, [], 0, proplists:get_value(max_uri, Options), Options, [string:strip(lists:reverse(Method)) | Result]); -parse_method(<<Octet, Rest/binary>>, Method, Current, Max, MaxSizes, Result) when Current =< Max -> - parse_method(Rest, [Octet | Method], Current + 1, Max, MaxSizes, Result); +parse_method(<<Octet, Rest/binary>>, Method, Current, Max, Options, Result) when Current =< Max -> + parse_method(Rest, [Octet | Method], Current + 1, Max, Options, Result); parse_method(_, _, _, Max, _, _) -> %% We do not know the version of the client as it comes after the %% method send the lowest version in the response so that the client @@ -153,30 +153,30 @@ parse_uri(_, _, Current, MaxURI, _, _) %% uri send the lowest version in the response so that the client %% will be able to handle it. {error, {size_error, MaxURI, 414, "URI unreasonably long"},lowest_version()}; -parse_uri(<<>>, URI, Current, Max, MaxSizes, Result) -> - {?MODULE, parse_uri, [URI, Current, Max, MaxSizes, Result]}; -parse_uri(<<?SP, Rest/binary>>, URI, _, _, MaxSizes, Result) -> - parse_version(Rest, [], 0, proplists:get_value(max_version, MaxSizes), MaxSizes, +parse_uri(<<>>, URI, Current, Max, Options, Result) -> + {?MODULE, parse_uri, [URI, Current, Max, Options, Result]}; +parse_uri(<<?SP, Rest/binary>>, URI, _, _, Options, Result) -> + parse_version(Rest, [], 0, proplists:get_value(max_version, Options), Options, [string:strip(lists:reverse(URI)) | Result]); %% Can happen if it is a simple HTTP/0.9 request e.i "GET /\r\n\r\n" -parse_uri(<<?CR, _Rest/binary>> = Data, URI, _, _, MaxSizes, Result) -> - parse_version(Data, [], 0, proplists:get_value(max_version, MaxSizes), MaxSizes, +parse_uri(<<?CR, _Rest/binary>> = Data, URI, _, _, Options, Result) -> + parse_version(Data, [], 0, proplists:get_value(max_version, Options), Options, [string:strip(lists:reverse(URI)) | Result]); -parse_uri(<<Octet, Rest/binary>>, URI, Current, Max, MaxSizes, Result) -> - parse_uri(Rest, [Octet | URI], Current + 1, Max, MaxSizes, Result). +parse_uri(<<Octet, Rest/binary>>, URI, Current, Max, Options, Result) -> + parse_uri(Rest, [Octet | URI], Current + 1, Max, Options, Result). -parse_version(<<>>, Version, Current, Max, MaxSizes, Result) -> - {?MODULE, parse_version, [<<>>, Version, Current, Max, MaxSizes, Result]}; -parse_version(<<?LF, Rest/binary>>, Version, Current, Max, MaxSizes, Result) -> +parse_version(<<>>, Version, Current, Max, Options, Result) -> + {?MODULE, parse_version, [<<>>, Version, Current, Max, Options, Result]}; +parse_version(<<?LF, Rest/binary>>, Version, Current, Max, Options, Result) -> %% If ?CR is is missing RFC2616 section-19.3 - parse_version(<<?CR, ?LF, Rest/binary>>, Version, Current, Max, MaxSizes, Result); -parse_version(<<?CR, ?LF, Rest/binary>>, Version, _, _, MaxSizes, Result) -> - parse_headers(Rest, [], [], 0, proplists:get_value(max_header, MaxSizes), MaxSizes, + parse_version(<<?CR, ?LF, Rest/binary>>, Version, Current, Max, Options, Result); +parse_version(<<?CR, ?LF, Rest/binary>>, Version, _, _, Options, Result) -> + parse_headers(Rest, [], [], 0, proplists:get_value(max_header, Options), Options, [string:strip(lists:reverse(Version)) | Result]); -parse_version(<<?CR>> = Data, Version, Current, Max, MaxSizes, Result) -> - {?MODULE, parse_version, [Data, Version, Current, Max, MaxSizes, Result]}; -parse_version(<<Octet, Rest/binary>>, Version, Current, Max, MaxSizes, Result) when Current =< Max -> - parse_version(Rest, [Octet | Version], Current + 1, Max, MaxSizes, Result); +parse_version(<<?CR>> = Data, Version, Current, Max, Options, Result) -> + {?MODULE, parse_version, [Data, Version, Current, Max, Options, Result]}; +parse_version(<<Octet, Rest/binary>>, Version, Current, Max, Options, Result) when Current =< Max -> + parse_version(Rest, [Octet | Version], Current + 1, Max, Options, Result); parse_version(_, _, _, Max,_,_) -> {error, {size_error, Max, 413, "Version string unreasonably long"}, lowest_version()}. @@ -185,34 +185,42 @@ parse_headers(_, _, _, Current, Max, _, Result) HttpVersion = lists:nth(3, lists:reverse(Result)), {error, {size_error, Max, 413, "Headers unreasonably long"}, HttpVersion}; -parse_headers(<<>>, Header, Headers, Current, Max, MaxSizes, Result) -> +parse_headers(<<>>, Header, Headers, Current, Max, Options, Result) -> {?MODULE, parse_headers, [<<>>, Header, Headers, Current, Max, - MaxSizes, Result]}; -parse_headers(<<?CR,?LF,?LF,Body/binary>>, [], [], Current, Max, MaxSizes, Result) -> + Options, Result]}; +parse_headers(<<?CR,?LF,?LF,Body/binary>>, [], [], Current, Max, Options, Result) -> %% If ?CR is is missing RFC2616 section-19.3 parse_headers(<<?CR,?LF,?CR,?LF,Body/binary>>, [], [], Current, Max, - MaxSizes, Result); + Options, Result); -parse_headers(<<?LF,?LF,Body/binary>>, [], [], Current, Max, MaxSizes, Result) -> +parse_headers(<<?LF,?LF,Body/binary>>, [], [], Current, Max, Options, Result) -> %% If ?CR is is missing RFC2616 section-19.3 parse_headers(<<?CR,?LF,?CR,?LF,Body/binary>>, [], [], Current, Max, - MaxSizes, Result); + Options, Result); parse_headers(<<?CR,?LF,?CR,?LF,Body/binary>>, [], [], _, _, _, Result) -> NewResult = list_to_tuple(lists:reverse([Body, {#http_request_h{}, []} | Result])), {ok, NewResult}; parse_headers(<<?CR,?LF,?CR,?LF,Body/binary>>, Header, Headers, _, _, - MaxSizes, Result) -> + Options, Result) -> + Customize = proplists:get_value(customize, Options), case http_request:key_value(lists:reverse(Header)) of undefined -> %% Skip headers with missing : - {ok, list_to_tuple(lists:reverse([Body, {http_request:headers(Headers, #http_request_h{}), Headers} | Result]))}; + FinalHeaders = lists:filtermap(fun(H) -> + httpd_custom:customize_headers(Customize, request_header, H) + end, + Headers), + {ok, list_to_tuple(lists:reverse([Body, {http_request:headers(FinalHeaders, #http_request_h{}), FinalHeaders} | Result]))}; NewHeader -> - case check_header(NewHeader, MaxSizes) of + case check_header(NewHeader, Options) of ok -> - {ok, list_to_tuple(lists:reverse([Body, {http_request:headers([NewHeader | Headers], + FinalHeaders = lists:filtermap(fun(H) -> + httpd_custom:customize_headers(Customize, request_header, H) + end, [NewHeader | Headers]), + {ok, list_to_tuple(lists:reverse([Body, {http_request:headers(FinalHeaders, #http_request_h{}), - [NewHeader | Headers]} | Result]))}; + FinalHeaders} | Result]))}; {error, Reason} -> HttpVersion = lists:nth(3, lists:reverse(Result)), @@ -221,12 +229,12 @@ parse_headers(<<?CR,?LF,?CR,?LF,Body/binary>>, Header, Headers, _, _, end; parse_headers(<<?CR,?LF,?CR>> = Data, Header, Headers, Current, Max, - MaxSizes, Result) -> + Options, Result) -> {?MODULE, parse_headers, [Data, Header, Headers, Current, Max, - MaxSizes, Result]}; -parse_headers(<<?LF>>, [], [], Current, Max, MaxSizes, Result) -> + Options, Result]}; +parse_headers(<<?LF>>, [], [], Current, Max, Options, Result) -> %% If ?CR is is missing RFC2616 section-19.3 - parse_headers(<<?CR,?LF>>, [], [], Current, Max, MaxSizes, Result); + parse_headers(<<?CR,?LF>>, [], [], Current, Max, Options, Result); %% There where no headers, which is unlikely to happen. parse_headers(<<?CR,?LF>>, [], [], _, _, _, Result) -> @@ -235,30 +243,30 @@ parse_headers(<<?CR,?LF>>, [], [], _, _, _, Result) -> {ok, NewResult}; parse_headers(<<?LF>>, Header, Headers, Current, Max, - MaxSizes, Result) -> + Options, Result) -> %% If ?CR is is missing RFC2616 section-19.3 - parse_headers(<<?CR,?LF>>, Header, Headers, Current, Max, MaxSizes, Result); + parse_headers(<<?CR,?LF>>, Header, Headers, Current, Max, Options, Result); parse_headers(<<?CR,?LF>> = Data, Header, Headers, Current, Max, - MaxSizes, Result) -> + Options, Result) -> {?MODULE, parse_headers, [Data, Header, Headers, Current, Max, - MaxSizes, Result]}; + Options, Result]}; parse_headers(<<?LF, Octet, Rest/binary>>, Header, Headers, Current, Max, - MaxSizes, Result) -> + Options, Result) -> %% If ?CR is is missing RFC2616 section-19.3 parse_headers(<<?CR,?LF, Octet, Rest/binary>>, Header, Headers, Current, Max, - MaxSizes, Result); + Options, Result); parse_headers(<<?CR,?LF, Octet, Rest/binary>>, Header, Headers, _, Max, - MaxSizes, Result) -> + Options, Result) -> case http_request:key_value(lists:reverse(Header)) of undefined -> %% Skip headers with missing : parse_headers(Rest, [Octet], Headers, - 0, Max, MaxSizes, Result); + 0, Max, Options, Result); NewHeader -> - case check_header(NewHeader, MaxSizes) of + case check_header(NewHeader, Options) of ok -> parse_headers(Rest, [Octet], [NewHeader | Headers], - 0, Max, MaxSizes, Result); + 0, Max, Options, Result); {error, Reason} -> HttpVersion = lists:nth(3, lists:reverse(Result)), {error, Reason, HttpVersion} @@ -266,19 +274,19 @@ parse_headers(<<?CR,?LF, Octet, Rest/binary>>, Header, Headers, _, Max, end; parse_headers(<<?CR>> = Data, Header, Headers, Current, Max, - MaxSizes, Result) -> + Options, Result) -> {?MODULE, parse_headers, [Data, Header, Headers, Current, Max, - MaxSizes, Result]}; + Options, Result]}; parse_headers(<<?LF>>, Header, Headers, Current, Max, - MaxSizes, Result) -> + Options, Result) -> %% If ?CR is is missing RFC2616 section-19.3 parse_headers(<<?CR, ?LF>>, Header, Headers, Current, Max, - MaxSizes, Result); + Options, Result); parse_headers(<<Octet, Rest/binary>>, Header, Headers, Current, - Max, MaxSizes, Result) -> + Max, Options, Result) -> parse_headers(Rest, [Octet | Header], Headers, Current + 1, Max, - MaxSizes, Result). + Options, Result). whole_body(Body, Length) -> case size(Body) of diff --git a/lib/inets/src/http_server/httpd_request_handler.erl b/lib/inets/src/http_server/httpd_request_handler.erl index f7a9fe5d49..9947e17b47 100644 --- a/lib/inets/src/http_server/httpd_request_handler.erl +++ b/lib/inets/src/http_server/httpd_request_handler.erl @@ -121,13 +121,15 @@ continue_init(Manager, ConfigDB, SocketType, Socket, TimeOut) -> MaxURISize = max_uri_size(ConfigDB), NrOfRequest = max_keep_alive_request(ConfigDB), MaxContentLen = max_content_length(ConfigDB), + Customize = customize(ConfigDB), {_, Status} = httpd_manager:new_connection(Manager), MFA = {httpd_request, parse, [[{max_uri, MaxURISize}, {max_header, MaxHeaderSize}, {max_version, ?HTTP_MAX_VERSION_STRING}, {max_method, ?HTTP_MAX_METHOD_STRING}, - {max_content_length, MaxContentLen} + {max_content_length, MaxContentLen}, + {customize, Customize} ]]}, State = #state{mod = Mod, @@ -550,11 +552,13 @@ handle_next_request(#state{mod = #mod{connection = true} = ModData, MaxHeaderSize = max_header_size(ModData#mod.config_db), MaxURISize = max_uri_size(ModData#mod.config_db), MaxContentLen = max_content_length(ModData#mod.config_db), + Customize = customize(ModData#mod.config_db), MFA = {httpd_request, parse, [[{max_uri, MaxURISize}, {max_header, MaxHeaderSize}, {max_version, ?HTTP_MAX_VERSION_STRING}, {max_method, ?HTTP_MAX_METHOD_STRING}, - {max_content_length, MaxContentLen} + {max_content_length, MaxContentLen}, + {customize, Customize} ]]}, TmpState = State#state{mod = NewModData, mfa = MFA, @@ -640,3 +644,6 @@ max_keep_alive_request(ConfigDB) -> max_content_length(ConfigDB) -> httpd_util:lookup(ConfigDB, max_content_length, ?HTTP_MAX_CONTENT_LENGTH). + +customize(ConfigDB) -> + httpd_util:lookup(ConfigDB, customize, httpd_custom). diff --git a/lib/inets/src/http_server/httpd_response.erl b/lib/inets/src/http_server/httpd_response.erl index 2fa91d47a0..71dc05e46d 100644 --- a/lib/inets/src/http_server/httpd_response.erl +++ b/lib/inets/src/http_server/httpd_response.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1997-2013. All Rights Reserved. +%% Copyright Ericsson AB 1997-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 @@ -176,7 +176,7 @@ send_header(#mod{socket_type = Type, StatusLine = [NewVer, " ", io_lib:write(NewStatusCode), " ", httpd_util:reason_phrase(NewStatusCode), ?CRLF], ConnectionHeader = get_connection(Conn, NewVer), - Head = list_to_binary([StatusLine, Headers, ConnectionHeader , ?CRLF]), + Head = [StatusLine, Headers, ConnectionHeader , ?CRLF], httpd_socket:deliver(Type, Sock, Head). map_status_code("HTTP/1.0", Code) @@ -286,45 +286,21 @@ create_header(ConfigDb, KeyValueTupleHeaders) -> Date = httpd_util:rfc1123_date(), ContentType = "text/html", Server = server(ConfigDb), - NewHeaders = add_default_headers([{"date", Date}, - {"content-type", ContentType} - | if Server=="" -> []; - true -> [{"server", Server}] - end - ], - KeyValueTupleHeaders), - lists:map(fun fix_header/1, NewHeaders). - - + Headers0 = add_default_headers([{"date", Date}, + {"content-type", ContentType} + | if Server=="" -> []; + true -> [{"server", Server}] + end + ], + KeyValueTupleHeaders), + CustomizeCB = httpd_util:lookup(ConfigDb, customize, httpd_custom), + lists:filtermap(fun(H) -> + httpd_custom:customize_headers(CustomizeCB, response_header, H) + end, + [Header || Header <- Headers0]). server(ConfigDb) -> httpd_util:lookup(ConfigDb, server, ?SERVER_SOFTWARE). -fix_header({Key0, Value}) -> - %% make sure first letter is capital - Words1 = string:tokens(Key0, "-"), - Words2 = upify(Words1, []), - Key = new_key(Words2), - Key ++ ": " ++ Value ++ ?CRLF . - -new_key([]) -> - ""; -new_key([W]) -> - W; -new_key([W1,W2]) -> - W1 ++ "-" ++ W2; -new_key([W|R]) -> - W ++ "-" ++ new_key(R). - -upify([], Acc) -> - lists:reverse(Acc); -upify([Key|Rest], Acc) -> - upify(Rest, [upify2(Key)|Acc]). - -upify2([C|Rest]) when (C >= $a) andalso (C =< $z) -> - [C-($a-$A)|Rest]; -upify2(Str) -> - Str. - add_default_headers([], Headers) -> Headers; diff --git a/lib/inets/src/inets_app/inets.app.src b/lib/inets/src/inets_app/inets.app.src index b7c3e341e8..6ba9795d9e 100644 --- a/lib/inets/src/inets_app/inets.app.src +++ b/lib/inets/src/inets_app/inets.app.src @@ -63,6 +63,7 @@ httpd_cgi, httpd_connection_sup, httpd_conf, + httpd_custom, httpd_esi, httpd_example, httpd_file, diff --git a/lib/inets/test/httpc_SUITE.erl b/lib/inets/test/httpc_SUITE.erl index 0dfc65e8f7..ab7ffadf75 100644 --- a/lib/inets/test/httpc_SUITE.erl +++ b/lib/inets/test/httpc_SUITE.erl @@ -1289,7 +1289,9 @@ dummy_server_init(Caller, ip_comm, Inet, _) -> {max_header, ?HTTP_MAX_HEADER_SIZE}, {max_version,?HTTP_MAX_VERSION_STRING}, {max_method, ?HTTP_MAX_METHOD_STRING}, - {max_content_length, ?HTTP_MAX_CONTENT_LENGTH}]]}, + {max_content_length, ?HTTP_MAX_CONTENT_LENGTH}, + {customize, httpd_custom} + ]]}, [], ListenSocket); dummy_server_init(Caller, ssl, Inet, SSLOptions) -> @@ -1305,7 +1307,8 @@ dummy_ssl_server_init(Caller, BaseOpts, Inet) -> {max_method, ?HTTP_MAX_METHOD_STRING}, {max_version,?HTTP_MAX_VERSION_STRING}, {max_method, ?HTTP_MAX_METHOD_STRING}, - {max_content_length, ?HTTP_MAX_CONTENT_LENGTH} + {max_content_length, ?HTTP_MAX_CONTENT_LENGTH}, + {customize, httpd_custom} ]]}, [], ListenSocket). @@ -1384,18 +1387,20 @@ handle_request(Module, Function, Args, Socket) -> stop; <<>> -> {httpd_request, parse, [[{max_uri,?HTTP_MAX_URI_SIZE}, - {max_header, ?HTTP_MAX_HEADER_SIZE}, - {max_version,?HTTP_MAX_VERSION_STRING}, - {max_method, ?HTTP_MAX_METHOD_STRING}, - {max_content_length, ?HTTP_MAX_CONTENT_LENGTH} - ]]}; + {max_header, ?HTTP_MAX_HEADER_SIZE}, + {max_version,?HTTP_MAX_VERSION_STRING}, + {max_method, ?HTTP_MAX_METHOD_STRING}, + {max_content_length, ?HTTP_MAX_CONTENT_LENGTH}, + {customize, httpd_custom} + ]]}; Data -> handle_request(httpd_request, parse, [Data, [{max_uri, ?HTTP_MAX_URI_SIZE}, - {max_header, ?HTTP_MAX_HEADER_SIZE}, + {max_header, ?HTTP_MAX_HEADER_SIZE}, {max_version,?HTTP_MAX_VERSION_STRING}, {max_method, ?HTTP_MAX_METHOD_STRING}, - {max_content_length, ?HTTP_MAX_CONTENT_LENGTH} + {max_content_length, ?HTTP_MAX_CONTENT_LENGTH}, + {customize, httpd_custom} ]], Socket) end; NewMFA -> diff --git a/lib/inets/test/httpd_SUITE.erl b/lib/inets/test/httpd_SUITE.erl index 7670c2cc60..854ffa8981 100644 --- a/lib/inets/test/httpd_SUITE.erl +++ b/lib/inets/test/httpd_SUITE.erl @@ -53,6 +53,8 @@ all() -> {group, https_basic}, {group, http_limit}, {group, https_limit}, + {group, http_custom}, + {group, https_custom}, {group, http_basic_auth}, {group, https_basic_auth}, {group, http_auth_api}, @@ -76,6 +78,8 @@ groups() -> {https_basic, [], basic_groups()}, {http_limit, [], [{group, limit}]}, {https_limit, [], [{group, limit}]}, + {http_custom, [], [{group, custom}]}, + {https_custom, [], [{group, custom}]}, {http_basic_auth, [], [{group, basic_auth}]}, {https_basic_auth, [], [{group, basic_auth}]}, {http_auth_api, [], [{group, auth_api}]}, @@ -92,1253 +96,7 @@ groups() -> {https_reload, [], [{group, reload}]}, {http_mime_types, [], [alias_1_1, alias_1_0, alias_0_9]}, {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 - ]}, - {auth_api_dets, [], [auth_api_1_1, auth_api_1_0, auth_api_0_9 - ]}, - {auth_api_mnesia, [], [auth_api_1_1, auth_api_1_0, auth_api_0_9 - ]}, - {htaccess, [], [htaccess_1_1, htaccess_1_0, htaccess_0_9]}, - {security, [], [security_1_1, security_1_0]}, %% Skip 0.9 as causes timing issus in test code - {http_1_1, [], [host, chunked, expect, cgi, cgi_chunked_encoding_test, - trace, range, if_modified_since] ++ http_head() ++ http_get() ++ load()}, - {http_1_0, [], [host, cgi, trace] ++ http_head() ++ http_get() ++ load()}, - {http_0_9, [], http_head() ++ http_get() ++ load()} - ]. - -basic_groups ()-> - [{group, http_1_1}, - {group, http_1_0}, - {group, http_0_9} - ]. - -http_head() -> - [head]. -http_get() -> - [alias, - get, - %%actions, Add configuration so that this test mod_action - esi, - content_length, - bad_hex, - missing_CR, - max_header, - max_content_length, - ipv6 - ]. - -load() -> - [light, medium - %%,heavy - ]. - -init_per_suite(Config) -> - PrivDir = ?config(priv_dir, Config), - DataDir = ?config(data_dir, Config), - inets_test_lib:stop_apps([inets]), - ServerRoot = filename:join(PrivDir, "server_root"), - 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]. - -end_per_suite(_Config) -> - ok. - -%%-------------------------------------------------------------------- -init_per_group(Group, Config0) when Group == https_basic; - Group == https_limit; - Group == https_basic_auth; - Group == https_auth_api; - Group == https_auth_api_dets; - Group == https_auth_api_mnesia; - Group == https_security; - Group == https_reload - -> - init_ssl(Group, Config0); -init_per_group(Group, Config0) when Group == http_basic; - Group == http_limit; - Group == http_basic_auth; - Group == http_auth_api; - Group == http_auth_api_dets; - Group == http_auth_api_mnesia; - Group == http_security; - Group == http_reload; - Group == http_mime_types - -> - ok = start_apps(Group), - init_httpd(Group, [{type, ip_comm} | Config0]); -init_per_group(http_1_1, Config) -> - [{http_version, "HTTP/1.1"} | Config]; -init_per_group(http_1_0, Config) -> - [{http_version, "HTTP/1.0"} | Config]; -init_per_group(http_0_9, Config) -> - case {os:type(), os:version()} of - {{win32, _}, {5,1,2600}} -> - {skip, "eaddrinuse XP problem"}; - _ -> - [{http_version, "HTTP/0.9"} | Config] - end; -init_per_group(http_htaccess = Group, Config) -> - Path = ?config(doc_root, Config), - catch remove_htaccess(Path), - create_htaccess_data(Path, ?config(address, Config)), - ok = start_apps(Group), - init_httpd(Group, [{type, ip_comm} | Config]); -init_per_group(https_htaccess = Group, Config) -> - Path = ?config(doc_root, Config), - catch remove_htaccess(Path), - create_htaccess_data(Path, ?config(address, Config)), - init_ssl(Group, Config); -init_per_group(auth_api, Config) -> - [{auth_prefix, ""} | Config]; -init_per_group(auth_api_dets, Config) -> - [{auth_prefix, "dets_"} | Config]; -init_per_group(auth_api_mnesia, Config) -> - start_mnesia(?config(node, Config)), - [{auth_prefix, "mnesia_"} | Config]; -init_per_group(_, Config) -> - Config. - -end_per_group(Group, _Config) when Group == http_basic; - Group == http_limit; - Group == http_basic_auth; - Group == http_auth_api; - Group == http_auth_api_dets; - Group == http_auth_api_mnesia; - Group == http_htaccess; - Group == http_security; - Group == http_reload; - Group == http_mime_types - -> - inets:stop(); -end_per_group(Group, _Config) when Group == https_basic; - Group == https_limit; - Group == https_basic_auth; - Group == https_auth_api; - Group == https_auth_api_dets; - Group == https_auth_api_mnesia; - Group == https_htaccess; - Group == https_security; - Group == https_reload - -> - ssl:stop(), - inets:stop(); - -end_per_group(auth_api_mnesia, _) -> - cleanup_mnesia(); - -end_per_group(_, _) -> - ok. - -%%-------------------------------------------------------------------- -init_per_testcase(Case, Config) when Case == host; Case == trace -> - Prop = ?config(tc_group_properties, Config), - Name = proplists:get_value(name, Prop), - Cb = case Name of - http_1_0 -> - httpd_1_0; - http_1_1 -> - httpd_1_1 - end, - [{version_cb, Cb} | proplists:delete(version_cb, Config)]; - -init_per_testcase(range, Config) -> - DocRoot = ?config(doc_root, Config), - create_range_data(DocRoot), - Config; - -init_per_testcase(_, Config) -> - Config. - -end_per_testcase(_Case, _Config) -> - ok. - -%%------------------------------------------------------------------------- -%% Test cases starts here. -%%------------------------------------------------------------------------- - -head() -> - [{doc, "HTTP HEAD request for static page"}]. - -head(Config) when is_list(Config) -> - Version = ?config(http_version, Config), - Host = ?config(host, Config), - ok = httpd_test_lib:verify_request(?config(type, Config), Host, - ?config(port, Config), ?config(node, Config), - http_request("HEAD /index.html ", Version, Host), - [{statuscode, head_status(Version)}, - {version, Version}]). - -get() -> - [{doc, "HTTP GET request for static page"}]. - -get(Config) when is_list(Config) -> - Version = ?config(http_version, Config), - Host = ?config(host, Config), - Type = ?config(type, Config), - ok = httpd_test_lib:verify_request(?config(type, Config), Host, - ?config(port, Config), - transport_opts(Type, Config), - ?config(node, Config), - http_request("GET /index.html ", Version, Host), - [{statuscode, 200}, - {header, "Content-Type", "text/html"}, - {header, "Date"}, - {header, "Server"}, - {version, Version}]). - -basic_auth_1_1(Config) when is_list(Config) -> - basic_auth([{http_version, "HTTP/1.1"} | Config]). - -basic_auth_1_0(Config) when is_list(Config) -> - basic_auth([{http_version, "HTTP/1.0"} | Config]). - -basic_auth_0_9(Config) when is_list(Config) -> - basic_auth([{http_version, "HTTP/0.9"} | Config]). - -basic_auth() -> - [{doc, "Test Basic authentication with WWW-Authenticate header"}]. - -basic_auth(Config) -> - Version = ?config(http_version, Config), - Host = ?config(host, Config), - basic_auth_requiered(Config), - %% Authentication OK! ["one:OnePassword" user first in user list] - ok = auth_status(auth_request("/open/dummy.html", "one", "onePassword", Version, Host), Config, - [{statuscode, 200}]), - %% Authentication OK and a directory listing is supplied! - %% ["Aladdin:open sesame" user second in user list] - ok = auth_status(auth_request("/open/", "Aladdin", "AladdinPassword", Version, Host), Config, - [{statuscode, 200}]), - %% User correct but wrong password! ["one:one" user first in user list] - ok = auth_status(auth_request("/open/dummy.html", "one", "one", Version, Host), Config, - [{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! - ok = auth_status(auth_request("/open/dummy.html", "one", "one", Version, Host), Config, - [{statuscode, 401}, - {header, "WWW-Authenticate"}]), - %% Neither user or password correct! ["dummy:dummy"] - ok = auth_status(auth_request("/open/dummy.html", "dummy", "dummy", Version, Host), Config, - [{statuscode, 401}]), - %% Nested secret/top_secret OK! ["Aladdin:open sesame"] - ok = http_status(auth_request("/secret/top_secret/", "Aladdin", "AladdinPassword", Version, Host), - Config, [{statuscode, 200}]), - %% Authentication still required! - basic_auth_requiered(Config). - -auth_api_1_1(Config) when is_list(Config) -> - auth_api([{http_version, "HTTP/1.1"} | Config]). - -auth_api_1_0(Config) when is_list(Config) -> - auth_api([{http_version, "HTTP/1.0"} | Config]). - -auth_api_0_9(Config) when is_list(Config) -> - auth_api([{http_version, "HTTP/0.9"} | Config]). - -auth_api() -> - [{doc, "Test mod_auth API"}]. - -auth_api(Config) when is_list(Config) -> - Prefix = ?config(auth_prefix, Config), - do_auth_api(Prefix, Config). - -do_auth_api(AuthPrefix, Config) -> - Version = ?config(http_version, Config), - Host = ?config(host, Config), - Port = ?config(port, Config), - Node = ?config(node, Config), - ServerRoot = ?config(server_root, Config), - ok = http_status("GET / ", Config, - [{statuscode, 200}]), - ok = auth_status(auth_request("/", "one", "WrongPassword", Version, Host), Config, - [{statuscode, 200}]), - - %% Make sure Authenticate header is received even the second time - %% we try a incorrect password! Otherwise a browser client will hang! - ok = auth_status(auth_request("/" ++ AuthPrefix ++ "open/", - "dummy", "WrongPassword", Version, Host), Config, - [{statuscode, 401}, - {header, "WWW-Authenticate"}]), - ok = auth_status(auth_request("/" ++ AuthPrefix ++ "open/", "dummy", "WrongPassword", - Version, Host), Config, [{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, AuthPrefix, - "open", "NoPassword", "DummyPassword"), - {error,bad_password} = - add_user(Node, ServerRoot, Port, AuthPrefix, "open", "one", - "onePassword", []), - ok = update_password(Node, ServerRoot, Host, Port, AuthPrefix, "open", - "DummyPassword", "NoPassword"), - - %% Test /*open, require user one Aladdin - remove_users(Node, ServerRoot, Host, Port, AuthPrefix, "open"), - - ok = auth_status(auth_request("/" ++ AuthPrefix ++ "open/", - "one", "onePassword", Version, Host), Config, - [{statuscode, 401}]), - - ok = auth_status(auth_request("/" ++ AuthPrefix ++ "open/", - "two", "twoPassword", Version, Host), Config, - [{statuscode, 401}]), - - ok = auth_status(auth_request("/" ++ AuthPrefix ++ "open/", - "Aladdin", "onePassword", Version, Host), - Config, [{statuscode, 401}]), - - true = add_user(Node, ServerRoot, Port, AuthPrefix, "open", "one", - "onePassword", []), - true = add_user(Node, ServerRoot, Port, AuthPrefix, "open", "two", - "twoPassword", []), - true = add_user(Node, ServerRoot, Port, AuthPrefix, "open", "Aladdin", - "AladdinPassword", []), - {ok, [_|_]} = list_users(Node, ServerRoot, Host, Port, - AuthPrefix, "open"), - ok = auth_status(auth_request("/" ++ AuthPrefix ++ "open/", - "one", "WrongPassword", Version, Host), - Config, [{statuscode, 401}]), - ok = auth_status(auth_request("/" ++ AuthPrefix ++ "open/", - "one", "onePassword", Version, Host), - Config, [{statuscode, 200}]), - ok = auth_status(auth_request("/" ++ AuthPrefix ++ "open/", - "two", "twoPassword", Version, Host), - Config,[{statuscode, 401}]), - ok = auth_status(auth_request("/" ++ AuthPrefix ++ "open/", - "Aladdin", "WrongPassword", Version, Host), - Config,[{statuscode, 401}]), - ok = auth_status(auth_request("/" ++ AuthPrefix ++ "open/", - "Aladdin", "AladdinPassword", Version, Host), - Config, [{statuscode, 200}]), - - remove_users(Node, ServerRoot, Host, Port, AuthPrefix, "open"), - {ok, []} = list_users(Node, ServerRoot, Host, Port, - AuthPrefix, "open"), - - %% Phase 2 - remove_users(Node, ServerRoot, Host, Port, AuthPrefix, "secret"), - {ok, []} = list_users(Node, ServerRoot, Host, Port, AuthPrefix, - "secret"), - ok = auth_status(auth_request("/" ++ AuthPrefix ++ "secret/", - "one", "onePassword", Version, Host), - Config, [{statuscode, 401}]), - ok = auth_status(auth_request("/" ++ AuthPrefix ++ "secret/", - "two", "twoPassword", Version, Host), - Config, [{statuscode, 401}]), - ok = auth_status(auth_request("/" ++ AuthPrefix ++ "secret/", - "three", "threePassword", Version, Host), - Config, [{statuscode, 401}]), - add_user(Node, ServerRoot, Port, AuthPrefix, "secret", "one", - "onePassword", - []), - add_user(Node, ServerRoot, Port, AuthPrefix, "secret", - "two", "twoPassword", []), - add_user(Node, ServerRoot, Port, AuthPrefix, "secret", "Aladdin", - "AladdinPassword",[]), - add_group_member(Node, ServerRoot, Port, AuthPrefix, "secret", - "one", "group1"), - add_group_member(Node, ServerRoot, Port, AuthPrefix, "secret", - "two", "group1"), - add_group_member(Node, ServerRoot, Port, AuthPrefix, - "secret", "Aladdin", "group2"), - ok = auth_status(auth_request("/" ++ AuthPrefix ++ "secret/", - "one", "onePassword", Version, Host), - Config, [{statuscode, 200}]), - ok = auth_status(auth_request("/" ++ AuthPrefix ++ "secret/", - "two", "twoPassword", Version, Host), - Config,[{statuscode, 200}]), - ok = auth_status(auth_request("/" ++ AuthPrefix ++ "secret/", - "Aladdin", "AladdinPassword", Version, Host), - Config, [{statuscode, 200}]), - ok = auth_status(auth_request("/" ++ AuthPrefix ++ "secret/", - "three", "threePassword", Version, Host), - Config, [{statuscode, 401}]), - remove_users(Node, ServerRoot, Host, Port, AuthPrefix, "secret"), - {ok, []} = list_users(Node, ServerRoot, Host, Port, - AuthPrefix, "secret"), - remove_groups(Node, ServerRoot, Host, Port, AuthPrefix, "secret"), - - {ok, []} = list_groups(Node, ServerRoot, Host, Port, AuthPrefix, "secret"), - - %% Phase 3 - remove_users(Node, ServerRoot, Host, Port, AuthPrefix, "secret/top_secret"), - remove_groups(Node, ServerRoot, Host, Port, AuthPrefix, "secret/top_secret"), - - ok = auth_status(auth_request("/" ++ AuthPrefix ++ - "secret/top_secret/", - "three", "threePassword", Version, Host), - Config, [{statuscode, 401}]), - ok = auth_status(auth_request("/" ++ AuthPrefix ++ - "secret/top_secret/", "two", "twoPassword", Version, Host), - Config, [{statuscode, 401}]), - add_user(Node, ServerRoot, Port, AuthPrefix, - "secret/top_secret","three", - "threePassword",[]), - add_user(Node, ServerRoot, Port, AuthPrefix, "secret/top_secret", - "two","twoPassword", []), - add_group_member(Node, ServerRoot, Port, AuthPrefix, "secret/top_secret", "three", "group3"), - ok = auth_status(auth_request("/" ++ AuthPrefix ++ - "secret/top_secret/", "three", "threePassword", - Version, Host), - Config, [{statuscode, 200}]), - ok = auth_status(auth_request("/" ++ AuthPrefix ++ - "secret/top_secret/", "two", "twoPassword", Version, Host), - Config, [{statuscode, 401}]), - add_group_member(Node, ServerRoot, Port, AuthPrefix, "secret/top_secret", "two", "group3"), - ok = auth_status(auth_request("/" ++ AuthPrefix ++ - "secret/top_secret/", - "two", "twoPassword", Version, Host), - Config, [{statuscode, 200}]), - remove_users(Node, ServerRoot, Host, Port, AuthPrefix, "secret/top_secret"), - {ok, []} = list_users(Node, ServerRoot, Host, Port, - AuthPrefix, "secret/top_secret"), - remove_groups(Node, ServerRoot, Host, Port, AuthPrefix, "secret/top_secret"), - {ok, []} = list_groups(Node, ServerRoot, Host, Port, AuthPrefix, "secret/top_secret"), - ok = auth_status(auth_request("/" ++ AuthPrefix ++ - "secret/top_secret/", "two", "twoPassword", Version, Host), - Config, [{statuscode, 401}]), - ok = auth_status(auth_request("/" ++ AuthPrefix ++ - "secret/top_secret/","three", "threePassword", Version, Host), - Config, [{statuscde, 401}]). -%%------------------------------------------------------------------------- -ipv6() -> - [{require, ipv6_hosts}, - {doc,"Test ipv6."}]. -ipv6(Config) when is_list(Config) -> - {ok, Hostname0} = inet:gethostname(), - case lists:member(list_to_atom(Hostname0), ct:get_config(ipv6_hosts)) of - true -> - Version = ?config(http_version, Config), - Host = ?config(host, Config), - URI = http_request("GET / ", Version, Host), - httpd_test_lib:verify_request(?config(type, Config), Host, - ?config(port, Config), [inet6], - ?config(code, Config), - URI, - [{statuscode, 200}, {version, Version}]); - false -> - {skip, "Host does not support IPv6"} - end. - -%%------------------------------------------------------------------------- -htaccess_1_1(Config) when is_list(Config) -> - htaccess([{http_version, "HTTP/1.1"} | Config]). - -htaccess_1_0(Config) when is_list(Config) -> - htaccess([{http_version, "HTTP/1.0"} | Config]). - -htaccess_0_9(Config) when is_list(Config) -> - htaccess([{http_version, "HTTP/0.9"} | Config]). - -htaccess() -> - [{doc, "Test mod_auth API"}]. - -htaccess(Config) when is_list(Config) -> - Version = ?config(http_version, Config), - Host = ?config(host, Config), - Type = ?config(type, Config), - Port = ?config(port, Config), - Node = ?config(node, Config), - %% 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, - http_request("GET /ht/open/ ", Version, Host), - [{statuscode, 401}, - {version, Version}, - {header, "WWW-Authenticate"}]), - ok = httpd_test_lib:verify_request(Type, Host, Port, Node, - http_request("GET /ht/secret/ ", Version, Host), - [{statuscode, 401}, - {version, Version}, - {header, "WWW-Authenticate"}]), - ok = httpd_test_lib:verify_request(Type, Host, Port, Node, - http_request("GET /ht/secret/top_secret/ ", - Version, Host), - [{statuscode, 401}, - {version, Version}, - {header, "WWW-Authenticate"}]), - - %% Make sure Authenticate header is received even the second time - %% we try a incorrect password! Otherwise a browser client will hang! - ok = auth_status(auth_request("/ht/open/", - "dummy", "WrongPassword", Version, Host), Config, - [{statuscode, 401}, - {header, "WWW-Authenticate"}]), - ok = auth_status(auth_request("/ht/open/", - "dummy", "WrongPassword", Version, Host), Config, - [{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] - ok = auth_status(auth_request("/ht/open/dummy.html", "one", "OnePassword", - Version, Host), Config, - [{statuscode, 200}]), - - %% Control the second user - %% Authentication OK and a directory listing is supplied! - %% ["Aladdin:open sesame" user second in user list] - ok = auth_status(auth_request("/ht/open/","Aladdin", - "AladdinPassword", Version, Host), Config, - [{statuscode, 200}]), - - %% Contro that bad passwords and userids get a good denial - %% User correct but wrong password! ["one:one" user first in user list] - ok = auth_status(auth_request("/ht/open/", "one", "one", Version, Host), Config, - [{statuscode, 401}]), - %% Neither user or password correct! ["dummy:dummy"] - ok = auth_status(auth_request("/ht/open/", "dummy", "dummy", Version, Host), Config, - [{statuscode, 401}]), - - %% Control that authetication still works, even if its a member in a group - %% Authentication OK! ["two:TwoPassword" user in first group] - ok = auth_status(auth_request("/ht/secret/dummy.html", "two", - "TwoPassword", Version, Host), Config, - [{statuscode, 200}]), - - %% Authentication OK and a directory listing is supplied! - %% ["three:ThreePassword" user in second group] - ok = auth_status(auth_request("/ht/secret/", "three", - "ThreePassword", Version, Host), Config, - [{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] - ok = auth_status(auth_request("/ht/secret/", "two", "two", Version, Host), Config, - [{statuscode, 401}]), - %% Neither user or password correct! ["dummy:dummy"] - ok = auth_status(auth_request("/ht/secret/", "dummy", "dummy", Version, Host), Config, - [{statuscode, 401}]), - - %% control that we deny the users that are in subnet above the allowed - ok = auth_status(auth_request("/ht/blocknet/dummy.html", "four", - "FourPassword", Version, Host), Config, - [{statuscode, 403}]), - %% Control that we only applies the rules to the right methods - ok = httpd_test_lib:verify_request(Type, Host, Port, Node, - http_request("HEAD /ht/blocknet/dummy.html ", Version, Host), - [{statuscode, head_status(Version)}, - {version, Version}]), - - %% Control that the rerquire directive can be overrideen - ok = auth_status(auth_request("/ht/secret/top_secret/ ", "Aladdin", "AladdinPassword", - Version, Host), Config, - [{statuscode, 401}]), - - %% Authentication still required! - ok = httpd_test_lib:verify_request(Type, Host, Port, Node, - http_request("GET /ht/open/ ", Version, Host), - [{statuscode, 401}, - {version, Version}, - {header, "WWW-Authenticate"}]), - ok = httpd_test_lib:verify_request(Type, Host, Port, Node, - http_request("GET /ht/secret/ ", Version, Host), - [{statuscode, 401}, - {version, Version}, - {header, "WWW-Authenticate"}]), - ok = httpd_test_lib:verify_request(Type, Host, Port, Node, - http_request("GET /ht/secret/top_secret/ ", Version, Host), - [{statuscode, 401}, - {version, Version}, - {header, "WWW-Authenticate"}]). - -%%------------------------------------------------------------------------- -host() -> - [{doc, "Test host header"}]. - -host(Config) when is_list(Config) -> - Cb = ?config(version_cb, Config), - Cb:host(?config(type, Config), ?config(port, Config), - ?config(host, Config), ?config(node, Config)). -%%------------------------------------------------------------------------- -chunked() -> - [{doc, "Check that the server accepts chunked requests."}]. - -chunked(Config) when is_list(Config) -> - httpd_1_1:chunked(?config(type, Config), ?config(port, Config), - ?config(host, Config), ?config(node, Config)). -%%------------------------------------------------------------------------- -expect() -> - ["Check that the server handles request with the expect header " - "field appropiate"]. -expect(Config) when is_list(Config) -> - httpd_1_1:expect(?config(type, Config), ?config(port, Config), - ?config(host, Config), ?config(node, Config)). -%%------------------------------------------------------------------------- -max_clients_1_1() -> - [{doc, "Test max clients limit"}]. - -max_clients_1_1(Config) when is_list(Config) -> - do_max_clients([{http_version, "HTTP/1.1"} | Config]). - -max_clients_1_0() -> - [{doc, "Test max clients limit"}]. - -max_clients_1_0(Config) when is_list(Config) -> - do_max_clients([{http_version, "HTTP/1.0"} | Config]). - -max_clients_0_9() -> - [{doc, "Test max clients limit"}]. - -max_clients_0_9(Config) when is_list(Config) -> - do_max_clients([{http_version, "HTTP/0.9"} | Config]). -%%------------------------------------------------------------------------- -esi() -> - [{doc, "Test mod_esi"}]. - -esi(Config) when is_list(Config) -> - ok = http_status("GET /eval?httpd_example:print(\"Hi!\") ", - Config, [{statuscode, 200}]), - ok = http_status("GET /eval?not_allowed:print(\"Hi!\") ", - Config, [{statuscode, 403}]), - ok = http_status("GET /eval?httpd_example:undef(\"Hi!\") ", - Config, [{statuscode, 500}]), - ok = http_status("GET /cgi-bin/erl/httpd_example ", - Config, [{statuscode, 400}]), - ok = http_status("GET /cgi-bin/erl/httpd_example:get ", - Config, [{statuscode, 200}]), - ok = http_status("GET /cgi-bin/erl/httpd_example:" - "get?input=4711 ", Config, - [{statuscode, 200}]), - ok = http_status("GET /cgi-bin/erl/httpd_example:post ", - Config, [{statuscode, 200}]), - ok = http_status("GET /cgi-bin/erl/not_allowed:post ", - Config, [{statuscode, 403}]), - ok = http_status("GET /cgi-bin/erl/httpd_example:undef ", - Config, [{statuscode, 404}]), - ok = http_status("GET /cgi-bin/erl/httpd_example/yahoo ", - Config, [{statuscode, 302}]), - %% Check "ErlScriptNoCache" directive (default: false) - ok = http_status("GET /cgi-bin/erl/httpd_example:get ", - Config, [{statuscode, 200}, - {no_header, "cache-control"}]). -%%------------------------------------------------------------------------- -cgi() -> - [{doc, "Test mod_cgi"}]. - -cgi(Config) when is_list(Config) -> - {Script, Script2, Script3} = - case test_server:os_type() of - {win32, _} -> - {"printenv.bat", "printenv.sh", "cgi_echo.exe"}; - _ -> - {"printenv.sh", "printenv.bat", "cgi_echo"} - end, - - %%The length (> 100) is intentional - ok = http_status("POST /cgi-bin/" ++ Script3 ++ " ", - {"Content-Length:100 \r\n", - "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" - "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" - "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" - "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" - "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" - "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" - "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" - "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" - "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" - "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" - "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" - "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" - "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" - "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" - "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" - "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" - "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"}, - Config, - [{statuscode, 200}, - {header, "content-type", "text/plain"}]), - - ok = http_status("GET /cgi-bin/"++ Script ++ " ", Config, [{statuscode, 200}]), - - ok = http_status("GET /cgi-bin/not_there ", Config, - [{statuscode, 404}, {statuscode, 500}]), - - ok = http_status("GET /cgi-bin/"++ Script ++ "?Nisse:kkk?sss/lll ", - Config, - [{statuscode, 200}]), - - ok = http_status("POST /cgi-bin/"++ Script ++ " ", Config, - [{statuscode, 200}]), - - ok = http_status("GET /htbin/"++ Script ++ " ", Config, - [{statuscode, 200}]), - - ok = http_status("GET /htbin/not_there ", Config, - [{statuscode, 404},{statuscode, 500}]), - - ok = http_status("GET /htbin/"++ Script ++ "?Nisse:kkk?sss/lll ", Config, - [{statuscode, 200}]), - - ok = http_status("POST /htbin/"++ Script ++ " ", Config, - [{statuscode, 200}]), - - ok = http_status("POST /htbin/"++ Script ++ " ", Config, - [{statuscode, 200}]), - - %% Execute an existing, but bad CGI script.. - ok = http_status("POST /htbin/"++ Script2 ++ " ", Config, - [{statuscode, 404}]), - - ok = http_status("POST /cgi-bin/"++ Script2 ++ " ", Config, - [{statuscode, 404}]), - - %% Check "ScriptNoCache" directive (default: false) - ok = http_status("GET /cgi-bin/" ++ Script ++ " ", Config, - [{statuscode, 200}, - {no_header, "cache-control"}]). -%%------------------------------------------------------------------------- -cgi_chunked_encoding_test() -> - [{doc, "Test chunked encoding together with mod_cgi "}]. -cgi_chunked_encoding_test(Config) when is_list(Config) -> - Host = ?config(host, Config), - Script = - case test_server:os_type() of - {win32, _} -> - "/cgi-bin/printenv.bat"; - _ -> - "/cgi-bin/printenv.sh" - end, - Requests = - ["GET " ++ Script ++ " HTTP/1.1\r\nHost:"++ Host ++"\r\n\r\n", - "GET /cgi-bin/erl/httpd_example/newformat HTTP/1.1\r\nHost:" - ++ Host ++"\r\n\r\n"], - httpd_1_1:mod_cgi_chunked_encoding_test(?config(type, Config), ?config(port, Config), - Host, - ?config(node, Config), - Requests). -%%------------------------------------------------------------------------- -alias_1_1() -> - [{doc, "Test mod_alias"}]. - -alias_1_1(Config) when is_list(Config) -> - alias([{http_version, "HTTP/1.1"} | Config]). - -alias_1_0() -> - [{doc, "Test mod_alias"}]. - -alias_1_0(Config) when is_list(Config) -> - alias([{http_version, "HTTP/1.0"} | Config]). - -alias_0_9() -> - [{doc, "Test mod_alias"}]. - -alias_0_9(Config) when is_list(Config) -> - alias([{http_version, "HTTP/0.9"} | Config]). - -alias() -> - [{doc, "Test mod_alias"}]. - -alias(Config) when is_list(Config) -> - ok = http_status("GET /pics/icon.sheet.gif ", Config, - [{statuscode, 200}, - {header, "Content-Type","image/gif"}, - {header, "Server"}, - {header, "Date"}]), - - ok = http_status("GET / ", Config, - [{statuscode, 200}, - {header, "Content-Type","text/html"}, - {header, "Server"}, - {header, "Date"}]), - - ok = http_status("GET /misc/ ", Config, - [{statuscode, 200}, - {header, "Content-Type","text/html"}, - {header, "Server"}, - {header, "Date"}]), - - %% Check redirection if trailing slash is missing. - ok = http_status("GET /misc ", Config, - [{statuscode, 301}, - {header, "Location"}, - {header, "Content-Type","text/html"}]). -%%------------------------------------------------------------------------- -actions() -> - [{doc, "Test mod_actions"}]. - -actions(Config) when is_list(Config) -> - ok = http_status("GET /", Config, [{statuscode, 200}]). - -%%------------------------------------------------------------------------- -range() -> - [{doc, "Test Range header"}]. - -range(Config) when is_list(Config) -> - httpd_1_1:range(?config(type, Config), ?config(port, Config), - ?config(host, Config), ?config(node, Config)). - -%%------------------------------------------------------------------------- -if_modified_since() -> - [{doc, "Test If-Modified-Since header"}]. - -if_modified_since(Config) when is_list(Config) -> - httpd_1_1:if_test(?config(type, Config), ?config(port, Config), - ?config(host, Config), ?config(node, Config), - ?config(doc_root, Config)). -%%------------------------------------------------------------------------- -trace() -> - [{doc, "Test TRACE method"}]. - -trace(Config) when is_list(Config) -> - Cb = ?config(version_cb, Config), - Cb:trace(?config(type, Config), ?config(port, Config), - ?config(host, Config), ?config(node, Config)). -%%------------------------------------------------------------------------- -light() -> - ["Test light load"]. -light(Config) when is_list(Config) -> - httpd_load:load_test(?config(type, Config), ?config(port, Config), ?config(host, Config), - ?config(node, Config), 10). -%%------------------------------------------------------------------------- -medium() -> - ["Test medium load"]. -medium(Config) when is_list(Config) -> - httpd_load:load_test(?config(type, Config), ?config(port, Config), ?config(host, Config), - ?config(node, Config), 100). -%%------------------------------------------------------------------------- -heavy() -> - ["Test heavy load"]. -heavy(Config) when is_list(Config) -> - httpd_load:load_test(?config(type, Config), ?config(port, Config), ?config(host, Config), - ?config(node, Config), - 1000). -%%------------------------------------------------------------------------- -content_length() -> - ["Tests that content-length is correct OTP-5775"]. -content_length(Config) -> - Version = ?config(http_version, Config), - Host = ?config(host, Config), - ok = httpd_test_lib:verify_request(?config(type, Config), Host, - ?config(port, Config), ?config(node, Config), - http_request("GET /cgi-bin/erl/httpd_example:get_bin ", - Version, Host), - [{statuscode, 200}, - {content_length, 274}, - {version, Version}]). -%%------------------------------------------------------------------------- -bad_hex() -> - ["Tests that a URI with a bad hexadecimal code is handled OTP-6003"]. -bad_hex(Config) -> - Version = ?config(http_version, Config), - Host = ?config(host, Config), - ok = httpd_test_lib:verify_request(?config(type, Config), Host, - ?config(port, Config), ?config(node, Config), - http_request("GET http://www.erlang.org/%skalle ", - Version, Host), - [{statuscode, 400}, - {version, Version}]). -%%------------------------------------------------------------------------- -missing_CR() -> - ["Tests missing CR in delimiter OTP-7304"]. -missing_CR(Config) -> - Version = ?config(http_version, Config), - Host = ?config(host, Config), - ok = httpd_test_lib:verify_request(?config(type, Config), Host, - ?config(port, Config), ?config(node, Config), - http_request_missing_CR("GET /index.html ", Version, Host), - [{statuscode, 200}, - {version, Version}]). - -%%------------------------------------------------------------------------- -max_header() -> - ["Denial Of Service (DOS) attack, prevented by max_header"]. -max_header(Config) when is_list(Config) -> - Version = ?config(http_version, Config), - Host = ?config(host, Config), - case Version of - "HTTP/0.9" -> - {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]). - -security_1_0(Config) when is_list(Config) -> - security([{http_version, "HTTP/1.0"} | Config]). - -security() -> - ["Test mod_security"]. -security(Config) -> - Version = ?config(http_version, Config), - Host = ?config(host, Config), - Port = ?config(port, Config), - Node = ?config(node, Config), - ServerRoot = ?config(server_root, Config), - - global:register_name(mod_security_test, self()), % Receive events - - test_server: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"), - - ok = auth_status(auth_request("/open/", - "one", "onePassword", Version, Host), Config, - [{statuscode, 401}]), - - receive_security_event({event, auth_fail, Port, OpenDir, - [{user, "one"}, {password, "onePassword"}]}, - Node, Port), - - ok = auth_status(auth_request("/open/", - "two", "twoPassword", Version, Host), Config, - [{statuscode, 401}]), - - receive_security_event({event, auth_fail, Port, OpenDir, - [{user, "two"}, {password, "twoPassword"}]}, - Node, Port), - - ok = auth_status(auth_request("/open/", - "Aladdin", "AladdinPassword", Version, Host), - Config, [{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", []), - - ok = auth_status(auth_request("/open/", "one", "WrongPassword", Version, Host), Config, - [{statuscode, 401}]), - - receive_security_event({event, auth_fail, Port, OpenDir, - [{user, "one"}, {password, "WrongPassword"}]}, - Node, Port), - - ok = auth_status(auth_request("/open/", "one", "WrongPassword", Version, Host), Config, - [{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. - - ok = auth_status(auth_request("/open/", "one", "WrongPassword", Version, Host), Config, - [{statuscode, 401}]), - - %% User "one" should be blocked now.. - case list_blocked_users(Node, Port) of - [{"one",_, Port, OpenDir,_}] -> - ok; - Blocked -> - ct:fail({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), - - ok = auth_status(auth_request("/open/", "one", "onePassword", Version, Host), Config, - [{statuscode, 200}]), - - %% Test list_auth_users & auth_timeout - - ["one"] = list_auth_users(Node, Port), - - ok = auth_status(auth_request("/open/", "two", "onePassword", Version, Host), Config, - [{statuscode, 401}]), - - ["one"] = list_auth_users(Node, Port), - - - ["one"] = list_auth_users(Node, Port, OpenDir), - - - ok = auth_status(auth_request("/open/", "two", "twoPassword", Version, Host), Config, - [{statuscode, 401}]), - - ["one"] = list_auth_users(Node, Port), - - - ["one"] = list_auth_users(Node, Port, OpenDir), - - %% Wait for successful auth to timeout. - test_server: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), - - ok = auth_status(auth_request("/open/", "two", "twoPassword", Version, Host), 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), - Port = ?config(port, Config), - Type = ?config(type, Config), - - Request = http_request("GET /index.html ", Version, Host), - 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_test_lib:verify_request(Type, Host, - Port, - transport_opts(Type, Config), - ?config(node, Config), - Request, - [{statuscode, 503}, - {version, Version}]), - receive - {_, Socket, _Msg} -> - ok - end, - inets_test_lib:close(Type, Socket), - ct:sleep(100), %% Avoid possible timing issues - ok = httpd_test_lib:verify_request(Type, Host, - Port, - transport_opts(Type, Config), - ?config(node, Config), - Request, - [{statuscode, 200}, - {version, Version}]). - -setup_server_dirs(ServerRoot, DocRoot, DataDir) -> - CgiDir = filename:join(ServerRoot, "cgi-bin"), - AuthDir = filename:join(ServerRoot, "auth"), - PicsDir = filename:join(ServerRoot, "icons"), - ConfigDir = filename:join(ServerRoot, "config"), - - ok = file:make_dir(ServerRoot), - ok = file:make_dir(DocRoot), - ok = file:make_dir(CgiDir), - ok = file:make_dir(AuthDir), - ok = file:make_dir(PicsDir), - ok = file:make_dir(ConfigDir), - - DocSrc = filename:join(DataDir, "server_root/htdocs"), - AuthSrc = filename:join(DataDir, "server_root/auth"), - CgiSrc = filename:join(DataDir, "server_root/cgi-bin"), - PicsSrc = filename:join(DataDir, "server_root/icons"), - ConfigSrc = filename:join(DataDir, "server_root/config"), - - inets_test_lib:copy_dirs(DocSrc, DocRoot), - inets_test_lib:copy_dirs(AuthSrc, AuthDir), - inets_test_lib:copy_dirs(CgiSrc, CgiDir), - inets_test_lib:copy_dirs(PicsSrc, PicsDir), - inets_test_lib:copy_dirs(ConfigSrc, ConfigDir), - - Cgi = case test_server:os_type() of - {win32, _} -> - "cgi_echo.exe"; - _ -> - "cgi_echo" - end, - - inets_test_lib:copy_file(Cgi, DataDir, CgiDir), - AbsCgi = filename:join([CgiDir, Cgi]), - {ok, FileInfo} = file:read_file_info(AbsCgi), - ok = file:write_file_info(AbsCgi, FileInfo#file_info{mode = 8#00755}), - - EnvCGI = filename:join([ServerRoot, "cgi-bin", "printenv.sh"]), - {ok, FileInfo1} = file:read_file_info(EnvCGI), - ok = file:write_file_info(EnvCGI, - FileInfo1#file_info{mode = 8#00755}). - -start_apps(Group) when Group == https_basic; - Group == https_limit; - Group == https_basic_auth; - Group == https_auth_api; - Group == https_auth_api_dets; - Group == https_auth_api_mnesia; - Group == http_htaccess; - 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; - Group == http_basic_auth; - Group == http_auth_api; - Group == http_auth_api_dets; - Group == http_auth_api_mnesia; - Group == https_htaccess; - Group == https_security; - Group == https_reload; - Group == http_mime_types-> + {custom, [], [customize]}, inets_test_lib:start_apps([inets]). server_start(_, HttpdConfig) -> @@ -1390,6 +148,10 @@ server_config(http_limit, Config) -> [{max_clients, 1}, %% Make sure option checking code is run {max_content_length, 100000002}] ++ server_config(http, Config); +server_config(http_custom, Config) -> + [{custom, ?MODULE}] ++ server_config(http, Config); +server_config(https_custom, Config) -> + [{custom, ?MODULE}] ++ server_config(https, Config); server_config(https_limit, Config) -> [{max_clients, 1}] ++ server_config(https, Config); server_config(http_basic_auth, Config) -> diff --git a/lib/ssh/doc/src/notes.xml b/lib/ssh/doc/src/notes.xml index 579a3ae4a8..c77ee1e77a 100644 --- a/lib/ssh/doc/src/notes.xml +++ b/lib/ssh/doc/src/notes.xml @@ -29,6 +29,33 @@ <file>notes.xml</file> </header> +<section><title>Ssh 3.2.4</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Gracefully terminate if sockets is unexpectedly closed.</p> + <p> + Own Id: OTP-12782</p> + </item> + <item> + <p> + Made Codenomicon Defensics test suite pass: <list> + <item>limit number of algorithms in kexinit + message</item> <item>check 'e' and 'f' parameters in + kexdh</item> <item>implement 'keyboard-interactive' user + authentication on server side</item> <item> return plain + text message to bad version exchange message</item> + </list></p> + <p> + Own Id: OTP-12784</p> + </item> + </list> + </section> + +</section> + <section><title>Ssh 3.2.3</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 57f7ae8b5e..10b526ba28 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -360,7 +360,13 @@ handle_option([{exec, _} = Opt | Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{auth_methods, _} = Opt | Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); +<<<<<<< HEAD handle_option([{preferred_algorithms,_} = Opt | Rest], SocketOptions, SshOptions) -> +======= +handle_option([{auth_method_kb_interactive_data, _} = Opt | Rest], SocketOptions, SshOptions) -> + handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); +handle_option([{pref_public_key_algs, _} = Opt | Rest], SocketOptions, SshOptions) -> +>>>>>>> maint handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); handle_option([{quiet_mode, _} = Opt|Rest], SocketOptions, SshOptions) -> handle_option(Rest, SocketOptions, [handle_ssh_option(Opt) | SshOptions]); @@ -429,6 +435,13 @@ handle_ssh_option({exec, Function} = Opt) when is_function(Function) -> Opt; handle_ssh_option({auth_methods, Value} = Opt) when is_list(Value) -> Opt; +handle_ssh_option({auth_method_kb_interactive_data, {Name,Instruction,Prompt,Echo}} = Opt) when is_list(Name), + is_list(Instruction), + is_list(Prompt), + is_boolean(Echo) -> + Opt; +handle_ssh_option({auth_method_kb_interactive_data, F} = Opt) when is_function(F,3) -> + Opt; handle_ssh_option({infofun, Value} = Opt) when is_function(Value) -> Opt; handle_ssh_option({connectfun, Value} = Opt) when is_function(Value) -> diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl index 197808754c..df9a97c8f8 100644 --- a/lib/ssh/src/ssh_auth.erl +++ b/lib/ssh/src/ssh_auth.erl @@ -243,6 +243,54 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User, handle_userauth_request(#ssh_msg_userauth_request{user = User, service = "ssh-connection", + method = "keyboard-interactive", + data = _}, + _, #ssh{opts = Opts} = Ssh) -> + %% RFC4256 + %% The data field contains: + %% - language tag (deprecated). If =/=[] SHOULD use it however. We skip + %% it for simplicity. + %% - submethods. "... the user can give a hint of which actual methods + %% he wants to use. ...". It's a "MAY use" so we skip + %% it. It also needs an understanding between the client + %% and the server. + %% + %% "The server MUST reply with an SSH_MSG_USERAUTH_SUCCESS, + %% SSH_MSG_USERAUTH_FAILURE, or SSH_MSG_USERAUTH_INFO_REQUEST message." + Default = {"SSH server", + "Enter password for \""++User++"\"", + "pwd: ", + false}, + + {Name, Instruction, Prompt, Echo} = + case proplists:get_value(auth_method_kb_interactive_data, Opts) of + undefined -> + Default; + {_,_,_,_}=V -> + V; + F when is_function(F) -> + {_,PeerName} = Ssh#ssh.peer, + F(PeerName, User, "ssh-connection") + end, + EchoEnc = case Echo of + true -> <<?TRUE>>; + false -> <<?FALSE>> + end, + Msg = #ssh_msg_userauth_info_request{name = unicode:characters_to_list(Name), + instruction = unicode:characters_to_list(Instruction), + language_tag = "", + num_prompts = 1, + data = <<?STRING(unicode:characters_to_binary(Prompt)), + EchoEnc/binary + >> + }, + {not_authorized, {User, undefined}, + ssh_transport:ssh_packet(Msg, Ssh#ssh{user = User, + opts = [{max_kb_tries,3},{kb_userauth_info_msg,Msg}|Opts] + })}; + +handle_userauth_request(#ssh_msg_userauth_request{user = User, + service = "ssh-connection", method = Other}, _, #ssh{userauth_supported_methods = Methods} = Ssh) -> {not_authorized, {User, {authmethod, Other}}, @@ -264,6 +312,38 @@ handle_userauth_info_request( #ssh_msg_userauth_info_response{num_responses = NumPrompts, data = Responses}, Ssh)}. +handle_userauth_info_response(#ssh_msg_userauth_info_response{num_responses = 1, + data = <<?UINT32(Sz), Password:Sz/binary>>}, + #ssh{opts = Opts0, + user = User} = Ssh) -> + NumTriesLeft = proplists:get_value(max_kb_tries, Opts0, 0) - 1, + Opts = lists:keydelete(max_kb_tries,1,Opts0), + case check_password(User, unicode:characters_to_list(Password), Opts) of + true -> + {authorized, User, + ssh_transport:ssh_packet(#ssh_msg_userauth_success{}, Ssh)}; + false when NumTriesLeft > 0 -> + UserAuthInfoMsg = + (proplists:get_value(kb_userauth_info_msg,Opts)) + #ssh_msg_userauth_info_request{name = "", + instruction = + lists:concat( + ["Bad user or password, try again. ", + integer_to_list(NumTriesLeft), + " tries left."])}, + {not_authorized, {User, undefined}, + ssh_transport:ssh_packet(UserAuthInfoMsg, + Ssh#ssh{opts = [{max_kb_tries,NumTriesLeft}|Opts]})}; + + false -> + {not_authorized, {User, {error,"Bad user or password"}}, + ssh_transport:ssh_packet(#ssh_msg_userauth_failure{ + authentications = "", + partial_success = false}, + Ssh#ssh{opts = lists:keydelete(kb_userauth_info_msg,1,Opts)} + )} + end; + handle_userauth_info_response(#ssh_msg_userauth_info_response{}, _Auth) -> throw(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_SERVICE_NOT_AVAILABLE, diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index ca63d2194f..3bdca4ba94 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -333,22 +333,25 @@ info(ConnectionHandler, ChannelProcess) -> hello(socket_control, #state{socket = Socket, ssh_params = Ssh} = State) -> VsnMsg = ssh_transport:hello_version_msg(string_version(Ssh)), send_msg(VsnMsg, State), - {ok, [{recbuf, Size}]} = inet:getopts(Socket, [recbuf]), - inet:setopts(Socket, [{packet, line}, {active, once}, {recbuf, ?MAX_PROTO_VERSION}]), - {next_state, hello, State#state{recbuf = Size}}; + case getopt(recbuf, Socket) of + {ok, Size} -> + inet:setopts(Socket, [{packet, line}, {active, once}, {recbuf, ?MAX_PROTO_VERSION}]), + {next_state, hello, State#state{recbuf = Size}}; + {error, Reason} -> + {stop, {shutdown, Reason}, State} + end; hello({info_line, _Line},#state{role = client, socket = Socket} = State) -> %% The server may send info lines before the version_exchange inet:setopts(Socket, [{active, once}]), {next_state, hello, State}; -hello({info_line, _Line},#state{role = server} = State) -> - DisconnectMsg = - #ssh_msg_disconnect{code = - ?SSH_DISCONNECT_PROTOCOL_ERROR, - description = "Did not receive expected protocol version exchange", - language = "en"}, - handle_disconnect(DisconnectMsg, State); +hello({info_line, _Line},#state{role = server, + socket = Socket, + transport_cb = Transport } = State) -> + %% as openssh + Transport:send(Socket, "Protocol mismatch."), + {stop, {shutdown,"Protocol mismatch in version exchange."}, State}; hello({version_exchange, Version}, #state{ssh_params = Ssh0, socket = Socket, @@ -501,10 +504,21 @@ userauth(#ssh_msg_userauth_info_request{} = Msg, {next_state, userauth, next_packet(State#state{ssh_params = Ssh})}; userauth(#ssh_msg_userauth_info_response{} = Msg, - #state{ssh_params = #ssh{role = server} = Ssh0} = State) -> - {ok, {Reply, Ssh}} = ssh_auth:handle_userauth_info_response(Msg, Ssh0), - send_msg(Reply, State), - {next_state, userauth, next_packet(State#state{ssh_params = Ssh})}; + #state{ssh_params = #ssh{role = server, + peer = {_, Address}} = Ssh0, + opts = Opts, starter = Pid} = State) -> + case ssh_auth:handle_userauth_info_response(Msg, Ssh0) of + {authorized, User, {Reply, Ssh}} -> + send_msg(Reply, State), + Pid ! ssh_connected, + connected_fun(User, Address, "keyboard-interactive", Opts), + {next_state, connected, + next_packet(State#state{auth_user = User, ssh_params = Ssh})}; + {not_authorized, {User, Reason}, {Reply, Ssh}} -> + retry_fun(User, Address, Reason, Opts), + send_msg(Reply, State), + {next_state, userauth, next_packet(State#state{ssh_params = Ssh})} + end; userauth(#ssh_msg_userauth_success{}, #state{ssh_params = #ssh{role = client} = Ssh, starter = Pid} = State) -> @@ -1763,3 +1777,12 @@ start_timeout(_,_, infinity) -> ok; start_timeout(Channel, From, Time) -> erlang:send_after(Time, self(), {timeout, {Channel, From}}). + +getopt(Opt, Socket) -> + case inet:getopts(Socket, [Opt]) of + {ok, [{Opt, Value}]} -> + {ok, Value}; + Other -> + {error, {unexpected_getopts_return, Other}} + end. + diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl index 7162d18b19..ea9bca2390 100644 --- a/lib/ssh/src/ssh_transport.erl +++ b/lib/ssh/src/ssh_transport.erl @@ -585,10 +585,15 @@ alg_final(SSH0) -> {ok,SSH6} = decompress_final(SSH5), SSH6. -select_all(CL, SL) -> +select_all(CL, SL) when length(CL) + length(SL) < 50 -> A = CL -- SL, %% algortihms only used by client %% algorithms used by client and server (client pref) - lists:map(fun(ALG) -> list_to_atom(ALG) end, (CL -- A)). + lists:map(fun(ALG) -> list_to_atom(ALG) end, (CL -- A)); +select_all(_CL, _SL) -> + throw(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_PROTOCOL_ERROR, + description = "Too many algorithms", + language = "en"}). + select([], []) -> none; diff --git a/lib/ssl/doc/src/notes.xml b/lib/ssl/doc/src/notes.xml index 352563700b..fe0606b1a3 100644 --- a/lib/ssl/doc/src/notes.xml +++ b/lib/ssl/doc/src/notes.xml @@ -25,7 +25,23 @@ <file>notes.xml</file> </header> <p>This document describes the changes made to the SSL application.</p> - <section><title>SSL 6.0</title> + <section><title>SSL 6.0.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Terminate gracefully when receving bad input to premaster + secret calculation</p> + <p> + Own Id: OTP-12783</p> + </item> + </list> + </section> + +</section> + +<section><title>SSL 6.0</title> <section><title>Fixed Bugs and Malfunctions</title> <list> diff --git a/lib/ssl/src/ssl.appup.src b/lib/ssl/src/ssl.appup.src index 1476336039..d100e41930 100644 --- a/lib/ssl/src/ssl.appup.src +++ b/lib/ssl/src/ssl.appup.src @@ -1,14 +1,16 @@ %% -*- erlang -*- {"%VSN%", [ - {<<"6\\..*">>, [{restart_application, ssl}]}, - {<<"5\\..*">>, [{restart_application, ssl}]}, + {<<"6.0">>, [{load_module, ssl_handshake, soft_purge, soft_purge, []}]}, + {<<"5\\.3\\.[1-7]($|\\..*)">>, [{restart_application, ssl}]}, + {<<"5\\.[0-2]($|\\..*)">>, [{restart_application, ssl}]}, {<<"4\\..*">>, [{restart_application, ssl}]}, {<<"3\\..*">>, [{restart_application, ssl}]} ], [ - {<<"6\\..*">>, [{restart_application, ssl}]}, - {<<"5\\..*">>, [{restart_application, ssl}]}, + {<<"6.0">>, [{load_module, ssl_handshake, soft_purge, soft_purge, []}]}, + {<<"5\\.3\\.[1-7]($|\\..*)">>, [{restart_application, ssl}]}, + {<<"5\\.[0-2]($|\\..*)">>, [{restart_application, ssl}]}, {<<"4\\..*">>, [{restart_application, ssl}]}, {<<"3\\..*">>, [{restart_application, ssl}]} ] diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl index b538fefe53..12a17cb6ac 100644 --- a/lib/ssl/src/ssl_handshake.erl +++ b/lib/ssl/src/ssl_handshake.erl @@ -476,19 +476,27 @@ update_handshake_history({Handshake0, _Prev}, Data) -> %% end. premaster_secret(OtherPublicDhKey, MyPrivateKey, #'DHParameter'{} = Params) -> - public_key:compute_key(OtherPublicDhKey, MyPrivateKey, Params); - + try + public_key:compute_key(OtherPublicDhKey, MyPrivateKey, Params) + catch + error:computation_failed -> + throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)) + end; premaster_secret(PublicDhKey, PrivateDhKey, #server_dh_params{dh_p = Prime, dh_g = Base}) -> - crypto:compute_key(dh, PublicDhKey, PrivateDhKey, [Prime, Base]); + try + crypto:compute_key(dh, PublicDhKey, PrivateDhKey, [Prime, Base]) + catch + error:computation_failed -> + throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)) + end; premaster_secret(#client_srp_public{srp_a = ClientPublicKey}, ServerKey, #srp_user{prime = Prime, verifier = Verifier}) -> case crypto:compute_key(srp, ClientPublicKey, ServerKey, {host, [Verifier, Prime, '6a']}) of error -> - ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER); + throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)); PremasterSecret -> PremasterSecret end; - premaster_secret(#server_srp_params{srp_n = Prime, srp_g = Generator, srp_s = Salt, srp_b = Public}, ClientKeys, {Username, Password}) -> case ssl_srp_primes:check_srp_params(Generator, Prime) of @@ -496,21 +504,19 @@ premaster_secret(#server_srp_params{srp_n = Prime, srp_g = Generator, srp_s = Sa DerivedKey = crypto:hash(sha, [Salt, crypto:hash(sha, [Username, <<$:>>, Password])]), case crypto:compute_key(srp, Public, ClientKeys, {user, [DerivedKey, Prime, Generator, '6a']}) of error -> - ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER); + throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)); PremasterSecret -> PremasterSecret end; _ -> - ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER) + throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)) end; - premaster_secret(#client_rsa_psk_identity{ identity = PSKIdentity, exchange_keys = #encrypted_premaster_secret{premaster_secret = EncPMS} }, #'RSAPrivateKey'{} = Key, PSKLookup) -> PremasterSecret = premaster_secret(EncPMS, Key), psk_secret(PSKIdentity, PSKLookup, PremasterSecret); - premaster_secret(#server_dhe_psk_params{ hint = IdentityHint, dh_params = #server_dh_params{dh_y = PublicDhKey} = Params}, @@ -518,7 +524,6 @@ premaster_secret(#server_dhe_psk_params{ LookupFun) -> PremasterSecret = premaster_secret(PublicDhKey, PrivateDhKey, Params), psk_secret(IdentityHint, LookupFun, PremasterSecret); - premaster_secret({rsa_psk, PSKIdentity}, PSKLookup, RSAPremasterSecret) -> psk_secret(PSKIdentity, PSKLookup, RSAPremasterSecret). @@ -527,13 +532,10 @@ premaster_secret(#client_dhe_psk_identity{ dh_public = PublicDhKey}, PrivateKey, #'DHParameter'{} = Params, PSKLookup) -> PremasterSecret = premaster_secret(PublicDhKey, PrivateKey, Params), psk_secret(PSKIdentity, PSKLookup, PremasterSecret). - premaster_secret(#client_psk_identity{identity = PSKIdentity}, PSKLookup) -> psk_secret(PSKIdentity, PSKLookup); - premaster_secret({psk, PSKIdentity}, PSKLookup) -> psk_secret(PSKIdentity, PSKLookup); - premaster_secret(#'ECPoint'{} = ECPoint, #'ECPrivateKey'{} = ECDHKeys) -> public_key:compute_key(ECPoint, ECDHKeys); premaster_secret(EncSecret, #'RSAPrivateKey'{} = RSAPrivateKey) -> @@ -2036,7 +2038,7 @@ psk_secret(PSKIdentity, PSKLookup) -> #alert{} = Alert -> Alert; _ -> - ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER) + throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)) end. psk_secret(PSKIdentity, PSKLookup, PremasterSecret) -> @@ -2048,7 +2050,7 @@ psk_secret(PSKIdentity, PSKLookup, PremasterSecret) -> #alert{} = Alert -> Alert; _ -> - ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER) + throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)) end. handle_psk_identity(_PSKIdentity, LookupFun) diff --git a/otp_versions.table b/otp_versions.table index a2e36e6377..50a77237ff 100644 --- a/otp_versions.table +++ b/otp_versions.table @@ -1,3 +1,4 @@ +OTP-17.5.6 : inets-5.10.9 ssh-3.2.4 ssl-6.0.1 # asn1-3.0.4 common_test-1.10.1 compiler-5.0.4 cosEvent-2.1.15 cosEventDomain-1.1.14 cosFileTransfer-1.1.16 cosNotification-1.1.21 cosProperty-1.1.17 cosTime-1.1.14 cosTransactions-1.2.14 crypto-3.5 debugger-4.0.3 dialyzer-2.7.4 diameter-1.9.2 edoc-0.7.16 eldap-1.1.1 erl_docgen-0.3.7 erl_interface-3.7.20 erts-6.4.1 et-1.5 eunit-2.2.9 gs-1.5.16 hipe-3.11.3 ic-4.3.6 jinterface-1.5.12 kernel-3.2 megaco-3.17.3 mnesia-4.12.5 observer-2.0.4 odbc-2.10.22 orber-3.7.1 os_mon-2.3.1 ose-1.0.2 otp_mibs-1.0.10 parsetools-2.0.12 percept-0.8.10 public_key-0.23 reltool-0.6.6 runtime_tools-1.8.16 sasl-2.4.1 snmp-5.1.2 stdlib-2.4 syntax_tools-1.6.18 test_server-3.8.1 tools-2.7.2 typer-0.9.8 webtool-0.8.10 wx-1.3.3 xmerl-1.3.7 : OTP-17.5.5 : diameter-1.9.2 # asn1-3.0.4 common_test-1.10.1 compiler-5.0.4 cosEvent-2.1.15 cosEventDomain-1.1.14 cosFileTransfer-1.1.16 cosNotification-1.1.21 cosProperty-1.1.17 cosTime-1.1.14 cosTransactions-1.2.14 crypto-3.5 debugger-4.0.3 dialyzer-2.7.4 edoc-0.7.16 eldap-1.1.1 erl_docgen-0.3.7 erl_interface-3.7.20 erts-6.4.1 et-1.5 eunit-2.2.9 gs-1.5.16 hipe-3.11.3 ic-4.3.6 inets-5.10.8 jinterface-1.5.12 kernel-3.2 megaco-3.17.3 mnesia-4.12.5 observer-2.0.4 odbc-2.10.22 orber-3.7.1 os_mon-2.3.1 ose-1.0.2 otp_mibs-1.0.10 parsetools-2.0.12 percept-0.8.10 public_key-0.23 reltool-0.6.6 runtime_tools-1.8.16 sasl-2.4.1 snmp-5.1.2 ssh-3.2.3 ssl-6.0 stdlib-2.4 syntax_tools-1.6.18 test_server-3.8.1 tools-2.7.2 typer-0.9.8 webtool-0.8.10 wx-1.3.3 xmerl-1.3.7 : OTP-17.5.4 : inets-5.10.8 ssh-3.2.3 # asn1-3.0.4 common_test-1.10.1 compiler-5.0.4 cosEvent-2.1.15 cosEventDomain-1.1.14 cosFileTransfer-1.1.16 cosNotification-1.1.21 cosProperty-1.1.17 cosTime-1.1.14 cosTransactions-1.2.14 crypto-3.5 debugger-4.0.3 dialyzer-2.7.4 diameter-1.9.1 edoc-0.7.16 eldap-1.1.1 erl_docgen-0.3.7 erl_interface-3.7.20 erts-6.4.1 et-1.5 eunit-2.2.9 gs-1.5.16 hipe-3.11.3 ic-4.3.6 jinterface-1.5.12 kernel-3.2 megaco-3.17.3 mnesia-4.12.5 observer-2.0.4 odbc-2.10.22 orber-3.7.1 os_mon-2.3.1 ose-1.0.2 otp_mibs-1.0.10 parsetools-2.0.12 percept-0.8.10 public_key-0.23 reltool-0.6.6 runtime_tools-1.8.16 sasl-2.4.1 snmp-5.1.2 ssl-6.0 stdlib-2.4 syntax_tools-1.6.18 test_server-3.8.1 tools-2.7.2 typer-0.9.8 webtool-0.8.10 wx-1.3.3 xmerl-1.3.7 : OTP-17.5.3 : common_test-1.10.1 diameter-1.9.1 erts-6.4.1 snmp-5.1.2 test_server-3.8.1 # asn1-3.0.4 compiler-5.0.4 cosEvent-2.1.15 cosEventDomain-1.1.14 cosFileTransfer-1.1.16 cosNotification-1.1.21 cosProperty-1.1.17 cosTime-1.1.14 cosTransactions-1.2.14 crypto-3.5 debugger-4.0.3 dialyzer-2.7.4 edoc-0.7.16 eldap-1.1.1 erl_docgen-0.3.7 erl_interface-3.7.20 et-1.5 eunit-2.2.9 gs-1.5.16 hipe-3.11.3 ic-4.3.6 inets-5.10.7 jinterface-1.5.12 kernel-3.2 megaco-3.17.3 mnesia-4.12.5 observer-2.0.4 odbc-2.10.22 orber-3.7.1 os_mon-2.3.1 ose-1.0.2 otp_mibs-1.0.10 parsetools-2.0.12 percept-0.8.10 public_key-0.23 reltool-0.6.6 runtime_tools-1.8.16 sasl-2.4.1 ssh-3.2.2 ssl-6.0 stdlib-2.4 syntax_tools-1.6.18 tools-2.7.2 typer-0.9.8 webtool-0.8.10 wx-1.3.3 xmerl-1.3.7 : |