#

Contents

Mirorring Active Directory user acconts in LDAP

Short description

The program allows to export user accounts from Active Directory (AD) in to LDAP. It is applied if user accounts are stored in your organization in AD, and accounts OX users are stored in LDAP.

This program useful for:

  • Transfers from AD in LDAP selected by a special filter (AD_GroupFilter) groups of the users
  • Transfers from AD in LDAP selected by a special filter (AD_UsersFilter) the users (more exactly some properties of the users)

This program useless for:

  • Does not export the users passwords (if you have the mechanism of reception of the passwords from AD, it will be not hard for you to make this changes. I shall be glad if you send to me this information);
  • Does not create mail boxes of the users;
  • LDAP users, not available in AD, are not export in AD and remove from LDAP (except 'service_users' (LDAP_ServiceUsers) )
  • LDAP groups, not available in AD, are are not export in AD and remove from LDAP (except 'service_groups' (LDAP_ServiceGroups) )

Program consists of two files settings.py and main.py. Settings.py contains common variable, which values are described in the comments. Main.py is, actually, body of the program.

The program is not the full completed. You can make changes, necessary for you.

Setup and run

Python

Setup Python and python-ldap, python-psycopg2 components (example for Debian)

apt-get install python2.4 python-ldap python-psycopg2

Active Direcory

Create in AD group 'OX_Users' and send in it the necessary users (in this example it is 'CN=OX_Users,OU=SupportUsers,DC=org')

Create in AD group 'OX_Groups' and send in it the necessary group of users (in this example it is 'CN=OX_Users,OU=SupportUsers,DC=org')

settings.py

Move in settings.py information on:

Active Directory: AD_Server, AD_BaseDN, AD_User, AD_Password, AD_UsersFilter, AD_GroupFilter

LDAP: LDAP_Server, LDAP_BaseDN, LDAP_User, LDAP_Password, LDAP_UsersFilter, LDAP_GroupFilter, LDAP_ServiceUsers, LDAP_ServiceGroups

Postgre SQL: SQL_server, SQL_db_name, SQL_user, SQL_password

Note: add to Postres SQL config pg_hba.conf line

host <ox_db_name> <ox_user_name> 127.0.0.1 255.255.255.255 trust

where <ox_db_name> = SQL_db_name and <ox_user_name> = SQL_user from file settings.py

Path to Open-xchange /sbin directory: OX_SBIN_PATH
Last letter must be '/'

Run program

It is important:

  • Before run this program make a backup of LDAP and PostgreSQL servers
  • Files main.py and settings.py should be in the one folder
  • The program should be run on the same computer, where run OX server (It is necessary for rules of the OX users)
  • All new OX users have password '1'

To set another default password edit string in main.py file

attrs['userPassword'] = '1'

Run program:

python main.py

settings.py listing

# -*- coding: cp1251 -*-

# settings.py 

# Active Directory (AD) server settings
## IP address of the AD server
AD_Server           =   '10.0.0.1'
## Base DN of AD server
AD_BaseDN           =   'DC=org'
## Name of the user, having access to AD (there is enough reading)
AD_User             =   'CN=AD_READ_USER,DC=org'
## Password for AD_User
AD_Password         =   'xxxxxxxx'
## AD users export filter
AD_UsersFilter      =   '(&(objectClass=person)(memberOf=CN=OX_Users,OU=SupportUsers,DC=org))'
## AD groups export filter
AD_GroupFilter      =   '(&(objectClass=group)(memberOf=CN=OX_Groups,OU=SupportUsers,DC=org))'

# LDAP server settings
## IP address of the LDAP server
LDAP_Server	    =   '10.0.0.2'
## Base DN of LDAP
LDAP_BaseDN	    =   'dc=org'
## Name of the user having access to LDAP
##(Rights on add, edit, remove are necessary)
LDAP_User           =   'cn=LDAP_WRITER,dc=org'	
## Password for LDAP_User
LDAP_Password	    =   'xxxxxxxxx'
## Place for users in LDAP
LDAP_UsersFilter    =   'ou=Users,ou=OxObjects,dc=org'
## Place for groups in LDAP
LDAP_GroupFilter    =   'ou=Groups,ou=OxObjects,dc=org'
## Support or system users in LDAP 
##(this users not editable, removable)
LDAP_ServiceUsers   =   ['mailadmin']
## Support or system groups in LDAP
##(this groups not editable, removable)
LDAP_ServiceGroups   =   ['Administration','users','OXUserAdmins','OXSMTPAdmins','OXResourceAdmins','OXIMAPAdmins','OXGroupAdmins','OXDNSAdmins']

