aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBadlop <badlop@process-one.net>2009-05-25 17:15:48 +0000
committerBadlop <badlop@process-one.net>2009-05-25 17:15:48 +0000
commit31aa201ee8ce0c016403916fee06684b15ff2829 (patch)
tree823df0c07130bae1192dccfa4f9c8c2b6c852f76
parentDecrease proxy65 buffer sizes to 8192 bytes. (diff)
Support LDAPS with TLS (EJAB-109)(thanks to Thomas Baden, Andy Harb, Sergei Golovan, Anton Podavalov)
SVN Revision: 2098
-rw-r--r--doc/guide.html17
-rw-r--r--doc/guide.tex17
-rw-r--r--src/ejabberd.cfg.example19
-rw-r--r--src/ejabberd_auth_ldap.erl15
-rw-r--r--src/eldap/eldap.erl107
-rw-r--r--src/eldap/eldap.hrl3
-rw-r--r--src/eldap/eldap_pool.erl6
-rw-r--r--src/mod_vcard_ldap.erl24
8 files changed, 159 insertions, 49 deletions
diff --git a/doc/guide.html b/doc/guide.html
index 5bbd6ea72..467a4dfe5 100644
--- a/doc/guide.html
+++ b/doc/guide.html
@@ -1568,12 +1568,15 @@ create accounts, change password or edit vCard that is stored in LDAP.</P><P> <A
</P><DL CLASS="description"><DT CLASS="dt-description">
<B><TT>ldap_servers</TT></B></DT><DD CLASS="dd-description"> List of IP addresses or DNS names of your
LDAP servers. This option is required.
+</DD><DT CLASS="dt-description"><B><TT>ldap_encrypt</TT></B></DT><DD CLASS="dd-description"> Type of connection encryption to the LDAP server.
+Allowed values are: <TT>none</TT>, <TT>tls</TT>.
+Note that STARTTLS is not supported.
+The default value is: <TT>none</TT>.
</DD><DT CLASS="dt-description"><B><TT>ldap_port</TT></B></DT><DD CLASS="dd-description"> Port to connect to your LDAP server.
-The initial default value is&#XA0;389, so it is used when nothing is set into the
-configuration file.
+The default port is&#XA0;389 if encryption is disabled; and 636 if encryption is enabled.
If you configure a value, it is stored in <TT>ejabberd</TT>&#X2019;s database.
Then, if you remove that value from the configuration file,
-the value previously stored in the database will be used instead of the default 389.
+the value previously stored in the database will be used instead of the default port.
</DD><DT CLASS="dt-description"><B><TT>ldap_rootdn</TT></B></DT><DD CLASS="dd-description"> Bind DN. The default value
is&#XA0;<TT>""</TT> which means &#X2018;anonymous connection&#X2019;.
</DD><DT CLASS="dt-description"><B><TT>ldap_password</TT></B></DT><DD CLASS="dd-description"> Bind password. The default
@@ -1628,14 +1631,18 @@ Example values:
<H5 CLASS="paragraph"><!--SEC ANCHOR --><A HREF="#ldapcommonexample">Common example</A></H5><!--SEC END --><P> <A NAME="ldapcommonexample"></A> </P><P>Let&#X2019;s say <TT>ldap.example.org</TT> is the name of our LDAP server. We have
users with their passwords in <TT>"ou=Users,dc=example,dc=org"</TT> directory.
Also we have addressbook, which contains users emails and their additional
-infos in <TT>"ou=AddressBook,dc=example,dc=org"</TT> directory. Corresponding
-authentication section should looks like this:</P><PRE CLASS="verbatim">%% Authentication method
+infos in <TT>"ou=AddressBook,dc=example,dc=org"</TT> directory.
+The connection to the LDAP server is encrypted using TLS,
+and using the custom port 6123.
+Corresponding authentication section should looks like this:</P><PRE CLASS="verbatim">%% Authentication method
{auth_method, ldap}.
%% DNS name of our LDAP server
{ldap_servers, ["ldap.example.org"]}.
%% Bind to LDAP server as "cn=Manager,dc=example,dc=org" with password "secret"
{ldap_rootdn, "cn=Manager,dc=example,dc=org"}.
{ldap_password, "secret"}.
+{ldap_encrypt, tls}.
+{ldap_port, 6123}.
%% Define the user's base
{ldap_base, "ou=Users,dc=example,dc=org"}.
%% We want to authorize users from 'shadowAccount' object class only
diff --git a/doc/guide.tex b/doc/guide.tex
index d8717f186..e496e6b94 100644
--- a/doc/guide.tex
+++ b/doc/guide.tex
@@ -2105,12 +2105,15 @@ Parameters:
\begin{description}
\titem{ldap\_servers} \ind{options!ldap\_server}List of IP addresses or DNS names of your
LDAP servers. This option is required.
+\titem{ldap\_encrypt} \ind{options!ldap\_encrypt}Type of connection encryption to the LDAP server.
+Allowed values are: \term{none}, \term{tls}.
+Note that STARTTLS is not supported.
+The default value is: \term{none}.
\titem{ldap\_port} \ind{options!ldap\_port}Port to connect to your LDAP server.
- The initial default value is~389, so it is used when nothing is set into the
-configuration file.
+The default port is~389 if encryption is disabled; and 636 if encryption is enabled.
If you configure a value, it is stored in \ejabberd{}'s database.
Then, if you remove that value from the configuration file,
-the value previously stored in the database will be used instead of the default 389.
+the value previously stored in the database will be used instead of the default port.
\titem{ldap\_rootdn} \ind{options!ldap\_rootdn}Bind DN. The default value
is~\term{""} which means `anonymous connection'.
\titem{ldap\_password} \ind{options!ldap\_password}Bind password. The default
@@ -2185,8 +2188,10 @@ You can authenticate users against an LDAP directory. Available options are:
Let's say \term{ldap.example.org} is the name of our LDAP server. We have
users with their passwords in \term{"ou=Users,dc=example,dc=org"} directory.
Also we have addressbook, which contains users emails and their additional
-infos in \term{"ou=AddressBook,dc=example,dc=org"} directory. Corresponding
-authentication section should looks like this:
+infos in \term{"ou=AddressBook,dc=example,dc=org"} directory.
+The connection to the LDAP server is encrypted using TLS,
+and using the custom port 6123.
+Corresponding authentication section should looks like this:
\begin{verbatim}
%% Authentication method
@@ -2196,6 +2201,8 @@ authentication section should looks like this:
%% Bind to LDAP server as "cn=Manager,dc=example,dc=org" with password "secret"
{ldap_rootdn, "cn=Manager,dc=example,dc=org"}.
{ldap_password, "secret"}.
+{ldap_encrypt, tls}.
+{ldap_port, 6123}.
%% Define the user's base
{ldap_base, "ou=Users,dc=example,dc=org"}.
%% We want to authorize users from 'shadowAccount' object class only
diff --git a/src/ejabberd.cfg.example b/src/ejabberd.cfg.example
index 26e3f51f6..e53f187ba 100644
--- a/src/ejabberd.cfg.example
+++ b/src/ejabberd.cfg.example
@@ -235,17 +235,28 @@
%% List of LDAP servers:
%%{ldap_servers, ["localhost"]}.
%%
-%% LDAP attribute that holds user ID:
-%%{ldap_uids, [{"mail", "%u@mail.example.org"}]}.
+%% Encryption of connection to LDAP servers:
+%%{ldap_encrypt, none}.
+%%{ldap_encrypt, tls}.
%%
-%% Search base of LDAP directory:
-%%{ldap_base, "dc=example,dc=com"}.
+%% Port connect to LDAP servers:
+%%{ldap_port, 389}.
+%%{ldap_port, 636}.
%%
%% LDAP manager:
%%{ldap_rootdn, "dc=example,dc=com"}.
%%
%% Password to LDAP manager:
%%{ldap_password, "******"}.
+%%
+%% Search base of LDAP directory:
+%%{ldap_base, "dc=example,dc=com"}.
+%%
+%% LDAP attribute that holds user ID:
+%%{ldap_uids, [{"mail", "%u@mail.example.org"}]}.
+%%
+%% LDAP filter:
+%%{ldap_filter, "(objectClass=shadowAccount)"}.
%%
%% Anonymous login support:
diff --git a/src/ejabberd_auth_ldap.erl b/src/ejabberd_auth_ldap.erl
index ea3de540a..2b97bd793 100644
--- a/src/ejabberd_auth_ldap.erl
+++ b/src/ejabberd_auth_ldap.erl
@@ -66,6 +66,7 @@
servers,
backups,
port,
+ encrypt,
dn,
password,
base,
@@ -122,13 +123,15 @@ init(Host) ->
State#state.backups,
State#state.port,
State#state.dn,
- State#state.password),
+ State#state.password,
+ State#state.encrypt),
eldap_pool:start_link(State#state.bind_eldap_id,
State#state.servers,
State#state.backups,
State#state.port,
State#state.dn,
- State#state.password),
+ State#state.password,
+ State#state.encrypt),
{ok, State}.
plain_password_required() ->
@@ -354,8 +357,13 @@ parse_options(Host) ->
undefined -> [];
Backups -> Backups
end,
+ LDAPEncrypt = ejabberd_config:get_local_option({ldap_encrypt, Host}),
LDAPPort = case ejabberd_config:get_local_option({ldap_port, Host}) of
- undefined -> 389;
+ undefined -> case LDAPEncrypt of
+ tls -> ?LDAPS_PORT;
+ starttls -> ?LDAP_PORT;
+ _ -> ?LDAP_PORT
+ end;
P -> P
end,
RootDN = case ejabberd_config:get_local_option({ldap_rootdn, Host}) of
@@ -390,6 +398,7 @@ parse_options(Host) ->
servers = LDAPServers,
backups = LDAPBackups,
port = LDAPPort,
+ encrypt = LDAPEncrypt,
dn = RootDN,
password = Password,
base = LDAPBase,
diff --git a/src/eldap/eldap.erl b/src/eldap/eldap.erl
index 6a16b9b54..3c3e736ae 100644
--- a/src/eldap/eldap.erl
+++ b/src/eldap/eldap.erl
@@ -42,6 +42,12 @@
%%% Modified by Mickael Remond <mremond@process-one.net>
%%% Now use ejabberd log mechanism
+%%% Modified by:
+%%% Thomas Baden <roo@ham9.net> 2008 April 6th
+%%% Andy Harb <Ahmad.N.Abou-Harb@jpl.nasa.gov> 2008 April 28th
+%%% Anton Podavalov <a.podavalov@gmail.com> 2009 February 22th
+%%% Added LDAPS support, modeled off jungerl eldap.erl version.
+%%% NOTICE: STARTTLS is not supported.
%%% --------------------------------------------------------------------
-vc('$Id$ ').
@@ -61,7 +67,7 @@
-include("ejabberd.hrl").
%% External exports
--export([start_link/1, start_link/5]).
+-export([start_link/1, start_link/6]).
-export([baseObject/0,singleLevel/0,wholeSubtree/0,close/1,
equalityMatch/2,greaterOrEqual/2,lessOrEqual/2,
@@ -94,10 +100,17 @@
%% Grace period after "soft" LDAP bind errors:
-define(GRACEFUL_RETRY_TIMEOUT, 5000).
+-define(SUPPORTEDEXTENSION, "1.3.6.1.4.1.1466.101.120.7").
+-define(SUPPORTEDEXTENSIONSYNTAX, "1.3.6.1.4.1.1466.115.121.1.38").
+-define(STARTTLS, "1.3.6.1.4.1.1466.20037").
+
-record(eldap, {version = ?LDAP_VERSION,
hosts, % Possible hosts running LDAP servers
host = null, % Connected Host LDAP server
port = 389, % The LDAP server port
+ sockmod, % SockMod (gen_tcp|tls)
+ tls = none, % LDAP/LDAPS (none|starttls|tls)
+ tls_options = [],
fd = null, % Socket filedescriptor.
rootdn = "", % Name of the entry to bind as
passwd, % Password for (above) entry
@@ -114,9 +127,9 @@ 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) ->
+start_link(Name, Hosts, Port, Rootdn, Passwd, Encrypt) ->
Reg_name = list_to_atom("eldap_" ++ Name),
- gen_fsm:start_link({local, Reg_name}, ?MODULE, {Hosts, Port, Rootdn, Passwd}, []).
+ gen_fsm:start_link({local, Reg_name}, ?MODULE, {Hosts, Port, Rootdn, Passwd, Encrypt}, []).
%%% --------------------------------------------------------------------
%%% Get status of connection.
@@ -380,16 +393,34 @@ get_handle(Name) when is_list(Name) -> list_to_atom("eldap_" ++ Name).
%%----------------------------------------------------------------------
init([]) ->
case get_config() of
- {ok, Hosts, Rootdn, Passwd} ->
- init({Hosts, Rootdn, Passwd});
+ {ok, Hosts, Rootdn, Passwd, Encrypt} ->
+ init({Hosts, Rootdn, Passwd, Encrypt});
{error, Reason} ->
{stop, Reason}
end;
-init({Hosts, Port, Rootdn, Passwd}) ->
+init({Hosts, Port, Rootdn, Passwd, Encrypt}) ->
+ catch ssl:start(),
+ {X1,X2,X3} = erlang:now(),
+ ssl:seed(integer_to_list(X1) ++ integer_to_list(X2) ++ integer_to_list(X3)),
+ PortTemp = case Port of
+ undefined ->
+ case Encrypt of
+ tls ->
+ ?LDAPS_PORT;
+ starttls ->
+ ?LDAP_PORT;
+ _ ->
+ ?LDAP_PORT
+ end;
+ PT -> PT
+ end,
+ TLSOpts = [verify_none],
{ok, connecting, #eldap{hosts = Hosts,
- port = Port,
+ port = PortTemp,
rootdn = Rootdn,
passwd = Passwd,
+ tls = Encrypt,
+ tls_options = TLSOpts,
id = 0,
dict = dict:new(),
req_q = queue:new()}, 0}.
@@ -438,7 +469,7 @@ active(Event, From, S) ->
%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
handle_event(close, _StateName, S) ->
- catch gen_tcp:close(S#eldap.fd),
+ catch (S#eldap.sockmod):close(S#eldap.fd),
{stop, normal, S};
handle_event(_Event, StateName, S) ->
@@ -467,11 +498,13 @@ handle_sync_event(_Event, _From, StateName, S) ->
%%
%% Packets arriving in various states
%%
-handle_info({tcp, _Socket, Data}, connecting, S) ->
+handle_info({Tag, _Socket, Data}, connecting, S)
+ when Tag == tcp; Tag == ssl ->
?DEBUG("tcp packet received when disconnected!~n~p", [Data]),
{next_state, connecting, S};
-handle_info({tcp, _Socket, Data}, wait_bind_response, S) ->
+handle_info({Tag, _Socket, Data}, wait_bind_response, S)
+ when Tag == tcp; Tag == ssl ->
cancel_timer(S#eldap.bind_timer),
case catch recvd_wait_bind_response(Data, S) of
bound ->
@@ -487,8 +520,9 @@ handle_info({tcp, _Socket, Data}, wait_bind_response, S) ->
{next_state, connecting, close_and_retry(S)}
end;
-handle_info({tcp, _Socket, Data}, StateName, S)
- when StateName == active orelse StateName == active_bind ->
+handle_info({Tag, _Socket, Data}, StateName, S)
+ when (StateName == active orelse StateName == active_bind) andalso
+ (Tag == tcp orelse Tag == ssl) ->
case catch recvd_packet(Data, S) of
{response, Response, RequestType} ->
NewS = case Response of
@@ -509,12 +543,14 @@ handle_info({tcp, _Socket, Data}, StateName, S)
{next_state, StateName, S}
end;
-handle_info({tcp_closed, _Socket}, Fsm_state, S) ->
+handle_info({Tag, _Socket}, Fsm_state, S)
+ when Tag == tcp_closed; Tag == ssl_closed ->
?WARNING_MSG("LDAP server closed the connection: ~s:~p~nIn State: ~p",
[S#eldap.host, S#eldap.port ,Fsm_state]),
{next_state, connecting, close_and_retry(S)};
-handle_info({tcp_error, _Socket, Reason}, Fsm_state, S) ->
+handle_info({Tag, _Socket, Reason}, Fsm_state, S)
+ when Tag == tcp_error; Tag == ssl_error ->
?DEBUG("eldap received tcp_error: ~p~nIn State: ~p", [Reason, Fsm_state]),
{next_state, connecting, close_and_retry(S)};
@@ -597,7 +633,7 @@ send_command(Command, From, S) ->
protocolOp = {Name, Request}},
?DEBUG("~p~n",[{Name, Request}]),
{ok, Bytes} = asn1rt:encode('ELDAPv3', 'LDAPMessage', Message),
- case gen_tcp:send(S#eldap.fd, Bytes) of
+ case (S#eldap.sockmod):send(S#eldap.fd, Bytes) of
ok ->
Timer = erlang:start_timer(?CMD_TIMEOUT, self(), {cmd_timeout, Id}),
New_dict = dict:store(Id, [{Timer, Command, From, Name}], S#eldap.dict),
@@ -796,7 +832,7 @@ check_tag(Data) ->
end.
close_and_retry(S, Timeout) ->
- catch gen_tcp:close(S#eldap.fd),
+ catch (S#eldap.sockmod):close(S#eldap.fd),
Queue = dict:fold(
fun(_Id, [{Timer, Command, From, _Name}|_], Q) ->
cancel_timer(Timer),
@@ -863,16 +899,28 @@ polish([], Res, Ref) ->
%%-----------------------------------------------------------------------
connect_bind(S) ->
Host = next_host(S#eldap.host, S#eldap.hosts),
- TcpOpts = [{packet, asn1}, {active, true}, {keepalive, true},
- {send_timeout, ?SEND_TIMEOUT}, binary],
?INFO_MSG("LDAP connection on ~s:~p", [Host, S#eldap.port]),
- case gen_tcp:connect(Host, S#eldap.port, TcpOpts) of
+ SocketData = case S#eldap.tls of
+ tls ->
+ SockMod = ssl,
+ SslOpts = [{packet, asn1}, {active, true}, {keepalive, true},
+ binary],
+ ssl:connect(Host, S#eldap.port, SslOpts);
+ %% starttls -> %% TODO: Implement STARTTLS;
+ _ ->
+ SockMod = gen_tcp,
+ TcpOpts = [{packet, asn1}, {active, true}, {keepalive, true},
+ {send_timeout, ?SEND_TIMEOUT}, binary],
+ gen_tcp:connect(Host, S#eldap.port, TcpOpts)
+ end,
+ case SocketData of
{ok, Socket} ->
- case bind_request(Socket, S) of
+ case bind_request(Socket, S#eldap{sockmod = SockMod}) of
{ok, NewS} ->
Timer = erlang:start_timer(?BIND_TIMEOUT, self(),
{timeout, bind_timeout}),
{ok, wait_bind_response, NewS#eldap{fd = Socket,
+ sockmod = SockMod,
host = Host,
bind_timer = Timer}};
{error, Reason} ->
@@ -896,7 +944,7 @@ bind_request(Socket, S) ->
protocolOp = {bindRequest, Req}},
?DEBUG("Bind Request Message:~p~n",[Message]),
{ok, Bytes} = asn1rt:encode('ELDAPv3', 'LDAPMessage', Message),
- case gen_tcp:send(Socket, Bytes) of
+ case (S#eldap.sockmod):send(Socket, Bytes) of
ok -> {ok, S#eldap{id = Id}};
Error -> Error
end.
@@ -970,8 +1018,8 @@ get_config() ->
case file:consult(File) of
{ok, Entries} ->
case catch parse(Entries) of
- {ok, Hosts, Port, Rootdn, Passwd} ->
- {ok, Hosts, Port, Rootdn, Passwd};
+ {ok, Hosts, Port, Rootdn, Passwd, Encrypt} ->
+ {ok, Hosts, Port, Rootdn, Passwd, Encrypt};
{error, Reason} ->
{error, Reason};
{'EXIT', Reason} ->
@@ -986,7 +1034,8 @@ parse(Entries) ->
get_hosts(host, Entries),
get_integer(port, Entries),
get_list(rootdn, Entries),
- get_list(passwd, Entries)}.
+ get_list(passwd, Entries),
+ get_atom(encrypt, Entries)}.
get_integer(Key, List) ->
case lists:keysearch(Key, 1, List) of
@@ -1008,6 +1057,16 @@ get_list(Key, List) ->
throw({error, "No Entry in Config for " ++ atom_to_list(Key)})
end.
+get_atom(Key, List) ->
+ case lists:keysearch(Key, 1, List) of
+ {value, {Key, Value}} when atom(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_hosts(Key, List) ->
lists:map(fun({Key1, {A,B,C,D}}) when is_integer(A),
is_integer(B),
diff --git a/src/eldap/eldap.hrl b/src/eldap/eldap.hrl
index 5ffc464c9..95aa6f9c5 100644
--- a/src/eldap/eldap.hrl
+++ b/src/eldap/eldap.hrl
@@ -19,6 +19,9 @@
%%%
%%%----------------------------------------------------------------------
+-define(LDAP_PORT, 389).
+-define(LDAPS_PORT, 636).
+
-record(eldap_search, {scope = wholeSubtree,
base = [],
filter,
diff --git a/src/eldap/eldap_pool.erl b/src/eldap/eldap_pool.erl
index f714129b5..d7f3acfab 100644
--- a/src/eldap/eldap_pool.erl
+++ b/src/eldap/eldap_pool.erl
@@ -29,7 +29,7 @@
%% API
-export([
- start_link/6,
+ start_link/7,
bind/3,
search/2
]).
@@ -45,12 +45,12 @@ bind(PoolName, DN, Passwd) ->
search(PoolName, Opts) ->
do_request(PoolName, {search, [Opts]}).
-start_link(Name, Hosts, Backups, Port, Rootdn, Passwd) ->
+start_link(Name, Hosts, Backups, Port, Rootdn, Passwd, Encrypt) ->
PoolName = make_id(Name),
pg2:create(PoolName),
lists:foreach(fun(Host) ->
ID = erlang:ref_to_list(make_ref()),
- case catch eldap:start_link(ID, [Host|Backups], Port, Rootdn, Passwd) of
+ case catch eldap:start_link(ID, [Host|Backups], Port, Rootdn, Passwd, Encrypt) of
{ok, Pid} ->
pg2:join(PoolName, Pid);
_ ->
diff --git a/src/mod_vcard_ldap.erl b/src/mod_vcard_ldap.erl
index 7de172d1c..68faa97da 100644
--- a/src/mod_vcard_ldap.erl
+++ b/src/mod_vcard_ldap.erl
@@ -62,6 +62,7 @@
servers,
backups,
port,
+ encrypt,
dn,
base,
password,
@@ -179,7 +180,8 @@ init([Host, Opts]) ->
State#state.backups,
State#state.port,
State#state.dn,
- State#state.password),
+ State#state.password,
+ State#state.encrypt),
case State#state.search of
true ->
ejabberd_router:register_route(State#state.myhost);
@@ -673,11 +675,22 @@ parse_options(Host, Opts) ->
ejabberd_config:get_local_option({ldap_servers, Host});
Backups -> Backups
end,
- LDAPPort = case gen_mod:get_opt(ldap_port, Opts, undefined) of
+ LDAPEncrypt = case gen_mod:get_opt(ldap_encrypt, Opts, undefined) of
+ undefined ->
+ ejabberd_config:get_local_option({ldap_encrypt, Host});
+ E -> E
+ end,
+ LDAPPortTemp = case gen_mod:get_opt(ldap_port, Opts, undefined) of
+ undefined ->
+ ejabberd_config:get_local_option({ldap_port, Host});
+ PT -> PT
+ end,
+ LDAPPort = case LDAPPortTemp of
undefined ->
- case ejabberd_config:get_local_option({ldap_port, Host}) of
- undefined -> 389;
- P -> P
+ case LDAPEncrypt of
+ tls -> ?LDAPS_PORT;
+ starttls -> ?LDAP_PORT;
+ _ -> ?LDAP_PORT
end;
P -> P
end,
@@ -747,6 +760,7 @@ parse_options(Host, Opts) ->
servers = LDAPServers,
backups = LDAPBackups,
port = LDAPPort,
+ encrypt = LDAPEncrypt,
dn = RootDN,
base = LDAPBase,
password = Password,