aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexey Shchepin <alexey@process-one.net>2003-11-23 20:11:21 +0000
committerAlexey Shchepin <alexey@process-one.net>2003-11-23 20:11:21 +0000
commit0822a55f05bb327f0d362e0a3de205f5f1ce604a (patch)
tree288319f357281e47946b284b65c1ad70a70e5810
parent* examples/mtr/ejabberd: Updated (thanks to Marshall T. Rose) (diff)
* src/cyrsasl_digest.erl: Bugfix (thanks to Sergei Golovan)
* src/ejabberd.cfg.example: Updated * src/ejabberd_auth.erl: Support for LDAP authentification * src/cyrsasl_digest.erl: Likewise * src/mod_register.erl: Likewise * src/ejabberd_c2s.erl: Likewise * src/eldap/: Imported "eldap" package * src/ejabberd_sm.erl: Bugfix * src/mod_muc/mod_muc_room.erl: Bugfixes SVN Revision: 176
-rw-r--r--ChangeLog21
-rw-r--r--doc/guide.html10
-rw-r--r--doc/guide.tex11
-rw-r--r--src/Makefile.in2
-rwxr-xr-xsrc/configure28
-rw-r--r--src/configure.ac2
-rw-r--r--src/cyrsasl_digest.erl11
-rw-r--r--src/ejabberd.cfg.example11
-rw-r--r--src/ejabberd.hrl2
-rw-r--r--src/ejabberd_auth.erl122
-rw-r--r--src/ejabberd_c2s.erl23
-rw-r--r--src/ejabberd_sm.erl24
-rw-r--r--src/eldap/ELDAPv3.asn291
-rw-r--r--src/eldap/Makefile.in36
-rw-r--r--src/eldap/eldap.erl995
-rw-r--r--src/eldap/eldap.hrl13
-rw-r--r--src/mod_muc/mod_muc_room.erl33
-rw-r--r--src/mod_register.erl2
18 files changed, 1600 insertions, 37 deletions
diff --git a/ChangeLog b/ChangeLog
index b2fcca687..d6d3ed609 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,24 @@
+2003-11-23 Alexey Shchepin <alexey@sevcom.net>
+
+ * src/cyrsasl_digest.erl: Bugfix (thanks to Sergei Golovan)
+
+ * src/ejabberd.cfg.example: Updated
+
+ * src/ejabberd_auth.erl: Support for LDAP authentification
+ * src/cyrsasl_digest.erl: Likewise
+ * src/mod_register.erl: Likewise
+ * src/ejabberd_c2s.erl: Likewise
+
+ * src/eldap/: Imported "eldap" package
+
+ * src/ejabberd_sm.erl: Bugfix
+
+2003-11-16 Alexey Shchepin <alexey@sevcom.net>
+
+ * src/mod_muc/mod_muc_room.erl: Bugfixes
+
+ * (all): Version 0.5 released
+
2003-11-13 Alexey Shchepin <alexey@sevcom.net>
* examples/mtr/ejabberd: Updated (thanks to Marshall T. Rose)
diff --git a/doc/guide.html b/doc/guide.html
index 8430cbd5f..861864c44 100644
--- a/doc/guide.html
+++ b/doc/guide.html
@@ -129,6 +129,10 @@ Works on most of popular platforms: *nix (tested on Linux, FreeBSD and
<A HREF="http://www.jabber.org/jeps/jep-0060.html">Publish-Subscribe</A>
service
<LI>Built-in Jabber Users Directory service based on users vCards
+<LI>SSL support
+<LI>Ability to interface with external components (JIT, MSN-t, Yahoo-t, etc)
+<LI>Migration from jabberd14 is possible
+<LI>Mostly XMPP-compliant
<LI>Support for
<A HREF="http://www.jabber.org/jeps/jep-0030.html">JEP-0030</A>
(Service Discovery).
@@ -137,6 +141,12 @@ Works on most of popular platforms: *nix (tested on Linux, FreeBSD and
(Statistics Gathering).
<LI>Support for <TT>xml:lang</TT> attribute in many XML elements
</UL>
+The misfeatures of <TT>ejabberd</TT> is:
+<UL><LI>
+No support for external authentification
+<LI>No support for virtual domains
+<LI>No support for STARTTLS
+</UL>
<!--TOC section Installation-->
<H2><A NAME="htoc2">2</A>&nbsp;&nbsp;Installation</H2><!--SEC END -->
diff --git a/doc/guide.tex b/doc/guide.tex
index ed4072b59..d20912c81 100644
--- a/doc/guide.tex
+++ b/doc/guide.tex
@@ -78,6 +78,10 @@ The main features of \ejabberd{} is:
\footahref{http://www.jabber.org/jeps/jep-0060.html}{Publish-Subscribe}
service
\item Built-in Jabber Users Directory service based on users vCards
+\item SSL support
+\item Ability to interface with external components (JIT, MSN-t, Yahoo-t, etc)
+\item Migration from jabberd14 is possible
+\item Mostly XMPP-compliant
\item Support for
\footahref{http://www.jabber.org/jeps/jep-0030.html}{JEP-0030}
(Service Discovery).
@@ -87,7 +91,12 @@ The main features of \ejabberd{} is:
\item Support for \ns{xml:lang} attribute in many XML elements
\end{itemize}
-
+The misfeatures of \ejabberd{} is:
+\begin{itemize}
+\item No support for external authentification
+\item No support for virtual domains
+\item No support for STARTTLS
+\end{itemize}
\section{Installation}
diff --git a/src/Makefile.in b/src/Makefile.in
index 6378dda1f..a73cc8471 100644
--- a/src/Makefile.in
+++ b/src/Makefile.in
@@ -12,7 +12,7 @@ INCLUDES = @ERLANG_CFLAGS@ @EXPAT_CFLAGS@
LIBDIRS = @ERLANG_LIBS@ @EXPAT_LIBS@
-SUBDIRS = @mod_irc@ @mod_pubsub@ @mod_muc@ stringprep
+SUBDIRS = @mod_irc@ @mod_pubsub@ @mod_muc@ @eldap@ stringprep
ERLSHLIBS = expat_erl.so
diff --git a/src/configure b/src/configure
index d4328b9a8..c80056810 100755
--- a/src/configure
+++ b/src/configure
@@ -828,6 +828,7 @@ Optional Features:
--enable-mod_pubsub enable mod_pubsub (default: yes)
--enable-mod_irc enable mod_irc (default: yes)
--enable-mod_muc enable mod_muc (default: yes)
+ --enable-eldap enable eldap (default: yes)
Optional Packages:
--with-PACKAGE[=ARG] use PACKAGE [ARG=yes]
@@ -3675,8 +3676,30 @@ echo "${ECHO_T}$mr_enable_mod_muc" >&6
+eldap=
+make_eldap=
+echo "$as_me:$LINENO: checking whether build eldap" >&5
+echo $ECHO_N "checking whether build eldap... $ECHO_C" >&6
+# Check whether --enable-eldap or --disable-eldap was given.
+if test "${enable_eldap+set}" = set; then
+ enableval="$enable_eldap"
+ mr_enable_eldap="$enableval"
+else
+ mr_enable_eldap=yes
+fi;
+if test "$mr_enable_eldap" = yes; then
+eldap=eldap
+make_eldap=eldap/Makefile
+fi
+echo "$as_me:$LINENO: result: $mr_enable_eldap" >&5
+echo "${ECHO_T}$mr_enable_eldap" >&6
+
+
+
+
+
-ac_config_files="$ac_config_files Makefile $make_mod_irc $make_mod_muc $make_mod_pubsub stringprep/Makefile"
+ac_config_files="$ac_config_files Makefile $make_mod_irc $make_mod_muc $make_mod_pubsub $make_eldap stringprep/Makefile"
cat >confcache <<\_ACEOF
# This file is a shell script that caches the results of configure
@@ -4178,6 +4201,7 @@ do
"$make_mod_irc" ) CONFIG_FILES="$CONFIG_FILES $make_mod_irc" ;;
"$make_mod_muc" ) CONFIG_FILES="$CONFIG_FILES $make_mod_muc" ;;
"$make_mod_pubsub" ) CONFIG_FILES="$CONFIG_FILES $make_mod_pubsub" ;;
+ "$make_eldap" ) CONFIG_FILES="$CONFIG_FILES $make_eldap" ;;
"stringprep/Makefile" ) CONFIG_FILES="$CONFIG_FILES stringprep/Makefile" ;;
*) { { echo "$as_me:$LINENO: error: invalid argument: $ac_config_target" >&5
echo "$as_me: error: invalid argument: $ac_config_target" >&2;}
@@ -4283,6 +4307,8 @@ s,@mod_irc@,$mod_irc,;t t
s,@make_mod_irc@,$make_mod_irc,;t t
s,@mod_muc@,$mod_muc,;t t
s,@make_mod_muc@,$make_mod_muc,;t t
+s,@eldap@,$eldap,;t t
+s,@make_eldap@,$make_eldap,;t t
CEOF
_ACEOF
diff --git a/src/configure.ac b/src/configure.ac
index 643fe2626..e594927c7 100644
--- a/src/configure.ac
+++ b/src/configure.ac
@@ -29,11 +29,13 @@ AC_HEADER_STDC
AC_MOD_ENABLE(mod_pubsub, yes)
AC_MOD_ENABLE(mod_irc, yes)
AC_MOD_ENABLE(mod_muc, yes)
+AC_MOD_ENABLE(eldap, yes)
AC_CONFIG_FILES([Makefile
$make_mod_irc
$make_mod_muc
$make_mod_pubsub
+ $make_eldap
stringprep/Makefile])
AC_OUTPUT
diff --git a/src/cyrsasl_digest.erl b/src/cyrsasl_digest.erl
index 5d768b926..ae94c181f 100644
--- a/src/cyrsasl_digest.erl
+++ b/src/cyrsasl_digest.erl
@@ -21,8 +21,13 @@
-record(state, {step, nonce, username, authzid}).
start(Opts) ->
- cyrsasl:register_mechanism("DIGEST-MD5", ?MODULE),
- ok.
+ case ejabberd_auth:plain_password_required() of
+ true ->
+ ok;
+ false ->
+ cyrsasl:register_mechanism("DIGEST-MD5", ?MODULE),
+ ok
+ end.
stop() ->
ok.
@@ -31,7 +36,7 @@ mech_new() ->
{ok, #state{step = 1,
nonce = randoms:get_string()}}.
-mech_step(#state{step = 1, nonce = Nonce} = State, "") ->
+mech_step(#state{step = 1, nonce = Nonce} = State, _) ->
{continue,
"nonce=\"" ++ Nonce ++
"\",qop=\"auth\",charset=utf-8,algorithm=md5-sess",
diff --git a/src/ejabberd.cfg.example b/src/ejabberd.cfg.example
index 36b47fee5..baf46eae1 100644
--- a/src/ejabberd.cfg.example
+++ b/src/ejabberd.cfg.example
@@ -61,6 +61,17 @@
% Admins of this server are also admins of MUC service:
{access, muc_admin, [{allow, admin}]}.
+
+% Authentification method. If you want to use internal user base, then use
+% this line:
+{auth_method, internal}.
+
+% For LDAP uthentification use these lines instead of above one:
+%{auth_method, ldap}.
+%{ldap_servers, ["localhost"]}. % List of LDAP servers
+%{ldap_base, "dc=example,dc=com"}. % Base of LDAP directory
+
+
% Host name:
{host, "localhost"}.
diff --git a/src/ejabberd.hrl b/src/ejabberd.hrl
index 29d2fcb24..042e5f125 100644
--- a/src/ejabberd.hrl
+++ b/src/ejabberd.hrl
@@ -6,7 +6,7 @@
%%% Id : $Id$
%%%----------------------------------------------------------------------
--define(VERSION, "0.1-alpha").
+-define(VERSION, "0.6-alpha").
%-define(ejabberd_debug, true).
%-define(DBGFSM, true).
diff --git a/src/ejabberd_auth.erl b/src/ejabberd_auth.erl
index 1503f7193..be7da007b 100644
--- a/src/ejabberd_auth.erl
+++ b/src/ejabberd_auth.erl
@@ -23,7 +23,11 @@
get_password_s/1,
is_user_exists/1,
remove_user/1,
- remove_user/2]).
+ remove_user/2,
+ plain_password_required/0,
+ check_password_ldap/2, % TODO: remove
+ is_user_exists_ldap/1 % TODO: remove
+ ]).
%% gen_server callbacks
-export([init/1,
@@ -33,6 +37,8 @@
handle_info/2,
terminate/2]).
+-include("eldap/eldap.hrl").
+
-record(state, {}).
-record(passwd, {user, password}).
@@ -59,6 +65,13 @@ start_link() ->
init([]) ->
mnesia:create_table(passwd,[{disc_copies, [node()]},
{attributes, record_info(fields, passwd)}]),
+ case auth_method() of
+ internal ->
+ ok;
+ ldap ->
+ LDAPServers = ejabberd_config:get_local_option(ldap_servers),
+ eldap:start_link("ejabberd", LDAPServers, 389, "", "")
+ end,
{ok, #state{}}.
%%----------------------------------------------------------------------
@@ -108,7 +121,32 @@ terminate(_Reason, _State) ->
%%% Internal functions
%%%----------------------------------------------------------------------
+auth_method() ->
+ case ejabberd_config:get_local_option(auth_method) of
+ ldap ->
+ ldap;
+ _ ->
+ internal
+ end.
+
+plain_password_required() ->
+ case auth_method() of
+ internal ->
+ false;
+ ldap ->
+ true
+ end.
+
+
check_password(User, Password) ->
+ case auth_method() of
+ internal ->
+ check_password_internal(User, Password);
+ ldap ->
+ check_password_ldap(User, Password)
+ end.
+
+check_password_internal(User, Password) ->
LUser = jlib:nodeprep(User),
case catch mnesia:dirty_read({passwd, LUser}) of
[#passwd{password = Password}] ->
@@ -118,6 +156,14 @@ check_password(User, Password) ->
end.
check_password(User, Password, StreamID, Digest) ->
+ case auth_method() of
+ internal ->
+ check_password_internal(User, Password, StreamID, Digest);
+ ldap ->
+ check_password_ldap(User, Password, StreamID, Digest)
+ end.
+
+check_password_internal(User, Password, StreamID, Digest) ->
LUser = jlib:nodeprep(User),
case catch mnesia:dirty_read({passwd, LUser}) of
[#passwd{password = Passwd}] ->
@@ -148,7 +194,16 @@ set_password(User, Password) ->
mnesia:transaction(F)
end.
+
try_register(User, Password) ->
+ case auth_method() of
+ internal ->
+ try_register_internal(User, Password);
+ ldap ->
+ {error, not_allowed}
+ end.
+
+try_register_internal(User, Password) ->
case jlib:nodeprep(User) of
error -> {error, invalid_jid};
LUser ->
@@ -187,6 +242,14 @@ get_password_s(User) ->
end.
is_user_exists(User) ->
+ case auth_method() of
+ internal ->
+ is_user_exists_internal(User);
+ ldap ->
+ is_user_exists_ldap(User)
+ end.
+
+is_user_exists_internal(User) ->
LUser = jlib:nodeprep(User),
case catch mnesia:dirty_read({passwd, LUser}) of
[] ->
@@ -198,6 +261,14 @@ is_user_exists(User) ->
end.
remove_user(User) ->
+ case auth_method() of
+ internal ->
+ remove_user_internal(User);
+ ldap ->
+ {error, not_allowed}
+ end.
+
+remove_user_internal(User) ->
LUser = jlib:nodeprep(User),
F = fun() ->
mnesia:delete({passwd, LUser})
@@ -210,6 +281,14 @@ remove_user(User) ->
catch mod_private:remove_user(User).
remove_user(User, Password) ->
+ case auth_method() of
+ internal ->
+ remove_user_internal(User, Password);
+ ldap ->
+ not_allowed
+ end.
+
+remove_user_internal(User, Password) ->
LUser = jlib:nodeprep(User),
F = fun() ->
case mnesia:read({passwd, LUser}) of
@@ -236,3 +315,44 @@ remove_user(User, Password) ->
bad_request
end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+check_password_ldap(User, Password, StreamID, Digest) ->
+ check_password_ldap(User, Password).
+
+check_password_ldap(User, Password) ->
+ case find_user_dn(User) of
+ false ->
+ false;
+ DN ->
+ case eldap:bind("ejabberd", DN, Password) of
+ ok ->
+ true;
+ _ ->
+ false
+ end
+ end.
+
+is_user_exists_ldap(User) ->
+ case find_user_dn(User) of
+ false ->
+ false;
+ _DN ->
+ true
+ end.
+
+find_user_dn(User) ->
+ Filter = eldap:equalityMatch("uid", User),
+ Base = ejabberd_config:get_local_option(ldap_base),
+ case eldap:search("ejabberd", [{base, Base},
+ {filter, Filter},
+ {attributes, []}]) of
+ #eldap_search_result{entries = [E | _]} ->
+ E#eldap_entry.object_name;
+ _ ->
+ false
+ end.
+
+
+
diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl
index 222436da6..fa7d8f726 100644
--- a/src/ejabberd_c2s.erl
+++ b/src/ejabberd_c2s.erl
@@ -201,13 +201,22 @@ wait_for_auth({xmlstreamelement, El}, StateData) ->
{next_state, wait_for_auth, StateData};
{auth, ID, get, {U, _, _, _}} ->
{xmlelement, Name, Attrs, Els} = jlib:make_result_iq_reply(El),
- Res = {xmlelement, Name, Attrs,
- [{xmlelement, "query", [{"xmlns", ?NS_AUTH}],
- [{xmlelement, "username", [], [{xmlcdata, U}]},
- {xmlelement, "password", [], []},
- {xmlelement, "digest", [], []},
- {xmlelement, "resource", [], []}
- ]}]},
+ Res = case ejabberd_auth:plain_password_required() of
+ false ->
+ {xmlelement, Name, Attrs,
+ [{xmlelement, "query", [{"xmlns", ?NS_AUTH}],
+ [{xmlelement, "username", [], [{xmlcdata, U}]},
+ {xmlelement, "password", [], []},
+ {xmlelement, "digest", [], []},
+ {xmlelement, "resource", [], []}
+ ]}]};
+ true ->
+ {xmlelement, Name, Attrs,
+ [{xmlelement, "query", [{"xmlns", ?NS_AUTH}],
+ [{xmlelement, "username", [], [{xmlcdata, U}]},
+ {xmlelement, "password", [], []}
+ ]}]}
+ end,
send_element(StateData, Res),
{next_state, wait_for_auth, StateData};
{auth, ID, set, {U, P, D, ""}} ->
diff --git a/src/ejabberd_sm.erl b/src/ejabberd_sm.erl
index e58562e85..2b3cec75f 100644
--- a/src/ejabberd_sm.erl
+++ b/src/ejabberd_sm.erl
@@ -298,12 +298,12 @@ route_message(From, To, Packet) ->
#jid{luser = LUser} = To,
case catch lists:max(get_user_present_resources(LUser)) of
{'EXIT', _} ->
- case ejabberd_auth:is_user_exists(LUser) of
- true ->
- case xml:get_tag_attr_s("type", Packet) of
- "error" ->
- ok;
- _ ->
+ case xml:get_tag_attr_s("type", Packet) of
+ "error" ->
+ ok;
+ _ ->
+ case ejabberd_auth:is_user_exists(LUser) of
+ true ->
case catch mod_offline:store_packet(
From, To, Packet) of
{'EXIT', _} ->
@@ -312,12 +312,12 @@ route_message(From, To, Packet) ->
ejabberd_router:route(To, From, Err);
_ ->
ok
- end
- end;
- _ ->
- Err = jlib:make_error_reply(
- Packet, ?ERR_ITEM_NOT_FOUND),
- ejabberd_router:route(To, From, Err)
+ end;
+ _ ->
+ Err = jlib:make_error_reply(
+ Packet, ?ERR_ITEM_NOT_FOUND),
+ ejabberd_router:route(To, From, Err)
+ end
end;
{_, R} ->
ejabberd_sm ! {route,
diff --git a/src/eldap/ELDAPv3.asn b/src/eldap/ELDAPv3.asn
new file mode 100644
index 000000000..0cfac48c3
--- /dev/null
+++ b/src/eldap/ELDAPv3.asn
@@ -0,0 +1,291 @@
+-- LDAPv3 ASN.1 specification, taken from RFC 2251
+
+-- Lightweight-Directory-Access-Protocol-V3 DEFINITIONS
+ELDAPv3 DEFINITIONS
+IMPLICIT TAGS ::=
+
+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 },
+ controls [0] Controls OPTIONAL }
+
+MessageID ::= INTEGER (0 .. maxInt)
+
+maxInt INTEGER ::= 2147483647 -- (2^^31 - 1) --
+
+LDAPString ::= OCTET STRING
+
+LDAPOID ::= OCTET STRING
+
+LDAPDN ::= LDAPString
+
+RelativeLDAPDN ::= LDAPString
+
+AttributeType ::= LDAPString
+
+AttributeDescription ::= LDAPString
+
+
+
+
+-- Wahl, et. al. Standards Track [Page 44]
+--
+-- RFC 2251 LDAPv3 December 1997
+
+
+AttributeDescriptionList ::= SEQUENCE OF
+ AttributeDescription
+
+AttributeValue ::= OCTET STRING
+
+AttributeValueAssertion ::= SEQUENCE {
+ attributeDesc AttributeDescription,
+ assertionValue AssertionValue }
+
+AssertionValue ::= OCTET STRING
+
+Attribute ::= SEQUENCE {
+ type AttributeDescription,
+ vals SET OF AttributeValue }
+
+MatchingRuleId ::= LDAPString
+
+LDAPResult ::= SEQUENCE {
+ resultCode ENUMERATED {
+ success (0),
+ operationsError (1),
+ protocolError (2),
+ timeLimitExceeded (3),
+ sizeLimitExceeded (4),
+ compareFalse (5),
+ compareTrue (6),
+ authMethodNotSupported (7),
+ strongAuthRequired (8),
+ -- 9 reserved --
+ referral (10), -- new
+ adminLimitExceeded (11), -- new
+ unavailableCriticalExtension (12), -- new
+ confidentialityRequired (13), -- new
+ saslBindInProgress (14), -- new
+ 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),
+
+-- Wahl, et. al. Standards Track [Page 45]
+--
+-- RFC 2251 LDAPv3 December 1997
+
+
+ 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), -- new
+ -- 72-79 unused --
+ other (80) },
+ -- 81-90 reserved for APIs --
+ matchedDN LDAPDN,
+ errorMessage LDAPString,
+ referral [3] Referral OPTIONAL }
+
+Referral ::= SEQUENCE OF LDAPURL
+
+LDAPURL ::= LDAPString -- limited to characters permitted in URLs
+
+Controls ::= SEQUENCE OF 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 {
+
+-- Wahl, et. al. Standards Track [Page 46]
+--
+-- RFC 2251 LDAPv3 December 1997
+
+
+ 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 AttributeDescriptionList }
+
+Filter ::= CHOICE {
+ and [0] SET OF Filter,
+ or [1] SET OF 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,
+ -- at least one must be present
+ substrings SEQUENCE OF CHOICE {
+ initial [0] LDAPString,
+ any [1] LDAPString,
+ final [2] LDAPString } }
+
+MatchingRuleAssertion ::= SEQUENCE {
+ matchingRule [1] MatchingRuleId OPTIONAL,
+ type [2] AttributeDescription OPTIONAL,
+ matchValue [3] AssertionValue,
+ dnAttributes [4] BOOLEAN DEFAULT FALSE }
+
+-- Wahl, et. al. Standards Track [Page 47]
+--
+-- RFC 2251 LDAPv3 December 1997
+
+SearchResultEntry ::= [APPLICATION 4] SEQUENCE {
+ objectName LDAPDN,
+ attributes PartialAttributeList }
+
+PartialAttributeList ::= SEQUENCE OF SEQUENCE {
+ type AttributeDescription,
+ vals SET OF AttributeValue }
+
+SearchResultReference ::= [APPLICATION 19] SEQUENCE OF LDAPURL
+
+SearchResultDone ::= [APPLICATION 5] LDAPResult
+
+ModifyRequest ::= [APPLICATION 6] SEQUENCE {
+ object LDAPDN,
+ modification SEQUENCE OF SEQUENCE {
+ operation ENUMERATED {
+ add (0),
+ delete (1),
+ replace (2) },
+ modification AttributeTypeAndValues } }
+
+AttributeTypeAndValues ::= SEQUENCE {
+ type AttributeDescription,
+ vals SET OF AttributeValue }
+
+ModifyResponse ::= [APPLICATION 7] LDAPResult
+
+AddRequest ::= [APPLICATION 8] SEQUENCE {
+ entry LDAPDN,
+ attributes AttributeList }
+
+AttributeList ::= SEQUENCE OF SEQUENCE {
+ type AttributeDescription,
+ vals SET OF AttributeValue }
+
+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
+
+-- Wahl, et. al. Standards Track [Page 48]
+--
+-- RFC 2251 LDAPv3 December 1997
+
+
+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,
+ response [11] OCTET STRING OPTIONAL }
+
+END
+
+
diff --git a/src/eldap/Makefile.in b/src/eldap/Makefile.in
new file mode 100644
index 000000000..b0abb25eb
--- /dev/null
+++ b/src/eldap/Makefile.in
@@ -0,0 +1,36 @@
+# $Id$
+
+CC = @CC@
+CFLAGS = @CFLAGS@
+CPPFLAGS = @CPPFLAGS@
+LDFLAGS = @LDFLAGS@
+LIBS = @LIBS@
+
+INCLUDES = @ERLANG_CFLAGS@
+
+LIBDIRS = @ERLANG_LIBS@
+
+SUBDIRS =
+
+
+OUTDIR = ..
+EFLAGS = -I .. -pz ..
+OBJS = \
+ $(OUTDIR)/eldap.beam \
+ $(OUTDIR)/ELDAPv3.beam
+
+all: $(OBJS)
+
+ELDAPv3.erl: ELDAPv3.asn
+ erlc -bber_bin -W $(EFLAGS) $<
+
+$(OUTDIR)/%.beam: %.erl ELDAPv3.erl
+ erlc -W $(EFLAGS) -o $(OUTDIR) $<
+
+
+clean:
+ rm -f $(OBJS)
+
+TAGS:
+ etags *.erl
+
diff --git a/src/eldap/eldap.erl b/src/eldap/eldap.erl
new file mode 100644
index 000000000..939a45e7e
--- /dev/null
+++ b/src/eldap/eldap.erl
@@ -0,0 +1,995 @@
+-module(eldap).
+%%% --------------------------------------------------------------------
+%%% Created: 12 Oct 2000 by Tobbe <tnt@home.se>
+%%% Function: Erlang client LDAP implementation according RFC 2251.
+%%% The interface is based on RFC 1823, and
+%%% draft-ietf-asid-ldap-c-api-00.txt
+%%%
+%%% Copyright (C) 2000 Torbjörn Törnkvist, tnt@home.se
+%%%
+%%% This program is free software; you can redistribute it and/or modify
+%%% it under the terms of the GNU General Public License as published by
+%%% the Free Software Foundation; either version 2 of the License, or
+%%% (at your option) any later version.
+%%%
+%%% This program is distributed in the hope that it will be useful,
+%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
+%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+%%% GNU General Public License for more details.
+%%%
+%%% You should have received a copy of the GNU General Public License
+%%% along with this program; if not, write to the Free Software
+%%% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+
+%%% Modified by Sean Hinde <shinde@iee.org> 7th Dec 2000
+%%% Turned into gen_fsm, made non-blocking, added timers etc to support this.
+%%% Now has the concept of a name (string() or atom()) per instance which allows
+%%% multiple users to call by name if so desired.
+%%%
+%%% Can be configured with start_link parameters or use a config file to get
+%%% host to connect to, dn, password, log function etc.
+
+
+%%% Modified by Alexey Shchepin <alexey@sevcom.net>
+%%% --------------------------------------------------------------------
+-vc('$Id$ ').
+
+
+%%%----------------------------------------------------------------------
+%%% LDAP Client state machine.
+%%% Possible states are:
+%%% connecting - actually disconnected, but retrying periodically
+%%% wait_bind_response - connected and sent bind request
+%%% active - bound to LDAP Server and ready to handle commands
+%%%----------------------------------------------------------------------
+
+%%-compile(export_all).
+%%-export([Function/Arity, ...]).
+
+-behaviour(gen_fsm).
+
+%% External exports
+-export([start_link/1, start_link/5, start_link/6]).
+
+-export([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, bind/3]).
+-export([debug_level/2, get_status/1]).
+
+%% gen_fsm callbacks
+-export([init/1, connecting/2,
+ connecting/3, wait_bind_response/3, active/3, handle_event/3,
+ handle_sync_event/4, handle_info/3, terminate/3, code_change/4]).
+
+
+-import(lists,[concat/1]).
+
+-include("ELDAPv3.hrl").
+-include("eldap.hrl").
+
+-define(LDAP_VERSION, 3).
+-define(RETRY_TIMEOUT, 5000).
+-define(BIND_TIMEOUT, 10000).
+-define(CMD_TIMEOUT, 5000).
+-define(MAX_TRANSACTION_ID, 65535).
+-define(MIN_TRANSACTION_ID, 0).
+
+-record(eldap, {version = ?LDAP_VERSION,
+ hosts, % Possible hosts running LDAP servers
+ host = null, % Connected Host LDAP server
+ port = 389 , % The LDAP server port
+ fd = null, % Socket filedescriptor.
+ rootdn = "", % Name of the entry to bind as
+ passwd, % Password for (above) entry
+ id = 0, % LDAP Request ID
+ log, % User provided log function
+ bind_timer, % Ref to bind timeout
+ dict, % dict holding operation params and results
+ debug_level % Integer debug/logging level
+ }).
+
+%%%----------------------------------------------------------------------
+%%% API
+%%%----------------------------------------------------------------------
+start_link(Name) ->
+ Reg_name = list_to_atom("eldap_" ++ Name),
+ gen_fsm:start_link({local, Reg_name}, ?MODULE, [], []).
+
+start_link(Name, Hosts, Port, Rootdn, Passwd) ->
+ Log = fun(N, Fmt, Args) -> io:format("---- " ++ Fmt, [Args]) end,
+ Reg_name = list_to_atom("eldap_" ++ Name),
+ gen_fsm:start_link({local, Reg_name}, ?MODULE, {Hosts, Port, Rootdn, Passwd, Log}, []).
+
+start_link(Name, Hosts, Port, Rootdn, Passwd, Log) ->
+ Reg_name = list_to_atom("eldap_" ++ Name),
+ gen_fsm:start_link({local, Reg_name}, ?MODULE, {Hosts, Port, Rootdn, Passwd, Log}, []).
+
+%%% --------------------------------------------------------------------
+%%% Set Debug Level. 0 - none, 1 - errors, 2 - ldap events
+%%% --------------------------------------------------------------------
+debug_level(Handle, N) when integer(N) ->
+ Handle1 = get_handle(Handle),
+ gen_fsm:sync_send_all_state_event(Handle1, {debug_level,N}).
+
+%%% --------------------------------------------------------------------
+%%% Get status of connection.
+%%% --------------------------------------------------------------------
+get_status(Handle) ->
+ Handle1 = get_handle(Handle),
+ gen_fsm:sync_send_all_state_event(Handle1, get_status).
+
+%%% --------------------------------------------------------------------
+%%% Shutdown connection (and process) asynchronous.
+%%% --------------------------------------------------------------------
+close(Handle) ->
+ Handle1 = get_handle(Handle),
+ gen_fsm:send_all_state_event(Handle1, close).
+
+%%% --------------------------------------------------------------------
+%%% 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 list(Entry),list(Attributes) ->
+ Handle1 = get_handle(Handle),
+ gen_fsm:sync_send_event(Handle1, {add, Entry, add_attrs(Attributes)}).
+
+%%% Do sanity check !
+add_attrs(Attrs) ->
+ F = fun({Type,Vals}) when list(Type),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 list(Entry) ->
+ Handle1 = get_handle(Handle),
+ gen_fsm:sync_send_event(Handle1, {delete, Entry}).
+
+%%% --------------------------------------------------------------------
+%%% 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",
+%%% [replace("telephoneNumber", ["555 555 00"]),
+%%% add("description", ["LDAP hacker"])]
+%%% )
+%%% --------------------------------------------------------------------
+modify(Handle, Object, Mods) when list(Object), list(Mods) ->
+ Handle1 = get_handle(Handle),
+ gen_fsm:sync_send_event(Handle1, {modify, Object, Mods}).
+
+%%%
+%%% Modification operations.
+%%% Example:
+%%% replace("telephoneNumber", ["555 555 00"])
+%%%
+mod_add(Type, Values) when list(Type), list(Values) -> m(add, Type, Values).
+mod_delete(Type, Values) when list(Type), list(Values) -> m(delete, Type, Values).
+mod_replace(Type, Values) when list(Type), list(Values) -> m(replace, Type, Values).
+
+m(Operation, Type, Values) ->
+ #'ModifyRequest_modification_SEQOF'{
+ operation = Operation,
+ modification = #'AttributeTypeAndValues'{
+ 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 list(Entry),list(NewRDN),atom(DelOldRDN),list(NewSup) ->
+ Handle1 = get_handle(Handle),
+ gen_fsm:sync_send_event(Handle1, {modify_dn, Entry, NewRDN, bool_p(DelOldRDN), optional(NewSup)}).
+
+
+%%% --------------------------------------------------------------------
+%%% Bind.
+%%% Example:
+%%%
+%%% bind(Handle,
+%%% "cn=Bill Valentine, ou=people, o=Bluetail AB, dc=bluetail, dc=com",
+%%% "secret")
+%%% --------------------------------------------------------------------
+bind(Handle, RootDN, Passwd)
+ when list(RootDN),list(Passwd) ->
+ Handle1 = get_handle(Handle),
+ gen_fsm:sync_send_event(Handle1, {bind, RootDN, Passwd}).
+
+%%% 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("sn", [{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 record(A, eldap_search) ->
+ call_search(Handle, A);
+search(Handle, L) when list(Handle), list(L) ->
+ case catch parse_search_args(L) of
+ {error, Emsg} -> {error, Emsg};
+ {'EXIT', Emsg} -> {error, Emsg};
+ A when record(A, eldap_search) -> call_search(Handle, A)
+ end.
+
+call_search(Handle, A) ->
+ Handle1 = get_handle(Handle),
+ gen_fsm:sync_send_event(Handle1, {search, A}).
+
+parse_search_args(Args) ->
+ parse_search_args(Args, #eldap_search{scope = wholeSubtree}).
+
+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([{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 integer(Timeout) ->
+ parse_search_args(T,A#eldap_search{timeout = Timeout});
+parse_search_args([H|T],A) ->
+ throw({error,{unknown_arg, H}});
+parse_search_args([],A) ->
+ A.
+
+%%%
+%%% The Scope parameter
+%%%
+baseObject() -> baseObject.
+singleLevel() -> singleLevel.
+wholeSubtree() -> wholeSubtree.
+
+%%%
+%%% Boolean filter operations
+%%%
+'and'(ListOfFilters) when list(ListOfFilters) -> {'and',ListOfFilters}.
+'or'(ListOfFilters) when list(ListOfFilters) -> {'or', ListOfFilters}.
+'not'(Filter) when 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 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 list(Type), list(SubStr) ->
+ Ss = {'SubstringFilter_substrings',v_substr(SubStr)},
+ {substrings,#'SubstringFilter'{type = Type,
+ substrings = Ss}}.
+
+
+get_handle(Pid) when pid(Pid) -> Pid;
+get_handle(Atom) when atom(Atom) -> Atom;
+get_handle(Name) when list(Name) -> list_to_atom("eldap_" ++ Name).
+%%%----------------------------------------------------------------------
+%%% Callback functions from gen_fsm
+%%%----------------------------------------------------------------------
+
+%%----------------------------------------------------------------------
+%% Func: init/1
+%% Returns: {ok, StateName, StateData} |
+%% {ok, StateName, StateData, Timeout} |
+%% ignore |
+%% {stop, StopReason}
+%% I use the trick of setting a timeout of 0 to pass control into the
+%% process.
+%%----------------------------------------------------------------------
+init([]) ->
+ case get_config() of
+ {ok, Hosts, Rootdn, Passwd, Log} ->
+ init({Hosts, Rootdn, Passwd, Log});
+ {error, Reason} ->
+ {stop, Reason}
+ end;
+init({Hosts, Port, Rootdn, Passwd, Log}) ->
+ {ok, connecting, #eldap{hosts = Hosts,
+ port = Port,
+ rootdn = Rootdn,
+ passwd = Passwd,
+ id = 0,
+ log = Log,
+ dict = dict:new(),
+ debug_level = 0}, 0}.
+
+%%----------------------------------------------------------------------
+%% Func: StateName/2
+%% Called when gen_fsm:send_event/2,3 is invoked (async)
+%% Returns: {next_state, NextStateName, NextStateData} |
+%% {next_state, NextStateName, NextStateData, Timeout} |
+%% {stop, Reason, NewStateData}
+%%----------------------------------------------------------------------
+connecting(timeout, S) ->
+ {ok, NextState, NewS} = connect_bind(S),
+ {next_state, NextState, NewS}.
+
+%%----------------------------------------------------------------------
+%% Func: StateName/3
+%% Called when gen_fsm:sync_send_event/2,3 is invoked.
+%% Returns: {next_state, NextStateName, NextStateData} |
+%% {next_state, NextStateName, NextStateData, Timeout} |
+%% {reply, Reply, NextStateName, NextStateData} |
+%% {reply, Reply, NextStateName, NextStateData, Timeout} |
+%% {stop, Reason, NewStateData} |
+%% {stop, Reason, Reply, NewStateData}
+%%----------------------------------------------------------------------
+connecting(Event, From, S) ->
+ Reply = {error, connecting},
+ {reply, Reply, connecting, S}.
+
+wait_bind_response(Event, From, S) ->
+ Reply = {error, wait_bind_response},
+ {reply, Reply, wait_bind_response, S}.
+
+active(Event, From, S) ->
+ case catch send_command(Event, From, S) of
+ {ok, NewS} ->
+ {next_state, active, NewS};
+ {error, Reason} ->
+ {reply, {error, Reason}, active, S};
+ {'EXIT', Reason} ->
+ {reply, {error, Reason}, active, S}
+ end.
+
+%%----------------------------------------------------------------------
+%% Func: handle_event/3
+%% Called when gen_fsm:send_all_state_event/2 is invoked.
+%% Returns: {next_state, NextStateName, NextStateData} |
+%% {next_state, NextStateName, NextStateData, Timeout} |
+%% {stop, Reason, NewStateData}
+%%----------------------------------------------------------------------
+handle_event(close, StateName, S) ->
+ gen_tcp:close(S#eldap.fd),
+ {stop, closed, S};
+
+handle_event(Event, StateName, S) ->
+ {next_state, StateName, S}.
+
+%%----------------------------------------------------------------------
+%% Func: handle_sync_event/4
+%% Called when gen_fsm:sync_send_all_state_event/2,3 is invoked
+%% Returns: {next_state, NextStateName, NextStateData} |
+%% {next_state, NextStateName, NextStateData, Timeout} |
+%% {reply, Reply, NextStateName, NextStateData} |
+%% {reply, Reply, NextStateName, NextStateData, Timeout} |
+%% {stop, Reason, NewStateData} |
+%% {stop, Reason, Reply, NewStateData}
+%%----------------------------------------------------------------------
+handle_sync_event({debug_level, N}, From, StateName, S) ->
+ {reply, ok, StateName, S#eldap{debug_level = N}};
+
+handle_sync_event(Event, From, StateName, S) ->
+ {reply, {StateName, S}, StateName, S};
+
+handle_sync_event(Event, From, StateName, S) ->
+ Reply = ok,
+ {reply, Reply, StateName, S}.
+
+%%----------------------------------------------------------------------
+%% Func: handle_info/3
+%% Returns: {next_state, NextStateName, NextStateData} |
+%% {next_state, NextStateName, NextStateData, Timeout} |
+%% {stop, Reason, NewStateData}
+%%----------------------------------------------------------------------
+
+%%
+%% Packets arriving in various states
+%%
+handle_info({tcp, Socket, Data}, connecting, S) ->
+ log1("eldap. tcp packet received when disconnected!~n~p~n", [Data], S),
+ {next_state, connecting, S};
+
+handle_info({tcp, Socket, Data}, wait_bind_response, S) ->
+ cancel_timer(S#eldap.bind_timer),
+ case catch recvd_wait_bind_response(Data, S) of
+ bound -> {next_state, active, S};
+ {fail_bind, Reason} -> close_and_retry(S),
+ {next_state, connecting, S#eldap{fd = null}};
+ {'EXIT', Reason} -> close_and_retry(S),
+ {next_state, connecting, S#eldap{fd = null}};
+ {error, Reason} -> close_and_retry(S),
+ {next_state, connecting, S#eldap{fd = null}}
+ end;
+
+handle_info({tcp, Socket, Data}, active, S) ->
+ case catch recvd_packet(Data, S) of
+ {reply, Reply, To, NewS} -> gen_fsm:reply(To, Reply),
+ {next_state, active, NewS};
+ {ok, NewS} -> {next_state, active, NewS};
+ {'EXIT', Reason} -> {next_state, active, S};
+ {error, Reason} -> {next_state, active, S}
+ end;
+
+handle_info({tcp_closed, Socket}, All_fsm_states, S) ->
+ F = fun(Id, [{Timer, From, Name}|Res]) ->
+ gen_fsm:reply(From, {error, tcp_closed}),
+ cancel_timer(Timer)
+ end,
+ dict:map(F, S#eldap.dict),
+ retry_connect(),
+ {next_state, connecting, S#eldap{fd = null,
+ dict = dict:new()}};
+
+handle_info({tcp_error, Socket, Reason}, Fsm_state, S) ->
+ log1("eldap received tcp_error: ~p~nIn State: ~p~n", [Reason, Fsm_state], S),
+ {next_state, Fsm_state, S};
+%%
+%% Timers
+%%
+handle_info({timeout, Timer, {cmd_timeout, Id}}, active, S) ->
+ case cmd_timeout(Timer, Id, S) of
+ {reply, To, Reason, NewS} -> gen_fsm:reply(To, Reason),
+ {next_state, active, NewS};
+ {error, Reason} -> {next_state, active, S}
+ end;
+
+handle_info({timeout, retry_connect}, connecting, S) ->
+ {ok, NextState, NewS} = connect_bind(S),
+ {next_state, NextState, NewS};
+
+handle_info({timeout, Timer, bind_timeout}, wait_bind_response, S) ->
+ close_and_retry(S),
+ {next_state, connecting, S#eldap{fd = null}};
+
+%%
+%% Make sure we don't fill the message queue with rubbish
+%%
+handle_info(Info, StateName, S) ->
+ log1("eldap. Unexpected Info: ~p~nIn state: ~p~n when StateData is: ~p~n",
+ [Info, StateName, S], S),
+ {next_state, StateName, S}.
+
+%%----------------------------------------------------------------------
+%% Func: terminate/3
+%% Purpose: Shutdown the fsm
+%% Returns: any
+%%----------------------------------------------------------------------
+terminate(Reason, StateName, StatData) ->
+ ok.
+
+%%----------------------------------------------------------------------
+%% Func: code_change/4
+%% Purpose: Convert process state when code is changed
+%% Returns: {ok, NewState, NewStateData}
+%%----------------------------------------------------------------------
+code_change(OldVsn, StateName, S, Extra) ->
+ {ok, StateName, S}.
+
+%%%----------------------------------------------------------------------
+%%% Internal functions
+%%%----------------------------------------------------------------------
+send_command(Command, From, S) ->
+ Id = bump_id(S),
+ {Name, Request} = gen_req(Command),
+ Message = #'LDAPMessage'{messageID = Id,
+ protocolOp = {Name, Request}},
+ log2("~p~n",[{Name, Request}], S),
+ {ok, Bytes} = asn1rt:encode('ELDAPv3', 'LDAPMessage', Message),
+ ok = gen_tcp:send(S#eldap.fd, Bytes),
+ Timer = erlang:start_timer(?CMD_TIMEOUT, self(), {cmd_timeout, Id}),
+ New_dict = dict:store(Id, [{Timer, From, Name}], S#eldap.dict),
+ {ok, S#eldap{id = Id,
+ dict = New_dict}}.
+
+gen_req({search, A}) ->
+ {searchRequest,
+ #'SearchRequest'{baseObject = A#eldap_search.base,
+ scope = v_scope(A#eldap_search.scope),
+ derefAliases = neverDerefAliases,
+ 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)
+ }};
+gen_req({add, Entry, Attrs}) ->
+ {addRequest,
+ #'AddRequest'{entry = Entry,
+ attributes = Attrs}};
+gen_req({delete, Entry}) ->
+ {delRequest, Entry};
+gen_req({modify, Obj, Mod}) ->
+ v_modifications(Mod),
+ {modifyRequest,
+ #'ModifyRequest'{object = Obj,
+ modification = Mod}};
+gen_req({modify_dn, Entry, NewRDN, DelOldRDN, NewSup}) ->
+ {modDNRequest,
+ #'ModifyDNRequest'{entry = Entry,
+ newrdn = NewRDN,
+ deleteoldrdn = DelOldRDN,
+ newSuperior = NewSup}};
+
+gen_req({bind, RootDN, Passwd}) ->
+ {bindRequest,
+ #'BindRequest'{version = ?LDAP_VERSION,
+ name = RootDN,
+ authentication = {simple, Passwd}}}.
+
+%%-----------------------------------------------------------------------
+%% recvd_packet
+%% Deals with incoming packets in the active state
+%% Will return one of:
+%% {ok, NewS} - Don't reply to client yet as this is part of a search
+%% result and we haven't got all the answers yet.
+%% {reply, Result, From, NewS} - Reply with result to client From
+%% {error, Reason}
+%% {'EXIT', Reason} - Broke
+%%-----------------------------------------------------------------------
+recvd_packet(Pkt, S) ->
+ check_tag(Pkt),
+ case asn1rt:decode('ELDAPv3', 'LDAPMessage', Pkt) of
+ {ok,Msg} ->
+ Op = Msg#'LDAPMessage'.protocolOp,
+ log2("~p~n",[Op], S),
+ Dict = S#eldap.dict,
+ Id = Msg#'LDAPMessage'.messageID,
+ {Timer, From, Name, Result_so_far} = get_op_rec(Id, Dict),
+ case {Name, Op} of
+ {searchRequest, {searchResEntry, R}} when
+ record(R,'SearchResultEntry') ->
+ New_dict = dict:append(Id, R, Dict),
+ {ok, S#eldap{dict = New_dict}};
+ {searchRequest, {searchResDone, Result}} ->
+ case Result#'LDAPResult'.resultCode of
+ success ->
+ {Res, Ref} = polish(Result_so_far),
+ New_dict = dict:erase(Id, Dict),
+ cancel_timer(Timer),
+ {reply, #eldap_search_result{entries = Res,
+ referrals = Ref}, From,
+ S#eldap{dict = New_dict}};
+ Reason ->
+ New_dict = dict:erase(Id, Dict),
+ cancel_timer(Timer),
+ {reply, {error, Reason}, From, S#eldap{dict = New_dict}}
+ end;
+ {searchRequest, {searchResRef, R}} ->
+ New_dict = dict:append(Id, R, Dict),
+ {ok, S#eldap{dict = New_dict}};
+ {addRequest, {addResponse, Result}} ->
+ New_dict = dict:erase(Id, Dict),
+ cancel_timer(Timer),
+ Reply = check_reply(Result, From),
+ {reply, Reply, From, S#eldap{dict = New_dict}};
+ {delRequest, {delResponse, Result}} ->
+ New_dict = dict:erase(Id, Dict),
+ cancel_timer(Timer),
+ Reply = check_reply(Result, From),
+ {reply, Reply, From, S#eldap{dict = New_dict}};
+ {modifyRequest, {modifyResponse, Result}} ->
+ New_dict = dict:erase(Id, Dict),
+ cancel_timer(Timer),
+ Reply = check_reply(Result, From),
+ {reply, Reply, From, S#eldap{dict = New_dict}};
+ {modDNRequest, {modDNResponse, Result}} ->
+ New_dict = dict:erase(Id, Dict),
+ cancel_timer(Timer),
+ Reply = check_reply(Result, From),
+ {reply, Reply, From, S#eldap{dict = New_dict}};
+ {bindRequest, {bindResponse, Result}} ->
+ New_dict = dict:erase(Id, Dict),
+ cancel_timer(Timer),
+ Reply = check_bind_reply(Result, From),
+ {reply, Reply, From, S#eldap{dict = New_dict}};
+ {OtherName, OtherResult} ->
+ New_dict = dict:erase(Id, Dict),
+ cancel_timer(Timer),
+ {reply, {error, {invalid_result, OtherName, OtherResult}},
+ From, S#eldap{dict = New_dict}}
+ end;
+ Error -> Error
+ end.
+
+check_reply(#'LDAPResult'{resultCode = success}, From) ->
+ ok;
+check_reply(#'LDAPResult'{resultCode = Reason}, From) ->
+ {error, Reason};
+check_reply(Other, From) ->
+ {error, Other}.
+
+check_bind_reply(#'BindResponse'{resultCode = success}, From) ->
+ ok;
+check_bind_reply(#'BindResponse'{resultCode = Reason}, From) ->
+ {error, Reason};
+check_bind_reply(Other, From) ->
+ {error, Other}.
+
+get_op_rec(Id, Dict) ->
+ case dict:find(Id, Dict) of
+ {ok, [{Timer, From, Name}|Res]} ->
+ {Timer, From, Name, Res};
+ error ->
+ throw({error, unkown_id})
+ end.
+
+%%-----------------------------------------------------------------------
+%% recvd_wait_bind_response packet
+%% Deals with incoming packets in the wait_bind_response state
+%% Will return one of:
+%% bound - Success - move to active state
+%% {fail_bind, Reason} - Failed
+%% {error, Reason}
+%% {'EXIT', Reason} - Broken packet
+%%-----------------------------------------------------------------------
+recvd_wait_bind_response(Pkt, S) ->
+ check_tag(Pkt),
+ case asn1rt:decode('ELDAPv3', 'LDAPMessage', Pkt) of
+ {ok,Msg} ->
+ log2("~p", [Msg], S),
+ check_id(S#eldap.id, Msg#'LDAPMessage'.messageID),
+ case Msg#'LDAPMessage'.protocolOp of
+ {bindResponse, Result} ->
+ case Result#'LDAPResult'.resultCode of
+ success -> bound;
+ Error -> {fail_bind, Error}
+ end
+ end;
+ Else ->
+ {fail_bind, Else}
+ end.
+
+check_id(Id, Id) -> ok;
+check_id(_, _) -> throw({error, wrong_bind_id}).
+
+%%-----------------------------------------------------------------------
+%% General Helpers
+%%-----------------------------------------------------------------------
+
+cancel_timer(Timer) ->
+ erlang:cancel_timer(Timer),
+ receive
+ {timeout, Timer, _} ->
+ ok
+ after 0 ->
+ ok
+ end.
+
+
+%%% Sanity check of received packet
+check_tag(Data) ->
+ case asn1rt_ber_bin:decode_tag(Data) of
+ {Tag, Data1, Rb} ->
+ case asn1rt_ber_bin:decode_length(Data1) of
+ {{Len,Data2}, Rb2} -> ok;
+ _ -> throw({error,decoded_tag_length})
+ end;
+ _ -> throw({error,decoded_tag})
+ end.
+
+close_and_retry(S) ->
+ gen_tcp:close(S#eldap.fd),
+ retry_connect().
+
+retry_connect() ->
+ erlang:send_after(?RETRY_TIMEOUT, self(),
+ {timeout, retry_connect}).
+
+
+%%-----------------------------------------------------------------------
+%% Sort out timed out commands
+%%-----------------------------------------------------------------------
+cmd_timeout(Timer, Id, S) ->
+ Dict = S#eldap.dict,
+ case dict:find(Id, Dict) of
+ {ok, [{Id, Timer, From, Name}|Res]} ->
+ case Name of
+ searchRequest ->
+ {Res1, Ref1} = polish(Res),
+ New_dict = dict:erase(Id, Dict),
+ {reply, From, {timeout,
+ #eldap_search_result{entries = Res1,
+ referrals = Ref1}},
+ S#eldap{dict = New_dict}};
+ Others ->
+ New_dict = dict:erase(Id, Dict),
+ {reply, From, {error, timeout}, S#eldap{dict = New_dict}}
+ end;
+ error ->
+ {error, timed_out_cmd_not_in_dict}
+ end.
+
+%%-----------------------------------------------------------------------
+%% Common stuff for results
+%%-----------------------------------------------------------------------
+%%%
+%%% Polish the returned search result
+%%%
+
+polish(Entries) ->
+ polish(Entries, [], []).
+
+polish([H|T], Res, Ref) when record(H, 'SearchResultEntry') ->
+ ObjectName = H#'SearchResultEntry'.objectName,
+ F = fun({_,A,V}) -> {A,V} end,
+ Attrs = lists:map(F, H#'SearchResultEntry'.attributes),
+ polish(T, [#eldap_entry{object_name = ObjectName,
+ attributes = Attrs}|Res], Ref);
+polish([H|T], Res, Ref) -> % No special treatment of referrals at the moment.
+ polish(T, Res, [H|Ref]);
+polish([], Res, Ref) ->
+ {Res, Ref}.
+
+%%-----------------------------------------------------------------------
+%% Connect to next server in list and attempt to bind to it.
+%%-----------------------------------------------------------------------
+connect_bind(S) ->
+ Host = next_host(S#eldap.host, S#eldap.hosts),
+ TcpOpts = [{packet, asn1}, {active, true}, binary],
+ case gen_tcp:connect(Host, S#eldap.port, TcpOpts) of
+ {ok, Socket} ->
+ case bind_request(Socket, S) of
+ {ok, NewS} ->
+ Timer = erlang:start_timer(?BIND_TIMEOUT, self(),
+ {timeout, bind_timeout}),
+ {ok, wait_bind_response, NewS#eldap{fd = Socket,
+ host = Host,
+ bind_timer = Timer}};
+ {error, Reason} ->
+ gen_tcp:close(Socket),
+ erlang:send_after(?RETRY_TIMEOUT, self(),
+ {timeout, retry_connect}),
+ {ok, connecting, S#eldap{host = Host}}
+ end;
+ {error, Reason} ->
+ erlang:send_after(?RETRY_TIMEOUT, self(),
+ {timeout, retry_connect}),
+ {ok, connecting, S#eldap{host = Host}}
+ end.
+
+bind_request(Socket, S) ->
+ Id = bump_id(S),
+ Req = #'BindRequest'{version = S#eldap.version,
+ name = S#eldap.rootdn,
+ authentication = {simple, S#eldap.passwd}},
+ Message = #'LDAPMessage'{messageID = Id,
+ protocolOp = {bindRequest, Req}},
+ log2("Message:~p~n",[Message], S),
+ {ok, Bytes} = asn1rt:encode('ELDAPv3', 'LDAPMessage', Message),
+ ok = gen_tcp:send(Socket, Bytes),
+ {ok, S#eldap{id = Id}}.
+
+%% Given last tried Server, find next one to try
+next_host(null, [H|_]) -> H; % First time, take first
+next_host(Host, Hosts) -> % Find next in turn
+ next_host(Host, Hosts, Hosts).
+
+next_host(Host, [Host], Hosts) -> hd(Hosts); % Wrap back to first
+next_host(Host, [Host|Tail], Hosts) -> hd(Tail); % Take next
+next_host(Host, [], Hosts) -> hd(Hosts); % Never connected before? (shouldn't happen)
+next_host(Host, [H|T], Hosts) -> next_host(Host, T, Hosts).
+
+
+%%% --------------------------------------------------------------------
+%%% 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 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 list(Str),Key==initial;Key==any;Key==final ->
+ [{Key,Str}|v_substr(T)];
+v_substr([H|T]) ->
+ 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_bool(true) -> true;
+v_bool(false) -> false;
+v_bool(_Bool) -> throw({error,concat(["not Boolean: ",_Bool])}).
+
+v_timeout(I) when integer(I), I>=0 -> I;
+v_timeout(_I) -> throw({error,concat(["timeout not positive integer: ",_I])}).
+
+v_attributes(Attrs) ->
+ F = fun(A) when list(A) -> A;
+ (A) -> throw({error,concat(["attribute not String: ",A])})
+ end,
+ lists:map(F,Attrs).
+
+
+%%% --------------------------------------------------------------------
+%%% Get and Validate the initial configuration
+%%% --------------------------------------------------------------------
+get_config() ->
+ Priv_dir = code:priv_dir(eldap),
+ File = filename:join(Priv_dir, "eldap.conf"),
+ case file:consult(File) of
+ {ok, Entries} ->
+ case catch parse(Entries) of
+ {ok, Hosts, Port, Rootdn, Passwd, Log} ->
+ {ok, Hosts, Port, Rootdn, Passwd, Log};
+ {error, Reason} ->
+ {error, Reason};
+ {'EXIT', Reason} ->
+ {error, Reason}
+ end;
+ {error, Reason} ->
+ {error, Reason}
+ end.
+
+parse(Entries) ->
+ {ok,
+ get_hosts(host, Entries),
+ get_integer(port, Entries),
+ get_list(rootdn, Entries),
+ get_list(passwd, Entries),
+ get_log(log, Entries)}.
+
+get_integer(Key, List) ->
+ case lists:keysearch(Key, 1, List) of
+ {value, {Key, Value}} when integer(Value) ->
+ Value;
+ {value, {Key, Value}} ->
+ throw({error, "Bad Value in Config for " ++ atom_to_list(Key)});
+ false ->
+ throw({error, "No Entry in Config for " ++ atom_to_list(Key)})
+ end.
+
+get_list(Key, List) ->
+ case lists:keysearch(Key, 1, List) of
+ {value, {Key, Value}} when list(Value) ->
+ Value;
+ {value, {Key, Value}} ->
+ throw({error, "Bad Value in Config for " ++ atom_to_list(Key)});
+ false ->
+ throw({error, "No Entry in Config for " ++ atom_to_list(Key)})
+ end.
+
+get_log(Key, List) ->
+ case lists:keysearch(Key, 1, List) of
+ {value, {Key, Value}} when function(Value) ->
+ Value;
+ {value, {Key, Else}} ->
+ false;
+ false ->
+ fun(Level, Format, Args) -> io:format("--- " ++ Format, Args) end
+ end.
+
+get_hosts(Key, List) ->
+ lists:map(fun({Key1, {A,B,C,D}}) when integer(A),
+ integer(B),
+ integer(C),
+ integer(D),
+ Key == Key1->
+ {A,B,C,D};
+ ({Key1, Value}) when list(Value),
+ Key == Key1->
+ Value;
+ ({Else, Value}) ->
+ throw({error, "Bad Hostname in config"})
+ end, List).
+
+%%% --------------------------------------------------------------------
+%%% Other Stuff
+%%% --------------------------------------------------------------------
+bump_id(#eldap{id = Id}) when Id > ?MAX_TRANSACTION_ID ->
+ ?MIN_TRANSACTION_ID;
+bump_id(#eldap{id = Id}) ->
+ Id + 1.
+
+%%% --------------------------------------------------------------------
+%%% Log routines. Call a user provided log routine Fun.
+%%% --------------------------------------------------------------------
+
+log1(Str, Args, #eldap{log = Fun, debug_level = N}) -> log(Fun, Str, Args, 1, N).
+log2(Str, Args, #eldap{log = Fun, debug_level = N}) -> log(Fun, Str, Args, 2, N).
+
+log(Fun, Str, Args, This_level, Status) when function(Fun), This_level =< Status ->
+ catch Fun(This_level, Str, Args);
+log(_, _, _, _, _) ->
+ ok.
diff --git a/src/eldap/eldap.hrl b/src/eldap/eldap.hrl
new file mode 100644
index 000000000..e31a34991
--- /dev/null
+++ b/src/eldap/eldap.hrl
@@ -0,0 +1,13 @@
+-record(eldap_search, {scope = wholeSubtree,
+ base = [],
+ filter,
+ attributes = [],
+ types_only = false,
+ timeout = 0}).
+
+
+-record(eldap_search_result, {entries,
+ referrals}).
+
+-record(eldap_entry, {object_name,
+ attributes}).
diff --git a/src/mod_muc/mod_muc_room.erl b/src/mod_muc/mod_muc_room.erl
index 71c1a4417..f9affed9d 100644
--- a/src/mod_muc/mod_muc_room.erl
+++ b/src/mod_muc/mod_muc_room.erl
@@ -308,6 +308,8 @@ normal_state({route, From, "",
_ ->
{next_state, normal_state, NewStateData}
end;
+ reply ->
+ {next_state, normal_state, StateData};
_ ->
Err = jlib:make_error_reply(
Packet, ?ERR_FEATURE_NOT_IMPLEMENTED),
@@ -448,11 +450,17 @@ normal_state({route, From, ToNick,
true ->
case find_jid_by_nick(ToNick, StateData) of
false ->
- Err = jlib:make_error_reply(
- Packet, ?ERR_ITEM_NOT_FOUND),
- ejabberd_router:route(
- jlib:jid_replace_resource(StateData#state.jid, ToNick),
- From, Err);
+ case jlib:iq_query_info(Packet) of
+ reply ->
+ ok;
+ _ ->
+ Err = jlib:make_error_reply(
+ Packet, ?ERR_ITEM_NOT_FOUND),
+ ejabberd_router:route(
+ jlib:jid_replace_resource(
+ StateData#state.jid, ToNick),
+ From, Err)
+ end;
ToJID ->
{ok, #user{nick = FromNick}} =
?DICT:find(jlib:jid_tolower(From),
@@ -462,11 +470,16 @@ normal_state({route, From, ToNick,
ToJID, Packet)
end;
_ ->
- Err = jlib:make_error_reply(
- Packet, ?ERR_NOT_ALLOWED),
- ejabberd_router:route(
- jlib:jid_replace_resource(StateData#state.jid, ToNick), From,
- Err)
+ case jlib:iq_query_info(Packet) of
+ reply ->
+ ok;
+ _ ->
+ Err = jlib:make_error_reply(
+ Packet, ?ERR_NOT_ALLOWED),
+ ejabberd_router:route(
+ jlib:jid_replace_resource(StateData#state.jid, ToNick),
+ From, Err)
+ end
end,
{next_state, normal_state, StateData};
diff --git a/src/mod_register.erl b/src/mod_register.erl
index 62a348d61..52ebcb00f 100644
--- a/src/mod_register.erl
+++ b/src/mod_register.erl
@@ -130,6 +130,8 @@ try_register(User, Password) ->
{error, ?ERR_CONFLICT};
{error, invalid_jid} ->
{error, ?ERR_JID_MALFORMED};
+ {error, not_allowed} ->
+ {error, ?ERR_NOT_ALLOWED};
{error, _Reason} ->
{error, ?ERR_INTERNAL_SERVER_ERROR}
end