## Default settings for new OX users in LDAP
DefaultOXUserSettings   = {'gidNumber':'500','OXAppointmentDays':'7',
                           'OXGroupID':'500','OXTaskDays':'7',
                           'OXTimeZone':'Asia/Novosibirsk',
			   'OXDayviewEndTime':'19:00',
			   'OXDayviewStartTime':'8:00',
                           'preferredLanguage':'DE',
                           'lnetMailAccess':'TRUE',
                           'mailEnabled':'OK',
                           'o':'Organisation Name',
                           'shadowExpire':'0',
                           'shadowMax':'9999',
                           'shadowMin':'0',
                           'shadowWarning':'7',
                           'loginShell':'/bin/bash',
                           'userCountry':'Some country'
                           }

# Postgre SQL server settings
## IP address of SQL server
SQL_server = '127.0.0.1'
## OX database name
SQL_db_name = 'openxchange'
## User  having access to SQL_db_name
##(Rights on add, edit, remove records are necessary)
SQL_user='openxchange'
## SQL_user password
SQL_password='xxxxxxxx'

## Path to Open-xchange /sbin directory
## Last letter must be '/' or '\' 
OX_SBIN_PATH='/usr/local/openxchange/sbin/'


# This tale - sample.
# It is need, if your AD contain russian(cyrillic) names
# You may modify this tale for other language
  
#AD_Server = unicode(AD_Server,"cp1251").encode('utf8') 
#AD_BaseDN = unicode(AD_BaseDN,"cp1251").encode('utf8')
#AD_User = unicode(AD_User,"cp1251").encode('utf8')
#AD_Password = unicode(AD_Password,"cp1251").encode('utf8')
#AD_UsersFilter = unicode(AD_UsersFilter,"cp1251").encode('utf8')
#AD_GroupFilter = unicode(AD_GroupFilter,"cp1251").encode('utf8')
#LDAP_Server = unicode(LDAP_Server,"cp1251").encode('utf8') 
#LDAP_BaseDN = unicode(LDAP_BaseDN,"cp1251").encode('utf8')
#LDAP_User = unicode(LDAP_User,"cp1251").encode('utf8')
#LDAP_Password = unicode(LDAP_Password,"cp1251").encode('utf8')
#LDAP_UsersFilter = unicode(LDAP_UsersFilter,"cp1251").encode('utf8')
#LDAP_GroupFilter = unicode(LDAP_GroupFilter,"cp1251").encode('utf8')
#LDAP_ServiceUsers = unicode(LDAP_ServiceUsers,"cp1251").encode('utf8')
#LDAP_ServiceGroups = unicode(LDAP_ServiceGroups,"cp1251").encode('utf8')

main.py listing

# -*- coding: cp1251 -*-

# main.py 

import ldap
import ldap.modlist as modlist
import settings
from psycopg import *
import subprocess 


## Find adduser permission string
f = open(settings.OX_SBIN_PATH + "addusersql_ox","r")
ss = "-Dopenexchange.propfile"
text = 'start'
while text != '':
    text = f.readline()
    if text.find(ss) > 0:
        ss = text
        text = ''
f.close
str_add = ss [: ss.find('$USERNAME') -1 ]
			
## Find deluser permission string
f = open(settings.OX_SBIN_PATH + "deluser_ox","r")
ss = "-Dopenexchange.propfile"
text = 'start'
while text != '':
    text = f.readline()
    if text.find(ss) > 0:
        ss = text
        text = ''
f.close
ss = ss [: ss.find('$USERNAME') -1 ]
str_del = ss [19:]

# Connect to AD
try:
	l = ldap.open(settings.AD_Server)
	l.protocol_version = ldap.VERSION3
        l.simple_bind(settings.AD_User,settings.AD_Password)
	
except ldap.LDAPError, e:
	print e

# Export groups from AD
baseDN = settings.AD_BaseDN
searchScope = ldap.SCOPE_SUBTREE
searchFilter = settings.AD_GroupFilter
retrieveAttributes =['cn','distinguishedName','sAMAccountName']
try:
    ldap_result_id = l.search(baseDN, searchScope, searchFilter, retrieveAttributes)
    groups = {}
    while True:
        result_type, result_data = l.result(ldap_result_id, 0)
	if result_data == []:
            break
        elif result_type == ldap.RES_SEARCH_ENTRY:
            groups[result_data[0][1]['distinguishedName'][0]] = [result_data[0][1]['cn'][0],result_data[0][1]['sAMAccountName'][0],[]]

except ldap.LDAPError, e:
	print e


