  <chapter>
    <title>&adminguide;</title>

    <section id="overview" xreflabel="Overview">
      <title>Overview</title>

      <para>The LDAP module implements an LDAP search interface for OpenSIPS. It exports script functions to perform an LDAP search operation and to store the search results as OpenSIPS AVPs. This allows for using LDAP directory data in the OpenSIPS SIP message routing script.</para>

      <para>The following features are offered by the LDAP module:</para>
      <itemizedlist>
        <listitem>
            <para>LDAP search function taking an LDAP URL as input both synchronous and asynchronous</para>
        </listitem>
        <listitem>
            <para>LDAP result parsing functions to store LDAP data as AVP</para>
        </listitem>
        <listitem>
            <para>Support for accessing multiple LDAP servers</para>
        </listitem>
        <listitem>
            <para>LDAP SIMPLE authentication</para>
        </listitem>
        <listitem>
            <para>LDAP server failover and automatic reconnect</para>
        </listitem>
		<listitem>
            <para>Configurable LDAP connection and bind timeouts</para>
        </listitem>
		<listitem>
            <para>Module API for LDAP search operations that can be used by other OpenSIPS modules</para>
        </listitem>
		<listitem>
            <para>StartTLS support</para>
        </listitem>
      </itemizedlist>

      <para>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 OpenSIPS 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).</para>

      <para>An LDAP search module API is provided that can be used by other OpenSIPS 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.</para>

      <para>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 OpenSIPS MYSQL module.</para>

      <section>
        <title>Usage Basics</title>

        <para>
			First so called LDAP sessions have to be specified in an external configuration file (as described in <xref linkend="ldap-config" xreflabel="LDAP Configuration File"/>). 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 OpenSIPS configuration script to refer to a specific LDAP session.
        </para>

        <para>
			The <varname>ldap_search</varname> function (<xref linkend="ldap-search-fn"/>) performs an LDAP search operation. It expects an LDAP URL as input which includes the LDAP session name and search parameters. <xref linkend="ldap-urls"/>  provides a quick overview on LDAP URLs.
        </para>

        <para>
			The result of an LDAP search is stored internally and can be accessed with one of the <varname>ldap_result*</varname> functions. <varname>ldap_result</varname> (<xref linkend="ldap-result-fn"/>) stores resulting LDAP attribute value as AVPs. <varname>ldap_result_check</varname> (<xref linkend="ldap-result-check-fn"/>) is a convenience function to compare a string with LDAP attribute values using regular expression matching. Finally, <varname>ldap_result_next</varname> (<xref linkend="ldap-result-next-fn"/>) allows to handle LDAP search queries that return more than one LDAP entry.
        </para>

        <para>
        All <varname>ldap_result*</varname> functions do always access the LDAP result set from the last <varname>ldap_search</varname> call. This should be kept in mind when calling <varname>ldap_search</varname> more than once in the OpenSIPS configuration script.
        </para>
      </section>

      <section id="ldap-urls">
        <title>LDAP URLs</title>

        <para>
        <varname>ldap_search</varname> expects an LDAP URL as argument. This section describes the format and semantics of an LDAP URL.
        </para>

        <para>
			RFC 4516 <xref linkend="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 <xref linkend="RFC4516"/> for ABNF notation):
        </para>

        <blockquote>
          <para><literal>ldap://[ldap_session_name][/dn?attrs[?scope[?filter]]]]</literal></para>
        </blockquote>

        <variablelist>
          <varlistentry>
            <term><parameter>ldap_session_name</parameter></term>

            <listitem>
              <para>An LDAP session name as defined in the LDAP
              configuration file.</para>

              <para>(RFC 4516 defines this as LDAP hostport parameter)</para>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term><parameter>dn</parameter></term>

            <listitem>
              <para>Base Distinguished Name (DN) of LDAP search or target of
              non-search operation, as defined in RFC 4514 <xref
				  linkend="RFC4514"/></para>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term><parameter>attrs</parameter></term>

            <listitem>
              <para>Comma separated list of LDAP attributes to be
              returned</para>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term><parameter>scope</parameter></term>

            <listitem>
              <para>Scope for LDAP search, valid values are
              <quote>base</quote>, <quote>one</quote>, or
              <quote>sub</quote></para>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term><parameter>filter</parameter></term>

            <listitem>
              <para>LDAP search filter definition following rules of RFC 4515
				  <xref linkend="RFC4515"/><note>
                  <para>The following table lists characters that have to be
                  escaped in LDAP search filters:</para>

                  <table>
                    <title>RFC 4515 Escaping Rules</title>

                    <tgroup cols="2">
                      <tbody>
                        <row>
                          <entry><constant>*</constant></entry>

                          <entry><constant>\2a</constant></entry>
                        </row>

                        <row>
                          <entry><constant>(</constant></entry>

                          <entry><constant>\28</constant></entry>
                        </row>

                        <row>
                          <entry><constant>)</constant></entry>

                          <entry><constant>\29</constant></entry>
                        </row>

                        <row>
                          <entry><constant>\</constant></entry>

                          <entry><constant>\5c</constant></entry>
                        </row>
                      </tbody>
                    </tgroup>
                  </table>
                </note></para>
            </listitem>
          </varlistentry>
        </variablelist>

        <note>
          <para>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.</para>
  <para>The exported function <varname>ldap_filter_url_encode</varname> (<xref linkend="ldap-filter-url-encode-fn"/>)
	  implements RFC 4515/4516 LDAP search filter and URL escaping
	  rules.</para>
        </note>
      </section>
    </section>

    <section id="dependencies" xreflabel="Dependencies">
      <title>Dependencies</title>

      <section>
        <title>OpenSIPS Modules</title>

        <para>The module depends on the following modules (the listed modules
        must be loaded before this module):</para>

        <itemizedlist>
          <listitem>
            <para><emphasis>No dependencies on other OpenSIPS modules.</emphasis></para>
          </listitem>
        </itemizedlist>
      </section>

      <section>
        <title>External Libraries or Applications</title>

        <para>The following libraries or applications must be installed before
        running OpenSIPS with this module loaded:</para>

        <itemizedlist>
          <listitem>
            <para>OpenLDAP library (libldap) v2.1 or greater, libldap header files
            (libldap-dev) are needed for compilation</para>
          </listitem>
        </itemizedlist>
      </section>
    </section>

    <section id="ldap-config">
      <title>LDAP Configuration File</title>

      <para>The module reads an external confiuration file at module
      initialization time that includes LDAP session definitions.</para>

      <section>
        <title>Configuration File Syntax</title>

        <para>The configuration file follows the Windows INI file syntax,
        section names are enclosed in square brackets:<programlisting>[Section_Name]</programlisting>Any
        section can contain zero or more configuration key assignments of the
        form<programlisting>key = value ; comment</programlisting>Values 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.</para>

        <para>Each section describes one LDAP session that can be referred to
        in the OpenSIPS 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:
