Index: announcer/resolvers.py
===================================================================
--- announcer/resolvers.py	(revision 13441)
+++ announcer/resolvers.py	(working copy)
@@ -8,7 +8,8 @@
 # you should have received as part of this distribution.
 #
 
-from trac.config import Option
+import ldap
+from trac.config import Option, BoolOption
 from trac.core import Component, implements
 from trac.util.compat import sorted
 
@@ -102,3 +103,91 @@
         specified = self.setting.get_user_setting(req.session.sid)[1] or ''
         data = dict(specified_xmpp = specified,)
         return "prefs_announcer_xmppaddress.html", data
+
+class LdapEmailResolver(Component):
+    implements(IAnnouncementAddressResolver)
+
+    #
+    # Options added by the LDAP e-mail resolver
+    #
+    ldap_uri = Option('announcer', 'ldap_uri', '',
+        """URI of the LDAP server to contact.""")
+    ldap_bind_dn = Option('announcer', 'ldap_bind_dn', '',
+        """DN to use when binding to directory.""")
+    ldap_bind_pw = Option('announcer', 'ldap_bind_pw', '',
+        """Password for the bind DN.""")
+    ldap_search_base = Option('announcer', 'ldap_search_base', '',
+        """Base from which to search for users.""")
+    ldap_search_filter = Option('announcer', 'ldap_search_filter',
+        '(&(uid={uid})(objectClass=posixAccount))',
+        """Filter to use when searching for users. {uid} will be substituted with the user ID being resolved to an e-mail address.""")
+    ldap_email_attr_name = Option('announcer', 'ldap_email_attr_name', 'mail',
+        """Name of the LDAP attribute that holds the e-mail address.""")
+    ldap_start_tls = BoolOption('announcer', 'ldap_start_tls', 'false',
+        """Use TLS when connecting to the LDAP server.""")
+
+    def get_address_for_name(self, name, authenticated):
+        address = None
+
+        #
+        # Check to make sure the LDAP URI was set; abort if not.
+        #
+        if not self.ldap_uri or self.ldap_uri == '':
+            self.log.error('LDAP URI must be provided for Announcer LDAP e-mail resolution.')
+            return None
+        directory = ldap.initialize(self.ldap_uri)
+
+        #
+        # If Start TLS was requested, do so.
+        #
+        if self.ldap_start_tls == True:
+            try:
+                directory.start_tls_s()
+            except ldap.LDAPError, e:
+                self.log.error('Announcer Start TLS Failed:' + self._string_from_ldap_error(e))
+                return None
+
+        #
+        # Bind to the directory server, using bind credentials if required.
+        #
+        bindArgs = {}
+        if self.ldap_bind_dn and self.ldap_bind_dn != '':
+            bindArgs['who'] = self.ldap_bind_dn
+            if self.ldap_bind_pw:
+                bindArgs['cred'] = self.ldap_bind_pw
+        try:
+            directory.simple_bind_s(**bindArgs)
+        except ldap.INVALID_CREDENTIALS:
+            self.log.error('Announcer used invalid login credentials for LDAP directory.')
+            return None
+        except ldap.LDAPError, e:
+            self.log.error('Announcer could not bind to LDAP directory at (' + self.ldap_uri + '):' + self._string_from_ldap_error(e))
+            return None
+
+        #
+        # Execute the search, using the specified search base and filter.
+        #
+        filter_string = str(self.ldap_search_filter).format(uid=name)
+        searchResult = directory.search_s(str(self.ldap_search_base), ldap.SCOPE_ONELEVEL, filter_string, [str(self.ldap_email_attr_name)])
+        if searchResult is None:
+            self.log.info('Announcer could not find user "'+name+'" in LDAP using base (' + self.ldap_search_base + ').')
+            return None
+        for aResult in searchResult:
+            attrList = aResult[1]
+            if str(self.ldap_email_attr_name) in attrList:
+                address = attrList[str(self.ldap_email_attr_name)][0]
+
+        #
+        # Result is either the e-mail address from LDAP or None
+        #
+        directory.unbind()
+        if address is None:
+            self.log.info('Announcer could not find "'+self.ldap_email_attr_name+'" attribute for user "'+name+'".')
+        return address
+
+    def _string_from_ldap_error(self, ldap_error):
+        if type(ldap_error.message) == dict and ldap_error.message.has_key('desc'):
+            return ldap_error.message['desc']
+        else:
+            return str(ldap_error)
+
