The client should already have OpenLDAP libraries from Section 2.1.3, “Configuring the Client”, but if you are installing several client machines you will need to install net/openldap24-client on each of them.
FreeBSD requires two ports to be installed to authenticate against an LDAP server, security/pam_ldap and net/nss_ldap.
security/pam_ldap is configured via
	/usr/local/etc/ldap.conf.
This is a different file than the
	  OpenLDAP library functions'
	  configuration file,
	  /usr/local/etc/openldap/ldap.conf;
	  however, it takes many of the same options; in fact it is a
	  superset of that file.  For the rest of this section,
	  references to ldap.conf will mean
	  /usr/local/etc/ldap.conf.
Thus, we will want to copy all of our original
	configuration parameters from
	openldap/ldap.conf to the new
	ldap.conf.  Once this is done, we want to
	tell security/pam_ldap what to look for on
	the directory server.
We are identifying our users with the
	uid attribute.  To configure this (though
	it is the default), set the
	pam_login_attribute directive in
	ldap.conf:
With this set, security/pam_ldap will
	search the entire LDAP directory under base
	for the value
	uid=.
	If it finds one and only one entry, it will attempt to bind as
	that user with the password it was given.  If it binds
	correctly, then it will allow access.  Otherwise it will
	fail.username
Users whose shell is not in
	/etc/shells will not be able to log in.
	This is particularly important when
	Bash is set as the user shell on
	the LDAP server.  Bash is not
	included with a default installation of FreeBSD.  When installed
	from a package or port, it is located at
	/usr/local/bin/bash.  Verify that the
	path to the shell on the server is set correctly:
%getent passwdusername
There are two choices when the output shows
	/bin/bash in the last column.  The first is
	to change the user's entry on the LDAP server to
	/usr/local/bin/bash.  The second option
	is to create a symlink on the LDAP client computer so
	Bash is found at the correct
	location:
#ln -s /usr/local/bin/bash /bin/bash
Make sure that /etc/shells contains
	entries for both /usr/local/bin/bash and
	/bin/bash.  The user will then be able to
	log in to the system with Bash as
	their shell.
PAM, which stands for “Pluggable Authentication Modules”, is the method by which FreeBSD authenticates most of its sessions. To tell FreeBSD we wish to use an LDAP server, we will have to add a line to the appropriate PAM file.
Most of the time the appropriate PAM file is
	  /etc/pam.d/sshd, if you want to use
	  SSH (remember to set the relevant
	  options in /etc/ssh/sshd_config,
	  otherwise SSH will not use
	  PAM).
To use PAM for authentication, add the line
auth sufficient /usr/local/lib/pam_ldap.so no_warn
Exactly where this line shows up in the file and which options appear in the fourth column determine the exact behavior of the authentication mechanism; see pam.d(5)
With this configuration you should be able to authenticate a user against an LDAP directory. PAM will perform a bind with your credentials, and if successful will tell SSH to allow access.
However it is not a good idea to allow every user in the directory into every client machine. With the current configuration, all that a user needs to log into a machine is an LDAP entry. Fortunately there are a few ways to restrict user access.
ldap.conf supports a
	  pam_groupdn directive; every account that
	  connects to this machine needs to be a member of the group
	  specified here.  For example, if you have
pam_groupdn cn=servername,ou=accessgroups,dc=example,dc=org
in ldap.conf, then only members of
	  that group will be able to log in.  There are a few things
	  to bear in mind, however.
Members of this group are specified in one or more
	  memberUid attributes, and each attribute
	  must have the full distinguished name of the member.  So
	  memberUid: someuser will not work; it
	  must be:
memberUid: uid=someuser,ou=people,dc=example,dc=org
Additionally, this directive is not checked in PAM
	  during authentication, it is checked during account
	  management, so you will need a second line in your PAM files
	  under account.  This will require, in
	  turn, every user to be listed in the
	  group, which is not necessarily what we want.  To avoid
	  blocking users that are not in LDAP, you should enable the
	  ignore_unknown_user attribute.  Finally,
	  you should set the
	  ignore_authinfo_unavail option so that
	  you are not locked out of every computer when the LDAP
	  server is unavailable.
