



= LDAP (Lightweight Directory Access Protocol) Module

The `ldap` module allows LDAP directory entries to be retrieved, modified,
inserted and deleted.

NOTE: May also perform user authentication using LDAP binds, or by retrieving
the contents of a password attribute for later comparison by a module such
as `pap`, or an `eap` method.

Please see the file `global.d/ldap` for server-global configuration items
which control LDAP library debugging.



## Configuration Settings

ldap { ... }::


server::

Note that this needs to match the name(s) in the LDAP server
certificate, if you're using ldaps.  See OpenLDAP documentation
for the behavioral semantics of specifying more than one host.

Depending on the `libldap` in use, server may be specified as an LDAP
URI.  In the case of `OpenLDAP` this allows the following
additional schemes:

[options="header,autowidth"]
|===
| Scheme   | Description
| ldaps:// | (LDAP over SSL)
| ldapi:// | (LDAP over Unix socket)
| ldapc:// | (Connectionless LDAP)
|===



port:: Port to connect on, defaults to 389, will be ignored for LDAP URIs.



identity::  Administrator account for searching and possibly modifying.

WARNING: If using SASL + (KRB5 | EXTERNAL) identity should be commented out
as it will set an authzid, which is likely not what you want.



password:: Password for the `identity` account.



base_dn:: Unless overridden in another section, the dn from which all
searches will start from.



[NOTE]
====
You can run the `ldapsearch` command line tool using the
parameters from this module's configuration.

  ldapsearch -D ${identity} -w ${password} -h ${server} -b 'CN=user,${base_dn}'

That will give you the LDAP information for 'user'.

Group membership can be queried by using the above "ldapsearch" string,
and adding "memberof" qualifiers.  For ActiveDirectory, use:

  ldapsearch ... '(&(objectClass=user)(sAMAccountName=user)(memberof=CN=group,${base_dn}))'

Where 'user' is the user as above, and 'group' is the group you are querying for.
====



### SASL parameters to use for admin binds

sasl { ... }::

When we're prompted by the SASL library, the config items in the SASL
section (in addition to the identity password config items above)
determine the responses given.

If any directive is commented out, a NULL response will be
provided to cyrus-sasl.

Unfortunately the only way to control Keberos here is through
environmental variables, as cyrus-sasl provides no API to
set the kerberos (libkrb5) config directly.

Full documentation for MIT krb5 can be
found at http://web.mit.edu/kerberos/krb5-devel/doc/admin/env_variables.html

At a minimum you probably want to set `KRB5_CLIENT_KTNAME`.


mech:: SASL mechanism.



proxy:: SASL authorisation identity to proxy.



realm:: SASL realm. Used for kerberos.



### Generic valuepair attribute

If set, this will attribute will be retrieved in addition to any
mapped attributes.

Values should be in the format:

  <fr attr> <op> <value>

Where:

[options="header,autowidth"]
|===
| Parameter  | Description
| <fr attr>  | Is the attribute you wish to create,
               with any valid list and request qualifiers.
| <op>       | Is any assignment operator (`=`, `:=`, `+=`, `-=`).
| <value>    | Is the value to parse into the new attribute.
               If the value is wrapped in double quotes it
               will be xlat expanded.
|===



### Mapping of LDAP directory attributes to RADIUS dictionary attributes.

WARNING: Although this format is almost identical to the `unlang`
update section format, it does *NOT* mean that you can use other
`unlang` constructs in module configuration files.

Configuration items are in the format:

  <fr attr> <op> <ldap attr>

Where:

[options="header,autowidth"]
|===
| Parameter   | Description
| <fr attr>   | Is the destination RADIUS attribute
                with any valid list and request qualifiers.
| <op>        | Is any assignment attribute (=, :=, +=, -=).
| <ldap attr> | Is the attribute associated with user or
                profile objects in the LDAP directory.
                If the attribute name is wrapped in double quotes
                it will be `xlat` expanded.
|===

Request and list qualifiers may be placed after the `update`
section name to set default destination requests/lists
for `<fr attr>s` with no list qualifiers.

These attribute maps are applied _before_ any profiles, meaning that
the values here can be referenced in profiles using expansions.