# Export users from AD
searchFilter = settings.AD_UsersFilter
retrieveAttributes =['displayName','mail','sAMAccountName','memberOf']
try:
    ldap_result_id = l.search(baseDN, searchScope, searchFilter, retrieveAttributes)
    users = {}
    while True:
        result_type, result_data = l.result(ldap_result_id, 0)
	if result_data == []:
            break
        elif result_type == ldap.RES_SEARCH_ENTRY:
            try:
                dname = result_data[0][1]['displayName'][0]
            except:
                dname = ' '
            try:
                mail = result_data[0][1]['mail'][0]
            except:
                mail = ' '
            san = result_data[0][1]['sAMAccountName'][0].lower()    
            users[san] = [dname,mail]
            for i in result_data[0][1]['memberOf']:
                if i in groups:
                    groups[i][2].append(san)

except ldap.LDAPError, e:
	print e

l.unbind

# Connect to LDAP
try:
	l = ldap.open(settings.LDAP_Server)
	l.protocol_version = ldap.VERSION3
        l.simple_bind(settings.LDAP_User,settings.LDAP_Password)
	
except ldap.LDAPError, e:
	print e


# Export groups from LDAP
baseDN = settings.LDAP_GroupFilter
searchScope = ldap.SCOPE_SUBTREE
searchFilter = '(&(cn=*)(gidNumber=*))'
retrieveAttributes = ['cn','gidNumber','memberUid']
MaxGid = 0
try:
    ldap_result_id = l.search(baseDN, searchScope, searchFilter, retrieveAttributes)
    ldap_groups = {}
    while True:
        result_type, result_data = l.result(ldap_result_id, 0)
	if result_data == []:
            break
        elif result_type == ldap.RES_SEARCH_ENTRY and not result_data[0][1]['cn'][0] in settings.LDAP_ServiceGroups:
            ldap_groups[result_data[0][1]['cn'][0]] = [result_data[0][1]['gidNumber'][0],result_data[0][1]['memberUid']]
            MaxGid = max(MaxGid, int(result_data[0][1]['gidNumber'][0]))

except ldap.LDAPError, e:
	print e

# Refresh in LDAP members of the groups 
for group in groups.values():
    if group[1] in ldap_groups:
        # refresh members list
        NewDN='cn=' + group[1] + ',' + settings.LDAP_GroupFilter 
        old = {'memberUid':ldap_groups[group[1]][1]}
        new = {'memberUid':group[2]}
        if old != new:
            ldif = modlist.modifyModlist(old,new)
            l.modify(NewDN,ldif)
        del ldap_groups[group[1]]
    else:
        # add new group
        NewDN='cn=' + group[1] + ',' + settings.LDAP_GroupFilter 
        attrs = {}
        attrs['objectClass'] = ['top','posixGroup']
        attrs['cn'] = group[1]
        attrs['gidNumber'] = str(MaxGid + 1)
        attrs['memberUid'] = group[2]
        MaxGid += 1
        ldif = modlist.addModlist(attrs)
        l.add(NewDN,ldif)

# Remove groups from LDAP, 
# which are not present in AD
for i in ldap_groups:
    NewDN='cn=' + i + ',' + settings.LDAP_GroupFilter
    try:
	l.delete(NewDN)
    except ldap.LDAPError, e:
	print e
    del ldap_groups[i]

ldap_groups = None
groups = None

# Add all users read permission to GlobalAdresBook
GAB_Members = []
NewDN = settings.LDAP_UsersFilter
for i in settings.LDAP_ServiceUsers:
    GAB_Members.append ('uid=' + i + ',' + NewDN)
for i in users:
    GAB_Members.append ('uid=' + i + ',' + NewDN)
NewDN = settings.LDAP_UsersFilter.split(',')
NewDN = 'cn=AddressReadAdmins,o=AddressBook,' + ','.join(NewDN[1:])
try:
    l.delete(NewDN)
except ldap.LDAPError, e:
    print e
attrs = {}
attrs['cn'] = ['AddressReadAdmins']
attrs['objectClass'] = ['top','groupOfNames']
attrs['member'] = GAB_Members
ldif = modlist.addModlist(attrs)
l.add(NewDN,ldif)

# Export users from LDAP
baseDN = settings.LDAP_UsersFilter
searchScope = ldap.SCOPE_SUBTREE
searchFilter = '(&(uid=*)(uidNumber=*))'
retrieveAttributes = ['uid','uidNumber','mail','description']
MaxGid = 0
try:
    ldap_result_id = l.search(baseDN, searchScope, searchFilter, retrieveAttributes)
    ldap_users = {}
    while True:
        result_type, result_data = l.result(ldap_result_id, 0)
	if result_data == []:
            break
        elif result_type == ldap.RES_SEARCH_ENTRY and result_data[0][1]['uid'][0] not in settings.LDAP_ServiceUsers:
            ldap_users[result_data[0][1]['uid'][0]] = [result_data[0][1]['uidNumber'][0],result_data[0][1]['description'][0],result_data[0][1]['mail'][0]]
            MaxGid = max(MaxGid, int(result_data[0][1]['uidNumber'][0]))

