Install and configure OpenLDAP server in FreePBX
Concept
Before diving into the installation and configuration, it’s better to know some terms used in LDAP.
Attribute
An attribute is a characteristic of an object. For example, an email of an account.
Object Class
An object class defines what attributes that object can have. For example, we define an object class, InetOrgPerson, it may contain displayName and mail attributes. Depends on the definition of object class, the attributes specified can be mandatory or optional.
Distinguished Name (DN)
Distinguished Name lets us uniquely identify the object. It is similar to the file path in a reverse order. For example, uid=JohnDoe,OU=People,DC=abc,DC=local is a DN
Entry
An entry is just an object. You define what object class this entry belongs to & each object class defines what attributes this object has. Each entry can belong to multiple object classes and need to have all mandatory attributes specified in all object classes it belongs to.
Schema
A schema contains the definitions of various attributes and object classes.
Domain Component (DC) & Organizational Unit (OU)
They are containers, contains object & let you manage objects in a hierarchy manner. People use them commonly.
OpenLDAP Installation
Install OpenLDAP related packages
sudo yum install openldap* -y
sudo systemctl start slapd
sudo systemctl enable slapd
sudo systemctl status slapd # Check service is started & enabled
● slapd.service - OpenLDAP Server Daemon
Loaded: loaded (/usr/lib/systemd/system/slapd.service; enabled; vendor preset: disabled)
Active: active (running) since Tue 2023-10-17 11:20:41 BST; 1 weeks 0 days ago
Docs: man:slapd
man:slapd-config
man:slapd-hdb
man:slapd-mdb
file:///usr/share/doc/openldap-servers/guide.html
Main PID: 1922 (slapd)
CGroup: /system.slice/slapd.service
└─1922 /usr/sbin/slapd -u ldap -h ldapi:/// ldap:///
Oct 24 16:46:06 testsystem.myserver.co.uk slapd[1922]: conn=1604 fd=22 ACCEPT from IP=192.168.1.202:45777 (IP=0.0.0.0:389)
Oct 24 16:46:06 testsystem.myserver.co.uk slapd[1922]: conn=1604 op=0 BIND dn="" method=128
Oct 24 16:46:06 testsystem.myserver.co.uk slapd[1922]: conn=1604 op=0 RESULT tag=97 err=0 text=
Oct 24 16:46:06 testsystem.myserver.co.uk slapd[1922]: conn=1604 op=1 SRCH base="dc=abc,dc=local" scope=2 deref=0 filter="(|(cn=*)(sn=*))"
Oct 24 16:46:06 testsystem.myserver.co.uk slapd[1922]: conn=1604 op=1 SEARCH RESULT tag=101 err=0 nentries=13 text=
Oct 24 16:46:06 testsystem.myserver.co.uk slapd[1922]: conn=1604 op=2 UNBIND
Oct 24 16:46:06 testsystem.myserver.co.uk slapd[1922]: conn=1604 fd=22 closed
Oct 24 16:46:49 testsystem.myserver.co.uk slapd[1922]: conn=1530 op=21 SRCH base="dc=abc,dc=local" scope=2 deref=0 filter="(cn=*)"
Oct 24 16:46:49 testsystem.myserver.co.uk slapd[1922]: conn=1530 op=21 SRCH attr=givenName title wWWHomePage telephoneNumber
Oct 24 16:46:49 testsystem.myserver.co.uk slapd[1922]: conn=1530 op=21 SEARCH RESULT tag=101 err=0 nentries=13 text=
OpenLDAP Configuration
Generate OpenLDAP password and save it
sudo slappasswd
Then, we will use ldapmodify to update /etc/openldap/slapd.d/cn=config/olcDatabase={2}hdb.ldif, which is our database config fileWe will create a file & customize and paste content below
vi db.ldif
Content you should paste: You should replace with your customized values
- olcSuffix (should be replaced by your domain, e.g. example.com -> dc=example,dc=com)
- olcRootDN (should be replaced by your domain admin name, can be any name you prefer, e.g. admin -> cn=admin,dc=abc,dc=local)
- olcRootPW (should be the password you generate above)
dn: olcDatabase={2}hdb,cn=config
changetype: modify
replace: olcSuffix
olcSuffix: dc=abc,dc=local
dn: olcDatabase={2}hdb,cn=config
changetype: modify
replace: olcRootDN
olcRootDN: cn=admin,dc=abc,dc=local
dn: olcDatabase={2}hdb,cn=config
changetype: modify
replace: olcRootPW
olcRootPW: {SSHA}xxxxx
sudo ldapmodify -Y External -H ldapi:/// -f db.ldif
Configuration of
/etc/openldap/slapd.d/cn=config/olcDatabase={2}hdb.ldif
should now change to:
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 9c010289
dn: olcDatabase={2}hdb
objectClass: olcDatabaseConfig
objectClass: olcHdbConfig
olcDatabase: {2}hdb
olcDbDirectory: /var/lib/ldap
olcDbIndex: objectClass eq,pres
olcDbIndex: ou,cn,mail,surname,givenname eq,pres,sub
structuralObjectClass: olcHdbConfig
entryUUID: 6ceb0374-fb9c-103d-91a1-952174d1b37c
creatorsName: cn=config
createTimestamp: 20231010093751Z
olcSuffix: dc=abc,dc=local
olcRootDN: cn=admin,dc=abc,dc=local
olcRootPW:: encryped_password_here
entryCSN: 20221010095035.541034Z#000000#000#000000
modifiersName: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
modifyTimestamp: 20221010095035Z
Apply some commonly used schema. The 2nd & 3rd schema allow us to create an object with InetOrgPerson & ShadowAccount which we will use to create an user
sudo ldapadd -Y EXTERNAL -H ldapi:/// -f /etc/openldap/schema/cosine.ldif
sudo ldapadd -Y EXTERNAL -H ldapi:/// -f /etc/openldap/schema/nis.ldif
sudo ldapadd -Y EXTERNAL -H ldapi:/// -f /etc/openldap/schema/inetorgperson.ldif
OpenLDAP Verification
Create objects, Organizational Unit and group
Create a file, entries.ldif, and add below content which
- create a user, john
- assign john to 2 groups, john & Engineering
dn: dc=abc,dc=local
dc: abc
objectClass: top
objectClass: domain
dn: ou=People,dc=abc,dc=local
objectClass: organizationalUnit
ou: People
dn: ou=Groups,dc=abc,dc=local
objectClass: organizationalUnit
ou: Groups
dn: cn=Engineering,ou=Groups,dc=abc,dc=local
cn: Engineering
objectClass: posixGroup
gidNumber: 32452
memberUid: John
dn: uid=John,ou=People,dc=abc,dc=local
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
uid: John
sn: Doe
givenName: John
cn: John Doe
displayName: John Ho
uidNumber: 32452
gidNumber: 32452
loginShell: /bin/bash
homeDirectory: /home/John
shadowMin: 0
shadowMax: 2
shadowWarning: 1
userPassword: {CRYPT}x
shadowLastChange: 19261
dn: cn=john,ou=Groups,dc=abc,dc=local
cn: John
objectClass: posixGroup
gidNumber: 32452
memberUid: John
Apply the content
ldapadd -x -W -D "cn=admin,dc=abc,dc=local" -f entries.ldif
Test querying LDAP
Query all entries
ldapsearch -D cn="admin,dc=abc,dc=local" -W -b "dc=abc,dc=local"
[root@testsystem ldapservice]# ldapsearch -D cn="admin,dc=abc,dc=local" -W -b "dc=abc,dc=local"
Enter LDAP Password:
# extended LDIF
#
# LDAPv3
# base <dc=abc,dc=local> with scope subtree
# filter: (objectclass=*)
# requesting: ALL
#
# abc.local
dn: dc=abc,dc=local
dc: abc
objectClass: top
objectClass: domain
# People, abc.local
dn: ou=People,dc=abc,dc=local
objectClass: organizationalUnit
ou: People
# Groups, abc.local
dn: ou=Groups,dc=abc,dc=local
objectClass: organizationalUnit
ou: Groups
Change account password (Optional)
The account we created above uses a password, {CRYPT}x, which we obviously don’t want it to be, so we change the password as below
sudo ldappasswd -x -D cn=admin,dc=abc,dc=local -W -S uid=john,ou=People,dc=abc,dc=local
Now we have to create a script to extract the data
The contact data we want is in the mysql database on Freepbx, This is the asterisk database and the data is across a few tables.
The basic concept that we extract the info from the db and remove add or update the entries in the ldap database.
Bash Scripts
First we have a file for the variables and passwords ‘fldapconfig.sh’
# LDAP Server Connection Details
LDAP_SERVER="ldap://server:port"
LDAP_BINDDN="cn=admin,dc=abc,dc=local"
LDAP_BINDPW="ldappassword"
DB_HOST="localhost"
DB_USER="username"
DB_PASSWORD="mysqlsecret"
DB_NAME="asterisk"
# LDAP Entry Details
BASE_DN="dc=abc,dc=local"
BASE_OU="ou=People"
# Files
rem_file="/tmp/rem.txt"
add_file="/tmp/add.txt"
chg_file="/tmp/chg.txt"
Now the code. I accept no responsibility for it, Its a mess but it does what it says. There is bound to be a better way but with the timescale I had i needed something quick and as such its dirty. each section is distinct so shouldnt be hard to clean up.
The file fpbxldap.sh
#!/bin/bash
#Script file to add delete and modify ladp database for freeepbx contact manager
#Copyright (C) 2023 Ian Plain Cyber-cottage.co.uk
#
#This program is free software; you can redistribute it and/or
#modify it under the terms of the GNU General Public License
#as published by the Free Software Foundation; either version 2
#of the License, or (at your option) any later version.
#
#This program is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
source fldapconfig.sh
# LDAP Entry Details
#BASE_DN="dc=abc,dc=local"
#BASE_OU="ou=People"
# Search for the LDAP entries ad file them
CURRENT_TELEPHONE_NUMBER=$(ldapsearch -x -D "$LDAP_BINDDN" -w "$LDAP_BINDPW" -H "$LDAP_SERVER" -b "$BASE_OU,$BASE_DN" telephoneNumber | awk -F ',|=|: ' '/dn:/ {print $3}')
echo "$CURRENT_TELEPHONE_NUMBER" |grep -w -v "People" > /tmp/ldapdb.txt
# Query to execute
QUERY="SELECT asterisk.contactmanager_entry_numbers.number as 'telephoneNumber', asterisk.contactmanager_group_entries.displayname as 'cn', asterisk.contactmanager_group_entries.fname as 'givenName', COALESCE(NULLIF(asterisk.contactmanager_group_entries.lname, ''), '-') AS 'sn', asterisk.contactmanager_entry_numbers.type as 'o', asterisk.contactmanager_groups.name as 'dir'
FROM asterisk.contactmanager_entry_numbers
INNER JOIN asterisk.contactmanager_group_entries ON asterisk.contactmanager_entry_numbers.entryid=asterisk.contactmanager_group_entries.id
INNER JOIN asterisk.contactmanager_groups ON asterisk.contactmanager_groups.id=asterisk.contactmanager_group_entries.groupid
WHERE asterisk.contactmanager_entry_numbers.number REGEXP '^[0-9]*$' AND asterisk.contactmanager_group_entries.displayname REGEXP '[:alpha:]'
;"
# Output file
OUTPUT_FILE="/tmp/fpbxdb.txt"
# Run the MySQL query and save the result to the output file
mysql -h "$DB_HOST" -u "$DB_USER" -p"$DB_PASSWORD" "$DB_NAME" -N -e "$QUERY" | sed 's/\t/,/g' > "$OUTPUT_FILE"
#Split out just the names
cat /tmp/fpbxdb.txt |awk -F ',' '{print $2" - "$5}' > /tmp/fpxname.txt
cat /tmp/fpbxdb.txt |awk -F ',' '{print $2" - "$5","$1}' > /tmp/fpxnumna.txt
# Assign filenames to variables
listB_file="/tmp/ldapdb.txt"
listA_file="/tmp/fpxname.txt"
# Check if the files exist
if [ ! -f "$listA_file" ]; then
echo "File $listA_file does not exist."
exit 1
fi
if [ ! -f "$listB_file" ]; then
echo "File $listB_file does not exist."
exit 1
fi
#Bit of a hack here that adds an entry to empty file, as AWK doesnt like empty files.. Thsi was quick a fix
if [ -s "$listA_file" ]; then
echo "The file is not empty."
else
echo "foobar" > /tmp/fpxname.txt
fi
if [ -s "$listB_file" ]; then
echo "The file is not empty."
else
echo "barfoo" > /tmp/ldapdb.txt
fi
# Compare the two files and echo names in List A but not in List B
awk 'NR==FNR{a[$0]++; next} !a[$0]' "$listB_file" "$listA_file" > /tmp/add.txt
awk 'NR==FNR{a[$0]++; next} !a[$0]' "$listA_file" "$listB_file" > /tmp/rem.txt
#lets delete the entries
# Loop through each line in the input file and run the command
while IFS= read -r REM_FILTER; do
# Run the specified command on each line
echo "$REM_FILTER deleted from Ldap" >> /tmp/remlog.txt
ldapdelete -x -D "$LDAP_BINDDN" -w "$LDAP_BINDPW" -H "$LDAP_SERVER" "cn=$REM_FILTER,$BASE_OU,$BASE_DN"
done < "$rem_file"
echo "Done-------" >> /tmp/remlog.txt
#delete the previous ldif files
rm -f /tmp/adding.ldif
rm -f /tmp/modify.ldif
#lets add the entries
# Loop through each line in the input file and run the command
while IFS= read -r ADD_FILTER; do
# Run the specified command on each line
# echo $ADD_FILTER |awk -F ' - ' '{print $1; print $2}'
ms_cn="$(echo $ADD_FILTER |awk -F ' - ' '{print $1}')"
ms_o="$(echo $ADD_FILTER |awk -F ' - ' '{print $2}')"
# Query to execute
QUERY="SELECT asterisk.contactmanager_entry_numbers.number as 'telephoneNumber', COALESCE(NULLIF(asterisk.contactmanager_group_entries.lname, ''), '-') AS 'sn'
FROM asterisk.contactmanager_entry_numbers
INNER JOIN asterisk.contactmanager_group_entries ON asterisk.contactmanager_entry_numbers.entryid=asterisk.contactmanager_group_entries.id
INNER JOIN asterisk.contactmanager_groups ON asterisk.contactmanager_groups.id=asterisk.contactmanager_group_entries.groupid
WHERE asterisk.contactmanager_entry_numbers.type = '$ms_o' AND asterisk.contactmanager_group_entries.displayname = '$ms_cn'
;"
# Run the MySQL query and save the result to the output file
ms_query=$(mysql -h "$DB_HOST" -u "$DB_USER" -p"$DB_PASSWORD" "$DB_NAME" -N -e "$QUERY")
ms_telephoneNumber=$(echo $ms_query | awk '{print $1}')
ms_sn=$(echo $ms_query | awk '{print $2}')
echo "dn: cn=$ms_cn - $ms_o,ou=People,dc=abc,dc=local" >> /tmp/adding.ldif
echo "cn: $ms_cn - $ms_o" >> /tmp/adding.ldif
echo "givenName: $ms_cn - $ms_o" >> /tmp/adding.ldif
echo "sn: $ms_sn" >> /tmp/adding.ldif
echo "telephonenumber: $ms_telephoneNumber" >> /tmp/adding.ldif
echo "objectclass: inetOrgPerson" >> /tmp/adding.ldif
echo "objectclass: top" >> /tmp/adding.ldif
echo "" >> /tmp/adding.ldif
echo "cn: $ms_cn - $ms_o , $ms_telephoneNumber added to Ldap" >> /tmp/addlog.txt
done < "$add_file"
#Lets run the ldif command
ldapadd -x -D "$LDAP_BINDDN" -w "$LDAP_BINDPW" -H "$LDAP_SERVER" -f /tmp/adding.ldif >> /tmp/addlog.txt
echo "Done-------" >> /tmp/addlog.txt
#OK now we are going to compare freepbx and ldap entries and update as required.
#lets get the current ldap names and numbers
ldapsearch -x -D "cn=admin,dc=abc,dc=local" -w "r1v3rp1g5" -b "ou=People,dc=abc,dc=local" | awk -v OFS=',' '{split($0,a,": ")} /^cn:/{cn=a[2]} /^telephoneNumber:/{telephoneNumber=a[2]; print cn,telephoneNumber}' > /tmp/ldapcsv.txt
awk 'NR==FNR{a[$0]++; next} !a[$0]' /tmp/ldapcsv.txt /tmp/fpxnumna.txt > /tmp/chg.txt
chg_file="/tmp/chg.txt"
# Loop through each line in the input file and run the command
while IFS= read -r CHG_FILTER; do
# Run the specified command on each line
# echo $CHG_FILTER |awk -F ',' '{print $1; print $2}'
ms_cn="$(echo $CHG_FILTER |awk -F ',' '{print $1}')"
ms_telephoneNumber="$(echo $CHG_FILTER |awk -F ',' '{print $2}')"
echo "Changing telephoneNumber to $ms_telephoneNumber"
echo "dn: cn=$ms_cn,$BASE_OU,$BASE_DN" >> /tmp/modify.ldif
echo "changetype: modify" >> /tmp/modify.ldif
echo "replace: telephoneNumber" >> /tmp/modify.ldif
echo "telephoneNumber: $ms_telephoneNumber" >> /tmp/modify.ldif
echo "" >> /tmp/modify.ldif
echo "$CHG_FILTER changed in Ldap" >> /tmp/chglog.txt
done < "$chg_file"
ldapmodify -x -D "$LDAP_BINDDN" -w "$LDAP_BINDPW" -H "$LDAP_SERVER" -f /tmp/modify.ldif >> /tmp/modify.ldif
echo "Done-------" >> /tmp/chglog.txt
Example phone configurations for Sangoma S series and Gigaset.
Gigaset example
Sangoma S Series example
Im sure this will work with other systems that support Ldap directories