NOTE: LDAP attribute names should be single quoted unless you want
the name to be derived from an xlat expansion, or an attribute ref.

update { ... }::


NOTE: Where only a list is specified as the RADIUS attribute,
the value of the LDAP attribute is parsed as a valuepair
in the same format as the 'valuepair_attribute' (above).


edir:: Set to `yes` if you have eDirectory and want to use the universal
password mechanism.



edir_autz:: Set to `yes` if you want to bind as the user after retrieving the
`Password.Cleartext`. This will consume the login grace, and verify user authorization.



[NOTE]
====
  * The option `set_auth_type` was removed in `v3.x.x`.
  * Equivalent functionality can be achieved by adding the following
  stanza to the `recv Access-Request {}` section of your virtual server.

e.g:

[source, unlang]
----
ldap
if ((ok || updated) && &User-Password) {
	&control.Auth-Type := ::ldap
}
----
====



### User object Identification


base_dn:: Where to start searching in the tree for users.



filter:: Filter for user objects, should be specific enough
to identify a single user object.


For Active Directory nested group, you should comment out the previous 'filter = ...'
and use the below. Where 'group' is the group you are querying for.

NOTE: The string '1.2.840.113556.1.4.1941' specifies LDAP_MATCHING_RULE_IN_CHAIN.
This applies only to DN attributes. This is an extended match operator that walks
the chain of ancestry in objects all the way to the root until it finds a match.
This reveals group nesting. It is available only on domain controllers with
Windows Server 2003 SP2 or Windows Server 2008 (or above).

See: https://social.technet.microsoft.com/wiki/contents/articles/5392.active-directory-ldap-syntax-filters.aspx



sasl { ... }:: SASL parameters to use for user binds

When we're prompted by the SASL library, these control
the responses given.

NOTE: Any of the config items below may be an attribute ref
or and expansion.  This allows different SASL mechs,
proxy IDs and realms to be used for different users.


mech:: SASL mechanism.



authname:: SASL authentication name.  Mechanism specific value
to use when prompted for the client authentication name.



proxy:: SASL authorisation identity to proxy.



realm:: SASL realm. Used for kerberos.



password_attribute:: Which attribute in the request should be used as
the password when performing user binds.

Note that Active Directory will allow unauthenticated user binds by default!

You can fix this by choosing the "ADSI Edit" command from the Server Manager's Tools menu.
Then, open the Configuration subtree, and then open the properties of the `CN=Directory
Service, CN=Windows NT, CN=Services, CN=Configuration` object.  Modify the
`msDS-Other-Settings` attribute, and add a new entry for `DenyUnauthenticatedBind=1`.



