Keep Zimbra in Sync with Active Directory
For my scenario, I want mailboxes created automatically when I create an Active Directory (AD) account. I do not want the mailbox to authenticate against AD though. To accomplish this, two or three scripts will be created and setup to run on a regular basis.
The 1st script will be run on a Windows server and will create a comma-delimited (CSV) file for all users who belong to a security group called "GRP_ZimbraEmail" which allows me to selectively pick who is allowed to have a mailbox. The script will probably just run once per day (after hours). The CSV file will be placed on Ubuntu's Samba share.
The 2nd script will be running on Ubuntu and checking for the input file very often (every minute). It will then cycle through all the AD users and see which users do not have a mailbox account and then create the mailbox using the supplied details.
export_zimbra_ad_users.vbs
Code:
Option Explicit
'****************************************
'** Name: export_zimbra_ad_users.vbs
'** Version: 1.1
'** Date: 2011-10-29
'** Author: LHammonds
'** Purpose: Export AD users to a comma-delimited file
'** that are authorized to have a Zimbra mailbox.
'****************************************
'* Field #1 = LoginID
'* Field #2 = First Name
'* Field #3 = Middle Initial
'* Field #4 = Last Name
'* Field #5 = Full Name
'* Field #6 = Title
'* Field #7 = Description
'* Field #8 = Comments
'* Field #9 = Telephone
'* Field #10 = Home Phone
'* Field #11 = Mobile Phone
'* Field #12 = Fax Number
'* Field #13 = Pager
'* Field #14 = Company
'* Field #15 = Office
'* Field #16 = Street Address
'* Field #17 = PO Box
'* Field #18 = City
'* Field #19 = State
'* Field #20 = Postal Code
'* Field #21 = Country
'* Field #22 = Password Replacement Value
'* Field #23 = Unused (mainly to avoid the end-of-line character being read into the last value)
'** NOTE: This could use a data cleanup routine that replaces all commas in a **
'** variable with something else such as a period instead to avoid CSV issues. **
Const ForAppending = 8
Const FilePath = "D:\Reports\adlist.csv"
Const ZimbraSecurityGroup = "GRP_ZimbraEmail"
Const DefaultPassword = "ChangeThisPassword"
Dim objRootDSE, strDNC, objDomain, objFSO, objFile
Set objRootDSE = GetObject("LDAP://RootDSE")
strDNC = objRootDSE.Get("DefaultNamingContext")
Set objDomain = GetObject("LDAP://" & strDNC)
'** Create / Overwrite the export file. **
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objFile = objFSO.CreateTextFile(FilePath)
objFile.Close
Set objFile = Nothing
Call TrollTheFolders(objDomain)
Sub TrollTheFolders(pobjDomain)
'** Function: Traverse the AD structure to find users wherever they may reside. **
'** The trick is that this function is called recursively in order to **
'** inspect every sub-folder that may contain user accounts. **
Dim lobjFile, lobjFSO, lobjMember, lstrLine, lblnInZimbraGroup
Dim lstrSamAccountName, lstrFirstName, lstrInitials, lstrLastName
Dim lstrFullName, lstrTitle, lstrDescription, lstrComment
Dim lstrTelephoneNumber, lstrHomePhone, lstrMobile, lstrFaxNumber, lstrPager
Dim lstrCompany, lstrOffice, lstrStreetAddress, lstrPostOfficeBox
Dim lstrCity, lstrState, lstrPostalCode, lstrCountry, lstrPassword
Dim lcolGroups, lobjGroup, lstrTemp
For Each lobjMember In pobjDomain
'** Examine each object but process only "user" objects. **
If lobjMember.Class = "user" Then
Set lobjFile = objFSO.OpenTextFile (FilePath, ForAppending, True)
If Not (isempty(lobjMember.samAccountName)) Then lstrSamAccountName = lobjMember.samAccountName Else lstrSamAccountName = "" End If
If Not (isempty(lobjMember.GivenName)) Then lstrFirstName = lobjMember.GivenName Else lstrFirstName = "" End If
If Not (isempty(lobjMember.initials)) Then lstrInitials = lobjMember.initials Else lstrInitials = "" End If
If Not (isempty(lobjMember.sn)) Then lstrLastname = lobjMember.sn Else lstrLastName = "" End If
If Not (isempty(lobjMember.CN)) Then lstrFullName = lobjMember.CN Else lstrFullName = "" End If
If Not (isempty(lobjMember.title)) Then lstrTitle = lobjMember.title Else lstrTitle = "" End If
If Not (isempty(lobjMember.Description)) Then lstrDescription = lobjMember.Description Else lstrDescription = "" End If
If Not (isempty(lobjMember.comment)) Then lstrComment = lobjMember.comment Else lstrComment = "" End If
If Not (isempty(lobjMember.telephoneNumber)) Then lstrTelephoneNumber = lobjMember.telephoneNumber Else lstrTelephoneNumber = "" End If
If Not (isempty(lobjMember.homePhone)) Then lstrHomePhone = lobjMember.homePhone Else lstrHomePhone = "" End If
If Not (isempty(lobjMember.mobile)) Then lstrMobile = lobjMember.mobile Else lstrMobile = "" End If
If Not (isempty(lobjMember.otherFacsimileTelephoneNumber)) Then lstrFaxNumber = lobjMember.otherFacsimileTelephoneNumber Else lstrFaxNumber = "" End If
If Not (isempty(lobjMember.pager)) Then lstrPager = lobjMember.pager Else lstrPager = "" End If
If Not (isempty(lobjMember.company)) Then lstrCompany = lobjMember.company Else lstrCompany = "" End If
If Not (isempty(lobjMember.physicalDeliveryOfficeName)) Then lstrOffice = lobjMember.physicalDeliveryOfficeName Else lstrOffice = "" End If
If Not (isempty(lobjMember.streetAddress)) Then lstrStreetAddress = lobjMember.streetAddress Else lstrStreetAddress = "" End If
If Not (isempty(lobjMember.postOfficeBox)) Then lstrPostOfficeBox = lobjMember.postOfficeBox Else lstrPostOfficeBox = "" End If
If Not (isempty(lobjMember.l)) Then lstrCity = lobjMember.l Else lstrCity = "" End If
If Not (isempty(lobjMember.st)) Then lstrState = lobjMember.st Else lstrState = "" End If
If Not (isempty(lobjMember.postalCode)) Then lstrPostalCode = lobjMember.postalCode Else lstrPostalCode = "" End If
If Not (isempty(lobjMember.countryCode)) Then lstrCountry = lobjMember.countryCode Else lstrCountry = "" End If
lstrPassword = DefaultPassword
lblnInZimbraGroup = 0
For Each lobjGroup in lobjMember.Groups
'** See if this member belongs to the group that allows Zimbra mailboxes **
If LCase(lobjGroup.cn) = LCase(ZimbraSecurityGroup) Then
lblnInZimbraGroup = 1
End If
Next
If lblnInZimbraGroup = 1 Then
'** Member is associated to the Zimbra Email group and thus allowed to have a Zimbra mailbox. **
lstrLine = lstrSamAccountName & "," & lstrFirstName & "," & lstrInitials & "," & lstrLastName & "," & lstrFullName & "," &_
lstrTitle & "," & lstrDescription & "," & lstrComment & "," & lstrTelephoneNumber & "," & lstrHomePhone & "," & lstrMobile & "," &_
lstrFaxNumber & "," & lstrPager & "," & lstrCompany & "," & lstrOffice & "," & lstrStreetAddress & "," & lstrPostOfficeBox & "," &_
lstrCity & "," & lstrState & "," & lstrPostalCode & "," & lstrCountry & "," & lstrPassword & ",unused"
lobjFile.WriteLine(lstrLine)
End If
lobjFile.Close
Set lobjFile = Nothing
End If
If lobjMember.Class = "organizationalUnit" or lobjMember.Class = "container" Then
'** Recurse further down to find the users. **
TrollTheFolders(lobjMember)
End If
Next
End Sub
Sample adlist.csv
Code:
mmouse,Mickey,,Mouse,Mickey Mouse,,Lab Assistant,,,,,,800-867-5309,,,,,,,,0,ChangeThisPassword,unused
jdirt,Joe,,Dirt,Joe Dirt,Chief Information Officer,Chief Information Officer,,800-555-5555,,,,,ABC,Information Systems,404 Hollywood Street,,Hollywood,CA,90210,840,ChangeThisPassword,unused
ddiggler,Dirk,,Diggler,Dirk Diggler,Database Administrator,IS-Database Administrator,,800-555-0404,800-789-1234,888-111-2222,,,,Information Systems,404 Cowboy Country Road,,Hickville,TX,77501,840,ChangeThisPassword,unused
jdoe,John,,Doe,John Doe,System Analyst,IS-Telco Systems Analyst,,800-999-8888,,,,,,Information Systems,666 Gov Road,,Washington,WA,10110,840,ChangeThisPassword,unused
import-ad.sh
Code:
#!/bin/bash
#############################################
## Name : import-ad.sh
## Version : 1.2
## Date : 2011-11-02
## Author : LHammonds
## Purpose : Add mailbox accounts for AD users that have none.
## Compatibility : Verified on Ubuntu Server 10.04.3 - 10.04.4 LTS, Zimbra 7.1.2 - 7.2.0 OSE
## Requirements : Zimbra must be online, must be run as root user.
## Run Frequency : Run very frequently (every minute)
## Exit Codes :
## 0 = Success
## 1 = Failure
################ CHANGE LOG #################
## DATE WHO WHAT WAS CHANGED
## ---------- --- ----------------------------
## 2011-10-11 LTH Created script.
## 2011-10-29 LTH Better logging, mail support, bug fixes.
## 2011-11-02 LTH Moved standard variables/functions to external file.
#############################################
## Import standard variables and functions. ##
source /var/scripts/common/standard.conf
LOGFILE="${TEMPDIR}/import-ad.log"
MAILFILE="${TEMPDIR}/import-ad-mail.$$"
ADLISTORG="${SHAREDIR}/adlist.csv"
ADLISTNEW="${TEMPDIR}/import-ad-adlist.$$"
ZMUSERS="${TEMPDIR}/import-ad-zmuserlist.$$"
ZMCMD="${TEMPDIR}/import-ad-zmcmd.$$"
NEWUSERS=""
RETURNVALUE=0
## Temporarily change the default field separator to a comma. #
OLDIFS="${IFS}"
IFS=","
## If the Active Directory user list file is not found, exit the script. #
if [ ! -f ${ADLISTORG} ]; then
## No file to process. Exit script.
exit 0
fi
echo "`date +%Y-%m-%d_%H:%M:%S` - Active Directory file found! Import process started." >> ${LOGFILE}
## Create files and set permissions for only the root user. #
touch ${ZMCMD}
touch ${ZMUSERS}
chmod 0600 ${ZMCMD}
chmod 0600 ${ZMUSERS}
## Take ownership and set permissions for only the root user. #
chown root:root ${ADLISTORG}
chmod 0400 ${ADLISTORG}
## Move the file to a working folder. #
mv ${ADLISTORG} ${ADLISTNEW}
## Create a list of users in Zimbra for the specified domain. #
${ZIMBRADIR}/bin/zmprov -l getAllAccounts ${MYDOMAIN} > ${ZMUSERS}
## Loop through the Active Directory user list. #
while read -a ADLINE ; do
## Slot 0 = UserID
## Slot 1 = First Name (GivenName)
## Slot 2 = Initials
## Slot 3 = Last Name (sn)
## Slot 4 = Full Name (CN)
## Slot 5 = Title
## Slot 6 = Description
## Slot 7 = Comment
## Slot 8 = Telephone / Work Number
## Slot 9 = Home Phone
## Slot 10 = Mobile Phone
## Slot 11 = Fax Number (facsimileTelephoneNumber)
## Slot 12 = Pager
## Slot 13 = Company
## Slot 14 = Office (physicalDeliveryOfficeName)
## Slot 15 = Street Address
## Slot 16 = Post Office Box
## Slot 17 = City (l)
## Slot 18 = State (st)
## Slot 19 = Postal Code
## Slot 20 = Country Code (co)
## Slot 21 = Password
## Set the "Found Match" variable to false #
FOUND=0
## Loop through the Zimbra User List. #
while read -a ZMLINE ; do
## Convert Usernames to lower case. #
ZMUSERID=${ZMLINE[0],,}
ADUSERID=${ADLINE[0],,}
## Add domain address to the end of the AD Username #
ADUSERID=${ADUSERID}"@"${MYDOMAIN}
## Compare the lowercase AD username to the lower case Zimbra username. #
if [ ${ADUSERID} == ${ZMUSERID} ]; then
## Match found. Exit this while loop. #
FOUND=1
break
fi
done < ${ZMUSERS}
if [ ${FOUND} -eq 0 ]; then
## We have an AD user that does not have a Zimbra mailbox. #
NEWUSERS="${NEWUSERS}${ADLINE[0]}@${MYDOMAIN}\n"
echo "`date +%Y-%m-%d_%H:%M:%S` --- Adding new user: ${ADLINE[0]}@${MYDOMAIN}" >> ${LOGFILE}
echo "createAccount ${ADLINE[0]}@${MYDOMAIN} \"${ADLINE[21]}\" gn \"${ADLINE[1]}\" initials \"${ADLINE[2]}\" sn \"${ADLINE[3]}\" cn \"${ADLINE[4]}\" title \"${ADLINE[5]}\" description \"${ADLINE[6]}\" zimbraNotes \"${ADLINE[7]}\" telephoneNumber \"${ADLINE[8]}\" homePhone \"${ADLINE[9]}\" mobile \"${ADLINE[10]}\" facsimileTelephoneNumber \"${ADLINE[11]}\" pager \"${ADLINE[12]}\" company \"${ADLINE[13]}\" physicalDeliveryOfficeName \"${ADLINE[14]}\" street \"${ADLINE[15]}\" postOfficeBox \"${ADLINE[16]}\" l \"${ADLINE[17]}\" st \"${ADLINE[18]}\" postalCode \"${ADLINE[19]}\" co \"${ADLINE[20]}\"">> ${ZMCMD}
fi
done < ${ADLISTNEW}
## If the Zimbra command file is not empty, we need to process it. #
if [ -s ${ZMCMD} ]; then
${ZIMBRADIR}/bin/zmprov < ${ZMCMD} 1>/dev/null 2>&1
RETURNVALUE=$?
if [ ${RETURNVALUE} -ne 0 ]; then
## Something went wrong with the mailbox creation.
## This error should notify administrators to the problem.
echo "`date +%Y-%m-%d_%H:%M:%S` --- ERROR: zmprov reported the following error: ${RETURNVALUE}" >> ${LOGFILE}
echo "`date +%Y-%m-%d_%H:%M:%S` - Import aborted. EXIT CODE = 1" >> ${LOGFILE}
f_sendmail "Zimbra Active Directory Import" "ERROR: zmprov reported the following error: ${RETURNVALUE}\n\nRemaining temporary files:\n${ZMUSERS}\n${ZMCMD}\n${ADLISTNEW}\n"
exit 1
fi
## Send email notification of the new users added to Zimbra. #
## NOTE: To be 100% sure about this, we could loop through the users #
## and verify that they now have Zimbra mailboxes. #
f_sendmail "Zimbra Notification - New Users" "The following users were added to Zimbra:\n\n${NEWUSERS}"
else
## There were no new users to process. #
echo "`date +%Y-%m-%d_%H:%M:%S` --- No new users to import." >> ${LOGFILE}
fi
echo "`date +%Y-%m-%d_%H:%M:%S` - Active Directory import completed." >> ${LOGFILE}
## Restore the default field separator value. #
IFS="${OLDIFS}"
## Remove temporary files. #
rm ${ZMUSERS}
rm ${ZMCMD}
rm ${ADLISTNEW}
Sample email when new mailboxes are created:
Code:
Subject: Zimbra Notification - New Users
The following users were added to Zimbra:
ddiggler@mydomain.com
jdoe@mydomain.com
jsmith@mydomain.com
lhammonds@mydomain.com
mmouse@mydomain.com
Server: mail
Program: /var/scripts/prod/import-ad.sh
Log: /var/temp/import-ad.log
Here is a sample log file (NOTE: There are no entries if no file is found):
Code:
2011-10-29_17:06:49 - Active Directory file found! Import process started.
2011-10-29_17:06:52 --- Adding new user: ddiggler@mydomain.com
2011-10-29_17:06:53 --- Adding new user: jdoe@mydomain.com
2011-10-29_17:06:53 --- Adding new user: jsmith@mydomain.com
2011-10-29_17:06:53 --- Adding new user: lhammonds@mydomain.com
2011-10-29_17:06:53 --- Adding new user: mmouse@mydomain.com
2011-10-29_17:06:56 - Active Directory import completed.
2011-10-29_17:07:29 - Active Directory file found! Import process started.
2011-10-29_17:07:32 --- No new users to import.
2011-10-29_17:07:32 - Active Directory import completed.
If you keep a database of all your user's IDs and passwords, you could export that information to the Linux box and have it safely tucked away and used in-between the AD Export and AD Import process. It could run through the AD user list and replace the default password with their existing password making it operate seamlessly within the environment.
If user passwords are unknown, the process would be to let the individual know their mailbox was created with a standard password and that they need to login and change it ASAP.
Reference Articles:
- http://www.zimbra.com/docs/os/latest/administration_guide/wwhelp/wwhimpl/js/html/wwhelp.htm#href=OS_AdminGuide_7_0.zmprov_(Provisio ning).html"]zmprov[/URL] command-line documentation
- Bash Guide
NOTE: If you like this solution, you might also want to review the modified version by digidt in my older thread.