Copyright © 2007 University of North Carolina
ldap_server_url
examplesldap_version
exampleldap_bind_dn
exampleldap_bind_password
exampleldap_network_timeout
exampleldap_client_bind_timeout
exampleconfig_file
parameter usageThe LDAP module implements an LDAP search interface for OpenSER. It exports script functions to perform an LDAP search operation and to store the search results as OpenSER AVPs. This allows for using LDAP directory data in the OpenSER SIP message routing script.
The following features are offered by the LDAP module:
LDAP search function taking an LDAP URL as input
LDAP result parsing functions to store LDAP data as AVP
Support for accessing multiple LDAP servers
LDAP SIMPLE authentication
LDAP server failover and automatic reconnect
Configurable LDAP connection and bind timeouts
Module API for LDAP search operations that can be used by other OpenSER modules
The module implementation makes use of the open source OpenLDAP library available on most UNIX/Linux platforms. Besides LDAP server failover and automatic reconnect, this module can handle multiple LDAP sessions concurrently allowing to access data stored on different LDAP servers. Each OpenSER worker process maintains one LDAP TCP connection per configured LDAP server. This enables parallel execution of LDAP requests and offloads LDAP concurrency control to the LDAP server(s).
An LDAP search module API is provided that can be used by other OpenSER modules. A module using this API does not have to implement LDAP connection management and configuration, while still having access to the full OpenLDAP API for searching and result handling.
Since LDAP server implementations are optimized for fast read access they are a good choice to store SIP provisioning data. Performance tests have shown that this module achieves lower data access times and higher call rates than other database modules like e.g. the OpenSER MYSQL module.
First so called LDAP sessions have to be specified in an external configuration file (as described in Section 1.3). Each LDAP session includes LDAP server access parameters like server hostname or connection timeouts. Normally only a single LDAP session will be used unless there is a need to access more than one LDAP server. The LDAP session name will then be used in the OpenSER configuration script to refer to a specific LDAP session.
The ldap_search
function (Section 1.5.1) performs an LDAP search operation. It expects an LDAP URL as input which includes the LDAP session name and search parameters. Section 1.1.2 provides a quick overview on LDAP URLs.
The result of an LDAP search is stored internally and can be accessed with one of the ldap_result*
functions. ldap_result
(Section 1.5.2) stores resulting LDAP attribute value as AVPs. ldap_result_check
(Section 1.5.3) is a convenience function to compare a string with LDAP attribute values using regular expression matching. Finally, ldap_result_next
(Section 1.5.4) allows to handle LDAP search queries that return more than one LDAP entry.
All ldap_result*
functions do always access the LDAP result set from the last ldap_search
call. This should be kept in mind when calling ldap_search
more than once in the OpenSER configuration script.
ldap_search
expects an LDAP URL as argument. This section describes the format and semantics of an LDAP URL.
RFC 4516 [RFC4516] describes the format of an LDAP Uniform Resource Locator (URL). An LDAP URL represents an LDAP search operation in a compact format. The LDAP URL format is defined as follows (slightly modified, refer to section 2 of [RFC4516] for ABNF notation):
ldap://[ldap_session_name][/dn?attrs[?scope[?filter]]]]
ldap_session_name
An LDAP session name as defined in the LDAP configuration file.
(RFC 4516 defines this as LDAP hostport parameter)
dn
Base Distinguished Name (DN) of LDAP search or target of non-search operation, as defined in RFC 4514 [RFC4514]
attrs
Comma separated list of LDAP attributes to be returned
scope
Scope for LDAP search, valid values are "base", "one", or "sub"
filter
LDAP search filter definition following rules of RFC 4515 [RFC4515]
Non-URL characters in an LDAP URL have to be escaped using percent-encoding (refer to section 2.1 of RFC 4516). In particular this means that any "?" character in an LDAP URL component must be written as "%3F", since "?" is used as a URL delimiter. The exported function |
The module depends on the following modules (the listed modules must be loaded before this module):
No dependencies on other OpenSER modules.
The following libraries or applications must be installed before running OpenSER with this module loaded:
OpenLDAP library (libldap) v2.1 or greater, libldap header files (libldap-dev) are needed for compilation
The module reads an external confiuration file at module initialization time that includes LDAP session definitions.
The configuration file follows the Windows INI file syntax, section names are enclosed in square brackets:
[Section_Name]Any section can contain zero or more configuration key assignments of the form
key = value ; commentValues can be given enclosed with quotes. If no quotes are present, the value is understood as containing all characters between the first and the last non-blank characters. Lines starting with a hash sign and blank lines are treated as comments.
Each section describes one LDAP session that can be referred to in the OpenSER configuration script. Using the section name as the host part of an LDAP URL tells the module to use the LDAP session specified in the respective section. An example LDAP session specification looks like:
[example_ldap] ldap_server_url = "ldap://ldap1.example.com, ldap://ldap2.example.com" ldap_bind_dn = "cn=sip_proxy,ou=accounts,dc=example,dc=com" ldap_bind_password = "pwd" ldap_network_timeout = 500 ldap_client_bind_timeout = 500The configuration keys are explained in the following section. This LDAP session can be referred to in the routing script by using an LDAP URL like e.g.
ldap://example_ldap/cn=admin,dc=example,dc=com
LDAP URL including fully qualified domain name or IP address of LDAP server optionally followed by a colon and TCP port to connect: ldap://<FQDN/IP>[:<port>]
. Failover LDAP servers can be added, each separated by a comma. In the event of connection errors, the module tries to connect to servers in order of appearance.
Default value: none, this is a mandatory setting
Supported LDAP versions are 2 and 3.
Default value: 3
(LDAPv3)
Authentication user DN used to bind to LDAP server (module currently only supports SIMPLE_AUTH). Empty string enables anonymous LDAP bind.
Default value: "" (empty string --> anonymous bind)
Authentication password used to bind to LDAP server (SIMPLE_AUTH). Empty string enables anonymous bind.
Default value: "" (empty string --> anonymous bind)
LDAP TCP connect timeout in milliseconds. Setting this
parameter to a low value enables fast failover if ldap_server_url
contains more than one LDAP server addresses.
Default value: 1000 (one second)
LDAP bind operation timeout in milliseconds.
Default value: 1000 (one second)
The following configuration file example includes two LDAP session definitions that could be used e.g. for accessing H.350 data and do phone number to name mappings.
Example 1-7. Example LDAP Configuration File
# LDAP session "sipaccounts": # # - using LDAPv3 (default) # - two redundant LDAP servers # [sipaccounts] ldap_server_url = "ldap://h350-1.example.com, ldap://h350-2.example.com" ldap_bind_dn = "cn=sip_proxy,ou=accounts,dc=example,dc=com" ldap_bind_password = "pwd" ldap_network_timeout = 500 ldap_client_bind_timeout = 500 # LDAP session "campus": # # - using LDAPv2 # - anonymous bind # [campus] ldap_version = 2 ldap_server_url = "ldap://ldap.example.com" ldap_network_timeout = 500 ldap_client_bind_timeout = 500
Full path to LDAP configuration file.
Default value:
/usr/local/etc/openser/ldap.cfg
Performs an LDAP search operation using given LDAP URL and stores result
internally for later retrieval by ldap_result*
functions. If one ore
more LDAP entries are found the function returns the number of found
entries which evaluates to TRUE in the OpenSER configuration script.
It returns -1
(FALSE
) in case no
LDAP entry was found, and -2
(FALSE
) if an internal error like e.g. an LDAP
error occurred.
Function Parameters:
ldap_url
An LDAP URL defining the LDAP search operation (refer to Section 1.1.2 for a description of the LDAP URL format). The hostport part must be one of the LDAP session names declared in the LDAP configuration script.
OpenSER pseudo variables and AVPs included in
ldap_url
do get substituted with their
value.
Example 1-9. Example Usage of ldap_url
Search with LDAP session named
sipaccounts
, base
ou=sip,dc=example,dc=com
,
one
level deep using search filter
(cn=schlatter)
and returning all
attributes:
ldap://sipaccounts/ou=sip,dc=example,dc=com??one?(cn=schlatter)
Subtree search with LDAP session named
ldap1
, base
dc=example,dc=com
using search filter
(cn=$(avp(s:name)))
and returning
SIPIdentityUserName
and
SIPIdentityServiceLevel
attributes
ldap://ldap_1/dc=example,dc=com? SIPIdentityUserName,SIPIdentityServiceLevel?sub?(cn=$(avp(s:name)))
Return Values:
n
> 0 (TRUE):Found n
matching LDAP
entries
-1
(FALSE):No matching LDAP entries found
-2
(FALSE):LDAP error (e.g. LDAP server unavailable), or
internal error
This function can be used from REQUEST_ROUTE, FAILURE_ROUTE, BRANCH_ROUTE, and ONREPLY_ROUTE.
Example 1-10. Example Usage
... # ldap search if (!ldap_search("ldap://sipaccounts/ou=sip,dc=example,dc=com??one?(cn=$rU)")) { switch ($retcode) { case -1: # no LDAP entry found sl_send_reply("404", "User Not Found"); exit; case -2: # internal error sl_send_reply("500", "Internal server error"); exit; default: exit; } } xlog("L_INFO", "ldap_search: found [$retcode] entries for (cn=$rU)"); # save telephone number in $avp(s:tel_number) ldap_result("telephoneNumber/$avp(s:tel_number)"); ...
This function converts LDAP attribute values into AVPs for later
use in the message routing script. It accesses the LDAP result set
fetched by the last ldap_search
call.
ldap_attr_name
specifies the LDAP attribute name
who's value should be stored in AVP avp_spec
. Multi
valued LDAP attributes generate an indexed AVP. The optional
regex_subst
parameter allows to further define what
part of an attribute value should be stored as AVP.
An AVP can either be of type string or integer. As default, ldap_result
stores LDAP attribute values as AVP of type string. The optional avp_type
parameter can be used to explicitly specify the type of the AVP. It can be either str
for string, or int
for integer. If avp_type
is specified as int
then ldap_result
tries to convert the LDAP attribute values to integer. In this case, the values are only stored as AVP if the conversion to integer is succesfull.
Function Parameters:
The name of the LDAP attribute who's value should be
stored, e.g. SIPIdentityServiceLevel
or
telephonenumber
Specification of destination AVP, e.g.
$avp(s:service_level)
or
$avp(i:12)
Opional specification of destination AVP type, either str
or int
. If this parameter is not specified then the LDAP attribute values are stored as AVP of type string.
Regex substitution that gets applied to LDAP attribute
value before storing it as AVP, e.g.
"/^sip:(.+)$/\1/"
to strip off "sip:" from
the beginning of an LDAP attribute value.
Return Values:
n
> 0 (TRUE) LDAP attribute ldap_attr_name
found in LDAP result set and n
LDAP attribute values stored in avp_spec
No LDAP attribute ldap_attr_name
found
in LDAP result set
Internal error occurred
This function can be used from REQUEST_ROUTE, FAILURE_ROUTE, BRANCH_ROUTE, and ONREPLY_ROUTE.
Example 1-11. Example Usage
... # ldap_search call ... # save SIPIdentityServiceLevel in $avp(s:service_level) if (!ldap_result("SIPIdentityServiceLevel/$avp(s:service_level)")) { switch ($retcode) { case -1: # no SIPIdentityServiceLevel found sl_send_reply("403", "Forbidden"); exit; case -2: # internal error sl_send_reply("500", "Internal server error"); exit; default: exit; } } # save SIP URI domain in $avp(i:10) ldap_result("SIPIdentitySIPURI/$avp(i:10)", "/^[^@]+@(.+)$/\1/"); ...
This function compares ldap_attr_name
's value
with string_to_match
for equality. It accesses the LDAP result set
fetched by the last ldap_search
call. The
optional regex_subst
parameter allows to further
define what part of the attribute value should be used for the
equality match. If ldap_attr_name
is multi valued,
each value is checked against string_to_match
. If
one or more of the values do match the function returns 1
(TRUE).
Function Parameters:
The name of the LDAP attribute who's value should be
matched, e.g. SIPIdentitySIPURI
String to be matched. Included AVPs and pseudo variabels do get expanded.
Regex substitution that gets applied to LDAP attribute
value before comparing it with string_to_match, e.g.
"/^[^@]@+(.+)$/\1/"
to extract the domain part
of a SIP URI
Return Values:
One or more ldap_attr_name
attribute values match
string_to_match
(after
regex_subst
is applied)
ldap_attr_name
attribute not found or
attribute value doesn't match string_to_match
(after regex_subst
is applied)
Internal error occurred
This function can be used from REQUEST_ROUTE, FAILURE_ROUTE, BRANCH_ROUTE, and ONREPLY_ROUTE.
Example 1-12. Example Usage
... # ldap_search call ... # check if 'sn' ldap attribute value equals username part of R-URI, # the same could be achieved with ldap_result_check("sn/$rU") if (!ldap_result_check("sn/$ru", "/^sip:([^@]).*$/\1/")) { switch ($retcode) { case -1: # R-URI username doesn't match sn sl_send_reply("401", "Unauthorized"); exit; case -2: # internal error sl_send_reply("500", "Internal server error"); exit; default: exit; } } ...
An LDAP search operation can return multiple LDAP entries. This
function can be used to cycle through all returned LDAP entries. It
returns 1 (TRUE) if there is another LDAP entry present in the LDAP
result set and causes ldap_result*
functions to work on the next LDAP
entry. The function returns -1 (FALSE) if there are no more LDAP
entries in the LDAP result set.
Return Values:
Another LDAP entry is present in the LDAP result set and result pointer is incremented by one
No more LDAP entries are available
-2
(FALSE)Internal error
This function can be used from REQUEST_ROUTE, FAILURE_ROUTE, BRANCH_ROUTE, and ONREPLY_ROUTE.
Example 1-13. Example Usage
... # ldap_search call ... ldap_result("telephonenumber/$avp(s:tel1)"); if (ldap_result_next()) { ldap_result("telephonenumber/$avp(s:tel2)"); } if (ldap_result_next()) { ldap_result("telephonenumber/$avp(s:tel3)"); } if (ldap_result_next()) { ldap_result("telephonenumber/$avp(s:tel4)"); } ...
This function applies the following escaping rules to
string
and stores the result in AVP
avp_spec
:
Table 1-2. ldap_filter_url_encode() escaping rules
character in
string | gets replaced with | defined in |
---|---|---|
* | \2a | RFC 4515 |
( | \28 | RFC 4515 |
) | \29 | RFC 4515 |
\ | \5c | RFC 4515 |
? | %3F | RFC 4516 |
The string stored in AVP avp_spec
can be safely used in an LDAP
URL filter string.
Function Parameters:
string
String to apply RFC 4515 and URL escpaing rules to.
AVPs and pseudo variables do get expanded. Example:
"cn=$avp(s:name)"
avp_spec
Specification of AVP to store resulting RFC 4515
and URL encoded string, e.g. $avp(s:ldap_search)
or $avp(i:10)
Return Values:
1
(TRUE)RFC 4515 and URL encoded
filter_component
stored as AVP
avp_name
-1
(FALSE)Internal error
This function can be used from REQUEST_ROUTE, FAILURE_ROUTE, BRANCH_ROUTE, and ONREPLY_ROUTE.
Example 1-14. Example Usage
... if (!ldap_filter_url_encode("cn=$avp(s:name)", "$avp(s:name_esc)")) { # RFC 4515/URL encoding failed --> silently discard request exit; } xlog("L_INFO", "encoded LDAP filter component: [$avp(s:name_esc)]\n"); if (ldap_search( "ldap://h350/ou=commObjects,dc=example,dc=com??sub?($avp(s:name_esc))")) { ... } ...
OpenLDAP library (libldap) and header files (libldap-dev) v2.1 or greater (this module was tested with v2.1.3 and v2.3.32) are required for compiling the LDAP module. The OpenLDAP source is available at http://www.openldap.org/.
The OpenLDAP library is available pre-compiled for most UNIX/Linux flavors. On Debian/Ubuntu, the following packages must be installed:
# apt-get install libldap2 libldap2-dev.
The LDAP module API can be used by other OpenSER modules to implement LDAP search functionality. This frees the module implementer from having to care about LDAP connection management and configuration.
In order to use this API, a module has to load the API using the load_ldap_api
function which returns a pointer to a ldap_api
structure. This structure includes pointers to the API functions described below. The LDAP module source file api.h
includes all declarations needed to load the API, it has to be included in the file that loads the API. Loading the API is typically done inside a module's mod_init
call as the following example shows:
Example 2-1. Example code fragment to load LDAP module API
#include "../../sr_module.h" #include "../ldap/api.h" /* * global pointer to ldap api */ extern ldap_api_t ldap_api; ... static int mod_init(void) { /* * load the LDAP API */ if (load_ldap_api(&ldap_api) != 0) { LM_ERR("Unable to load LDAP API - this module requires ldap module\n"); return -1; } ... } ...
The API functions can then be used like in the following example:
Performs an LDAP search using the parameters given as function arguments.
typedef int (*ldap_params_search_t)(int* _ld_result_count, char* _lds_name, char* _dn, int _scope, char** _attrs, char* _filter, ...);
Function arguments:
The function stores the number of returned LDAP entries in _ld_result_count
.
LDAP session name as configured in the LDAP module configuration file.
LDAP search DN.
LDAP search scope, one of LDAP_SCOPE_ONELEVEL
, LDAP_SCOPE_BASE
, or LDAP_SCOPE_SUBTREE
, as defined in OpenLDAP's ldap.h
.
A null-terminated array of attribute types to return from entries. If empty (NULL
), all attribute types are returned.
LDAP search filter string according to RFC 4515. printf
patterns in this string do get replaced with the function arguments' values following the _filter
argument.
Return Values:
Internal error.
Success, _ld_result_count
includes the number of LDAP entries found.
Performs an LDAP search using an LDAP URL.
typedef int (*ldap_url_search_t)(char* _ldap_url, int* _result_count);
Function arguments:
LDAP URL as described in Section 1.1.2.
The function stores the number of returned LDAP entries in _ld_result_count
.
Return Values:
Internal error.
Success, _ld_result_count
includes the number of LDAP entries found.
Retrieve the value(s) of a returned LDAP attribute. The function accesses the LDAP result returned by the last call of ldap_params_search
or ldap_url_search
. The berval
structure is defined in OpenLDAP's ldap.h
, which has to be included.
This function allocates memory to store the LDAP attribute value(s). This memory has to freed with the function ldap_value_free_len
(see next section).
typedef int (*ldap_result_attr_vals_t)(str* _attr_name, struct berval ***_vals); typedef struct berval { ber_len_t bv_len; char *bv_val; } BerValue;
Function arguments:
str
structure holding the LDAP attribute name.
A null-terminated array of the attribute's value(s).
Return Values:
Internal error.
Success, _vals
includes the attribute's value(s).
No attribute value found.
Function used to free memory allocated by ldap_result_attr_vals
. The berval
structure is defined in OpenLDAP's ldap.h
, which has to be included.
typedef void (*ldap_value_free_len_t)(struct berval **_vals); typedef struct berval { ber_len_t bv_len; char *bv_val; } BerValue;
Function arguments:
berval
array returned by ldap_result_attr_vals
.
Increments the LDAP result pointer.
typedef int (*ldap_result_next_t)();
Return Values:
No LDAP result found, probably because ldap_params_search
or ldap_url_search
was not called.
Success, LDAP result pointer points now to next result.
No more results available.
Converts LDAP search scope string into integer value e.g. for ldap_params_search
.
typedef int (*ldap_str2scope_t)(char* scope_str);
Function arguments:
LDAP search scope string. One of "one", "onelevel", "base", "sub", or "subtree".
Return Values:
scope_str
not recognized.
LDAP search scope integer.
Applies escaping rules described in Section 1.5.5.
typedef int (*ldap_rfc4515_escape_t)(str *sin, str *sout, int url_encode);
Function arguments:
str
structure holding the string to apply the escaping rules.
str
structure holding the escaped string. The length of this string must be at least three times the length of sin
plus one.
Flag that specifies if a '?' character gets escaped with '%3F' or not. If url_encode
equals 0
, '?' does not get escaped.
Return Values:
Internal error.
Success, sout
contains escaped string.
Returns the OpenLDAP LDAP handle for a specific LDAP session. This allows a module implementor to use the OpenLDAP API functions directly, instead of using the API functions exported by the OpenSER LDAP module. The LDAP
structure is defined in OpenLDAP's ldap.h
, which has to be included.
typedef int (*get_ldap_handle_t)(char* _lds_name, LDAP** _ldap_handle);
Function arguments:
LDAP session name as specified in the LDAP module configuration file.
OpenLDAP LDAP handle returned by this function.
Return Values:
Internal error.
Success, _ldap_handle
contains the OpenLDAP LDAP handle.
Returns the OpenLDAP LDAP handle and OpenLDAP result handle of the last LDAP search operation. These handles can be used as input for OpenLDAP LDAP result API functions. LDAP
and LDAPMessage
structures are defined in OpenLDAP's ldap.h
, which has to be included.
typedef void (*get_last_ldap_result_t) (LDAP** _last_ldap_handle, LDAPMessage** _last_ldap_result);
Function arguments:
OpenLDAP LDAP handle returned by this function.
OpenLDAP result handle returned by this function.
The following example shows how this API can be used to perform an LDAP search operation. It is assumed that the API is loaded and available through the ldap_api
pointer.
... int rc, ld_result_count, scope = 0; char* sip_username = "test"; /* * get LDAP search scope integer */ scope = ldap_api.ldap_str2scope("sub"); if (scope == -1) { LM_ERR("ldap_str2scope failed\n"); return -1; } /* * perform LDAP search */ if (ldap_api.ldap_params_search( &ld_result_count, "campus", "dc=example,dc=com", scope, NULL, "(&(objectClass=SIPIdentity)(SIPIdentityUserName=%s))", sip_username) != 0) { LM_ERR("LDAP search failed\n"); return -1; } /* * check result count */ if (ld_result_count < 1) { LM_ERR("LDAP search returned no entry\n"); return 1; } /* * get password attribute value */ struct berval **attr_vals = NULL; str ldap_pwd_attr_name = str_init("SIPIdentityPassword"); str res_password; rc = ldap_api.ldap_result_attr_vals(&ldap_pwd_attr_name, &attr_vals); if (rc < 0) { LM_ERR("ldap_result_attr_vals failed\n"); ldap_api.ldap_value_free_len(attr_vals); return -1; } if (rc == 1) { LM_INFO("No password attribute value found for [%s]\n", sip_username); ldap_api.ldap_value_free_len(attr_vals); return 2; } res_password.s = attr_vals[0]->bv_val; res_password.len = attr_vals[0]->bv_len; ldap_api.ldap_value_free_len(attr_vals); LM_INFO("Password for user [%s]: [%s]\n", sip_username, res_password.s); ... return 0;
Take a look at http://www.openser-project.org/.
First at all check if your question was already answered on one of our mailing lists:
User Mailing List - http://lists.openser-project.org/cgi-bin/mailman/listinfo/users
Developer Mailing List - http://lists.openser-project.org/cgi-bin/mailman/listinfo/devel
E-mails regarding any stable OpenSER release should be sent to
<users@lists.openser-project.org>
and e-mails regarding development versions
should be sent to <devel@lists.openser-project.org>
.
If you want to keep the mail private, send it to
<team@lists.openser-project.org>
.
Please follow the guidelines provided at: http://sourceforge.net/tracker/?group_id=139143.
[RFC4510] Lightweight Directory Access Protocol (LDAP): Technical Specification Road Map, June 2006, Internet Engineering Task Force.
[RFC4511] Lightweight Directory Access Protocol (LDAP): The Protocol, June 2006, Internet Engineering Task Force.
[RFC4514] Lightweight Directory Access Protocol (LDAP): String Representation of Distinguished Names, June 2006, Internet Engineering Task Force.
[RFC4515] Lightweight Directory Access Protocol (LDAP): String Representation of Search Filters, June 2006, Internet Engineering Task Force.
[RFC4516] Lightweight Directory Access Protocol (LDAP): Uniform Resource Locator, June 2006, Internet Engineering Task Force.
[RFC2617] HTTP Authentication: Basic and Digest Access Authentication, June 1999, Internet Engineering Task Force.
[RFC3261] SIP: Session Initiation Protocol, June 2002, Internet Engineering Task Force.
[H.350] Directory Services Architecture for Multimedia Conferencing, August 2003, ITU-T.
[H.350.4] Directory services architecture for SIP, August 2003, ITU-T.