[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