except ldap.LDAPError, e:
	print e

# Connect to SQL
conx = connect(host=settings.SQL_server,database=settings.SQL_db_name,user=settings.SQL_user,password=settings.SQL_password)
cur = conx.cursor()

# Refresh in LDAP setings of the users
for i in users:
    if i in ldap_users:
        # refresh user setinngs
        NewDN='uid=' + i + ',' + settings.LDAP_UsersFilter 
        try:
                fam, im, otch = ldap_users[i][1].split()
        except:
                fam = ldap_users[i][1]
                im = ' '
                otch = ' '
        old = {'description':ldap_users[i][1],'mail':ldap_users[i][2],'sn':fam,'givenName':im}
        try:
                fam, im, otch = users[i][0].split()
        except:
                fam = users[i][0]
                im = ' '
                otch = ' '
        new = {'description':users[i][0],'mail':users[i][1],'sn':fam,'givenName':im}
        if old != new:
            ldif = modlist.modifyModlist(old,new)
            l.modify(NewDN,ldif)
        del ldap_users[i]
    else:
        # add new user
        NewDN='uid=' + i + ',' + settings.LDAP_UsersFilter
        try:
                fam, im, otch = users[i][0].split()
        except:
                fam = users[i][0]
                im = ' '
                otch = ' '

        attrs = settings.DefaultOXUserSettings
        attrs['objectClass'] = ['top','shadowAccount','posixAccount','person','inetOrgPerson','OXUserObject']
        attrs['cn'] = i
        attrs['uid'] = i
        attrs['uidNumber'] = str(MaxGid + 1)
        attrs['homeDirectory'] = '/tmp'
        attrs['sn'] = fam
        
        attrs['givenName'] = im
        attrs['mail'] = users[i][1]
        attrs['description'] = users[i][0]
        attrs['userPassword'] = '1'
        
        MaxGid += 1
        ldif = modlist.addModlist(attrs)
        l.add(NewDN,ldif)

        # add user mail container
        NewDN = 'ou=addr,' + NewDN
        attrs = {}
        attrs['ou'] = 'addr'
        attrs['objectClass'] = ['top','organizationalUnit']
        ldif = modlist.addModlist(attrs)
        l.add(NewDN,ldif)

        # Setup OX folder permissions
        sql = str_add + ' \'' + i  + '\' ' + 'DE'
        try:
            subprocess.call(sql, shell=True)
        except:
            pass

        # Add new user in SQL
        sql = 'INSERT INTO usr_general_rights SELECT creating_date, created_from, changing_date, changed_from, \''
        sql = sql + i
        sql = sql + '\', addr_u, addr_r, addr_d, cont_u, cont_r, cont_d, data_u, data_r, data_d, serie_u, serie_r, serie_d, task_u, task_r, task_d, refer, proj_u, proj_r, proj_d, dfolder_u, dfolder_r, dfolder_d, doc_u, doc_r, doc_d, knowl_u, knowl_r, knowl_d, bfolder_u, bfolder_r, bfolder_d, bookm_u, bookm_r, bookm_d, pin_u, pin_r, pin_d, forum_n, fentrie_n, setup, pin_public, internal, int_groups, kfolder_u, kfolder_r, kfolder_d, webmail FROM sys_gen_rights_template WHERE login LIKE \'default_template\''
        try:
            cur.execute(sql)
        except:
            pass
        conx.commit()


# Remove LDAP users,
# which are not present in AD
for i in ldap_users:
    NewDN='uid=' + i + ',' + settings.LDAP_UsersFilter
    NewDNadr='ou=addr,' + NewDN
    try:
	l.delete(NewDNadr)
    except ldap.LDAPError, e:
	print e
    try:
	l.delete(NewDN)
	print 'delete user ' + i
    except ldap.LDAPError, e:
	print e

    # Remove permissions to OX folder
    sql = str_del + ' \'' + i + '\''
    try:
        subprocess.call(sql, shell=True)
    except:
        pass
    
    # Remove user from SQL
    sql = 'DELETE FROM usr_general_rights WHERE login LIKE \'' + i + '\''
    try:
        cur.execute(sql)
    except:
        pass
        
    conx.commit()

    
    del ldap_users[i]

ldap_users = None
users = None

l.unbind

cur.close()
conx.close