mirror of
https://github.com/processone/ejabberd.git
synced 2024-11-28 16:34:13 +01:00
* doc/guide.tex: Updated (thanks to Evgeniy Khramtsov)
* src/ejabberd_auth_ldap.erl: Better LDAP support (thanks to Evgeniy Khramtsov) * src/mod_vcard_ldap.erl: Likewise * src/eldap/eldap_filter.erl: Likewise SVN Revision: 606
This commit is contained in:
parent
6acc96c171
commit
7d2a1af9d9
@ -1,3 +1,12 @@
|
|||||||
|
2006-09-14 Alexey Shchepin <alexey@sevcom.net>
|
||||||
|
|
||||||
|
* doc/guide.tex: Updated (thanks to Evgeniy Khramtsov)
|
||||||
|
|
||||||
|
* src/ejabberd_auth_ldap.erl: Better LDAP support (thanks to
|
||||||
|
Evgeniy Khramtsov)
|
||||||
|
* src/mod_vcard_ldap.erl: Likewise
|
||||||
|
* src/eldap/eldap_filter.erl: Likewise
|
||||||
|
|
||||||
2006-09-11 Mickael Remond <mickael.remond@process-one.net>
|
2006-09-11 Mickael Remond <mickael.remond@process-one.net>
|
||||||
|
|
||||||
* src/odbc/mssql.sql: Removed unused fields.
|
* src/odbc/mssql.sql: Removed unused fields.
|
||||||
|
1662
doc/guide.html
1662
doc/guide.html
File diff suppressed because it is too large
Load Diff
341
doc/guide.tex
341
doc/guide.tex
@ -1985,15 +1985,14 @@ Options:
|
|||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
\begin{itemize}
|
\begin{itemize}
|
||||||
\item Next example prohibits the registration of too short account names and of
|
\item Next example prohibits the registration of too short account names:
|
||||||
account names with exotic characters in it:
|
\begin{verbatim}
|
||||||
\begin{verbatim}
|
|
||||||
{acl, shortname, {user_glob, "?"}}.
|
{acl, shortname, {user_glob, "?"}}.
|
||||||
{acl, shortname, {user_glob, "??"}}.
|
{acl, shortname, {user_glob, "??"}}.
|
||||||
{acl, strangename, {user_regexp, "^..?$"}}.
|
% The same using regexp:
|
||||||
|
%{acl, shortname, {user_regexp, "^..?$"}}.
|
||||||
...
|
...
|
||||||
{access, register, [{deny, shortname},
|
{access, register, [{deny, shortname},
|
||||||
{deny, strangename},
|
|
||||||
{allow, all}]}.
|
{allow, all}]}.
|
||||||
...
|
...
|
||||||
{modules,
|
{modules,
|
||||||
@ -2292,6 +2291,338 @@ Examples:
|
|||||||
\end{verbatim}
|
\end{verbatim}
|
||||||
\end{itemize}
|
\end{itemize}
|
||||||
|
|
||||||
|
\subsection{LDAP and \modvcardldap{}}
|
||||||
|
\label{sec:ldap}
|
||||||
|
\ind{modules!\modvcardldap{}}\ind{JUD}\ind{Jabber User Directory}\ind{vCard}\ind{protocols!JEP-0054: vcard-temp}
|
||||||
|
|
||||||
|
|
||||||
|
\subsubsection{Features}
|
||||||
|
\label{sec:ldapfeatures}
|
||||||
|
|
||||||
|
\ejabberd{} has built-in LDAP support. You can authenticate users against LDAP
|
||||||
|
server and use LDAP directory as vCard storage. Shared rosters are not
|
||||||
|
supported yet.
|
||||||
|
|
||||||
|
|
||||||
|
\subsubsection{Connection}
|
||||||
|
\label{sec:ldapconnection}
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
|
||||||
|
\begin{description}
|
||||||
|
\titem{ldap\_server} \ind{options!ldap_server}IP address or dns name of your
|
||||||
|
LDAP server. This option is required.
|
||||||
|
\titem{ldap\_port} \ind{options!ldap_port}Port to connect to LDAP server.
|
||||||
|
Default is~389.
|
||||||
|
\titem{ldap\_rootdn} \ind{options!ldap_rootdn}Bind DN. Default is~\term{""}
|
||||||
|
which means anonymous connection.
|
||||||
|
\titem{ldap\_password} \ind{options!ldap_password}Bind password. Default
|
||||||
|
is~\term{""}.
|
||||||
|
\end{description}
|
||||||
|
|
||||||
|
Example:
|
||||||
|
\begin{verbatim}
|
||||||
|
{auth_method, ldap}.
|
||||||
|
{ldap_servers, ["ldap.mydomain.org"]}.
|
||||||
|
{ldap_port, 389}.
|
||||||
|
{ldap_rootdn, "cn=Manager,dc=domain,dc=org"}.
|
||||||
|
{ldap_password, "secret"}.
|
||||||
|
\end{verbatim}
|
||||||
|
|
||||||
|
Note that current LDAP implementation doesn't support SSL connection and SASL
|
||||||
|
authentication.
|
||||||
|
|
||||||
|
|
||||||
|
\subsubsection{Authentication}
|
||||||
|
\label{sec:ldapauthentication}
|
||||||
|
|
||||||
|
You can authenticate users against LDAP directory. Available parameters are
|
||||||
|
listed below:
|
||||||
|
|
||||||
|
\begin{description}
|
||||||
|
\titem{ldap\_base} \ind{options!ldap_base}LDAP base directory which stores users
|
||||||
|
accounts. This option is required.
|
||||||
|
\titem{ldap\_uidattr} \ind{options!ldap_uidattr}LDAP attribute which holds
|
||||||
|
user's part of JID. Default is \term{"uid"}.
|
||||||
|
\titem{ldap\_uidattr\_format} \ind{options!ldap_uidattr_format}Format of the
|
||||||
|
\term{ldap\_uidattr} variable. Format MUST contain one and only one pattern
|
||||||
|
variable \term{"\%u"} which will be replaced by user's part of JID. For example,
|
||||||
|
\term{"\%u@mydomain.org"}. Default value is \term{"\%u"}.
|
||||||
|
\titem{ldap\_filter} \ind{options!ldap_filter}RFC 2254 LDAP filter. Default is
|
||||||
|
\term{none}. Example: \term{"(\&(objectClass=shadowAccount)(memberOf=Jabber
|
||||||
|
Users))"}. Please, don't forget closing brackets and don't use superfluous
|
||||||
|
whitespaces. Also you MUST NOT use \option{ldap\_uidattr} attribute in filter
|
||||||
|
because this attribute will be substituted in LDAP filter automatically.
|
||||||
|
\end{description}
|
||||||
|
|
||||||
|
|
||||||
|
\subsubsection{vCards and Search}
|
||||||
|
\label{sec:modvcardldap}
|
||||||
|
|
||||||
|
\ejabberd{} can map LDAP attributes to vCard fields. This behaviour is
|
||||||
|
implemented in \modvcardldap{} module. This module doesn't depend on
|
||||||
|
authentication method. \modvcardldap{} module has it's own optional
|
||||||
|
parameters. The first group of parameters has the same meaning as top-level
|
||||||
|
LDAP parameters: \option{ldap\_servers}, \option{ldap\_port},
|
||||||
|
\option{ldap\_rootdn}, \option{ldap\_password}, \option{ldap\_base},
|
||||||
|
\option{ldap\_uidattr}, \option{ldap\_uidattr\_format} and
|
||||||
|
\option{ldap\_filter}. If one of this option is not set \ejabberd{} will look
|
||||||
|
for top-level option with the same name. The second group of parameters
|
||||||
|
consists of the following options:
|
||||||
|
|
||||||
|
\begin{description}
|
||||||
|
\hostitem{vjud}
|
||||||
|
\iqdiscitem{\ns{vcard-temp}}
|
||||||
|
\titem{search} \ind{options!search}This option specifies whether the search
|
||||||
|
functionality is enabled (value: \term{true}) or disabled
|
||||||
|
(value: \term{false}). If disabled, the option \term{hosts} will be
|
||||||
|
ignored and the \Jabber{} User Directory service will not appear in the
|
||||||
|
Service Discovery item list. The default value is \term{true}.
|
||||||
|
\titem{ldap\_vcard\_map} \ind{options!ldap_vcard_map}the table which defines
|
||||||
|
reflection of LDAP attributes to vCard fields.
|
||||||
|
Format is:
|
||||||
|
\term{[{Name\_of\_vcard\_field, Pattern, List\_of\_LDAP\_attributes}, ...]}
|
||||||
|
where
|
||||||
|
\term{Name\_of\_vcard\_field} is the type name of vCard as defined
|
||||||
|
in RFC 2426,
|
||||||
|
Pattern is a string which contains pattern variables \term{"\%u"}, \term{"\%d"} or \term{"\%s"},
|
||||||
|
\term{List\_of\_LDAP\_attributes} is the list which contains of LDAP attributes.
|
||||||
|
Pattern variables \term{"\%s"} will be sequentially replaced with the values of
|
||||||
|
LDAP attributes from \term{List\_of\_LDAP\_attributes}; \term{"\%u"} will be replaced with
|
||||||
|
user's part of JID and \term{"\%d"} will be replaced with domain part of JID.
|
||||||
|
Example:
|
||||||
|
\begin{verbatim}
|
||||||
|
{ldap_vcard_map,
|
||||||
|
[{"NICKNAME", "%u", []},
|
||||||
|
{"FN", "%s", ["displayName"]},
|
||||||
|
{"CTRY", "Russia", []},
|
||||||
|
{"EMAIL", "%u@%d", []},
|
||||||
|
{"DESC", "%s\n%s", ["title", "description"]}
|
||||||
|
]},
|
||||||
|
\end{verbatim}
|
||||||
|
|
||||||
|
Default is:
|
||||||
|
\begin{verbatim}
|
||||||
|
[{"NICKNAME", "%u", []},
|
||||||
|
{"FN", "%s", ["displayName"]},
|
||||||
|
{"FAMILY", "%s", ["sn"]},
|
||||||
|
{"GIVEN", "%s", ["givenName"]},
|
||||||
|
{"MIDDLE", "%s", ["initials"]},
|
||||||
|
{"ORGNAME", "%s", ["o"]},
|
||||||
|
{"ORGUNIT", "%s", ["ou"]},
|
||||||
|
{"CTRY", "%s", ["c"]},
|
||||||
|
{"LOCALITY", "%s", ["l"]},
|
||||||
|
{"STREET", "%s", ["street"]},
|
||||||
|
{"REGION", "%s", ["st"]},
|
||||||
|
{"PCODE", "%s", ["postalCode"]},
|
||||||
|
{"TITLE", "%s", ["title"]},
|
||||||
|
{"URL", "%s", ["labeleduri"]},
|
||||||
|
{"DESC", "%s", ["description"]},
|
||||||
|
{"TEL", "%s", ["telephoneNumber"]},
|
||||||
|
{"EMAIL", "%s", ["mail"]},
|
||||||
|
{"BDAY", "%s", ["birthDay"]},
|
||||||
|
{"ROLE", "%s", ["employeeType"]},
|
||||||
|
{"PHOTO", "%s", ["jpegPhoto"]}]
|
||||||
|
\end{verbatim}
|
||||||
|
\titem{ldap\_search\_fields} \ind{options!ldap_search_fields}This option defines
|
||||||
|
search form and LDAP attributes to search.
|
||||||
|
Format:
|
||||||
|
\term{[{Name, Attribute}, ...]}
|
||||||
|
where
|
||||||
|
\term{Name} is the name of field in the search form. Will be automatically
|
||||||
|
translated according to definitions in translation files (see
|
||||||
|
\term{msgs/*.msg} for available words).
|
||||||
|
Attribute is the LDAP attribute or the pattern \term{"\%u"}
|
||||||
|
Example:
|
||||||
|
\begin{verbatim}
|
||||||
|
{ldap_search_fields,
|
||||||
|
[{"User", "uid"},
|
||||||
|
{"Full Name", "displayName"},
|
||||||
|
{"Email", "mail"}
|
||||||
|
]},
|
||||||
|
\end{verbatim}
|
||||||
|
|
||||||
|
Default is:
|
||||||
|
\begin{verbatim}
|
||||||
|
[{"User", "%u"},
|
||||||
|
{"Full Name", "displayName"},
|
||||||
|
{"Given Name", "givenName"},
|
||||||
|
{"Middle Name", "initials"},
|
||||||
|
{"Family Name", "sn"},
|
||||||
|
{"Nickname", "%u"},
|
||||||
|
{"Birthday", "birthDay"},
|
||||||
|
{"Country", "c"},
|
||||||
|
{"City", "l"},
|
||||||
|
{"Email", "mail"},
|
||||||
|
{"Organization Name", "o"},
|
||||||
|
{"Organization Unit", "ou"}]
|
||||||
|
\end{verbatim}
|
||||||
|
\titem{ldap\_search\_reported} \ind{options!ldap_search_reported}This option defines search fields to be reported.
|
||||||
|
Format:
|
||||||
|
\term{[{Name, VCard\_Name}, ...]}
|
||||||
|
where
|
||||||
|
\term{Name} is the name of field in the search form. Will be automatically
|
||||||
|
translated according to definitions in translation files (see
|
||||||
|
\term{msgs/*.msg} for available words).
|
||||||
|
\term{VCard\_Name} is the name of vCard field defined in \option{ldap\_vcard\_map} option.
|
||||||
|
Example:
|
||||||
|
\begin{verbatim}
|
||||||
|
{ldap_search_reported,
|
||||||
|
[{"Full Name", "FN"},
|
||||||
|
{"Email", "EMAIL"},
|
||||||
|
{"Birthday", "BDAY"},
|
||||||
|
{"Nickname", "NICKNAME"}
|
||||||
|
]},
|
||||||
|
\end{verbatim}
|
||||||
|
|
||||||
|
Default is:
|
||||||
|
\begin{verbatim}
|
||||||
|
[{"Full Name", "FN"},
|
||||||
|
{"Given Name", "GIVEN"},
|
||||||
|
{"Middle Name", "MIDDLE"},
|
||||||
|
{"Family Name", "FAMILY"},
|
||||||
|
{"Nickname", "NICKNAME"},
|
||||||
|
{"Birthday", "BDAY"},
|
||||||
|
{"Country", "CTRY"},
|
||||||
|
{"City", "LOCALITY"},
|
||||||
|
{"Email", "EMAIL"},
|
||||||
|
{"Organization Name", "ORGNAME"},
|
||||||
|
{"Organization Unit", "ORGUNIT"}]
|
||||||
|
\end{verbatim}
|
||||||
|
\end{description}
|
||||||
|
|
||||||
|
|
||||||
|
\subsubsection{Examples}
|
||||||
|
\label{sec:ldapexamples}
|
||||||
|
|
||||||
|
\paragraph{Common example}
|
||||||
|
|
||||||
|
Let's say \term{ldap.mydomain.org} is the name of our LDAP server. We have
|
||||||
|
users with their passwords in \term{"ou=Users,dc=mydomain,dc=org"} directory.
|
||||||
|
Also we have addressbook, which contains users emails and their additional
|
||||||
|
infos in \term{"ou=AddressBook,dc=mydomain,dc=org"} directory. Corresponding
|
||||||
|
authentication section should looks like this:
|
||||||
|
|
||||||
|
\begin{verbatim}
|
||||||
|
%% authentication method
|
||||||
|
{auth_method, ldap}.
|
||||||
|
%% DNS name of our LDAP server
|
||||||
|
{ldap_servers, ["ldap.mydomain.org"]}.
|
||||||
|
%% Bind to LDAP server as "cn=Manager,dc=mydomain,dc=org" with password "secret"
|
||||||
|
{ldap_rootdn, "cn=Manager,dc=mydomain,dc=org"}.
|
||||||
|
{ldap_password, "secret"}.
|
||||||
|
%% define the user's base
|
||||||
|
{ldap_base, "ou=Users,dc=mydomain,dc=org"}.
|
||||||
|
%% We want to authorize users from 'shadowAccount' object class only
|
||||||
|
{ldap_filter, "(objectClass=shadowAccount)"}.
|
||||||
|
\end{verbatim}
|
||||||
|
|
||||||
|
Now we want to use users LDAP-info as their vCards. We have four attributes
|
||||||
|
defined in our LDAP schema: \term{"mail"} --- email address, \term{"givenName"}
|
||||||
|
--- first name, \term{"sn"} --- second name, \term{"birthDay"} --- birthday.
|
||||||
|
Also we want users to search each other. Let's see how we can set it up:
|
||||||
|
|
||||||
|
\begin{verbatim}
|
||||||
|
{modules,
|
||||||
|
...
|
||||||
|
{mod_vcard_ldap,
|
||||||
|
[
|
||||||
|
%% We use the same server and port, but want to bind anonymously because
|
||||||
|
%% our LDAP server accepts anonymous requests to
|
||||||
|
%% "ou=AddressBook,dc=mydomain,dc=org" subtree.
|
||||||
|
{ldap_rootdn, ""},
|
||||||
|
{ldap_password, ""},
|
||||||
|
%% define the addressbook's base
|
||||||
|
{ldap_base, "ou=AddressBook,dc=mydomain,dc=org"},
|
||||||
|
%% user's part of JID is located in the "mail" attribute
|
||||||
|
{ldap_uidattr, "mail"},
|
||||||
|
%% common format for our emails
|
||||||
|
{ldap_uidattr_format, "%u@mail.mydomain.org"},
|
||||||
|
%% We have to define empty filter here, because entries in addressbook doesn't
|
||||||
|
%% belong to shadowAccount object class
|
||||||
|
{ldap_filter, ""},
|
||||||
|
%% Now we want to define vCard pattern
|
||||||
|
{ldap_vcard_map,
|
||||||
|
[{"NICKNAME", "%u", []}, % just use user's part of JID as his nickname
|
||||||
|
{"GIVEN", "%s", ["givenName"]},
|
||||||
|
{"FAMILY", "%s", ["sn"]},
|
||||||
|
{"FN", "%s, %s", ["sn", "givenName"]}, % example: "Smith, John"
|
||||||
|
{"EMAIL", "%s", ["mail"]},
|
||||||
|
{"BDAY", "%s", ["birthDay"]}]},
|
||||||
|
%% Search form
|
||||||
|
{ldap_search_fields,
|
||||||
|
[{"User", "%u"},
|
||||||
|
{"Name", "givenName"},
|
||||||
|
{"Family Name", "sn"},
|
||||||
|
{"Email", "mail"},
|
||||||
|
{"Birthday", "birthDay"}]},
|
||||||
|
%% vCard fields to be reported
|
||||||
|
%% Note that JID is always returned with search results
|
||||||
|
{ldap_search_reported,
|
||||||
|
[{"Full Name", "FN"},
|
||||||
|
{"Nickname", "NICKNAME"},
|
||||||
|
{"Birthday", "BDAY"}]}
|
||||||
|
]}
|
||||||
|
...
|
||||||
|
}.
|
||||||
|
\end{verbatim}
|
||||||
|
|
||||||
|
Note that \modvcardldap{} module checks an existence of the user before
|
||||||
|
searching his info in LDAP.
|
||||||
|
|
||||||
|
|
||||||
|
\paragraph{Active Directory}
|
||||||
|
|
||||||
|
Active Directory is just an LDAP-server with predefined attributes. Sample
|
||||||
|
config file is listed below:
|
||||||
|
|
||||||
|
\begin{verbatim}
|
||||||
|
{auth_method, ldap}.
|
||||||
|
{ldap_servers, ["office.org"]}. % List of LDAP servers
|
||||||
|
{ldap_base, "DC=office,DC=org"}. % Search base of LDAP directory
|
||||||
|
{ldap_rootdn, "CN=Administrator,CN=Users,DC=office,DC=org"}. % LDAP manager
|
||||||
|
{ldap_password, "*******"}. % Password to LDAP manager
|
||||||
|
{ldap_uidattr, "sAMAccountName"}.
|
||||||
|
{ldap_filter, "(memberOf=*)"}.
|
||||||
|
|
||||||
|
{mod_vcard_ldap,
|
||||||
|
[{ldap_vcard_map,
|
||||||
|
[{"NICKNAME", "%u", []},
|
||||||
|
{"GIVEN", "%s", ["givenName"]},
|
||||||
|
{"MIDDLE", "%s", ["initials"]},
|
||||||
|
{"FAMILY", "%s", ["sn"]},
|
||||||
|
{"FN", "%s", ["displayName"]},
|
||||||
|
{"EMAIL", "%s", ["mail"]},
|
||||||
|
{"ORGNAME", "%s", ["company"]},
|
||||||
|
{"ORGUNIT", "%s", ["department"]},
|
||||||
|
{"CTRY", "%s", ["c"]},
|
||||||
|
{"LOCALITY", "%s", ["l"]},
|
||||||
|
{"STREET", "%s", ["streetAddress"]},
|
||||||
|
{"REGION", "%s", ["st"]},
|
||||||
|
{"PCODE", "%s", ["postalCode"]},
|
||||||
|
{"TITLE", "%s", ["title"]},
|
||||||
|
{"URL", "%s", ["wWWHomePage"]},
|
||||||
|
{"DESC", "%s", ["description"]},
|
||||||
|
{"TEL", "%s", ["telephoneNumber"]}]},
|
||||||
|
{ldap_search_fields,
|
||||||
|
[{"User", "%u"},
|
||||||
|
{"Name", "givenName"},
|
||||||
|
{"Family Name", "sn"},
|
||||||
|
{"Email", "mail"},
|
||||||
|
{"Company", "company"},
|
||||||
|
{"Department", "department"},
|
||||||
|
{"Role", "title"},
|
||||||
|
{"Description", "description"},
|
||||||
|
{"Phone", "telephoneNumber"}]},
|
||||||
|
{ldap_search_reported,
|
||||||
|
[{"Full Name", "FN"},
|
||||||
|
{"Nickname", "NICKNAME"},
|
||||||
|
{"Email", "EMAIL"}]}
|
||||||
|
]
|
||||||
|
}.
|
||||||
|
\end{verbatim}
|
||||||
|
|
||||||
|
|
||||||
\subsection{\modversion{}}
|
\subsection{\modversion{}}
|
||||||
\label{sec:modversion}
|
\label{sec:modversion}
|
||||||
\ind{modules!\modversion{}}\ind{protocols!JEP-0092: Software Version}
|
\ind{modules!\modversion{}}\ind{protocols!JEP-0092: Software Version}
|
||||||
|
@ -10,8 +10,21 @@
|
|||||||
-author('alexey@sevcom.net').
|
-author('alexey@sevcom.net').
|
||||||
-vsn('$Revision$ ').
|
-vsn('$Revision$ ').
|
||||||
|
|
||||||
|
-behaviour(gen_server).
|
||||||
|
|
||||||
|
%% gen_server callbacks
|
||||||
|
-export([init/1,
|
||||||
|
handle_info/2,
|
||||||
|
handle_call/3,
|
||||||
|
handle_cast/2,
|
||||||
|
terminate/2,
|
||||||
|
code_change/3
|
||||||
|
]).
|
||||||
|
|
||||||
%% External exports
|
%% External exports
|
||||||
-export([start/1,
|
-export([start/1,
|
||||||
|
stop/1,
|
||||||
|
start_link/1,
|
||||||
set_password/3,
|
set_password/3,
|
||||||
check_password/3,
|
check_password/3,
|
||||||
check_password/5,
|
check_password/5,
|
||||||
@ -29,39 +42,84 @@
|
|||||||
-include("ejabberd.hrl").
|
-include("ejabberd.hrl").
|
||||||
-include("eldap/eldap.hrl").
|
-include("eldap/eldap.hrl").
|
||||||
|
|
||||||
|
-record(state, {host,
|
||||||
|
eldap_id,
|
||||||
|
servers,
|
||||||
|
port,
|
||||||
|
dn,
|
||||||
|
password,
|
||||||
|
base,
|
||||||
|
uidattr,
|
||||||
|
uidattr_format,
|
||||||
|
ufilter,
|
||||||
|
sfilter,
|
||||||
|
dn_filter,
|
||||||
|
dn_filter_attrs
|
||||||
|
}).
|
||||||
|
|
||||||
|
%% Unused callbacks.
|
||||||
|
handle_cast(_Request, State) ->
|
||||||
|
{noreply, State}.
|
||||||
|
code_change(_OldVsn, State, _Extra) ->
|
||||||
|
{ok, State}.
|
||||||
|
handle_info(_Info, State) ->
|
||||||
|
{noreply, State}.
|
||||||
|
%% -----
|
||||||
|
|
||||||
%%%----------------------------------------------------------------------
|
%%%----------------------------------------------------------------------
|
||||||
%%% API
|
%%% API
|
||||||
%%%----------------------------------------------------------------------
|
%%%----------------------------------------------------------------------
|
||||||
|
|
||||||
start(Host) ->
|
start(Host) ->
|
||||||
LDAPServers = ejabberd_config:get_local_option({ldap_servers, Host}),
|
Proc = gen_mod:get_module_proc(Host, ?MODULE),
|
||||||
RootDN = ejabberd_config:get_local_option({ldap_rootdn, Host}),
|
ChildSpec = {
|
||||||
Password = ejabberd_config:get_local_option({ldap_password, Host}),
|
Proc, {?MODULE, start_link, [Host]},
|
||||||
eldap:start_link(get_eldap_id(Host, ejabberd),
|
permanent, 1000, worker, [?MODULE]
|
||||||
LDAPServers, 389, RootDN, Password),
|
},
|
||||||
eldap:start_link(get_eldap_id(Host, ejabberd_bind),
|
supervisor:start_child(ejabberd_sup, ChildSpec).
|
||||||
LDAPServers, 389, RootDN, Password),
|
|
||||||
|
stop(Host) ->
|
||||||
|
Proc = gen_mod:get_module_proc(Host, ?MODULE),
|
||||||
|
gen_server:call(Proc, stop),
|
||||||
|
supervisor:terminate_child(ejabberd_sup, Proc),
|
||||||
|
supervisor:delete_child(ejabberd_sup, Proc).
|
||||||
|
|
||||||
|
start_link(Host) ->
|
||||||
|
Proc = gen_mod:get_module_proc(Host, ?MODULE),
|
||||||
|
gen_server:start_link({local, Proc}, ?MODULE, Host, []).
|
||||||
|
|
||||||
|
terminate(_Reason, State) ->
|
||||||
|
ejabberd_ctl:unregister_commands(
|
||||||
|
State#state.host,
|
||||||
|
[{"registered-users", "list all registered users"}],
|
||||||
|
ejabberd_auth, ctl_process_get_registered).
|
||||||
|
|
||||||
|
init(Host) ->
|
||||||
|
State = parse_options(Host),
|
||||||
|
eldap:start_link(State#state.eldap_id,
|
||||||
|
State#state.servers,
|
||||||
|
State#state.port,
|
||||||
|
State#state.dn,
|
||||||
|
State#state.password),
|
||||||
ejabberd_ctl:register_commands(
|
ejabberd_ctl:register_commands(
|
||||||
Host,
|
Host,
|
||||||
[{"registered-users", "list all registered users"}],
|
[{"registered-users", "list all registered users"}],
|
||||||
ejabberd_auth, ctl_process_get_registered),
|
ejabberd_auth, ctl_process_get_registered),
|
||||||
ok.
|
{ok, State}.
|
||||||
|
|
||||||
|
-define(REPLY_TIMEOUT, 10000).
|
||||||
|
|
||||||
plain_password_required() ->
|
plain_password_required() ->
|
||||||
true.
|
true.
|
||||||
|
|
||||||
check_password(User, Server, Password) ->
|
check_password(User, Server, Password) ->
|
||||||
case find_user_dn(User, Server) of
|
Proc = gen_mod:get_module_proc(Server, ?MODULE),
|
||||||
false ->
|
case catch gen_server:call(Proc,
|
||||||
|
{check_pass, User, Password}, ?REPLY_TIMEOUT) of
|
||||||
|
{'EXIT', _} ->
|
||||||
false;
|
false;
|
||||||
DN ->
|
Result ->
|
||||||
LServer = jlib:nameprep(Server),
|
Result
|
||||||
case eldap:bind(get_eldap_id(LServer, ejabberd_bind),
|
|
||||||
DN, Password) of
|
|
||||||
ok ->
|
|
||||||
true;
|
|
||||||
_ ->
|
|
||||||
false
|
|
||||||
end
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
check_password(User, Server, Password, _StreamID, _Digest) ->
|
check_password(User, Server, Password, _StreamID, _Digest) ->
|
||||||
@ -77,31 +135,13 @@ dirty_get_registered_users() ->
|
|||||||
get_vh_registered_users(?MYNAME).
|
get_vh_registered_users(?MYNAME).
|
||||||
|
|
||||||
get_vh_registered_users(Server) ->
|
get_vh_registered_users(Server) ->
|
||||||
LServer = jlib:nameprep(Server),
|
Proc = gen_mod:get_module_proc(Server, ?MODULE),
|
||||||
Attr = ejabberd_config:get_local_option({ldap_uidattr, LServer}),
|
case catch gen_server:call(Proc,
|
||||||
Filter = eldap:present(Attr),
|
get_vh_registered_users, ?REPLY_TIMEOUT) of
|
||||||
Base = ejabberd_config:get_local_option({ldap_base, LServer}),
|
{'EXIT', _} ->
|
||||||
case eldap:search(get_eldap_id(LServer, ejabberd),
|
[];
|
||||||
[{base, Base},
|
Result ->
|
||||||
{filter, Filter},
|
Result
|
||||||
{attributes, [Attr]}]) of
|
|
||||||
#eldap_search_result{entries = Es} ->
|
|
||||||
lists:flatmap(
|
|
||||||
fun(E) ->
|
|
||||||
case lists:keysearch(Attr, 1, E#eldap_entry.attributes) of
|
|
||||||
{value, {_, [U]}} ->
|
|
||||||
case jlib:nodeprep(U) of
|
|
||||||
error ->
|
|
||||||
[];
|
|
||||||
LU ->
|
|
||||||
[{LU, LServer}]
|
|
||||||
end;
|
|
||||||
_ ->
|
|
||||||
[]
|
|
||||||
end
|
|
||||||
end, Es);
|
|
||||||
_ ->
|
|
||||||
[]
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
get_password(_User, _Server) ->
|
get_password(_User, _Server) ->
|
||||||
@ -111,11 +151,13 @@ get_password_s(_User, _Server) ->
|
|||||||
"".
|
"".
|
||||||
|
|
||||||
is_user_exists(User, Server) ->
|
is_user_exists(User, Server) ->
|
||||||
case find_user_dn(User, Server) of
|
Proc = gen_mod:get_module_proc(Server, ?MODULE),
|
||||||
false ->
|
case catch gen_server:call(Proc,
|
||||||
|
{is_user_exists, User}, ?REPLY_TIMEOUT) of
|
||||||
|
{'EXIT', _} ->
|
||||||
false;
|
false;
|
||||||
_DN ->
|
Result ->
|
||||||
true
|
Result
|
||||||
end.
|
end.
|
||||||
|
|
||||||
remove_user(_User, _Server) ->
|
remove_user(_User, _Server) ->
|
||||||
@ -124,25 +166,212 @@ remove_user(_User, _Server) ->
|
|||||||
remove_user(_User, _Server, _Password) ->
|
remove_user(_User, _Server, _Password) ->
|
||||||
not_allowed.
|
not_allowed.
|
||||||
|
|
||||||
|
|
||||||
%%%----------------------------------------------------------------------
|
%%%----------------------------------------------------------------------
|
||||||
%%% Internal functions
|
%%% Internal functions
|
||||||
%%%----------------------------------------------------------------------
|
%%%----------------------------------------------------------------------
|
||||||
|
handle_call({check_pass, User, Password}, _From, State) ->
|
||||||
|
Reply = case find_user_dn(User, State) of
|
||||||
|
false ->
|
||||||
|
false;
|
||||||
|
DN ->
|
||||||
|
case eldap:bind(State#state.eldap_id, DN, Password) of
|
||||||
|
ok -> true;
|
||||||
|
_ -> false
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
{reply, Reply, State};
|
||||||
|
|
||||||
find_user_dn(User, Server) ->
|
handle_call(get_vh_registered_users, _From, State) ->
|
||||||
LServer = jlib:nameprep(Server),
|
UA = State#state.uidattr,
|
||||||
Attr = ejabberd_config:get_local_option({ldap_uidattr, LServer}),
|
UAF = State#state.uidattr_format,
|
||||||
Filter = eldap:equalityMatch(Attr, User),
|
Eldap_ID = State#state.eldap_id,
|
||||||
Base = ejabberd_config:get_local_option({ldap_base, LServer}),
|
Server = State#state.host,
|
||||||
case eldap:search(get_eldap_id(LServer, ejabberd),
|
SortedDNAttrs = usort_attrs(State#state.dn_filter_attrs),
|
||||||
[{base, Base},
|
Reply = case eldap_filter:parse(State#state.sfilter) of
|
||||||
{filter, Filter},
|
{ok, EldapFilter} ->
|
||||||
{attributes, []}]) of
|
case eldap:search(Eldap_ID, [{base, State#state.base},
|
||||||
#eldap_search_result{entries = [E | _]} ->
|
{filter, EldapFilter},
|
||||||
E#eldap_entry.object_name;
|
{attributes, SortedDNAttrs}]) of
|
||||||
|
#eldap_search_result{entries = Entries} ->
|
||||||
|
lists:flatmap(
|
||||||
|
fun(#eldap_entry{attributes = Attrs,
|
||||||
|
object_name = DN}) ->
|
||||||
|
case is_valid_dn(DN, Attrs, State) of
|
||||||
|
false -> [];
|
||||||
|
_ ->
|
||||||
|
case get_ldap_attr(UA, Attrs) of
|
||||||
|
"" -> [];
|
||||||
|
User ->
|
||||||
|
case get_user_part(User, UAF) of
|
||||||
|
{ok, U} ->
|
||||||
|
case jlib:nodeprep(U) of
|
||||||
|
error -> [];
|
||||||
|
LU -> [{LU, jlib:nameprep(Server)}]
|
||||||
|
end;
|
||||||
|
_ -> []
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end, Entries);
|
||||||
|
_ ->
|
||||||
|
[]
|
||||||
|
end;
|
||||||
|
_ ->
|
||||||
|
[]
|
||||||
|
end,
|
||||||
|
{reply, Reply, State};
|
||||||
|
|
||||||
|
handle_call({is_user_exists, User}, _From, State) ->
|
||||||
|
Reply = case find_user_dn(User, State) of
|
||||||
|
false -> false;
|
||||||
|
_DN -> true
|
||||||
|
end,
|
||||||
|
{reply, Reply, State};
|
||||||
|
|
||||||
|
handle_call(stop, _From, State) ->
|
||||||
|
{stop, normal, ok, State};
|
||||||
|
|
||||||
|
handle_call(_Request, _From, State) ->
|
||||||
|
{reply, bad_request, State}.
|
||||||
|
|
||||||
|
find_user_dn(User, State) ->
|
||||||
|
DNAttrs = usort_attrs(State#state.dn_filter_attrs),
|
||||||
|
case eldap_filter:parse(State#state.ufilter, [{"%u", User}]) of
|
||||||
|
{ok, Filter} ->
|
||||||
|
case eldap:search(State#state.eldap_id, [{base, State#state.base},
|
||||||
|
{filter, Filter},
|
||||||
|
{attributes, DNAttrs}]) of
|
||||||
|
#eldap_search_result{entries = [#eldap_entry{attributes = Attrs,
|
||||||
|
object_name = DN} | _]} ->
|
||||||
|
is_valid_dn(DN, Attrs, State);
|
||||||
|
_ ->
|
||||||
|
false
|
||||||
|
end;
|
||||||
_ ->
|
_ ->
|
||||||
false
|
false
|
||||||
end.
|
end.
|
||||||
|
|
||||||
get_eldap_id(Host, Name) ->
|
is_valid_dn(DN, _, #state{dn_filter = undefined}) ->
|
||||||
atom_to_list(gen_mod:get_module_proc(Host, Name)).
|
DN;
|
||||||
|
|
||||||
|
is_valid_dn(DN, Attrs, State) ->
|
||||||
|
DNAttrs = State#state.dn_filter_attrs,
|
||||||
|
UA = State#state.uidattr,
|
||||||
|
UAF = State#state.uidattr_format,
|
||||||
|
Values = [{"%s", get_ldap_attr(Attr, Attrs), 1} || Attr <- DNAttrs],
|
||||||
|
SubstValues = case get_ldap_attr(UA, Attrs) of
|
||||||
|
"" -> Values;
|
||||||
|
S ->
|
||||||
|
case get_user_part(S, UAF) of
|
||||||
|
{ok, U} -> [{"%u", U} | Values];
|
||||||
|
_ -> Values
|
||||||
|
end
|
||||||
|
end ++ [{"%d", State#state.host}, {"%D", DN}],
|
||||||
|
case eldap_filter:parse(State#state.dn_filter, SubstValues) of
|
||||||
|
{ok, EldapFilter} ->
|
||||||
|
case eldap:search(State#state.eldap_id, [
|
||||||
|
{base, State#state.base},
|
||||||
|
{filter, EldapFilter},
|
||||||
|
{attributes, ["dn"]}]) of
|
||||||
|
#eldap_search_result{entries = [_|_]} ->
|
||||||
|
DN;
|
||||||
|
_ ->
|
||||||
|
false
|
||||||
|
end;
|
||||||
|
_ ->
|
||||||
|
false
|
||||||
|
end.
|
||||||
|
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
%%% Auxiliary functions
|
||||||
|
%%%----------------------------------------------------------------------
|
||||||
|
get_user_part(String, Pattern) ->
|
||||||
|
F = fun(S, P) ->
|
||||||
|
First = string:str(P, "%u"),
|
||||||
|
TailLength = length(P) - (First+1),
|
||||||
|
string:sub_string(S, First, length(S) - TailLength)
|
||||||
|
end,
|
||||||
|
case catch F(String, Pattern) of
|
||||||
|
{'EXIT', _} ->
|
||||||
|
{error, badmatch};
|
||||||
|
Result ->
|
||||||
|
case regexp:sub(Pattern, "%u", Result) of
|
||||||
|
{ok, String, _} -> {ok, Result};
|
||||||
|
_ -> {error, badmatch}
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
case_insensitive_match(X, Y) ->
|
||||||
|
X1 = stringprep:tolower(X),
|
||||||
|
Y1 = stringprep:tolower(Y),
|
||||||
|
if
|
||||||
|
X1 == Y1 -> true;
|
||||||
|
true -> false
|
||||||
|
end.
|
||||||
|
|
||||||
|
get_ldap_attr(LDAPAttr, Attributes) ->
|
||||||
|
Res = lists:filter(
|
||||||
|
fun({Name, _}) ->
|
||||||
|
case_insensitive_match(Name, LDAPAttr)
|
||||||
|
end, Attributes),
|
||||||
|
case Res of
|
||||||
|
[{_, [Value|_]}] -> Value;
|
||||||
|
_ -> ""
|
||||||
|
end.
|
||||||
|
|
||||||
|
usort_attrs(Attrs) when is_list(Attrs) ->
|
||||||
|
lists:usort(Attrs);
|
||||||
|
|
||||||
|
usort_attrs(_) ->
|
||||||
|
[].
|
||||||
|
|
||||||
|
parse_options(Host) ->
|
||||||
|
Eldap_ID = atom_to_list(gen_mod:get_module_proc(Host, ?MODULE)),
|
||||||
|
LDAPServers = ejabberd_config:get_local_option({ldap_servers, Host}),
|
||||||
|
LDAPPort = case ejabberd_config:get_local_option({ldap_port, Host}) of
|
||||||
|
undefined -> 389;
|
||||||
|
P -> P
|
||||||
|
end,
|
||||||
|
RootDN = case ejabberd_config:get_local_option({ldap_rootdn, Host}) of
|
||||||
|
undefined -> "";
|
||||||
|
RDN -> RDN
|
||||||
|
end,
|
||||||
|
Password = case ejabberd_config:get_local_option({ldap_password, Host}) of
|
||||||
|
undefined -> "";
|
||||||
|
Pass -> Pass
|
||||||
|
end,
|
||||||
|
UIDAttr = case ejabberd_config:get_local_option({ldap_uidattr, Host}) of
|
||||||
|
undefined -> "uid";
|
||||||
|
UA -> UA
|
||||||
|
end,
|
||||||
|
UIDAttrFormat = case ejabberd_config:get_local_option({ldap_uidattr_format, Host}) of
|
||||||
|
undefined -> "%u";
|
||||||
|
UAF -> UAF
|
||||||
|
end,
|
||||||
|
SubFilter = "(" ++ UIDAttr ++ "=" ++ UIDAttrFormat ++ ")",
|
||||||
|
UserFilter = case ejabberd_config:get_local_option({ldap_filter, Host}) of
|
||||||
|
undefined -> SubFilter;
|
||||||
|
"" -> SubFilter;
|
||||||
|
F -> "(&" ++ SubFilter ++ F ++ ")"
|
||||||
|
end,
|
||||||
|
SearchFilter = eldap_filter:do_sub(UserFilter, [{"%u", "*"}]),
|
||||||
|
LDAPBase = ejabberd_config:get_local_option({ldap_base, Host}),
|
||||||
|
{DNFilter, DNFilterAttrs} =
|
||||||
|
case ejabberd_config:get_local_option({ldap_dn_filter, Host}) of
|
||||||
|
undefined -> {undefined, undefined};
|
||||||
|
{DNF, DNFA} -> {DNF, DNFA}
|
||||||
|
end,
|
||||||
|
#state{host = Host,
|
||||||
|
eldap_id = Eldap_ID,
|
||||||
|
servers = LDAPServers,
|
||||||
|
port = LDAPPort,
|
||||||
|
dn = RootDN,
|
||||||
|
password = Password,
|
||||||
|
base = LDAPBase,
|
||||||
|
uidattr = UIDAttr,
|
||||||
|
uidattr_format = UIDAttrFormat,
|
||||||
|
ufilter = UserFilter,
|
||||||
|
sfilter = SearchFilter,
|
||||||
|
dn_filter = DNFilter,
|
||||||
|
dn_filter_attrs = DNFilterAttrs
|
||||||
|
}.
|
||||||
|
@ -12,7 +12,8 @@ OUTDIR = ..
|
|||||||
EFLAGS = -I .. -pz ..
|
EFLAGS = -I .. -pz ..
|
||||||
OBJS = \
|
OBJS = \
|
||||||
$(OUTDIR)/eldap.beam \
|
$(OUTDIR)/eldap.beam \
|
||||||
$(OUTDIR)/ELDAPv3.beam
|
$(OUTDIR)/ELDAPv3.beam \
|
||||||
|
$(OUTDIR)/eldap_filter.beam
|
||||||
|
|
||||||
all: $(OBJS)
|
all: $(OBJS)
|
||||||
|
|
||||||
|
@ -6,7 +6,8 @@ EFLAGS = -I .. -pz ..
|
|||||||
|
|
||||||
OBJS = \
|
OBJS = \
|
||||||
$(OUTDIR)\eldap.beam \
|
$(OUTDIR)\eldap.beam \
|
||||||
$(OUTDIR)\ELDAPv3.beam
|
$(OUTDIR)\ELDAPv3.beam \
|
||||||
|
$(OUTDIR)\eldap_filter.beam
|
||||||
|
|
||||||
ALL : $(OBJS)
|
ALL : $(OBJS)
|
||||||
|
|
||||||
|
269
src/eldap/eldap_filter.erl
Normal file
269
src/eldap/eldap_filter.erl
Normal file
@ -0,0 +1,269 @@
|
|||||||
|
%%%====================================================
|
||||||
|
%%% File: eldap_filter.erl
|
||||||
|
%%% Purpose: Converts String Representation of
|
||||||
|
%%% LDAP Search Filter (RFC 2254)
|
||||||
|
%%% to eldap's representation of filter
|
||||||
|
%%% Author: Evgeniy Khramtsov <xramtsov@gmail.com>
|
||||||
|
%%% License: GPL
|
||||||
|
%%%====================================================
|
||||||
|
|
||||||
|
-module(eldap_filter).
|
||||||
|
-author('xram@jabber.ru').
|
||||||
|
|
||||||
|
%%%======================
|
||||||
|
%%% Export functions
|
||||||
|
%%%======================
|
||||||
|
|
||||||
|
-export([parse/1,
|
||||||
|
parse/2,
|
||||||
|
do_sub/2
|
||||||
|
]).
|
||||||
|
|
||||||
|
%%%-------------------------------------------------------------------------
|
||||||
|
%%% Arity: parse/1
|
||||||
|
%%% Function: parse(RFC2254_Filter) -> {ok, EldapFilter} |
|
||||||
|
%%% {error, bad_filter}
|
||||||
|
%%%
|
||||||
|
%%% RFC2254_Filter = string().
|
||||||
|
%%%
|
||||||
|
%%% Description: Converts String Representation of LDAP Search Filter (RFC 2254)
|
||||||
|
%%% to eldap's representation of filter.
|
||||||
|
%%%
|
||||||
|
%%% Example:
|
||||||
|
%%% > eldap_filter:parse("(&(!(uid<=100))(mail=*))").
|
||||||
|
%%%
|
||||||
|
%%% {ok,{'and',[{'not',{lessOrEqual,{'AttributeValueAssertion',"uid","100"}}},
|
||||||
|
%%% {present,"mail"}]}}
|
||||||
|
%%%-------------------------------------------------------------------------
|
||||||
|
parse(RFC2254_Filter) ->
|
||||||
|
parse(RFC2254_Filter, []).
|
||||||
|
|
||||||
|
%%%-------------------------------------------------------------------------
|
||||||
|
%%% Arity: parse/2
|
||||||
|
%%% Function: parse(RFC2254_Filter, [SubstValue |...]) ->
|
||||||
|
%%% {ok, EldapFilter} |
|
||||||
|
%%% {error, bad_filter} |
|
||||||
|
%%% {error, bad_regexp} |
|
||||||
|
%%% {error, max_substitute_recursion}
|
||||||
|
%%%
|
||||||
|
%%% SubstValue = {RegExp, Value} | {RegExp, Value, N},
|
||||||
|
%%% RFC2254_Filter = RegExp = Value = string(),
|
||||||
|
%%% N = integer().
|
||||||
|
%%%
|
||||||
|
%%% Description: The same as parse/1, but substitutes N or all occurences
|
||||||
|
%%% of RegExp with Value *after* parsing.
|
||||||
|
%%%
|
||||||
|
%%% Example:
|
||||||
|
%%% > eldap_filter:parse(
|
||||||
|
%%% "(|(mail=%u@%d)(jid=%u@%d))",
|
||||||
|
%%% [{"%u", "xramtsov"},{"%d","gmail.com"}]).
|
||||||
|
%%%
|
||||||
|
%%% {ok,{'or',[{equalityMatch,{'AttributeValueAssertion',
|
||||||
|
%%% "mail",
|
||||||
|
%%% "xramtsov@gmail.com"}},
|
||||||
|
%%% {equalityMatch,{'AttributeValueAssertion',
|
||||||
|
%%% "jid",
|
||||||
|
%%% "xramtsov@gmail.com"}}]}}
|
||||||
|
%%%--------------------------------------------------------------------------
|
||||||
|
parse(RFC2254_Filter, ListOfSubValues) ->
|
||||||
|
case catch convert_filter(parse_filter(RFC2254_Filter), ListOfSubValues) of
|
||||||
|
[EldapFilter] when is_tuple(EldapFilter) ->
|
||||||
|
{ok, EldapFilter};
|
||||||
|
{regexp, Error} ->
|
||||||
|
{error, Error};
|
||||||
|
_ ->
|
||||||
|
{error, bad_filter}
|
||||||
|
end.
|
||||||
|
|
||||||
|
%%%==========================
|
||||||
|
%%% Internal functions
|
||||||
|
%%%==========================
|
||||||
|
|
||||||
|
%%%----------------------
|
||||||
|
%%% split/1,4
|
||||||
|
%%%----------------------
|
||||||
|
split(Filter) ->
|
||||||
|
split(Filter, 0, [], []).
|
||||||
|
|
||||||
|
split([], _, _, Result) ->
|
||||||
|
Result;
|
||||||
|
|
||||||
|
split([H|T], Num, Rest, Result) ->
|
||||||
|
NewNum = case H of
|
||||||
|
$( -> Num + 1;
|
||||||
|
$) -> Num - 1;
|
||||||
|
_ -> Num
|
||||||
|
end,
|
||||||
|
if
|
||||||
|
NewNum == 0 ->
|
||||||
|
X = Rest++[H],
|
||||||
|
LenX = length(X),
|
||||||
|
if
|
||||||
|
LenX > 2 ->
|
||||||
|
split(T, 0, [], Result ++ [lists:sublist(X, 2, LenX-2)]);
|
||||||
|
true ->
|
||||||
|
split(T, 0, Rest, Result)
|
||||||
|
end;
|
||||||
|
true ->
|
||||||
|
split(T, NewNum, Rest++[H], Result)
|
||||||
|
end.
|
||||||
|
|
||||||
|
%%%-----------------------
|
||||||
|
%%% parse_filter/1
|
||||||
|
%%%-----------------------
|
||||||
|
parse_filter(Filter) ->
|
||||||
|
case Filter of
|
||||||
|
[$! | T] ->
|
||||||
|
{'not', parse_filter(T)};
|
||||||
|
[$| | T] ->
|
||||||
|
{'or', parse_filter(T)};
|
||||||
|
[$& | T] ->
|
||||||
|
{'and', parse_filter(T)};
|
||||||
|
[$( | _] ->
|
||||||
|
parse_filter(split(Filter));
|
||||||
|
[List | _] when is_list(List) ->
|
||||||
|
[parse_filter(X) || X <- Filter];
|
||||||
|
_ ->
|
||||||
|
Filter
|
||||||
|
end.
|
||||||
|
|
||||||
|
%%%--------------------
|
||||||
|
%%% convert_filter/2
|
||||||
|
%%%--------------------
|
||||||
|
convert_filter({'not', [Val | _]}, Replace) ->
|
||||||
|
eldap:'not'(convert_filter(Val, Replace));
|
||||||
|
|
||||||
|
convert_filter({'or', Vals}, Replace) ->
|
||||||
|
eldap:'or'([convert_filter(X, Replace) || X <- Vals]);
|
||||||
|
|
||||||
|
convert_filter({'and', Vals}, Replace) ->
|
||||||
|
eldap:'and'([convert_filter(X, Replace) || X <- Vals]);
|
||||||
|
|
||||||
|
convert_filter([H|_] = Filter, Replace) when is_integer(H) ->
|
||||||
|
parse_attr(Filter, Replace);
|
||||||
|
|
||||||
|
convert_filter(Filter, Replace) when is_list(Filter) ->
|
||||||
|
[convert_filter(X, Replace) || X <- Filter].
|
||||||
|
|
||||||
|
%%%-----------------
|
||||||
|
%%% parse_attr/2,3
|
||||||
|
%%%-----------------
|
||||||
|
parse_attr(Attr, ListOfSubValues) ->
|
||||||
|
{Action, [_|_] = Name, [_|_] = Value} = split_attribute(Attr),
|
||||||
|
parse_attr(Action, {Name, Value}, ListOfSubValues).
|
||||||
|
|
||||||
|
parse_attr(approx, {Name, Value}, ListOfSubValues) ->
|
||||||
|
NewValue = do_sub(Value, ListOfSubValues),
|
||||||
|
eldap:approxMatch(Name, NewValue);
|
||||||
|
|
||||||
|
parse_attr(greater, {Name, Value}, ListOfSubValues) ->
|
||||||
|
NewValue = do_sub(Value, ListOfSubValues),
|
||||||
|
eldap:greaterOrEqual(Name, NewValue);
|
||||||
|
|
||||||
|
parse_attr(less, {Name, Value}, ListOfSubValues) ->
|
||||||
|
NewValue = do_sub(Value, ListOfSubValues),
|
||||||
|
eldap:lessOrEqual(Name, NewValue);
|
||||||
|
|
||||||
|
parse_attr(equal, {Name, Value}, ListOfSubValues) ->
|
||||||
|
{ok, RegSList} = regexp:split(remove_extra_asterisks(Value), "[*]"),
|
||||||
|
Pattern = case [do_sub(X, ListOfSubValues) || X <- RegSList] of
|
||||||
|
[Head | Tail] when Tail /= [] ->
|
||||||
|
{Head, lists:sublist(Tail, length(Tail)-1), lists:last(Tail)};
|
||||||
|
R ->
|
||||||
|
R
|
||||||
|
end,
|
||||||
|
case Pattern of
|
||||||
|
[V] ->
|
||||||
|
eldap:equalityMatch(Name, V);
|
||||||
|
{[], [], []} ->
|
||||||
|
eldap:present(Name);
|
||||||
|
{"", Any, ""} ->
|
||||||
|
eldap:substrings(Name, [{any, X} || X<-Any]);
|
||||||
|
{H, Any, ""} ->
|
||||||
|
eldap:substrings(Name, [{initial, H}]++[{any, X} || X<-Any]);
|
||||||
|
{"", Any, T} ->
|
||||||
|
eldap:substrings(Name, [{any, X} || X<-Any]++[{final, T}]);
|
||||||
|
{H, Any, T} ->
|
||||||
|
eldap:substrings(Name, [{initial, H}]++[{any, X} || X<-Any]++[{final, T}])
|
||||||
|
end;
|
||||||
|
|
||||||
|
parse_attr(_, _, _) ->
|
||||||
|
false.
|
||||||
|
|
||||||
|
%%%--------------------
|
||||||
|
%%% do_sub/2,3
|
||||||
|
%%%--------------------
|
||||||
|
|
||||||
|
-define(MAX_RECURSION, 100).
|
||||||
|
|
||||||
|
do_sub(S, []) ->
|
||||||
|
S;
|
||||||
|
|
||||||
|
do_sub([], _) ->
|
||||||
|
[];
|
||||||
|
|
||||||
|
do_sub(S, [H | T]) ->
|
||||||
|
Result = do_sub(S, H, 1),
|
||||||
|
do_sub(Result, T).
|
||||||
|
|
||||||
|
do_sub(S, {RegExp, New}, Iter) ->
|
||||||
|
case regexp:sub(S, RegExp, New) of
|
||||||
|
{ok, NewS, 0} ->
|
||||||
|
NewS;
|
||||||
|
{ok, NewS, _} when Iter =< ?MAX_RECURSION ->
|
||||||
|
do_sub(NewS, {RegExp, New}, Iter+1);
|
||||||
|
{ok, _, _} when Iter > ?MAX_RECURSION ->
|
||||||
|
throw({regexp, max_substitute_recursion});
|
||||||
|
_ ->
|
||||||
|
throw({regexp, bad_regexp})
|
||||||
|
end;
|
||||||
|
|
||||||
|
do_sub(S, {_, _, N}, _) when N<1 ->
|
||||||
|
S;
|
||||||
|
|
||||||
|
do_sub(S, {RegExp, New, Times}, Iter) ->
|
||||||
|
case regexp:sub(S, RegExp, New) of
|
||||||
|
{ok, NewS, 0} ->
|
||||||
|
NewS;
|
||||||
|
{ok, NewS, _} when Iter < Times ->
|
||||||
|
do_sub(NewS, {RegExp, New, Times}, Iter+1);
|
||||||
|
{ok, NewS, _} ->
|
||||||
|
NewS;
|
||||||
|
_ ->
|
||||||
|
throw({regexp, bad_regexp})
|
||||||
|
end.
|
||||||
|
|
||||||
|
remove_extra_asterisks(String) ->
|
||||||
|
{Res, _} = lists:foldl(
|
||||||
|
fun(X, {Acc, Last}) ->
|
||||||
|
case X of
|
||||||
|
$* when Last==$* ->
|
||||||
|
{Acc, X};
|
||||||
|
_ ->
|
||||||
|
{Acc ++ [X], X}
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
{"", ""}, String),
|
||||||
|
Res.
|
||||||
|
|
||||||
|
split_attribute(String) ->
|
||||||
|
split_attribute(String, "", $0).
|
||||||
|
|
||||||
|
split_attribute([], _, _) ->
|
||||||
|
{error, "", ""};
|
||||||
|
|
||||||
|
split_attribute([H|Tail], Acc, Last) ->
|
||||||
|
case H of
|
||||||
|
$= when Last==$> ->
|
||||||
|
{greater, lists:sublist(Acc, 1, length(Acc)-1), Tail};
|
||||||
|
$= when Last==$< ->
|
||||||
|
{less, lists:sublist(Acc, 1, length(Acc)-1), Tail};
|
||||||
|
$= when Last==$~ ->
|
||||||
|
{approx, lists:sublist(Acc, 1, length(Acc)-1), Tail};
|
||||||
|
$= when Last==$: ->
|
||||||
|
{equal, lists:sublist(Acc, 1, length(Acc)-1), Tail};
|
||||||
|
$= ->
|
||||||
|
{equal, Acc, Tail};
|
||||||
|
_ ->
|
||||||
|
split_attribute(Tail, Acc++[H], H)
|
||||||
|
end.
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user