K Addressbook LDAP Bug

For those who are stuck up with using Kontact with an Exchange server should have experiences of how difficult it is to get everything properly working.

For instance, address book is an issue. Assuming that your exchange server holds more that 10,000 user records, it is going to be a challenging task to get your addressbook configurd to fetch those addresses.

A friend of mine figured out that this was rather a bug with either ldap or exchange (in that case a feature) which restricted the query response to 1000 replies. This bug is inherited in the ldap code of K Address Book also because it silently fetches 1000 records only.

Here’s a workaround for this issue.

  • Configure a resource and use LDAP for it.
  • In K Address Book, make the “Offline Cache Policy” to “Always use offline copy”
  • Download any of this or this script and run it as a cron job. (Make sure that you change the settings in the script accordingly)
#!/usr/bin/env python  
  
import os  
import sys  
import string  
import tempfile  
  
import threading  
import Queue  
  
from time import sleep  
  
_hostname = None  
_username = None  
_password = None  
_ldapDataFile = '/tmp/kabc_ldap_threaded_record.txt'  
  
if _hostname is None or _username is None or _password is None or
_ldapDataFile is None:  
    sys.stderr.write("Please first set the credentials properly.\n")  
    sys.exit(1)  
  
_ldapPreCommand = 'ldapsearch -LLL -z 0 "(Sn='  
_ldapPostCommand = '*)" -h ' \+ _hostname + ' -x -D "'  \+ _username + '" -b
Cn=users,DC=hq,DC=domain,DC=com' \+ " -w " \+ _password  
NumberOfThreads = 5  
  
ldap_attributes = ['dn:', 'cn:', 'sn:', 'l:', 'st:', 'title:', 'description:',
'postalCode:', 'telephoneNumber:', 'facsimileTelephoneNumber:',  
                   'givenName:', 'mail:', 'homePhone:', 'mobile:', 'pager:']  
  
try:  
    writeFile = open(_ldapDataFile, 'w')  
except IOError:  
    sys.stderr.write("Couldn't open file %s to write.\n" % (writeFile) )  
    sys.exit(1)  
  
def RecordFetcher(char):  
        (temp_file_fd, temp_file_name) = tempfile.mkstemp()  
        os.environ['__kabc_ldap'] = temp_file_name  
        sleep(5) #Let's not thrash the exchange server ![;-\)](http://www.researchut.com/blog/pivotx/includes/emoticons/trillian/e_121.gif)  
        ldapCommand = _ldapPreCommand + char + _ldapPostCommand  
        if os.system(ldapCommand + "> $__kabc_ldap") != 0:  
            sys.stderr.write("Couldn't execute the command %s\n" % (ldapCommand) )  
            sys.exit(1)  
        try:  
            readFile = open(temp_file_name, 'r')  
        except IOError:  
            sys.stderr.write("Couldn't open file %s to read.\n" % (readFile) )  
            sys.exit(1)  
              
        for record in readFile.readlines():  
            if record.startswith(' '): # Remove the junk  
                pass  
            record = string.rstrip(record, "\n")  
            for attrib in ldap_attributes:  
                if record.startswith(attrib):  
                    try:  
                        FileLock.acquire(True)  
                        if ldap_attributes[0] == attrib: #This attribute is common/mandatory in all records, so we can rely on it  
                           writeFile.write("\n")  
                        writeFile.write(record)  
                        writeFile.write("\n")  
                    finally:  
                        writeFile.flush()  
                        FileLock.release()  
                    break  
        readFile.close()  
        os.remove(temp_file_name)  
      
def run(request, response, func=RecordFetcher):  
    while 1:  
        item = request.get()  
        if item is None:  
            break  
        (char, subChar) = item  
          
        response.put(func(char+subChar) )  
  
requestQueue = Queue.Queue()  
responseQueue = Queue.Queue()  
  
FileLock = threading.Lock()  
  
thread_pool = [  
               threading.Thread(  
                                target=run,  
                                args=(requestQueue, responseQueue)  
                                )  
               for i in range(NumberOfThreads)  
               ]  
  
for t in thread_pool: t.start()  
  
for char in string.lowercase:  
    # I know this is ugly. Too many cycles  
    # But ldapsearch or exchange is restricting, the query max result limit is 1000  
    for subChar in string.lowercase:  
        requestQueue.put( (char, subChar) )  
  
for t in thread_pool: requestQueue.put(None)  
  
for t in thread_pool: t.join()  
  
writeFile.close()  

There is a bug in this script which looks more like a locking issue because from some of the threads data is not getting written. Probably you can use the non-threaded version but that’d be real slow.