diff options
63 files changed, 2981 insertions, 202 deletions
diff --git a/INSTALL-WIN32.md b/INSTALL-WIN32.md index 93b7e3a1e1..4e14c6fee4 100644 --- a/INSTALL-WIN32.md +++ b/INSTALL-WIN32.md @@ -211,10 +211,10 @@ Frequently Asked Questions A: For Cygwin and Msys alike, we try to use the latest releases available when building. What versions you use shouldn't really matter, I try to include workarounds for the bugs I've found in - different Cygwin/Msys releases, please help me to add workarounds + different Cygwin/Msys releases, please help me add workarounds for new Cygwin/Msys-related bugs as soon as you encounter them. Also please do submit bug reports to the appropriate Cygwin - adn/or Msys developers. The GCC we used for %OTP-REL% was version + and/or Msys developers. The GCC we used for %OTP-REL% was version 4.7.0 (MinGW 64bit) and 4.3.4 (Cygwin 32bit). We used VC++ 10.0 (i.e. Visual studio 2010), Sun's JDK 1.5.0\_17 (32bit) and Sun's JDK 1.7.0\_1 (64bit), NSIS 2.46, and Win32 OpenSSL 0.9.8r. Please @@ -291,7 +291,7 @@ OpenSSL. Well' here's the list: used but Sun's Java compiler and virtual machine... If you are going to build a 64bit Windows version, you should make - sure to get MinGWs 64bit gcc installed with cygwin. It's in one of + sure to get MinGW's 64bit gcc installed with cygwin. It's in one of the development packages. URL: <http://www.cygwin.com> @@ -428,7 +428,7 @@ OpenSSL. Well' here's the list: e.g. `C:\Program`, they might still reside in `C:\Program Files` in reality... - If you are building a 64 bit verision of Erlang, you should set up + If you are building a 64 bit version of Erlang, you should set up PATHs etc a little differently. I use the following script to make things work in both Cygwin and Msys: @@ -977,7 +977,7 @@ URL:<http://code.google.com/p/msysgit/> that makes a nice Git port. The msys prompt you get from MsysGIT is however not compatible with the full version from MinGW, so you will -need to check out files using MsysGITs command prompt and then switch +need to check out files using MsysGIT's command prompt and then switch to a common Msys command prompt for building. Also all test suites cannot be built as MsysGIT/Msys does not handle symbolic links. To build test suites on Windows, you will need Cygwin for now. Hopefully @@ -1002,11 +1002,11 @@ deserved. Of course this would have been completely impossible without the excellent Cygwin. The guys at Cygnus solutions and -Redhat deserves a huge THANKS! as well as all the other people in the +Redhat deserve a huge THANKS! as well as all the other people in the free software community who have helped in creating the magnificent software that constitutes Cygwin. -Also the people developing the alternative command prompt Msys anfd +Also the people developing the alternative command prompt Msys and the MinGW compiler are worth huge THANKS! The 64bit port would have been impossible without the 64bit MinGW compiler. diff --git a/erts/doc/src/driver.xml b/erts/doc/src/driver.xml index ac5729880d..52283879c7 100644 --- a/erts/doc/src/driver.xml +++ b/erts/doc/src/driver.xml @@ -31,7 +31,7 @@ <note><p>This document was written a long time ago. A lot of it is still interesting since it explains important concepts, but it was - written for an older driver interface so the examples does not + written for an older driver interface so the examples do not work anymore. The reader is encouraged to read <seealso marker="erl_driver">erl_driver</seealso> and the <seealso marker="driver_entry">driver_entry</seealso> documentation. diff --git a/erts/doc/src/erl_driver.xml b/erts/doc/src/erl_driver.xml index b5df4ca0c8..4fd74b783e 100644 --- a/erts/doc/src/erl_driver.xml +++ b/erts/doc/src/erl_driver.xml @@ -43,8 +43,8 @@ to the runtime system at driver initialization, and some new driver API functions. </p> <note> - <p>As of erts version 5.9 old drivers has to be recompiled - and has to use the extended interface. They also has to be + <p>As of erts version 5.9 old drivers have to be recompiled + and have to use the extended interface. They also have to be adjusted to the <seealso marker="#rewrites_for_64_bits">64-bit capable driver interface. </seealso> @@ -283,7 +283,7 @@ called return garbage sizes to the emulator causing it to read random memory and create huge incorrect result blobs.</p> <p>Therefore it is not enough to just recompile drivers written with - version management for pre-R15B types; the types has to be changed + version management for pre-R15B types; the types have to be changed in the driver suggesting other rewrites especially regarding size variables. Investigate all warnings when recompiling!</p> <p>Also, the API driver functions <c>driver_output*</c>, @@ -356,19 +356,19 @@ <p> Driver callback <c><seealso marker="driver_entry#output">output</seealso></c> - now gets <c>ErlDrvSizeT</c> as 3:rd argument instead + now gets <c>ErlDrvSizeT</c> as 3rd argument instead of previously <c>int</c>. </p> <p> Driver callback <c><seealso marker="driver_entry#control">control</seealso></c> - now gets <c>ErlDrvSizeT</c> as 4:th and 6:th arguments instead + now gets <c>ErlDrvSizeT</c> as 4th and 6th arguments instead of previously <c>int</c>. </p> <p> Driver callback <c><seealso marker="driver_entry#call">call</seealso></c> - now gets <c>ErlDrvSizeT</c> as 4:th and 6:th arguments instead + now gets <c>ErlDrvSizeT</c> as 4th and 6th arguments instead of previously <c>int</c>. </p> <p> @@ -410,54 +410,54 @@ <tag>Arguments and return values in the driver API</tag> <item> <p> - Many driver API functions has changed argument type + Many driver API functions have changed argument type and/or return value to <c>ErlDrvSizeT</c> from mostly <c>int</c>. Automatic type casting probably makes these changes necessary only for a driver that encounters sizes larger than 32 bits. </p> <taglist> <tag><seealso marker="#driver_output">driver_output</seealso></tag> - <item>3:rd argument</item> + <item>3rd argument</item> <tag><seealso marker="#driver_output2">driver_output2</seealso></tag> - <item>3:rd and 5:th arguments</item> + <item>3rd and 5th arguments</item> <tag> <seealso marker="#driver_output_binary">driver_output_binary</seealso> </tag> - <item>3:rd 5:th and 6:th arguments</item> + <item>3rd 5th and 6th arguments</item> <tag><seealso marker="#driver_outputv">driver_outputv</seealso></tag> - <item>3:rd and 5:th arguments</item> + <item>3rd and 5th arguments</item> <tag> <seealso marker="#driver_vec_to_buf">driver_vec_to_buf</seealso> </tag> - <item>3:rd argument and return value</item> + <item>3rd argument and return value</item> <tag><seealso marker="#driver_alloc">driver_alloc</seealso></tag> - <item>1:st argument</item> + <item>1st argument</item> <tag><seealso marker="#driver_realloc">driver_realloc</seealso></tag> - <item>2:nd argument</item> + <item>2nd argument</item> <tag> <seealso marker="#driver_alloc_binary">driver_alloc_binary</seealso> </tag> - <item>1:st argument</item> + <item>1st argument</item> <tag> <seealso marker="#driver_realloc_binary">driver_realloc_binary</seealso> </tag> - <item>2:nd argument</item> + <item>2nd argument</item> <tag><seealso marker="#driver_enq">driver_enq</seealso></tag> - <item>3:rd argument</item> + <item>3rd argument</item> <tag><seealso marker="#driver_pushq">driver_pushq</seealso></tag> - <item>3:rd argument</item> + <item>3rd argument</item> <tag><seealso marker="#driver_deq">driver_deq</seealso></tag> - <item>2:nd argument and return value</item> + <item>2nd argument and return value</item> <tag><seealso marker="#driver_sizeq">driver_sizeq</seealso></tag> <item>return value</item> <tag><seealso marker="#driver_enq_bin">driver_enq_bin</seealso></tag> - <item>3:rd and 4:th argument</item> + <item>3rd and 4th argument</item> <tag><seealso marker="#driver_pushq_bin">driver_pushq_bin</seealso></tag> - <item>3:rd and 4:th argument</item> + <item>3rd and 4th argument</item> <tag><seealso marker="#driver_enqv">driver_enqv</seealso></tag> - <item>3:rd argument</item> + <item>3rd argument</item> <tag><seealso marker="#driver_pushqv">driver_pushqv</seealso></tag> - <item>3:rd argument</item> + <item>3rd argument</item> <tag><seealso marker="#driver_peekqv">driver_peekqv</seealso></tag> <item>return value</item> </taglist> diff --git a/erts/test/ethread_SUITE.erl b/erts/test/ethread_SUITE.erl index 84b08d93d5..ad01854f9f 100644 --- a/erts/test/ethread_SUITE.erl +++ b/erts/test/ethread_SUITE.erl @@ -246,9 +246,16 @@ dw_atomic_massage(Config) -> %% %% -init_per_testcase(_Case, Config) -> - Dog = ?t:timetrap(?DEFAULT_TIMEOUT), - [{watchdog, Dog}|Config]. +init_per_testcase(Case, Config) -> + case inet:gethostname() of + {ok,"fenris"} when Case == max_threads -> + %% Cannot use os:type+os:version as not all + %% solaris10 machines are buggy. + {skip, "This machine is buggy"}; + _Else -> + Dog = ?t:timetrap(?DEFAULT_TIMEOUT), + [{watchdog, Dog}|Config] + end. end_per_testcase(_Case, Config) -> Dog = ?config(watchdog, Config), diff --git a/lib/Makefile b/lib/Makefile index aa4e074830..3753bd165b 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -1,7 +1,7 @@ # # %CopyrightBegin% # -# Copyright Ericsson AB 1996-2011. All Rights Reserved. +# Copyright Ericsson AB 1996-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 @@ -31,7 +31,7 @@ ifdef BUILD_ALL cosTransactions cosEvent cosTime cosNotification \ cosProperty cosFileTransfer cosEventDomain et megaco webtool \ xmerl edoc eunit ssh inviso typer erl_docgen \ - percept dialyzer hipe + percept eldap dialyzer hipe EXTRA_FILE := $(wildcard EXTRA-APPLICATIONS) EXTRA_APPLICATIONS := $(if $(EXTRA_FILE),$(shell cat $(EXTRA_FILE))) endif diff --git a/lib/asn1/src/asn1_db.erl b/lib/asn1/src/asn1_db.erl index 0862ef6e02..843fc66c9c 100644 --- a/lib/asn1/src/asn1_db.erl +++ b/lib/asn1/src/asn1_db.erl @@ -48,11 +48,19 @@ dbstop() -> Resp = req(stop), erase(?MODULE), Resp. %% Internal functions req(Request) -> - get(?MODULE) ! {self(), Request}, - receive {?MODULE, Reply} -> Reply after 5000 -> exit(db_timeout) end. + DbPid = get(?MODULE), + Ref = erlang:monitor(process,DbPid), + get(?MODULE) ! {{Ref, self()}, Request}, + receive + {{Ref,?MODULE}, Reply} -> + erlang:demonitor(Ref,[flush]), + Reply; + {'DOWN',Ref,_,_,Info} -> + exit({db_error,Info}) + end. -reply(From, Response) -> - From ! {?MODULE, Response}. +reply({Ref,From}, Response) -> + From ! {{Ref,?MODULE}, Response}. init(Parent, Includes) -> MRef = erlang:monitor(process, Parent), diff --git a/lib/asn1/src/asn1ct.erl b/lib/asn1/src/asn1ct.erl index 2d17f73a2c..99755a95a6 100644 --- a/lib/asn1/src/asn1ct.erl +++ b/lib/asn1/src/asn1ct.erl @@ -816,7 +816,9 @@ check({true,M},File,OutFile,Includes,EncodingRule,DbFile,Options,InputMods) -> asn1_db:dbsave(DbFile,M#module.name), verbose("--~p--~n",[{generated,DbFile}],Options), {true,{M,NewM,GenTypeOrVal}} - end + end; + ErrorList = {error,_} -> + {false,ErrorList} end; check({false,M},_,_,_,_,_,_,_) -> {false,M}. diff --git a/lib/asn1/src/asn1ct_check.erl b/lib/asn1/src/asn1ct_check.erl index c223561f12..187339fb53 100644 --- a/lib/asn1/src/asn1ct_check.erl +++ b/lib/asn1/src/asn1ct_check.erl @@ -4399,22 +4399,22 @@ constraint_union(_S,C) -> constraint_union1(S,[A={'ValueRange',_},union,B={'ValueRange',_}|Rest],Acc) -> AunionB = constraint_union_vr([A,B]), - constraint_union1(S,Rest,AunionB++Acc); + constraint_union1(S,Rest,Acc ++ AunionB); constraint_union1(S,[A={'SingleValue',_},union,B={'SingleValue',_}|Rest],Acc) -> AunionB = constraint_union_sv(S,[A,B]), - constraint_union1(S,Rest,AunionB++Acc); + constraint_union1(S,Rest,Acc ++ AunionB); constraint_union1(S,[A={'SingleValue',_},union,B={'ValueRange',_}|Rest],Acc) -> AunionB = union_sv_vr(S,A,B), - constraint_union1(S,Rest,AunionB++Acc); + constraint_union1(S,Rest,Acc ++ AunionB); constraint_union1(S,[A={'ValueRange',_},union,B={'SingleValue',_}|Rest],Acc) -> AunionB = union_sv_vr(S,B,A), - constraint_union1(S,Rest,AunionB++Acc); + constraint_union1(S,Rest,Acc ++ AunionB); constraint_union1(S,[union|Rest],Acc) -> %skip when unsupported constraints constraint_union1(S,Rest,Acc); constraint_union1(S,[A|Rest],Acc) -> constraint_union1(S,Rest,[A|Acc]); constraint_union1(_S,[],Acc) -> - lists:reverse(Acc). + Acc. constraint_union_sv(_S,SV) -> Values=lists:map(fun({_,V})->V end,SV), @@ -4467,63 +4467,33 @@ constraint_union_vr([{_,{_,Ub2}}|Rest],A=[{_,{_,Ub1}}|_Acc]) when Ub2=<Ub1-> constraint_union_vr([VR|Rest],Acc) -> constraint_union_vr(Rest,[VR|Acc]). -union_sv_vr(_S,C1={'SingleValue',SV},C2={'ValueRange',VR={Lb,Ub}}) +union_sv_vr(_S,{'SingleValue',SV},VR) when is_integer(SV) -> - case is_int_in_vr(SV,C2) of - true -> [C2]; - _ -> - case VR of - {'MIN',Ub} when SV==Ub+1 -> [{'ValueRange',{'MIN',SV}}]; - {Lb,'MAX'} when SV==Lb-1 -> [{'ValueRange',{SV,'MAX'}}]; - {Lb,Ub} when SV==Ub+1 -> [{'ValueRange',{Lb,SV}}]; - {Lb,Ub} when SV==Lb-1 -> [{'ValueRange',{SV,Ub}}]; - _ -> - [C1,C2] - end - end; -union_sv_vr(_S,C1={'SingleValue',SV},C2={'ValueRange',{_Lb,_Ub}}) + union_sv_vr(_S,{'SingleValue',[SV]},VR); +union_sv_vr(_S,{'SingleValue',SV},{'ValueRange',{VLb,VUb}}) when is_list(SV) -> - case lists:filter(fun(X)->is_int_in_vr(X,C2) end,SV) of - [] -> [C2]; - L -> - case expand_vr(L,C2) of - {[],C3} -> [C3]; - {L,C2} -> [C1,C2]; - {[Val],C3} -> [{'SingleValue',Val},C3]; - {L2,C3} -> [{'SingleValue',L2},C3] - end - end. - -expand_vr(L,VR={_,{Lb,Ub}}) -> - case lower_Lb(L,Lb) of - false -> - case higher_Ub(L,Ub) of - false -> - {L,VR}; - {L1,UbNew} -> - expand_vr(L1,{'ValueRange',{Lb,UbNew}}) - end; - {L1,LbNew} -> - expand_vr(L1,{'ValueRange',{LbNew,Ub}}) - end. - -lower_Lb(_,'MIN') -> - false; -lower_Lb(L,Lb) -> - remove_val_from_list(Lb - 1,L). - -higher_Ub(_,'MAX') -> - false; -higher_Ub(L,Ub) -> - remove_val_from_list(Ub + 1,L). + L = lists:sort(SV++[VLb,VUb]), + {Lb,L1} = case lists:member('MIN',L) of + true -> {'MIN',L--['MIN']}; % remove 'MIN' so it does not disturb + false -> {hd(L),tl(L)} + end, + Ub = case lists:member('MAX',L1) of + true -> 'MAX'; + false -> lists:last(L1) + end, + case SV of + [H] -> H; + _ -> SV + end, + %% for now we through away the Singlevalues so that they don't disturb + %% in the code generating phase (the effective Valuerange is already + %% calculated. If we want to keep the Singlevalues as well for + %% use in code gen phases we need to introduce a new representation + %% like {'ValueRange',{Lb,Ub},[ListOfRanges|AntiValues|Singlevalues] + %% These could be used to generate guards which allows only the specific + %% values , not the full range + [{'ValueRange',{Lb,Ub}}]. -remove_val_from_list(Val,List) -> - case lists:member(Val,List) of - true -> - {lists:delete(Val,List),Val}; - false -> - false - end. %% get_constraints/2 %% Arguments are a list of constraints, which has the format {key,value}, diff --git a/lib/asn1/test/asn1_SUITE_data/Constraints.py b/lib/asn1/test/asn1_SUITE_data/Constraints.py index b18c29bd89..de48c4c2ca 100644 --- a/lib/asn1/test/asn1_SUITE_data/Constraints.py +++ b/lib/asn1/test/asn1_SUITE_data/Constraints.py @@ -51,6 +51,7 @@ Thing ::= INTEGER {fred (0),fred2 (1),fred3 (2)} AnotherThing ::= Thing (fred | fred2) I ::= INTEGER (0|15..269) -- OTP-5457 +X1 ::= INTEGER (1..4 | 8 | 10 | 20) -- OTP-9946 -- OTP-5511 diff --git a/lib/asn1/test/testConstraints.erl b/lib/asn1/test/testConstraints.erl index cb15a24359..1ce68ec522 100644 --- a/lib/asn1/test/testConstraints.erl +++ b/lib/asn1/test/testConstraints.erl @@ -127,17 +127,26 @@ int_constraints(Rules) -> ?line {ok,0} = asn1_wrapper:decode('Constraints','I',Bytes12), ?line {ok,Bytes13} = asn1_wrapper:encode('Constraints','I',20), ?line {ok,20} = asn1_wrapper:decode('Constraints','I',Bytes13), - + + %%========================================================== + %% Constraint Combinations (Duboisson p. 285) + %% X1 ::= INTEGER (1..4|8|10|20) + %%========================================================== + + ?line {ok,Bytes14} = asn1_wrapper:encode('Constraints','X1',1), + ?line {ok,1} = asn1_wrapper:decode('Constraints','X1',Bytes14), + ?line {ok,Bytes15} = asn1_wrapper:encode('Constraints','X1',20), + ?line {ok,20} = asn1_wrapper:decode('Constraints','X1',Bytes15), %%========================================================== %% SIZE Constraint (Duboisson p. 268) %% T ::= IA5String (SIZE (1|2, ..., SIZE (1|2|3))) %% T2 ::= IA5String (SIZE (1|2, ..., 3)) %%========================================================== - ?line {ok,Bytes14} = asn1_wrapper:encode('Constraints','T',"IA"), - ?line {ok,"IA"} = asn1_wrapper:decode('Constraints','T',Bytes14), - ?line {ok,Bytes15} = asn1_wrapper:encode('Constraints','T2',"IA"), - ?line {ok,"IA"} = asn1_wrapper:decode('Constraints','T2',Bytes15). + ?line {ok,Bytes16} = asn1_wrapper:encode('Constraints','T',"IA"), + ?line {ok,"IA"} = asn1_wrapper:decode('Constraints','T',Bytes16), + ?line {ok,Bytes17} = asn1_wrapper:encode('Constraints','T2',"IA"), + ?line {ok,"IA"} = asn1_wrapper:decode('Constraints','T2',Bytes17). refed_NNL_name(_Erule) -> diff --git a/lib/common_test/doc/src/basics_chapter.xml b/lib/common_test/doc/src/basics_chapter.xml index 20141d2561..d43ac44ac5 100644 --- a/lib/common_test/doc/src/basics_chapter.xml +++ b/lib/common_test/doc/src/basics_chapter.xml @@ -141,8 +141,9 @@ individual test case. </p> <p> - The test suite module must conform to a callback interface specified - by the CT test server. See the + The test suite module must conform to a + <seealso marker="common_test">callback interface</seealso> + specified by the CT test server. See the <seealso marker="write_test_chapter#intro">Writing Test Suites</seealso> chapter for more information. </p> diff --git a/lib/common_test/doc/src/ct_hooks_chapter.xml b/lib/common_test/doc/src/ct_hooks_chapter.xml index c5b4fd0073..cb06e570bd 100644 --- a/lib/common_test/doc/src/ct_hooks_chapter.xml +++ b/lib/common_test/doc/src/ct_hooks_chapter.xml @@ -189,6 +189,22 @@ it.</p> </section> + <section> + <title>External configuration data and Logging</title> + <p>It's possible in the CTH to read configuration data values + by calling <c>ct:get_config/1/2/3</c> (as explained in the + <seealso marker="config_file_chapter#require_config_data"> + External configuration data</seealso> + chapter). The config variables in question must, as always, first have been + <c>required</c> by means of a suite-, group-, or test case info function, + or the <c>ct:require/1/2</c> function. Note that the latter can also be used + in CT hook functions.</p> + <p>The CT hook functions may call any of the logging functions available + in the <c>ct</c> interface to print information to the log files, or to + add comments in the suite overview page. + </p> + </section> + </section> <marker id="manipulating"/> @@ -201,11 +217,13 @@ functions for a CTH follow a common interface, this interface is described below.</p> - <p>It is only possible to hook into test function which exists in the test - suite. So in order for a CTH to hook in before - <seealso marker="common_test#Module:init_per_suite-1">init_per_suite</seealso>, - the <seealso marker="common_test#Module:init_per_suite-1">init_per_suite</seealso> - function must exist in the test suite.</p> + <p>Common Test will always call all available hook functions, even pre- and post + hooks for configuration functions that are not implemented in the suite. + For example, <c>pre_init_per_suite(x_SUITE, ...)</c> and + <c>post_init_per_suite(x_SUITE, ...)</c> will be called for test suite + <c>x_SUITE</c>, even if it doesn't export <c>init_per_suite/1</c>. This feature + makes it possible to use hooks as configuration fallbacks, or even + completely replace all configuration functions with hook functions.</p> <marker id="pre"/> <section> diff --git a/lib/common_test/doc/src/ct_run.xml b/lib/common_test/doc/src/ct_run.xml index b01ab3667d..4d4521957c 100644 --- a/lib/common_test/doc/src/ct_run.xml +++ b/lib/common_test/doc/src/ct_run.xml @@ -103,6 +103,7 @@ [-no_auto_compile] [-muliply_timetraps Multiplier] [-scale_timetraps] + [-create_priv_dir auto_per_run | auto_per_tc | manual_per_tc] [-repeat N [-force_stop]] | [-duration HHMMSS [-force_stop]] | [-until [YYMoMoDD]HHMMSS [-force_stop]] @@ -130,6 +131,7 @@ [-no_auto_compile] [-muliply_timetraps Multiplier] [-scale_timetraps] + [-create_priv_dir auto_per_run | auto_per_tc | manual_per_tc] [-repeat N [-force_stop]] | [-duration HHMMSS [-force_stop]] | [-until [YYMoMoDD]HHMMSS [-force_stop]] @@ -150,6 +152,7 @@ [-no_auto_compile] [-muliply_timetraps Multiplier] [-scale_timetraps] + [-create_priv_dir auto_per_run | auto_per_tc | manual_per_tc] [-basic_html]</pre> </section> <section> diff --git a/lib/common_test/doc/src/getting_started_chapter.xml b/lib/common_test/doc/src/getting_started_chapter.xml index 7de0912036..7919edd183 100644 --- a/lib/common_test/doc/src/getting_started_chapter.xml +++ b/lib/common_test/doc/src/getting_started_chapter.xml @@ -90,7 +90,7 @@ <p>As you can understand from the illustration above, Common Test requires that a test case generates a runtime error to indicate failure (e.g. by causing a bad match error or by calling <c>exit/1</c>, preferrably - through the <c>ct:fail/1/2</c> help function). A succesful execution is + through the <c>ct:fail/1,2</c> help function). A succesful execution is indicated by means of a normal return from the test case function. </p> </section> @@ -98,7 +98,8 @@ <section> <title>A simple test suite</title> <p>As you've seen in the basics chapter, the test suite module implements - callback functions (mandatory or optional) for various purposes, e.g: + <seealso marker="common_test">callback functions</seealso> + (mandatory or optional) for various purposes, e.g: <list> <item>Init/end configuration function for the test suite</item> <item>Init/end configuration function for a test case</item> @@ -197,7 +198,7 @@ <pre> 1> ct:run_test([{suite, "check_log_SUITE"}]).</pre> <p> - The result from running our test is printed in log files on HTML format + The result from running our test is printed in log files in HTML format (stored in unique log directories on different level). This illustration shows the log file structure: </p> diff --git a/lib/common_test/doc/src/run_test_chapter.xml b/lib/common_test/doc/src/run_test_chapter.xml index 848f278fa6..b3ec524b21 100644 --- a/lib/common_test/doc/src/run_test_chapter.xml +++ b/lib/common_test/doc/src/run_test_chapter.xml @@ -466,6 +466,9 @@ {logdir, LogDir}. {logdir, NodeRefs, LogDir}. + + {create_priv_dir, PrivDirOption}. + {create_priv_dir, NodeRefs, PrivDirOption}. {event_handler, EventHandlers}. {event_handler, NodeRefs, EventHandlers}. @@ -493,6 +496,9 @@ {skip_suites, DirRef, Suites, Comment}. {skip_suites, NodeRefs, DirRef, Suites, Comment}. + + {skip_groups, DirRef, Suite, GroupNames, Comment}. + {skip_groups, NodeRefs, DirRef, Suite, GroupNames, Comment}. {skip_cases, DirRef, Suite, Cases, Comment}. {skip_cases, NodeRefs, DirRef, Suite, Cases, Comment}. @@ -512,6 +518,7 @@ DirAlias = atom() Dir = string() LogDir = string() + PrivDirOption = auto_per_run | auto_per_tc | manual_per_tc EventHandlers = atom() | [atom()] InitArgs = [term()] CTHModules = [CTHModule | {CTHModule, CTHInitArgs} | {CTHModule, CTHInitArgs, CTHPriority}] @@ -521,8 +528,9 @@ Suites = atom() | [atom()] | all Suite = atom() Groups = GroupSpec | [GroupSpec] | all - GroupSpec = Group | {Group,Properties} | {Group,Properties,GroupSpec} - Group = atom() + GroupSpec = GroupName | {GroupName,Properties} | {GroupName,Properties,GroupSpec} + GroupName = atom() + GroupNames = GroupName | [GroupName] Cases = atom() | [atom()] | all Comment = string() | "" </pre> diff --git a/lib/common_test/doc/src/write_test_chapter.xml b/lib/common_test/doc/src/write_test_chapter.xml index c0ec26ddcc..09f4f8ed3b 100644 --- a/lib/common_test/doc/src/write_test_chapter.xml +++ b/lib/common_test/doc/src/write_test_chapter.xml @@ -29,7 +29,6 @@ <file>write_test_chapter.xml</file> </header> - <section> <marker id="intro"></marker> <title>Support for test suite authors</title> @@ -63,7 +62,8 @@ function in CT will not be able to locate it (at least not per default). </p> - <p>The <c>ct.hrl</c> header file must be included in all test suite files. + <p>It is also recommended that the <c>ct.hrl</c> header file is included + in all test suite modules. </p> <p>Each test suite module must export the function <c>all/0</c> @@ -71,6 +71,12 @@ to be executed in that module. </p> + <p>The callback functions that the test suite should implement, and + which will be described in more detail below, are + all listed in the <seealso marker="common_test">common_test + reference manual page</seealso>. + </p> + </section> <section> @@ -113,6 +119,14 @@ suite will be skipped automatically (so called <em>auto skipped</em>), including <c>end_per_suite</c>. </p> + + <p>Note that if <c>init_per_suite</c> and <c>end_per_suite</c> do not exist + in the suite, Common Test calls dummy functions (with the same names) + instead, so that output generated by hook functions may be saved to the log + files for these dummies + (see the <seealso marker="ct_hooks_chapter#manipulating">Common Test Hooks</seealso> + chapter for more information). + </p> </section> <marker id="per_testcase"/> @@ -513,8 +527,7 @@ execution is immediately stopped and the rest of the cases skipped.</p> <p>Before execution of a group begins, the configuration function - <c>init_per_group(GroupName, Config)</c> is called (the function is - mandatory if one or more test case groups are defined). The list of tuples + <c>init_per_group(GroupName, Config)</c> is called. The list of tuples returned from this function is passed to the test cases in the usual manner by means of the <c>Config</c> argument. <c>init_per_group/2</c> is meant to be used for initializations common for the test cases in the @@ -522,6 +535,14 @@ <c>end_per_group(GroupName, Config</c> function is called. This function is meant to be used for cleaning up after <c>init_per_group/2</c>.</p> + <p>Whenever a group is executed, if <c>init_per_group</c> and + <c>end_per_group</c> do not exist in the suite, Common Test calls + dummy functions (with the same names) instead. Output generated by + hook functions will be saved to the log files for these dummies + (see the <seealso marker="ct_hooks_chapter#manipulating">Common Test + Hooks</seealso> chapter for more information). + </p> + <note><p><c>init_per_testcase/2</c> and <c>end_per_testcase/2</c> are always called for each individual test case, no matter if the case belongs to a group or not.</p></note> @@ -611,6 +632,25 @@ </section> <section> + <title>Parallel test cases and IO</title> + <p>A parallel test case has a private IO server as its group leader. + (Please see the Erlang Run-Time System Application documentation for + a description of the group leader concept). The + central IO server process that handles the output from regular test + cases and configuration functions, does not respond to IO messages + during execution of parallel groups. This is important to understand + in order to avoid certain traps, like this one:</p> + <p>If a process, <c>P</c>, is spawned during execution of e.g. + <c>init_per_suite/1</c>, it will inherit the group leader of the + <c>init_per_suite</c> process. This group leader is the central IO server + process mentioned above. If, at a later time, <em>during parallel test case + execution</em>, some event triggers process <c>P</c> to call + <c>io:format/1/2</c>, that call will never return (since the group leader + is in a non-responsive state) and cause <c>P</c> to hang. + </p> + </section> + + <section> <title>Repeated groups</title> <marker id="repeated_groups"></marker> <p>A test case group may be repeated a certain number of times @@ -745,7 +785,7 @@ <marker id="data_priv_dir"></marker> <title>Data and Private Directories</title> - <p>The data directory (<c>data_dir</c>) is the directory where the + <p>The data directory, <c>data_dir</c>, is the directory where the test module has its own files needed for the testing. The name of the <c>data_dir</c> is the the name of the test suite followed by <c>"_data"</c>. For example, @@ -769,12 +809,39 @@ </p> --> <p> - The <c>priv_dir</c> is the test suite's private directory. This - directory should be used when a test case needs to write to - files. The name of the private directory is generated by the test - server, which also creates the directory. + <c>priv_dir</c> is the private directory for the test cases. + This directory may be used whenever a test case (or configuration function) + needs to write something to file. The name of the private directory is + generated by Common Test, which also creates the directory. </p> - + <p>By default, Common Test creates one central private directory + per test run that all test cases share. This may not always be suitable, + especially if the same test cases are executed multiple times during + a test run (e.g. if they belong to a test case group with repeat + property), and there's a risk that files in the private directory get + overwritten. Under these circumstances, it's possible to configure + Common Test to create one dedicated private directory per + test case and execution instead. This is accomplished by means of + the flag/option: <c>create_priv_dir</c> (to be used with the + <c>ct_run</c> program, the <c>ct:run_test/1</c> function, or + as test specification term). There are three possible values + for this option: + <list> + <item><c>auto_per_run</c></item> + <item><c>auto_per_tc</c></item> + <item><c>manual_per_tc</c></item> + </list> + The first value indicates the default priv_dir behaviour, i.e. + one private directory created per test run. The two latter + values tell Common Test to generate a unique test directory name + per test case and execution. If the auto version is used, <em>all</em> + private directories will be created automatically. This can obviously + become very inefficient for test runs with many test cases and/or + repetitions. Therefore, in case the manual version is instead used, the + test case must tell Common Test to create priv_dir when it needs it. + It does this by calling the function <c>ct:make_priv_dir/0</c>. + </p> + <note><p>You should not depend on current working directory for reading and writing data files since this is not portable. All scratch files are to be written in the <c>priv_dir</c> and all diff --git a/lib/eldap/.gitignore b/lib/eldap/.gitignore new file mode 100644 index 0000000000..5585418186 --- /dev/null +++ b/lib/eldap/.gitignore @@ -0,0 +1,4 @@ +*.beam +*.asn1db +src/ELDAPv3.hrl +src/ELDAPv3.erl diff --git a/lib/eldap/AUTHORS b/lib/eldap/AUTHORS new file mode 100644 index 0000000000..8f1e399306 --- /dev/null +++ b/lib/eldap/AUTHORS @@ -0,0 +1,7 @@ +Original author: + Torbjorn Tornkvist + +With patches from: + Simon MacMullen + Manuel Durán Aguete + voluntas
\ No newline at end of file diff --git a/lib/eldap/LICENSE b/lib/eldap/LICENSE new file mode 100644 index 0000000000..1f6200918f --- /dev/null +++ b/lib/eldap/LICENSE @@ -0,0 +1,21 @@ + +Copyright (c) 2010, Torbjorn Tornkvist + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/lib/eldap/Makefile b/lib/eldap/Makefile new file mode 100644 index 0000000000..3635ec759d --- /dev/null +++ b/lib/eldap/Makefile @@ -0,0 +1,39 @@ +# +# %CopyrightBegin% +# +# Copyright Ericsson AB 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% +# + +# +include $(ERL_TOP)/make/target.mk +include $(ERL_TOP)/make/$(TARGET)/otp.mk + +# ---------------------------------------------------- +# Macros +# ---------------------------------------------------- + +SUB_DIRECTORIES = src doc/src + +include vsn.mk +VSN = $(ELDAP_VSN) + +SPECIAL_TARGETS = + +# ---------------------------------------------------- +# Default Subdir Targets +# ---------------------------------------------------- +include $(ERL_TOP)/make/otp_subdir.mk + diff --git a/lib/eldap/README b/lib/eldap/README new file mode 100644 index 0000000000..e1bde9d658 --- /dev/null +++ b/lib/eldap/README @@ -0,0 +1,33 @@ +Hi, + +This is 'eldap', the Erlang LDAP library. + +It exports an API that can do all possible operations +you may want to do against an LDAP server. The code has +been tested to work at some point, but only the bind +and search operations are running daily in our products, +so there may be bugs lurking in some parts of the code. + +To just use eldap for doing authentication, do like in: + + {ok,X} = eldap:open(["ldap.mycorp.com"], []). + eldap:simple_bind(X, "uid=tobbe,ou=People,dc=mycorp,dc=com", "passwd"). + +In the doc/README.example you'll find a trace from a +Erlang shell session as an example on how to setup a +connection, authenticate (bind) and perform a search. +Note that by using the option {ssl, true}, you should +be able to setup an SSL tunnel (LDAPS) if your Erlang +system has been configured with SSL. + +In the test directory there are some hints and examples +on how to test the code and how to setup and populate +an OpenLDAP server. The 'eldap' code has been tested +agains OpenLDAP, IPlanet and ActiveDirectory servers. + +If you plan to incorporate this code into your system +I suggest that you build a server/supervisor harnesk +that uses 'eldap' (as we have done in our products). + +Good luck ! +/Tobbe diff --git a/lib/eldap/asn1/ELDAPv3.asn1 b/lib/eldap/asn1/ELDAPv3.asn1 new file mode 100644 index 0000000000..72b87d7221 --- /dev/null +++ b/lib/eldap/asn1/ELDAPv3.asn1 @@ -0,0 +1,278 @@ +-- Lightweight-Directory-Access-Protocol-V3 {1 3 6 1 1 18} +-- Copyright (C) The Internet Society (2006). This version of +-- this ASN.1 module is part of RFC 4511; see the RFC itself +-- for full legal notices. +ELDAPv3 DEFINITIONS +IMPLICIT TAGS +EXTENSIBILITY IMPLIED ::= + +BEGIN + +LDAPMessage ::= SEQUENCE { + messageID MessageID, + protocolOp CHOICE { + bindRequest BindRequest, + bindResponse BindResponse, + unbindRequest UnbindRequest, + searchRequest SearchRequest, + searchResEntry SearchResultEntry, + searchResDone SearchResultDone, + searchResRef SearchResultReference, + modifyRequest ModifyRequest, + modifyResponse ModifyResponse, + addRequest AddRequest, + addResponse AddResponse, + delRequest DelRequest, + delResponse DelResponse, + modDNRequest ModifyDNRequest, + modDNResponse ModifyDNResponse, + compareRequest CompareRequest, + compareResponse CompareResponse, + abandonRequest AbandonRequest, + extendedReq ExtendedRequest, + extendedResp ExtendedResponse, + ..., + intermediateResponse IntermediateResponse }, + controls [0] Controls OPTIONAL } + +MessageID ::= INTEGER (0 .. maxInt) + +maxInt INTEGER ::= 2147483647 -- (2^^31 - 1) -- + +LDAPString ::= OCTET STRING -- UTF-8 encoded, + -- [ISO10646] characters + +LDAPOID ::= OCTET STRING -- Constrained to <numericoid> + -- [RFC4512] + +LDAPDN ::= LDAPString -- Constrained to <distinguishedName> + -- [RFC4514] + +RelativeLDAPDN ::= LDAPString -- Constrained to <name-component> + -- [RFC4514] + +AttributeDescription ::= LDAPString + -- Constrained to <attributedescription> + -- [RFC4512] + +AttributeValue ::= OCTET STRING + +AttributeValueAssertion ::= SEQUENCE { + attributeDesc AttributeDescription, + assertionValue AssertionValue } + +AssertionValue ::= OCTET STRING + +PartialAttribute ::= SEQUENCE { + type AttributeDescription, + vals SET OF value AttributeValue } + +Attribute ::= PartialAttribute(WITH COMPONENTS { + ..., + vals (SIZE(1..MAX))}) + +MatchingRuleId ::= LDAPString + +LDAPResult ::= SEQUENCE { + resultCode ENUMERATED { + success (0), + operationsError (1), + protocolError (2), + timeLimitExceeded (3), + sizeLimitExceeded (4), + compareFalse (5), + compareTrue (6), + authMethodNotSupported (7), + strongerAuthRequired (8), + -- 9 reserved -- + referral (10), + adminLimitExceeded (11), + unavailableCriticalExtension (12), + confidentialityRequired (13), + saslBindInProgress (14), + + noSuchAttribute (16), + undefinedAttributeType (17), + inappropriateMatching (18), + constraintViolation (19), + attributeOrValueExists (20), + invalidAttributeSyntax (21), + -- 22-31 unused -- + noSuchObject (32), + aliasProblem (33), + invalidDNSyntax (34), + -- 35 reserved for undefined isLeaf -- + aliasDereferencingProblem (36), + -- 37-47 unused -- + inappropriateAuthentication (48), + invalidCredentials (49), + insufficientAccessRights (50), + busy (51), + unavailable (52), + unwillingToPerform (53), + loopDetect (54), + -- 55-63 unused -- + namingViolation (64), + objectClassViolation (65), + notAllowedOnNonLeaf (66), + notAllowedOnRDN (67), + entryAlreadyExists (68), + objectClassModsProhibited (69), + -- 70 reserved for CLDAP -- + affectsMultipleDSAs (71), + -- 72-79 unused -- + other (80), + ... }, + matchedDN LDAPDN, + diagnosticMessage LDAPString, + referral [3] Referral OPTIONAL } + +Referral ::= SEQUENCE SIZE (1..MAX) OF uri URI + +URI ::= LDAPString -- limited to characters permitted in + -- URIs + +Controls ::= SEQUENCE OF control Control + +Control ::= SEQUENCE { + controlType LDAPOID, + criticality BOOLEAN DEFAULT FALSE, + controlValue OCTET STRING OPTIONAL } + +BindRequest ::= [APPLICATION 0] SEQUENCE { + version INTEGER (1 .. 127), + name LDAPDN, + authentication AuthenticationChoice } + +AuthenticationChoice ::= CHOICE { + simple [0] OCTET STRING, + -- 1 and 2 reserved + sasl [3] SaslCredentials, + ... } + +SaslCredentials ::= SEQUENCE { + mechanism LDAPString, + credentials OCTET STRING OPTIONAL } + +BindResponse ::= [APPLICATION 1] SEQUENCE { + COMPONENTS OF LDAPResult, + serverSaslCreds [7] OCTET STRING OPTIONAL } + +UnbindRequest ::= [APPLICATION 2] NULL + +SearchRequest ::= [APPLICATION 3] SEQUENCE { + baseObject LDAPDN, + scope ENUMERATED { + baseObject (0), + singleLevel (1), + wholeSubtree (2), + ... }, + derefAliases ENUMERATED { + neverDerefAliases (0), + derefInSearching (1), + derefFindingBaseObj (2), + derefAlways (3) }, + sizeLimit INTEGER (0 .. maxInt), + timeLimit INTEGER (0 .. maxInt), + typesOnly BOOLEAN, + filter Filter, + attributes AttributeSelection } + +AttributeSelection ::= SEQUENCE OF selector LDAPString + -- The LDAPString is constrained to + -- <attributeSelector> in Section 4.5.1.8 + +Filter ::= CHOICE { + and [0] SET SIZE (1..MAX) OF filter Filter, + or [1] SET SIZE (1..MAX) OF filter Filter, + not [2] Filter, + equalityMatch [3] AttributeValueAssertion, + substrings [4] SubstringFilter, + greaterOrEqual [5] AttributeValueAssertion, + lessOrEqual [6] AttributeValueAssertion, + present [7] AttributeDescription, + approxMatch [8] AttributeValueAssertion, + extensibleMatch [9] MatchingRuleAssertion, + ... } + +SubstringFilter ::= SEQUENCE { + type AttributeDescription, + substrings SEQUENCE SIZE (1..MAX) OF substring CHOICE { + initial [0] AssertionValue, -- can occur at most once + any [1] AssertionValue, + final [2] AssertionValue } -- can occur at most once + } + +MatchingRuleAssertion ::= SEQUENCE { + matchingRule [1] MatchingRuleId OPTIONAL, + type [2] AttributeDescription OPTIONAL, + matchValue [3] AssertionValue, + dnAttributes [4] BOOLEAN DEFAULT FALSE } + +SearchResultEntry ::= [APPLICATION 4] SEQUENCE { + objectName LDAPDN, + attributes PartialAttributeList } + +PartialAttributeList ::= SEQUENCE OF + partialAttribute PartialAttribute + +SearchResultReference ::= [APPLICATION 19] SEQUENCE + SIZE (1..MAX) OF uri URI + +SearchResultDone ::= [APPLICATION 5] LDAPResult + +ModifyRequest ::= [APPLICATION 6] SEQUENCE { + object LDAPDN, + changes SEQUENCE OF change SEQUENCE { + operation ENUMERATED { + add (0), + delete (1), + replace (2), + ... }, + modification PartialAttribute } } + +ModifyResponse ::= [APPLICATION 7] LDAPResult + +AddRequest ::= [APPLICATION 8] SEQUENCE { + entry LDAPDN, + attributes AttributeList } + +AttributeList ::= SEQUENCE OF attribute Attribute + +AddResponse ::= [APPLICATION 9] LDAPResult + +DelRequest ::= [APPLICATION 10] LDAPDN + +DelResponse ::= [APPLICATION 11] LDAPResult + +ModifyDNRequest ::= [APPLICATION 12] SEQUENCE { + entry LDAPDN, + newrdn RelativeLDAPDN, + deleteoldrdn BOOLEAN, + newSuperior [0] LDAPDN OPTIONAL } + +ModifyDNResponse ::= [APPLICATION 13] LDAPResult + +CompareRequest ::= [APPLICATION 14] SEQUENCE { + entry LDAPDN, + ava AttributeValueAssertion } + +CompareResponse ::= [APPLICATION 15] LDAPResult + +AbandonRequest ::= [APPLICATION 16] MessageID + +ExtendedRequest ::= [APPLICATION 23] SEQUENCE { + requestName [0] LDAPOID, + requestValue [1] OCTET STRING OPTIONAL } + +ExtendedResponse ::= [APPLICATION 24] SEQUENCE { + COMPONENTS OF LDAPResult, + responseName [10] LDAPOID OPTIONAL, + responseValue [11] OCTET STRING OPTIONAL } + +IntermediateResponse ::= [APPLICATION 25] SEQUENCE { + responseName [0] LDAPOID OPTIONAL, + responseValue [1] OCTET STRING OPTIONAL } + +END + diff --git a/lib/eldap/doc/html/.gitignore b/lib/eldap/doc/html/.gitignore new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/lib/eldap/doc/html/.gitignore diff --git a/lib/eldap/doc/man3/.gitignore b/lib/eldap/doc/man3/.gitignore new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/lib/eldap/doc/man3/.gitignore diff --git a/lib/eldap/doc/pdf/.gitignore b/lib/eldap/doc/pdf/.gitignore new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/lib/eldap/doc/pdf/.gitignore diff --git a/lib/eldap/doc/src/Makefile b/lib/eldap/doc/src/Makefile new file mode 100644 index 0000000000..4c827319b4 --- /dev/null +++ b/lib/eldap/doc/src/Makefile @@ -0,0 +1,115 @@ +# ``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 via the world wide web 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. +# +# The Initial Developer of the Original Code is Ericsson Utvecklings AB. +# Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings +# AB. All Rights Reserved.'' +# +# $Id$ +# +include $(ERL_TOP)/make/target.mk +include $(ERL_TOP)/make/$(TARGET)/otp.mk + +# ---------------------------------------------------- +# Application version +# ---------------------------------------------------- +include ../../vsn.mk +VSN=$(ELDAP_VSN) +APPLICATION=eldap + +# ---------------------------------------------------- +# Release directory specification +# ---------------------------------------------------- +RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN) + +# ---------------------------------------------------- +# Target Specs +# ---------------------------------------------------- +XML_APPLICATION_FILES = ref_man.xml +XML_REF3_FILES = eldap.xml + +XML_PART_FILES = release_notes.xml usersguide.xml +XML_CHAPTER_FILES = notes.xml + +BOOK_FILES = book.xml + +XML_FILES = $(BOOK_FILES) $(XML_APPLICATION_FILES) $(XML_REF3_FILES) $(XML_REF6_FILES) \ + $(XML_PART_FILES) $(XML_CHAPTER_FILES) + +GIF_FILES = + +# ---------------------------------------------------- + +HTML_FILES = $(XML_APPLICATION_FILES:%.xml=$(HTMLDIR)/%.html) \ + $(XML_PART_FILES:%.xml=$(HTMLDIR)/%.html) + +INFO_FILE = ../../info + +MAN3_FILES = $(XML_REF3_FILES:%.xml=$(MAN3DIR)/%.3) +MAN6_FILES = $(XML_REF6_FILES:%_app.xml=$(MAN6DIR)/%.6) + +HTML_REF_MAN_FILE = $(HTMLDIR)/index.html + +TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf + +# ---------------------------------------------------- +# FLAGS +# ---------------------------------------------------- +XML_FLAGS += + +# ---------------------------------------------------- +# Targets +# ---------------------------------------------------- +$(HTMLDIR)/%.gif: %.gif + $(INSTALL_DATA) $< $@ + + +docs: pdf html man + +$(TOP_PDF_FILE): $(XML_FILES) + +pdf: $(TOP_PDF_FILE) + +html: gifs $(HTML_REF_MAN_FILE) + +man: $(MAN3_FILES) $(MAN6_FILES) + +gifs: $(GIF_FILES:%=$(HTMLDIR)/%) + +debug opt valgrind: + +clean clean_docs clean_tex: + rm -rf $(HTMLDIR)/* + rm -f $(MAN3DIR)/* + rm -f $(MAN6DIR)/* + rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo) + rm -f errs core *~ + +# ---------------------------------------------------- +# Release Target +# ---------------------------------------------------- +include $(ERL_TOP)/make/otp_release_targets.mk + +release_docs_spec: docs + $(INSTALL_DIR) $(RELSYSDIR)/doc/pdf + $(INSTALL_DATA) $(TOP_PDF_FILE) $(RELSYSDIR)/doc/pdf + $(INSTALL_DIR) $(RELSYSDIR)/doc/html + $(INSTALL_DATA) $(HTMLDIR)/* \ + $(RELSYSDIR)/doc/html + $(INSTALL_DATA) $(INFO_FILE) $(RELSYSDIR) + $(INSTALL_DIR) $(RELEASE_PATH)/man/man3 + $(INSTALL_DATA) $(MAN3DIR)/* $(RELEASE_PATH)/man/man3 +# $(INSTALL_DIR) $(RELEASE_PATH)/man/man6 +# $(INSTALL_DATA) $(MAN6DIR)/* $(RELEASE_PATH)/man/man6 + + +release_spec: + diff --git a/lib/eldap/doc/src/book.xml b/lib/eldap/doc/src/book.xml new file mode 100644 index 0000000000..74f1c37cdc --- /dev/null +++ b/lib/eldap/doc/src/book.xml @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="latin1" ?> +<!DOCTYPE book SYSTEM "book.dtd"> + +<book xmlns:xi="http://www.w3.org/2001/XInclude"> + <header titlestyle="normal"> + <copyright> + <year>2012</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>eldap</title> + <prepared>OTP team</prepared> + <docno></docno> + <date>1999-01-21</date> + <rev>A</rev> + <file>book.xml</file> + </header> + <pagetext>eldap</pagetext> + <preamble> + <contents level="2"></contents> + </preamble> + <parts lift="no"> + <xi:include href="usersguide.xml"/> + </parts> + <applications> + <xi:include href="ref_man.xml"/> + </applications> + <releasenotes> + <xi:include href="notes.xml"/> + </releasenotes> + <listofterms></listofterms> + <index></index> +</book> + diff --git a/lib/eldap/doc/src/eldap.xml b/lib/eldap/doc/src/eldap.xml new file mode 100644 index 0000000000..04dad2eee7 --- /dev/null +++ b/lib/eldap/doc/src/eldap.xml @@ -0,0 +1,342 @@ +<?xml version="1.0" encoding="iso-8859-1" ?> +<!DOCTYPE erlref SYSTEM "erlref.dtd"> + +<erlref> + <header> + <copyright> + <year>2012</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>eldap</title> + <prepared>Peter Högfeldt</prepared> + <docno></docno> + <date>2000-06-20</date> + <rev>B</rev> + </header> + <module>eldap</module> + <modulesummary>Eldap Functions</modulesummary> + <description> + <p>This module provides a client api to the Lightweight Directory Access Protocol (LDAP). + </p> + <p>References:</p> + <list type="bulleted"> + <item> <p>RFC 4510 - RFC 4519</p> </item> + </list> + <p>The above publications can be found at <url href="http://www.ietf.org">IETF</url>. + </p> + <p><em>Types</em></p> + <pre> +handle() Connection handle +attribute() {Type = string(), Values=[string()]} +modify_op() See mod_add/2, mod_delete/2, mod_replace/2 +scope() See baseObject/0, singleLevel/0, wholeSubtree/0 +dereference() See neverDerefAliases/0, derefInSearching/0, derefFindingBaseObj/0, derefAlways/0 +filter() See present/1, substrings/2, + equalityMatch/2, greaterOrEqual/2, lessOrEqual/2, + approxMatch/2, + 'and'/1, 'or'/1, 'not'/1. + </pre> + <p></p> + </description> + <funcs> + <func> + <name>open([Host]) -> {ok, Handle} | {error, Reason}</name> + <fsummary>Open a connection to an LDAP server.</fsummary> + <type> + <v>Handle = handle()</v> + </type> + <desc> + <p>Setup a connection to an LDAP server, the <c>HOST</c>'s are tried in order. </p> + </desc> + </func> + <func> + <name>open([Host], [Option]) -> {ok, Handle} | {error, Reason}</name> + <fsummary>Open a connection to an LDAP server.</fsummary> + <type> + <v>Handle = handle()</v> + <v>Option = {port, integer()} | {log, function()} | {timeout, integer()} | {ssl, boolean()}</v> + </type> + <desc> + <p>Setup a connection to an LDAP server, the <c>HOST</c>'s are tried in order.</p> + <p>The log function takes three arguments, <c>fun(Level, FormatString, [FormatArg]) end</c>.</p> + <p>Timeout set the maximum time in milliseconds that each server request may take.</p> + </desc> + </func> + <func> + <name>close(Handle) -> ok</name> + <fsummary>Shutdown the connection.</fsummary> + <type> + <v>Handle = handle()</v> + </type> + <desc> + <p>Shutdown the connection.</p> + </desc> + </func> + <func> + <name>simple_bind(Handle, Dn, Password) -> ok | {error, Reason}</name> + <fsummary>Authenticate the connection.</fsummary> + <type> + <v>Handle = handle()</v> + <v>Dn = string()</v> + <v>Password = string()</v> + </type> + <desc> + <p>Authenticate the connection using simple authentication.</p> + </desc> + </func> + <func> + <name>add(Handle, Dn, [Attribute]) -> ok | {error, Reason}</name> + <fsummary>Add an entry.</fsummary> + <type> + <v>Handle = handle()</v> + <v>Dn = string()</v> + <v>Attribute = attribute()</v> + </type> + <desc> + <p> Add an entry. The entry must not exist.</p> + <pre> + add(Handle, + "cn=Bill Valentine, ou=people, o=Example Org, dc=example, dc=com", + [{"objectclass", ["person"]}, + {"cn", ["Bill Valentine"]}, + {"sn", ["Valentine"]}, + {"telephoneNumber", ["545 555 00"]}] + ) + </pre> + </desc> + </func> + <func> + <name>delete(Handle, Dn) -> ok | {error, Reason}</name> + <fsummary>Delete an entry.</fsummary> + <type> + <v>Dn = string()</v> + </type> + <desc> + <p> Delete an entry.</p> + <pre> + delete(Handle, "cn=Bill Valentine, ou=people, o=Example Org, dc=example, dc=com") + </pre> + </desc> + </func> + + <func> + <name>mod_add(Type, [Value]) -> modify_op()</name> + <fsummary>Create a modification operation.</fsummary> + <type> + <v>Type = string()</v> + <v>Value = string()</v> + </type> + <desc> <p> Create an add modification operation.</p> </desc> + </func> + <func> + <name>mod_delete(Type, [Value]) -> modify_op()</name> + <fsummary>Create a modification operation.</fsummary> + <type> + <v>Type = string()</v> + <v>Value = string()</v> + </type> + <desc> <p> Create a delete modification operation.</p> </desc> + </func> + <func> + <name>mod_replace(Type, [Value]) -> modify_op()</name> + <fsummary>Create a modification operation.</fsummary> + <type> + <v>Type = string()</v> + <v>Value = string()</v> + </type> + <desc> <p> Create a replace modification operation.</p> </desc> + </func> + + <func> + <name>modify(Handle, Dn, [ModifyOp]) -> ok | {error, Reason}</name> + <fsummary>Modify an entry.</fsummary> + <type> + <v>Dn = string()</v> + <v>ModifyOp = modify_op()</v> + </type> + <desc> + <p> Modify an entry.</p> + <pre> + modify(Handle, "cn=Bill Valentine, ou=people, o=Example Org, dc=example, dc=com", + [eldap:mod_replace("telephoneNumber", ["555 555 00"]), + eldap:mod_add("description", ["LDAP Hacker"]) ]) + </pre> + </desc> + </func> + <func> + <name>modify_dn(Handle, Dn, NewRDN, DeleteOldRDN, NewSupDN) -> ok | {error, Reason}</name> + <fsummary>Modify the DN of an entry.</fsummary> + <type> + <v>Dn = string()</v> + <v>NewRDN = string()</v> + <v>DeleteOldRDN = boolean()</v> + <v>NewSupDN = string()</v> + </type> + <desc> + <p> Modify the DN of an entry. <c>DeleteOldRDN</c> indicates + whether the current RDN should be removed after operation. + <c>NewSupDN</c> should be "" if the RDN should not be moved or the new parent which + the RDN will be moved to.</p> + <pre> + modify_dn(Handle, "cn=Bill Valentine, ou=people, o=Example Org, dc=example, dc=com ", + "cn=Bill Jr Valentine", true, "") + </pre> + </desc> + </func> + <func> + <name>search(Handle, SearchOptions) -> {ok, #eldap_search_result{}} | {error, Reason}</name> + <fsummary>Search the Directory</fsummary> + <type> + <v>SearchOptions = #eldap_search{} | [SearchOption]</v> + <v>SearchOption = {base, string()} | {filter, filter()} | {scope, scope()} + | {attributes, [string()]} | {deref, dereference()} | + | {types_only, boolean()} | {timeout, integer()} + </v> + </type> + <desc> + <p>Search the directory with the supplied the SearchOptions. + The base and filter options must be supplied. + Default values: scope is <c>wholeSubtree()</c>, deref is <c>derefAlways()</c>, + types_only is <c>false</c> and timeout is <c>0</c> (meaning infinity). + </p> + <pre> + Filter = eldap:substrings("cn", [{any,"V"}]), + search(Handle, [{base, "dc=example, dc=com"}, {filter, Filter}, {attributes, ["cn"]}]), + </pre> + </desc> + </func> + + <func> + <name>baseObject() -> scope()</name> + <fsummary>Create search scope.</fsummary> + <desc> <p> Search baseobject only.</p> </desc> + </func> + <func> + <name>singleLevel() -> scope()</name> + <fsummary>Create search scope.</fsummary> + <desc> <p> Search the specified level only, i.e. do not recurse.</p> </desc> + </func> + <func> + <name>wholeSubtree() -> scope()</name> + <fsummary>Create search scope.</fsummary> + <desc> <p> Search the entire subtree.</p> </desc> + </func> + + <func> + <name>neverDerefAliases() -> dereference()</name> + <fsummary>Create search option.</fsummary> + <desc> <p>Never derefrence aliases, treat aliases as entries.</p> </desc> + </func> + <func> + <name>derefAlways() -> dereference()</name> + <fsummary>Create search option.</fsummary> + <desc> <p>Always derefrence aliases.</p> </desc> + </func> + <func> + <name>derefInSearching() -> dereference()</name> + <fsummary>Create search option.</fsummary> + <desc> <p>Derefrence aliases only when searching.</p> </desc> + </func> + <func> + <name>derefFindingBaseObj() -> dereference()</name> + <fsummary>Create search option.</fsummary> + <desc> <p>Derefrence aliases only in finding the base.</p> </desc> + </func> + + <func> + <name>present(Type) -> filter()</name> + <fsummary>Create search filter option.</fsummary> + <type> + <v>Type = string()</v> + </type> + <desc> <p>Create a filter which filters on attribute type presence.</p> </desc> + </func> + <func> + <name>substrings(Type, [SubString]) -> filter()</name> + <fsummary>Create search filter option.</fsummary> + <type> + <v>Type = string()</v> + <v>SubString = {StringPart, string()}</v> + <v>StringPart = initial | any | final</v> + </type> + <desc> <p>Create a filter which filters on substrings.</p> </desc> + </func> + <func> + <name>equalityMatch(Type, Value) -> filter()</name> + <fsummary>Create search filter option.</fsummary> + <type> + <v>Type = string()</v> + <v>Value = string()</v> + </type> + <desc> <p>Create a equality filter.</p> </desc> + </func> + <func> + <name>greaterOrEqual(Type, Value) -> filter()</name> + <fsummary>Create search filter option.</fsummary> + <type> + <v>Type = string()</v> + <v>Value = string()</v> + </type> + <desc> <p>Create a greater or equal filter.</p> </desc> + </func> + <func> + <name>lessOrEqual(Type, Value) -> filter()</name> + <fsummary>Create search filter option.</fsummary> + <type> + <v>Type = string()</v> + <v>Value = string()</v> + </type> + <desc> <p>Create a less or equal filter.</p> </desc> + </func> + <func> + <name>approxMatch(Type, Value) -> filter()</name> + <fsummary>Create search filter option.</fsummary> + <type> + <v>Type = string()</v> + <v>Value = string()</v> + </type> + <desc> <p>Create a approximation match filter.</p> </desc> + </func> + <func> + <name>'and'([Filter]) -> filter()</name> + <fsummary>Create search filter option.</fsummary> + <type> + <v>Filter = filter()</v> + </type> + <desc> <p>Creates a filter where all <c>Filter</c> must be true.</p> </desc> + </func> + <func> + <name>'or'([Filter]) -> filter()</name> + <fsummary>Create search filter option.</fsummary> + <type> + <v>Filter = filter()</v> + </type> + <desc> <p>Create a filter where at least one of the <c>Filter</c> must be true.</p> </desc> + </func> + <func> + <name>'not'(Filter) -> filter()</name> + <fsummary>Create search filter option.</fsummary> + <type> + <v>Filter = filter()</v> + </type> + <desc> <p>Negate a filter.</p> </desc> + </func> + + </funcs> + +</erlref> + diff --git a/lib/eldap/doc/src/fascicules.xml b/lib/eldap/doc/src/fascicules.xml new file mode 100644 index 0000000000..8fc250bc75 --- /dev/null +++ b/lib/eldap/doc/src/fascicules.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="latin1" ?> +<!DOCTYPE fascicules SYSTEM "fascicules.dtd"> + +<fascicules> + <fascicule file="usersguide" href="usersguide_frame.html" entry="no"> + User's Guide + </fascicule> + <fascicule file="ref_man" href="ref_man_frame.html" entry="yes"> + Reference Manual + </fascicule> + <fascicule file="release_notes" href="release_notes_frame.html" entry="no"> + Release Notes + </fascicule> + <fascicule file="" href="../../../../doc/print.html" entry="no"> + Off-Print + </fascicule> +</fascicules> + diff --git a/lib/eldap/doc/src/note.gif b/lib/eldap/doc/src/note.gif Binary files differnew file mode 100644 index 0000000000..6fffe30419 --- /dev/null +++ b/lib/eldap/doc/src/note.gif diff --git a/lib/eldap/doc/src/notes.xml b/lib/eldap/doc/src/notes.xml new file mode 100644 index 0000000000..d9b11875de --- /dev/null +++ b/lib/eldap/doc/src/notes.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="latin1" ?> +<!DOCTYPE chapter SYSTEM "chapter.dtd"> + +<chapter> + <header> + <copyright> + <year>2012</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>Eldap Release Notes</title> + <prepared>OTP team</prepared> + <docno></docno> + <date>2003-06-06</date> + <rev>B</rev> + <file>notes.xml</file> + </header> + <p>This document describes the changes made to the Eldap application.</p> + + <section> + <title>Eldap 1.0</title> + <p>New application. </p> + </section> +</chapter> + diff --git a/lib/eldap/doc/src/ref_man.xml b/lib/eldap/doc/src/ref_man.xml new file mode 100644 index 0000000000..70c4cee16d --- /dev/null +++ b/lib/eldap/doc/src/ref_man.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="latin1" ?> +<!DOCTYPE application SYSTEM "application.dtd"> + +<application xmlns:xi="http://www.w3.org/2001/XInclude"> + <header> + <copyright> + <year>2012</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>Eldap Reference Manual</title> + <prepared>OTP team</prepared> + <docno></docno> + <date>2003-06-01</date> + <rev>B</rev> + <file>refman.sgml</file> + </header> + <description> + <p>The <em>Eldap</em> application provides an api for accessing an LDAP server.</p> + <p>The original code was developed by Torbjörn Törnkvist.</p> + </description> + <xi:include href="eldap.xml"/> +</application> + diff --git a/lib/eldap/doc/src/release_notes.xml b/lib/eldap/doc/src/release_notes.xml new file mode 100644 index 0000000000..778a49b894 --- /dev/null +++ b/lib/eldap/doc/src/release_notes.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="latin1" ?> +<!DOCTYPE part SYSTEM "part.dtd"> + +<part> + <header> + <copyright> + <year>2012</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. + + The Initial Developer of the Original Code is Ericsson AB. + </legalnotice> + + <title>Eldap Release Notes</title> + <prepared>OTP team</prepared> + <docno></docno> + <date>2003-06-01</date> + <rev>B</rev> + <file>release_notes.xml</file> + </header> + <description> + <p></p> + </description> + <include file="notes"></include> +</part> + diff --git a/lib/eldap/doc/src/usersguide.xml b/lib/eldap/doc/src/usersguide.xml new file mode 100644 index 0000000000..828588e88e --- /dev/null +++ b/lib/eldap/doc/src/usersguide.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="latin1" ?> +<!DOCTYPE part SYSTEM "part.dtd"> + +<part xmlns:xi="http://www.w3.org/2001/XInclude"> + <header> + <copyright> + <year>2012</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>Eldap User's Guide</title> + <prepared>OTP Team</prepared> + <docno></docno> + <date>2003-06-01</date> + <rev>C</rev> + <file>usersguide.xml</file> + </header> + <description> + <p>The <em>Eldap</em> application provides an api for accessing an LDAP server.</p> + <p>The original code was developed by Torbjörn Törnkvist.</p> + </description> + + +</part> + diff --git a/lib/eldap/doc/src/warning.gif b/lib/eldap/doc/src/warning.gif Binary files differnew file mode 100644 index 0000000000..96af52360e --- /dev/null +++ b/lib/eldap/doc/src/warning.gif diff --git a/lib/eldap/ebin/.gitignore b/lib/eldap/ebin/.gitignore new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/lib/eldap/ebin/.gitignore diff --git a/lib/eldap/include/eldap.hrl b/lib/eldap/include/eldap.hrl new file mode 100644 index 0000000000..7c12cd4f2b --- /dev/null +++ b/lib/eldap/include/eldap.hrl @@ -0,0 +1,33 @@ +-ifndef( _ELDAP_HRL ). +-define( _ELDAP_HRL , 1 ). + +%%% +%%% Search input parameters +%%% +-record(eldap_search, { + base = [], % Baseobject + filter = [], % Search conditions + scope=wholeSubtree, % Search scope + deref=derefAlways, % Dereference + attributes = [], % Attributes to be returned + types_only = false, % Return types+values or types + timeout = 0 % Timelimit for search + }). + +%%% +%%% Returned search result +%%% +-record(eldap_search_result, { + entries = [], % List of #eldap_entry{} records + referrals = [] % List of referrals + }). + +%%% +%%% LDAP entry +%%% +-record(eldap_entry, { + object_name = "", % The DN for the entry + attributes = [] % List of {Attribute, Value} pairs + }). + +-endif. diff --git a/lib/eldap/info b/lib/eldap/info new file mode 100644 index 0000000000..463f9af6ea --- /dev/null +++ b/lib/eldap/info @@ -0,0 +1,2 @@ +group: comm +short: eldap - Erlang LDAP library diff --git a/lib/eldap/src/Makefile b/lib/eldap/src/Makefile new file mode 100644 index 0000000000..4ddb8082d7 --- /dev/null +++ b/lib/eldap/src/Makefile @@ -0,0 +1,110 @@ +# +# %CopyrightBegin% +# +# Copyright Ericsson AB 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% +# +# +include $(ERL_TOP)/make/target.mk + +include $(ERL_TOP)/make/$(TARGET)/otp.mk + +# ---------------------------------------------------- +# Application version +# ---------------------------------------------------- +include ../vsn.mk + +# ---------------------------------------------------- +# Release directory specification +# ---------------------------------------------------- +RELSYSDIR = $(RELEASE_PATH)/lib/eldap-$(ELDAP_VSN) + +# ---------------------------------------------------- +# Target Specs +# ---------------------------------------------------- + +MODULES= eldap + +ASN1_FILES = ELDAPv3.asn1 +ASN1_HRL = $(EBIN)/$(ASN1_FILES:%.asn1=%.hrl) + +ERL_FILES= $(MODULES:%=%.erl) + +TARGET_FILES = $(MODULES:%=$(EBIN)/%.$(EMULATOR)) $(ASN1_FILES:%.asn1=$(EBIN)/%.$(EMULATOR)) + +EXTERNAL_HRL_FILES = ../include/eldap.hrl + +HRL_FILES = $(EXTERNAL_HRL_FILES) $(ASN1_HRL) + +APPUP_FILE = eldap.appup +APPUP_SRC = $(APPUP_FILE).src +APPUP_TARGET = $(EBIN)/$(APPUP_FILE) + +APP_FILE = eldap.app +APP_SRC = $(APP_FILE).src +APP_TARGET = $(EBIN)/$(APP_FILE) + +# ---------------------------------------------------- +# FLAGS +# ---------------------------------------------------- +ERL_COMPILE_FLAGS += -I../include -I../ebin + +# ---------------------------------------------------- +# Targets +# ---------------------------------------------------- +opt: $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET) + +debug: + @${MAKE} TYPE=debug opt + +clean: + rm -f $(TARGET_FILES) $(GEN_FILES) $(APP_TARGET) $(APPUP_TARGET) + rm -f $(ASN1_FILES:%.asn1=$(EBIN)/%.*) + rm -f errs core *~ + +$(APP_TARGET): $(APP_SRC) ../vsn.mk + sed -e 's;%VSN%;$(ELDAP_VSN);' $< > $@ + +$(APPUP_TARGET): $(APPUP_SRC) ../vsn.mk + sed -e 's;%VSN%;$(ELDAP_VSN);' $< > $@ + +docs: + +$(TARGET_FILES): $(HRL_FILES) + +# ---------------------------------------------------- +# Special Build Targets +# ---------------------------------------------------- +$(ASN1_HRL): ../asn1/$(ASN1_FILES) + $(ERLC) -o $(EBIN) -bber_bin +optimize +nif $(ERL_COMPILE_FLAGS) ../asn1/ELDAPv3.asn1 + +# ---------------------------------------------------- +# Release Target +# ---------------------------------------------------- +include $(ERL_TOP)/make/otp_release_targets.mk + +release_spec: opt + $(INSTALL_DIR) $(RELSYSDIR)/ebin + $(INSTALL_DATA) $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET) $(RELSYSDIR)/ebin + $(INSTALL_DIR) $(RELSYSDIR)/src + $(INSTALL_DATA) $(ERL_FILES) $(RELSYSDIR)/src + $(INSTALL_DIR) $(RELSYSDIR)/asn1 + $(INSTALL_DATA) ../asn1/$(ASN1_FILES) $(RELSYSDIR)/asn1 + $(INSTALL_DIR) $(RELSYSDIR)/include + $(INSTALL_DATA) $(EXTERNAL_HRL_FILES) $(RELSYSDIR)/include + +release_docs_spec: + + diff --git a/lib/eldap/src/eldap.app.src b/lib/eldap/src/eldap.app.src new file mode 100644 index 0000000000..8215328910 --- /dev/null +++ b/lib/eldap/src/eldap.app.src @@ -0,0 +1,8 @@ +{application, eldap, + [{description, "Ldap api"}, + {vsn, "%VSN%"}, + {modules, [eldap, 'ELDAPv3']}, + {registered, []}, + {applications, [kernel, stdlib]}, + {env, []} +]}. diff --git a/lib/eldap/src/eldap.appup.src b/lib/eldap/src/eldap.appup.src new file mode 100644 index 0000000000..8d33482f11 --- /dev/null +++ b/lib/eldap/src/eldap.appup.src @@ -0,0 +1,6 @@ +%% -*- erlang -*- +{"%VSN%", + [ + ], + [ + ]}. diff --git a/lib/eldap/src/eldap.erl b/lib/eldap/src/eldap.erl new file mode 100644 index 0000000000..699dfc8791 --- /dev/null +++ b/lib/eldap/src/eldap.erl @@ -0,0 +1,1114 @@ +-module(eldap). +%%% -------------------------------------------------------------------- +%%% Created: 12 Oct 2000 by Tobbe <[email protected]> +%%% Function: Erlang client LDAP implementation according RFC 2251,2253 +%%% and 2255. The interface is based on RFC 1823, and +%%% draft-ietf-asid-ldap-c-api-00.txt +%%% +%%% Copyright (c) 2010 Torbjorn Tornkvist +%%% See MIT-LICENSE at the top dir for licensing information. +%%% -------------------------------------------------------------------- +-vc('$Id$ '). +-export([open/1,open/2,simple_bind/3,controlling_process/2, + baseObject/0,singleLevel/0,wholeSubtree/0,close/1, + equalityMatch/2,greaterOrEqual/2,lessOrEqual/2, + approxMatch/2,search/2,substrings/2,present/1, + 'and'/1,'or'/1,'not'/1,modify/3, mod_add/2, mod_delete/2, + mod_replace/2, add/3, delete/2, modify_dn/5,parse_dn/1, + parse_ldap_url/1]). + +-export([neverDerefAliases/0, derefInSearching/0, + derefFindingBaseObj/0, derefAlways/0]). + +%% for upgrades +-export([loop/2]). + +-import(lists,[concat/1]). + +-include("ELDAPv3.hrl"). +-include("eldap.hrl"). + +-define(LDAP_VERSION, 3). +-define(LDAP_PORT, 389). +-define(LDAPS_PORT, 636). + +-record(eldap, {version = ?LDAP_VERSION, + host, % Host running LDAP server + port = ?LDAP_PORT, % The LDAP server port + fd, % Socket filedescriptor. + binddn = "", % Name of the entry to bind as + passwd, % Password for (above) entry + id = 0, % LDAP Request ID + log, % User provided log function + timeout = infinity, % Request timeout + anon_auth = false, % Allow anonymous authentication + use_tls = false % LDAP/LDAPS + }). + +%%% For debug purposes +%%-define(PRINT(S, A), io:fwrite("~w(~w): " ++ S, [?MODULE,?LINE|A])). +-define(PRINT(S, A), true). + +-define(elog(S, A), error_logger:info_msg("~w(~w): "++S,[?MODULE,?LINE|A])). + +%%% ==================================================================== +%%% Exported interface +%%% ==================================================================== + +%%% -------------------------------------------------------------------- +%%% open(Hosts [,Opts] ) +%%% -------------------- +%%% Setup a connection to on of the Hosts in the argument +%%% list. Stop at the first successful connection attempt. +%%% Valid Opts are: Where: +%%% +%%% {port, Port} - Port is the port number +%%% {log, F} - F(LogLevel, FormatString, ListOfArgs) +%%% {timeout, milliSec} - Server request timeout +%%% +%%% -------------------------------------------------------------------- +open(Hosts) -> + open(Hosts, []). + +open(Hosts, Opts) when is_list(Hosts), is_list(Opts) -> + Self = self(), + Pid = spawn_link(fun() -> init(Hosts, Opts, Self) end), + recv(Pid). + +%%% -------------------------------------------------------------------- +%%% Shutdown connection (and process) asynchronous. +%%% -------------------------------------------------------------------- + +close(Handle) when is_pid(Handle) -> + send(Handle, close). + +%%% -------------------------------------------------------------------- +%%% Set who we should link ourselves to +%%% -------------------------------------------------------------------- + +controlling_process(Handle, Pid) when is_pid(Handle), is_pid(Pid) -> + link(Pid), + send(Handle, {cnt_proc, Pid}), + recv(Handle). + +%%% -------------------------------------------------------------------- +%%% Authenticate ourselves to the Directory +%%% using simple authentication. +%%% +%%% Dn - The name of the entry to bind as +%%% Passwd - The password to be used +%%% +%%% Returns: ok | {error, Error} +%%% -------------------------------------------------------------------- +simple_bind(Handle, Dn, Passwd) when is_pid(Handle) -> + send(Handle, {simple_bind, Dn, Passwd}), + recv(Handle). + +%%% -------------------------------------------------------------------- +%%% Add an entry. The entry field MUST NOT exist for the AddRequest +%%% to succeed. The parent of the entry MUST exist. +%%% Example: +%%% +%%% add(Handle, +%%% "cn=Bill Valentine, ou=people, o=Bluetail AB, dc=bluetail, dc=com", +%%% [{"objectclass", ["person"]}, +%%% {"cn", ["Bill Valentine"]}, +%%% {"sn", ["Valentine"]}, +%%% {"telephoneNumber", ["545 555 00"]}] +%%% ) +%%% -------------------------------------------------------------------- +add(Handle, Entry, Attributes) when is_pid(Handle),is_list(Entry),is_list(Attributes) -> + send(Handle, {add, Entry, add_attrs(Attributes)}), + recv(Handle). + +%%% Do sanity check ! +add_attrs(Attrs) -> + F = fun({Type,Vals}) when is_list(Type),is_list(Vals) -> + %% Confused ? Me too... :-/ + {'AddRequest_attributes',Type, Vals} + end, + case catch lists:map(F, Attrs) of + {'EXIT', _} -> throw({error, attribute_values}); + Else -> Else + end. + +%%% -------------------------------------------------------------------- +%%% Delete an entry. The entry consists of the DN of +%%% the entry to be deleted. +%%% Example: +%%% +%%% delete(Handle, +%%% "cn=Bill Valentine, ou=people, o=Bluetail AB, dc=bluetail, dc=com" +%%% ) +%%% -------------------------------------------------------------------- +delete(Handle, Entry) when is_pid(Handle), is_list(Entry) -> + send(Handle, {delete, Entry}), + recv(Handle). + +%%% -------------------------------------------------------------------- +%%% Modify an entry. Given an entry a number of modification +%%% operations can be performed as one atomic operation. +%%% Example: +%%% +%%% modify(Handle, +%%% "cn=Torbjorn Tornkvist, ou=people, o=Bluetail AB, dc=bluetail, dc=com", +%%% [mod_replace("telephoneNumber", ["555 555 00"]), +%%% mod_add("description", ["LDAP hacker"])] +%%% ) +%%% -------------------------------------------------------------------- +modify(Handle, Object, Mods) when is_pid(Handle), is_list(Object), is_list(Mods) -> + send(Handle, {modify, Object, Mods}), + recv(Handle). + +%%% +%%% Modification operations. +%%% Example: +%%% mod_replace("telephoneNumber", ["555 555 00"]) +%%% +mod_add(Type, Values) when is_list(Type), is_list(Values) -> m(add, Type, Values). +mod_delete(Type, Values) when is_list(Type), is_list(Values) -> m(delete, Type, Values). +mod_replace(Type, Values) when is_list(Type), is_list(Values) -> m(replace, Type, Values). + +m(Operation, Type, Values) -> + #'ModifyRequest_changes_SEQOF'{ + operation = Operation, + modification = #'PartialAttribute'{ + type = Type, + vals = Values}}. + +%%% -------------------------------------------------------------------- +%%% Modify an entry. Given an entry a number of modification +%%% operations can be performed as one atomic operation. +%%% Example: +%%% +%%% modify_dn(Handle, +%%% "cn=Bill Valentine, ou=people, o=Bluetail AB, dc=bluetail, dc=com", +%%% "cn=Ben Emerson", +%%% true, +%%% "" +%%% ) +%%% -------------------------------------------------------------------- +modify_dn(Handle, Entry, NewRDN, DelOldRDN, NewSup) + when is_pid(Handle),is_list(Entry),is_list(NewRDN),is_atom(DelOldRDN),is_list(NewSup) -> + send(Handle, {modify_dn, Entry, NewRDN, + bool_p(DelOldRDN), optional(NewSup)}), + recv(Handle). + +%%% Sanity checks ! + +bool_p(Bool) when Bool==true;Bool==false -> Bool. + +optional([]) -> asn1_NOVALUE; +optional(Value) -> Value. + +%%% -------------------------------------------------------------------- +%%% Synchronous search of the Directory returning a +%%% requested set of attributes. +%%% +%%% Example: +%%% +%%% Filter = eldap:substrings("cn", [{any,"o"}]), +%%% eldap:search(S, [{base, "dc=bluetail, dc=com"}, +%%% {filter, Filter}, +%%% {attributes,["cn"]}])), +%%% +%%% Returned result: {ok, #eldap_search_result{}} +%%% +%%% Example: +%%% +%%% {ok,{eldap_search_result, +%%% [{eldap_entry, +%%% "cn=Magnus Froberg, dc=bluetail, dc=com", +%%% [{"cn",["Magnus Froberg"]}]}, +%%% {eldap_entry, +%%% "cn=Torbjorn Tornkvist, dc=bluetail, dc=com", +%%% [{"cn",["Torbjorn Tornkvist"]}]}], +%%% []}} +%%% +%%% -------------------------------------------------------------------- +search(Handle, A) when is_pid(Handle), is_record(A, eldap_search) -> + call_search(Handle, A); +search(Handle, L) when is_pid(Handle), is_list(L) -> + case catch parse_search_args(L) of + {error, Emsg} -> {error, Emsg}; + A when is_record(A, eldap_search) -> call_search(Handle, A) + end. + +call_search(Handle, A) -> + send(Handle, {search, A}), + recv(Handle). + +parse_search_args(Args) -> + parse_search_args(Args, + #eldap_search{scope = wholeSubtree, + deref = derefAlways}). + +parse_search_args([{base, Base}|T],A) -> + parse_search_args(T,A#eldap_search{base = Base}); +parse_search_args([{filter, Filter}|T],A) -> + parse_search_args(T,A#eldap_search{filter = Filter}); +parse_search_args([{scope, Scope}|T],A) -> + parse_search_args(T,A#eldap_search{scope = Scope}); +parse_search_args([{deref, Deref}|T],A) -> + parse_search_args(T,A#eldap_search{deref = Deref}); +parse_search_args([{attributes, Attrs}|T],A) -> + parse_search_args(T,A#eldap_search{attributes = Attrs}); +parse_search_args([{types_only, TypesOnly}|T],A) -> + parse_search_args(T,A#eldap_search{types_only = TypesOnly}); +parse_search_args([{timeout, Timeout}|T],A) when is_integer(Timeout) -> + parse_search_args(T,A#eldap_search{timeout = Timeout}); +parse_search_args([H|_],_) -> + throw({error,{unknown_arg, H}}); +parse_search_args([],A) -> + A. + +%%% +%%% The Scope parameter +%%% +baseObject() -> baseObject. +singleLevel() -> singleLevel. +wholeSubtree() -> wholeSubtree. + +%% +%% The derefAliases parameter +%% +neverDerefAliases() -> neverDerefAliases. +derefInSearching() -> derefInSearching. +derefFindingBaseObj() -> derefFindingBaseObj. +derefAlways() -> derefAlways. + +%%% +%%% Boolean filter operations +%%% +'and'(ListOfFilters) when is_list(ListOfFilters) -> {'and',ListOfFilters}. +'or'(ListOfFilters) when is_list(ListOfFilters) -> {'or', ListOfFilters}. +'not'(Filter) when is_tuple(Filter) -> {'not',Filter}. + +%%% +%%% The following Filter parameters consist of an attribute +%%% and an attribute value. Example: F("uid","tobbe") +%%% +equalityMatch(Desc, Value) -> {equalityMatch, av_assert(Desc, Value)}. +greaterOrEqual(Desc, Value) -> {greaterOrEqual, av_assert(Desc, Value)}. +lessOrEqual(Desc, Value) -> {lessOrEqual, av_assert(Desc, Value)}. +approxMatch(Desc, Value) -> {approxMatch, av_assert(Desc, Value)}. + +av_assert(Desc, Value) -> + #'AttributeValueAssertion'{attributeDesc = Desc, + assertionValue = Value}. + +%%% +%%% Filter to check for the presence of an attribute +%%% +present(Attribute) when is_list(Attribute) -> + {present, Attribute}. + + +%%% +%%% A substring filter seem to be based on a pattern: +%%% +%%% InitValue*AnyValue*FinalValue +%%% +%%% where all three parts seem to be optional (at least when +%%% talking with an OpenLDAP server). Thus, the arguments +%%% to substrings/2 looks like this: +%%% +%%% Type ::= string( <attribute> ) +%%% SubStr ::= listof( {initial,Value} | {any,Value}, {final,Value}) +%%% +%%% Example: substrings("sn",[{initial,"To"},{any,"kv"},{final,"st"}]) +%%% will match entries containing: 'sn: Tornkvist' +%%% +substrings(Type, SubStr) when is_list(Type), is_list(SubStr) -> + Ss = {'SubstringFilter_substrings',v_substr(SubStr)}, + {substrings,#'SubstringFilter'{type = Type, + substrings = Ss}}. + +%%% -------------------------------------------------------------------- +%%% Worker process. We keep track of a controlling process to +%%% be able to terminate together with it. +%%% -------------------------------------------------------------------- + +init(Hosts, Opts, Cpid) -> + Data = parse_args(Opts, Cpid, #eldap{}), + case try_connect(Hosts, Data) of + {ok,Data2} -> + send(Cpid, {ok,self()}), + ?MODULE:loop(Cpid, Data2); + Else -> + send(Cpid, Else), + unlink(Cpid), + exit(Else) + end. + +parse_args([{port, Port}|T], Cpid, Data) when is_integer(Port) -> + parse_args(T, Cpid, Data#eldap{port = Port}); +parse_args([{timeout, Timeout}|T], Cpid, Data) when is_integer(Timeout),Timeout>0 -> + parse_args(T, Cpid, Data#eldap{timeout = Timeout}); +parse_args([{anon_auth, true}|T], Cpid, Data) -> + parse_args(T, Cpid, Data#eldap{anon_auth = false}); +parse_args([{anon_auth, _}|T], Cpid, Data) -> + parse_args(T, Cpid, Data); +parse_args([{ssl, true}|T], Cpid, Data) -> + parse_args(T, Cpid, Data#eldap{use_tls = true}); +parse_args([{ssl, _}|T], Cpid, Data) -> + parse_args(T, Cpid, Data); +parse_args([{log, F}|T], Cpid, Data) when is_function(F) -> + parse_args(T, Cpid, Data#eldap{log = F}); +parse_args([{log, _}|T], Cpid, Data) -> + parse_args(T, Cpid, Data); +parse_args([H|_], Cpid, _) -> + send(Cpid, {error,{wrong_option,H}}), + exit(wrong_option); +parse_args([], _, Data) -> + Data. + +%%% Try to connect to the hosts in the listed order, +%%% and stop with the first one to which a successful +%%% connection is made. + +try_connect([Host|Hosts], Data) -> + TcpOpts = [{packet, asn1}, {active,false}], + try do_connect(Host, Data, TcpOpts) of + {ok,Fd} -> {ok,Data#eldap{host = Host, fd = Fd}}; + Err -> + log2(Data, "Connect: ~p failed ~p~n",[Host, Err]), + try_connect(Hosts, Data) + catch _:Err -> + log2(Data, "Connect: ~p failed ~p~n",[Host, Err]), + try_connect(Hosts, Data) + end; +try_connect([],_) -> + {error,"connect failed"}. + +do_connect(Host, Data, Opts) when Data#eldap.use_tls == false -> + gen_tcp:connect(Host, Data#eldap.port, Opts, Data#eldap.timeout); +do_connect(Host, Data, Opts) when Data#eldap.use_tls == true -> + ssl:connect(Host, Data#eldap.port, [{verify,0}|Opts]). + + +loop(Cpid, Data) -> + receive + + {From, {search, A}} -> + {Res,NewData} = do_search(Data, A), + send(From,Res), + ?MODULE:loop(Cpid, NewData); + + {From, {modify, Obj, Mod}} -> + {Res,NewData} = do_modify(Data, Obj, Mod), + send(From,Res), + ?MODULE:loop(Cpid, NewData); + + {From, {modify_dn, Obj, NewRDN, DelOldRDN, NewSup}} -> + {Res,NewData} = do_modify_dn(Data, Obj, NewRDN, DelOldRDN, NewSup), + send(From,Res), + ?MODULE:loop(Cpid, NewData); + + {From, {add, Entry, Attrs}} -> + {Res,NewData} = do_add(Data, Entry, Attrs), + send(From,Res), + ?MODULE:loop(Cpid, NewData); + + {From, {delete, Entry}} -> + {Res,NewData} = do_delete(Data, Entry), + send(From,Res), + ?MODULE:loop(Cpid, NewData); + + {From, {simple_bind, Dn, Passwd}} -> + {Res,NewData} = do_simple_bind(Data, Dn, Passwd), + send(From,Res), + ?MODULE:loop(Cpid, NewData); + + {From, {cnt_proc, NewCpid}} -> + unlink(Cpid), + send(From,ok), + ?PRINT("New Cpid is: ~p~n",[NewCpid]), + ?MODULE:loop(NewCpid, Data); + + {_From, close} -> + unlink(Cpid), + exit(closed); + + {Cpid, 'EXIT', Reason} -> + ?PRINT("Got EXIT from Cpid, reason=~p~n",[Reason]), + exit(Reason); + + _XX -> + ?PRINT("loop got: ~p~n",[_XX]), + ?MODULE:loop(Cpid, Data) + + end. + +%%% -------------------------------------------------------------------- +%%% bindRequest +%%% -------------------------------------------------------------------- + +%%% Authenticate ourselves to the directory using +%%% simple authentication. + +do_simple_bind(Data, anon, anon) -> %% For testing + do_the_simple_bind(Data, "", ""); +do_simple_bind(Data, Dn, _Passwd) when Dn=="",Data#eldap.anon_auth==false -> + {{error,anonymous_auth},Data}; +do_simple_bind(Data, _Dn, Passwd) when Passwd=="",Data#eldap.anon_auth==false -> + {{error,anonymous_auth},Data}; +do_simple_bind(Data, Dn, Passwd) -> + do_the_simple_bind(Data, Dn, Passwd). + +do_the_simple_bind(Data, Dn, Passwd) -> + case catch exec_simple_bind(Data#eldap{binddn = Dn, + passwd = Passwd, + id = bump_id(Data)}) of + {ok,NewData} -> {ok,NewData}; + {error,Emsg} -> {{error,Emsg},Data}; + Else -> {{error,Else},Data} + end. + +exec_simple_bind(Data) -> + Req = #'BindRequest'{version = Data#eldap.version, + name = Data#eldap.binddn, + authentication = {simple, Data#eldap.passwd}}, + log2(Data, "bind request = ~p~n", [Req]), + Reply = request(Data#eldap.fd, Data, Data#eldap.id, {bindRequest, Req}), + log2(Data, "bind reply = ~p~n", [Reply]), + exec_simple_bind_reply(Data, Reply). + +exec_simple_bind_reply(Data, {ok,Msg}) when + Msg#'LDAPMessage'.messageID == Data#eldap.id -> + case Msg#'LDAPMessage'.protocolOp of + {bindResponse, Result} -> + case Result#'BindResponse'.resultCode of + success -> {ok,Data}; + Error -> {error, Error} + end; + Other -> {error, Other} + end; +exec_simple_bind_reply(_, Error) -> + {error, Error}. + + +%%% -------------------------------------------------------------------- +%%% searchRequest +%%% -------------------------------------------------------------------- + +do_search(Data, A) -> + case catch do_search_0(Data, A) of + {error,Emsg} -> {ldap_closed_p(Data, Emsg),Data}; + {'EXIT',Error} -> {ldap_closed_p(Data, Error),Data}; + {ok,Res,Ref,NewData} -> {{ok,polish(Res, Ref)},NewData}; + {{error,Reason},NewData} -> {{error,Reason},NewData}; + Else -> {ldap_closed_p(Data, Else),Data} + end. + +%%% +%%% Polish the returned search result +%%% + +polish(Res, Ref) -> + R = polish_result(Res), + %%% No special treatment of referrals at the moment. + #eldap_search_result{entries = R, + referrals = Ref}. + +polish_result([H|T]) when is_record(H, 'SearchResultEntry') -> + ObjectName = H#'SearchResultEntry'.objectName, + F = fun({_,A,V}) -> {A,V} end, + Attrs = lists:map(F, H#'SearchResultEntry'.attributes), + [#eldap_entry{object_name = ObjectName, + attributes = Attrs}| + polish_result(T)]; +polish_result([]) -> + []. + +do_search_0(Data, A) -> + Req = #'SearchRequest'{baseObject = A#eldap_search.base, + scope = v_scope(A#eldap_search.scope), + derefAliases = v_deref(A#eldap_search.deref), + sizeLimit = 0, % no size limit + timeLimit = v_timeout(A#eldap_search.timeout), + typesOnly = v_bool(A#eldap_search.types_only), + filter = v_filter(A#eldap_search.filter), + attributes = v_attributes(A#eldap_search.attributes) + }, + Id = bump_id(Data), + collect_search_responses(Data#eldap{id=Id}, Req, Id). + +%%% The returned answers cames in one packet per entry +%%% mixed with possible referals + +collect_search_responses(Data, Req, ID) -> + S = Data#eldap.fd, + log2(Data, "search request = ~p~n", [Req]), + send_request(S, Data, ID, {searchRequest, Req}), + Resp = recv_response(S, Data), + log2(Data, "search reply = ~p~n", [Resp]), + collect_search_responses(Data, S, ID, Resp, [], []). + +collect_search_responses(Data, S, ID, {ok,Msg}, Acc, Ref) + when is_record(Msg,'LDAPMessage') -> + case Msg#'LDAPMessage'.protocolOp of + {'searchResDone',R} -> + case R#'LDAPResult'.resultCode of + success -> + log2(Data, "search reply = searchResDone ~n", []), + {ok,Acc,Ref,Data}; + Reason -> + {{error,Reason},Data} + end; + {'searchResEntry',R} when is_record(R,'SearchResultEntry') -> + Resp = recv_response(S, Data), + log2(Data, "search reply = ~p~n", [Resp]), + collect_search_responses(Data, S, ID, Resp, [R|Acc], Ref); + {'searchResRef',R} -> + %% At the moment we don't do anyting sensible here since + %% I haven't been able to trigger the server to generate + %% a response like this. + Resp = recv_response(S, Data), + log2(Data, "search reply = ~p~n", [Resp]), + collect_search_responses(Data, S, ID, Resp, Acc, [R|Ref]); + Else -> + throw({error,Else}) + end; +collect_search_responses(_, _, _, Else, _, _) -> + throw({error,Else}). + +%%% -------------------------------------------------------------------- +%%% addRequest +%%% -------------------------------------------------------------------- + +do_add(Data, Entry, Attrs) -> + case catch do_add_0(Data, Entry, Attrs) of + {error,Emsg} -> {ldap_closed_p(Data, Emsg),Data}; + {'EXIT',Error} -> {ldap_closed_p(Data, Error),Data}; + {ok,NewData} -> {ok,NewData}; + Else -> {ldap_closed_p(Data, Else),Data} + end. + +do_add_0(Data, Entry, Attrs) -> + Req = #'AddRequest'{entry = Entry, + attributes = Attrs}, + S = Data#eldap.fd, + Id = bump_id(Data), + log2(Data, "add request = ~p~n", [Req]), + Resp = request(S, Data, Id, {addRequest, Req}), + log2(Data, "add reply = ~p~n", [Resp]), + check_reply(Data#eldap{id = Id}, Resp, addResponse). + + +%%% -------------------------------------------------------------------- +%%% deleteRequest +%%% -------------------------------------------------------------------- + +do_delete(Data, Entry) -> + case catch do_delete_0(Data, Entry) of + {error,Emsg} -> {ldap_closed_p(Data, Emsg),Data}; + {'EXIT',Error} -> {ldap_closed_p(Data, Error),Data}; + {ok,NewData} -> {ok,NewData}; + Else -> {ldap_closed_p(Data, Else),Data} + end. + +do_delete_0(Data, Entry) -> + S = Data#eldap.fd, + Id = bump_id(Data), + log2(Data, "del request = ~p~n", [Entry]), + Resp = request(S, Data, Id, {delRequest, Entry}), + log2(Data, "del reply = ~p~n", [Resp]), + check_reply(Data#eldap{id = Id}, Resp, delResponse). + + +%%% -------------------------------------------------------------------- +%%% modifyRequest +%%% -------------------------------------------------------------------- + +do_modify(Data, Obj, Mod) -> + case catch do_modify_0(Data, Obj, Mod) of + {error,Emsg} -> {ldap_closed_p(Data, Emsg),Data}; + {'EXIT',Error} -> {ldap_closed_p(Data, Error),Data}; + {ok,NewData} -> {ok,NewData}; + Else -> {ldap_closed_p(Data, Else),Data} + end. + +do_modify_0(Data, Obj, Mod) -> + v_modifications(Mod), + Req = #'ModifyRequest'{object = Obj, + changes = Mod}, + S = Data#eldap.fd, + Id = bump_id(Data), + log2(Data, "modify request = ~p~n", [Req]), + Resp = request(S, Data, Id, {modifyRequest, Req}), + log2(Data, "modify reply = ~p~n", [Resp]), + check_reply(Data#eldap{id = Id}, Resp, modifyResponse). + +%%% -------------------------------------------------------------------- +%%% modifyDNRequest +%%% -------------------------------------------------------------------- + +do_modify_dn(Data, Entry, NewRDN, DelOldRDN, NewSup) -> + case catch do_modify_dn_0(Data, Entry, NewRDN, DelOldRDN, NewSup) of + {error,Emsg} -> {ldap_closed_p(Data, Emsg),Data}; + {'EXIT',Error} -> {ldap_closed_p(Data, Error),Data}; + {ok,NewData} -> {ok,NewData}; + Else -> {ldap_closed_p(Data, Else),Data} + end. + +do_modify_dn_0(Data, Entry, NewRDN, DelOldRDN, NewSup) -> + Req = #'ModifyDNRequest'{entry = Entry, + newrdn = NewRDN, + deleteoldrdn = DelOldRDN, + newSuperior = NewSup}, + S = Data#eldap.fd, + Id = bump_id(Data), + log2(Data, "modify DN request = ~p~n", [Req]), + Resp = request(S, Data, Id, {modDNRequest, Req}), + log2(Data, "modify DN reply = ~p~n", [Resp]), + check_reply(Data#eldap{id = Id}, Resp, modDNResponse). + +%%% -------------------------------------------------------------------- +%%% Send an LDAP request and receive the answer +%%% -------------------------------------------------------------------- + +request(S, Data, ID, Request) -> + send_request(S, Data, ID, Request), + recv_response(S, Data). + +send_request(S, Data, ID, Request) -> + Message = #'LDAPMessage'{messageID = ID, + protocolOp = Request}, + {ok,Bytes} = asn1rt:encode('ELDAPv3', 'LDAPMessage', Message), + case do_send(S, Data, Bytes) of + {error,Reason} -> throw({gen_tcp_error,Reason}); + Else -> Else + end. + +do_send(S, Data, Bytes) when Data#eldap.use_tls == false -> + gen_tcp:send(S, Bytes); +do_send(S, Data, Bytes) when Data#eldap.use_tls == true -> + ssl:send(S, Bytes). + +do_recv(S, #eldap{use_tls=false, timeout=Timeout}, Len) -> + gen_tcp:recv(S, Len, Timeout); +do_recv(S, #eldap{use_tls=true, timeout=Timeout}, Len) -> + ssl:recv(S, Len, Timeout). + +recv_response(S, Data) -> + case do_recv(S, Data, 0) of + {ok, Packet} -> + check_tag(Packet), + case asn1rt:decode('ELDAPv3', 'LDAPMessage', Packet) of + {ok,Resp} -> {ok,Resp}; + Error -> throw(Error) + end; + {error,Reason} -> + throw({gen_tcp_error, Reason}); + Error -> + throw(Error) + end. + +%%% Sanity check of received packet +check_tag(Data) -> + case asn1rt_ber_bin:decode_tag(l2b(Data)) of + {_Tag, Data1, _Rb} -> + case asn1rt_ber_bin:decode_length(l2b(Data1)) of + {{_Len, _Data2}, _Rb2} -> ok; + _ -> throw({error,decoded_tag_length}) + end; + _ -> throw({error,decoded_tag}) + end. + +%%% Check for expected kind of reply +check_reply(Data, {ok,Msg}, Op) when + Msg#'LDAPMessage'.messageID == Data#eldap.id -> + case Msg#'LDAPMessage'.protocolOp of + {Op, Result} -> + case Result#'LDAPResult'.resultCode of + success -> {ok,Data}; + Error -> {error, Error} + end; + Other -> {error, Other} + end; +check_reply(_, Error, _) -> + {error, Error}. + + +%%% -------------------------------------------------------------------- +%%% Verify the input data +%%% -------------------------------------------------------------------- + +v_filter({'and',L}) -> {'and',L}; +v_filter({'or', L}) -> {'or',L}; +v_filter({'not',L}) -> {'not',L}; +v_filter({equalityMatch,AV}) -> {equalityMatch,AV}; +v_filter({greaterOrEqual,AV}) -> {greaterOrEqual,AV}; +v_filter({lessOrEqual,AV}) -> {lessOrEqual,AV}; +v_filter({approxMatch,AV}) -> {approxMatch,AV}; +v_filter({present,A}) -> {present,A}; +v_filter({substrings,S}) when is_record(S,'SubstringFilter') -> {substrings,S}; +v_filter(_Filter) -> throw({error,concat(["unknown filter: ",_Filter])}). + +v_modifications(Mods) -> + F = fun({_,Op,_}) -> + case lists:member(Op,[add,delete,replace]) of + true -> true; + _ -> throw({error,{mod_operation,Op}}) + end + end, + lists:foreach(F, Mods). + +v_substr([{Key,Str}|T]) when is_list(Str),Key==initial;Key==any;Key==final -> + [{Key,Str}|v_substr(T)]; +v_substr([H|_]) -> + throw({error,{substring_arg,H}}); +v_substr([]) -> + []. +v_scope(baseObject) -> baseObject; +v_scope(singleLevel) -> singleLevel; +v_scope(wholeSubtree) -> wholeSubtree; +v_scope(_Scope) -> throw({error,concat(["unknown scope: ",_Scope])}). + +v_deref(DR = neverDerefAliases) -> DR; +v_deref(DR = derefInSearching) -> DR; +v_deref(DR = derefFindingBaseObj) -> DR; +v_deref(DR = derefAlways ) -> DR. + +v_bool(true) -> true; +v_bool(false) -> false; +v_bool(_Bool) -> throw({error,concat(["not Boolean: ",_Bool])}). + +v_timeout(I) when is_integer(I), I>=0 -> I; +v_timeout(_I) -> throw({error,concat(["timeout not positive integer: ",_I])}). + +v_attributes(Attrs) -> + F = fun(A) when is_list(A) -> A; + (A) -> throw({error,concat(["attribute not String: ",A])}) + end, + lists:map(F,Attrs). + + +%%% -------------------------------------------------------------------- +%%% Log routines. Call a user provided log routine F. +%%% -------------------------------------------------------------------- + +%log1(Data, Str, Args) -> log(Data, Str, Args, 1). +log2(Data, Str, Args) -> log(Data, Str, Args, 2). + +log(Data, Str, Args, Level) when is_function(Data#eldap.log) -> + catch (Data#eldap.log)(Level, Str, Args); +log(_, _, _, _) -> + ok. + + +%%% -------------------------------------------------------------------- +%%% Misc. routines +%%% -------------------------------------------------------------------- + +send(To,Msg) -> To ! {self(),Msg}. +recv(From) -> + receive + {From,Msg} -> Msg; + {'EXIT', From, Reason} -> + {error, {internal_error, Reason}} + end. + +ldap_closed_p(Data, Emsg) when Data#eldap.use_tls == true -> + %% Check if the SSL socket seems to be alive or not + case catch ssl:sockname(Data#eldap.fd) of + {error, _} -> + ssl:close(Data#eldap.fd), + {error, ldap_closed}; + {ok, _} -> + {error, Emsg}; + _ -> + %% sockname crashes if the socket pid is not alive + {error, ldap_closed} + end; +ldap_closed_p(Data, Emsg) -> + %% non-SSL socket + case inet:port(Data#eldap.fd) of + {error,_} -> {error, ldap_closed}; + _ -> {error,Emsg} + end. + +bump_id(Data) -> Data#eldap.id + 1. + + +%%% -------------------------------------------------------------------- +%%% parse_dn/1 - Implementation of RFC 2253: +%%% +%%% "UTF-8 String Representation of Distinguished Names" +%%% +%%% Test cases: +%%% +%%% The simplest case: +%%% +%%% 1> eldap:parse_dn("CN=Steve Kille,O=Isode Limited,C=GB"). +%%% {ok,[[{attribute_type_and_value,"CN","Steve Kille"}], +%%% [{attribute_type_and_value,"O","Isode Limited"}], +%%% [{attribute_type_and_value,"C","GB"}]]} +%%% +%%% The first RDN is multi-valued: +%%% +%%% 2> eldap:parse_dn("OU=Sales+CN=J. Smith,O=Widget Inc.,C=US"). +%%% {ok,[[{attribute_type_and_value,"OU","Sales"}, +%%% {attribute_type_and_value,"CN","J. Smith"}], +%%% [{attribute_type_and_value,"O","Widget Inc."}], +%%% [{attribute_type_and_value,"C","US"}]]} +%%% +%%% Quoting a comma: +%%% +%%% 3> eldap:parse_dn("CN=L. Eagle,O=Sue\\, Grabbit and Runn,C=GB"). +%%% {ok,[[{attribute_type_and_value,"CN","L. Eagle"}], +%%% [{attribute_type_and_value,"O","Sue\\, Grabbit and Runn"}], +%%% [{attribute_type_and_value,"C","GB"}]]} +%%% +%%% A value contains a carriage return: +%%% +%%% 4> eldap:parse_dn("CN=Before +%%% 4> After,O=Test,C=GB"). +%%% {ok,[[{attribute_type_and_value,"CN","Before\nAfter"}], +%%% [{attribute_type_and_value,"O","Test"}], +%%% [{attribute_type_and_value,"C","GB"}]]} +%%% +%%% 5> eldap:parse_dn("CN=Before\\0DAfter,O=Test,C=GB"). +%%% {ok,[[{attribute_type_and_value,"CN","Before\\0DAfter"}], +%%% [{attribute_type_and_value,"O","Test"}], +%%% [{attribute_type_and_value,"C","GB"}]]} +%%% +%%% An RDN in OID form: +%%% +%%% 6> eldap:parse_dn("1.3.6.1.4.1.1466.0=#04024869,O=Test,C=GB"). +%%% {ok,[[{attribute_type_and_value,"1.3.6.1.4.1.1466.0","#04024869"}], +%%% [{attribute_type_and_value,"O","Test"}], +%%% [{attribute_type_and_value,"C","GB"}]]} +%%% +%%% +%%% -------------------------------------------------------------------- + +parse_dn("") -> % empty DN string + {ok,[]}; +parse_dn([H|_] = Str) when H=/=$, -> % 1:st name-component ! + case catch parse_name(Str,[]) of + {'EXIT',Reason} -> {parse_error,internal_error,Reason}; + Else -> Else + end. + +parse_name("",Acc) -> + {ok,lists:reverse(Acc)}; +parse_name([$,|T],Acc) -> % N:th name-component ! + parse_name(T,Acc); +parse_name(Str,Acc) -> + {Rest,NameComponent} = parse_name_component(Str), + parse_name(Rest,[NameComponent|Acc]). + +parse_name_component(Str) -> + parse_name_component(Str,[]). + +parse_name_component(Str,Acc) -> + case parse_attribute_type_and_value(Str) of + {[$+|Rest], ATV} -> + parse_name_component(Rest,[ATV|Acc]); + {Rest,ATV} -> + {Rest,lists:reverse([ATV|Acc])} + end. + +parse_attribute_type_and_value(Str) -> + case parse_attribute_type(Str) of + {_Rest,[]} -> + parse_error(expecting_attribute_type,Str); + {Rest,Type} -> + Rest2 = parse_equal_sign(Rest), + {Rest3,Value} = parse_attribute_value(Rest2), + {Rest3,{attribute_type_and_value,Type,Value}} + end. + +-define(IS_ALPHA(X) , X>=$a,X=<$z;X>=$A,X=<$Z ). +-define(IS_DIGIT(X) , X>=$0,X=<$9 ). +-define(IS_SPECIAL(X) , X==$,;X==$=;X==$+;X==$<;X==$>;X==$#;X==$; ). +-define(IS_QUOTECHAR(X) , X=/=$\\,X=/=$" ). +-define(IS_STRINGCHAR(X) , + X=/=$,,X=/=$=,X=/=$+,X=/=$<,X=/=$>,X=/=$#,X=/=$;,?IS_QUOTECHAR(X) ). +-define(IS_HEXCHAR(X) , ?IS_DIGIT(X);X>=$a,X=<$f;X>=$A,X=<$F ). + +parse_attribute_type([H|T]) when ?IS_ALPHA(H) -> + %% NB: It must be an error in the RFC in the definition + %% of 'attributeType', should be: (ALPHA *keychar) + {Rest,KeyChars} = parse_keychars(T), + {Rest,[H|KeyChars]}; +parse_attribute_type([H|_] = Str) when ?IS_DIGIT(H) -> + parse_oid(Str); +parse_attribute_type(Str) -> + parse_error(invalid_attribute_type,Str). + + + +%%% Is a hexstring ! +parse_attribute_value([$#,X,Y|T]) when ?IS_HEXCHAR(X),?IS_HEXCHAR(Y) -> + {Rest,HexString} = parse_hexstring(T), + {Rest,[$#,X,Y|HexString]}; +%%% Is a "quotation-sequence" ! +parse_attribute_value([$"|T]) -> + {Rest,Quotation} = parse_quotation(T), + {Rest,[$"|Quotation]}; +%%% Is a stringchar , pair or Empty ! +parse_attribute_value(Str) -> + parse_string(Str). + +parse_hexstring(Str) -> + parse_hexstring(Str,[]). + +parse_hexstring([X,Y|T],Acc) when ?IS_HEXCHAR(X),?IS_HEXCHAR(Y) -> + parse_hexstring(T,[Y,X|Acc]); +parse_hexstring(T,Acc) -> + {T,lists:reverse(Acc)}. + +parse_quotation([$"|T]) -> % an empty: "" is ok ! + {T,[$"]}; +parse_quotation(Str) -> + parse_quotation(Str,[]). + +%%% Parse to end of quotation +parse_quotation([$"|T],Acc) -> + {T,lists:reverse([$"|Acc])}; +parse_quotation([X|T],Acc) when ?IS_QUOTECHAR(X) -> + parse_quotation(T,[X|Acc]); +parse_quotation([$\\,X|T],Acc) when ?IS_SPECIAL(X) -> + parse_quotation(T,[X,$\\|Acc]); +parse_quotation([$\\,$\\|T],Acc) -> + parse_quotation(T,[$\\,$\\|Acc]); +parse_quotation([$\\,$"|T],Acc) -> + parse_quotation(T,[$",$\\|Acc]); +parse_quotation([$\\,X,Y|T],Acc) when ?IS_HEXCHAR(X),?IS_HEXCHAR(Y) -> + parse_quotation(T,[Y,X,$\\|Acc]); +parse_quotation(T,_) -> + parse_error(expecting_double_quote_mark,T). + +parse_string(Str) -> + parse_string(Str,[]). + +parse_string("",Acc) -> + {"",lists:reverse(Acc)}; +parse_string([H|T],Acc) when ?IS_STRINGCHAR(H) -> + parse_string(T,[H|Acc]); +parse_string([$\\,X|T],Acc) when ?IS_SPECIAL(X) -> % is a pair ! + parse_string(T,[X,$\\|Acc]); +parse_string([$\\,$\\|T],Acc) -> % is a pair ! + parse_string(T,[$\\,$\\|Acc]); +parse_string([$\\,$" |T],Acc) -> % is a pair ! + parse_string(T,[$" ,$\\|Acc]); +parse_string([$\\,X,Y|T],Acc) when ?IS_HEXCHAR(X),?IS_HEXCHAR(Y) -> % is a pair! + parse_string(T,[Y,X,$\\|Acc]); +parse_string(T,Acc) -> + {T,lists:reverse(Acc)}. + +parse_equal_sign([$=|T]) -> T; +parse_equal_sign(T) -> parse_error(expecting_equal_sign,T). + +parse_keychars(Str) -> parse_keychars(Str,[]). + +parse_keychars([H|T],Acc) when ?IS_ALPHA(H) -> parse_keychars(T,[H|Acc]); +parse_keychars([H|T],Acc) when ?IS_DIGIT(H) -> parse_keychars(T,[H|Acc]); +parse_keychars([$-|T],Acc) -> parse_keychars(T,[$-|Acc]); +parse_keychars(T,Acc) -> {T,lists:reverse(Acc)}. + +parse_oid(Str) -> parse_oid(Str,[]). + +parse_oid([H,$.|T], Acc) when ?IS_DIGIT(H) -> + parse_oid(T,[$.,H|Acc]); +parse_oid([H|T], Acc) when ?IS_DIGIT(H) -> + parse_oid(T,[H|Acc]); +parse_oid(T, Acc) -> + {T,lists:reverse(Acc)}. + +parse_error(Emsg,Rest) -> + throw({parse_error,Emsg,Rest}). + + +%%% -------------------------------------------------------------------- +%%% Parse LDAP url according to RFC 2255 +%%% +%%% Test case: +%%% +%%% 2> eldap:parse_ldap_url("ldap://10.42.126.33:389/cn=Administrative%20CA,o=Post%20Danmark,c=DK?certificateRevokationList;binary"). +%%% {ok,{{10,42,126,33},389}, +%%% [[{attribute_type_and_value,"cn","Administrative%20CA"}], +%%% [{attribute_type_and_value,"o","Post%20Danmark"}], +%%% [{attribute_type_and_value,"c","DK"}]], +%%% {attributes,["certificateRevokationList;binary"]}} +%%% +%%% -------------------------------------------------------------------- + +parse_ldap_url("ldap://" ++ Rest1 = Str) -> + {Rest2,HostPort} = parse_hostport(Rest1), + %% Split the string into DN and Attributes+etc + {Sdn,Rest3} = split_string(rm_leading_slash(Rest2),$?), + case parse_dn(Sdn) of + {parse_error,internal_error,_Reason} -> + {parse_error,internal_error,{Str,[]}}; + {parse_error,Emsg,Tail} -> + Head = get_head(Str,Tail), + {parse_error,Emsg,{Head,Tail}}; + {ok,DN} -> + %% We stop parsing here for now and leave + %% 'scope', 'filter' and 'extensions' to + %% be implemented later if needed. + {_Rest4,Attributes} = parse_attributes(Rest3), + {ok,HostPort,DN,Attributes} + end. + +rm_leading_slash([$/|Tail]) -> Tail; +rm_leading_slash(Tail) -> Tail. + +parse_attributes([$?|Tail]) -> + case split_string(Tail,$?) of + {[],Attributes} -> + {[],{attributes,string:tokens(Attributes,",")}}; + {Attributes,Rest} -> + {Rest,{attributes,string:tokens(Attributes,",")}} + end. + +parse_hostport(Str) -> + {HostPort,Rest} = split_string(Str,$/), + case split_string(HostPort,$:) of + {Shost,[]} -> + {Rest,{parse_host(Rest,Shost),?LDAP_PORT}}; + {Shost,[$:|Sport]} -> + {Rest,{parse_host(Rest,Shost), + parse_port(Rest,Sport)}} + end. + +parse_port(Rest,Sport) -> + try list_to_integer(Sport) + catch _:_ -> parse_error(parsing_port,Rest) + end. + +parse_host(Rest,Shost) -> + case catch validate_host(Shost) of + {parse_error,Emsg,_} -> parse_error(Emsg,Rest); + Host -> Host + end. + +validate_host(Shost) -> + case inet_parse:address(Shost) of + {ok,Host} -> Host; + _ -> + case inet_parse:domain(Shost) of + true -> Shost; + _ -> parse_error(parsing_host,Shost) + end + end. + + +split_string(Str,Key) -> + Pred = fun(X) when X==Key -> false; (_) -> true end, + lists:splitwith(Pred, Str). + +get_head(Str,Tail) -> + get_head(Str,Tail,[]). + +%%% Should always succeed ! +get_head([H|Tail],Tail,Rhead) -> lists:reverse([H|Rhead]); +get_head([H|Rest],Tail,Rhead) -> get_head(Rest,Tail,[H|Rhead]). + +l2b(B) when is_binary(B) -> B; +l2b(L) when is_list(L) -> list_to_binary(L). + diff --git a/lib/eldap/test/Makefile b/lib/eldap/test/Makefile new file mode 100644 index 0000000000..a17d4f56b2 --- /dev/null +++ b/lib/eldap/test/Makefile @@ -0,0 +1,83 @@ +# +# %CopyrightBegin% +# +# Copyright Ericsson AB 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% +# + +include $(ERL_TOP)/make/target.mk +include $(ERL_TOP)/make/$(TARGET)/otp.mk + + +INCLUDES= -I. -I ../include + +# ---------------------------------------------------- +# Target Specs +# ---------------------------------------------------- + +MODULES= \ + eldap_basic_SUITE + +ERL_FILES= $(MODULES:%=%.erl) + +HRL_FILES= + +TARGET_FILES= \ + $(MODULES:%=$(EBIN)/%.$(EMULATOR)) + +SPEC_FILES = eldap.spec + +# COVER_FILE = eldap.cover + + +# ---------------------------------------------------- +# Release directory specification +# ---------------------------------------------------- +RELSYSDIR = $(RELEASE_PATH)/eldap_test + +# ---------------------------------------------------- +# FLAGS +# ---------------------------------------------------- +ERL_COMPILE_FLAGS += $(INCLUDES) + +EBIN = . + +# ---------------------------------------------------- +# Targets +# ---------------------------------------------------- + +tests debug opt: $(TARGET_FILES) + + +clean: + rm -f $(TARGET_FILES) + rm -f core + +docs: + +# ---------------------------------------------------- +# Release Target +# ---------------------------------------------------- +include $(ERL_TOP)/make/otp_release_targets.mk + +release_spec: opt + +release_tests_spec: opt + $(INSTALL_DIR) $(RELSYSDIR) + $(INSTALL_DATA) $(SPEC_FILES) $(ERL_FILES) $(COVER_FILE) $(HRL_FILES) $(RELSYSDIR) + $(INSTALL_DATA) $(TARGET_FILES) $(RELSYSDIR) + chmod -R u+w $(RELSYSDIR) +# @tar cf - *_SUITE_data | (cd $(RELSYSDIR); tar xf -) +release_docs_spec: diff --git a/lib/eldap/test/eldap.spec b/lib/eldap/test/eldap.spec new file mode 100644 index 0000000000..b14e9a655a --- /dev/null +++ b/lib/eldap/test/eldap.spec @@ -0,0 +1 @@ +{suites,"../eldap_test",all}. diff --git a/lib/eldap/test/eldap_basic_SUITE.erl b/lib/eldap/test/eldap_basic_SUITE.erl new file mode 100644 index 0000000000..c7e3052b29 --- /dev/null +++ b/lib/eldap/test/eldap_basic_SUITE.erl @@ -0,0 +1,222 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 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(eldap_basic_SUITE). + +-compile(export_all). + +%%-include_lib("common_test/include/ct.hrl"). +-include_lib("test_server/include/test_server.hrl"). +-include_lib("eldap/include/eldap.hrl"). + +-define(TIMEOUT, 120000). % 2 min + +init_per_suite(Config0) -> + {{EldapHost,Port}, Config1} = + case catch ct:get_config(eldap_server, undefined) of + undefined -> %% Dev test only + Server = {"localhost", 9876}, + {Server, [{eldap_server, {"localhost", 9876}}|Config0]}; + {'EXIT', _} -> %% Dev test only + Server = {"localhost", 9876}, + {Server, [{eldap_server, {"localhost", 9876}}|Config0]}; + Server -> + {Server, [{eldap_server, Server}|Config0]} + end, + %% Add path for this test run + 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", + Config = [{eldap_path,Path}|Config1], + 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"]}]), + Config + 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", [EldapHost]))} + end. +end_per_suite(Config) -> + %% Cleanup everything + {EHost, Port} = proplists:get_value(eldap_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. + +init_per_testcase(_TestCase, Config) -> Config. +end_per_testcase(_TestCase, _Config) -> ok. + +%% suite() -> + +all() -> + [app, + api]. + +app(doc) -> "Test that the eldap app file is ok"; +app(suite) -> []; +app(Config) when is_list(Config) -> + ok = test_server:app_test(public_key). + +api(doc) -> "Basic test that all api functions works as expected"; +api(suite) -> []; +api(Config) -> + {Host,Port} = proplists:get_value(eldap_server, Config), + {ok, H} = eldap:open([Host], [{port,Port}]), + %% {ok, H} = eldap:open([Host], [{port,Port+1}, {ssl, true}]), + BasePath = proplists:get_value(eldap_path, Config), + All = fun(Where) -> + eldap:search(H, #eldap_search{base=Where, + filter=eldap:present("objectclass"), + scope= eldap:wholeSubtree()}) + end, + Search = fun(Filter) -> + eldap:search(H, #eldap_search{base=BasePath, + filter=Filter, + scope=eldap:singleLevel()}) + end, + {ok, #eldap_search_result{entries=[_]}} = All(BasePath), + {error, noSuchObject} = All("cn=Bar,"++BasePath), + + {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"), + + %% Add + ok = 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"]}]), + + %% Search + 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), + + %% MODIFY + 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", [])]), + + %% DELETE + {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), + + %% MODIFY_DN + ok = eldap:modify_dn(H, FB, "cn=Niclas Andre", true, ""), + %%io:format("Res ~p~n ~p~n",[R, All(BasePath)]), + + eldap:close(H), + ok. + +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) + 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}) + 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/ldap_server/slapd.conf b/lib/eldap/test/ldap_server/slapd.conf new file mode 100644 index 0000000000..87be676d9f --- /dev/null +++ b/lib/eldap/test/ldap_server/slapd.conf @@ -0,0 +1,14 @@ +include /etc/ldap/schema/core.schema +pidfile /tmp/openldap-data/slapd.pid +argsfile /tmp/openldap-data/slapd.args + +database bdb +suffix "dc=ericsson,dc=se" +rootdn "cn=Manager,dc=ericsson,dc=se" +rootpw hejsan +# The database must exist before running slapd +directory /tmp/openldap-data +# Indices to maintain +index objectClass eq +# URI "ldap://0.0.0.0:9876 ldaps://0.0.0.0:9870" +# servers/slapd/slapd -d 255 -h "ldap://0.0.0.0:9876 ldaps://0.0.0.0:9870" -f /ldisk/dgud/src/otp/lib/eldap/test/ldap_server/slapd.conf
\ No newline at end of file diff --git a/lib/eldap/vsn.mk b/lib/eldap/vsn.mk new file mode 100644 index 0000000000..c9d6e4e324 --- /dev/null +++ b/lib/eldap/vsn.mk @@ -0,0 +1 @@ +ELDAP_VSN = 1.0 diff --git a/lib/observer/test/crashdump_viewer_SUITE.erl b/lib/observer/test/crashdump_viewer_SUITE.erl index 79ece7edf5..5bbce9d076 100644 --- a/lib/observer/test/crashdump_viewer_SUITE.erl +++ b/lib/observer/test/crashdump_viewer_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2003-2011. All Rights Reserved. +%% Copyright Ericsson AB 2003-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 @@ -318,7 +318,7 @@ browse_file(Port,File) -> %% The page where a filename can be entered title(Port,"read_file_frame","Read File"), - + %% Load a file Url = "http://localhost:"++Port++"/cdv_erl/crashdump_viewer/read_file", Html = request_sync(post,{Url,[],[],"path="++File}), @@ -711,6 +711,7 @@ rel_opt(Rel) -> r11b -> [{erl,[{release,"r11b_patched"}]}]; r12b -> [{erl,[{release,"r12b_patched"}]}]; r13b -> [{erl,[{release,"r13b_patched"}]}]; + r14b -> [{erl,[{release,"r14b_latest"}]}]; %naming convention changed current -> [] end. @@ -734,6 +735,6 @@ compat_rel(Rel) -> r11b -> "+R11 "; r12b -> "+R12 "; r13b -> "+R13 "; - r14b -> "+R13 "; + r14b -> "+R14 "; current -> "" end. diff --git a/lib/orber/doc/src/ch_install.xml b/lib/orber/doc/src/ch_install.xml index de9c0e3a9d..e5af2a4a27 100644 --- a/lib/orber/doc/src/ch_install.xml +++ b/lib/orber/doc/src/ch_install.xml @@ -396,13 +396,13 @@ nodeB@hostB> orber:start(). <row> <cell align="left" valign="middle">ssl_server_options</cell> <cell align="left" valign="middle">list()</cell> - <cell align="left" valign="middle">See the <seealso marker="ssl:ssl">SSL</seealso> application + <cell align="left" valign="middle">See the <seealso marker="ssl:ssl">SSL application</seealso> for valid options.</cell> </row> <row> <cell align="left" valign="middle">ssl_client_options</cell> <cell align="left" valign="middle">list()</cell> - <cell align="left" valign="middle">See the <seealso marker="ssl:ssl">SSL</seealso> application + <cell align="left" valign="middle">See the <seealso marker="ssl:ssl">SSL application</seealso> for valid options.</cell> </row> <row> @@ -631,9 +631,19 @@ nodeB@hostB> orber:start(). <c>{local, DefaultNATPort, [{Port, NATPort}]}</c>. See also <seealso marker="ch_install#firewall">Firewall Configuration</seealso>.</item> <tag><em>ssl_server_options</em></tag> - <item>the file path to a server side CA certificate.</item> + <item>A list of the SSL options when Orber is the server. + In general it's just to remove the 'ssl_server_' prefix from the old options, + i.e. <c>ssl_server_verify</c> will just be <c>verify</c> in this option list. + See the <seealso marker="ssl:ssl">SSL application</seealso> for the correct list of possible + options and their values. + </item> <tag><em>ssl_client_options</em></tag> - <item>The path to a file containing a chain of PEM encoded certificates.</item> + <item>A list of the SSL options when Orber is the client. + In general it's just to remove the <c>ssl_client_</c> prefix from the old options, + i.e. <c>ssl_client_depth</c> will just be <c>depth</c> in this option list. + See the <seealso marker="ssl:ssl">SSL application</seealso> for the correct list of possible + options and their values. + </item> <tag><em>iiop_ssl_out_keepalive</em></tag> <item>Enables periodic transmission on a connected socket, when no other data is being exchanged. If the other end does not respond, the diff --git a/lib/reltool/src/reltool.hrl b/lib/reltool/src/reltool.hrl index 71d3b60a2b..26dcd82447 100644 --- a/lib/reltool/src/reltool.hrl +++ b/lib/reltool/src/reltool.hrl @@ -131,21 +131,22 @@ trap_exit :: boolean() }). +%% Types '$1','$2' and '_' are needed for match specs to ets:select -record(mod, { %% Static - name :: mod_name(), - app_name :: app_name(), - incl_cond :: incl_cond() | undefined, - debug_info :: debug_info() | undefined, - is_app_mod :: boolean(), - is_ebin_mod :: boolean(), - uses_mods :: [mod_name()], - exists :: boolean(), + name :: '$1' | mod_name(), + app_name :: '_' | app_name(), + incl_cond :: '_' | incl_cond() | undefined, + debug_info :: '_' | debug_info() | undefined, + is_app_mod :: '_' | boolean(), + is_ebin_mod :: '_' | boolean(), + uses_mods :: '$2' | [mod_name()], + exists :: '_' | boolean(), %% Dynamic - status :: status(), - used_by_mods :: [mod_name()], - is_pre_included :: boolean() | undefined, - is_included :: boolean() | undefined + status :: '_' | status(), + used_by_mods :: '_' | [mod_name()], + is_pre_included :: '_' | boolean() | undefined, + is_included :: '_' | boolean() | undefined }). -record(app_info, @@ -166,41 +167,42 @@ -record(regexp, {source, compiled}). +%% Types '$1','$2' and '_' are needed for match specs to ets:select -record(app, { %% Static info - name :: app_name(), - is_escript :: boolean() | {inlined, escript_app_name()}, - use_selected_vsn :: vsn | dir | undefined, - active_dir :: dir(), - sorted_dirs :: [dir()], - vsn :: app_vsn(), - label :: app_label(), - info :: #app_info{} | undefined, - mods :: [#mod{}], + name :: '_' | app_name(), + is_escript :: '_' | boolean() | {inlined, escript_app_name()}, + use_selected_vsn :: '_' | vsn | dir | undefined, + active_dir :: '_' | dir(), + sorted_dirs :: '_' | [dir()], + vsn :: '_' | app_vsn(), + label :: '_' | app_label(), + info :: '_' | #app_info{} | undefined, + mods :: '_' | [#mod{}], %% Static source cond - mod_cond :: mod_cond() | undefined, - incl_cond :: incl_cond() | undefined, + mod_cond :: '_' | mod_cond() | undefined, + incl_cond :: '_' | incl_cond() | undefined, %% Static target cond - debug_info :: debug_info() | undefined, - app_file :: app_file() | undefined, - app_type :: app_type() | undefined, - incl_app_filters :: [#regexp{}], - excl_app_filters :: [#regexp{}], - incl_archive_filters :: [#regexp{}], - excl_archive_filters :: [#regexp{}], - archive_opts :: [archive_opt()], + debug_info :: '_' | debug_info() | undefined, + app_file :: '_' | app_file() | undefined, + app_type :: '_' | app_type() | undefined, + incl_app_filters :: '_' | [#regexp{}], + excl_app_filters :: '_' | [#regexp{}], + incl_archive_filters :: '_' | [#regexp{}], + excl_archive_filters :: '_' | [#regexp{}], + archive_opts :: '_' | [archive_opt()], %% Dynamic - status :: status(), - uses_mods :: [mod_name()], - used_by_mods :: [mod_name()], - uses_apps :: [app_name()], - used_by_apps :: [app_name()], - is_pre_included :: boolean() | undefined, - is_included :: boolean() | undefined, - rels :: [rel_name()] + status :: '_' | status(), + uses_mods :: '_' | [mod_name()], + used_by_mods :: '_' | [mod_name()], + uses_apps :: '_' | [app_name()], + used_by_apps :: '_' | [app_name()], + is_pre_included :: '_' | '$2' | boolean() | undefined, + is_included :: '_' | '$1' | boolean() | undefined, + rels :: '_' | [rel_name()] }). -record(rel_app, @@ -224,7 +226,7 @@ escripts :: [file()], mod_cond :: mod_cond(), incl_cond :: incl_cond(), - apps :: [#app{}], + apps :: [#app{}] | undefined, %% Target cond boot_rel :: boot_rel(), diff --git a/lib/wx/api_gen/wx_gen_erl.erl b/lib/wx/api_gen/wx_gen_erl.erl index 6159b7c4bd..a8f23575f3 100644 --- a/lib/wx/api_gen/wx_gen_erl.erl +++ b/lib/wx/api_gen/wx_gen_erl.erl @@ -763,8 +763,8 @@ doc_arg_type3(#type{base=string}, in) -> "unicode:chardata()"; doc_arg_type3(#type{base=string}, out) -> "unicode:charlist()"; doc_arg_type3(#type{name="wxChar", single=S},in) when S =/= true -> "unicode:chardata()"; doc_arg_type3(#type{name="wxChar", single=S},out) when S =/= true -> "unicode:charlist()"; -doc_arg_type3(#type{name="wxArrayString"},in) -> "[unicode:chardata()]"; -doc_arg_type3(#type{name="wxArrayString"},out) -> "[unicode:charlist()]"; +doc_arg_type3(#type{name="wxArrayString"},in) -> "unicode:chardata()"; +doc_arg_type3(#type{name="wxArrayString"},out) -> "unicode:charlist()"; doc_arg_type3(#type{name="wxDateTime"}, _) -> "wx:wx_datetime()"; doc_arg_type3(#type{name="wxArtClient"}, _) -> "unicode:chardata()"; doc_arg_type3(#type{base=int}, _) -> "integer()"; diff --git a/lib/wx/src/gen/wxCheckListBox.erl b/lib/wx/src/gen/wxCheckListBox.erl index 083a9e32f4..382345abfa 100644 --- a/lib/wx/src/gen/wxCheckListBox.erl +++ b/lib/wx/src/gen/wxCheckListBox.erl @@ -104,7 +104,7 @@ new(Parent,Id) Parent::wxWindow:wxWindow(), Id::integer(), Option :: {pos, {X::integer(), Y::integer()}} | {size, {W::integer(), H::integer()}} - | {choices, [[unicode:chardata()]]} + | {choices, [unicode:chardata()]} | {style, integer()} | {validator, wx:wx_object()}. new(#wx_ref{type=ParentT,ref=ParentRef},Id, Options) diff --git a/lib/wx/src/gen/wxChoice.erl b/lib/wx/src/gen/wxChoice.erl index 2a2b2688fe..92b094036e 100644 --- a/lib/wx/src/gen/wxChoice.erl +++ b/lib/wx/src/gen/wxChoice.erl @@ -102,7 +102,7 @@ new(Parent,Id) Parent::wxWindow:wxWindow(), Id::integer(), Option :: {pos, {X::integer(), Y::integer()}} | {size, {W::integer(), H::integer()}} - | {choices, [[unicode:chardata()]]} + | {choices, [unicode:chardata()]} | {style, integer()} | {validator, wx:wx_object()}. new(#wx_ref{type=ParentT,ref=ParentRef},Id, Options) @@ -120,7 +120,7 @@ new(#wx_ref{type=ParentT,ref=ParentRef},Id, Options) %% @equiv create(This,Parent,Id,Pos,Size,Choices, []) -spec create(This, Parent, Id, Pos, Size, Choices) -> boolean() when - This::wxChoice(), Parent::wxWindow:wxWindow(), Id::integer(), Pos::{X::integer(), Y::integer()}, Size::{W::integer(), H::integer()}, Choices::[[unicode:chardata()]]. + This::wxChoice(), Parent::wxWindow:wxWindow(), Id::integer(), Pos::{X::integer(), Y::integer()}, Size::{W::integer(), H::integer()}, Choices::[unicode:chardata()]. create(This,Parent,Id,Pos={PosX,PosY},Size={SizeW,SizeH},Choices) when is_record(This, wx_ref),is_record(Parent, wx_ref),is_integer(Id),is_integer(PosX),is_integer(PosY),is_integer(SizeW),is_integer(SizeH),is_list(Choices) -> @@ -128,7 +128,7 @@ create(This,Parent,Id,Pos={PosX,PosY},Size={SizeW,SizeH},Choices) %% @doc See <a href="http://www.wxwidgets.org/manuals/stable/wx_wxchoice.html#wxchoicecreate">external documentation</a>. -spec create(This, Parent, Id, Pos, Size, Choices, [Option]) -> boolean() when - This::wxChoice(), Parent::wxWindow:wxWindow(), Id::integer(), Pos::{X::integer(), Y::integer()}, Size::{W::integer(), H::integer()}, Choices::[[unicode:chardata()]], + This::wxChoice(), Parent::wxWindow:wxWindow(), Id::integer(), Pos::{X::integer(), Y::integer()}, Size::{W::integer(), H::integer()}, Choices::[unicode:chardata()], Option :: {style, integer()} | {validator, wx:wx_object()}. create(#wx_ref{type=ThisT,ref=ThisRef},#wx_ref{type=ParentT,ref=ParentRef},Id,{PosX,PosY},{SizeW,SizeH},Choices, Options) diff --git a/lib/wx/src/gen/wxComboBox.erl b/lib/wx/src/gen/wxComboBox.erl index c67ff82bbc..4e6b247f67 100644 --- a/lib/wx/src/gen/wxComboBox.erl +++ b/lib/wx/src/gen/wxComboBox.erl @@ -105,7 +105,7 @@ new(Parent,Id) Option :: {value, unicode:chardata()} | {pos, {X::integer(), Y::integer()}} | {size, {W::integer(), H::integer()}} - | {choices, [[unicode:chardata()]]} + | {choices, [unicode:chardata()]} | {style, integer()} | {validator, wx:wx_object()}. new(#wx_ref{type=ParentT,ref=ParentRef},Id, Options) @@ -124,7 +124,7 @@ new(#wx_ref{type=ParentT,ref=ParentRef},Id, Options) %% @equiv create(This,Parent,Id,Value,Pos,Size,Choices, []) -spec create(This, Parent, Id, Value, Pos, Size, Choices) -> boolean() when - This::wxComboBox(), Parent::wxWindow:wxWindow(), Id::integer(), Value::unicode:chardata(), Pos::{X::integer(), Y::integer()}, Size::{W::integer(), H::integer()}, Choices::[[unicode:chardata()]]. + This::wxComboBox(), Parent::wxWindow:wxWindow(), Id::integer(), Value::unicode:chardata(), Pos::{X::integer(), Y::integer()}, Size::{W::integer(), H::integer()}, Choices::[unicode:chardata()]. create(This,Parent,Id,Value,Pos={PosX,PosY},Size={SizeW,SizeH},Choices) when is_record(This, wx_ref),is_record(Parent, wx_ref),is_integer(Id),is_list(Value),is_integer(PosX),is_integer(PosY),is_integer(SizeW),is_integer(SizeH),is_list(Choices) -> @@ -132,7 +132,7 @@ create(This,Parent,Id,Value,Pos={PosX,PosY},Size={SizeW,SizeH},Choices) %% @doc See <a href="http://www.wxwidgets.org/manuals/stable/wx_wxcombobox.html#wxcomboboxcreate">external documentation</a>. -spec create(This, Parent, Id, Value, Pos, Size, Choices, [Option]) -> boolean() when - This::wxComboBox(), Parent::wxWindow:wxWindow(), Id::integer(), Value::unicode:chardata(), Pos::{X::integer(), Y::integer()}, Size::{W::integer(), H::integer()}, Choices::[[unicode:chardata()]], + This::wxComboBox(), Parent::wxWindow:wxWindow(), Id::integer(), Value::unicode:chardata(), Pos::{X::integer(), Y::integer()}, Size::{W::integer(), H::integer()}, Choices::[unicode:chardata()], Option :: {style, integer()} | {validator, wx:wx_object()}. create(#wx_ref{type=ThisT,ref=ThisRef},#wx_ref{type=ParentT,ref=ParentRef},Id,Value,{PosX,PosY},{SizeW,SizeH},Choices, Options) diff --git a/lib/wx/src/gen/wxControlWithItems.erl b/lib/wx/src/gen/wxControlWithItems.erl index 1e2f89dbf0..92632a1c31 100644 --- a/lib/wx/src/gen/wxControlWithItems.erl +++ b/lib/wx/src/gen/wxControlWithItems.erl @@ -103,7 +103,7 @@ append(#wx_ref{type=ThisT,ref=ThisRef},Item,ClientData) %% @doc See <a href="http://www.wxwidgets.org/manuals/stable/wx_wxcontrolwithitems.html#wxcontrolwithitemsappend">external documentation</a>. -spec appendStrings(This, Strings) -> ok when - This::wxControlWithItems(), Strings::[[unicode:chardata()]]. + This::wxControlWithItems(), Strings::[unicode:chardata()]. appendStrings(#wx_ref{type=ThisT,ref=ThisRef},Strings) when is_list(Strings) -> ?CLASS(ThisT,wxControlWithItems), diff --git a/lib/wx/src/gen/wxFileDataObject.erl b/lib/wx/src/gen/wxFileDataObject.erl index bcb30a5ece..435c795cdc 100644 --- a/lib/wx/src/gen/wxFileDataObject.erl +++ b/lib/wx/src/gen/wxFileDataObject.erl @@ -55,7 +55,7 @@ addFile(#wx_ref{type=ThisT,ref=ThisRef},Filename) <<ThisRef:32/?UI,(byte_size(Filename_UC)):32/?UI,(Filename_UC)/binary, 0:(((8- ((0+byte_size(Filename_UC)) band 16#7)) band 16#7))/unit:8>>). %% @doc See <a href="http://www.wxwidgets.org/manuals/stable/wx_wxfiledataobject.html#wxfiledataobjectgetfilenames">external documentation</a>. --spec getFilenames(This) -> [[unicode:charlist()]] when +-spec getFilenames(This) -> [unicode:charlist()] when This::wxFileDataObject(). getFilenames(#wx_ref{type=ThisT,ref=ThisRef}) -> ?CLASS(ThisT,wxFileDataObject), diff --git a/lib/wx/src/gen/wxFileDialog.erl b/lib/wx/src/gen/wxFileDialog.erl index 3d65c92758..a257905795 100644 --- a/lib/wx/src/gen/wxFileDialog.erl +++ b/lib/wx/src/gen/wxFileDialog.erl @@ -138,7 +138,7 @@ getFilename(#wx_ref{type=ThisT,ref=ThisRef}) -> <<ThisRef:32/?UI>>). %% @doc See <a href="http://www.wxwidgets.org/manuals/stable/wx_wxfiledialog.html#wxfiledialoggetfilenames">external documentation</a>. --spec getFilenames(This) -> [[unicode:charlist()]] when +-spec getFilenames(This) -> [unicode:charlist()] when This::wxFileDialog(). getFilenames(#wx_ref{type=ThisT,ref=ThisRef}) -> ?CLASS(ThisT,wxFileDialog), @@ -170,7 +170,7 @@ getPath(#wx_ref{type=ThisT,ref=ThisRef}) -> <<ThisRef:32/?UI>>). %% @doc See <a href="http://www.wxwidgets.org/manuals/stable/wx_wxfiledialog.html#wxfiledialoggetpaths">external documentation</a>. --spec getPaths(This) -> [[unicode:charlist()]] when +-spec getPaths(This) -> [unicode:charlist()] when This::wxFileDialog(). getPaths(#wx_ref{type=ThisT,ref=ThisRef}) -> ?CLASS(ThisT,wxFileDialog), diff --git a/lib/wx/src/gen/wxGridCellChoiceEditor.erl b/lib/wx/src/gen/wxGridCellChoiceEditor.erl index 6f7d7ceffb..6b037e01b3 100644 --- a/lib/wx/src/gen/wxGridCellChoiceEditor.erl +++ b/lib/wx/src/gen/wxGridCellChoiceEditor.erl @@ -41,7 +41,7 @@ parent_class(_Class) -> erlang:error({badtype, ?MODULE}). -type wxGridCellChoiceEditor() :: wx:wx_object(). %% @equiv new(Choices, []) -spec new(Choices) -> wxGridCellChoiceEditor() when - Choices::[[unicode:chardata()]]. + Choices::[unicode:chardata()]. new(Choices) when is_list(Choices) -> @@ -49,7 +49,7 @@ new(Choices) %% @doc See <a href="http://www.wxwidgets.org/manuals/stable/wx_wxgridcellchoiceeditor.html#wxgridcellchoiceeditorwxgridcellchoiceeditor">external documentation</a>. -spec new(Choices, [Option]) -> wxGridCellChoiceEditor() when - Choices::[[unicode:chardata()]], + Choices::[unicode:chardata()], Option :: {allowOthers, boolean()}. new(Choices, Options) when is_list(Choices),is_list(Options) -> diff --git a/lib/wx/src/gen/wxListBox.erl b/lib/wx/src/gen/wxListBox.erl index 3b41de9ffc..fa4cbd01d5 100644 --- a/lib/wx/src/gen/wxListBox.erl +++ b/lib/wx/src/gen/wxListBox.erl @@ -102,7 +102,7 @@ new(Parent,Id) Parent::wxWindow:wxWindow(), Id::integer(), Option :: {pos, {X::integer(), Y::integer()}} | {size, {W::integer(), H::integer()}} - | {choices, [[unicode:chardata()]]} + | {choices, [unicode:chardata()]} | {style, integer()} | {validator, wx:wx_object()}. new(#wx_ref{type=ParentT,ref=ParentRef},Id, Options) @@ -120,7 +120,7 @@ new(#wx_ref{type=ParentT,ref=ParentRef},Id, Options) %% @equiv create(This,Parent,Id,Pos,Size,Choices, []) -spec create(This, Parent, Id, Pos, Size, Choices) -> boolean() when - This::wxListBox(), Parent::wxWindow:wxWindow(), Id::integer(), Pos::{X::integer(), Y::integer()}, Size::{W::integer(), H::integer()}, Choices::[[unicode:chardata()]]. + This::wxListBox(), Parent::wxWindow:wxWindow(), Id::integer(), Pos::{X::integer(), Y::integer()}, Size::{W::integer(), H::integer()}, Choices::[unicode:chardata()]. create(This,Parent,Id,Pos={PosX,PosY},Size={SizeW,SizeH},Choices) when is_record(This, wx_ref),is_record(Parent, wx_ref),is_integer(Id),is_integer(PosX),is_integer(PosY),is_integer(SizeW),is_integer(SizeH),is_list(Choices) -> @@ -128,7 +128,7 @@ create(This,Parent,Id,Pos={PosX,PosY},Size={SizeW,SizeH},Choices) %% @doc See <a href="http://www.wxwidgets.org/manuals/stable/wx_wxlistbox.html#wxlistboxcreate">external documentation</a>. -spec create(This, Parent, Id, Pos, Size, Choices, [Option]) -> boolean() when - This::wxListBox(), Parent::wxWindow:wxWindow(), Id::integer(), Pos::{X::integer(), Y::integer()}, Size::{W::integer(), H::integer()}, Choices::[[unicode:chardata()]], + This::wxListBox(), Parent::wxWindow:wxWindow(), Id::integer(), Pos::{X::integer(), Y::integer()}, Size::{W::integer(), H::integer()}, Choices::[unicode:chardata()], Option :: {style, integer()} | {validator, wx:wx_object()}. create(#wx_ref{type=ThisT,ref=ThisRef},#wx_ref{type=ParentT,ref=ParentRef},Id,{PosX,PosY},{SizeW,SizeH},Choices, Options) @@ -164,7 +164,7 @@ getSelections(#wx_ref{type=ThisT,ref=ThisRef}) -> %% @doc See <a href="http://www.wxwidgets.org/manuals/stable/wx_wxlistbox.html#wxlistboxinsertitems">external documentation</a>. -spec insertItems(This, Items, Pos) -> ok when - This::wxListBox(), Items::[[unicode:chardata()]], Pos::integer(). + This::wxListBox(), Items::[unicode:chardata()], Pos::integer(). insertItems(#wx_ref{type=ThisT,ref=ThisRef},Items,Pos) when is_list(Items),is_integer(Pos) -> ?CLASS(ThisT,wxListBox), @@ -184,7 +184,7 @@ isSelected(#wx_ref{type=ThisT,ref=ThisRef},N) %% @doc See <a href="http://www.wxwidgets.org/manuals/stable/wx_wxlistbox.html#wxlistboxset">external documentation</a>. -spec set(This, Items) -> ok when - This::wxListBox(), Items::[[unicode:chardata()]]. + This::wxListBox(), Items::[unicode:chardata()]. set(#wx_ref{type=ThisT,ref=ThisRef},Items) when is_list(Items) -> ?CLASS(ThisT,wxListBox), diff --git a/lib/wx/src/gen/wxMultiChoiceDialog.erl b/lib/wx/src/gen/wxMultiChoiceDialog.erl index 04cbb832ea..6a6a6b833a 100644 --- a/lib/wx/src/gen/wxMultiChoiceDialog.erl +++ b/lib/wx/src/gen/wxMultiChoiceDialog.erl @@ -94,7 +94,7 @@ new() -> %% @equiv new(Parent,Message,Caption,Choices, []) -spec new(Parent, Message, Caption, Choices) -> wxMultiChoiceDialog() when - Parent::wxWindow:wxWindow(), Message::unicode:chardata(), Caption::unicode:chardata(), Choices::[[unicode:chardata()]]. + Parent::wxWindow:wxWindow(), Message::unicode:chardata(), Caption::unicode:chardata(), Choices::[unicode:chardata()]. new(Parent,Message,Caption,Choices) when is_record(Parent, wx_ref),is_list(Message),is_list(Caption),is_list(Choices) -> @@ -102,7 +102,7 @@ new(Parent,Message,Caption,Choices) %% @doc See <a href="http://www.wxwidgets.org/manuals/stable/wx_wxmultichoicedialog.html#wxmultichoicedialogwxmultichoicedialog">external documentation</a>. -spec new(Parent, Message, Caption, Choices, [Option]) -> wxMultiChoiceDialog() when - Parent::wxWindow:wxWindow(), Message::unicode:chardata(), Caption::unicode:chardata(), Choices::[[unicode:chardata()]], + Parent::wxWindow:wxWindow(), Message::unicode:chardata(), Caption::unicode:chardata(), Choices::[unicode:chardata()], Option :: {style, integer()} | {pos, {X::integer(), Y::integer()}}. new(#wx_ref{type=ParentT,ref=ParentRef},Message,Caption,Choices, Options) diff --git a/lib/wx/src/gen/wxRadioBox.erl b/lib/wx/src/gen/wxRadioBox.erl index 7843fde488..b7f52d7d9c 100644 --- a/lib/wx/src/gen/wxRadioBox.erl +++ b/lib/wx/src/gen/wxRadioBox.erl @@ -82,7 +82,7 @@ parent_class(_Class) -> erlang:error({badtype, ?MODULE}). -type wxRadioBox() :: wx:wx_object(). %% @equiv new(Parent,Id,Title,Pos,Size,Choices, []) -spec new(Parent, Id, Title, Pos, Size, Choices) -> wxRadioBox() when - Parent::wxWindow:wxWindow(), Id::integer(), Title::unicode:chardata(), Pos::{X::integer(), Y::integer()}, Size::{W::integer(), H::integer()}, Choices::[[unicode:chardata()]]. + Parent::wxWindow:wxWindow(), Id::integer(), Title::unicode:chardata(), Pos::{X::integer(), Y::integer()}, Size::{W::integer(), H::integer()}, Choices::[unicode:chardata()]. new(Parent,Id,Title,Pos={PosX,PosY},Size={SizeW,SizeH},Choices) when is_record(Parent, wx_ref),is_integer(Id),is_list(Title),is_integer(PosX),is_integer(PosY),is_integer(SizeW),is_integer(SizeH),is_list(Choices) -> @@ -90,7 +90,7 @@ new(Parent,Id,Title,Pos={PosX,PosY},Size={SizeW,SizeH},Choices) %% @doc See <a href="http://www.wxwidgets.org/manuals/stable/wx_wxradiobox.html#wxradioboxwxradiobox">external documentation</a>. -spec new(Parent, Id, Title, Pos, Size, Choices, [Option]) -> wxRadioBox() when - Parent::wxWindow:wxWindow(), Id::integer(), Title::unicode:chardata(), Pos::{X::integer(), Y::integer()}, Size::{W::integer(), H::integer()}, Choices::[[unicode:chardata()]], + Parent::wxWindow:wxWindow(), Id::integer(), Title::unicode:chardata(), Pos::{X::integer(), Y::integer()}, Size::{W::integer(), H::integer()}, Choices::[unicode:chardata()], Option :: {majorDim, integer()} | {style, integer()} | {val, wx:wx_object()}. @@ -110,7 +110,7 @@ new(#wx_ref{type=ParentT,ref=ParentRef},Id,Title,{PosX,PosY},{SizeW,SizeH},Choic %% @equiv create(This,Parent,Id,Title,Pos,Size,Choices, []) -spec create(This, Parent, Id, Title, Pos, Size, Choices) -> boolean() when - This::wxRadioBox(), Parent::wxWindow:wxWindow(), Id::integer(), Title::unicode:chardata(), Pos::{X::integer(), Y::integer()}, Size::{W::integer(), H::integer()}, Choices::[[unicode:chardata()]]. + This::wxRadioBox(), Parent::wxWindow:wxWindow(), Id::integer(), Title::unicode:chardata(), Pos::{X::integer(), Y::integer()}, Size::{W::integer(), H::integer()}, Choices::[unicode:chardata()]. create(This,Parent,Id,Title,Pos={PosX,PosY},Size={SizeW,SizeH},Choices) when is_record(This, wx_ref),is_record(Parent, wx_ref),is_integer(Id),is_list(Title),is_integer(PosX),is_integer(PosY),is_integer(SizeW),is_integer(SizeH),is_list(Choices) -> @@ -118,7 +118,7 @@ create(This,Parent,Id,Title,Pos={PosX,PosY},Size={SizeW,SizeH},Choices) %% @doc See <a href="http://www.wxwidgets.org/manuals/stable/wx_wxradiobox.html#wxradioboxcreate">external documentation</a>. -spec create(This, Parent, Id, Title, Pos, Size, Choices, [Option]) -> boolean() when - This::wxRadioBox(), Parent::wxWindow:wxWindow(), Id::integer(), Title::unicode:chardata(), Pos::{X::integer(), Y::integer()}, Size::{W::integer(), H::integer()}, Choices::[[unicode:chardata()]], + This::wxRadioBox(), Parent::wxWindow:wxWindow(), Id::integer(), Title::unicode:chardata(), Pos::{X::integer(), Y::integer()}, Size::{W::integer(), H::integer()}, Choices::[unicode:chardata()], Option :: {majorDim, integer()} | {style, integer()} | {val, wx:wx_object()}. diff --git a/lib/wx/src/gen/wxSingleChoiceDialog.erl b/lib/wx/src/gen/wxSingleChoiceDialog.erl index 6f96e283c8..db6b41ae75 100644 --- a/lib/wx/src/gen/wxSingleChoiceDialog.erl +++ b/lib/wx/src/gen/wxSingleChoiceDialog.erl @@ -94,7 +94,7 @@ new() -> %% @equiv new(Parent,Message,Caption,Choices, []) -spec new(Parent, Message, Caption, Choices) -> wxSingleChoiceDialog() when - Parent::wxWindow:wxWindow(), Message::unicode:chardata(), Caption::unicode:chardata(), Choices::[[unicode:chardata()]]. + Parent::wxWindow:wxWindow(), Message::unicode:chardata(), Caption::unicode:chardata(), Choices::[unicode:chardata()]. new(Parent,Message,Caption,Choices) when is_record(Parent, wx_ref),is_list(Message),is_list(Caption),is_list(Choices) -> @@ -102,7 +102,7 @@ new(Parent,Message,Caption,Choices) %% @doc See <a href="http://www.wxwidgets.org/manuals/stable/wx_wxsinglechoicedialog.html#wxsinglechoicedialogwxsinglechoicedialog">external documentation</a>. -spec new(Parent, Message, Caption, Choices, [Option]) -> wxSingleChoiceDialog() when - Parent::wxWindow:wxWindow(), Message::unicode:chardata(), Caption::unicode:chardata(), Choices::[[unicode:chardata()]], + Parent::wxWindow:wxWindow(), Message::unicode:chardata(), Caption::unicode:chardata(), Choices::[unicode:chardata()], Option :: {style, integer()} | {pos, {X::integer(), Y::integer()}}. new(#wx_ref{type=ParentT,ref=ParentRef},Message,Caption,Choices, Options) diff --git a/system/COPYRIGHT b/system/COPYRIGHT index 94e9795b16..c6b7d8b64f 100644 --- a/system/COPYRIGHT +++ b/system/COPYRIGHT @@ -245,3 +245,26 @@ terms specified in this license. %% POSSIBILITY OF SUCH DAMAGE. --------------------------------------------------------------------------- +[eldap] + +Copyright (c) 2010, Torbjorn Tornkvist + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +--------------------------------------------------------------------------- |