scope:: Search scope, may be `base`, `one`, `sub' or `children`.



sort_by:: Server side result sorting.

A list of space delimited attributes to order the result set by.

  * If the filter matches multiple objects only the first
result will be processed.

  * If the attribute name is prefixed with a hyphen '-' the
sorting order will be reversed for that attribute.

  * If `sort_by` is set, and the server does not support sorting
the search will fail.

  * If a search returns multiple user objects and `sort_by` is not
set, the search will fail.



access_attribute:: If this is undefined, anyone is authorised.

If it is defined, the contents of this attribute determine whether
or not the user is authorised.



access_positive:: Control whether the presence of `access_attribute`
allows access, or denies access.

  * If `yes`, and the `access_attribute` is present, or `no` and the
`access_attribute` is absent then access  will be allowed.

  * If `yes`, and the `access_attribute` is absent, or `no` and the
`access_attribute` is present, then access will not be allowed.

  * If the value of the retrieved `access_attribute` is `false`, it will
negate the result.

e.g:

  access_positive = yes
  access_attribute = userAccessAllowed

With an LDAP object containing:

  userAccessAllowed: false

Will result in the user being locked out.



access_value_negate:: Which value we look for in access_attribute
to indicate that we should negate the result.



access_value_suspend:: Which value we look for in access_attribute
to indicate that the user should be suspended.



expect_password:: When set to no, disable warnings for missing password
attributes in user objects returned from LDAP.  This is useful for
ISP environments where some subscribers have passwords set, and others
do not (e.g. mixed IPoE and PPPoE).



### User membership checking


base_dn:: Where to start searching in the tree for groups.



filter:: Filter for group objects, should match all available
group objects a user might be a member of.

If using Active Directory you are likely to need `group`
instead of `posixGroup`.



scope:: Search scope, may be `base`, `one`, `sub` or `children`.



name_attribute:: Attribute that uniquely identifies a group.

Is used when converting group DNs to group names.



membership_filter:: Filter to find all group objects a user is a member of.

That is, group objects with attributes that identify
members (the inverse of `membership_attribute`).



membership_attribute:: The attribute, in user objects, which contain
the names or DNs of groups a user is a member of.

Unless a conversion between group name and group DN is
needed, there's no requirement for the group objects
referenced to actually exist.

[NOTE]
====
If the LDAP server does not support the `memberOf` attribute (or equivalent),
then you will need to use the membership_filter option above instead.
If you can't see the `memberOf` attribute then it is also possible that the
LDAP bind user does not have the correct permissions to view it.
====



cacheable_name:: If `cacheable_name` or `cacheable_dn` are enabled,
all group information for the user will be retrieved from the directory
and written to `LDAP-Group` attributes appropriate for the instance of rlm_ldap.

For group comparisons these attributes will be checked instead of querying
the LDAP directory directly.

This feature is intended to be used with `rlm_cache`, but may also be useful
if all group values need to be processed using `unlang` policies.

If you wish to use this feature, you should enable the type that matches the
format of your check items.

i.e. if your groups are specified as DNs then enable `cacheable_dn`
else enable `cacheable_name`.



cacheable_dn:: See `cacheable_name` for more details.



cache_attribute:: Override the normal cache attribute (`<inst>-LDAP-Group` or
`LDAP-Group` if using the default instance) and create a custom attribute.

This can help if multiple module instances are used in fail-over.



allow_dangling_group_ref::

If the group being checked is specified as a name, but the user's groups are
referenced by DN, and one of those group DNs is invalid, the whole group check
is treated as invalid, and a negative result will be returned.

When set to `yes`, this option ignores invalid DN references.



group_attribute:: Override the normal group comparison attribute name
`(<inst>-Group` or `LDAP-Group` if using the default instance).



skip_on_suspend::

Don't process user groups if the user has been suspended.
If set to 'no', groups will still be processed.

Groups are never processed for disabled users.

Defaults to 'yes'.



### User profiles

RADIUS profile objects contain sets of attributes to insert into the request.
These attributes are mapped using the same mapping scheme applied to user
objects (the update section above).


filter:: Filter for RADIUS profile objects.



scope:: Search scope, may be `base`, `one`, `sub` or `children`.

Should usually be left as "base", to retrieve the specific profile
specified by 'default' or in the user or group objects.



default:: The default profile. This may be a DN or an attribute reference.

NOTE: To get old v2.2.x style behaviour, or to use the `&User-Profile` attribute
to specify the default profile, set this to `&control.User-Profile`.



attribute:: The LDAP attribute containing profile DNs to apply
in addition to the default profile above.

These are retrieved from the user object, at the same time as the
attributes from the update section, are are applied if authorization
is successful.



attribute_suspend: The LDAP attribute containing profile DNs to apply
in addition to the default profile above, when the user account is in
the suspended state

These are retrieved from the user object, at the same time as the
attributes from the update section, are are applied if authorization
is successful.



### Modify user object on receiving Accounting-Request

Useful for recording things like the last time the user logged
in, or the `Acct-Session-ID` for CoA/DM.

LDAP modification items are in the format:

  <ldap attr> <op> <value>

Where:

[options="header,autowidth"]
|===
| Parameter   | Description
| <ldap attr> | The LDAP attribute to add modify or delete.
| <op>        | One of the assignment operators: (`:=`, `+=`, `-=`, `++`).
                Note: `=` is *not* supported.
| <value>     | The value to add modify or delete.
|===

WARNING: If using the `:=` operator with a multi-valued LDAP
attribute, all instances of the attribute will be removed and
replaced with a single attribute.






### Post-Auth can modify LDAP objects too



### LDAP connection-specific options

These options set timeouts, keep-alives, etc. for the connections.


dereference:: Control under which situations aliases are followed.

May be one of 'never', 'searching', 'finding' or 'always'

default: libldap's default which is usually 'never'.

NOTE: `LDAP_OPT_DEREF` is set to this value.



chase_referrals:: controls whether the server follows references returned
by the LDAP directory.

They are mostly for Active Directory compatibility.
If you set this to `no`, then searches will likely return 'operations error',
instead of a useful result.



rebind:: If `chase_referrals` is `yes` then, when a referral is followed
having `rebind` set to `no` will cause the server to do an anonymous bind when
making any additional connections.  Setting this to `yes` will either bind
with the admin credentials or the credentials from the rebind url depending
on `use_referral_credentials`.



use_referral_credentials:: On `rebind`, use the credentials from the rebind url
instead of admin credentials used during the initial bind.

Default `no`



session_tracking:: If `yes`, then include `link:https://tools.ietf.org/html/draft-wahl-ldap-session-03[draft-wahl-ldap-session]` tracking
controls.

If yes, encodes `link:https://freeradius.org/rfc/rfc2865.html#NAS-IP-Address[NAS-IP-Address]`, `link:https://freeradius.org/rfc/rfc3162.html#NAS-IPv6-Address[NAS-IPv6-Address]`, `link:https://freeradius.org/rfc/rfc2865.html#User-Name[User-Name]`, `link:https://freeradius.org/rfc/rfc2866.html#Acct-Session-Id[Acct-Session-Id]`,
`link:https://freeradius.org/rfc/rfc2866.html#Acct-Multi-Session-Id[Acct-Multi-Session-Id]` as session tracking controls in applicable LDAP operations.

Default `no`



sasl_secprops:: SASL Security Properties (see SASL_SECPROPS in ldap.conf man page).

NOTE: uncomment when using GSS-API sasl mechanism along with TLS encryption against
Active-Directory LDAP servers (this disables sealing and signing at the GSS level as
required by AD).



res_timeout:: Seconds to wait for LDAP query to finish.

Default: `20`



srv_timelimit:: Seconds LDAP server has to process the query (server-side
time limit).

Default: `20`

NOTE: `LDAP_OPT_TIMELIMIT` is set to this value.



idle:: Set the number of seconds a connection needs to remain idle
before  TCP starts sending keepalive probes.

NOTE: `LDAP_OPT_X_KEEPALIVE_IDLE` is set to this value.



probes:: Set the maximum number of keepalive probes TCP should send
before dropping  the  connection.

NOTE: `LDAP_OPT_X_KEEPALIVE_PROBES` is set to this value.



interval:: Set the interval in seconds between individual keepalive probes.

NOTE: `LDAP_OPT_X_KEEPALIVE_INTERVAL` is set to this value.



net_timeout:: Sets the timeout for establishing connections.

NOTE: `LDAP_OPT_NETWORK_TIMEOUT` is set to this value.



reconnection_delay:: Sets the time in seconds before a failed connection
will attempt reconnection.  This includes failures to bind as the admin
user due to incorrect credentials.



### TLS encrypted connections

This subsection configures the `tls` related items that control how FreeRADIUS
connects to an LDAP server. It contains all of the `tls_*` configuration entries
used in older versions of FreeRADIUS.

Those configuration entries can still be used, but we recommend using these.


start_tls:: Set this to `yes` to use TLS encrypted connections
to the LDAP database by using the StartTLS extended operation.

The StartTLS operation is supposed to be used with normal ldap connections
instead of using ldaps (port 636) connections



NOTE: If `start_tls = yes`, then fill up those such options with the certificate information.



require_cert:: Certificate Verification requirements.

May be one of:

[options="header,autowidth"]
|===
| Option   | Description
| 'never'  | do not even bother trying.
| 'allow'  | try, but don't fail if the certificate cannot be verified.
| 'demand' | fail if the certificate does not verify.
| 'hard'   | similar to 'demand' but fails if TLS cannot negotiate.
|===

NOTE: The default is libldap's default, which varies based on the contents of `ldap.conf`.



Minimum TLS version to accept. We STRONGLY recommend
setting this to "1.2"



### Connection Pool

The connection pool is a set of per-thread parameters for connections
to the LDAP server.

This connection pool is used for LDAP queries run as the administrative user.

All LDAP operations are performed asynchronously, meaning that many queries
can be active on a single connection simultaneously.


start:: Connections to create during module instantiation.

If the server cannot create specified number of connections
during instantiation it will exit.
Set to `0` to allow the server to start without the directory
being available.



min:: Minimum number of connections to keep open.



max:: Maximum number of connections.

If these connections are all fully in use (refer to per_connection_max below)
and a new one is requested, the request will NOT get a connection.



connecting:: Number of connections which can be starting at once

Used to throttle connection spawning.



uses:: Number of uses before the connection is closed.

NOTE: A setting of `0` means infinite (no limit).



lifetime:: The lifetime (in seconds) of the connection.



open_delay:: Open delay (in seconds).

How long must we be above the target utilisation for connections to be opened.


close_delay:: Close delay (in seconds).

How long we must be below the target utilisation for connections to be closed



manage_interval:: How often to manage the connection pool.



request:: Options specific to requests handled by this connection pool


per_connection_max::  Maximum number of active queries there can be on a
single connection.



per_connection_target::  Target number of active queries on a single connection.



free_delay:: How long must a request in the unassigned (free) list not have been
used for before it's cleaned up and actually freed.

Unassigned requests can be re-used, multiple times, reducing memory allocation
and freeing overheads.



### Bind Connection Pool

This connection pool is used for LDAP binds used to authenticate requests when
calling the ldap module in authenticate context.  If passwords are retrieved
from the ldap directory and FreeRADIUS performs the authentication then this is
not used.

The options are essentially identical to the pool section above with certain
limitations.  Since only one bind operation can be in progress on a connection at
a time, `per_connection_max` and `per_connection_target` are always set to 1.

This limitation means that `max` represents the maximum number of in progress
binds which there can be on a single thread.



## Expansions

The rlm_ldap provides the below xlat's functions.

### %ldap.uri.escape(...)

Escape a string for use in an LDAP filter or DN.  The value will then be marked as safe for use
in LDAP URIs and DNs, and will not be escaped or modified.

.Return: _string_

.Example

[source,unlang]
----
&my-string := "ldap:///ou=profiles,dc=example,dc=com??sub?(objectClass=radiusprofile)"
&reply.Reply-Message := "The LDAP url is %ldap.uri.escape(%{my-string}}"
----

.Output

```
"The LDAP url is ldap:///ou=profiles,dc=example,dc=com??sub?\28objectClass=radiusprofile\29"
```

### %ldap.uri.safe(...)

Mark a string as safe for use in an LDAP filter or DN.  Values marked as safe for use in LDAP
URIs will not be escaped or modified, and will be allowed in places where dynamic values are
usually prohibited.

.Return: _string_

.Example

[source,unlang]
----
&my-int := "%ldap.profile(ldap://%ldap.uri.safe(%{LDAP-Host}):%ldap.uri.safe(%{LDAP-Port})/ou=profiles,dc=example,dc=com??sub?(objectClass=radiusprofile)"
----

### %ldap.uri.unescape(...)

Unescape a string for use in an LDAP filter or DN.

.Return: _string_

.Example

[source,unlang]
----
&my-string := "ldap:///ou=profiles,dc=example,dc=com??sub?\28objectClass=radiusprofile\29"
&reply.Reply-Message := "The LDAP url is %ldap.uri.unescape(%{my-string})"
----

.Output

```
"The LDAP url is ldap:///ou=profiles,dc=example,dc=com??sub?(objectClass=radiusprofile)"
```

### %ldap.group(...)

Check whether the current user is a member of a the given group.  If the attribute
`control.LDAP-UserDN` exists, that will be used as the "user" object.  If it does
not then the user is first looked up using the filter form the `user { }` section
of the module configuration.

Groups can be specified either as a name or a DN, with a lookup used if necessary
to convert to the required format.

.Return: _bool_

.Example

[source,unlang]
---
if (%ldap.group('cn=group1,ou=Groups,dc=example,dc=org')) {
  ...
}
---


== Default Configuration

```
ldap {
	server = 'localhost'
#	server = 'ldap.rrdns.example.org'
#	server = 'ldap.rrdns.example.org'
#	port = 389
#	identity = 'cn=admin,dc=example,dc=org'
#	password = mypass
	base_dn = 'dc=example,dc=org'
	sasl {
#		mech = 'PLAIN'
#		proxy = 'autz_id'
#		realm = 'example.org'
	}
#	valuepair_attribute = 'radiusAttribute'
	update {
		&control.Password.With-Header	+= 'userPassword'
#		&control.Password.NT		:= 'ntPassword'
#		&reply.Reply-Message		:= 'radiusReplyMessage'
#		&reply.Tunnel-Type		:= 'radiusTunnelType'
#		&reply.Tunnel-Medium-Type	:= 'radiusTunnelMediumType'
#		&reply.Tunnel-Private-Group-ID	:= 'radiusTunnelPrivategroupId'
		&control			+= 'radiusControlAttribute'
		&request			+= 'radiusRequestAttribute'
		&reply				+= 'radiusReplyAttribute'
	}
#	edir = no
#	edir_autz = no
	user {
		base_dn = "${..base_dn}"
		filter = "(uid=%{&Stripped-User-Name || &User-Name})"
#		filter = "(&(objectClass=user)(sAMAccountName=%{&Stripped-User-Name || &User-Name})(memberOf:1.2.840.113556.1.4.1941:=cn=group,${..base_dn}))"
		sasl {
#			mech = 'PLAIN'
#			authname = &User-Name
#			proxy = &User-Name
#			realm = 'example.org'
		}
#		password_attribute = &User-Password
#		scope = 'sub'
#		sort_by = '-uid'
#		access_attribute = 'dialupAccess'
#		access_positive = yes
#		access_value_negate = 'false'
#		access_value_suspend = 'suspended'
#		expect_password = no
	}
	group {
		base_dn = "${..base_dn}"
		filter = '(objectClass=posixGroup)'
#		scope = 'sub'
#		name_attribute = cn
#		membership_filter = "(|(member=%{control.Ldap-UserDn})(memberUid=%{&Stripped-User-Name || &User-Name}))"
		membership_attribute = 'memberOf'
#		cacheable_name = 'no'
#		cacheable_dn = 'no'
#		cache_attribute = 'LDAP-Cached-Membership'
#		allow_dangling_group_ref = 'no'
		group_attribute = "${..:instance}-Group"
		skip_on_suspend = 'yes'
	}
	profile {
#		filter = '(objectclass=radiusprofile)'
#		scope = 'base'
#		default = 'cn=radprofile,dc=example,dc=org'
#		attribute = 'radiusProfileDn'
#		attribute_suspend = 'radiusProfileDn'
	}
	accounting {
		reference = "%tolower(type.%{Acct-Status-Type})"
		type {
			start {
				update {
					description := "Online at %S"
				}
			}
			interim-update {
				update {
					description := "Last seen at %S"
				}
			}
			stop {
				update {
					description := "Offline at %S"
				}
			}
		}
	}
	post-auth {
		update {
			description := "Authenticated at %S"
		}
	}
	options {
#		dereference = 'always'
		chase_referrals = yes
		rebind = yes
		use_referral_credentials = no
#		session_tracking = yes
#		sasl_secprops = 'noanonymous,noplain,maxssf=0'
		res_timeout = 10
		srv_timelimit = 3
		idle = 60
		probes = 3
		interval = 3
		net_timeout = 10
		reconnection_delay = 10
	}
	tls {
#		start_tls = yes
#		ca_file	= ${certdir}/cacert.pem
#		ca_path	= ${certdir}
#		certificate_file = /path/to/radius.crt
#		private_key_file = /path/to/radius.key
#		random_file = /dev/urandom
#		require_cert = 'demand'
#		tls_min_version = "1.2"
	}
	pool {
		start = 0
		min = 1
		max = 5
		connecting = 2
		uses = 0
		lifetime = 0
#		open_delay = 0.2
#		close_delay = 10
#		manage_interval = 0.2
		request {
#			per_connection_max = 2000
#			per_connection_target = 1000
#			free_delay = 10
		}
	}
	bind_pool {
		start = 0
		min = 1
		max = 1000
	}
}
```