<programlisting>
[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   = 500
ldap_ca_cert_file		   = "/usr/share/ca-certificates/mycert.pem"
ldap_cert_file			   = "/var/my-certificate/certificate.pem"
ldap_key_file			   = "/var/my-certificate/key.pem"
ldap_require_certificate   = "ALLOW"
</programlisting>
        The 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.<programlisting>ldap://example_ldap/cn=admin,dc=example,dc=com</programlisting>
        </para>
      </section>

      <section>
        <title>LDAP Session Settings</title>

        <variablelist>
          <varlistentry>
            <term>ldap_server_url (mandatory)</term>

            <listitem>
			  <para>
			    LDAP URL including fully qualified domain name or IP address of LDAP server optionally followed by a colon and TCP port to connect: <varname>ldap://&lt;FQDN/IP&gt;[:&lt;port&gt;]</varname>. 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.
			  </para>

              <para>Default value: none, this is a mandatory setting</para>

              <example>
                <title><varname>ldap_server_url</varname> examples</title>

                <programlisting format="linespecific">
ldap_server_url = "ldap://localhost"
ldap_server_url = "ldap://ldap.example.com:7777"
ldap_server_url = "ldap://ldap1.example.com,
                   ldap://ldap2.example.com:80389"
				</programlisting>
              </example>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term>ldap_version (optional)</term>

            <listitem>
              <para>Supported LDAP versions are 2 and 3.</para>

              <para>Default value: <varname>3</varname> (LDAPv3)</para>

              <example>
                <title><varname>ldap_version</varname> example</title>

                <programlisting format="linespecific">ldap_version = 2</programlisting>
              </example>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term>ldap_bind_dn (optional)</term>

            <listitem>
              <para>Authentication user DN used to bind to LDAP server (module
              currently only supports SIMPLE_AUTH). Empty string enables
              anonymous LDAP bind.</para>

              <para>Default value: <quote></quote> (empty string --&gt;
              anonymous bind)</para>

              <example>
                <title><varname>ldap_bind_dn</varname> example</title>

                <programlisting format="linespecific">ldap_bind_dn = "cn=root,dc=example,dc=com";</programlisting>
              </example>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term>ldap_bind_password (optional)</term>

            <listitem>
              <para>Authentication password used to bind to LDAP server
              (SIMPLE_AUTH). Empty string enables anonymous bind.</para>

              <para>Default value: <quote></quote> (empty string --&gt;
              anonymous bind)</para>

              <example>
                <title><varname>ldap_bind_password</varname> example</title>

                <programlisting format="linespecific">ldap_bind_password = "secret";</programlisting>
              </example>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term>ldap_network_timeout (optional)</term>

            <listitem>
              <para>LDAP TCP connect timeout in milliseconds. Setting this
              parameter to a low value enables fast failover if <varname>ldap_server_url</varname> contains more than one LDAP server addresses.</para>

              <para>Default value: 1000 (one second)</para>

              <example>
                <title><varname>ldap_network_timeout</varname> example</title>

                <programlisting format="linespecific">ldap_network_timeout = 500 ; setting TCP timeout to 500 ms</programlisting>
              </example>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term>ldap_client_bind_timeout (optional)</term>

            <listitem>
              <para>LDAP bind operation timeout in milliseconds.</para>

              <para>Default value: 1000 (one second)</para>

              <example>
                <title><varname>ldap_client_bind_timeout</varname>
                example</title>

                <programlisting format="linespecific">ldap_client_bind_timeout = 1000</programlisting>
              </example>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term>ldap_ca_cert_file (optional)</term>

            <listitem>
              <para>LDAP full path of the CA certificate file.</para>

              <para>No default value. It is mandatory in case you wish to use StartTLS</para>

              <example>
                <title><varname>ldap_ca_cert_file</varname>
                example</title>

			<programlisting format="linespecific">ldap_ca_cert_file = "/usr/local/CAcert.pem"</programlisting>
              </example>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term>ldap_cert_file (optional)</term>

            <listitem>
              <para>LDAP full path of the certificate file.</para>

              <para>No default value. It is mandatory in case you wish to use StartTLS</para>

              <example>
                <title><varname>ldap_cert_file</varname>
                example</title>

			<programlisting format="linespecific">ldap_cert_file = "/usr/local/mycert.pem"</programlisting>
              </example>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term>ldap_key_file (optional)</term>

            <listitem>
              <para>LDAP full path of the key file.</para>

              <para>No default value. It is mandatory in case you wish to use StartTLS</para>

              <example>
                <title><varname>ldap_key_file</varname>
                example</title>

			<programlisting format="linespecific">ldap_key_file = "/usr/local/mykey.pem"</programlisting>
              </example>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term>ldap_require_certificate (optional)</term>

            <listitem>
				<para>LDAP peer certificate checking strategy, one of "NEVER", "HARD", "DEMAND", "ALLOW", "TRY".
						Lower case letters are also accepted.</para>

              <para>Default value "NEVER".</para>

              <example>
                <title><varname>ldap_require_certificate</varname>
                example</title>

			<programlisting format="linespecific">ldap_require_certificate = "NEVER"</programlisting>
              </example>
            </listitem>
          </varlistentry>

        </variablelist>
      </section>

      <section>
        <title>Configuration File Example</title>

        <para>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.</para>

        <example>
          <title>Example LDAP Configuration File</title>

          <programlisting>
# 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
#using StartTLS
ldap_ca_cert_file = "/ldap/path/to/ca/certificate.pem"
ldap_cert_file = "/ldap/path/to/certificate.pem"
ldap_key_file = "/ldap/path/to/key/file.pem"
ldap_require_certificate = "NEVER"


# 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
			</programlisting>
        </example>
      </section>
    </section>

    <section id="exported_parameters" xreflabel="Exported Parameters">
      <title>Exported Parameters</title>

      <section id="param_config_file" xreflabel="config_file">
        <title>config_file (string)</title>

        <para>Full path to LDAP configuration file.</para>

        <para>Default value:
        <varname>/usr/local/etc/opensips/ldap.cfg</varname></para>

        <example>
          <title><varname>config_file</varname> parameter usage</title>

          <programlisting format="linespecific">
modparam("ldap", "config_file", "/etc/opensips/ldap.ini")
		  </programlisting>
        </example>
      </section>

	  <section id="param_max_async_connections" xreflabel="max_async_connections">
		  <title>max_async_connections (int)</title>

		  <para>Number of maximum asynchronous connections that will be started
			  with the ldap server for executing asynchronous ldap_search calls.
			  The number of connections is per process, so if there are 8
			  worker processes with 20 max_async_connections, there will be a
			  maximum of 160 connections to the ldap server.
		  </para>

		 <para>Default value: <varname>20</varname></para>

		<example>
          <title><varname>max_async_connections</varname> parameter usage</title>

          <programlisting format="linespecific">
modparam("ldap", "max_async_connections", 50)
		  </programlisting>
        </example>

	  </section>
    </section>

    <section id="exported_functions" xreflabel="exported_functions">
      <title>Exported Functions</title>

      <section id="ldap-search-fn">
        <title>ldap_search(ldap_url)</title>

        <para>Performs an LDAP search operation using given LDAP URL and stores result
        internally for later retrieval by <varname>ldap_result*</varname> functions. If one ore
        more LDAP entries are found the function returns the number of found
        entries which evaluates to TRUE in the OpenSIPS configuration script.
        It returns <varname>-1</varname> (<varname>FALSE</varname>) in case no
        LDAP entry was found, and <varname>-2</varname>
        (<varname>FALSE</varname>) if an internal error like e.g. an LDAP
        error occurred.</para>

        <variablelist>
          <title>Function Parameters:</title>

          <varlistentry>
            <term><parameter>ldap_url (string)</parameter></term>

            <listitem>
              <para>An LDAP URL defining the LDAP search operation (refer to
			  <xref linkend="ldap-urls"/> 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.</para>

              <example>
                <title>Example Usage of ldap_url</title>

                <para>Search with LDAP session named
                <varname>sipaccounts</varname>, base
                <varname>ou=sip,dc=example,dc=com</varname>,
                <varname>one</varname> level deep using search filter
                <varname>(cn=schlatter)</varname> and returning all
                attributes:</para>

                <programlisting>ldap://sipaccounts/ou=sip,dc=example,dc=com??one?(cn=schlatter)</programlisting>

                <para>Subtree search with LDAP session named
                <varname>ldap1</varname>, base
                <varname>dc=example,dc=com</varname> using search filter
                <varname>(cn=$(avp(name)))</varname> and returning
                <varname>SIPIdentityUserName</varname> and
                <varname>SIPIdentityServiceLevel</varname> attributes</para>

                <programlisting>
ldap://ldap_1/dc=example,dc=com?
       SIPIdentityUserName,SIPIdentityServiceLevel?sub?(cn=$(avp(name)))
	        </programlisting>
              </example>
            </listitem>
          </varlistentry>
        </variablelist>

        <variablelist>
          <title>Return Values:</title>

          <varlistentry>
            <term><varname>n</varname> &gt; 0 (TRUE):</term>

            <listitem>
              <itemizedlist>
                <listitem>
                  <para>Found <varname>n</varname> matching LDAP
                  entries</para>
                </listitem>
              </itemizedlist>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term><constant>-1</constant> (FALSE):</term>

            <listitem>
              <itemizedlist>
                <listitem>
                  <para>No matching LDAP entries found</para>
                </listitem>
              </itemizedlist>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term><constant>-2</constant> (FALSE):</term>

            <listitem>
              <itemizedlist>
                <listitem>
                  <para>LDAP error (e.g. LDAP server unavailable), or</para>
                </listitem>

                <listitem>
                  <para>internal error</para>
                </listitem>
              </itemizedlist>
            </listitem>
          </varlistentry>
        </variablelist>

	<para>
        This function can be used from REQUEST_ROUTE, FAILURE_ROUTE, BRANCH_ROUTE, and ONREPLY_ROUTE.
	</para>

        <example>
          <title>Example Usage</title>

          <programlisting>
...
# 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(tel_number)
ldap_result("telephoneNumber/$avp(tel_number)");
...
			</programlisting>
        </example>
      </section>

      <section id="ldap-result-fn">
        <title>ldap_result(ldap_attr_name, avp_spec, [avp_type], [regex_subst])</title>

        <para>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 <varname>ldap_search</varname> call.
        <varname>ldap_attr_name</varname> specifies the LDAP attribute name
        who's value should be stored in AVP <varname>avp_spec</varname>. Multi
        valued LDAP attributes generate an indexed AVP. The optional
        <varname>regex_subst</varname> parameter allows to further define what
        part of an attribute value should be stored as AVP.</para>

		<para>
			An AVP can either be of type string or integer. As default, <varname>ldap_result</varname> stores LDAP attribute values as AVP of type string. The optional <varname>avp_type</varname> parameter can be used to explicitly specify the type of the AVP. It can be either <varname>str</varname> for string, or <varname>int</varname> for integer. If <varname>avp_type</varname> is specified as <varname>int</varname> then <varname>ldap_result</varname> 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 successful.
		</para>

        <variablelist>
          <title>Function Parameters:</title>

          <varlistentry>
            <term>ldap_attr_name (string)</term>

            <listitem>
              <para>The name of the LDAP attribute who's value should be
              stored, e.g. <varname>SIPIdentityServiceLevel</varname> or
              <varname>telephonenumber</varname></para>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term>avp_spec (var)</term>

            <listitem>
              <para>Specification of destination AVP, e.g.
              <varname>$avp(service_level)</varname> or
              <varname>$avp(12)</varname></para>
            </listitem>
          </varlistentry>

		  <varlistentry>
            <term>avp_type (string, optional)</term>

            <listitem>
              <para>
				Specification of destination AVP type, either <varname>str</varname> or <varname>int</varname>. If this parameter is not specified then the LDAP attribute values are stored as AVP of type string.
			  </para>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term>regex_subst (string)</term>

            <listitem>
              <para>Regex substitution that gets applied to LDAP attribute
              value before storing it as AVP, e.g.
              <varname>"/^sip:(.+)$/\1/"</varname> to strip off "sip:" from
              the beginning of an LDAP attribute value.</para>
            </listitem>
          </varlistentry>
        </variablelist>

        <variablelist>
          <title>Return Values:</title>

          <varlistentry>
            <term><varname>n</varname> &gt; 0 (TRUE)</term>

            <listitem>
              <para>
				LDAP attribute <varname>ldap_attr_name</varname> found in LDAP result set and <varname>n</varname> LDAP attribute values stored in <varname>avp_spec</varname>
			  </para>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term>-1 (FALSE)</term>

            <listitem>
              <para>No LDAP attribute <varname>ldap_attr_name</varname> found
              in LDAP result set</para>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term>-2 (FALSE)</term>

            <listitem>
              <para>Internal error occurred</para>
            </listitem>
          </varlistentry>
        </variablelist>

	<para>
        This function can be used from REQUEST_ROUTE, FAILURE_ROUTE, BRANCH_ROUTE, and ONREPLY_ROUTE.
	</para>

        <example>
          <title>Example Usage</title>

          <programlisting>
...

# ldap_search call
...

# save SIPIdentityServiceLevel in $avp(service_level)
if (!ldap_result("SIPIdentityServiceLevel", $avp(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(10)
ldap_result("SIPIdentitySIPURI", $avp(10), "/^[^@]+@(.+)$/\1/");
...
			</programlisting>
        </example>
      </section>

      <section id="ldap-result-check-fn">
        <title>ldap_result_check(ldap_attr_name, string_to_match, [,
        regex_subst])</title>

        <para>This function compares <varname>ldap_attr_name</varname>'s value
        with <varname>string_to_match</varname> for equality. It accesses the LDAP result set
        fetched by the last <varname>ldap_search</varname> call. The
        optional <varname>regex_subst</varname> parameter allows to further
        define what part of the attribute value should be used for the
        equality match. If <varname>ldap_attr_name</varname> is multi valued,
        each value is checked against <varname>string_to_match</varname>. If
        one or more of the values do match the function returns <varname>1</varname>
        (TRUE).</para>

        <variablelist>
          <title>Function Parameters:</title>

          <varlistentry>
            <term>ldap_attr_name (string)</term>

            <listitem>
              <para>The name of the LDAP attribute who's value should be
              matched, e.g. <varname>SIPIdentitySIPURI</varname></para>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term>string_to_match (string)</term>

            <listitem>
              <para>String to be matched. Included AVPs and pseudo variabels
              do get expanded.</para>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term>regex_subst (string, optional)</term>

            <listitem>
              <para>Regex substitution that gets applied to LDAP attribute
              value before comparing it with string_to_match, e.g.
              <varname>"/^[^@]@+(.+)$/\1/"</varname> to extract the domain part
              of a SIP URI</para>
            </listitem>
          </varlistentry>
        </variablelist>

        <variablelist>
          <title>Return Values:</title>

          <varlistentry>
            <term>1 (TRUE)</term>

            <listitem>
              <para>One or more <varname>ldap_attr_name</varname> attribute values match
              <varname>string_to_match</varname> (after
              <varname>regex_subst</varname> is applied)</para>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term>-1 (FALSE)</term>

            <listitem>
              <para><varname>ldap_attr_name</varname> attribute not found or
              attribute value doesn't match <varname>string_to_match</varname>
              (after <varname>regex_subst</varname> is applied)</para>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term>-2 (FALSE)</term>

            <listitem>
              <para>Internal error occurred</para>
            </listitem>
          </varlistentry>
        </variablelist>

	<para>
        This function can be used from REQUEST_ROUTE, FAILURE_ROUTE, BRANCH_ROUTE, and ONREPLY_ROUTE.
        </para>

        <example>
          <title>Example Usage</title>

          <programlisting>
...
# 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;
    }
}
...
			</programlisting>
        </example>
      </section>

      <section id="ldap-result-next-fn">
        <title>ldap_result_next()</title>

        <para>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 <varname>ldap_result*</varname> 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.</para>

        <variablelist>
          <title>Return Values:</title>

          <varlistentry>
            <term>1 (TRUE)</term>

            <listitem>
              <para>Another LDAP entry is present in the LDAP result set and
              result pointer is incremented by one</para>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term>-1 (FALSE)</term>

            <listitem>
              <para>No more LDAP entries are available</para>
            </listitem>
          </varlistentry>
		  <varlistentry>
            <term><constant>-2</constant> (FALSE)</term>

            <listitem>
              <para>Internal error</para>
            </listitem>
          </varlistentry>
        </variablelist>

	<para>
        This function can be used from REQUEST_ROUTE, FAILURE_ROUTE, BRANCH_ROUTE, and ONREPLY_ROUTE.
        </para>

        <example>
          <title>Example Usage</title>

          <programlisting>
...
# ldap_search call
...

ldap_result("telephonenumber/$avp(tel1)");
if (ldap_result_next())
{
	ldap_result("telephonenumber/$avp(tel2)");
}
if (ldap_result_next())
{
	ldap_result("telephonenumber/$avp(tel3)");
}
if (ldap_result_next())
{
	ldap_result("telephonenumber/$avp(tel4)");
}
...
			</programlisting>
        </example>
      </section>

      <section id="ldap-filter-url-encode-fn">
        <title>ldap_filter_url_encode(string, avp_spec)</title>

	<para>This function applies the following escaping rules to
        <varname>string</varname> and stores the result in AVP
        <varname>avp_spec</varname>:</para>

        <table>
          <title>ldap_filter_url_encode() escaping rules</title>

          <tgroup cols="3">
            <thead>
              <row>
                <entry align="center">character in
                <varname>string</varname></entry>

                <entry align="center">gets replaced with</entry>

                <entry align="center">defined in</entry>
              </row>
            </thead>

            <tbody>
              <row>
                <entry>*</entry>

                <entry>\2a</entry>

                <entry>RFC 4515</entry>
              </row>

              <row>
                <entry>(</entry>

                <entry>\28</entry>

                <entry>RFC 4515</entry>
              </row>

              <row>
                <entry>)</entry>

                <entry>\29</entry>

                <entry>RFC 4515</entry>
              </row>

              <row>
                <entry>\</entry>

                <entry>\5c</entry>

                <entry>RFC 4515</entry>
              </row>

              <row>
                <entry>?</entry>

                <entry>%3F</entry>

                <entry>RFC 4516</entry>
              </row>
            </tbody>
          </tgroup>
        </table>

	<para>The string stored in AVP <varname>avp_spec</varname> can be safely used in an LDAP
        URL filter string.</para>

	<variablelist>
          <title>Function Parameters:</title>

          <varlistentry>
            <term><parameter>string</parameter></term>

            <listitem>
              <para>String to apply RFC 4515 and URL escpaing rules to.
	      AVPs and pseudo variables do get expanded. Example:
              <varname>"cn=$avp(name)"</varname></para>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term><parameter>avp_spec (var)</parameter></term>

            <listitem>
              <para>AVP to store resulting RFC 4515
	      and URL encoded string, e.g. <varname>$avp(ldap_search)</varname>
	      or <varname>$avp(10)</varname></para>
            </listitem>
          </varlistentry>
        </variablelist>

        <variablelist>
          <title>Return Values:</title>

          <varlistentry>
            <term><constant>1</constant> (TRUE)</term>

            <listitem>
              <para>RFC 4515 and URL encoded
              <varname>filter_component</varname> stored as AVP
              <varname>avp_name</varname></para>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term><constant>-1</constant> (FALSE)</term>

            <listitem>
              <para>Internal error</para>
            </listitem>
          </varlistentry>
        </variablelist>

	<para>
        This function can be used from REQUEST_ROUTE, FAILURE_ROUTE, BRANCH_ROUTE, and ONREPLY_ROUTE.
        </para>

        <example>
          <title>Example Usage</title>

          <programlisting>
...
if (!ldap_filter_url_encode("cn=$avp(name)", $avp(name_esc)))
{
    # RFC 4515/URL encoding failed --> silently discard request
    exit;
}

xlog("L_INFO", "encoded LDAP filter component: [$avp(name_esc)]\n");

if (ldap_search(
     "ldap://h350/ou=commObjects,dc=example,dc=com??sub?($avp(name_esc))"))
    { ... }
...
			</programlisting>
        </example>
      </section>
    </section>

    <section>
      <title>Exported Async Functions</title>

      <section id="ldap-search-async-fn">
        <title>ldap_search(ldap_url)</title>

        <para>Performs an LDAP search operation using given LDAP URL and stores result
        internally for later retrieval by <varname>ldap_result*</varname> functions. If one ore
        more LDAP entries are found the function returns the number of found
        entries which evaluates to TRUE in the OpenSIPS configuration script.
        It returns <varname>-1</varname> (<varname>FALSE</varname>) in case no
        LDAP entry was found, and <varname>-2</varname>
        (<varname>FALSE</varname>) if an internal error like e.g. an LDAP
        error occurred.</para>

        <variablelist>
          <title>Function Parameters:</title>

          <varlistentry>
            <term><parameter>ldap_url (string)</parameter></term>

            <listitem>
              <para>An LDAP URL defining the LDAP search operation (refer to
			  <xref linkend="ldap-urls"/> 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.</para>

              <example>
                <title>Example Usage of ldap_url</title>

                <para>Search with LDAP session named
                <varname>sipaccounts</varname>, base
                <varname>ou=sip,dc=example,dc=com</varname>,
                <varname>one</varname> level deep using search filter
                <varname>(cn=schlatter)</varname> and returning all
                attributes:</para>

                <programlisting>ldap://sipaccounts/ou=sip,dc=example,dc=com??one?(cn=schlatter)</programlisting>

                <para>Subtree search with LDAP session named
                <varname>ldap1</varname>, base
                <varname>dc=example,dc=com</varname> using search filter
                <varname>(cn=$(avp(name)))</varname> and returning
                <varname>SIPIdentityUserName</varname> and
                <varname>SIPIdentityServiceLevel</varname> attributes</para>

                <programlisting>
ldap://ldap_1/dc=example,dc=com?
       SIPIdentityUserName,SIPIdentityServiceLevel?sub?(cn=$(avp(name)))
	        </programlisting>
              </example>
            </listitem>
          </varlistentry>
        </variablelist>

        <variablelist>
          <title>Return Values:</title>

          <varlistentry>
            <term><varname>n</varname> &gt; 0 (TRUE):</term>

            <listitem>
              <itemizedlist>
                <listitem>
                  <para>Found <varname>n</varname> matching LDAP
                  entries</para>
                </listitem>
              </itemizedlist>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term><constant>-1</constant> (FALSE):</term>

            <listitem>
              <itemizedlist>
                <listitem>
                  <para>No matching LDAP entries found</para>
                </listitem>
              </itemizedlist>
            </listitem>
          </varlistentry>

          <varlistentry>
            <term><constant>-2</constant> (FALSE):</term>

            <listitem>
              <itemizedlist>
                <listitem>
                  <para>LDAP error (e.g. LDAP server unavailable), or</para>
                </listitem>

                <listitem>
                  <para>internal error</para>
                </listitem>
              </itemizedlist>
            </listitem>
          </varlistentry>
        </variablelist>

	<para>
        This function can be used from REQUEST_ROUTE, FAILURE_ROUTE, BRANCH_ROUTE, and ONREPLY_ROUTE.
	</para>

        <example>
          <title>Example Usage</title>

          <programlisting>
...
# ldap search

route {
	async( ldap_search("ldap://sipaccounts/ou=sip,dc=example,dc=com??one?(cn=$rU)"), resume);
}
....
route[resume] {
{
    switch ($rc)
    {
    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(tel_number)
    ldap_result("telephoneNumber", $avp(tel_number)");
...
}
			</programlisting>
        </example>
      </section>


    </section>



    <section>
      <title>Installation &amp; Running</title>

      <section>
        <title>Compiling the Module</title>

        <para>
			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 <ulink url="http://www.openldap.org/">http://www.openldap.org/</ulink>.
		</para>
		<para>
			The OpenLDAP library is available pre-compiled for most UNIX/Linux flavors. On Debian/Ubuntu, the following packages must be installed: <programlisting># apt-get install libldap2 libldap2-dev</programlisting>.
		</para>
      </section>
    </section>
  </chapter>

