2003-11-23 21:11:21 +01:00
|
|
|
-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
|
|
|
|
%%%
|
2007-12-24 14:57:53 +01:00
|
|
|
%%% Copyright (C) 2000 Torbjorn Tornkvist, tnt@home.se
|
2003-11-23 21:11:21 +01:00
|
|
|
%%%
|
|
|
|
%%% 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>
|
2007-01-27 17:40:37 +01:00
|
|
|
|
|
|
|
%%% Modified by Evgeniy Khramtsov <xram@jabber.ru>
|
|
|
|
%%% Implemented queue for bind() requests to prevent pending binds.
|
2008-03-20 17:28:36 +01:00
|
|
|
|
|
|
|
%%% Modified by Christophe Romain <christophe.romain@process-one.net>
|
|
|
|
%%% Improve error case handling
|
|
|
|
|
|
|
|
%%% Modified by Mickael Remond <mremond@process-one.net>
|
|
|
|
%%% Now use ejabberd log mechanism
|
|
|
|
|
2009-05-25 19:15:48 +02:00
|
|
|
%%% 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.
|
2008-03-20 17:28:36 +01:00
|
|
|
|
2003-11-23 21:11:21 +01:00
|
|
|
%%% --------------------------------------------------------------------
|
|
|
|
-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
|
2007-01-27 17:40:37 +01:00
|
|
|
%%% active_bind - sent bind() request and waiting for response
|
2003-11-23 21:11:21 +01:00
|
|
|
%%%----------------------------------------------------------------------
|
|
|
|
|
|
|
|
-behaviour(gen_fsm).
|
|
|
|
|
2008-03-20 17:28:36 +01:00
|
|
|
-include("ejabberd.hrl").
|
|
|
|
|
2003-11-23 21:11:21 +01:00
|
|
|
%% External exports
|
2009-05-25 19:15:48 +02:00
|
|
|
-export([start_link/1, start_link/6]).
|
2003-11-23 21:11:21 +01:00
|
|
|
|
|
|
|
-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]).
|
2008-03-20 17:28:36 +01:00
|
|
|
-export([get_status/1]).
|
2003-11-23 21:11:21 +01:00
|
|
|
|
|
|
|
%% gen_fsm callbacks
|
|
|
|
-export([init/1, connecting/2,
|
2007-01-27 17:40:37 +01:00
|
|
|
connecting/3, wait_bind_response/3, active/3, active_bind/3, handle_event/3,
|
2003-11-23 21:11:21 +01:00
|
|
|
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).
|
2008-03-20 17:28:36 +01:00
|
|
|
-define(RETRY_TIMEOUT, 500).
|
2003-11-23 21:11:21 +01:00
|
|
|
-define(BIND_TIMEOUT, 10000).
|
2007-01-27 17:40:37 +01:00
|
|
|
-define(CMD_TIMEOUT, 100000).
|
2009-01-27 14:24:18 +01:00
|
|
|
%% Used in gen_fsm sync calls.
|
|
|
|
-define(CALL_TIMEOUT, ?CMD_TIMEOUT + ?BIND_TIMEOUT + ?RETRY_TIMEOUT).
|
|
|
|
%% Used as a timeout for gen_tcp:send/2
|
|
|
|
-define(SEND_TIMEOUT, 30000).
|
2003-11-23 21:11:21 +01:00
|
|
|
-define(MAX_TRANSACTION_ID, 65535).
|
|
|
|
-define(MIN_TRANSACTION_ID, 0).
|
2009-05-16 15:18:15 +02:00
|
|
|
%% Grace period after "soft" LDAP bind errors:
|
|
|
|
-define(GRACEFUL_RETRY_TIMEOUT, 5000).
|
2003-11-23 21:11:21 +01:00
|
|
|
|
2009-05-25 19:15:48 +02:00
|
|
|
-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").
|
|
|
|
|
2003-11-23 21:11:21 +01:00
|
|
|
-record(eldap, {version = ?LDAP_VERSION,
|
2008-03-20 17:28:36 +01:00
|
|
|
hosts, % Possible hosts running LDAP servers
|
2007-01-27 17:40:37 +01:00
|
|
|
host = null, % Connected Host LDAP server
|
2008-03-20 17:28:36 +01:00
|
|
|
port = 389, % The LDAP server port
|
2009-05-25 19:15:48 +02:00
|
|
|
sockmod, % SockMod (gen_tcp|tls)
|
|
|
|
tls = none, % LDAP/LDAPS (none|starttls|tls)
|
|
|
|
tls_options = [],
|
2007-01-27 17:40:37 +01:00
|
|
|
fd = null, % Socket filedescriptor.
|
|
|
|
rootdn = "", % Name of the entry to bind as
|
|
|
|
passwd, % Password for (above) entry
|
|
|
|
id = 0, % LDAP Request ID
|
|
|
|
bind_timer, % Ref to bind timeout
|
|
|
|
dict, % dict holding operation params and results
|
2009-01-27 14:24:18 +01:00
|
|
|
req_q % Queue for requests
|
2008-03-20 17:28:36 +01:00
|
|
|
}).
|
2003-11-23 21:11:21 +01:00
|
|
|
|
|
|
|
%%%----------------------------------------------------------------------
|
|
|
|
%%% API
|
|
|
|
%%%----------------------------------------------------------------------
|
|
|
|
start_link(Name) ->
|
|
|
|
Reg_name = list_to_atom("eldap_" ++ Name),
|
|
|
|
gen_fsm:start_link({local, Reg_name}, ?MODULE, [], []).
|
|
|
|
|
2009-05-25 19:15:48 +02:00
|
|
|
start_link(Name, Hosts, Port, Rootdn, Passwd, Encrypt) ->
|
2003-11-23 21:11:21 +01:00
|
|
|
Reg_name = list_to_atom("eldap_" ++ Name),
|
2009-05-25 19:15:48 +02:00
|
|
|
gen_fsm:start_link({local, Reg_name}, ?MODULE, {Hosts, Port, Rootdn, Passwd, Encrypt}, []).
|
2003-11-23 21:11:21 +01:00
|
|
|
|
|
|
|
%%% --------------------------------------------------------------------
|
|
|
|
%%% 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"]}]
|
|
|
|
%%% )
|
|
|
|
%%% --------------------------------------------------------------------
|
2009-05-06 18:54:43 +02:00
|
|
|
add(Handle, Entry, Attributes) when is_list(Entry), is_list(Attributes) ->
|
2003-11-23 21:11:21 +01:00
|
|
|
Handle1 = get_handle(Handle),
|
2009-01-27 14:24:18 +01:00
|
|
|
gen_fsm:sync_send_event(Handle1, {add, Entry, add_attrs(Attributes)},
|
|
|
|
?CALL_TIMEOUT).
|
2003-11-23 21:11:21 +01:00
|
|
|
|
|
|
|
%%% Do sanity check !
|
|
|
|
add_attrs(Attrs) ->
|
2009-05-06 18:54:43 +02:00
|
|
|
F = fun({Type,Vals}) when is_list(Type), is_list(Vals) ->
|
2003-11-23 21:11:21 +01:00
|
|
|
%% 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"
|
|
|
|
%%% )
|
|
|
|
%%% --------------------------------------------------------------------
|
2009-05-06 18:54:43 +02:00
|
|
|
delete(Handle, Entry) when is_list(Entry) ->
|
2003-11-23 21:11:21 +01:00
|
|
|
Handle1 = get_handle(Handle),
|
2009-01-27 14:24:18 +01:00
|
|
|
gen_fsm:sync_send_event(Handle1, {delete, Entry}, ?CALL_TIMEOUT).
|
2003-11-23 21:11:21 +01:00
|
|
|
|
|
|
|
%%% --------------------------------------------------------------------
|
|
|
|
%%% 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"])]
|
|
|
|
%%% )
|
|
|
|
%%% --------------------------------------------------------------------
|
2009-05-06 18:54:43 +02:00
|
|
|
modify(Handle, Object, Mods) when is_list(Object), is_list(Mods) ->
|
2003-11-23 21:11:21 +01:00
|
|
|
Handle1 = get_handle(Handle),
|
2009-01-27 14:24:18 +01:00
|
|
|
gen_fsm:sync_send_event(Handle1, {modify, Object, Mods}, ?CALL_TIMEOUT).
|
2003-11-23 21:11:21 +01:00
|
|
|
|
|
|
|
%%%
|
|
|
|
%%% Modification operations.
|
|
|
|
%%% Example:
|
|
|
|
%%% replace("telephoneNumber", ["555 555 00"])
|
|
|
|
%%%
|
2009-05-06 18:54:43 +02:00
|
|
|
mod_add(Type, Values) when is_list(Type), is_list(Values) -> m(add, Type, Values).
|
|
|
|
mod_delete(Type, Values) when is_list(Type), is_list(Values) -> m(delete, Type, Values).
|
|
|
|
mod_replace(Type, Values) when is_list(Type), is_list(Values) -> m(replace, Type, Values).
|
2003-11-23 21:11:21 +01:00
|
|
|
|
|
|
|
m(Operation, Type, Values) ->
|
|
|
|
#'ModifyRequest_modification_SEQOF'{
|
2007-01-27 17:40:37 +01:00
|
|
|
operation = Operation,
|
|
|
|
modification = #'AttributeTypeAndValues'{
|
|
|
|
type = Type,
|
|
|
|
vals = Values}}.
|
2003-11-23 21:11:21 +01:00
|
|
|
|
|
|
|
%%% --------------------------------------------------------------------
|
|
|
|
%%% 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)
|
2009-05-06 18:54:43 +02:00
|
|
|
when is_list(Entry), is_list(NewRDN), is_atom(DelOldRDN), is_list(NewSup) ->
|
2003-11-23 21:11:21 +01:00
|
|
|
Handle1 = get_handle(Handle),
|
2009-01-27 14:24:18 +01:00
|
|
|
gen_fsm:sync_send_event(
|
|
|
|
Handle1,
|
|
|
|
{modify_dn, Entry, NewRDN, bool_p(DelOldRDN), optional(NewSup)},
|
|
|
|
?CALL_TIMEOUT).
|
2003-11-23 21:11:21 +01:00
|
|
|
|
|
|
|
|
|
|
|
%%% --------------------------------------------------------------------
|
|
|
|
%%% Bind.
|
|
|
|
%%% Example:
|
|
|
|
%%%
|
|
|
|
%%% bind(Handle,
|
|
|
|
%%% "cn=Bill Valentine, ou=people, o=Bluetail AB, dc=bluetail, dc=com",
|
|
|
|
%%% "secret")
|
|
|
|
%%% --------------------------------------------------------------------
|
|
|
|
bind(Handle, RootDN, Passwd)
|
2009-05-06 18:54:43 +02:00
|
|
|
when is_list(RootDN), is_list(Passwd) ->
|
2003-11-23 21:11:21 +01:00
|
|
|
Handle1 = get_handle(Handle),
|
2009-01-27 14:24:18 +01:00
|
|
|
gen_fsm:sync_send_event(Handle1, {bind, RootDN, Passwd}, ?CALL_TIMEOUT).
|
2003-11-23 21:11:21 +01:00
|
|
|
|
|
|
|
%%% 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"]}]}],
|
|
|
|
%%% []}}
|
|
|
|
%%%
|
|
|
|
%%% --------------------------------------------------------------------
|
2009-05-06 18:54:43 +02:00
|
|
|
search(Handle, A) when is_record(A, eldap_search) ->
|
2003-11-23 21:11:21 +01:00
|
|
|
call_search(Handle, A);
|
2009-05-06 18:54:43 +02:00
|
|
|
search(Handle, L) when is_list(L) ->
|
2003-11-23 21:11:21 +01:00
|
|
|
case catch parse_search_args(L) of
|
|
|
|
{error, Emsg} -> {error, Emsg};
|
|
|
|
{'EXIT', Emsg} -> {error, Emsg};
|
2009-05-06 18:54:43 +02:00
|
|
|
A when is_record(A, eldap_search) -> call_search(Handle, A)
|
2003-11-23 21:11:21 +01:00
|
|
|
end.
|
|
|
|
|
|
|
|
call_search(Handle, A) ->
|
|
|
|
Handle1 = get_handle(Handle),
|
2009-01-27 14:24:18 +01:00
|
|
|
gen_fsm:sync_send_event(Handle1, {search, A}, ?CALL_TIMEOUT).
|
2003-11-23 21:11:21 +01:00
|
|
|
|
|
|
|
parse_search_args(Args) ->
|
|
|
|
parse_search_args(Args, #eldap_search{scope = wholeSubtree}).
|
2007-01-27 17:40:37 +01:00
|
|
|
|
2003-11-23 21:11:21 +01:00
|
|
|
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});
|
2009-05-06 18:54:43 +02:00
|
|
|
parse_search_args([{timeout, Timeout}|T],A) when is_integer(Timeout) ->
|
2003-11-23 21:11:21 +01:00
|
|
|
parse_search_args(T,A#eldap_search{timeout = Timeout});
|
2007-01-27 17:40:37 +01:00
|
|
|
parse_search_args([{limit, Limit}|T],A) when is_integer(Limit) ->
|
|
|
|
parse_search_args(T,A#eldap_search{limit = Limit});
|
2007-12-07 02:40:24 +01:00
|
|
|
parse_search_args([H|_],_) ->
|
2003-11-23 21:11:21 +01:00
|
|
|
throw({error,{unknown_arg, H}});
|
|
|
|
parse_search_args([],A) ->
|
|
|
|
A.
|
|
|
|
|
|
|
|
%%%
|
|
|
|
%%% The Scope parameter
|
|
|
|
%%%
|
|
|
|
baseObject() -> baseObject.
|
|
|
|
singleLevel() -> singleLevel.
|
|
|
|
wholeSubtree() -> wholeSubtree.
|
|
|
|
|
|
|
|
%%%
|
|
|
|
%%% Boolean filter operations
|
|
|
|
%%%
|
2009-05-06 18:54:43 +02:00
|
|
|
'and'(ListOfFilters) when is_list(ListOfFilters) -> {'and',ListOfFilters}.
|
|
|
|
'or'(ListOfFilters) when is_list(ListOfFilters) -> {'or', ListOfFilters}.
|
|
|
|
'not'(Filter) when is_tuple(Filter) -> {'not',Filter}.
|
2003-11-23 21:11:21 +01:00
|
|
|
|
|
|
|
%%%
|
|
|
|
%%% 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
|
|
|
|
%%%
|
2009-05-06 18:54:43 +02:00
|
|
|
present(Attribute) when is_list(Attribute) ->
|
2003-11-23 21:11:21 +01:00
|
|
|
{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'
|
|
|
|
%%%
|
2009-05-06 18:54:43 +02:00
|
|
|
substrings(Type, SubStr) when is_list(Type), is_list(SubStr) ->
|
2003-11-23 21:11:21 +01:00
|
|
|
Ss = {'SubstringFilter_substrings',v_substr(SubStr)},
|
|
|
|
{substrings,#'SubstringFilter'{type = Type,
|
|
|
|
substrings = Ss}}.
|
|
|
|
|
|
|
|
|
2009-05-06 18:54:43 +02:00
|
|
|
get_handle(Pid) when is_pid(Pid) -> Pid;
|
|
|
|
get_handle(Atom) when is_atom(Atom) -> Atom;
|
|
|
|
get_handle(Name) when is_list(Name) -> list_to_atom("eldap_" ++ Name).
|
2003-11-23 21:11:21 +01:00
|
|
|
%%%----------------------------------------------------------------------
|
|
|
|
%%% 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
|
2009-05-25 19:15:48 +02:00
|
|
|
{ok, Hosts, Rootdn, Passwd, Encrypt} ->
|
|
|
|
init({Hosts, Rootdn, Passwd, Encrypt});
|
2003-11-23 21:11:21 +01:00
|
|
|
{error, Reason} ->
|
|
|
|
{stop, Reason}
|
|
|
|
end;
|
2009-05-25 19:15:48 +02:00
|
|
|
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],
|
2003-11-23 21:11:21 +01:00
|
|
|
{ok, connecting, #eldap{hosts = Hosts,
|
2009-05-25 19:15:48 +02:00
|
|
|
port = PortTemp,
|
2003-11-23 21:11:21 +01:00
|
|
|
rootdn = Rootdn,
|
|
|
|
passwd = Passwd,
|
2009-05-25 19:15:48 +02:00
|
|
|
tls = Encrypt,
|
|
|
|
tls_options = TLSOpts,
|
2003-11-23 21:11:21 +01:00
|
|
|
id = 0,
|
|
|
|
dict = dict:new(),
|
2009-01-27 14:24:18 +01:00
|
|
|
req_q = queue:new()}, 0}.
|
2003-11-23 21:11:21 +01:00
|
|
|
|
|
|
|
%%----------------------------------------------------------------------
|
|
|
|
%% 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}
|
|
|
|
%%----------------------------------------------------------------------
|
2009-01-27 14:24:18 +01:00
|
|
|
connecting(Event, From, S) ->
|
|
|
|
Q = queue:in({Event, From}, S#eldap.req_q),
|
|
|
|
{next_state, connecting, S#eldap{req_q=Q}}.
|
2003-11-23 21:11:21 +01:00
|
|
|
|
2009-01-27 14:24:18 +01:00
|
|
|
wait_bind_response(Event, From, S) ->
|
|
|
|
Q = queue:in({Event, From}, S#eldap.req_q),
|
|
|
|
{next_state, wait_bind_response, S#eldap{req_q=Q}}.
|
2003-11-23 21:11:21 +01:00
|
|
|
|
2007-01-27 17:40:37 +01:00
|
|
|
active_bind(Event, From, S) ->
|
2009-01-27 14:24:18 +01:00
|
|
|
Q = queue:in({Event, From}, S#eldap.req_q),
|
|
|
|
{next_state, active_bind, S#eldap{req_q=Q}}.
|
|
|
|
|
|
|
|
active(Event, From, S) ->
|
|
|
|
process_command(S, Event, From).
|
2007-01-27 17:40:37 +01:00
|
|
|
|
2003-11-23 21:11:21 +01:00
|
|
|
%%----------------------------------------------------------------------
|
|
|
|
%% 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}
|
|
|
|
%%----------------------------------------------------------------------
|
2007-12-07 02:40:24 +01:00
|
|
|
handle_event(close, _StateName, S) ->
|
2009-05-25 19:15:48 +02:00
|
|
|
catch (S#eldap.sockmod):close(S#eldap.fd),
|
2009-01-27 14:24:18 +01:00
|
|
|
{stop, normal, S};
|
2007-01-27 17:40:37 +01:00
|
|
|
|
2007-12-07 02:40:24 +01:00
|
|
|
handle_event(_Event, StateName, S) ->
|
2003-11-23 21:11:21 +01:00
|
|
|
{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}
|
|
|
|
%%----------------------------------------------------------------------
|
2007-12-07 02:40:24 +01:00
|
|
|
handle_sync_event(_Event, _From, StateName, S) ->
|
|
|
|
{reply, {StateName, S}, StateName, S}.
|
2003-11-23 21:11:21 +01:00
|
|
|
|
|
|
|
%%----------------------------------------------------------------------
|
|
|
|
%% Func: handle_info/3
|
|
|
|
%% Returns: {next_state, NextStateName, NextStateData} |
|
|
|
|
%% {next_state, NextStateName, NextStateData, Timeout} |
|
|
|
|
%% {stop, Reason, NewStateData}
|
|
|
|
%%----------------------------------------------------------------------
|
|
|
|
|
|
|
|
%%
|
|
|
|
%% Packets arriving in various states
|
|
|
|
%%
|
2009-05-25 19:15:48 +02:00
|
|
|
handle_info({Tag, _Socket, Data}, connecting, S)
|
|
|
|
when Tag == tcp; Tag == ssl ->
|
2009-01-27 14:24:18 +01:00
|
|
|
?DEBUG("tcp packet received when disconnected!~n~p", [Data]),
|
2003-11-23 21:11:21 +01:00
|
|
|
{next_state, connecting, S};
|
|
|
|
|
2009-05-25 19:15:48 +02:00
|
|
|
handle_info({Tag, _Socket, Data}, wait_bind_response, S)
|
|
|
|
when Tag == tcp; Tag == ssl ->
|
2003-11-23 21:11:21 +01:00
|
|
|
cancel_timer(S#eldap.bind_timer),
|
|
|
|
case catch recvd_wait_bind_response(Data, S) of
|
2009-01-27 14:24:18 +01:00
|
|
|
bound ->
|
|
|
|
dequeue_commands(S);
|
2009-05-16 15:18:15 +02:00
|
|
|
{fail_bind, Reason} ->
|
|
|
|
report_bind_failure(S#eldap.host, S#eldap.port, Reason),
|
|
|
|
{next_state, connecting, close_and_retry(S, ?GRACEFUL_RETRY_TIMEOUT)};
|
|
|
|
{'EXIT', Reason} ->
|
|
|
|
report_bind_failure(S#eldap.host, S#eldap.port, Reason),
|
2009-01-27 14:24:18 +01:00
|
|
|
{next_state, connecting, close_and_retry(S)};
|
2009-05-16 15:18:15 +02:00
|
|
|
{error, Reason} ->
|
|
|
|
report_bind_failure(S#eldap.host, S#eldap.port, Reason),
|
2009-01-27 14:24:18 +01:00
|
|
|
{next_state, connecting, close_and_retry(S)}
|
2003-11-23 21:11:21 +01:00
|
|
|
end;
|
|
|
|
|
2009-05-25 19:15:48 +02:00
|
|
|
handle_info({Tag, _Socket, Data}, StateName, S)
|
|
|
|
when (StateName == active orelse StateName == active_bind) andalso
|
|
|
|
(Tag == tcp orelse Tag == ssl) ->
|
2003-11-23 21:11:21 +01:00
|
|
|
case catch recvd_packet(Data, S) of
|
2009-01-27 14:24:18 +01:00
|
|
|
{response, Response, RequestType} ->
|
|
|
|
NewS = case Response of
|
|
|
|
{reply, Reply, To, S1} ->
|
|
|
|
gen_fsm:reply(To, Reply),
|
|
|
|
S1;
|
|
|
|
{ok, S1} ->
|
|
|
|
S1
|
|
|
|
end,
|
|
|
|
if (StateName == active_bind andalso
|
|
|
|
RequestType == bindRequest) orelse
|
|
|
|
(StateName == active) ->
|
|
|
|
dequeue_commands(NewS);
|
|
|
|
true ->
|
|
|
|
{next_state, StateName, NewS}
|
|
|
|
end;
|
|
|
|
_ ->
|
|
|
|
{next_state, StateName, S}
|
2003-11-23 21:11:21 +01:00
|
|
|
end;
|
|
|
|
|
2009-05-25 19:15:48 +02:00
|
|
|
handle_info({Tag, _Socket}, Fsm_state, S)
|
|
|
|
when Tag == tcp_closed; Tag == ssl_closed ->
|
2008-03-20 17:28:36 +01:00
|
|
|
?WARNING_MSG("LDAP server closed the connection: ~s:~p~nIn State: ~p",
|
|
|
|
[S#eldap.host, S#eldap.port ,Fsm_state]),
|
2009-01-27 14:24:18 +01:00
|
|
|
{next_state, connecting, close_and_retry(S)};
|
2003-11-23 21:11:21 +01:00
|
|
|
|
2009-05-25 19:15:48 +02:00
|
|
|
handle_info({Tag, _Socket, Reason}, Fsm_state, S)
|
|
|
|
when Tag == tcp_error; Tag == ssl_error ->
|
2008-03-20 17:28:36 +01:00
|
|
|
?DEBUG("eldap received tcp_error: ~p~nIn State: ~p", [Reason, Fsm_state]),
|
2009-01-27 14:24:18 +01:00
|
|
|
{next_state, connecting, close_and_retry(S)};
|
2008-03-20 17:28:36 +01:00
|
|
|
|
2003-11-23 21:11:21 +01:00
|
|
|
%%
|
|
|
|
%% Timers
|
|
|
|
%%
|
2009-01-27 14:24:18 +01:00
|
|
|
handle_info({timeout, Timer, {cmd_timeout, Id}}, StateName, S) ->
|
2003-11-23 21:11:21 +01:00
|
|
|
case cmd_timeout(Timer, Id, S) of
|
|
|
|
{reply, To, Reason, NewS} -> gen_fsm:reply(To, Reason),
|
2009-01-27 14:24:18 +01:00
|
|
|
{next_state, StateName, NewS};
|
|
|
|
{error, _Reason} -> {next_state, StateName, S}
|
2003-11-23 21:11:21 +01:00
|
|
|
end;
|
|
|
|
|
|
|
|
handle_info({timeout, retry_connect}, connecting, S) ->
|
|
|
|
{ok, NextState, NewS} = connect_bind(S),
|
|
|
|
{next_state, NextState, NewS};
|
|
|
|
|
2007-12-07 02:40:24 +01:00
|
|
|
handle_info({timeout, _Timer, bind_timeout}, wait_bind_response, S) ->
|
2009-01-27 14:24:18 +01:00
|
|
|
{next_state, connecting, close_and_retry(S)};
|
2003-11-23 21:11:21 +01:00
|
|
|
|
|
|
|
%%
|
|
|
|
%% Make sure we don't fill the message queue with rubbish
|
|
|
|
%%
|
|
|
|
handle_info(Info, StateName, S) ->
|
2008-03-20 17:28:36 +01:00
|
|
|
?DEBUG("eldap. Unexpected Info: ~p~nIn state: ~p~n when StateData is: ~p",
|
|
|
|
[Info, StateName, S]),
|
2003-11-23 21:11:21 +01:00
|
|
|
{next_state, StateName, S}.
|
|
|
|
|
|
|
|
%%----------------------------------------------------------------------
|
|
|
|
%% Func: terminate/3
|
|
|
|
%% Purpose: Shutdown the fsm
|
|
|
|
%% Returns: any
|
|
|
|
%%----------------------------------------------------------------------
|
2007-12-07 02:40:24 +01:00
|
|
|
terminate(_Reason, _StateName, _StatData) ->
|
2003-11-23 21:11:21 +01:00
|
|
|
ok.
|
|
|
|
|
|
|
|
%%----------------------------------------------------------------------
|
|
|
|
%% Func: code_change/4
|
|
|
|
%% Purpose: Convert process state when code is changed
|
|
|
|
%% Returns: {ok, NewState, NewStateData}
|
|
|
|
%%----------------------------------------------------------------------
|
2007-12-07 02:40:24 +01:00
|
|
|
code_change(_OldVsn, StateName, S, _Extra) ->
|
2003-11-23 21:11:21 +01:00
|
|
|
{ok, StateName, S}.
|
|
|
|
|
|
|
|
%%%----------------------------------------------------------------------
|
|
|
|
%%% Internal functions
|
|
|
|
%%%----------------------------------------------------------------------
|
2009-01-27 14:24:18 +01:00
|
|
|
dequeue_commands(S) ->
|
|
|
|
case queue:out(S#eldap.req_q) of
|
|
|
|
{{value, {Event, From}}, Q} ->
|
|
|
|
case process_command(S#eldap{req_q=Q}, Event, From) of
|
|
|
|
{_, active, NewS} ->
|
|
|
|
dequeue_commands(NewS);
|
|
|
|
Res ->
|
|
|
|
Res
|
|
|
|
end;
|
|
|
|
{empty, _} ->
|
|
|
|
{next_state, active, S}
|
|
|
|
end.
|
|
|
|
|
|
|
|
process_command(S, Event, From) ->
|
|
|
|
case send_command(Event, From, S) of
|
|
|
|
{ok, NewS} ->
|
|
|
|
case Event of
|
|
|
|
{bind, _, _} ->
|
|
|
|
{next_state, active_bind, NewS};
|
|
|
|
_ ->
|
|
|
|
{next_state, active, NewS}
|
|
|
|
end;
|
|
|
|
{error, _Reason} ->
|
|
|
|
Q = queue:in_r({Event, From}, S#eldap.req_q),
|
|
|
|
NewS = close_and_retry(S#eldap{req_q=Q}),
|
|
|
|
{next_state, connecting, NewS}
|
|
|
|
end.
|
|
|
|
|
2003-11-23 21:11:21 +01:00
|
|
|
send_command(Command, From, S) ->
|
|
|
|
Id = bump_id(S),
|
|
|
|
{Name, Request} = gen_req(Command),
|
|
|
|
Message = #'LDAPMessage'{messageID = Id,
|
|
|
|
protocolOp = {Name, Request}},
|
2008-03-20 17:28:36 +01:00
|
|
|
?DEBUG("~p~n",[{Name, Request}]),
|
2003-11-23 21:11:21 +01:00
|
|
|
{ok, Bytes} = asn1rt:encode('ELDAPv3', 'LDAPMessage', Message),
|
2009-05-25 19:15:48 +02:00
|
|
|
case (S#eldap.sockmod):send(S#eldap.fd, Bytes) of
|
2008-03-20 17:28:36 +01:00
|
|
|
ok ->
|
|
|
|
Timer = erlang:start_timer(?CMD_TIMEOUT, self(), {cmd_timeout, Id}),
|
2009-01-30 16:22:18 +01:00
|
|
|
New_dict = dict:store(Id, [{Timer, Command, From, Name}], S#eldap.dict),
|
2008-03-20 17:28:36 +01:00
|
|
|
{ok, S#eldap{id = Id, dict = New_dict}};
|
|
|
|
Error ->
|
|
|
|
Error
|
|
|
|
end.
|
2003-11-23 21:11:21 +01:00
|
|
|
|
|
|
|
gen_req({search, A}) ->
|
|
|
|
{searchRequest,
|
|
|
|
#'SearchRequest'{baseObject = A#eldap_search.base,
|
|
|
|
scope = v_scope(A#eldap_search.scope),
|
|
|
|
derefAliases = neverDerefAliases,
|
2007-01-27 17:40:37 +01:00
|
|
|
sizeLimit = A#eldap_search.limit,
|
2003-11-23 21:11:21 +01:00
|
|
|
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,
|
2008-03-20 17:28:36 +01:00
|
|
|
?DEBUG("~p",[Op]),
|
2003-11-23 21:11:21 +01:00
|
|
|
Dict = S#eldap.dict,
|
|
|
|
Id = Msg#'LDAPMessage'.messageID,
|
|
|
|
{Timer, From, Name, Result_so_far} = get_op_rec(Id, Dict),
|
2009-01-27 14:24:18 +01:00
|
|
|
Answer =
|
2003-11-23 21:11:21 +01:00
|
|
|
case {Name, Op} of
|
|
|
|
{searchRequest, {searchResEntry, R}} when
|
2007-01-27 17:40:37 +01:00
|
|
|
record(R,'SearchResultEntry') ->
|
2003-11-23 21:11:21 +01:00
|
|
|
New_dict = dict:append(Id, R, Dict),
|
|
|
|
{ok, S#eldap{dict = New_dict}};
|
|
|
|
{searchRequest, {searchResDone, Result}} ->
|
2007-01-27 17:40:37 +01:00
|
|
|
Reason = Result#'LDAPResult'.resultCode,
|
|
|
|
if
|
|
|
|
Reason==success; Reason=='sizeLimitExceeded' ->
|
2003-11-23 21:11:21 +01:00
|
|
|
{Res, Ref} = polish(Result_so_far),
|
|
|
|
New_dict = dict:erase(Id, Dict),
|
|
|
|
cancel_timer(Timer),
|
|
|
|
{reply, #eldap_search_result{entries = Res,
|
|
|
|
referrals = Ref}, From,
|
2007-01-27 17:40:37 +01:00
|
|
|
S#eldap{dict = New_dict}};
|
|
|
|
true ->
|
2003-11-23 21:11:21 +01:00
|
|
|
New_dict = dict:erase(Id, Dict),
|
|
|
|
cancel_timer(Timer),
|
|
|
|
{reply, {error, Reason}, From, S#eldap{dict = New_dict}}
|
2007-01-27 17:40:37 +01:00
|
|
|
end;
|
2003-11-23 21:11:21 +01:00
|
|
|
{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}},
|
2007-01-27 17:40:37 +01:00
|
|
|
From, S#eldap{dict = New_dict}}
|
2009-01-27 14:24:18 +01:00
|
|
|
end,
|
|
|
|
{response, Answer, Name};
|
2003-11-23 21:11:21 +01:00
|
|
|
Error -> Error
|
|
|
|
end.
|
|
|
|
|
2007-12-07 02:40:24 +01:00
|
|
|
check_reply(#'LDAPResult'{resultCode = success}, _From) ->
|
2003-11-23 21:11:21 +01:00
|
|
|
ok;
|
2007-12-07 02:40:24 +01:00
|
|
|
check_reply(#'LDAPResult'{resultCode = Reason}, _From) ->
|
2003-11-23 21:11:21 +01:00
|
|
|
{error, Reason};
|
2007-12-07 02:40:24 +01:00
|
|
|
check_reply(Other, _From) ->
|
2003-11-23 21:11:21 +01:00
|
|
|
{error, Other}.
|
|
|
|
|
2007-12-07 02:40:24 +01:00
|
|
|
check_bind_reply(#'BindResponse'{resultCode = success}, _From) ->
|
2003-11-23 21:11:21 +01:00
|
|
|
ok;
|
2007-12-07 02:40:24 +01:00
|
|
|
check_bind_reply(#'BindResponse'{resultCode = Reason}, _From) ->
|
2003-11-23 21:11:21 +01:00
|
|
|
{error, Reason};
|
2007-12-07 02:40:24 +01:00
|
|
|
check_bind_reply(Other, _From) ->
|
2003-11-23 21:11:21 +01:00
|
|
|
{error, Other}.
|
|
|
|
|
|
|
|
get_op_rec(Id, Dict) ->
|
|
|
|
case dict:find(Id, Dict) of
|
2009-01-30 16:22:18 +01:00
|
|
|
{ok, [{Timer, _Command, From, Name}|Res]} ->
|
2003-11-23 21:11:21 +01:00
|
|
|
{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} ->
|
2008-03-20 17:28:36 +01:00
|
|
|
?DEBUG("~p", [Msg]),
|
2003-11-23 21:11:21 +01:00
|
|
|
check_id(S#eldap.id, Msg#'LDAPMessage'.messageID),
|
|
|
|
case Msg#'LDAPMessage'.protocolOp of
|
|
|
|
{bindResponse, Result} ->
|
2006-11-23 07:34:05 +01:00
|
|
|
case Result#'BindResponse'.resultCode of
|
2003-11-23 21:11:21 +01:00
|
|
|
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
|
2007-12-07 02:40:24 +01:00
|
|
|
{_Tag, Data1, _Rb} ->
|
2003-11-23 21:11:21 +01:00
|
|
|
case asn1rt_ber_bin:decode_length(Data1) of
|
2007-12-07 02:40:24 +01:00
|
|
|
{{_Len,_Data2}, _Rb2} -> ok;
|
2003-11-23 21:11:21 +01:00
|
|
|
_ -> throw({error,decoded_tag_length})
|
|
|
|
end;
|
|
|
|
_ -> throw({error,decoded_tag})
|
|
|
|
end.
|
|
|
|
|
2009-05-16 15:18:15 +02:00
|
|
|
close_and_retry(S, Timeout) ->
|
2009-05-25 19:15:48 +02:00
|
|
|
catch (S#eldap.sockmod):close(S#eldap.fd),
|
2009-01-30 16:22:18 +01:00
|
|
|
Queue = dict:fold(
|
|
|
|
fun(_Id, [{Timer, Command, From, _Name}|_], Q) ->
|
|
|
|
cancel_timer(Timer),
|
|
|
|
queue:in_r({Command, From}, Q);
|
|
|
|
(_, _, Q) ->
|
|
|
|
Q
|
|
|
|
end, S#eldap.req_q, S#eldap.dict),
|
2009-05-16 15:18:15 +02:00
|
|
|
erlang:send_after(Timeout, self(), {timeout, retry_connect}),
|
2009-01-30 16:22:18 +01:00
|
|
|
S#eldap{fd=null, req_q=Queue, dict=dict:new()}.
|
2003-11-23 21:11:21 +01:00
|
|
|
|
2009-05-16 15:18:15 +02:00
|
|
|
close_and_retry(S) ->
|
|
|
|
close_and_retry(S, ?RETRY_TIMEOUT).
|
|
|
|
|
|
|
|
report_bind_failure(Host, Port, Reason) ->
|
|
|
|
?WARNING_MSG("LDAP bind failed on ~s:~p~nReason: ~p",
|
|
|
|
[Host, Port, Reason]).
|
|
|
|
|
2003-11-23 21:11:21 +01:00
|
|
|
%%-----------------------------------------------------------------------
|
|
|
|
%% Sort out timed out commands
|
|
|
|
%%-----------------------------------------------------------------------
|
|
|
|
cmd_timeout(Timer, Id, S) ->
|
|
|
|
Dict = S#eldap.dict,
|
|
|
|
case dict:find(Id, Dict) of
|
2009-01-30 16:22:18 +01:00
|
|
|
{ok, [{Timer, _Command, From, Name}|Res]} ->
|
2003-11-23 21:11:21 +01:00
|
|
|
case Name of
|
|
|
|
searchRequest ->
|
|
|
|
{Res1, Ref1} = polish(Res),
|
|
|
|
New_dict = dict:erase(Id, Dict),
|
|
|
|
{reply, From, {timeout,
|
|
|
|
#eldap_search_result{entries = Res1,
|
|
|
|
referrals = Ref1}},
|
2007-01-27 17:40:37 +01:00
|
|
|
S#eldap{dict = New_dict}};
|
2007-12-07 02:40:24 +01:00
|
|
|
_ ->
|
2003-11-23 21:11:21 +01:00
|
|
|
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, [], []).
|
|
|
|
|
2009-05-06 18:54:43 +02:00
|
|
|
polish([H|T], Res, Ref) when is_record(H, 'SearchResultEntry') ->
|
2003-11-23 21:11:21 +01:00
|
|
|
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),
|
2008-03-20 17:28:36 +01:00
|
|
|
?INFO_MSG("LDAP connection on ~s:~p", [Host, S#eldap.port]),
|
2009-05-25 19:15:48 +02:00
|
|
|
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
|
2003-11-23 21:11:21 +01:00
|
|
|
{ok, Socket} ->
|
2009-05-25 19:15:48 +02:00
|
|
|
case bind_request(Socket, S#eldap{sockmod = SockMod}) of
|
2003-11-23 21:11:21 +01:00
|
|
|
{ok, NewS} ->
|
|
|
|
Timer = erlang:start_timer(?BIND_TIMEOUT, self(),
|
|
|
|
{timeout, bind_timeout}),
|
|
|
|
{ok, wait_bind_response, NewS#eldap{fd = Socket,
|
2009-05-25 19:15:48 +02:00
|
|
|
sockmod = SockMod,
|
2003-11-23 21:11:21 +01:00
|
|
|
host = Host,
|
|
|
|
bind_timer = Timer}};
|
2008-03-20 17:28:36 +01:00
|
|
|
{error, Reason} ->
|
2009-05-16 15:18:15 +02:00
|
|
|
report_bind_failure(Host, S#eldap.port, Reason),
|
2009-01-27 14:24:18 +01:00
|
|
|
NewS = close_and_retry(S),
|
|
|
|
{ok, connecting, NewS#eldap{host = Host}}
|
2003-11-23 21:11:21 +01:00
|
|
|
end;
|
2008-03-20 17:28:36 +01:00
|
|
|
{error, Reason} ->
|
2009-01-27 14:24:18 +01:00
|
|
|
?ERROR_MSG("LDAP connection failed on ~s:~p~nReason: ~p",
|
|
|
|
[Host, S#eldap.port, Reason]),
|
|
|
|
NewS = close_and_retry(S),
|
|
|
|
{ok, connecting, NewS#eldap{host = Host}}
|
2003-11-23 21:11:21 +01:00
|
|
|
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}},
|
2008-03-20 17:28:36 +01:00
|
|
|
?DEBUG("Bind Request Message:~p~n",[Message]),
|
2003-11-23 21:11:21 +01:00
|
|
|
{ok, Bytes} = asn1rt:encode('ELDAPv3', 'LDAPMessage', Message),
|
2009-05-25 19:15:48 +02:00
|
|
|
case (S#eldap.sockmod):send(Socket, Bytes) of
|
2008-03-20 17:28:36 +01:00
|
|
|
ok -> {ok, S#eldap{id = Id}};
|
|
|
|
Error -> Error
|
|
|
|
end.
|
2003-11-23 21:11:21 +01:00
|
|
|
|
|
|
|
%% 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
|
2007-12-07 02:40:24 +01:00
|
|
|
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, [_|T], Hosts) -> next_host(Host, T, Hosts).
|
2003-11-23 21:11:21 +01:00
|
|
|
|
|
|
|
|
|
|
|
%%% --------------------------------------------------------------------
|
|
|
|
%%% 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};
|
2009-05-06 18:54:43 +02:00
|
|
|
v_filter({substrings,S}) when is_record(S,'SubstringFilter') -> {substrings,S};
|
2003-11-23 21:11:21 +01:00
|
|
|
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).
|
|
|
|
|
2009-05-06 18:54:43 +02:00
|
|
|
v_substr([{Key,Str}|T]) when is_list(Str),Key==initial;Key==any;Key==final ->
|
2003-11-23 21:11:21 +01:00
|
|
|
[{Key,Str}|v_substr(T)];
|
2007-12-07 02:40:24 +01:00
|
|
|
v_substr([H|_]) ->
|
2003-11-23 21:11:21 +01:00
|
|
|
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])}).
|
|
|
|
|
2009-05-06 18:54:43 +02:00
|
|
|
v_timeout(I) when is_integer(I), I>=0 -> I;
|
2003-11-23 21:11:21 +01:00
|
|
|
v_timeout(_I) -> throw({error,concat(["timeout not positive integer: ",_I])}).
|
|
|
|
|
|
|
|
v_attributes(Attrs) ->
|
2009-05-06 18:54:43 +02:00
|
|
|
F = fun(A) when is_list(A) -> A;
|
2003-11-23 21:11:21 +01:00
|
|
|
(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
|
2009-05-25 19:15:48 +02:00
|
|
|
{ok, Hosts, Port, Rootdn, Passwd, Encrypt} ->
|
|
|
|
{ok, Hosts, Port, Rootdn, Passwd, Encrypt};
|
2003-11-23 21:11:21 +01:00
|
|
|
{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),
|
2009-05-25 19:15:48 +02:00
|
|
|
get_list(passwd, Entries),
|
|
|
|
get_atom(encrypt, Entries)}.
|
2003-11-23 21:11:21 +01:00
|
|
|
|
|
|
|
get_integer(Key, List) ->
|
|
|
|
case lists:keysearch(Key, 1, List) of
|
2009-05-06 18:54:43 +02:00
|
|
|
{value, {Key, Value}} when is_integer(Value) ->
|
2003-11-23 21:11:21 +01:00
|
|
|
Value;
|
2007-12-07 02:40:24 +01:00
|
|
|
{value, {Key, _Value}} ->
|
2003-11-23 21:11:21 +01:00
|
|
|
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
|
2009-05-06 18:54:43 +02:00
|
|
|
{value, {Key, Value}} when is_list(Value) ->
|
2003-11-23 21:11:21 +01:00
|
|
|
Value;
|
2007-12-07 02:40:24 +01:00
|
|
|
{value, {Key, _Value}} ->
|
2003-11-23 21:11:21 +01:00
|
|
|
throw({error, "Bad Value in Config for " ++ atom_to_list(Key)});
|
|
|
|
false ->
|
|
|
|
throw({error, "No Entry in Config for " ++ atom_to_list(Key)})
|
|
|
|
end.
|
|
|
|
|
2009-05-25 19:15:48 +02:00
|
|
|
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.
|
|
|
|
|
2003-11-23 21:11:21 +01:00
|
|
|
get_hosts(Key, List) ->
|
2009-05-06 18:54:43 +02:00
|
|
|
lists:map(fun({Key1, {A,B,C,D}}) when is_integer(A),
|
|
|
|
is_integer(B),
|
|
|
|
is_integer(C),
|
|
|
|
is_integer(D),
|
2003-11-23 21:11:21 +01:00
|
|
|
Key == Key1->
|
|
|
|
{A,B,C,D};
|
2009-05-06 18:54:43 +02:00
|
|
|
({Key1, Value}) when is_list(Value),
|
2003-11-23 21:11:21 +01:00
|
|
|
Key == Key1->
|
|
|
|
Value;
|
2007-12-07 02:40:24 +01:00
|
|
|
({_Else, _Value}) ->
|
2003-11-23 21:11:21 +01:00
|
|
|
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.
|