[CalendarServer-changes] [9812] CalendarServer/branches/users/cdaboo/ischedule-dkim

source_changes at macosforge.org source_changes at macosforge.org
Mon Sep 17 14:39:32 PDT 2012


Revision: 9812
          http://trac.calendarserver.org//changeset/9812
Author:   cdaboo at apple.com
Date:     2012-09-17 11:35:01 -0700 (Mon, 17 Sep 2012)
Log Message:
-----------
Further DKIM/iSchedule integration, including DNS SRV and TXT lookups.

Modified Paths:
--------------
    CalendarServer/branches/users/cdaboo/ischedule-dkim/calendarserver/tap/caldav.py
    CalendarServer/branches/users/cdaboo/ischedule-dkim/calendarserver/tap/util.py
    CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/addressmapping.py
    CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/caldav/delivery.py
    CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/caldav/test/test_delivery.py
    CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/delivery.py
    CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/imip/test/test_delivery.py
    CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/delivery.py
    CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/dkim.py
    CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/remoteservers.py
    CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/scheduler.py
    CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/test/test_dkim.py
    CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/utils.py
    CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/stdconfig.py

Added Paths:
-----------
    CalendarServer/branches/users/cdaboo/ischedule-dkim/bin/calendarserver_dkimtool
    CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/test/data/
    CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/test/data/db.example.com
    CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/test/data/db.two.zones
    CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/test/test_delivery.py
    CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/test/test_utils.py

Added: CalendarServer/branches/users/cdaboo/ischedule-dkim/bin/calendarserver_dkimtool
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/bin/calendarserver_dkimtool	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/bin/calendarserver_dkimtool	2012-09-17 18:35:01 UTC (rev 9812)
@@ -0,0 +1,33 @@
+#!/usr/bin/env python
+##
+# Copyright (c) 2012 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+import sys
+
+#PYTHONPATH
+
+if __name__ == "__main__":
+
+    if "PYTHONPATH" in globals():
+        sys.path.insert(0, PYTHONPATH)
+    else:
+        try:
+            import _calendarserver_preamble
+        except ImportError:
+            sys.exc_clear()
+
+    from calendarserver.tools.dkimtool import main
+    main()


Property changes on: CalendarServer/branches/users/cdaboo/ischedule-dkim/bin/calendarserver_dkimtool
___________________________________________________________________
Added: svn:executable
   + *

Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/calendarserver/tap/caldav.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/calendarserver/tap/caldav.py	2012-09-14 21:34:53 UTC (rev 9811)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/calendarserver/tap/caldav.py	2012-09-17 18:35:01 UTC (rev 9812)
@@ -1758,7 +1758,7 @@
         the inherited implementation of startService because ProcessMonitor
         doesn't allow customization of subprocess environment).
         """
-        if name in self.protocols.has_key:
+        if name in self.protocols:
             return
         p = self.protocols[name] = DelayedStartupLoggingProtocol()
         p.service = self

Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/calendarserver/tap/util.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/calendarserver/tap/util.py	2012-09-14 21:34:53 UTC (rev 9811)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/calendarserver/tap/util.py	2012-09-17 18:35:01 UTC (rev 9812)
@@ -286,7 +286,7 @@
     directories = []
 
     directoryClass = namedClass(config.DirectoryService.type)
-    principalResourceClass       = DirectoryPrincipalProvisioningResource
+    principalResourceClass = DirectoryPrincipalProvisioningResource
 
     log.info("Configuring directory service of type: %s"
         % (config.DirectoryService.type,))
@@ -348,6 +348,7 @@
     return directory
 
 
+
 def getRootResource(config, newStore, resources=None):
     """
     Set up directory service and resource hierarchy based on config.
@@ -373,16 +374,16 @@
     #
     # Default resource classes
     #
-    rootResourceClass               = RootResource
-    calendarResourceClass           = DirectoryCalendarHomeProvisioningResource
-    iScheduleResourceClass          = IScheduleInboxResource
-    timezoneServiceResourceClass    = TimezoneServiceResource
+    rootResourceClass = RootResource
+    calendarResourceClass = DirectoryCalendarHomeProvisioningResource
+    iScheduleResourceClass = IScheduleInboxResource
+    timezoneServiceResourceClass = TimezoneServiceResource
     timezoneStdServiceResourceClass = TimezoneStdServiceResource
-    webCalendarResourceClass        = WebCalendarResource
-    webAdminResourceClass           = WebAdminResource
-    addressBookResourceClass        = DirectoryAddressBookHomeProvisioningResource
+    webCalendarResourceClass = WebCalendarResource
+    webAdminResourceClass = WebAdminResource
+    addressBookResourceClass = DirectoryAddressBookHomeProvisioningResource
     directoryBackedAddressBookResourceClass = DirectoryBackedAddressBookResource
-    apnSubscriptionResourceClass    = APNSubscriptionResource
+    apnSubscriptionResourceClass = APNSubscriptionResource
 
     directory = directoryFromConfig(config)
 
@@ -459,12 +460,10 @@
         if credFactory:
             credentialFactories.append(credFactory)
 
-
     #
     # Setup Resource hierarchy
     #
-    log.info("Setting up document root at: %s"
-                  % (config.DocumentRoot,))
+    log.info("Setting up document root at: %s" % (config.DocumentRoot,))
 
     principalCollection = directory.principalCollection
 
@@ -499,10 +498,10 @@
             # remove /directory from previous runs that may have created it
             try:
                 FilePath(directoryPath).remove()
-                log.info("Deleted: %s" %    directoryPath)
+                log.info("Deleted: %s" % directoryPath)
             except (OSError, IOError), e:
                 if e.errno != errno.ENOENT:
-                    log.error("Could not delete: %s : %r" %  (directoryPath, e,))
+                    log.error("Could not delete: %s : %r" % (directoryPath, e,))
 
     log.info("Setting up root resource: %r" % (rootResourceClass,))
 
@@ -511,7 +510,6 @@
         principalCollections=(principalCollection,),
     )
 
-
     root.putChild("principals", principalCollection)
     if config.EnableCalDAV:
         root.putChild("calendars", calendarCollection)
@@ -579,7 +577,7 @@
             root,
         )
         root.putChild("stdtimezones", timezoneStdService)
-        
+
         # TODO: we only want the master to do this
         if _reactor._started:
             _reactor.callLater(0, timezoneStdService.onStartup)
@@ -598,10 +596,11 @@
             newStore,
         )
         root.putChild("ischedule", ischedule)
-        
+
         # Do DomainKey resources
         DKIMUtils.validConfiguration(config)
         if config.Scheduling.iSchedule.DKIM.Enabled:
