Categories
Blog Knowledge Base

Ldap for FreePBX

Install and configure OpenLDAP server in FreePBX for handset directories

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

  1. olcSuffix (should be replaced by your domain, e.g. example.com -> dc=example,dc=com)
  2. olcRootDN (should be replaced by your domain admin name, can be any name you prefer, e.g. admin -> cn=admin,dc=abc,dc=local)
  3. 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

Run this command to update.

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

  1. create a user, john
  2. 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