Your pam.d/sshd might then end up
	  looking like this:
pam.d/sshdauth required pam_nologin.so no_warn auth sufficient pam_opie.so no_warn no_fake_prompts auth requisite pam_opieaccess.so no_warn allow_local auth sufficient /usr/local/lib/pam_ldap.so no_warn auth required pam_unix.so no_warn try_first_pass account required pam_login_access.so account required /usr/local/lib/pam_ldap.so no_warn ignore_authinfo_unavail ignore_unknown_user
Since we are adding these lines specifically to
	    pam.d/sshd, this will only have an
	    effect on SSH sessions.  LDAP
	    users will be unable to log in at the console.  To change
	    this behavior, examine the other files in
	    /etc/pam.d and modify them
	    accordingly.
NSS is the service that maps
	attributes to names.  So, for example, if a file is owned by
	user 1001, an application will query
	NSS for the name of
	1001, and it might get
	bob or ted or whatever
	the user's name is.
Now that our user information is kept in LDAP, we need to tell NSS to look there when queried.
The net/nss_ldap port does this.  It
	uses the same configuration file as
	security/pam_ldap, and should not need any
	extra parameters once it is installed.  Instead, what is left
	is simply to edit /etc/nsswitch.conf to
	take advantage of the directory.  Simply replace the following
	lines:
group: compat passwd: compat
with
group: files ldap passwd: files ldap
This will allow you to map usernames to UIDs and UIDs to usernames.
Congratulations! You should now have working LDAP authentication.
Unfortunately, as of the time this was written FreeBSD did not support changing user passwords with passwd(1). Because of this, most administrators are left to implement a solution themselves. I provide some examples here. Note that if you write your own password change script, there are some security issues you should be made aware of; see Section 4.3, “Password Storage”
#!/bin/sh stty -echo read -p "Old Password: " oldp; echo read -p "New Password: " np1; echo read -p "Retype New Password: " np2; echo stty echo if [ "$np1" != "$np2" ]; then echo "Passwords do not match." exit 1 fi ldappasswd -D uid="$USER",ou=people,dc=example,dc=org \ -w "$oldp" \ -a "$oldp" \ -s "$np1"
This script does hardly any error checking, but more
	  important it is very cavalier about how it stores your
	  passwords.  If you do anything like this, at least adjust
	  the security.bsd.see_other_uids sysctl
	  value:
#sysctl security.bsd.see_other_uids=0
A more flexible (and probably more secure) approach can be used by writing a custom program, or even a web interface. The following is part of a Ruby library that can change LDAP passwords. It sees use both on the command line, and on the web.
require 'ldap'
require 'base64'
require 'digest'
require 'password' # ruby-password
ldap_server = "ldap.example.org"
luser = "uid=#{ENV['USER']},ou=people,dc=example,dc=org"
# get the new password, check it, and create a salted hash from it
def get_password
  pwd1 = Password.get("New Password: ")
  pwd2 = Password.get("Retype New Password: ")
  raise if pwd1 != pwd2
  pwd1.check # check password strength
  salt = rand.to_s.gsub(/0\./, '')
  pass = pwd1.to_s
  hash = "{SSHA}"+Base64.encode64(Digest::SHA1.digest("#{pass}#{salt}")+salt).chomp!
  return hash
end
oldp = Password.get("Old Password: ")
newp = get_password
# We'll just replace it.  That we can bind proves that we either know
# the old password or are an admin.
replace = LDAP::Mod.new(LDAP::LDAP_MOD_REPLACE | LDAP::LDAP_MOD_BVALUES,
                        "userPassword",
                        [newp])
conn = LDAP::SSLConn.new(ldap_server, 389, true)
conn.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3)
conn.bind(luser, oldp)
conn.modify(luser, [replace])Although not guaranteed to be free of security holes (the
	password is kept in memory, for example) this is cleaner and
	more flexible than a simple sh
	script.
All FreeBSD documents are available for download at https://download.freebsd.org/ftp/doc/
Questions that are not answered by the
    documentation may be
    sent to <freebsd-questions@FreeBSD.org>.
    Send questions about this document to <freebsd-doc@FreeBSD.org>.