+            log.info("Setting up domainkey resource: %r" % (DomainKeyResource,))
             domain = config.Scheduling.iSchedule.DKIM.Domain if config.Scheduling.iSchedule.DKIM.Domain else config.ServerHostName
             dk = DomainKeyResource(
                 domain,
@@ -651,10 +650,9 @@
     log.info("Setting up Timezone Cache")
     TimezoneCache.create()
 
-
     log.info("Configuring authentication wrapper")
 
-    overrides = { }
+    overrides = {}
     if resources:
         for path, cls, args, schemes in resources:
 
@@ -694,6 +692,7 @@
     return logWrapper
 
 
+
 def getDBPool(config):
     """
     Inspect configuration to determine what database connection pool
@@ -770,8 +769,6 @@
 
 
 
-
-
 class FakeRequest(object):
 
     def __init__(self, rootResource, method, path, uri='/'):
@@ -783,6 +780,7 @@
         self._urlsByResource = {}
         self.headers = Headers()
 
+
     @inlineCallbacks
     def _getChild(self, resource, segments):
         if not segments:
@@ -791,6 +789,7 @@
         child, remaining = (yield resource.locateChild(self, segments))
         returnValue((yield self._getChild(child, remaining)))
 
+
     @inlineCallbacks
     def locateResource(self, url):
         url = url.strip("/")
@@ -800,6 +799,7 @@
             self._rememberResource(resource, url)
         returnValue(resource)
 
+
     @inlineCallbacks
     def locateChildResource(self, parent, childName):
         if parent is None or childName is None:
@@ -814,17 +814,20 @@
             self._rememberResource(resource, url)
         returnValue(resource)
 
+
     def _rememberResource(self, resource, url):
         self._resourcesByURL[url] = resource
         self._urlsByResource[resource] = url
         return resource
 
+
     def _forgetResource(self, resource, url):
-        if self._resourcesByURL.has_key(url):
+        if url in self._resourcesByURL:
             del self._resourcesByURL[url]
-        if self._urlsByResource.has_key(resource):
+        if resource in self._urlsByResource:
             del self._urlsByResource[resource]
 
+
     def urlForResource(self, resource):
         url = self._urlsByResource.get(resource, None)
         if url is None:
@@ -833,6 +836,6 @@
             raise NoURLForResourceError(resource)
         return url
 
-    def addResponseFilter(*args, **kwds):
+
+    def addResponseFilter(self, *args, **kwds):
         pass
-

Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/addressmapping.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/addressmapping.py	2012-09-14 21:34:53 UTC (rev 9811)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/addressmapping.py	2012-09-17 18:35:01 UTC (rev 9812)
@@ -1,5 +1,5 @@
 ##
-# Copyright (c) 2005-2010 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2012 Apple Inc. All rights reserved.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -81,7 +81,8 @@
             if config.Scheduling[DeliveryService.serviceType_imip]["Enabled"]:
                 serviceTypes += (ScheduleViaIMip,)
             for service in serviceTypes:
-                if service.matchCalendarUserAddress(cuaddr):
+                matched = (yield service.matchCalendarUserAddress(cuaddr))
+                if matched:
                     yield self.cache.set(str(cuaddr), service.serviceType())
                     returnValue(service.serviceType())
 

Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/caldav/delivery.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/caldav/delivery.py	2012-09-14 21:34:53 UTC (rev 9811)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/caldav/delivery.py	2012-09-17 18:35:01 UTC (rev 9812)
@@ -19,7 +19,7 @@
 from twext.python.log import Logger
 from twext.web2.dav.http import ErrorResponse
 
-from twisted.internet.defer import inlineCallbacks, returnValue
+from twisted.internet.defer import inlineCallbacks, returnValue, succeed
 from twisted.python.failure import Failure
 from twext.web2 import responsecode
 from txdav.xml import element as davxml
@@ -75,17 +75,17 @@
             domain = config.Scheduling[cls.serviceType()]["EmailDomain"]
             _ignore_account, addrDomain = addr.split("@")
             if addrDomain == domain:
-                return True
+                return succeed(True)
 
         elif (cuaddr.startswith("http://") or cuaddr.startswith("https://")) and config.Scheduling[cls.serviceType()]["HTTPDomain"]:
             splits = cuaddr.split(":")[0][2:].split("?")
             domain = config.Scheduling[cls.serviceType()]["HTTPDomain"]
             if splits[0].endswith(domain):
-                return True
+                return succeed(True)
 
         elif cuaddr.startswith("/"):
             # Assume relative HTTP URL - i.e. on this server
-            return True
+            return succeed(True)
 
         # Do default match
         return super(ScheduleViaCalDAV, cls).matchCalendarUserAddress(cuaddr)

Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/caldav/test/test_delivery.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/caldav/test/test_delivery.py	2012-09-14 21:34:53 UTC (rev 9811)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/caldav/test/test_delivery.py	2012-09-17 18:35:01 UTC (rev 9812)
@@ -1,5 +1,5 @@
 ##
-# Copyright (c) 2005-2009 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2012 Apple Inc. All rights reserved.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -17,17 +17,22 @@
 import twistedcaldav.test.util
 from twistedcaldav.scheduling.caldav.delivery import ScheduleViaCalDAV
 from twistedcaldav.config import config
+from twisted.internet.defer import inlineCallbacks
 
 class CalDAV (twistedcaldav.test.util.TestCase):
     """
     twistedcaldav.scheduling.caldav tests
     """
 
+    @inlineCallbacks
     def test_matchCalendarUserAddress(self):
         """
         Make sure we do an exact comparison on EmailDomain
         """
-        config.Scheduling[ScheduleViaCalDAV.serviceType()]["EmailDomain"] = "example.com"
-        self.assertTrue(ScheduleViaCalDAV.matchCalendarUserAddress("mailto:user at example.com"))
-        self.assertFalse(ScheduleViaCalDAV.matchCalendarUserAddress("mailto:user at foo.example.com"))
-        self.assertFalse(ScheduleViaCalDAV.matchCalendarUserAddress("mailto:user at xyzexample.com"))
+        self.patch(config.Scheduling[ScheduleViaCalDAV.serviceType()], "EmailDomain", "example.com")
+        result = yield ScheduleViaCalDAV.matchCalendarUserAddress("mailto:user at example.com")
+        self.assertTrue(result)
+        result = yield ScheduleViaCalDAV.matchCalendarUserAddress("mailto:user at foo.example.com")
+        self.assertFalse(result)
+        result = yield ScheduleViaCalDAV.matchCalendarUserAddress("mailto:user at xyzexample.com")
+        self.assertFalse(result)

Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/delivery.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/delivery.py	2012-09-14 21:34:53 UTC (rev 9811)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/delivery.py	2012-09-17 18:35:01 UTC (rev 9812)
@@ -1,5 +1,5 @@
 ##
-# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2012 Apple Inc. All rights reserved.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@
 from twext.python.log import Logger
 
 from twistedcaldav.config import config
+from twisted.internet.defer import succeed
 
 __all__ = [
     "DeliveryService",
@@ -52,17 +53,25 @@
 
     @classmethod
     def matchCalendarUserAddress(cls, cuaddr):
+        """
+        Determine whether the delivery service is able to handle the specified calendar user address.
 
+        @param cuaddr: calendar user address to test
+        @type cuaddr: C{str}
+
+        @return: L{Deferred} with result C{True} or C{False}
+        """
+
         cuaddr = cuaddr.lower()
         # Do the pattern match
         for pattern in config.Scheduling[cls.serviceType()]["AddressPatterns"]:
             try:
                 if re.match(pattern, cuaddr) is not None:
-                    return True
+                    return succeed(True)
             except re.error:
                 log.error("Invalid regular expression for Scheduling configuration '%s/LocalAddresses': %s" % (cls.serviceType(), pattern,))
 
-        return False
+        return succeed(False)
 
 
     def generateSchedulingResponses(self):

Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/imip/test/test_delivery.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/imip/test/test_delivery.py	2012-09-14 21:34:53 UTC (rev 9811)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/imip/test/test_delivery.py	2012-09-17 18:35:01 UTC (rev 9812)
@@ -1,5 +1,5 @@
 ##
-# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2012 Apple Inc. All rights reserved.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -64,8 +64,11 @@
         self.assertEqual(str(responses.responses[0].children[1]), iTIPRequestStatus.SERVICE_UNAVAILABLE)
 
 
+    @inlineCallbacks
     def test_matchCalendarUserAddress(self):
         # iMIP not sensitive to case:
         self.patch(config.Scheduling[ScheduleViaIMip.serviceType()], "AddressPatterns", ["mailto:.*"])
-        self.assertTrue(ScheduleViaIMip.matchCalendarUserAddress("mailto:user at xyzexample.com"))
-        self.assertTrue(ScheduleViaIMip.matchCalendarUserAddress("MAILTO:user at xyzexample.com"))
+        result = yield ScheduleViaIMip.matchCalendarUserAddress("mailto:user at xyzexample.com")
+        self.assertTrue(result)
+        result = ScheduleViaIMip.matchCalendarUserAddress("MAILTO:user at xyzexample.com")
+        self.assertTrue(result)

Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/delivery.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/delivery.py	2012-09-14 21:34:53 UTC (rev 9811)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/delivery.py	2012-09-17 18:35:01 UTC (rev 9812)
@@ -16,7 +16,8 @@
 
 from StringIO import StringIO
 
-from twisted.internet.defer import inlineCallbacks, DeferredList, succeed
+from twisted.internet.defer import inlineCallbacks, DeferredList, succeed, \
+    returnValue
 from twisted.internet.protocol import Factory
 
 from twisted.python.failure import Failure
@@ -47,6 +48,8 @@
 from twext.internet.gaiendpoint import GAIEndpoint
 from twistedcaldav.scheduling.ischedule.dkim import DKIMRequest, DKIMUtils
 from twistedcaldav.config import config
+from twistedcaldav.scheduling.ischedule.utils import lookupServerViaSRV
+from urlparse import urlsplit
 
 """
 Handles the sending of iSchedule scheduling messages. Used for both cross-domain scheduling,
@@ -63,20 +66,53 @@
 
 class ScheduleViaISchedule(DeliveryService):
 
+    domainServerMap = {}
+
     @classmethod
     def serviceType(cls):
         return DeliveryService.serviceType_ischedule
 
 
     @classmethod
+    @inlineCallbacks
     def matchCalendarUserAddress(cls, cuaddr):
 
         # TODO: here is where we would attempt service discovery based on the cuaddr.
 
+        # Only handle mailtos:
+        if cuaddr.lower().startswith("mailto:"):
+            _ignore_local, domain = cuaddr[7:].split("@", 1)
+            server = (yield cls.serverForDomain(domain))
+            returnValue(server is not None)
+
         # Do default match
-        return super(ScheduleViaISchedule, cls).matchCalendarUserAddress(cuaddr)
+        result = (yield super(ScheduleViaISchedule, cls).matchCalendarUserAddress(cuaddr))
+        returnValue(result)
 
 
+    @classmethod
+    @inlineCallbacks
+    def serverForDomain(cls, domain):
+        if domain not in cls.domainServerMap:
+
+            # First check built-in list of remote servers
+            servermgr = IScheduleServers()
+            server = servermgr.mapDomain(domain)
+            if server is not None:
+                cls.domainServerMap[domain] = server
+            else:
+                # Lookup domain
+                result = (yield lookupServerViaSRV(domain))
+                if result is None:
+                    cls.domainServerMap[domain] = None
+                else:
+                    # Create the iSchedule server record for this server
+                    cls.domainServerMap[domain] = IScheduleServerRecord(uri="https://%s:%s/.well-known/ischedule" % result)
+
+        returnValue(cls.domainServerMap[domain])
+
+
+    @inlineCallbacks
     def generateSchedulingResponses(self, refreshOnly=False):
         """
         Generate scheduling responses for remote recipients.
@@ -85,11 +121,10 @@
         # Group recipients by server so that we can do a single request with multiple recipients
         # to each different server.
         groups = {}
-        servermgr = IScheduleServers()
         for recipient in self.recipients:
             if isinstance(recipient, RemoteCalendarUser):
                 # Map the recipient's domain to a server
-                server = servermgr.mapDomain(recipient.domain)
+                server = (yield self.serverForDomain(recipient.domain))
             elif isinstance(recipient, PartitionedCalendarUser):
                 server = self._getServerForPartitionedUser(recipient)
             elif isinstance(recipient, OtherServerCalendarUser):
@@ -123,7 +158,7 @@
             groups.setdefault(server, []).append(recipient)
 
         if len(groups) == 0:
-            return
+            returnValue(None)
 
         # Now we process each server: let's use a DeferredList to aggregate all the Deferred's
         # we will generate for each request. That way we can have parallel requests in progress
@@ -133,7 +168,7 @@
             requestor = IScheduleRequest(self.scheduler, server, recipients, self.responses, refreshOnly)
             deferreds.append(requestor.doRequest())
 
-        return DeferredList(deferreds)
+        yield DeferredList(deferreds)
 
 
     def _getServerForPartitionedUser(self, recipient):
@@ -174,12 +209,10 @@
         self.recipients = recipients
         self.responses = responses
         self.refreshOnly = refreshOnly
+        self.headers = None
+        self.data = None
 
-        self.sign_headers = []
-        self._generateHeaders()
-        self._prepareData()
 
-
     @inlineCallbacks
     def doRequest(self):
 
@@ -191,40 +224,29 @@
                 self.scheduler.request.extendedLogItems["itip.ischedule"] = 0
             self.scheduler.request.extendedLogItems["itip.ischedule"] += 1
 
-            from twisted.internet import reactor
-            f = Factory()
-            f.protocol = HTTPClientProtocol
-            if self.server.ssl:
-                ep = GAIEndpoint(reactor, self.server.host, self.server.port,
-                                 _configuredClientContextFactory())
+            # Loop over at most 3 redirects
+            ssl, host, port, path = self.server.details()
+            for _ignore in xrange(3):
+                self._prepareRequest(host, port)
+                response = (yield self._processRequest(ssl, host, port, path))
+                if response.code not in (responsecode.MOVED_PERMANENTLY, responsecode.TEMPORARY_REDIRECT,):
+                    break
+                if response.code == responsecode.MOVED_PERMANENTLY:
+                    self.server.redirect(response.headers.getRawHeaders("location")[0])
+                    ssl, host, port, path = self.server.details()
+                else:
+                    scheme, netloc, path, _ignore_query, _ignore_fragment = urlsplit(response.headers.getRawHeaders("location")[0])
+                    ssl = scheme.lower() == "https"
+                    host = netloc.split(":")
+                    if ":" in netloc:
+                        host, port = netloc.split(":")
+                        port = int(port)
+                    else:
+                        host = netloc
+                        port = 443 if ssl else 80
             else:
-                ep = GAIEndpoint(reactor, self.server.host, self.server.port)
-            proto = (yield ep.connect(f))
+                raise ValueError("Too many redirects")
 
-            if config.Scheduling.iSchedule.DKIM.Enabled:
-                domain, selector, key_file, algorithm, useDNSKey, useHTTPKey, usePrivateExchangeKey, expire = DKIMUtils.getConfiguration(config)
-                request = DKIMRequest(
-                    "POST",
-                    self.server.path,
-                    self.headers,
-                    self.data,
-                    domain,
-                    selector,
-                    key_file,
-                    algorithm,
-                    self.sign_headers,
-                    useDNSKey,
-                    useHTTPKey,
-                    usePrivateExchangeKey,
-                    expire,
-                )
-                yield request.sign()
-            else:
-                request = ClientRequest("POST", self.server.path, self.headers, self.data)
-
-            yield self.logRequest("debug", "Sending server-to-server POST request:", request)
-            response = (yield proto.submitRequest(request))
-
             yield self.logResponse("debug", "Received server-to-server POST response:", response)
             xml = (yield davXMLFromStream(response.stream))
 
@@ -321,9 +343,25 @@
             return d
 
 
-    def _generateHeaders(self):
+    def _prepareRequest(self, host, port):
+        """
+        Setup the request for sending. We might need to do this several times
+        whilst following redirects.
+        """
+
+        self._prepareHeaders(host, port)
+        self._prepareData()
+
+
+    def _prepareHeaders(self, host, port):
+        """
+        Always generate a new set of headers because the Host may varying during redirects,
+        or we may need to dump DKIM added headers during a redirect.
+        """
+        self.sign_headers = []
+
         self.headers = Headers()
-        self.headers.setHeader("Host", utf8String(self.server.host + ":%s" % (self.server.port,)))
+        self.headers.setHeader("Host", utf8String(host + ":%s" % (port,)))
         self.sign_headers.append("Host")
 
         # The Originator must be the ORGANIZER (for a request) or ATTENDEE (for a reply)
@@ -359,17 +397,62 @@
 
 
     def _prepareData(self):
-        if self.server.unNormalizeAddresses and self.scheduler.method == "PUT":
-            normalizedCalendar = self.scheduler.calendar.duplicate()
-            normalizedCalendar.normalizeCalendarUserAddresses(
-                normalizationLookup,
-                self.scheduler.resource.principalForCalendarUserAddress,
-                toUUID=False)
+        """
+        Prepare data via normalization etc. Only need to do this once even when
+        redirects occur.
+        """
+
+        if self.data is None:
+            if self.server.unNormalizeAddresses and self.scheduler.method == "PUT":
+                normalizedCalendar = self.scheduler.calendar.duplicate()
+                normalizedCalendar.normalizeCalendarUserAddresses(
+                    normalizationLookup,
+                    self.scheduler.resource.principalForCalendarUserAddress,
+                    toUUID=False)
+            else:
+                normalizedCalendar = self.scheduler.calendar
+            self.data = str(normalizedCalendar)
+
+
+    @inlineCallbacks
+    def _processRequest(self, ssl, host, port, path):
+        from twisted.internet import reactor
+        f = Factory()
+        f.protocol = HTTPClientProtocol
+        if ssl:
+            ep = GAIEndpoint(reactor, host, port,
+                             _configuredClientContextFactory())
         else:
-            normalizedCalendar = self.scheduler.calendar
-        self.data = str(normalizedCalendar)
+            ep = GAIEndpoint(reactor, host, port)
+        proto = (yield ep.connect(f))
 
+        if config.Scheduling.iSchedule.DKIM.Enabled:
+            domain, selector, key_file, algorithm, useDNSKey, useHTTPKey, usePrivateExchangeKey, expire = DKIMUtils.getConfiguration(config)
+            request = DKIMRequest(
+                "POST",
+                path,
+                self.headers,
+                self.data,
+                domain,
+                selector,
+                key_file,
+                algorithm,
+                self.sign_headers,
+                useDNSKey,
+                useHTTPKey,
+                usePrivateExchangeKey,
+                expire,
+            )
+            yield request.sign()
+        else:
+            request = ClientRequest("POST", path, self.headers, self.data)
 
+        yield self.logRequest("debug", "Sending server-to-server POST request:", request)
+        response = (yield proto.submitRequest(request))
+
+        returnValue(response)
+
+
     def _parseResponse(self, xml):
 
         # Check for correct root element

Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/dkim.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/dkim.py	2012-09-14 21:34:53 UTC (rev 9811)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/dkim.py	2012-09-17 18:35:01 UTC (rev 9812)
@@ -25,6 +25,7 @@
 from twistedcaldav.client.geturl import getURL
 from twistedcaldav.config import ConfigurationError
 from twistedcaldav.simpleresource import SimpleResource, SimpleDataResource
+from twistedcaldav.scheduling.ischedule.utils import lookupDataViaTXT
 
 import base64
 import binascii
@@ -408,6 +409,15 @@
 
 
 
+class DKIMMissingError(Exception):
+    """
+    Used to indicate that the DKIM-Signature header is not present when
+    attempting verification.
+    """
+    pass
+
+
+
 class DKIMVerificationError(Exception):
     """
     Used to indicate a DKIM verification error.
@@ -509,7 +519,7 @@
         if dkim is None:
             msg = "No DKIM-Signature header present in the request"
             log.debug("DKIM: " + msg)
-            raise DKIMVerificationError(msg)
+            raise DKIMMissingError(msg)
         if len(dkim) != 1:
             # TODO: This might need to be changed if we ever support forwarding of iSchedule messages - the forwarder
             # might also sign the message and add its own header
@@ -749,11 +759,15 @@
         return "%s._domainkey.%s" % (self.dkim_tags["s"], self.dkim_tags["d"],)
 
 
+    @inlineCallbacks
     def _lookupKeys(self):
         """
         Do the key lookup using the actual lookup method.
         """
-        raise NotImplementedError
+        log.debug("DKIM: TXT lookup: %s" % (self._getSelectorKey(),))
+        data = (yield lookupDataViaTXT(self._getSelectorKey()))
+        log.debug("DKIM: TXT lookup results: %s\n%s" % (self._getSelectorKey(), "\n".join(data),))
+        returnValue(tuple([DKIMUtils.extractTags(line) for line in data]))
 
 
 
@@ -777,6 +791,7 @@
         Do the key lookup using the actual lookup method.
         """
 
+        log.debug("DKIM: HTTP/.well-known lookup: %s" % (self._getSelectorKey(),))
         response = (yield getURL(self._getSelectorKey()))
         if response is None or response.code / 100 != 2:
             log.debug("DKIM: Failed http/well-known lookup: %s %s" % (self._getSelectorKey(), response,))
@@ -789,6 +804,7 @@
             log.debug("DKIM: Failed http/well-known lookup: wrong content-type returned %s %s" % (self._getSelectorKey(), ct,))
             returnValue(())
 
+        log.debug("DKIM: HTTP/.well-known lookup results: %s\n%s" % (self._getSelectorKey(), response.data,))
         returnValue(tuple([DKIMUtils.extractTags(line) for line in response.data.splitlines()]))
 
 
@@ -821,6 +837,7 @@
             return succeed(())
 
         # Now read the data
+        log.debug("DKIM: Private exchange lookup: %s" % (keyfile,))
         try:
             with open(keyfile) as f:
                 keys = f.read()
@@ -828,6 +845,7 @@
             log.debug("DKIM: Failed private-exchange lookup: could not read %s %s" % (keyfile, e,))
             return succeed(())
 
+        log.debug("DKIM: Private exchange lookup results: %s\n%s" % (keyfile, keys))
         return succeed(tuple([DKIMUtils.extractTags(line) for line in keys.splitlines()]))
 
 
@@ -871,7 +889,7 @@
 
         # Make the TXT record
         key_data = "".join(key_data.strip().splitlines()[1:-1])
-        txt_data = "v=DKIM1; s=ischedule; p=%s" % (key_data,)
+        txt_data = "v=DKIM1; s=ischedule; p=%s\n" % (key_data,)
 
         # Setup resource hierarchy
         domainResource = SimpleResource(principalCollections=None, isdir=True, defaultACL=SimpleResource.allReadACL)

Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/remoteservers.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/remoteservers.py	2012-09-14 21:34:53 UTC (rev 9811)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/remoteservers.py	2012-09-17 18:35:01 UTC (rev 9812)
@@ -1,5 +1,5 @@
 ##
-# Copyright (c) 2006-2010 Apple Inc. All rights reserved.
+# Copyright (c) 2006-2012 Apple Inc. All rights reserved.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -19,7 +19,6 @@
 from twext.python.log import Logger
 
 from twistedcaldav.config import config, fullServerPath
-from twistedcaldav.scheduling.delivery import DeliveryService
 from twistedcaldav import xmlutil
 
 """
@@ -48,20 +47,24 @@
 
 
     def _loadConfig(self):
-        if IScheduleServers._servers is None:
-            IScheduleServers._xmlFile = FilePath(
-                fullServerPath(
-                    config.ConfigRoot,
-                    config.Scheduling.iSchedule.RemoteServers,
+        if config.Scheduling.iSchedule.RemoteServers:
+            if IScheduleServers._servers is None:
+                IScheduleServers._xmlFile = FilePath(
+                    fullServerPath(
+                        config.ConfigRoot,
+                        config.Scheduling.iSchedule.RemoteServers,
+                    )
                 )
-            )
-        IScheduleServers._xmlFile.restat()
-        fileInfo = (IScheduleServers._xmlFile.getmtime(), IScheduleServers._xmlFile.getsize())
-        if fileInfo != IScheduleServers._fileInfo:
-            parser = IScheduleServersParser(IScheduleServers._xmlFile)
-            IScheduleServers._servers = parser.servers
-            self._mapDomains()
-            IScheduleServers._fileInfo = fileInfo
+            IScheduleServers._xmlFile.restat()
+            fileInfo = (IScheduleServers._xmlFile.getmtime(), IScheduleServers._xmlFile.getsize())
+            if fileInfo != IScheduleServers._fileInfo:
+                parser = IScheduleServersParser(IScheduleServers._xmlFile)
+                IScheduleServers._servers = parser.servers
+                self._mapDomains()
+                IScheduleServers._fileInfo = fileInfo
+        else:
+            IScheduleServers._servers = ()
+            IScheduleServers._domainMap = {}
 
 
     def _mapDomains(self):
@@ -147,6 +150,18 @@
             self._parseDetails()
 
 
+    def details(self):
+        return (self.ssl, self.host, self.port, self.path,)
+
+
+    def redirect(self, location):
+        """
+        Permanent redirect for the lifetime of this record.
+        """
+        self.uri = location
+        self._parseDetails()
+
+
     def parseXML(self, node):
         for child in node.getchildren():
             if child.tag == ELEMENT_URI:

Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/scheduler.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/scheduler.py	2012-09-14 21:34:53 UTC (rev 9811)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/scheduler.py	2012-09-17 18:35:01 UTC (rev 9812)
@@ -34,7 +34,7 @@
 import urlparse
 from twistedcaldav.config import config
 from twistedcaldav.scheduling.ischedule.dkim import DKIMVerifier, \
-    DKIMVerificationError
+    DKIMVerificationError, DKIMMissingError
 from twext.web2.http_headers import MimeType
 from twistedcaldav.scheduling.ischedule.xml import ischedule_namespace
 from txdav.xml.base import WebDAVUnknownElement
@@ -118,11 +118,21 @@
         """
         Carry out iSchedule specific processing.
         """
+
+        self.verified = False
         if config.Scheduling.iSchedule.DKIM.Enabled:
             verifier = DKIMVerifier(self.request, protocol_debug=config.Scheduling.iSchedule.DKIM.ProtocolDebug)
             try:
                 yield verifier.verify()
+                self.verified = True
+
+            except DKIMMissingError:
+                # Carry on processing, but we will do extra checks on the originator as we would
+                # when DKIM is not enabled, so that any local policy via remoteservers.xml can be used.
+                pass
+
             except DKIMVerificationError, e:
+                # If DKIM is enabled and there was a DKIM header present, then fail
                 msg = "Failed to verify DKIM signature"
                 _debug_msg = str(e)
                 log.debug("%s:%s" % (msg, _debug_msg,))
@@ -201,6 +211,10 @@
         Check the validity of the iSchedule host.
         """
 
+        # Check for DKIM verification first and treat as valid
+        if self.verified:
+            return
+
         # We will only accept originator in known domains.
         servermgr = IScheduleServers()
         server = servermgr.mapDomain(self.originator.domain)

Added: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/test/data/db.example.com
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/test/data/db.example.com	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/test/data/db.example.com	2012-09-17 18:35:01 UTC (rev 9812)
@@ -0,0 +1,15 @@
+example.com.		  10800 IN SOA	ns.example.com. 	admin.example.com. (
+                                                        2012090810 ; serial
+                                                        3600       ; refresh (1 hour)
+                                                        900        ; retry (15 minutes)
+                                                        1209600    ; expire (2 weeks)
+                                                        86400      ; minimum (1 day)
+							)
+									10800 IN NS		ns.example.com.
+									10800 IN A		127.0.0.1
+ns.example.com.						10800 IN A		127.0.0.1
+
+_caldavs._tcp.example.com.			10800 IN SRV	0	0	8443	example.com.
+_ischedules._tcp.example.com.		10800 IN SRV	0	0	8443	example.com.
+_ischedule._domainkey.example.com.	10800 IN TXT	"v=DKIM1; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDjUfDqd8ICAL0dyq2KdjKN6LS8O/Y4yMxOxgATqtSIMi7baKXEs1w5Wj9efOC2nU+aqyhP2/J6AzfFJfSB+GV5gcIT+LAC4btJKPGjPUyXcQFJV4a73y0jIgCTBzWxdaP6qD9P9rzYlvMPcdrrKiKoAOtI3JZqAAdZudOmGlc4QQIDAQAB"
+_revoked._domainkey.example.com.	10800 IN TXT	"v=DKIM1; p="

Added: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/test/data/db.two.zones
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/test/data/db.two.zones	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/test/data/db.two.zones	2012-09-17 18:35:01 UTC (rev 9812)
@@ -0,0 +1,31 @@
+example.com.		  10800 IN SOA	ns.example.com. 	admin.example.com. (
+                                                        2012090810 ; serial
+                                                        3600       ; refresh (1 hour)
+                                                        900        ; retry (15 minutes)
+                                                        1209600    ; expire (2 weeks)
+                                                        86400      ; minimum (1 day)
+							)
+									10800 IN NS		ns.example.com.
+									10800 IN A		127.0.0.1
+ns.example.com.						10800 IN A		127.0.0.1
+
+_caldavs._tcp.example.com.			10800 IN SRV	0	0	8443	example.com.
+_ischedules._tcp.example.com.		10800 IN SRV	0	0	8443	example.com.
+_ischedule._domainkey.example.com.	10800 IN TXT	"v=DKIM1; p="
+
+
+
+example.org.		  10800 IN SOA	ns.example.org. 	admin.example.org. (
+                                                        2012090810 ; serial
+                                                        3600       ; refresh (1 hour)
+                                                        900        ; retry (15 minutes)
+                                                        1209600    ; expire (2 weeks)
+                                                        86400      ; minimum (1 day)
+							)
+									10800 IN NS		ns.example.org.
+									10800 IN A		127.0.0.1
+ns.example.org.						10800 IN A		127.0.0.1
+
+_caldavs._tcp.example.org.			10800 IN SRV	0	0	8543	example.org.
+_ischedules._tcp.example.org.		10800 IN SRV	0	0	8543	example.org.
+_ischedule2._domainkey.example.org.	10800 IN TXT	"v=DKIM1; s=ischedule; p="

Added: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/test/test_delivery.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/test/test_delivery.py	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/test/test_delivery.py	2012-09-17 18:35:01 UTC (rev 9812)
@@ -0,0 +1,62 @@
+##
+# Copyright (c) 2005-2012 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+import twistedcaldav.test.util
+from twisted.internet.defer import inlineCallbacks
+from twisted.python.modules import getModule
+from twistedcaldav.config import config
+from twistedcaldav.scheduling.ischedule import utils
+from twisted.names import client
+from twistedcaldav.scheduling.ischedule.delivery import ScheduleViaISchedule
+
+class CalDAV (twistedcaldav.test.util.TestCase):
+    """
+    twistedcaldav.scheduling.caldav tests
+    """
+
+    def tearDown(self):
+        """
+        By setting the resolver to None, it will be recreated next time a name
+        lookup is done.
+        """
+        client.theResolver = None
+        utils.DebugResolver = None
+
+
+    @inlineCallbacks
+    def test_matchCalendarUserAddress(self):
+        """
+        Make sure we do an exact comparison on EmailDomain
+        """
+
+        self.patch(config.Scheduling.iSchedule, "RemoteServers", "")
+
+        # Only mailtos:
+        result = yield ScheduleViaISchedule.matchCalendarUserAddress("http://example.com/principal/user")
+        self.assertFalse(result)
+
+        # Need to setup a fake resolver
+        module = getModule(__name__)
+        dataPath = module.filePath.sibling("data")
+        bindPath = dataPath.child("db.example.com")
+        self.patch(config.Scheduling.iSchedule, "DNSDebug", bindPath.path)
+        utils.DebugResolver = None
+        utils._initResolver()
+
+        result = yield ScheduleViaISchedule.matchCalendarUserAddress("mailto:user at example.com")
+        self.assertTrue(result)
+        result = yield ScheduleViaISchedule.matchCalendarUserAddress("mailto:user at example.org")
+        self.assertFalse(result)

Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/test/test_dkim.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/test/test_dkim.py	2012-09-14 21:34:53 UTC (rev 9811)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/test/test_dkim.py	2012-09-17 18:35:01 UTC (rev 9812)
@@ -26,6 +26,10 @@
 import time
 import twistedcaldav.test.util
 import os
+from twisted.names import client
+from twistedcaldav.scheduling.ischedule import utils
+from twisted.python.modules import getModule
+from twistedcaldav.config import config
 
 class TestDKIMBase (twistedcaldav.test.util.TestCase):
     """
@@ -636,6 +640,15 @@
     L{PublicKeyLookup} support tests.
     """
 
+    def tearDown(self):
+        """
+        By setting the resolver to None, it will be recreated next time a name
+        lookup is done.
+        """
+        client.theResolver = None
+        utils.DebugResolver = None
+
+
     def test_selector_key(self):
 
         for lookup, d, result in (
@@ -767,6 +780,30 @@
 
 
     @inlineCallbacks
+    def test_TXT_key(self):
+
+        # Need to setup a fake resolver
+        module = getModule(__name__)
+        dataPath = module.filePath.sibling("data")
+        bindPath = dataPath.child("db.example.com")
+        self.patch(config.Scheduling.iSchedule, "DNSDebug", bindPath.path)
+        utils.DebugResolver = None
+        utils._initResolver()
+
+        for d, s, result in (
+            ("example.com", "_ischedule", True),
+            ("example.com", "_revoked", False),
+            ("example.com", "dkim", False),
+            ("calendar.example.com", "_ischedule", False),
+            ("example.org", "_ischedule", False),
+        ):
+            dkim = "v=1; d=%s; s = %s; t = 1234; a=rsa-sha1; q=dns/txt ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b=" % (d, s,)
+            tester = PublicKeyLookup_DNSTXT(DKIMUtils.extractTags(dkim))
+            pkey = yield tester.getPublicKey(False)
+            self.assertEqual(pkey is not None, result)
+
+
+    @inlineCallbacks
     def test_private_exchange(self):
 
         keydir = self.mktemp()

Added: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/test/test_utils.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/test/test_utils.py	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/test/test_utils.py	2012-09-17 18:35:01 UTC (rev 9812)
@@ -0,0 +1,116 @@
+##
+# Copyright (c) 2012 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from twisted.internet.defer import inlineCallbacks
+from twistedcaldav.config import config
+from twistedcaldav.scheduling.ischedule import utils
+from twistedcaldav.test.util import TestCase
+from twisted.python.modules import getModule
+from twisted.names.authority import BindAuthority
+from twisted.names import client
+from twisted.names.test.test_client import FakeResolver
+
+class LookupService (TestCase):
+
+
+    def setUp(self):
+        """
+        Replace the resolver with a FakeResolver
+        """
+        client.theResolver = FakeResolver()
+
+
+    def tearDown(self):
+        """
+        By setting the resolver to None, it will be recreated next time a name
+        lookup is done.
+        """
+        client.theResolver = None
+        utils.DebugResolver = None
+
+
+    def test_initResolver(self):
+        """
+        Test L{lookupServerViaSRV} with a local Bind find
+        """
+
+        # Default resolver
+        utils.DebugResolver = None
+        utils._initResolver()
+        self.assertNotEqual(utils.DebugResolver, None)
+        self.assertFalse(isinstance(utils.DebugResolver, BindAuthority))
+
+        # Patch config for Bind resolver
+        for zonefile in ("db.example.com", "db.two.zones",):
+            module = getModule(__name__)
+            dataPath = module.filePath.sibling("data")
+            bindPath = dataPath.child(zonefile)
+            self.patch(config.Scheduling.iSchedule, "DNSDebug", bindPath.path)
+            utils.DebugResolver = None
+            utils._initResolver()
+            self.assertNotEqual(utils.DebugResolver, None)
+            self.assertTrue(isinstance(utils.DebugResolver, BindAuthority))
+
+
+    @inlineCallbacks
+    def test_lookupServerViaSRV(self):
+        """
+        Test L{lookupServerViaSRV} with a local Bind find
+        """
+
+        # Patch config
+        for zonefile, checks in (
+            ("db.example.com", (("example.com", "example.com", 8443,),),),
+            ("db.two.zones", (
+                ("example.com", "example.com", 8443,),
+                ("example.org", "example.org", 8543,),
+            ),),
+        ):
+            module = getModule(__name__)
+            dataPath = module.filePath.sibling("data")
+            bindPath = dataPath.child(zonefile)
+            self.patch(config.Scheduling.iSchedule, "DNSDebug", bindPath.path)
+            utils.DebugResolver = None
+
+            for domain, result_host, result_port in checks:
+                host, port = (yield utils.lookupServerViaSRV(domain))
+                self.assertEqual(host, result_host)
+                self.assertEqual(port, result_port)
+
+
+    @inlineCallbacks
+    def test_lookupDataViaTXT(self):
+        """
+        Test L{lookupDataViaTXT} with a local Bind find
+        """
+
+        # Patch config
+        for zonefile, checks in (
+            ("db.example.com", (("example.com", "_ischedule._domainkey", "v=DKIM1; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDjUfDqd8ICAL0dyq2KdjKN6LS8O/Y4yMxOxgATqtSIMi7baKXEs1w5Wj9efOC2nU+aqyhP2/J6AzfFJfSB+GV5gcIT+LAC4btJKPGjPUyXcQFJV4a73y0jIgCTBzWxdaP6qD9P9rzYlvMPcdrrKiKoAOtI3JZqAAdZudOmGlc4QQIDAQAB"),),),
+            ("db.two.zones", (
+                ("example.com", "_ischedule._domainkey", "v=DKIM1; p="),
+                ("example.org", "_ischedule2._domainkey", "v=DKIM1; s=ischedule; p="),
+            )),
+        ):
+            module = getModule(__name__)
+            dataPath = module.filePath.sibling("data")
+            bindPath = dataPath.child(zonefile)
+            self.patch(config.Scheduling.iSchedule, "DNSDebug", bindPath.path)
+            utils.DebugResolver = None
+
+            for domain, prefix, result in checks:
+                texts = (yield utils.lookupDataViaTXT(domain, prefix))
+                self.assertEqual(texts, [result])

Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/utils.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/utils.py	2012-09-14 21:34:53 UTC (rev 9811)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/utils.py	2012-09-17 18:35:01 UTC (rev 9812)
@@ -14,9 +14,23 @@
 # limitations under the License.
 ##
 
+from twext.python.log import Logger
+
+from twisted.internet.defer import inlineCallbacks, returnValue
+from twisted.names import dns
+from twisted.names.authority import BindAuthority
+from twisted.names.client import getResolver
+from twisted.names.error import DomainError, AuthoritativeDomainError
+
+from twistedcaldav.config import config
+
 import socket
 
+log = Logger()
 
+DebugResolver = None
+
+
 def getIPsFromHost(host):
     """
     Map a hostname to an IPv4 or IPv6 address.
@@ -33,3 +47,136 @@
             ips.add(sockaddr[0])
 
     return ips
+
+
+
+ at inlineCallbacks
+def lookupServerViaSRV(domain, service="_ischedules"):
+
+    _initResolver()
+
+    lookup = "%s._tcp.%s" % (service, domain,)
+    log.debug("DNS SRV: lookup: %s" % (lookup,))
+    try:
+        answers = (yield DebugResolver.lookupService(lookup))
+    except (DomainError, AuthoritativeDomainError), e:
+        log.debug("DNS SRV: lookup failed: %s" % (e,))
+        returnValue(None)
+
+    if len(answers) == 1 and answers[0].type == dns.SRV \
+                         and answers[0].payload \
+                         and answers[0].payload.target == dns.Name('.'):
+        # decidedly not available
+        log.debug("DNS SRV: disabled: %s" % (lookup,))
+        returnValue(None)
+
+    servers = []
+    for a in answers:
+
+        if a.type != dns.SRV or not a.payload:
+            continue
+
+        servers.append((a.payload.priority, a.payload.weight, str(a.payload.target), a.payload.port))
+
+    log.debug("DNS SRV: lookup results: %s\n%s" % (lookup, servers,))
+
+
+    def _serverCmp(a, b):
+        if a[0] != b[0]:
+            return cmp(a[0], b[0])
+        else:
+            return cmp(a[1], b[1])
+
+    servers.sort(_serverCmp)
+    minPriority = servers[0][0]
+
+    weightIndex = zip(xrange(len(servers)), [x[1] for x in servers if x[0] == minPriority])
+    weightSum = reduce(lambda x, y: (None, x[1] + y[1]), weightIndex, (None, 0))[1]
+
+    for index, weight in weightIndex:
+        weightSum -= weight
+        if weightSum <= 0:
+            chosen = servers[index]
+            _ignore_p, _ignore_w, host, port = chosen
+            host = host.rstrip(".")
+            break
+    else:
+        log.debug("DNS SRV: unable to determine best record to use: %s" % (lookup,))
+        returnValue(None)
+
+    log.debug("DNS SRV: lookup chosen service: %s %s %s" % (lookup, host, port,))
+    returnValue((host, port,))
+
+
+
+ at inlineCallbacks
+def lookupDataViaTXT(domain, prefix=""):
+
+    _initResolver()
+
+    lookup = "%s.%s" % (prefix, domain,) if prefix else domain
+    log.debug("DNS TXT: lookup: %s" % (lookup,))
+    try:
+        answers = (yield DebugResolver.lookupText(lookup))
+    except (DomainError, AuthoritativeDomainError), e:
+        log.debug("DNS TXT: lookup failed: %s" % (e,))
+        answers = ()
+
+    results = []
+    for a in answers:
+
+        if a.type != dns.TXT or not a.payload:
+            continue
+
+        results.append("".join(a.payload.data))
+
+    log.debug("DNS TXT: lookup results: %s\n%s" % (lookup, "\n".join(results),))
+    returnValue(results)
+
+
+
+class FakeBindAuthority(BindAuthority):
+
+    @inlineCallbacks
+    def _lookup(self, name, cls, type, timeout=None):
+        log.debug("DNS FakeBindAuthority: lookup: %s %s %s" % (name, cls, type,))
+        result = yield BindAuthority._lookup(self, name, cls, type, timeout)
+        log.debug("DNS FakeBindAuthority: lookup results: %s %s %s\n%s" % (name, cls, type, result[0]))
+        returnValue(result[0])
+
+
+    def stripComments(self, lines):
+        """
+        Work around a bug in the base implementation that causes parsing of TXT RRs with
+        a ; in the RDATA to fail because the ; is treated as the start of a comment. Here
+        we simply ignore all comments.
+        """
+        return [
+            (a.find(';') == -1 or "TXT" in a) and a or a[:a.find(';')] for a in [
+                b.strip() for b in lines
+            ]
+        ]
+
+
+    def parseLines(self, lines):
+        """
+        Work around a bug in the base implementation that causes parsing of TXT RRs with
+        spaces in the RDATA to be broken into multiple fragments and for quotes around the
+        data to not be removed.
+        """
+        for line in lines:
+            if line[3] == "TXT":
+                line[4] = " ".join(line[4:])[1:-1]
+                del line[5:]
+
+        BindAuthority.parseLines(self, lines)
+
+
+
+def _initResolver():
+    global DebugResolver
+    if DebugResolver is None:
+        if config.Scheduling.iSchedule.DNSDebug:
+            DebugResolver = FakeBindAuthority(config.Scheduling.iSchedule.DNSDebug)
+        else:
+            DebugResolver = getResolver()

Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/stdconfig.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/stdconfig.py	2012-09-14 21:34:53 UTC (rev 9811)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/stdconfig.py	2012-09-17 18:35:01 UTC (rev 9812)
@@ -606,6 +606,7 @@
             "Enabled"          : False, # iSchedule protocol
             "AddressPatterns"  : [], # Reg-ex patterns to match iSchedule-able calendar user addresses
             "RemoteServers"    : "remoteservers.xml", # iSchedule server configurations
+            "DNSDebug"         : "", # File where a fake Bind zone exists for creating fake DNS results
             "DKIM"             : {      # DKIM options
                 "Enabled"               : True, # DKIM signing/verification enabled
                 "Domain"                : "", # Domain for DKIM (defaults to ServerHostName)
@@ -1036,6 +1037,7 @@
     ("DataRoot", "AttachmentsRoot"),
     ("DataRoot", ("TimezoneService", "BasePath",)),
     ("ConfigRoot", "SudoersFile"),
+    ("ConfigRoot", ("Scheduling", "iSchedule", "DNSDebug",)),
     ("ConfigRoot", ("Scheduling", "iSchedule", "DKIM", "PrivateKeyFile",)),
     ("ConfigRoot", ("Scheduling", "iSchedule", "DKIM", "PublicKeyFile",)),
     ("ConfigRoot", ("Scheduling", "iSchedule", "DKIM", "PrivateExchanges",)),
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20120917/26681a8c/attachment-0001.html>


More information about the calendarserver-changes mailing list