diff options
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/eldap/test/Makefile | 5 | ||||
| -rw-r--r-- | lib/eldap/test/eldap_basic_SUITE.erl | 1114 | ||||
| -rw-r--r-- | lib/eldap/test/eldap_basic_SUITE_data/RAND | bin | 0 -> 512 bytes | |||
| -rw-r--r-- | lib/eldap/test/eldap_connections_SUITE.erl | 147 | ||||
| -rw-r--r-- | lib/eldap/test/eldap_misc_SUITE.erl | 51 | ||||
| -rw-r--r-- | lib/eldap/test/make_certs.erl | 357 | 
6 files changed, 1101 insertions, 573 deletions
diff --git a/lib/eldap/test/Makefile b/lib/eldap/test/Makefile index 24e71cebaa..28a7a107e1 100644 --- a/lib/eldap/test/Makefile +++ b/lib/eldap/test/Makefile @@ -28,8 +28,9 @@ INCLUDES= -I. -I ../include  # ----------------------------------------------------  MODULES= \ -	eldap_connections_SUITE \ -	eldap_basic_SUITE +	eldap_basic_SUITE \ +	make_certs +  ERL_FILES= $(MODULES:%=%.erl) diff --git a/lib/eldap/test/eldap_basic_SUITE.erl b/lib/eldap/test/eldap_basic_SUITE.erl index d87f3ac4ac..806048258f 100644 --- a/lib/eldap/test/eldap_basic_SUITE.erl +++ b/lib/eldap/test/eldap_basic_SUITE.erl @@ -1,7 +1,7 @@  %%  %% %CopyrightBegin%  %% -%% Copyright Ericsson AB 2012-2013. All Rights Reserved. +%% Copyright Ericsson AB 2012-2014. 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 @@ -24,305 +24,893 @@  %%-include_lib("common_test/include/ct.hrl").  -include_lib("test_server/include/test_server.hrl").  -include_lib("eldap/include/eldap.hrl"). +-include_lib("eldap/ebin/ELDAPv3.hrl"). +  -define(TIMEOUT, 120000). % 2 min +all() -> +    [app, +     appup, +     {group, encode_decode}, +     {group, v4_connections}, +     {group, v6_connections}, +     {group, plain_api}, +     {group, ssl_api}, +     {group, start_tls_api} +    ]. + +groups() -> +    [{encode_decode, [], [encode, +			  decode +			  ]}, +     {plain_api,     [], [{group,api}]}, +     {ssl_api,       [], [{group,api}, start_tls_on_ssl_should_fail]}, +     {start_tls_api, [], [{group,api}, start_tls_twice_should_fail]}, + +     {api, [], [{group,api_not_bound}, +		{group,api_bound}]}, + +     {api_not_bound, [], [elementary_search, search_non_existant,  +			  add_when_not_bound, +			  bind]}, +     {api_bound, [], [add_when_bound,  +		      add_already_exists, +		      more_add,  +		      search_filter_equalityMatch, +		      search_filter_substring_any, +		      search_filter_initial, +		      search_filter_final, +		      search_filter_and, +		      search_filter_or, +		      search_filter_and_not, +		      search_two_hits, +		      modify, +		      delete, +		      modify_dn_delete_old, +		      modify_dn_keep_old]}, +     {v4_connections, [], connection_tests()}, +     {v6_connections, [], connection_tests()} +    ]. + +connection_tests() -> +    [tcp_connection,  +     tcp_connection_option,  +     ssl_connection, +     client_side_start_tls_timeout,  +     client_side_bind_timeout,  +     client_side_add_timeout, +     client_side_search_timeout +    ]. + + +  init_per_suite(Config) -> -    StartSsl = try ssl:start()  -    catch -	Error:Reason -> -	    {skip, lists:flatten(io_lib:format("eldap init_per_suite failed to start ssl Error=~p Reason=~p", [Error, Reason]))} -    end, -    case StartSsl of -	ok -> -	    chk_config(ldap_server, {"localhost",9876}, -		       chk_config(ldaps_server, {"localhost",9877}, -				  Config)); -	_ -> -	    StartSsl -    end. +    SSL_available = init_ssl_certs_et_al(Config), +    LDAP_server =  find_first_server(false, [{config,eldap_server}, {config,ldap_server}, {"localhost",9876}]), +    LDAPS_server =  +	case SSL_available of +	    true -> +		find_first_server(true,  [{config,ldaps_server}, {"localhost",9877}]); +	    false -> +		undefined +	end, +    [{ssl_available, SSL_available}, +     {ldap_server,   LDAP_server}, +     {ldaps_server,  LDAPS_server} | Config].  end_per_suite(_Config) -> -    ok. - -init_per_testcase(_TestCase, Config0) -> -    {EldapHost,Port} = proplists:get_value(ldap_server,Config0), -    try -	{ok, Handle} = eldap:open([EldapHost], [{port,Port}]), -	ok = eldap:simple_bind(Handle, "cn=Manager,dc=ericsson,dc=se", "hejsan"), -	{ok, MyHost} = inet:gethostname(), -	Path = "dc="++MyHost++",dc=ericsson,dc=se", -	eldap:add(Handle,"dc=ericsson,dc=se", -		  [{"objectclass", ["dcObject", "organization"]}, -		   {"dc", ["ericsson"]}, {"o", ["Testing"]}]), -	eldap:add(Handle,Path, -		  [{"objectclass", ["dcObject", "organization"]}, -		   {"dc", [MyHost]}, {"o", ["Test machine"]}]), -	[{eldap_path,Path}|Config0] -    catch error:{badmatch,Error} -> -	    io:format("Eldap init error ~p~n ~p~n",[Error, erlang:get_stacktrace()]), -	    {skip, lists:flatten(io_lib:format("Ldap init failed with host ~p:~p. Error=~p", [EldapHost,Port,Error]))} +    ssl:stop(). + + +init_per_group(plain_api, Config0) ->  +    case ?config(ldap_server,Config0) of +	undefined ->  +	    {skip, "LDAP server not availble"}; +	Server = {Host,Port} ->  +	    ct:comment("ldap://~s:~p",[Host,Port]), +	    initialize_db([{server,Server}, {ssl_flag,false}, {start_tls,false} | Config0]) +    end; +init_per_group(ssl_api, Config0) ->  +    case ?config(ldaps_server,Config0) of +	undefined ->  +	    {skip, "LDAPS server not availble"}; +	Server = {Host,Port} -> +	    ct:comment("ldaps://~s:~p",[Host,Port]), +	    initialize_db([{server,Server}, {ssl_flag,true}, {start_tls,false} | Config0]) +    end; +init_per_group(start_tls_api, Config0) ->  +    case {?config(ldap_server,Config0), ?config(ssl_available,Config0)} of +	{undefined,true} ->  +	    {skip, "LDAP server not availble"}; +	{_,false} ->  +	    {skip, "TLS not availble"}; +	{Server={Host,Port}, true} -> +	    ct:comment("ldap://~s:~p + start_tls",[Host,Port]), +	    Config = [{server,Server}, {ssl_flag,false} | Config0], +	    case supported_extension("1.3.6.1.4.1.1466.20037", Config) of +		true -> initialize_db([{start_tls,true} | Config]); +		false -> {skip, "start_tls not supported according to the server"} +	    end +    end; +init_per_group(v4_connections, Config) -> +    [{tcp_listen_opts,  [{reuseaddr, true}]}, +     {listen_host,  "localhost"}, +     {tcp_connect_opts, []} +     |  Config]; +init_per_group(v6_connections, Config) -> +    {ok, Hostname} = inet:gethostname(), +    case lists:member(list_to_atom(Hostname), ct:get_config(ipv6_hosts,[])) of +	true ->  +	    [{tcp_listen_opts,  [inet6,{reuseaddr, true}]}, +	     {listen_host,  "::"}, +	     {tcp_connect_opts, [{tcpopts,[inet6]}]} +	     |  Config]; +	false ->  +	    {skip, io_lib:format("~p is not an ipv6_host",[Hostname])} +    end; +init_per_group(_, Config) ->  +    Config. + +end_per_group(plain_api,     Config) -> clear_db(Config); +end_per_group(ssl_api,       Config) -> clear_db(Config); +end_per_group(start_tls_api, Config) -> clear_db(Config); +end_per_group(_Group, Config) -> Config. + + +init_per_testcase(ssl_connection, Config) -> +    case ?config(ssl_available,Config) of +	true -> +	    SSL_Port = 9999, +	    CertFile = filename:join(?config(data_dir,Config), "certs/server/cert.pem"), +	    KeyFile = filename:join(?config(data_dir,Config), "certs/server/key.pem"), + +	    Parent = self(), +	    Listener = spawn_link( +			 fun() -> +				 case ssl:listen(SSL_Port, [{certfile, CertFile}, +							    {keyfile, KeyFile} +							    | ?config(tcp_listen_opts,Config) +							   ]) of +				     {ok,SSL_LSock} -> +					 Parent ! {ok,self()}, +					 (fun L() -> +						ct:log("ssl server waiting for connections...",[]), +						{ok, S} = ssl:transport_accept(SSL_LSock), +						ct:log("ssl:transport_accept/1 ok",[]), +						ok = ssl:ssl_accept(S), +						ct:log("ssl:ssl_accept/1 ok",[]), +						L() +				          end)(); +				     Other -> +					 Parent ! {not_ok,Other,self()} +				 end +			 end), +	    receive +		{ok,Listener} -> +		    ct:log("SSL listening to port ~p (process ~p)",[SSL_Port, Listener]), +		    [{ssl_listener,Listener}, +		     {ssl_listen_port,SSL_Port}, +		     {ssl_connect_opts,[]} +		     | Config]; +		{no_ok,SSL_Other,Listener} -> +		    ct:log("ssl:listen on port ~p failed: ~p",[SSL_Port,SSL_Other]), +		    {fail, "ssl:listen/2 failed"} +	    after 5000 -> +		    {fail, "Waiting for ssl:listen timeout"} +	    end; +	false -> +	    {skip, "ssl not available"} +    end; + +init_per_testcase(TC, Config) -> +    case lists:member(TC,connection_tests()) of +	true -> +	    case gen_tcp:listen(0, proplists:get_value(tcp_listen_opts,Config)) of +		{ok,LSock} -> +		    {ok,{_,Port}} = inet:sockname(LSock), +		    [{listen_socket,LSock}, +		     {listen_port,Port} +		     | Config]; +		Other -> +		    {fail, Other} +	    end; +	 +	false -> +	    case proplists:get_value(name,?config(tc_group_properties, Config)) of +		api_not_bound -> +		    {ok,H} = open(Config), +		    [{handle,H} | Config]; +		api_bound -> +		    {ok,H} = open(Config), +		    ok = eldap:simple_bind(H, +					   "cn=Manager,dc=ericsson,dc=se", +					   "hejsan"), +		    [{handle,H} | Config]; +		_Name -> +		    Config +	    end      end. -end_per_testcase(_TestCase, Config) -> -    {EHost, Port} = proplists:get_value(ldap_server, Config), -    Path = proplists:get_value(eldap_path, Config), -    {ok, H} = eldap:open([EHost], [{port, Port}]), -    ok = eldap:simple_bind(H, "cn=Manager,dc=ericsson,dc=se", "hejsan"), -    case eldap:search(H, [{base, Path}, -			  {filter, eldap:present("objectclass")}, -			  {scope,  eldap:wholeSubtree()}]) -    of -	{ok, {eldap_search_result, Entries, _}} -> -	    [ok = eldap:delete(H, Entry) || {eldap_entry, Entry, _} <- Entries]; -	_ -> ignore -    end, - -    ok. +end_per_testcase(_, Config) -> +    catch gen_tcp:close( proplists:get_value(listen_socket, Config) ), +    catch eldap:close( proplists:get_value(handle,Config) ). -%% suite() -> -all() -> -    [app, -     appup, -     api, -     ssl_api, -     start_tls, -     tls_operations, -     start_tls_twice, -     start_tls_on_ssl -    ]. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% +%%% Test cases +%%% -app(doc) ->  "Test that the eldap app file is ok"; -app(suite) -> []; +%%%---------------------------------------------------------------- +%%% Test that the eldap app file is ok  app(Config) when is_list(Config) ->      ok = test_server:app_test(eldap). -%% Test that the eldap appup file is ok +%%%---------------------------------------------------------------- +%%% Test that the eldap appup file is ok  appup(Config) when is_list(Config) ->      ok = test_server:appup_test(eldap). -api(doc) -> "Basic test that all api functions works as expected"; -api(suite) -> []; -api(Config) -> -    {Host,Port} = proplists:get_value(ldap_server, Config), -    {ok, H} = eldap:open([Host], [{port,Port} -  ,{log,fun(Lvl,Fmt,Args)-> io:format("~p: ~s",[Lvl,io_lib:format(Fmt,Args)]) end} -				 ]), -    %% {ok, H} = eldap:open([Host], [{port,Port+1}, {ssl, true}]), -    do_api_checks(H, Config), -    eldap:close(H), -    ok. +%%%---------------------------------------------------------------- +tcp_connection(Config) -> +    Host = proplists:get_value(listen_host, Config), +    Port = proplists:get_value(listen_port, Config), +    Opts = proplists:get_value(tcp_connect_opts, Config), +    case eldap:open([Host], [{port,Port}|Opts]) of +	{ok,_H} -> +	    Sl = proplists:get_value(listen_socket, Config), +	    case gen_tcp:accept(Sl,1000) of +		{ok,_S} -> ok; +		{error,timeout} -> ct:fail("server side accept timeout",[]); +		Other -> ct:fail("gen_tdp:accept failed: ~p",[Other]) +	    end; +	Other -> ct:fail("eldap:open failed: ~p",[Other]) +    end. +%%%---------------------------------------------------------------- +ssl_connection(Config) -> +    Host = proplists:get_value(listen_host, Config), +    Port = proplists:get_value(ssl_listen_port, Config), +    Opts = proplists:get_value(tcp_connect_opts, Config), +    SSLOpts = proplists:get_value(ssl_connect_opts, Config), +    case eldap:open([Host], [{port,Port}, +			     {ssl,true}, +			     {timeout,5000}, +			     {sslopts,SSLOpts}|Opts]) of +	{ok,_H} -> ok; +	Other -> ct:fail("eldap:open failed: ~p",[Other]) +    end. -ssl_api(doc) -> "Basic test that all api functions works as expected"; -ssl_api(suite) -> []; -ssl_api(Config) -> -    {Host,Port} = proplists:get_value(ldaps_server, Config), -    {ok, H} = eldap:open([Host], [{port,Port}, {ssl,true}]), -    do_api_checks(H, Config), -    eldap:close(H), -    ok. +%%%---------------------------------------------------------------- +client_side_add_timeout(Config) -> +    client_timeout( +      fun(H) -> +	      eldap:add(H, "cn=Foo Bar,dc=host,dc=ericsson,dc=se", +			[{"objectclass", ["person"]}, +			 {"cn", ["Foo Bar"]},  +			 {"sn", ["Bar"]},  +			 {"telephoneNumber", ["555-1232", "555-5432"]}]) +      end, Config). + +%%%---------------------------------------------------------------- +client_side_bind_timeout(Config) -> +    client_timeout( +      fun(H) -> +	      eldap:simple_bind(H, anon, anon) +      end, Config). + +%%%---------------------------------------------------------------- +client_side_search_timeout(Config) -> +    client_timeout( +      fun(H) -> +	      eldap:search(H, [{base,"dc=host,dc=ericsson,dc=se"}, +			       {filter, eldap:present("objectclass")}, +			       {scope,  eldap:wholeSubtree()}]) +      end, Config). + +%%%---------------------------------------------------------------- +client_side_start_tls_timeout(Config) -> +    client_timeout( +      fun(H) -> +	      eldap:start_tls(H, []) +      end, Config). + +%%%---------------------------------------------------------------- +tcp_connection_option(Config) ->  +    Host = proplists:get_value(listen_host, Config), +    Port = proplists:get_value(listen_port, Config), +    Opts = proplists:get_value(tcp_connect_opts, Config), +    Sl = proplists:get_value(listen_socket, Config), + +    %% Make an option value to test.  The option must be implemented on all +    %% platforms that we test on.  Must check what the default value is +    %% so we don't happen to choose that particular value. +    {ok,[{linger,DefaultLinger}]} = inet:getopts(Sl, [linger]), +    TestLinger = case DefaultLinger of +		     {false,_} -> {true,5}; +		     {true,_} -> {false,0} +		 end, + +    case catch eldap:open([Host],  +			  [{port,Port},{tcpopts,[{linger,TestLinger}]}|Opts]) of +	{ok,H} -> +	    case gen_tcp:accept(Sl,1000) of +		{ok,_} ->  +		    case eldap:getopts(H, [{tcpopts,[linger]}]) of +			{ok,[{tcpopts,[{linger,ActualLinger}]}]} -> +			    case ActualLinger of +				TestLinger ->  +				    ok; +				DefaultLinger -> +				    ct:fail("eldap:getopts: 'linger' didn't change," +					    " got ~p (=default) expected ~p", +					    [ActualLinger,TestLinger]); +				_ -> +				    ct:fail("eldap:getopts: bad 'linger', got ~p expected ~p", +					    [ActualLinger,TestLinger]) +			    end; +			Other -> +			    ct:fail("eldap:getopts: bad result ~p",[Other]) +		    end; +		{error,timeout} ->  +		    ct:fail("server side accept timeout",[]) +	    end; + +	Other -> +	    ct:fail("eldap:open failed: ~p",[Other]) +    end. -start_tls(doc) -> "Test that an existing (tcp) connection can be upgraded to tls"; -start_tls(suite) -> []; -start_tls(Config) -> -    {Host,Port} = proplists:get_value(ldap_server, Config), -    {ok, H} = eldap:open([Host], [{port,Port}]), -    ok = eldap:start_tls(H, [ -			     {keyfile, filename:join([proplists:get_value(data_dir,Config), -						      "certs/client/key.pem"])} -			    ]), -    eldap:close(H). +%%%---------------------------------------------------------------- +%%% Basic test that all api functions works as expected + +%%%---------------------------------------------------------------- +elementary_search(Config) -> +    {ok, #eldap_search_result{entries=[_]}} =  +	eldap:search(?config(handle,Config), +		     #eldap_search{base  = ?config(eldap_path, Config), +				   filter= eldap:present("objectclass"), +				   scope = eldap:wholeSubtree()}). + +%%%---------------------------------------------------------------- +search_non_existant(Config) -> +    {error, noSuchObject} =  +	eldap:search(?config(handle,Config), +		     #eldap_search{base  = "cn=Bar," ++ ?config(eldap_path, Config), +				   filter= eldap:present("objectclass"), +				   scope = eldap:wholeSubtree()}). + +%%%---------------------------------------------------------------- +add_when_not_bound(Config) -> +    {error, _} = eldap:add(?config(handle,Config), +			   "cn=Jonas Jonsson," ++ ?config(eldap_path, Config), +			   [{"objectclass", ["person"]}, +			    {"cn", ["Jonas Jonsson"]},  +			    {"sn", ["Jonsson"]}]). + +%%%---------------------------------------------------------------- +bind(Config) -> +    ok = eldap:simple_bind(?config(handle,Config), +			   "cn=Manager,dc=ericsson,dc=se", +			   "hejsan"). + +%%%---------------------------------------------------------------- +add_when_bound(Config) -> +    ok = eldap:add(?config(handle, Config), +		   "cn=Jonas Jonsson," ++  ?config(eldap_path, Config), +		   [{"objectclass", ["person"]}, +		    {"cn", ["Jonas Jonsson"]},  +		    {"sn", ["Jonsson"]}]). + +%%%---------------------------------------------------------------- +add_already_exists(Config) -> +    {error, entryAlreadyExists} =  +	eldap:add(?config(handle, Config), +		  "cn=Jonas Jonsson," ++ ?config(eldap_path, Config), +		  [{"objectclass", ["person"]}, +		   {"cn", ["Jonas Jonsson"]},  +		   {"sn", ["Jonsson"]}]). + +%%%---------------------------------------------------------------- +more_add(Config) -> +    H = ?config(handle, Config), +    BasePath = ?config(eldap_path, Config), +    ok = eldap:add(H, "cn=Foo Bar," ++ BasePath, +		   [{"objectclass", ["person"]}, +		    {"cn", ["Foo Bar"]},  +		    {"sn", ["Bar"]},  +		    {"telephoneNumber", ["555-1232", "555-5432"]}]), +    ok = eldap:add(H, "ou=Team," ++ BasePath, +		   [{"objectclass", ["organizationalUnit"]}, +		    {"ou", ["Team"]}]). -tls_operations(doc) -> "Test that an upgraded connection is usable for ldap stuff"; -tls_operations(suite) -> []; -tls_operations(Config) -> -    {Host,Port} = proplists:get_value(ldap_server, Config), -    {ok, H} = eldap:open([Host], [{port,Port}]), -    ok = eldap:start_tls(H, [ -			     {keyfile, filename:join([proplists:get_value(data_dir,Config), -						      "certs/client/key.pem"])} -			    ]), -    do_api_checks(H, Config), -    eldap:close(H). - -start_tls_twice(doc) -> "Test that start_tls on an already upgraded connection fails"; -start_tls_twice(suite) -> []; -start_tls_twice(Config) -> -    {Host,Port} = proplists:get_value(ldap_server, Config), -    {ok, H} = eldap:open([Host], [{port,Port}]), -    ok = eldap:start_tls(H, []), +%%%---------------------------------------------------------------- +search_filter_equalityMatch(Config) -> +    BasePath = ?config(eldap_path, Config), +    ExpectedDN = "cn=Jonas Jonsson," ++ BasePath, +    {ok, #eldap_search_result{entries=[#eldap_entry{object_name=ExpectedDN}]}} =  +	eldap:search(?config(handle, Config), +		     #eldap_search{base = BasePath, +				   filter = eldap:equalityMatch("sn", "Jonsson"), +				   scope=eldap:singleLevel()}). + +%%%---------------------------------------------------------------- +search_filter_substring_any(Config) -> +    BasePath = ?config(eldap_path, Config), +    ExpectedDN = "cn=Jonas Jonsson," ++ BasePath, +    {ok, #eldap_search_result{entries=[#eldap_entry{object_name=ExpectedDN}]}} =  +	eldap:search(?config(handle, Config), +		     #eldap_search{base = BasePath, +				   filter = eldap:substrings("sn", [{any, "ss"}]), +				   scope=eldap:singleLevel()}). + +%%%---------------------------------------------------------------- +search_filter_initial(Config) -> +    H = ?config(handle, Config), +    BasePath = ?config(eldap_path, Config), +    ExpectedDN = "cn=Foo Bar," ++ BasePath, +    {ok, #eldap_search_result{entries=[#eldap_entry{object_name=ExpectedDN}]}} = +	eldap:search(H, +		     #eldap_search{base = BasePath, +				   filter = eldap:substrings("sn", [{initial, "B"}]), +				   scope=eldap:singleLevel()}). + +%%%---------------------------------------------------------------- +search_filter_final(Config) -> +    H = ?config(handle, Config), +    BasePath = ?config(eldap_path, Config), +    ExpectedDN = "cn=Foo Bar," ++ BasePath, +    {ok, #eldap_search_result{entries=[#eldap_entry{object_name=ExpectedDN}]}} = +	eldap:search(H, +		     #eldap_search{base = BasePath, +				   filter = eldap:substrings("sn", [{final, "r"}]), +				   scope=eldap:singleLevel()}). + +%%%---------------------------------------------------------------- +search_filter_and(Config) -> +    H = ?config(handle, Config), +    BasePath = ?config(eldap_path, Config), +    ExpectedDN = "cn=Foo Bar," ++ BasePath, +    {ok, #eldap_search_result{entries=[#eldap_entry{object_name=ExpectedDN}]}} = +	eldap:search(H, +		     #eldap_search{base = BasePath, +				   filter = eldap:'and'([eldap:substrings("sn", [{any, "a"}]), +							 eldap:equalityMatch("cn","Foo Bar")]), +				   scope=eldap:singleLevel()}). + +%%%---------------------------------------------------------------- +search_filter_or(Config) -> +    H = ?config(handle, Config), +    BasePath = ?config(eldap_path, Config), +    ExpectedDNs = lists:sort(["cn=Foo Bar," ++ BasePath, +			      "ou=Team," ++ BasePath]), +    {ok, #eldap_search_result{entries=Es}} = +	eldap:search(H, +		     #eldap_search{base = BasePath, +				   filter = eldap:'or'([eldap:substrings("sn", [{any, "a"}]), +							eldap:equalityMatch("ou","Team")]), +				   scope=eldap:singleLevel()}), +    ExpectedDNs = lists:sort([DN || #eldap_entry{object_name=DN} <- Es]). + +%%%---------------------------------------------------------------- +search_filter_and_not(Config) -> +    H = ?config(handle, Config), +    BasePath = ?config(eldap_path, Config), +    {ok, #eldap_search_result{entries=[]}} = +	eldap:search(H, +		     #eldap_search{base = BasePath, +				   filter = eldap:'and'([eldap:substrings("sn", [{any, "a"}]), +							 eldap:'not'( +							   eldap:equalityMatch("cn","Foo Bar") +							  )]), +				   scope=eldap:singleLevel()}). + +%%%---------------------------------------------------------------- +search_two_hits(Config) -> +    H = ?config(handle, Config), +    BasePath = ?config(eldap_path, Config), +    DN1 = "cn=Santa Claus," ++ BasePath, +    DN2 = "cn=Jultomten," ++ BasePath, +    %% Add two objects: +    ok = eldap:add(H, DN1, +		   [{"objectclass", ["person"]}, +		    {"cn", ["Santa Claus"]},  +		    {"sn", ["Santa"]}, +		    {"description", ["USA"]}]), +    ok = eldap:add(H, DN2, +		   [{"objectclass", ["person"]}, +		    {"cn", ["Jultomten"]},  +		    {"sn", ["Tomten"]}, +		    {"description", ["Sweden"]}]), + +    %% Search for them: +    {ok, #eldap_search_result{entries=Es}} = +	eldap:search(H, +		     #eldap_search{base = BasePath, +				   filter = eldap:present("description"), +				   scope=eldap:singleLevel()}), + +    %% And check that they are the expected ones: +    ExpectedDNs = lists:sort([DN1, DN2]), +    ExpectedDNs = lists:sort([D || #eldap_entry{object_name=D} <- Es]), + +    %% Restore the database: +    [ok=eldap:delete(H,DN) || DN <- ExpectedDNs]. + +%%%---------------------------------------------------------------- +modify(Config) -> +    H = ?config(handle, Config), +    BasePath = ?config(eldap_path, Config), +    %% The object to modify +    DN = "cn=Foo Bar," ++ BasePath, + +    %% Save a copy to restore later: +    {ok,OriginalAttrs} = attributes(H, DN), +     +    %% Do a change +    Mod = [eldap:mod_replace("telephoneNumber", ["555-12345"]), +	   eldap:mod_add("description", ["Nice guy"])], +    ok = eldap:modify(H, DN, Mod), + +    %% Check that the object was changed +    {ok, #eldap_search_result{entries=[#eldap_entry{object_name=DN}]}} = +	eldap:search(H, +		     #eldap_search{base = BasePath, +				   filter = eldap:equalityMatch("telephoneNumber", "555-12345"), +				   scope=eldap:singleLevel()}), + +    %% Do another type of change +    ok = eldap:modify(H, DN, [eldap:mod_delete("telephoneNumber", [])]), +    %% and check that it worked by repeating the test above +    {ok, #eldap_search_result{entries=[]}} = +	eldap:search(H, +		     #eldap_search{base = BasePath, +				   filter = eldap:equalityMatch("telephoneNumber", "555-12345"), +				   scope=eldap:singleLevel()}), +    %% restore the orignal version: +    restore_original_object(H, DN, OriginalAttrs). + +%%%---------------------------------------------------------------- +delete(Config) -> +    H = ?config(handle, Config), +    BasePath = ?config(eldap_path, Config), +    %% The element to play with: +    DN = "cn=Jonas Jonsson," ++ BasePath, + +    %% Prove that the element is present before deletion +    {ok,OriginalAttrs} = attributes(H, DN), + +    %% Do what the test has to do: +    ok = eldap:delete(H, DN), +    %% check that it really was deleted: +    {error, noSuchObject} = eldap:delete(H, DN), + +    %% And restore the object for subsequent tests +    restore_original_object(H, DN, OriginalAttrs). + +%%%---------------------------------------------------------------- +modify_dn_delete_old(Config) -> +    H = ?config(handle, Config), +    BasePath = ?config(eldap_path, Config), +    OrigCN = "Foo Bar", +    OriginalRDN = "cn="++OrigCN, +    DN = OriginalRDN ++ "," ++ BasePath, +    NewCN = "Niclas Andre", +    NewRDN = "cn="++NewCN, +    NewDN = NewRDN ++ "," ++BasePath, + +    %% Check that the object to modify_dn of exists: +    {ok,OriginalAttrs} = attributes(H, DN), +    CN_orig = lists:sort(proplists:get_value("cn",OriginalAttrs)), +    {ok, #eldap_search_result{entries=[#eldap_entry{object_name=DN}]}} = +	eldap:search(H, +		     #eldap_search{base = BasePath, +				   filter = eldap:substrings("sn", [{any, "a"}]), +				   scope = eldap:singleLevel()}), +     +    %% Modify and delete the old one: +    ok = eldap:modify_dn(H, DN, NewRDN, true, ""), +     +    %% Check that DN was modified and the old one was deleted: +    {ok,NewAttrs} = attributes(H, NewDN), +    CN_new = lists:sort(proplists:get_value("cn",NewAttrs)), +    {ok, #eldap_search_result{entries=[#eldap_entry{object_name=NewDN}]}} = +	eldap:search(H, +		     #eldap_search{base = BasePath, +				   filter = eldap:substrings("sn", [{any, "a"}]), +				   scope = eldap:singleLevel()}), +    %% What we expect: +    CN_new = lists:sort([NewCN | CN_orig -- [OrigCN]]), +     +    %% Change back: +    ok = eldap:modify_dn(H, NewDN, OriginalRDN, true, ""), + +    %% Check that DN was modified and the new one was deleted: +    {ok,SameAsOriginalAttrs} = attributes(H, DN), +    CN_orig = lists:sort(proplists:get_value("cn",SameAsOriginalAttrs)), +    {ok, #eldap_search_result{entries=[#eldap_entry{object_name=DN}]}} = +	eldap:search(H, +		     #eldap_search{base = BasePath, +				   filter = eldap:substrings("sn", [{any, "a"}]), +				   scope = eldap:singleLevel()}). +     +%%%---------------------------------------------------------------- +modify_dn_keep_old(Config) -> +    H = ?config(handle, Config), +    BasePath = ?config(eldap_path, Config), +    OriginalRDN = "cn=Foo Bar", +    DN = OriginalRDN ++ "," ++ BasePath, +    NewCN = "Niclas Andre", +    NewRDN = "cn="++NewCN, +    NewDN = NewRDN ++ "," ++BasePath, + +    %% Check that the object to modify_dn of exists but the new one does not: +    {ok,OriginalAttrs} = attributes(H, DN), +    {ok, #eldap_search_result{entries=[#eldap_entry{object_name=DN}]}} = +	eldap:search(H, +		     #eldap_search{base = BasePath, +				   filter = eldap:substrings("sn", [{any, "a"}]), +				   scope = eldap:singleLevel()}), +     +    %% Modify but keep the old "cn" attr: +    ok = eldap:modify_dn(H, DN, NewRDN, false, ""), +     +    %% Check that DN was modified and the old CN entry is not deleted: +    {ok,NewAttrs} = attributes(H, NewDN),     +    CN_orig = proplists:get_value("cn",OriginalAttrs), +    CN_new = proplists:get_value("cn",NewAttrs), +    Expected = lists:sort([NewCN|CN_orig]), +    Expected = lists:sort(CN_new), +     +    %% Restore db: +    ok = eldap:delete(H, NewDN), +    restore_original_object(H, DN, OriginalAttrs). + +%%%---------------------------------------------------------------- +%%% Test that start_tls on an already upgraded connection makes no noise +start_tls_twice_should_fail(Config) -> +    {ok,H} = open_bind(Config),      {error,tls_already_started} = eldap:start_tls(H, []), -    do_api_checks(H, Config), -    eldap:close(H). - +    _Ok = eldap:close(H), +    ok. -start_tls_on_ssl(doc) -> "Test that start_tls on an ldaps connection fails"; -start_tls_on_ssl(suite) -> []; -start_tls_on_ssl(Config) -> -    {Host,Port} = proplists:get_value(ldaps_server, Config), -    {ok, H} = eldap:open([Host], [{port,Port}, {ssl,true}]), +%%%---------------------------------------------------------------- +%%% Test that start_tls on an ldaps connection fails +start_tls_on_ssl_should_fail(Config) -> +    {ok,H} = open_bind(Config),      {error,tls_already_started} = eldap:start_tls(H, []), -    do_api_checks(H, Config), -    eldap:close(H). +    _Ok = eldap:close(H), +    ok. +%%%---------------------------------------------------------------- +encode(_Config) -> +    {ok,Bin} = 'ELDAPv3':encode('AddRequest', #'AddRequest'{entry="hejHopp"  ,attributes=[]} ), +    Expected = <<104,11,4,7,104,101,106,72,111,112,112,48,0>>, +    case Bin of +	Expected -> ok; +	_ -> ct:log("Encoded erroneously to:~n~p~nExpected:~n~p",[Bin,Expected]), +	     {fail, "Bad encode"} +    end. -%%%-------------------------------------------------------------------------------- -chk_config(Key, Default, Config) -> -    case catch ct:get_config(ldap_server, undefined) of -	undefined  -> [{Key,Default} | Config ]; -	{'EXIT',_} -> [{Key,Default} | Config ]; -	Value -> [{Key,Value} | Config] +%%%---------------------------------------------------------------- +decode(_Config) -> +    {ok,Res} = 'ELDAPv3':decode('AddRequest', <<104,11,4,7,104,101,106,72,111,112,112,48,0>>), +    ct:log("Res = ~p", [Res]), +    Expected = #'AddRequest'{entry = "hejHopp",attributes = []}, +    case Res of +	Expected -> ok; +	#'AddRequest'{entry= <<"hejHopp">>, attributes=[]} ->  +	    {fail, "decoded to (correct) binary!!"}; +	_ -> +	    {fail, "Bad decode"} +    end. +	     + + +%%%**************************************************************** +%%% Private + +attributes(H, DN) -> +    case eldap:search(H, +		     #eldap_search{base  = DN, +				   filter= eldap:present("objectclass"), +				   scope = eldap:wholeSubtree()}) of +	{ok, #eldap_search_result{entries=[#eldap_entry{object_name=DN, +							attributes=OriginalAttrs}]}} -> +	    {ok, OriginalAttrs}; +	Other -> +	    Other      end. +restore_original_object(H, DN, Attrs) -> +    eldap:delete(H, DN), +    ok = eldap:add(H, DN, Attrs). + + +find_first_server(UseSSL, [{config,Key}|Ss]) -> +    case ct:get_config(Key) of +	{Host,Port} -> +	    ct:log("find_first_server config ~p -> ~p",[Key,{Host,Port}]), +	    find_first_server(UseSSL, [{Host,Port}|Ss]); +	undefined -> +	    ct:log("find_first_server config ~p is undefined",[Key]), +	    find_first_server(UseSSL, Ss) +    end; +find_first_server(UseSSL, [{Host,Port}|Ss]) -> +    case eldap:open([Host],[{port,Port},{ssl,UseSSL}]) of +	{ok,H} when UseSSL==false, Ss=/=[] -> +	    case eldap:start_tls(H,[]) of +		ok ->  +		    ct:log("find_first_server ~p UseSSL=~p -> ok",[{Host,Port},UseSSL]), +		    _Ok = eldap:close(H), +		    {Host,Port}; +		Res -> +		    ct:log("find_first_server ~p UseSSL=~p failed with~n~p~nSave as spare host.",[{Host,Port},UseSSL,Res]), +		    _Ok = eldap:close(H), +		    find_first_server(UseSSL, Ss++[{spare_host,Host,Port}]) +	    end; +	{ok,H} -> +	    ct:log("find_first_server ~p UseSSL=~p -> ok",[{Host,Port},UseSSL]), +	    _Ok = eldap:close(H), +	    {Host,Port}; +	Res -> +	    ct:log("find_first_server ~p UseSSL=~p failed with~n~p",[{Host,Port},UseSSL,Res]), +	    find_first_server(UseSSL, Ss) +    end; +find_first_server(false, [{spare_host,Host,Port}|_]) -> +    ct:log("find_first_server can't find start_tls host, use the spare non-start_tls host for plain ldap: ~p",[{Host,Port}]), +    {Host,Port}; +find_first_server(_, []) -> +    ct:log("find_first_server, nothing left to try",[]), +    undefined. + +initialize_db(Config) -> +    case {open_bind(Config), inet:gethostname()} of +	{{ok,H}, {ok,MyHost}} -> +	    Path = "dc="++MyHost++",dc=ericsson,dc=se", +	    delete_old_contents(H, Path), +	    add_new_contents(H, Path, MyHost), +	    _Ok = eldap:close(H), +	    [{eldap_path,Path}|Config]; +	Other -> +	    ct:fail("initialize_db failed: ~p",[Other]) +    end. +clear_db(Config) -> +    {ok,H} = open_bind(Config), +    Path = ?config(eldap_path, Config), +    delete_old_contents(H, Path), +    _Ok = eldap:close(H), +    Config. -do_api_checks(H, Config) -> -    BasePath = proplists:get_value(eldap_path, Config), +delete_old_contents(H, Path) -> +    case eldap:search(H, [{base,  Path}, +			  {filter, eldap:present("objectclass")}, +			  {scope,  eldap:wholeSubtree()}]) +    of +	{ok, #eldap_search_result{entries=Entries}} -> +	    [ok = eldap:delete(H,DN) || #eldap_entry{object_name=DN} <- Entries]; +	_Res ->  +	    ignore +    end. -    All = fun(Where) -> -		  eldap:search(H, #eldap_search{base=Where, -						filter=eldap:present("objectclass"), -						scope= eldap:wholeSubtree()}) -	  end, -    {ok, #eldap_search_result{entries=[_XYZ]}} = All(BasePath), -%%    ct:log("XYZ=~p",[_XYZ]), -    {error, noSuchObject} = All("cn=Bar,"++BasePath), +add_new_contents(H, Path, MyHost) -> +    ok(eldap:add(H,"dc=ericsson,dc=se", +		 [{"objectclass", ["dcObject", "organization"]}, +		  {"dc", ["ericsson"]},  +		  {"o", ["Testing"]}])), +    ok(eldap:add(H,Path, +		 [{"objectclass", ["dcObject", "organization"]}, +		  {"dc", [MyHost]},  +		  {"o", ["Test machine"]}])). +     + +ok({error,entryAlreadyExists}) -> ok; +ok(X) -> ok=X. +     + + +cond_start_tls(H, Config) -> +    case ?config(start_tls,Config) of +	true -> start_tls(H,Config); +	_ -> Config +    end. +	     +start_tls(H, Config) -> +    KeyFile = filename:join([?config(data_dir,Config), +			     "certs/client/key.pem" +			    ]), +    case eldap:start_tls(H, [{keyfile, KeyFile}]) of +	ok -> +	    [{start_tls_success,true} | Config]; +	Error -> +	    ct:log("Start_tls on ~p failed: ~p",[?config(url,Config) ,Error]), +	    ct:fail("start_tls failed") +    end. -    {error, _} = eldap:add(H, "cn=Jonas Jonsson," ++ BasePath, -			   [{"objectclass", ["person"]}, -			    {"cn", ["Jonas Jonsson"]}, {"sn", ["Jonsson"]}]), -    eldap:simple_bind(H, "cn=Manager,dc=ericsson,dc=se", "hejsan"), -    chk_add(H, BasePath), -    {ok,FB} = chk_search(H, BasePath), -    chk_modify(H, FB), -    chk_delete(H, BasePath), -    chk_modify_dn(H, FB). +%%%---------------------------------------------------------------- +open_bind(Config) -> +    {ok,H} = open(Config), +    ok = eldap:simple_bind(H, "cn=Manager,dc=ericsson,dc=se", "hejsan"), +    {ok,H}. + +open(Config) -> +    {Host,Port} = ?config(server,Config), +    SSLflag = ?config(ssl_flag,Config), +    {ok,H} = eldap:open([Host], [{port,Port},{ssl,SSLflag}]), +    cond_start_tls(H, Config), +    {ok,H}. + +%%%---------------------------------------------------------------- +supported_extension(OID, Config) -> +    {ok,H} = open_bind(Config), +    case eldap:search(H, [{scope,  eldap:baseObject()}, +			  {filter, eldap:present("objectclass")}, +			  {deref,  eldap:neverDerefAliases()}, +			  {attributes, ["+"]}]) of +	{ok,R=#eldap_search_result{}} -> +	    _Ok = eldap:close(H), +	    lists:member(OID, +			 [SE || EE <- R#eldap_search_result.entries, +				{"supportedExtension",SEs} <- EE#eldap_entry.attributes, +				SE<-SEs]); +	_ -> +	    _Ok = eldap:close(H), +	    false +    end. +%%%---------------------------------------------------------------- +client_timeout(Fun, Config) -> +    Host = proplists:get_value(listen_host, Config), +    Port = proplists:get_value(listen_port, Config), +    Opts = proplists:get_value(tcp_connect_opts, Config), +    T = 1000, +    case eldap:open([Host], [{timeout,T},{port,Port}|Opts]) of +	{ok,H} ->  +	    T0 = now(), +	    {error,{gen_tcp_error,timeout}} = Fun(H), +	    T_op = diff(T0,now()), +	    ct:log("Time = ~p, Timeout spec = ~p",[T_op,T]), +	    if  +		T_op < T ->  +		    {fail, "Timeout too early"}; +		true -> +		    ok +	    end; +		     +	Other -> ct:fail("eldap:open failed: ~p",[Other]) +    end. -chk_add(H, BasePath) -> -    ok = eldap:add(H, "cn=Jonas Jonsson," ++ BasePath, -		   [{"objectclass", ["person"]}, -		    {"cn", ["Jonas Jonsson"]}, {"sn", ["Jonsson"]}]), -    {error, entryAlreadyExists} = eldap:add(H, "cn=Jonas Jonsson," ++ BasePath, -					    [{"objectclass", ["person"]}, -					     {"cn", ["Jonas Jonsson"]}, {"sn", ["Jonsson"]}]), -    ok = eldap:add(H, "cn=Foo Bar," ++ BasePath, -		   [{"objectclass", ["person"]}, -		    {"cn", ["Foo Bar"]}, {"sn", ["Bar"]}, {"telephoneNumber", ["555-1232", "555-5432"]}]), -    ok = eldap:add(H, "ou=Team," ++ BasePath, -		   [{"objectclass", ["organizationalUnit"]}, -		    {"ou", ["Team"]}]). +diff({M1,S1,U1},{M2,S2,U2}) -> +    ( ((M2-M1)*1000 + (S2-S1))*1000 + (U2-U1) ). -chk_search(H, BasePath) -> -    Search = fun(Filter) -> -		     eldap:search(H, #eldap_search{base=BasePath, -						   filter=Filter, -						   scope=eldap:singleLevel()}) -	     end, -    JJSR = {ok, #eldap_search_result{entries=[#eldap_entry{}]}} = Search(eldap:equalityMatch("sn", "Jonsson")), -    JJSR = Search(eldap:substrings("sn", [{any, "ss"}])), -    FBSR = {ok, #eldap_search_result{entries=[#eldap_entry{object_name=FB}]}} = -	Search(eldap:substrings("sn", [{any, "a"}])), -    FBSR = Search(eldap:substrings("sn", [{initial, "B"}])), -    FBSR = Search(eldap:substrings("sn", [{final, "r"}])), -    F_AND = eldap:'and'([eldap:present("objectclass"), eldap:present("ou")]), -    {ok, #eldap_search_result{entries=[#eldap_entry{}]}} = Search(F_AND), -    F_NOT = eldap:'and'([eldap:present("objectclass"), eldap:'not'(eldap:present("ou"))]), -    {ok, #eldap_search_result{entries=[#eldap_entry{}, #eldap_entry{}]}} = Search(F_NOT), -    {ok, #eldap_search_result{entries=[#eldap_entry{}]}} = Search(eldap:extensibleMatch("Bar",[{type,"sn"},{matchingRule,"caseExactMatch"}])), -    {ok, #eldap_search_result{entries=[#eldap_entry{}]}} = Search(eldap:extensibleMatch("Bar",[{type,"sn"},{matchingRule,"2.5.13.5"}])), -    {ok, #eldap_search_result{entries=[#eldap_entry{}]}} = Search(eldap:extensibleMatch("Bar",[{type,"sn"},{matchingRule,"caseIgnoreMatch"}])), -    {ok, #eldap_search_result{entries=[#eldap_entry{}]}} = Search(eldap:extensibleMatch("bar",[{type,"sn"},{matchingRule,"caseIgnoreMatch"}])), -    {ok, #eldap_search_result{entries=[]}} = Search(eldap:extensibleMatch("bar",[{type,"sn"},{matchingRule,"gluffgluff"}])), -    {ok, #eldap_search_result{entries=[]}} = Search(eldap:extensibleMatch("bar",[{type,"sn"},{matchingRule,"caseExactMatch"}])), -    {ok,FB}.					%% FIXME - -chk_modify(H, FB) -> -    Mod = [eldap:mod_replace("telephoneNumber", ["555-12345"]), -	   eldap:mod_add("description", ["Nice guy"])], -    %% io:format("MOD ~p ~p ~n",[FB, Mod]), -    ok = eldap:modify(H, FB, Mod), -    %% DELETE ATTR -    ok = eldap:modify(H, FB, [eldap:mod_delete("telephoneNumber", [])]). - - -chk_delete(H, BasePath) -> -    {error, entryAlreadyExists} = eldap:add(H, "cn=Jonas Jonsson," ++ BasePath, -					    [{"objectclass", ["person"]}, -					     {"cn", ["Jonas Jonsson"]}, {"sn", ["Jonsson"]}]), -    ok = eldap:delete(H, "cn=Jonas Jonsson," ++ BasePath), -    {error, noSuchObject} = eldap:delete(H, "cn=Jonas Jonsson," ++ BasePath). - -chk_modify_dn(H, FB) -> -    ok = eldap:modify_dn(H, FB, "cn=Niclas Andre", true, ""). -    %%io:format("Res ~p~n ~p~n",[R, All(BasePath)]). - - -%%%---------------- -add(H,  Attr, Value, Path0, Attrs, Class) -> -    Path = case Path0 of -	       [] -> Attr ++ "=" ++ Value; -	       _ -> Attr ++ "=" ++ Value ++ "," ++ Path0 -	   end, -    case eldap:add(H, Path, [{"objectclass", Class}, {Attr, [Value]}] ++ Attrs) +%%%---------------------------------------------------------------- +init_ssl_certs_et_al(Config) -> +    try ssl:start()      of -	ok -> {ok, Path}; -	{error, E = entryAlreadyExists} -> {E, Path}; -	R = {error, Reason} -> -	    io:format("~p:~p: ~s,~s =>~n ~p~n", -		      [?MODULE,?LINE, Attr, Value, R]), -	    exit({ldap, add, Reason}) +	R when R==ok ; R=={error,{already_started,ssl}} -> +	    try make_certs:all("/dev/null",  +			       filename:join(?config(data_dir,Config), "certs")) +	    of +		{ok,_} -> true; +		Other ->  +		    ct:comment("make_certs failed"), +		    ct:log("make_certs failed ~p", [Other]), +		    false +	    catch +		C:E ->  +		    ct:comment("make_certs crashed"), +		    ct:log("make_certs failed ~p:~p", [C,E]), +		    false +	    end; +	_ -> +	    false +    catch +	Error:Reason -> +	    ct:comment("ssl failed to start"), +	    ct:log("init_per_suite failed to start ssl Error=~p Reason=~p", [Error, Reason]), +	    false      end. - - - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -%% Develop -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -test() -> -    run(). - -run() -> -    Cases = all(), -    run(Cases). - -run(Case) when is_atom(Case) -> -    run([Case]); -run(Cases) when is_list(Cases) -> -    Run = fun(Test, Config0) -> -		  Config = init_per_testcase(Test, Config0), -		  try -		      io:format("~nTest ~p ... ",[Test]), -		      ?MODULE:Test(Config), -		      end_per_testcase(Test, Config), -		      io:format("ok~n",[]) -		  catch _:Reason -> -			  io:format("~n   FAIL (~p): ~p~n ~p~n", -				    [Test, Reason, erlang:get_stacktrace()]) -		  end -	  end, -    process_flag(trap_exit, true), -    Pid = spawn_link(fun() -> -			     case init_per_suite([]) of -				 {skip, Reason} -> io:format("Skip ~s~n",[Reason]); -				 Config -> -				     try -					 [Run(Test, Config) || Test <- Cases] -				     catch _:Err -> -					     io:format("Error ~p in ~p~n",[Err, erlang:get_stacktrace()]) -				     end, -				     end_per_suite(Config) -			     end -		     end), -    receive -	{'EXIT', Pid, normal} -> ok; -	Msg -> io:format("Received ~p (~p)~n",[Msg, Pid]) -    after 100 -> ok end, -    process_flag(trap_exit, false), -    ok. diff --git a/lib/eldap/test/eldap_basic_SUITE_data/RAND b/lib/eldap/test/eldap_basic_SUITE_data/RAND Binary files differnew file mode 100644 index 0000000000..70997bd01f --- /dev/null +++ b/lib/eldap/test/eldap_basic_SUITE_data/RAND diff --git a/lib/eldap/test/eldap_connections_SUITE.erl b/lib/eldap/test/eldap_connections_SUITE.erl deleted file mode 100644 index c5460fef09..0000000000 --- a/lib/eldap/test/eldap_connections_SUITE.erl +++ /dev/null @@ -1,147 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2012-2014. 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(eldap_connections_SUITE). - --compile(export_all). - --include_lib("common_test/include/ct.hrl"). -%-include_lib("eldap/include/eldap.hrl"). - - -all() -> -    [ -     {group, v4}, -     {group, v6} -    ]. - -      -init_per_group(v4, Config) -> -    [{listen_opts,  []}, -     {listen_host,  "localhost"}, -     {connect_opts, []} -     |  Config]; -init_per_group(v6, Config) -> -    {ok, Hostname} = inet:gethostname(), -    case lists:member(list_to_atom(Hostname), ct:get_config(ipv6_hosts,[])) of -	true ->  -	    [{listen_opts,  [inet6]}, -	     {listen_host,  "::"}, -	     {connect_opts, [{tcpopts,[inet6]}]} -	     |  Config]; -	false ->  -	    {skip, io_lib:format("~p is not an ipv6_host",[Hostname])} -    end. - - -end_per_group(_GroupName, Config) -> -    Config. - - -groups() -> -    [{v4, [], [tcp_connection, tcp_connection_option]}, -     {v6, [], [tcp_connection, tcp_connection_option]} -    ]. - - -init_per_suite(Config) -> Config. - - -end_per_suite(_Config) -> ok. - - -init_per_testcase(_TestCase, Config) -> -    case gen_tcp:listen(0, proplists:get_value(listen_opts,Config)) of -	{ok,LSock} -> -	    {ok,{_,Port}} = inet:sockname(LSock), -	    [{listen_socket,LSock}, -	     {listen_port,Port} -	     | Config]; -	Other -> -	    {fail, Other} -    end. - - -end_per_testcase(_TestCase, Config) -> -    catch gen_tcp:close( proplists:get_value(listen_socket, Config) ). - -%%%================================================================ -%%% -%%% Test cases -%%%  -%%%---------------------------------------------------------------- -tcp_connection(Config) -> -    Host = proplists:get_value(listen_host, Config), -    Port = proplists:get_value(listen_port, Config), -    Opts = proplists:get_value(connect_opts, Config), -    case eldap:open([Host], [{port,Port}|Opts]) of -	{ok,_H} -> -	    Sl = proplists:get_value(listen_socket, Config), -	    case gen_tcp:accept(Sl,1000) of -		{ok,_S} -> ok; -		{error,timeout} -> ct:fail("server side accept timeout",[]) -	    end; -	Other -> ct:fail("eldap:open failed: ~p",[Other]) -    end. - - -%%%---------------------------------------------------------------- -tcp_connection_option(Config) ->  -    Host = proplists:get_value(listen_host, Config), -    Port = proplists:get_value(listen_port, Config), -    Opts = proplists:get_value(connect_opts, Config), -    Sl = proplists:get_value(listen_socket, Config), - -    %% Make an option value to test.  The option must be implemented on all -    %% platforms that we test on.  Must check what the default value is -    %% so we don't happen to choose that particular value. -    {ok,[{linger,DefaultLinger}]} = inet:getopts(Sl, [linger]), -    TestLinger = case DefaultLinger of -		     {false,_} -> {true,5}; -		     {true,_} -> {false,0} -		 end, - -    case catch eldap:open([Host],  -			  [{port,Port},{tcpopts,[{linger,TestLinger}]}|Opts]) of -	{ok,H} -> -	    case gen_tcp:accept(Sl,1000) of -		{ok,_} ->  -		    case eldap:getopts(H, [{tcpopts,[linger]}]) of -			{ok,[{tcpopts,[{linger,ActualLinger}]}]} -> -			    case ActualLinger of -				TestLinger ->  -				    ok; -				DefaultLinger -> -				    ct:fail("eldap:getopts: 'linger' didn't change," -					    " got ~p (=default) expected ~p", -					    [ActualLinger,TestLinger]); -				_ -> -				    ct:fail("eldap:getopts: bad 'linger', got ~p expected ~p", -					    [ActualLinger,TestLinger]) -			    end; -			Other -> -			    ct:fail("eldap:getopts: bad result ~p",[Other]) -		    end; -		{error,timeout} ->  -		    ct:fail("server side accept timeout",[]) -	    end; - -	Other -> -	    ct:fail("eldap:open failed: ~p",[Other]) -    end. diff --git a/lib/eldap/test/eldap_misc_SUITE.erl b/lib/eldap/test/eldap_misc_SUITE.erl deleted file mode 100644 index ca810ee33c..0000000000 --- a/lib/eldap/test/eldap_misc_SUITE.erl +++ /dev/null @@ -1,51 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2012-2014. 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(eldap_misc_SUITE). - --compile(export_all). %% Use this only in test suites... - --include_lib("common_test/include/ct.hrl"). --include_lib("eldap/include/eldap.hrl"). --include_lib("eldap/ebin/ELDAPv3.hrl"). - -all() -> -    [ -     encode, -     decode -    ]. - - -encode(_Config) -> -    {ok,Bin} = 'ELDAPv3':encode('AddRequest', #'AddRequest'{entry="hejHopp"  ,attributes=[]} ), -    Expected = <<104,11,4,7,104,101,106,72,111,112,112,48,0>>, -    Expected = Bin. - -decode(_Config) -> -    {ok,Res} = 'ELDAPv3':decode('AddRequest', <<104,11,4,7,104,101,106,72,111,112,112,48,0>>), -    ct:log("Res = ~p", [Res]), -    Expected = #'AddRequest'{entry = "hejHopp",attributes = []}, -    case Res of -	Expected -> ok; -	#'AddRequest'{entry= <<"hejHopp">>, attributes=[]} ->  -	    {fail, "decoded to (correct) binary!!"}; -	_ -> -	    {fail, "Bad decode"} -    end. -	     diff --git a/lib/eldap/test/make_certs.erl b/lib/eldap/test/make_certs.erl index f963af180d..15a7e118ff 100644 --- a/lib/eldap/test/make_certs.erl +++ b/lib/eldap/test/make_certs.erl @@ -1,41 +1,89 @@  %%  %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2007-2013. All Rights Reserved. -%% +%%  +%% Copyright Ericsson AB 2007-2012. 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(make_certs). +-compile([export_all]). --export([all/2]). +%-export([all/1, all/2, rootCA/2, intermediateCA/3, endusers/3, enduser/3, revoke/3, gencrl/2, verify/3]). --record(dn, {commonName, +-record(config, {commonName,   	     organizationalUnitName = "Erlang OTP",  	     organizationName = "Ericsson AB",  	     localityName = "Stockholm",  	     countryName = "SE", -	     emailAddress = "[email protected]"}). +	     emailAddress = "[email protected]", +	     default_bits = 2048, +	     v2_crls = true, +	     ecc_certs = false, +	     issuing_distribution_point = false, +	     crl_port = 8000, +	     openssl_cmd = "openssl"}). + + +default_config() -> +    #config{}. + +make_config(Args) -> +    make_config(Args, #config{}). + +make_config([], C) -> +    C; +make_config([{organizationalUnitName, Name}|T], C) when is_list(Name) -> +    make_config(T, C#config{organizationalUnitName = Name}); +make_config([{organizationName, Name}|T], C) when is_list(Name) -> +    make_config(T, C#config{organizationName = Name}); +make_config([{localityName, Name}|T], C) when is_list(Name) -> +    make_config(T, C#config{localityName = Name}); +make_config([{countryName, Name}|T], C) when is_list(Name) -> +    make_config(T, C#config{countryName = Name}); +make_config([{emailAddress, Name}|T], C) when is_list(Name) -> +    make_config(T, C#config{emailAddress = Name}); +make_config([{default_bits, Bits}|T], C) when is_integer(Bits) -> +    make_config(T, C#config{default_bits = Bits}); +make_config([{v2_crls, Bool}|T], C) when is_boolean(Bool) -> +    make_config(T, C#config{v2_crls = Bool}); +make_config([{crl_port, Port}|T], C) when is_integer(Port) -> +    make_config(T, C#config{crl_port = Port}); +make_config([{ecc_certs, Bool}|T], C) when is_boolean(Bool) -> +    make_config(T, C#config{ecc_certs = Bool}); +make_config([{issuing_distribution_point, Bool}|T], C) when is_boolean(Bool) -> +    make_config(T, C#config{issuing_distribution_point = Bool}); +make_config([{openssl_cmd, Cmd}|T], C) when is_list(Cmd) -> +    make_config(T, C#config{openssl_cmd = Cmd}). + + +all([DataDir, PrivDir]) -> +    all(DataDir, PrivDir).  all(DataDir, PrivDir) -> -    OpenSSLCmd = "openssl", +    all(DataDir, PrivDir, #config{}). + +all(DataDir, PrivDir, C) when is_list(C) -> +    all(DataDir, PrivDir, make_config(C)); +all(DataDir, PrivDir, C = #config{}) -> +    ok = filelib:ensure_dir(filename:join(PrivDir, "erlangCA")),      create_rnd(DataDir, PrivDir),			% For all requests -    rootCA(PrivDir, OpenSSLCmd, "erlangCA"), -    intermediateCA(PrivDir, OpenSSLCmd, "otpCA", "erlangCA"), -    endusers(PrivDir, OpenSSLCmd, "otpCA", ["client", "server"]), -    collect_certs(PrivDir, ["erlangCA", "otpCA"], ["client", "server"]), -    %% Create keycert files +    rootCA(PrivDir, "erlangCA", C), +    intermediateCA(PrivDir, "otpCA", "erlangCA", C), +    endusers(PrivDir, "otpCA", ["client", "server", "revoked"], C), +    endusers(PrivDir, "erlangCA", ["localhost"], C), +    %% Create keycert files       SDir = filename:join([PrivDir, "server"]),      SC = filename:join([SDir, "cert.pem"]),      SK = filename:join([SDir, "key.pem"]), @@ -46,7 +94,14 @@ all(DataDir, PrivDir) ->      CK = filename:join([CDir, "key.pem"]),      CKC = filename:join([CDir, "keycert.pem"]),      append_files([CK, CC], CKC), -    remove_rnd(PrivDir). +    RDir = filename:join([PrivDir, "revoked"]), +    RC = filename:join([RDir, "cert.pem"]), +    RK = filename:join([RDir, "key.pem"]), +    RKC = filename:join([RDir, "keycert.pem"]), +    revoke(PrivDir, "otpCA", "revoked", C), +    append_files([RK, RC], RKC), +    remove_rnd(PrivDir), +    {ok, C}.  append_files(FileNames, ResultFileName) ->      {ok, ResultFile} = file:open(ResultFileName, [write]), @@ -59,117 +114,182 @@ do_append_files([F|Fs], RF) ->      ok = file:write(RF, Data),      do_append_files(Fs, RF). -rootCA(Root, OpenSSLCmd, Name) -> -    create_ca_dir(Root, Name, ca_cnf(Name)), -    DN = #dn{commonName = Name}, -    create_self_signed_cert(Root, OpenSSLCmd, Name, req_cnf(DN)), -    ok. +rootCA(Root, Name, C) -> +    create_ca_dir(Root, Name, ca_cnf(C#config{commonName = Name})), +    create_self_signed_cert(Root, Name, req_cnf(C#config{commonName = Name}), C), +    file:copy(filename:join([Root, Name, "cert.pem"]), filename:join([Root, Name, "cacerts.pem"])), +    gencrl(Root, Name, C). -intermediateCA(Root, OpenSSLCmd, CA, ParentCA) -> -    CA = "otpCA", -    create_ca_dir(Root, CA, ca_cnf(CA)), +intermediateCA(Root, CA, ParentCA, C) -> +    create_ca_dir(Root, CA, ca_cnf(C#config{commonName = CA})),      CARoot = filename:join([Root, CA]), -    DN = #dn{commonName = CA},      CnfFile = filename:join([CARoot, "req.cnf"]), -    file:write_file(CnfFile, req_cnf(DN)), -    KeyFile = filename:join([CARoot, "private", "key.pem"]), -    ReqFile =  filename:join([CARoot, "req.pem"]), -    create_req(Root, OpenSSLCmd, CnfFile, KeyFile, ReqFile), +    file:write_file(CnfFile, req_cnf(C#config{commonName = CA})), +    KeyFile = filename:join([CARoot, "private", "key.pem"]),  +    ReqFile =  filename:join([CARoot, "req.pem"]),  +    create_req(Root, CnfFile, KeyFile, ReqFile, C),      CertFile = filename:join([CARoot, "cert.pem"]), -    sign_req(Root, OpenSSLCmd, ParentCA, "ca_cert", ReqFile, CertFile). - -endusers(Root, OpenSSLCmd, CA, Users) -> -    lists:foreach(fun(User) -> enduser(Root, OpenSSLCmd, CA, User) end, Users). - -enduser(Root, OpenSSLCmd, CA, User) -> +    sign_req(Root, ParentCA, "ca_cert", ReqFile, CertFile, C), +    CACertsFile = filename:join(CARoot, "cacerts.pem"), +    file:copy(filename:join([Root, ParentCA, "cacerts.pem"]), CACertsFile), +    %% append this CA's cert to the cacerts file +    {ok, Bin} = file:read_file(CertFile), +    {ok, FD} = file:open(CACertsFile, [append]), +    file:write(FD, ["\n", Bin]), +    file:close(FD), +    gencrl(Root, CA, C). + +endusers(Root, CA, Users, C) -> +    [enduser(Root, CA, User, C) || User <- Users]. + +enduser(Root, CA, User, C) ->      UsrRoot = filename:join([Root, User]),      file:make_dir(UsrRoot),      CnfFile = filename:join([UsrRoot, "req.cnf"]), -    DN = #dn{commonName = User}, -    file:write_file(CnfFile, req_cnf(DN)), -    KeyFile = filename:join([UsrRoot, "key.pem"]), -    ReqFile =  filename:join([UsrRoot, "req.pem"]), -    create_req(Root, OpenSSLCmd, CnfFile, KeyFile, ReqFile), +    file:write_file(CnfFile, req_cnf(C#config{commonName = User})), +    KeyFile = filename:join([UsrRoot, "key.pem"]),  +    ReqFile =  filename:join([UsrRoot, "req.pem"]),  +    create_req(Root, CnfFile, KeyFile, ReqFile, C), +    %create_req(Root, CnfFile, KeyFile, ReqFile),      CertFileAllUsage =  filename:join([UsrRoot, "cert.pem"]), -    sign_req(Root, OpenSSLCmd, CA, "user_cert", ReqFile, CertFileAllUsage), +    sign_req(Root, CA, "user_cert", ReqFile, CertFileAllUsage, C),      CertFileDigitalSigOnly =  filename:join([UsrRoot, "digital_signature_only_cert.pem"]), -    sign_req(Root, OpenSSLCmd, CA, "user_cert_digital_signature_only", ReqFile, CertFileDigitalSigOnly). - -collect_certs(Root, CAs, Users) -> -    Bins = lists:foldr( -	     fun(CA, Acc) -> -		     File = filename:join([Root, CA, "cert.pem"]), -		     {ok, Bin} = file:read_file(File), -		     [Bin, "\n" | Acc] -	     end, [], CAs), -    lists:foreach( -      fun(User) -> -	      File = filename:join([Root, User, "cacerts.pem"]), -	      file:write_file(File, Bins) -      end, Users). +    sign_req(Root, CA, "user_cert_digital_signature_only", ReqFile, CertFileDigitalSigOnly, C), +    CACertsFile = filename:join(UsrRoot, "cacerts.pem"), +    file:copy(filename:join([Root, CA, "cacerts.pem"]), CACertsFile), +    ok. -create_self_signed_cert(Root, OpenSSLCmd, CAName, Cnf) -> +revoke(Root, CA, User, C) -> +    UsrCert = filename:join([Root, User, "cert.pem"]), +    CACnfFile = filename:join([Root, CA, "ca.cnf"]), +    Cmd = [C#config.openssl_cmd, " ca" +	   " -revoke ", UsrCert, +	   [" -crl_reason keyCompromise" || C#config.v2_crls ], +	   " -config ", CACnfFile], +    Env = [{"ROOTDIR", filename:absname(Root)}],  +    cmd(Cmd, Env), +    gencrl(Root, CA, C). + +gencrl(Root, CA, C) -> +    CACnfFile = filename:join([Root, CA, "ca.cnf"]), +    CACRLFile = filename:join([Root, CA, "crl.pem"]), +    Cmd = [C#config.openssl_cmd, " ca" +	   " -gencrl ", +	   " -crlhours 24", +	   " -out ", CACRLFile, +	   " -config ", CACnfFile], +    Env = [{"ROOTDIR", filename:absname(Root)}],  +    cmd(Cmd, Env). + +verify(Root, CA, User, C) -> +    CAFile = filename:join([Root, User, "cacerts.pem"]), +    CACRLFile = filename:join([Root, CA, "crl.pem"]), +    CertFile = filename:join([Root, User, "cert.pem"]), +    Cmd = [C#config.openssl_cmd, " verify" +	   " -CAfile ", CAFile, +	   " -CRLfile ", CACRLFile, %% this is undocumented, but seems to work +	   " -crl_check ", +	   CertFile], +    Env = [{"ROOTDIR", filename:absname(Root)}], +    try cmd(Cmd, Env) catch +	   exit:{eval_cmd, _, _} -> +		invalid +    end. + +create_self_signed_cert(Root, CAName, Cnf, C = #config{ecc_certs = true}) ->      CARoot = filename:join([Root, CAName]),      CnfFile = filename:join([CARoot, "req.cnf"]),      file:write_file(CnfFile, Cnf), -    KeyFile = filename:join([CARoot, "private", "key.pem"]), -    CertFile = filename:join([CARoot, "cert.pem"]), -    Cmd = [OpenSSLCmd, " req" +    KeyFile = filename:join([CARoot, "private", "key.pem"]),  +    CertFile = filename:join([CARoot, "cert.pem"]),  +    Cmd = [C#config.openssl_cmd, " ecparam" +	   " -out ", KeyFile, +	   " -name secp521r1 ", +	   %" -name sect283k1 ", +	   " -genkey "], +    Env = [{"ROOTDIR", filename:absname(Root)}],  +    cmd(Cmd, Env), + +    Cmd2 = [C#config.openssl_cmd, " req" +	   " -new" +	   " -x509" +	   " -config ", CnfFile, +	   " -key ", KeyFile,  +		 " -outform PEM ", +	   " -out ", CertFile],  +    cmd(Cmd2, Env); +create_self_signed_cert(Root, CAName, Cnf, C) -> +    CARoot = filename:join([Root, CAName]), +    CnfFile = filename:join([CARoot, "req.cnf"]), +    file:write_file(CnfFile, Cnf), +    KeyFile = filename:join([CARoot, "private", "key.pem"]),  +    CertFile = filename:join([CARoot, "cert.pem"]),  +    Cmd = [C#config.openssl_cmd, " req"  	   " -new"  	   " -x509"  	   " -config ", CnfFile,  	   " -keyout ", KeyFile, -	   " -out ", CertFile], -    Env = [{"ROOTDIR", Root}], -    cmd(Cmd, Env), -    fix_key_file(OpenSSLCmd, KeyFile). - -% openssl 1.0 generates key files in pkcs8 format by default and we don't handle this format -fix_key_file(OpenSSLCmd, KeyFile) -> -    KeyFileTmp = KeyFile ++ ".tmp", -    Cmd = [OpenSSLCmd, " rsa", -           " -in ", -           KeyFile, -           " -out ", -           KeyFileTmp], -    cmd(Cmd, []), -    ok = file:rename(KeyFileTmp, KeyFile). +	   " -outform PEM", +	   " -out ", CertFile],  +    Env = [{"ROOTDIR", filename:absname(Root)}],   +    cmd(Cmd, Env). +  create_ca_dir(Root, CAName, Cnf) ->      CARoot = filename:join([Root, CAName]), +    ok = filelib:ensure_dir(CARoot),      file:make_dir(CARoot),      create_dirs(CARoot, ["certs", "crl", "newcerts", "private"]),      create_rnd(Root, filename:join([CAName, "private"])),      create_files(CARoot, [{"serial", "01\n"}, +			  {"crlnumber", "01"},  			  {"index.txt", ""},  			  {"ca.cnf", Cnf}]). -create_req(Root, OpenSSLCmd, CnfFile, KeyFile, ReqFile) -> -    Cmd = [OpenSSLCmd, " req" +create_req(Root, CnfFile, KeyFile, ReqFile, C = #config{ecc_certs = true}) -> +    Cmd = [C#config.openssl_cmd, " ecparam" +	   " -out ", KeyFile, +	   " -name secp521r1 ", +	   %" -name sect283k1 ", +	   " -genkey "], +    Env = [{"ROOTDIR", filename:absname(Root)}],  +    cmd(Cmd, Env), +    Cmd2 = [C#config.openssl_cmd, " req" +	   " -new ", +	   " -key ", KeyFile, +	   " -outform PEM ", +	   " -out ", ReqFile, +	   " -config ", CnfFile], +    cmd(Cmd2, Env); +    %fix_key_file(KeyFile). +create_req(Root, CnfFile, KeyFile, ReqFile, C) -> +    Cmd = [C#config.openssl_cmd, " req"  	   " -new"  	   " -config ", CnfFile, -	   " -keyout ", KeyFile, -	   " -out ", ReqFile], -    Env = [{"ROOTDIR", Root}], -    cmd(Cmd, Env), -    fix_key_file(OpenSSLCmd, KeyFile). +	   " -outform PEM ", +	   " -keyout ", KeyFile,  +	   " -out ", ReqFile],  +    Env = [{"ROOTDIR", filename:absname(Root)}],  +    cmd(Cmd, Env). +    %fix_key_file(KeyFile). + -sign_req(Root, OpenSSLCmd, CA, CertType, ReqFile, CertFile) -> +sign_req(Root, CA, CertType, ReqFile, CertFile, C) ->      CACnfFile = filename:join([Root, CA, "ca.cnf"]), -    Cmd = [OpenSSLCmd, " ca" +    Cmd = [C#config.openssl_cmd, " ca"  	   " -batch"  	   " -notext" -	   " -config ", CACnfFile, +	   " -config ", CACnfFile,   	   " -extensions ", CertType, -	   " -in ", ReqFile, +	   " -in ", ReqFile,   	   " -out ", CertFile], -    Env = [{"ROOTDIR", Root}], +    Env = [{"ROOTDIR", filename:absname(Root)}],       cmd(Cmd, Env). - +      %%  %%  Misc  %% - +      create_dirs(Root, Dirs) ->      lists:foreach(fun(Dir) ->  			  file:make_dir(filename:join([Root, Dir])) end, @@ -192,30 +312,30 @@ remove_rnd(Dir) ->  cmd(Cmd, Env) ->      FCmd = lists:flatten(Cmd), -    Port = open_port({spawn, FCmd}, [stream, eof, exit_status, stderr_to_stdout, +    Port = open_port({spawn, FCmd}, [stream, eof, exit_status, stderr_to_stdout,   				    {env, Env}]), -    eval_cmd(Port). +    eval_cmd(Port, FCmd). -eval_cmd(Port) -> -    receive +eval_cmd(Port, Cmd) -> +    receive   	{Port, {data, _}} -> -	    eval_cmd(Port); +	    eval_cmd(Port, Cmd);  	{Port, eof} ->  	    ok      end,      receive  	{Port, {exit_status, Status}} when Status /= 0 ->  	    %% io:fwrite("exit status: ~w~n", [Status]), -	    exit({eval_cmd, Status}) +	    exit({eval_cmd, Cmd, Status})      after 0 ->  	    ok      end.  %% -%% Contents of configuration files +%% Contents of configuration files   %% -req_cnf(DN) -> +req_cnf(C) ->      ["# Purpose: Configuration for requests (end users and CAs)."       "\n"       "ROOTDIR	        = $ENV::ROOTDIR\n" @@ -224,10 +344,10 @@ req_cnf(DN) ->       "[req]\n"       "input_password	= secret\n"       "output_password	= secret\n" -     "default_bits	= 1024\n" +     "default_bits	= ", integer_to_list(C#config.default_bits), "\n"       "RANDFILE		= $ROOTDIR/RAND\n"       "encrypt_key	= no\n" -     "default_md	= sha1\n" +     "default_md	= md5\n"       "#string_mask	= pkix\n"       "x509_extensions	= ca_ext\n"       "prompt		= no\n" @@ -235,12 +355,12 @@ req_cnf(DN) ->       "\n"       "[name]\n" -     "commonName		= ", DN#dn.commonName, "\n" -     "organizationalUnitName	= ", DN#dn.organizationalUnitName, "\n" -     "organizationName	        = ", DN#dn.organizationName, "\n" -     "localityName		= ", DN#dn.localityName, "\n" -     "countryName		= ", DN#dn.countryName, "\n" -     "emailAddress		= ", DN#dn.emailAddress, "\n" +     "commonName		= ", C#config.commonName, "\n" +     "organizationalUnitName	= ", C#config.organizationalUnitName, "\n" +     "organizationName	        = ", C#config.organizationName, "\n"  +     "localityName		= ", C#config.localityName, "\n" +     "countryName		= ", C#config.countryName, "\n" +     "emailAddress		= ", C#config.emailAddress, "\n"       "\n"       "[ca_ext]\n" @@ -249,8 +369,7 @@ req_cnf(DN) ->       "subjectKeyIdentifier = hash\n"       "subjectAltName	= email:copy\n"]. - -ca_cnf(CA) -> +ca_cnf(C) ->      ["# Purpose: Configuration for CAs.\n"       "\n"       "ROOTDIR	        = $ENV::ROOTDIR\n" @@ -258,21 +377,23 @@ ca_cnf(CA) ->       "\n"       "[ca]\n" -     "dir		= $ROOTDIR/", CA, "\n" +     "dir		= $ROOTDIR/", C#config.commonName, "\n"       "certs		= $dir/certs\n"       "crl_dir	        = $dir/crl\n"       "database	        = $dir/index.txt\n"       "new_certs_dir	= $dir/newcerts\n"       "certificate	= $dir/cert.pem\n"       "serial		= $dir/serial\n" -     "crl		= $dir/crl.pem\n" +     "crl		= $dir/crl.pem\n", +     ["crlnumber		= $dir/crlnumber\n" || C#config.v2_crls],       "private_key	= $dir/private/key.pem\n"       "RANDFILE	        = $dir/private/RAND\n"       "\n" -     "x509_extensions   = user_cert\n" +     "x509_extensions   = user_cert\n", +     ["crl_extensions = crl_ext\n" || C#config.v2_crls],       "unique_subject  = no\n"       "default_days	= 3600\n" -     "default_md	= sha1\n" +     "default_md	= md5\n"       "preserve	        = no\n"       "policy		= policy_match\n"       "\n" @@ -286,6 +407,13 @@ ca_cnf(CA) ->       "emailAddress		= supplied\n"       "\n" +     "[crl_ext]\n" +     "authorityKeyIdentifier=keyid:always,issuer:always\n", +     ["issuingDistributionPoint=critical, @idpsec\n" || C#config.issuing_distribution_point], + +     "[idpsec]\n" +     "fullname=URI:http://localhost:8000/",C#config.commonName,"/crl.pem\n" +       "[user_cert]\n"       "basicConstraints	= CA:false\n"       "keyUsage 		= nonRepudiation, digitalSignature, keyEncipherment\n" @@ -293,6 +421,12 @@ ca_cnf(CA) ->       "authorityKeyIdentifier = keyid,issuer:always\n"       "subjectAltName	= email:copy\n"       "issuerAltName	= issuer:copy\n" +     "crlDistributionPoints=@crl_section\n" + +     "[crl_section]\n" +     %% intentionally invalid +     "URI.1=http://localhost/",C#config.commonName,"/crl.pem\n" +     "URI.2=http://localhost:",integer_to_list(C#config.crl_port),"/",C#config.commonName,"/crl.pem\n"       "\n"       "[user_cert_digital_signature_only]\n" @@ -310,4 +444,7 @@ ca_cnf(CA) ->       "subjectKeyIdentifier = hash\n"       "authorityKeyIdentifier = keyid:always,issuer:always\n"       "subjectAltName	= email:copy\n" -     "issuerAltName	= issuer:copy\n"]. +     "issuerAltName	= issuer:copy\n" +     "crlDistributionPoints=@crl_section\n" +    ]. +  | 
